FreeBSD ZFS SHA256: производительность (и Intel SHA Extensions)

(пока придумаешь заголовок, язык об голову сломаешь)

Вот значит у одного моего друга есть будущий NAS на Atom C3758:

$ grep -E 'CPU:|SHA' /var/run/dmesg.boot
	CPU: Intel(R) Atom(TM) CPU C3758 @ 2.20GHz (2200.08-MHz K8-class CPU)
Structured Extended Features=0x2294e283<fsgsbase,tscadj,smep,erms,nfpusg,mpx,pqe,rdseed,smap,clflushopt,proctrace,sha>

Беру я (ну то есть он, один друг) три 6-Tb диска HGST HUS726060ALE614 (надо бы больше, но нету свободных), слепляю zfs pool и запускаю там такое:

$ for cksum in off fletcher2 fletcher4 sha256 sha512 skein; do zfs set checksum=$cksum ztest/test; echo Method: $cksum; dd if=/dev/zero of=/ztest/test/file bs=1G count=50; rm -f /ztest/test/file ; sleep 200; done 
 Method: off
53687091200 bytes transferred in 82.793456 secs (648446073 bytes/sec)
Method: fletcher2
53687091200 bytes transferred in 83.082330 secs (646191446 bytes/sec)
Method: fletcher4
53687091200 bytes transferred in 82.178876 secs (653295513 bytes/sec)
Method: sha256
53687091200 bytes transferred in 116.553766 secs (460620818 bytes/sec)
Method: sha512
53687091200 bytes transferred in 84.072162 secs (638583451 bytes/sec)
Method: skein
53687091200 bytes transferred in 108.943920 secs (492795663 bytes/sec)

При этом, off, fletcher* - в CPU не упираются (systat -pigs показывает 6 kernel с потреблением в районе 5-15%), sha*, skein - близки к тому, чтобы упереться (6x80-95%, вот sha512 похоже повезло и с 4-мя дисками был бы виден провал).

В то же время, у процессора есть поддержка SHA1/SHA256 и вот что получается на одном ядре:

$dd if=/dev/zero bs=1G count=5 | /usr/bin/openssl dgst -sha256
5368709120 bytes transferred in 15.999813 secs (335548252 bytes/sec)

При этом то самое "одно ядро" выжирается, понятно, на те же 90% по systat -pigs

Дальше я полез в ... fs/zfs/sha256.c и обнаружил там OpenSSL: SHA256_Init(), SHA256_Update().

Так, по идее, sha256 на шести ядрах - не должно упираться в CPU при использовании SHA*-инструкций...?

Теперь, собственно, вопросы

  1. Как узнать, использует ли ядро (zfs) SHA256MSG1 и вообще эти инструкции? Ну то есть понятно, можно printf вставить в исходники, а можно ли не пересобирая?
  2. Возможно, все уже известно и "надо при сборке ядра указать флаги компилятору, что SHA есть"
  3. Ну и тоже интересно - почему ядро жрет 6 ядер, когда у меня их 8 (ну ладно, одно съедает dd, но одно - простаивает же).

Comments

"ODROID-H2 Gemini Lake single-board PC launches for $111
It’s priced at $111 and features a 10 watt, quad-core Intel Celeron J4105 processor and a 4.3″ x 4.3″ x 1.7″ with a bunch of ports, two slots for DDR4 memory, and support for several types of storage including a PCIe NVMe solid state drive." - https://liliputing.com/2018/11/odroid-h2-gemini-lake-single-board-pc-lau...

Ты обнаружил не OpenSSL, есго в ядре нет, а похожее на него API. Когда это API апдейтили и апдйтили ли вообще — бог весть. Я ставлю на то, что никто не делал в ядре имплементации на новых инструкциях. В ядре вообще не используют SSE и выше НИГДЕ кроме ОТДЕЛЬНОГО МОДУЛЯ aesni,
aesni не умеет SHA и экспортирует себя в Open Crypto Framework, которым ZFS не пользуется потому что переносимое.

В общем, я ставлю на то, что никто никогда не делал оптимизированного SHA для ZFS.

И более того, я ставлю на то, что сделать это будет непросто, потому что в ZFS нет инфраструктуры ветвлений по cpuid.

