engine
engine copied to clipboard
Streebog dynamic dispatch + import of SSE4.1 & MMX implementations
Идея такая:
- В файле
gosthash2012_dispatch.hсоздаются разные версииg()под разные микроархтектуры (sse2, sse4.1, и ref). - Они переключаются динамически во время выполнения (сейчас для определения микроархитектуры используется
__builtin_cpu_supports()и__has_builtin()(так что это работает только на свежих версиях gcc), но потом можно сделать определение через CPUID "как у всех"). - Если dynamic dispatch невозможен, то все должно работать по старому (только sse2 и ref).
- Переключение с помощью
if— это наиболее быстрый вариант из доступных.
ps. gosthash2012: Fix '_mm_empty' call - надо в stable ветки перегнать, это багфикс.
А диспетчер менее адским сделать не получается, да? :(
Там всё рационально, только непривычно такое количество логики на препроцессоре. Но, оно нужно, чтоб сохранить уже существующую портабельность. Рассмотрим gosthash2012_dispatch.h как 2 смысловые части:
- Создание разных версий
g(). -- Этот вариант вносит наименьшее кол-во изменений в существующий код (не раскрывает все макросы и не создаёт повторов одного и того-же как в php-stribog). Не меняет способ сборки (иначе придется перенести усложнение вCMakeLists.txt, а там это может оказаться ещё "страшнее"). Так же у нас уже поддержка нескольких компиляторов, которую надо было оставить. - Сам диспетчер (top level
g()) вполне обычный. Вот пример для BLAKE3: https://github.com/BLAKE3-team/BLAKE3/blob/master/c/blake3_dispatch.c Если есть идеи, то предлагай.
# if defined(__clang__)
# pragma clang attribute push (__attribute__((target("mmx"))), apply_to = function)
# elif defined(__GNUC__)
# pragma GCC push_options
# pragma GCC target("mmx")
# endif
...
# if defined(__clang__)
# pragma clang attribute pop
# elif defined(__GNUC__)
# pragma GCC pop_options
# endif
Вот этот фрагмент скорее всего можно заменить на 1 макрос устанавливающий __attribute__((target("mmx"))), а потом или прилеплять его сразу к функции g, или спрятать в другой (компиляторо-зависимый) макрос.
# undef XLOAD
# undef STORE
# undef TRANSPOSE
# undef XTRANSPOSE
# undef XLPS32
# undef XLPS
# undef __GOST3411_USE_MMX__
# undef g
Такое всё равно придется делать при темплейтировании и в gosthash2012_mmx.h это не убрать, так как эти мкросы ещё нужны в момент include gosthash2012_g.h. Но может можно это загнать в один макрос.
Пример диспетчера в libntru: https://github.com/tbuktu/libntru/blob/master/src/poly.c#L1082 общая схема такая-же, но всё проще так как этот код только под x86_64. Нам же так нельзя.
А что именно показалось адом - темплейт или сам диспетчер? Темплейт ещё можно попытаться украсить или сделать без него (но это не без своих серьезных минусов), а диспетчер мне кажется таким и должен быть.
Пример из BIKE https://github.com/open-quantum-safe/liboqs/blob/main/src/kem/bike/additional_r3/decode_internal.h#L61
Все равно в _init есть сложная логика. И далее они устанавливают callbacks.
g() через pointers будет работать значительно медленнее для такой часто вызываемой функции,.
Вот соавтор BLAKE3 рассуждает о ifunc и поинтерах: https://github.com/BLAKE3-team/BLAKE3/pull/92#issuecomment-647158735
Другой эксперт по performance мне так же советовал из трех вариантов - if(), pointer, ifunc - использовать if() для переключения (для кода в библиотеках).
Мало кто делает темплейт. Можно пойти на дублирование кода и перенести копии функции g() (она не такая большая) в gosthash2012_{ref,mmx,sse2,sse41}.h тогда все #undef и attribute target() можно перенести в них же. А в gosthash2012_dispatch.h будет только 4 #include этих микроархитектурно зависимых хедеров и диспетчер.
Это можно дальше развить и переименовать все эти gosthash2012_sse2.h в gosthash2012_sse2.c и собирать их отдельно (заодно выкинуть #undef). А gosthash2012_dispatch.h вернуть обратно в gosthash2012.c.
LGTM
Только сейчас подумал, что можно же было делать #include "gosthash2012_g.h" внутри gosthash2012_sse41.h и др. Странно мозг работает.
LGTM
Это про что?
Это по совокупности
Переделал так — убрал прагмы (вместо них attribute target() прямо у функций — для msvc говорят они не нужны), вместо темплейта (с include _g.h) и {ref,sse2,...}.h сделал .c где в каждом своя версия g. Логика выбора реализаций в gosthash2012.h, диспетчер (top-level g остался) в gosthash2012.c. Добавил атрибут _internal для всех g (может это повлияет на оптимизацию.), (А для главной g (диспетчера) заменил static на _internal, чтоб его asm можно было посмотреть в gdb -ex 'disas/s g'" — это можно убрать, но, вроде, не мешает).
Так оно стало гораздо читабельнее.