О, сколько нам открытий чудных!
(примечание: если вы смотрели на показатель своего проца в Intel Burn Test/Lintel и мечтали - доставайте губозакатывательную машинку. На проце с лимитом 20 гигафлопс программа на паскале выдаст в районе 0.8. Ибо там - сферический конь, написанный на самом возвышенном AVX специальными людьми - а тут по одной, да ещё с гарантированной побитовой воспроизводимостью)
1. Frac() - чудовищно тормозная функция. Позорное днище на уровне Sin(). Если вы надеялись сделать ускоренный фейковый синус типа
- Код: Выделить всё
function ebd_sin(a: float): float; inline;
begin
a:= frac(a * float(0.318309886183790671537767526745031));// 1 / 3.141592653589793));
a:= (float(1.0) - a) * a;
Result:= float (129600.0) * a / (float(40500.0) - a);
end;
- забудьте, он будет валяться в одной канаве с синусом и хрюкать они будут ноздря в ноздрю (sin() 0.04 гигафлопса, ebd_sin() 0.05).
Что в 13 раз медленнее умножения и в полтора раза медленнее, чем 1/sqrt(x).
2. В 64-битном коде некоторые вещи сильно медленнее, а некоторые сильно быстрее - но воспроизводимость при этом идеальная. Контрольные суммы всегда сходятся с таковыми от 32-битного кода. Чтобы не сошлись - надо лезть в ассемблер и руками тянуться в розетку RSQRTPS (быстрый и грязный обратный квадратный корень). Вот та - да, у той на каждом процессоре контрольная сумма будет иная.
ЕМНИП, на Cortex A7 контрольные суммы были абсолютно такими же - хотя казалось бы. Не могу сейчас проверить, все мои малины и апельсины пылятся на полке. И тем более не могу проверить арм 64: у меня таких просто нет. Купил в прошлом году апельсин - думал, чё так дёшево. Оказалось - внутри всё тот же Cortex A7 в обнимку с Mali 400. То есть, Orange Pi PC - это китайский аналог Raspberry Pi 2B, не выше. А в продаже всё ещё есть!
Как бы то ни было, на x86-64:
- Frac() ускорилась ровно в три раза, благодаря чему ebd_sin() обогнал Sin() в 3.4 раза - поскольку тот *ещё* замедлился, до 0.035 гигафлопса. У них специальное соревнование, штоле?
- умножение на константу, не завёрнутую в тайпкаст ко флоату, замедлилось в 2.78 раза по сравнению с завёрнутой. Причём, контрольные суммы что того, что другого варианта - сходятся со своими аналогами из 32-битного кода (а между собой они разные).
Подробнее (включая исходник теста) - когда починю свой сервер и будет, куда выложить.
Добавлено спустя 21 час 10 минут 8 секунд:Развивая тему скорости: SQRTPS + DIVPS заранее загруженными в регистры единицами *ровно* в четыре раза быстрее штатного 1/ sqrt(x). Очевидно, компилятор использует абсолютно те же команды - только скалярные, а не векторные. Во за счёт четырёх операций за раз - и ускорение ровно в четыре раза. У меня там закоментирована RCPPS - очевидно, контрольная сумма не сошлась, побитово получимлось не так, как честное 1/x посредством DIVPS.
Но вы посмотрите, как жж
от RSQRTPS (в четыре с половиной раза быстрее воспроизводимого sse и в восемнадцать раз - штатного 1/ sqrt(x)) - и становится очевидно, что это не компилятор плохой, это процессор задумчивый, когда от него требуют побитового соответствия стандартам.
..checking 1/sqrt(x)
.................................
..ok, in 45 (pure 21,2) seconds (0,1 GFLOPS)
..md5 checksum = 7BA70F1439D5E2955151CC565477E924
..checking SSE SIMD4 1/sqrt(x)
.................................
..ok, in 29 (pure 5,31) seconds (0,401 GFLOPS)
..md5 checksum = 7BA70F1439D5E2955151CC565477E924
..checking SSE SIMD4 RSQRTPS (packed quick reverse square root)
.................................
..ok, in 25 (pure 1,18) seconds (1,81 GFLOPS)
..md5 checksum = F881C03FB2C6F5BBDFF57AE5532CFFFD
Напомню, это на камне, для которого Lintel репорртует 20 гигафлопс на одно ядро (и 30 на два, ибо оба на форсаже не укладываются в TDP).
Добавлено спустя 3 минуты 45 секунд:- Код: Выделить всё
dck_one_div_sqrt: begin
for m:= 0 to (mm div 8) - 1 do begin
pointer(pv):= p + m * 8 * sizeof(float);
pv[0]:= 1/sqrt(pv[0]);
pv[1]:= 1/sqrt(pv[1]);
pv[2]:= 1/sqrt(pv[2]);
pv[3]:= 1/sqrt(pv[3]);
pv[4]:= 1/sqrt(pv[4]);
pv[5]:= 1/sqrt(pv[5]);
pv[6]:= 1/sqrt(pv[6]);
pv[7]:= 1/sqrt(pv[7]);
end;
end;
{$if defined(cpu386)}
dck_sse_one_div_sqrt: begin
for m:= 0 to (mm div 8) - 1 do begin
pointer(pv):= p + m * 8 * sizeof(float);
asm
mov eax, [fourones]
MOVAPS xmm5, [eax]
mov eax, [pv]
MOVAPS xmm6, [eax]
SQRTPS xmm6, xmm6
MOVAPS xmm4, xmm5
DIVPS xmm4, xmm6 //RCPPS xmm6, xmm6 //Reciprocal Parallel Scalars or, simply speaking, 1.0/x
MOVAPS xmm7, [eax + 16]
SQRTPS xmm7, xmm7
MOVAPS [eax], xmm4
DIVPS xmm5, xmm7 //RCPSS xmm7, xmm7
MOVAPS [eax + 16], xmm5
end['eax', 'xmm6', 'xmm7', 'xmm4', 'xmm5'];
end;
end;
dck_sse_rsqrtps: begin
for m:= 0 to (mm div 8) - 1 do begin
pointer(pv):= p + m * 8 * sizeof(float);
asm
mov eax, [pv]
MOVAPS xmm6, [eax]
RSQRTPS xmm6, xmm6
MOVAPS xmm7, [eax + 16]
RSQRTPS xmm7, xmm7
MOVAPS [eax], xmm6
MOVAPS [eax + 16], xmm7
end['eax', 'xmm6', 'xmm7'];
end;
end;
{$endif}
, где mm в подавляющем большинстве случаев = 2048