Хотя теперь у нас есть ifunc'и (но не в 11!) и можно сделать на них — это механизм ровно для таких дел с поддержкой со стороны компилятора и линкера.

Читаю исходники. Там идёт редирект в общеядерную реализацию

#include
#include

/sys/crypto/sha2

Там нету никаких спец-реализаций. В принципе, я могу попробовать сделать, даже интересно, но тестировать не на чем. И работать будет только на 13+ и после релиза бэкпорт в 12 но не в 11.

Ну насколько я понимаю, "лично для себя" можно SHA256* скопипастить из OpenSLL под конкретный процессор и будет ЩАСТЬЕ.

Ну, я бы начал именно с этого что бы убедиться, что смысл есть.

Куда копировать я показал :-)

Хотя стоп. Нельзя ПРОСТО скопировать. Будут проблемы с FPU context который в общем случае при переключении userland-kernel не сохраняется и не восстанавливается, потому что в общем случае в ядре FPU нет.

Надо смотреть как именно это сделано в aesni.

Смысл, почти очевидно, есть (потому что 600MB/sec - это в 6 ядер, а на SHA-ext - 300 на одном).
Правда fletcher4 меня вполне устраивает.

Чтобы два раза не вставать: AES (ssh) между новым и старым ящиком - ~300Mb/sec т.е. (минимум) раза в три медленнее чем между двумя i3.

Осваиваю -o NoneSwitch у openssh-portable.

Кстати я соврал — aesni поддерживает SHA: SHA1,SHA256
Но до aesni можно достучаться только через Open Crypto Framework, а не через прямой доступ. А ZFS так не делает. Всё сложно, блин. С криптой в ядре полный бардак, я уже второй раз в это влетаю.

Я бы вот поныл в списках рассылки если бы мне было где тестировать если сделают.

Ну как бы тебе сказать, это вообще у меня боевой ящик (будет)

Не хочу тестировать FreeBSD на нем.

А начиная с какой архитектуры эти инструкции появились?

Goldmont и рязань

На больших нет что ли!? i7-9700 какой?

Похоже что нету.

Смешно, но в ARK нету этого флага (в отличие от AES-NI), т.е. нельзя отобрать процессоры по нему.

Да я уже увидел, это первое, куда я полез.

Вот похоже только тут есть список: https://en.wikichip.org/wiki/intel/microarchitectures/goldmont

Ну и плюс рязань 2xxx

При этом у Apollo Lake (на этом же сайте) нету SHA в списке.

Короче, боюсь я что вот Atom C3xxx и рязань - и на этом все кончается. Но рязани много у людей.

В общем, я бы поныл в списках рассылки что в aesni нет SHA512 и что ZFS не использует aesni если он есть.

У меня отпуск скоро, мне сейчас начинать хипёшь неудобно — я скоро на месяц перестану читать почту :-)

Я в 11.2 не вижу sha в aesni (смотрел грепом), наверное он в 12-й появился.
Если я правильно понимаю, то SHA512 интелом не подперт в ISA, только -1 и -256 (ну судя по инструкциям).

Поскольку апгрейдиться на -12 я не собираюсь (работает - и слава богу!), то и хрен с ним пока.

Ну то есть чтобы поныть - нужно еще купить что-то на чем ныть (хоть рязань, хоть мелкую борду с C3xxx), а ныть об боевой ящик - который придется апгрейдить и тестировать я очень не хочу (у меня потом L2ARC набирается неделями после перезагрузки до состояния когда он реально помогает а не по тестам)

Так и нету. И MFC не планировалось

------------------------------------------------------------------------
r336444 | cem | 2018-07-18 07:43:18 +0300 (Wed, 18 Jul 2018) | 2 lines

aesni(4): Add SHA2-224(-HMAC) support as well

------------------------------------------------------------------------
r336443 | cem | 2018-07-18 07:37:14 +0300 (Wed, 18 Jul 2018) | 2 lines

aesni(4): Add sha256 plain hash support

Да понятно, да, что что бы ныть железку для тестов нету.

Рязань, судя по тексту коммита прошлогоднего, 1xxx тоже подходит (и дает 8GB/sec)

Нашёл как гарантированно наполнять L2ARC?

Не, я не парюсь.
Просто если работаешь с каким-то "датасетом", то оно и наполняется. Медленнее чем хотелось бы (ну и ARC работает лучше, в смысле хватает конечно основные запросы), но наполняется.

