03.06.2020

Развлекаюсь, как могу

В этот раз могу так: взять любимый язык Pascal и на нем что-нибудь наваять. Наваять захотелось какую-нибудь мерилку производительности процессора. Люблю, я, понимаешь, всякими числами всё измерять.
Понятно, что мерилок таких понаделано и без меня не один вагон. Но все же хочется чего-то своего. Думал, думал, как мерить, и не придумал ничего лучше, чем решением систем линейных уравнений методом Гаусса-Жордана. Не знаю, почему именно этот метод мне понравился, но вроде норм.
Такой подход хорош тем, что можно замерить не только систему команд SISD, но и SIMD, смотря как реализовывать. SIMD мне давно хотелось попробовать, но все руки никак не доходили, да и там на x86/x64 такой зоопарк из инструкций, что просто так, без бутылки, к ним лучше и не лезть.
Ну, то есть вы поняли уже, да? Я не только Pascal люблю, но и Assembler в лайтой версии тоже. Видимо, детская травма суровым советским бытом: калькулятор MK-61 и вот всё это вот 😉.


Короче, накидал быстренько тестовый код на паскале, проверил, что все корректно работает. Правда, жизнь себе немного упростил, считая, что случайно сгенерированные системы будут всегда совместными и определенным.
Дальше мне стало скучно и я переписал решение на ассемблере. Сначала начал для чисел расширенной точности, в терминах Pascal это тип extended или, для краткости, буду здесь его называть FP80 (потому что занимает 80 бит).
В результате получил небольшое ускорение. Примерно в 2.3 раза для маленьких систем с тремя неизвестными, и в 1.12 раза для больших систем в 768 неизвестным. Ну, думаю, не такой уж большой выигрыш, что бы переписывать на ассемблере код.


Думаю, для более коротких вещественных типов выигрыш будет, наверное, еще меньше. Но все же реализовал ради забавы. И оказалось, что я ошибался. Для типа с двойной точноностью (double или FP64) выигрыш оказался 1.76 раза для маленьких и 3.44 раза для больших систем (1536 неизвестных).


Для типа одинарной точности (single или FP32) выигрыш составил 1.7 раза для маленьких и 5 раз для больших систем (1536 неизвестных). Конечно, я город таким достижением 😀, хотя на самом деле это не моя заслуга, а Delphi, так как ходят слухи, что её последние версию не очень хороши в оптимизации.

Кстати, заметна разница в производительности между FP80 и остальными. Это связано с тем, что в FPU нет команд для операций с FP80 прямо из памяти. То есть приходится загружать оба операнда в регистры, проводить операцию между регистрами, потом сохранять. Примерно так:
  fld tbyte ptr [eax];
  fmul st(0), st(1);
  fstp tbyte ptr [eax];
 

А для FP32 это выглядит чуть по-другому:
  fld st(0);  
  fmul dword ptr [eax];
  fstp dword ptr [eax];
Вроде команд столько же, но работают они гораздо быстрее, такая уж особенность архитектуры: при выполнении операции с операндом в памяти загрузка из памяти и расчет значения производятся, похоже, одновременно.
Ну и сказывается, конечно, то, что 10 байт плохо выравниваются, что снижает эффективность и кэширования, и операций с памятью.


Предварительные результаты: на стареньком бюджетном AMD FX-4350 частотой 4.2 ГГц и памятью на 1600 МГц процессор решает 3.8 млн. систем FP80 из 3-х линейных уравнений за 1 секунду, 10.7 млн систем FP64 и 10.8 млн. FP32.
Правда, сложность решения пропорциональна кубу количества переменных, так что максимум, что можно решить за одну секунду, составляет в районе несколько сотен переменных FP80 (< 700), и чуть больше 1000 для FP32.
Много это или мало? Смотря для чего. Скажу так, что 30 лет назад о таком и мечтать было нельзя, причем не только в г. Новокузнецке. Но, например, при расчетах, связанных с моделированием реального мира, это не много. Например, при расчете модели трехмерного мира модным ныне методом трассировки лучей, где используются не совсем такие, но близкие к этому методы, такая производительность даже близко не позволит подойти к получению высококачественной картинки в реальном времени.


Ну а я пока продолжу развлечение. Если будет что интересное - напишу.