Стратегия минимизации банковских конфликтов для 64-битной разделяемой потоками разделяемой памяти - PullRequest
0 голосов
/ 10 июня 2018

Предположим, у меня есть полная деформация потоков в блоке CUDA, и каждый из этих потоков предназначен для работы с N элементами типа T, находящимися в общей памяти (таким образом, мы имеем warp_size * N = 32 N элементов).Различные потоки никогда не обращаются к данным друг друга.(Ну, они делают, но на более позднем этапе, который нас не волнует здесь).Этот доступ должен происходить в следующем цикле:

for(int i = 0; i < big_number; i++) {
    auto thread_idx = determine_thread_index_into_its_own_array();
    T value = calculate_value();
    write_to_own_shmem(thread_idx, value);
}

Теперь разные потоки могут иметь разные индексы или одинаковые - я не делаю никаких предположений, так или иначе.Но я хочу минимизировать конфликты в банках совместно используемой памяти.

Если sizeof(T) == 4, то это просто: просто поместите все данные потока i в адреса совместно используемой памяти i, 32 + i, 64 + i, 96 + i и т. Д. Это помещает все данные i в один и тот же банк, который также отличается от банков других линий.Отлично.

Но теперь - что если sizeof(T) == 8?Как мне разместить свои данные и получить к ним доступ, чтобы минимизировать банковские конфликты (без каких-либо знаний об индексах)?

Примечание. Предположим, что T - обычные старые данные.Вы даже можете предположить, что это число, если это упрощает ваш ответ.

Ответы [ 2 ]

0 голосов
/ 11 июня 2018

tl; dr: использовать тот же тип чередования, что и для 32-битных значений.

На микроархитектурах позже, чем Kepler (до Volta), лучшее, что мы теоретически могли бы получить - это 2 общихтранзакции памяти для полного деформирования, считывающие одно 64-битное значение (поскольку одна транзакция обеспечивает максимум 32 бита для каждой полосы).

На практике это достижимо с помощью аналогичного OP шаблона размещения, описанного для 32-битовые данные.То есть, для T* arr, полоса i должна считывать idx '-ый элемент как T[idx + i * 32].Это скомпилируется так, что произойдут две транзакции:

  1. Нижние 16 дорожек получают свои данные из первых 32 * 4 байтов в T (используя все банки)
  2. Старшие 16 получают своиданные из последовательных 32 * 4 байтов в T (с использованием всех банков)

Таким образом, графический процессор является более умным / более гибким, чем попытка выбрать 4 байта для каждой дорожки в отдельности.Это означает, что это может быть лучше, чем упрощенная идея «разбить Т на половинки», предложенная ранее.

(Этот ответ основан на комментариях @ RobertCrovella.)

0 голосов
/ 10 июня 2018

На GPU Kepler это было простое решение: просто измените размер банка!Кеплер поддерживал динамическое изменение размера банка общей памяти на 8 вместо 4.Но, увы, эта функция недоступна в более поздних микроархитектурах (например, Maxwell, Pascal).

Теперь вот уродливый и неоптимальный ответ для более новых микроархитектур CUDA: уменьшите 64-битный случай до 32-битовый регистр.

  • Вместо каждого потока, хранящего N значений типа T, он хранит 2N значений, каждая последовательная пара является младшим и старшим 32-битами T.
  • Чтобы получить доступ к 64-битным значениям, осуществляются 2 половинных T доступа, а T состоит из чего-то вроде `

    uint64_t joined =
        reinterpret_cast<uint32_t&>(&upper_half) << 32  +
        reinterpret_cast<uint32_t&>(&lower_half);
    auto& my_t_value = reinterpret_cast<T&>(&joined);
    

    и наоборотпри записи.

Как показывают комментарии, лучше сделать 64-битный доступ, как описано в в этом ответе .

...