Оказалось, что при чтении полностью помещает нужные файлы только если выключен префетч. Ну или при записи. Если хочется префетч и L2ARC одновременно, нужно патчить.

Ага, то есть для рабочих датасетов лучше префетч выключать, они тогда быстрее попадут в L2?

А с включенным - попадает только то, что явно фетчится, префетч то есть не сработал

Поразвлекаюсь, интересно.

Тогда они полностью попадут в L2ARC и будут читаться только с ssd. Иначе при одновременном чтении (часть с hdd и часть ssd) положительный эффект стремится к нулю. Префетч вроде нельзя включать/выключать для отдельных датасетов, а при выключенном префетче последовательная скорость чтения удручает. То что при срабатывании префетча файл не может полностью попасть в L2ARC это похоже не фича, а банальный недосмотр.

А почему "положительный эффект стремится к нулю"? Мне так не показалось

Имелось ввиду чтение всего файла. Если читаем только с ssd - имеем низкую латентность и его скорость чтения. Если читаем какие-то части файла с hdd (допустим больше 10-20%), то для всего файла практически получаем скоростные показатели основного пула. То что иногда очень быстро - это больше заслуга ARC-а, который служит подпоркой и часто содержит, то что не попало в L2ARC. Если же читать только отдельные блоки (случайный доступ) тогда эффект конечно ощутимый.

Ща, вы меня сбили с курса.

Вот есть tunable: vfs.zfs.l2arc_noprefetch
Если выключить, то префетченное в l2 все едино не попадает? Тогда да, это ошибка

По моему это разрешает/запрещает префетчу читать из L2ARC.

Вроде как и писать. Собственно, эксперимент же несложно поставить:
Настройки:

# sysctl vfs.zfs | grep prefetch
	vfs.zfs.no_scrub_prefetch: 0
	vfs.zfs.prefetch_disable: 0
	vfs.zfs.dedup.prefetch: 1
	vfs.zfs.l2arc_noprefetch: 0

Берем файлы, которые не трогались после перезагрузки (которая была вчера), читаем их в dev/null и смотрим, пишется ли что-то в L2ARC (первая строчка - это общая статистика с перезагрузки). Указанный diskid - это кэш, записи - последняя колонка.
 
# zpool iostat -v zdata 5 | grep diskid/DISK-C6EF076A07E100005289
  diskid/DISK-C6EF076A07E100005289  60,1G   387G      0      1   137K  1003K
  diskid/DISK-C6EF076A07E100005289  60,1G   387G      0      0      0    715
  diskid/DISK-C6EF076A07E100005289  60,1G   387G      0      1      0  8,78K
  diskid/DISK-C6EF076A07E100005289  60,1G   387G      0      0      0  15,1K
  diskid/DISK-C6EF076A07E100005289  60,1G   387G      0      1      0  32,5K
  diskid/DISK-C6EF076A07E100005289  60,1G   387G      0      0      0  23,8K
  diskid/DISK-C6EF076A07E100005289  61,0G   386G      0    193      0   173M
  diskid/DISK-C6EF076A07E100005289  61,2G   386G      0     58      0  56,2M
  diskid/DISK-C6EF076A07E100005289  62,1G   385G      0    186      0   181M
  diskid/DISK-C6EF076A07E100005289  62,5G   385G      0     74      0  72,9M
  diskid/DISK-C6EF076A07E100005289  63,6G   384G      0    236      0   233M
  diskid/DISK-C6EF076A07E100005289  64,2G   383G      0    117      0   116M
  diskid/DISK-C6EF076A07E100005289  65,3G   382G      0    221      0   218M

Ну пишется. Повторно читаем их же:

 

