Я пытаюсь быстро создать большое количество хэшей sha256 на машине T4. У T4 есть инструкция ‘sha256’, которая позволяет мне вычислять хэш в одном операционном коде. Я создал встроенный шаблон сборки для вызова кода операции sha256:
в моем коде C ++:
extern "C"{
void ProcessChunk(const char* buf, uint32_t* state);
}
pchunk.il:
.inline ProcessChunk,8
.volatile
/* copy state */
ldd [%o1],%f0 /* load 8 bytes */
ldd [%o1 + 8],%f2 /* load 8 bytes */
ldd [%o1 +16],%f4 /* load 8 bytes */
ldd [%o1 +24],%f6 /* load 8 bytes */
/* copy data */
ldd [%o0],%f8 /* load 8 bytes */
ldd [%o0+8],%f10 /* load 8 bytes */
ldd [%o0+16],%f12 /* load 8 bytes */
ldd [%o0+24],%f14 /* load 8 bytes */
ldd [%o0+32],%f16 /* load 8 bytes */
ldd [%o0+40],%f18 /* load 8 bytes */
ldd [%o0+48],%f20 /* load 8 bytes */
ldd [%o0+56],%f22 /* load 8 bytes */
sha256
nop
std %f0, [%o1]
std %f2, [%o1+8]
std %f4, [%o1+16]
std %f6, [%o1+24]
.end
В однопоточной среде все работает отлично, но недостаточно быстро. Я использовал openmp для распараллеливания приложения, чтобы я мог вызывать ProcessChunk одновременно. Многопоточная версия приложения работает нормально для нескольких потоков, но когда я увеличиваю количество потоков (например, 16), я начинаю получать фиктивные результаты. Входными данными для функции ProcessChunk являются переменные стека, локальные для каждого потока. Я подтвердил, что входные данные генерируются правильно, независимо от количества потоков. Если я помещаю ProcessChunk в критическую секцию, я получаю правильные результаты, но производительность значительно снижается (один поток работает лучше). Я поставлен в тупик, в чем может быть проблема. Возможно ли для потоков Solaris наступать на регистры с плавающей запятой другого потока?
Любые идеи, как я могу отладить это?
С уважением
Обновить:
Я изменил код, чтобы использовать четырехмерную (16 байтную) нагрузку, и сохранил:
.inline ProcessChunk,8
.volatile
/* copy state */
ldq [%o1], %f0
ldq [%o1 +16],%f4
/* copy data */
ldq [%o0], %f8
ldq [%o0+16],%f12
ldq [%o0+32],%f16
ldq [%o0+48],%f20
lzd %o0,%o0
nop
stq %f0, [%o1]
stq %f4, [%o1+16]
.end
На первый взгляд проблема, кажется, ушла. Производительность значительно снижается после 32 потоков, так что это число, которое я придерживаюсь (по крайней мере, на данный момент), и с текущим кодом я, кажется, получаю правильные результаты. Я, вероятно, просто замаскировал проблему, поэтому собираюсь провести дальнейшие тесты.
Обновление 2:
Я нашел время, чтобы вернуться к этому, и я смог получить приличные результаты от T4 (10 миллионов хешей в минуту).
Изменения, которые я сделал, были:
Я собрал все в библиотеке и сделал код доступным Вот
Не эксперт по архитектуре Spark (я могу ошибаться), но вот мое предположение:
Ваш встроенный код сборки загружает переменную стека в набор определенных регистров с плавающей запятой, чтобы иметь возможность вызывать операцию sha asssembly.
Как это работает для двух потоков? Оба вызова ProcessChunk будут пытаться скопировать разные входные значения в одни и те же регистры ЦП.
Обычно я вижу, что регистры ЦП в ассемблерном коде похожи на «глобальные» переменные для языка программирования высокого уровня.
Сколько ядер у вашей системы? Может быть, вы в порядке, пока у вас нет потока на ядро / набор аппаратных регистров. Но это также подразумевает, что поведение кода может зависеть от того, как запланированы потоки в разных ядрах вашей системы.
Знаете ли вы, как система ведет себя, когда она планирует потоки из того же процесса на ядре процессора? Я имею в виду: хранит ли система регистры незапланированного потока, как при переключении контекста?
Тест, который я бы запустил, состоит в том, чтобы порождать количество потоков, равное N ядер ЦП, а затем запустить тот же тест с N + 1 (я предполагаю, что для каждого ядра ЦП установлен регистр с плавающей запятой).
Других решений пока нет …