.NET Core: интринсики x86_64 на виртуальных машинах

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

Архитектура x86 за время своего существования (и популярности) пережила много крупных апдейтов (например, расширение до 64 бит — x86_64) и добавлений «расширенных наборов инструкций». К этому приходится подстраиваться и компиляторам, которые по-умолчанию генерируют максимально общий для всех процессоров код. Но среди расширенных инструкций есть много интересного и полезного. Например, в шахматных программах часто используются инструкции для работы с битами: POPCNT, BSF/BSR (или более свежие аналоги TZCNT/LZCNT), PDEP, BSWAP и т.д.

В компиляторах C и C++ явный доступ к таким инструкциям реализован через «присущие (intrinsic) данному процессору функции». пример1 пример2

Для .NET и C# такого удобного доступа не существовало, поэтому когда-то давно я сделал свою обёртку, которая предоставляла эмуляцию таких функций, но если CPU их поддерживал, то заменяла их вызов прямо в вызывающем коде. Благо, большинство нужных мне интринсиков помещались в 5 байт опкода CALL. Подробности можно почитать на хабре по этой ссылке.

С тех пор прошло много лет, в .NET нормальных интринсиков так и не появилось. Но вышел .NET Core, в котором ситуацию исправили. Сначала появились векторные инструкции, в потом и почти весь* набор System.Runtime.Intrinsics.X86.
* — нет «устаревших» BSF и BSR

И всё вроде-бы стало хорошо и удобно. Если не считать того, что определение поддержки каждого набора инструкций всегда было запутанным (какие-то включаются сразу наборами, для каких-то есть отдельные флаги). Так .NET Core запутало нас ещё сильнее с тем, что между «разрешёнными» наборами есть ещё и какие-то зависимости.

Всплыло это при попытке запустить код на виртуальной машине с гипервизором KVM: посыпались ошибки System.PlatformNotSupportedException: Operation is not supported on this platform at System.Runtime.Intrinsics.X86.Bmi1.X64.TrailingZeroCount(UInt64 value). Аналогично и для System.Runtime.Intrinsics.X86.Popcnt.X64.PopCount. Но если для POPCNT можно было поставить достаточно очевидный флаг в параметрах виртуализации, то TZCNT ввёл меня в тупик. На следующей картинке вывод тулзы, проверяющей доступность интринсиков в netcore (код и бинарник в конце статьи) и всем известного CPU-Z:



А вот вывод тулзы, взятой со страницы MSDN про CPUID:



Несмотря на то, что процессор рапортует о поддержке всего, что требуется, инструкция Intrinsics.X86.Bmi1.X64.TrailingZeroCount всё равно продолжала падать с эксепшеном System.PlatformNotSupportedException.

Чтобы в этом разобраться, нам надо взглянуть на процессор глазами NETCore. Исходники которого лежат на гитхабе. Поищем там cupid и выйдем на метод EEJitManager::SetCpuInfo()

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



Как видим, флаг InstructionSet_BMI1 всё же выставлен (хотя не выставлены некоторые другие).

Если поискать этот флаг по репозиторию, то можно наткнуться на такой код:

if (resultflags.HasInstructionSet(InstructionSet_BMI1) && !resultflags.HasInstructionSet(InstructionSet_AVX))
    resultflags.RemoveInstructionSet(InstructionSet_BMI1);

Так вот, она наша зависимость! Если не определился AVX, то отключается и BMI1 (и некоторые другие наборы). В чём логика, мне пока не ясно, но будем надеяться на то, что она всё-таки есть. Теперь осталось разобраться, почему cpu-z и другие тулзы видят AVX, а netcore — нет.

Посмотрим, чем отличается вывод нашей тулзы на разных процессорах:

>diff a b
7c7,8
< Test ((buffer[8] & 0x02) != 0) -> 0
---
> Test ((buffer[8] & 0x02) != 0) -> 1
> ==> Set InstructionSet_PCLMULQDQ
18c19,32
< Test ((buffer[11] & 0x18) == 0x18) -> 0
---
> Test ((buffer[11] & 0x18) == 0x18) -> 1
> Test (hMod == NULL) -> 0
> Test (pfnGetEnabledXStateFeatures == NULL) -> 0
> Test ((FeatureMask & XSTATE_MASK_AVX) == 0) -> 0
> Test (DoesOSSupportAVX() && (xmmYmmStateSupport() == 1)) -> 1
> Test (hMod == NULL) -> 0
> Test (pfnGetEnabledXStateFeatures == NULL) -> 0
> Test ((FeatureMask & XSTATE_MASK_AVX) == 0) -> 0
> ==> Set InstructionSet_AVX
> Test ((buffer[9] & 0x10) != 0) -> 1
> ==> Set InstructionSet_FMA
> Test (maxCpuId >= 0x07) -> 1
> Test ((buffer[4] & 0x20) != 0) -> 1
> ==> Set InstructionSet_AVX2

  1. Фейлится проверка buffer[8] & 0x02, это PCLMULQDQ
  2. Фейлится buffer[11] & 0x18, это AVX & OSXSAVE, AVX уже выставлен (это видит CPU-Z), нужен OSXSAVE
  3. А за ней и другие проверки, которые ведут к флагу InstructionSet_AVX

Так что же делать с вируалкой? Если есть возможность, то лучше всего поставить libvirt.cpu_mode в host-passthrough или host-model.

Но если такой возможности нет, то придётся добавлять весь суп из инструкций, в частности ssse3, sse4.1, sse4.2, sse4a, popcnt, abm, bmi1, bmi2, avx, avx2, osxsave, xsave, pclmulqdq. Здесь я передаю привет и спасибо vdsina_m ;)

А проверить ваш хост или виртуалку на поддержку инструкций и то, как на это смотрит .NET Core можно с помощью этой тулзы: (пока что зип, позже выложу на гитхаб).

  • Бинарники core_intrin_check.bin.win64.zip
  • Исходники core_intrin_check.source.zip
Источник: https://habr.com/ru/post/495462/


Интересные статьи

Интересные статьи

Одна из самых важных (на мой взгляд) функций в Битрикс24 это бизнес-процессы. Теоретически они позволяют вам полностью избавиться от бумажных служебок и перенести их в эл...
У некоторых бизнес-тренеров в области е-коммерса и консультантов по увеличению интернет-продаж на многие вопросы часто можно слышать универсальную отмазку — «надо тестировать» или другую (чтобы не...
Привет, Хабр! Представляю вашему вниманию перевод статьи «5 Reasons You Should Stop Using System.Drawing from ASP.NET». Ну что ж, они таки сделали это. Команда corefx в конце концов соглас...
От скорости сайта зависит многое: количество отказов, брошенных корзин. Согласно исследованию Google, большинство посетителей не ждёт загрузки больше 3 секунд и уходит к конкурентам. Бывает, что сайт ...
В «1С-Битрикс» считают: современный интернет-магазин должен быть визуально привлекательным, адаптированным для просмотра с мобильных устройств и максимально персонализированным с помощью технологии Бо...