Я совсем новичок в программировании на CUDA и хочу убедиться, что понимаю некоторые основные принципы, связанные с памятью, поскольку я немного запутался в некоторых своих мыслях.

Я работаю над симуляцией, используя миллиарды одноразовых случайных чисел в диапазоне от 0 до 50.

После того, как cuRand заполняет огромный массив случайными значениями от 0.0 до 1.0, я запускаю ядро, которое конвертирует все эти данные в желаемый диапазон целых чисел. Из того, что я узнал, у меня возникло ощущение, что хранение 5 таких значений в одном unsigned int с использованием всего 6 бит будет лучше из-за очень низкой пропускной способности глобальной памяти. Поэтому я так и сделал.

Сейчас мне нужно хранить около 20000 значений "да/нет", доступ к которым будет осуществляться случайным образом, скажем, с одинаковой вероятностью, основанной на случайных значениях, поступающих в симуляцию.

Сначала я подумал об общей памяти. Использование одного бита выглядело отлично, пока я не понял, что чем больше информации в одном банке, тем больше будет коллизий. Поэтому решением кажется использование unsigned short (2Byte->40KB всего) для представления одной информации да/нет, используя максимум доступной памяти и таким образом минимизируя вероятность чтения одного и того же банка разными потоками.

Еще одна мысль возникла при использовании постоянной памяти и кэша L1. Здесь, насколько я понял, подход будет прямо противоположным по сравнению с общей памятью. Чтение одного и того же места несколькими потоками теперь желательно, поэтому размещение 32 значений в одном банке 4B теперь оптимально.

Так что на основе общей вероятности я должен выбрать между общей памятью и кэшем, причем общее расположение, вероятно, лучше, поскольку при таком количестве значений "да/нет" и всего лишь десятках или сотнях потоков на блок не будет много столкновений банков.

Но прав ли я в своем общем понимании проблемы? Действительно ли процессоры настолько быстры по сравнению с памятью, что оптимизация доступа к памяти имеет решающее значение, а мысли о дополнительных инструкциях при извлечении данных с помощью операций типа <<, >>, |=, &= не важны?

Извините за стену текста, есть миллион способов заставить симуляцию работать, но только несколько способов сделать это правильно. Я просто не хочу повторять глупые ошибки снова и снова только потому, что я что-то плохо понимаю.

hoacin

Ответов: 1

Ответы (1)

Первыми двумя приоритетами оптимизации для любого программиста CUDA являются:

  1. Запустите достаточное количество потоков (т.е. проявите достаточный параллелизм), чтобы у машины было достаточно работы, чтобы скрыть задержку

  2. .
  3. Эффективно использовать память.

Во втором пункте выше будут приведены различные рекомендации в зависимости от типов памяти GPU, которые вы используете, например:

  1. Стремитесь к коалиционному доступу к глобальной памяти
  2. Используйте преимущества кэшей и общей памяти для оптимизации повторного использования данных
  3. Для общей памяти стремитесь к неконфликтному доступу
  4. Для постоянной памяти стремитесь к равномерному доступу (все потоки в пределах искривления читают из одного и того же места, в данном цикле доступа)
  5. .
  6. Подумайте об использовании кэширования текстур и других специальных возможностей GPU.

Ваш вопрос сосредоточен на памяти:

Действительно ли процессоры настолько быстры по сравнению с памятью, что оптимизация доступа к памяти имеет решающее значение, а мысли о дополнительных инструкциях при извлечении данных операциями типа <<, >>, |=, &= не важны?

Оптимизация доступа к памяти обычно очень важна для того, чтобы код быстро работал на GPU. Большинство приведенных мной выше рекомендаций/руководств не направлены на то, чтобы упаковывать несколько элементов данных в int (или какое-то другое количество слов), однако это не исключено для кода, ограниченного памятью. Многие программы на GPU в конечном итоге оказываются ограниченными памятью (т. е. в конечном итоге ограничителем производительности является использование памяти, а не использование вычислительных ресурсов GPU).

Примером области, где разработчики рассматривают возможность упаковки нескольких элементов данных в одно слово, является область глубокого обучения, в частности конволюционные нейронные сети на графических процессорах. Разработчики обнаружили, что в некоторых случаях они могут ускорить работу своих обучающих кодов за счет:

  1. сохранить набор данных в виде упакованных половин (16-битных плавающих значений) в памяти GPU
  2. загрузить набор данных в упакованном виде
  3. распаковать (и преобразовать) каждый упакованный набор из 2 half величин в 2 float величины
  4. выполнить вычисления на плоских величинах
  5. преобразовать результаты float в half и упаковать их
  6. сохраните упакованные количества
  7. повторите процесс для следующего цикла обучения

(См. этот ответ для дополнительной информации о типе данных half)

.

Суть в том, что эффективное использование памяти является одним из главных приоритетов для обеспечения быстрого выполнения кода на GPU, и это может включать использование упакованных объемов в некоторых случаях.

Вероятно, для вашего случая это целесообразно, но для определения влияния на ваш код вам придется провести сравнительный анализ.

Некоторые другие комментарии, прочитав ваш вопрос:

Здесь, из того, что я узнал, подход будет прямо противоположным по сравнению с общей памятью.

  1. В некотором смысле, использование общей памяти и постоянной памяти "противоположны" с точки зрения шаблона доступа. Эффективное использование постоянной памяти подразумевает равномерный доступ, как уже говорилось. В случае равномерного доступа разделяемая память также должна работать довольно хорошо, поскольку она имеет функцию широковещания. Вы правы, что беспокоитесь о конфликтах банков в общей памяти, но общая память на новых GPU имеет функцию трансляции, которая сводит на нет проблему конфликта банков когда доступ осуществляется из одного и того же фактического места, а не только из одного и того же банка. Эти нюансы рассматриваются во многих других вопросах об общей памяти на SO, поэтому здесь я не буду углубляться в эту тему.

  2. Поскольку вы не знаете, что делать с общей памятью на SO.

  3. Поскольку вы упаковываете 5 целочисленных величин (0...50) в один int, вы также можете рассмотреть возможность (возможно, вы уже это делаете) выполнения каждым потоком вычислений для 5 величин. Это избавит вас от необходимости разделять эти данные между несколькими потоками или заставлять несколько потоков считывать одно и то же место. Если вы еще не делаете этого, то, возможно, есть определенная эффективность в том, чтобы каждый поток выполнял вычисления для нескольких точек данных.

  4. Если вы еще не делаете этого, то, возможно, есть определенная эффективность в том, чтобы каждый поток выполнял вычисления для нескольких точек данных.

  5. Если вы захотите перейти на хранение 4 упакованных величин (на int) вместо 5, возможно, появятся некоторые возможности для использования SIMD instrinsics, в зависимости от того, что именно вы делаете с этими упакованными величинами.

2022 WebDevInsider