# zpool iostat -v zdata 5 | grep diskid/DISK-C6EF076A07E100005289
4 # zpool iostat -v zdata 5 | grep diskid/DISK-C6EF076A07E100005289
  diskid/DISK-C6EF076A07E100005289  76,2G   371G      0      1   136K  1,23M
  diskid/DISK-C6EF076A07E100005289  76,2G   371G      0      0      0      0
  diskid/DISK-C6EF076A07E100005289  76,2G   371G      0      0      0      0
  diskid/DISK-C6EF076A07E100005289  76,2G   371G      0      0      0      0
  diskid/DISK-C6EF076A07E100005289  76,3G   371G    150     25   132M  24,4M
  diskid/DISK-C6EF076A07E100005289  76,8G   370G    230     92   225M  91,6M
  diskid/DISK-C6EF076A07E100005289  77,3G   370G    261    119   258M   118M
  diskid/DISK-C6EF076A07E100005289  78,4G   369G    221    136   220M   136M
  diskid/DISK-C6EF076A07E100005289  78,7G   368G    239    154   239M   152M
  diskid/DISK-C6EF076A07E100005289  79,8G   367G    193    203   192M   202M
  diskid/DISK-C6EF076A07E100005289  80,0G   367G    257     59   256M  59,5M
  diskid/DISK-C6EF076A07E100005289  80,6G   367G    235    126   235M   120M
  diskid/DISK-C6EF076A07E100005289  81,0G   366G    221     88   220M  88,2M
  diskid/DISK-C6EF076A07E100005289  81,5G   366G    176     90   176M  89,8M
  diskid/DISK-C6EF076A07E100005289  81,6G   366G    203     18   202M  18,6M
  diskid/DISK-C6EF076A07E100005289  82,1G   365G    241     98   241M  98,4M
  diskid/DISK-C6EF076A07E100005289  82,7G   364G    240    129   240M   130M
  diskid/DISK-C6EF076A07E100005289  83,1G   364G    236     91   233M  90,8M

Ну читается (и еще что-то пишется)

 

Извинте за форматирование, ща все исправлю

Вот почему оно читает только 250Mb/sec - ну наверное потому что только часть блоков попала и действительно чтения с магнитных блинов все тормозят.

Прочитал эти же файлы еще два раза (всего, значит, четыре). Размер набора файлов - 50G, в L2 легло 46G (до начала - было занято 60, сейчас 106). Читаем в пятый раз:

# zpool iostat -v zdata 5 | grep diskid/DISK-C6EF076A07E100005289
  diskid/DISK-C6EF076A07E100005289   106G   341G      1      1  1,26M  1,68M
  diskid/DISK-C6EF076A07E100005289   106G   341G    180      0   156M   204K
  diskid/DISK-C6EF076A07E100005289   106G   341G    802      6   793M  6,76M
  diskid/DISK-C6EF076A07E100005289   106G   341G  1,07K     49  1,06G  49,1M
  diskid/DISK-C6EF076A07E100005289   107G   340G    802     74   799M  74,5M
  diskid/DISK-C6EF076A07E100005289   107G   340G    557     32   555M  32,5M
  diskid/DISK-C6EF076A07E100005289   107G   340G    691     13   689M  13,6M
  diskid/DISK-C6EF076A07E100005289   107G   340G    895     38   892M  38,1M
  diskid/DISK-C6EF076A07E100005289   108G   339G     20    214  19,1M   214M
  diskid/DISK-C6EF076A07E100005289   108G   339G      0      8      0  8,57M

О чем я, собственно, и говорю: L2 набирается, но сильно постепенно, вот за пять чтений набора файлов (путем tar cvf /dev/null .) где-то процентов 90 куда надо легло.

И на 8-й раз оно туда улеглось примено целиком

# zpool iostat -v zdata 5 | grep diskid/DISK-C6EF076A07E100005289
  diskid/DISK-C6EF076A07E100005289   111G   336G      2      1  2,65M  1,75M
  diskid/DISK-C6EF076A07E100005289   111G   336G    715      0   684M  1020K
  diskid/DISK-C6EF076A07E100005289   111G   336G  1,09K     47  1,08G  47,0M
  diskid/DISK-C6EF076A07E100005289   111G   336G  1,18K     12  1,18G  12,2M
  diskid/DISK-C6EF076A07E100005289   111G   336G  1,16K     11  1,16G  11,2M
  diskid/DISK-C6EF076A07E100005289   111G   336G  1,24K      0  1,24G      0
  diskid/DISK-C6EF076A07E100005289   111G   336G    717      1   711M  2,00M

Первая строчка - общая статистика, вторая и последняя - не все время из 5 сек. измерения было чтение, а вот 1+ гиг в секунду - это сколько SSD-шка умеет как раз, больше не умеет.

Если сделать vfs.zfs.prefetch_disable=1 и подобрать параметры сканирования/записи в L2ARC, то данные могут закешироваться даже после первого-второго чтения, причем в последующем чтение с hdd будет полностью отсутствовать. Что бы была понятна причина - в одной из ветвей после предварительного чтения и последующего результативного чтения в созданной структуре для арка не устанавливается ARC_FLAG_L2CACHE и при сканировании данная запись не может попасть в L2ARC. Попадание в это ответвление зависит - было ли на момент результативного чтения предварительное чтение завершено или нет.

Забыл сказать это точно было в 11.1. 11.2 ещё не пробовал.

если сделать prefetch disable, то все очень медленно.

Вообще, вы бы порепортили в FreeBSD, раз уж в это место влезли и разобрались?

«Вообще, вы бы порепортили в FreeBSD, раз уж в это место влезли и разобрались?»

Вот да.

(у меня L2ARC нету, я снял, у меня хитрейт был 2.5%, а SSD понадобился, к тому же это был SATA SSD и он не сильно быстрее массива).

"Тотальное" кеширование противоречит заявленной "The main role of this cache is to boost the performance of random read workloads." И сам механизм сканирования хвоста арка для такого "тотального" кеширования не очень подходит. Для хорошего эффекта нужно рассчитывать время, глубину сканирования, учитывать производительность основного пула и ssd. Наверно это не для универсального продукта. Только для специфических задач.

Я поразмыслил, вопрос непростой
1) Ну действительно, если префетч справился - значит это место (в этот раз) прочлось быстро и хрен ли его кэшировать
2) а если нет - то кэшируем.

Это выглядит разумным поведением на первый взгляд.

А на второй - работает случайность/вероятность и для моих конкретных тестовых наборов - ну вот почти 100% читаемого линейно большого датасета оказывается в L2 с 5..8 прохода.

Лично мне бы конечно тут нужен бы тюнинг (per dataset)
- писать в L2 все (даже то, что считалось префетчем). Это типа "для рабочих файлов". Ну вот когда я с фоточками работаю - я просмотрю набор сильно больше одного раза.
- не писать в L2 - это уже есть (secondarycache = metadata)
- не писать то, что успешно читалось префетчем, независимо от того, успело или не успело (см. выше).

Впрочем, конечно, раз прочитать 4-5 раз годится - то я просто рабочие файлы могу потарить в devnull перед началом работы и усе.

Тоже были такие мысли :-). Можно выключить префетч, быстро полностью затарить нужные файлы в L2ARC, а затем включить префетч. Думал даже сделать это автоматом при загрузке. Где-то год назад в одном месте стояла подобная задача и нужно было сделать - "что бы летало". Поэтому пришлось вникнуть, что бы заставить "работать как надо". В результате после первого чтения имел скорость nvme (около 1,7GB/s). Затем, после того как заметил, что метаданные сжимаются где-то в 25 раз, сообразил, что в данной схеме можно ещё настроить и жёсткую пирамиду - самые востребованные данные читаются только из ARC, менее востребованные из L2ARC, остальные из основного пула. Отстроил и это. И ... упёрся в латентность сети :-). Оказалось проще и эффективней для той задачи переместить нужные для работы каталоги на локальный nvme.

Intel Celeron J3455 — это goldmont… Хм. Есть коробки китайские, причём даже с двумя I210 :-)
Лучше бы я себе его взял чем на N3160 :-)

Еще E5-2600 v3
Короче, бардак у них везде, не нашел в первой странице гугла полного списка

Мда. Небюджетно.

Нет, нихрена нету в это зионе, там только RORX

> Осваиваю -o NoneSwitch у openssh-portable

Ну тогда rcp, в 11 оно пока не отменено (в 12 - ставить из портов).

Мне не rcp, мне rsh, мне надо zfs recv запускать на remote.

И что-то я не хочу rsh, у ssh хотя-бы как-то контроль доступа есть (ключами).

У rsh тоже есть "контроль" - по IP-адресу :-)

Для изолированного домашнего сегмента использую, нормально.

>> В общем, я ставлю на то, что никто никогда не делал оптимизированного SHA для ZFS.
Поаккуратнее с кванторами то: https://github.com/kernelOfTruth/zfs/commit/089ad082f8d500e948239397746c...

Другой вопрос, что это ZOL, а я бы хотел бы понятно хде.