Сейчас сделал предварительный расчет SIN и COS, в основном цикле использую уже готовые значения. Никогда не задумывался, что такие элементарные операции требуют столько времени

Код: Выделить всё
inline void TestNoFMA(float a[], float b[], float c[], float res[], int Size)
{
int i;
for(i=0; i < Size; i++) {
res[i] = a[i] * b[i] + c[i];
}
}
inline void TestYesFMA(float a[], float b[], float c[], float res[], int Size)
{
int i;
for(i=0; i < Size; i++) {
res[i] = fmaf(a[i], b[i], c[i]);
}
}
Там много факторов, начиная от пропускной способности памяти, в которую обычно и упирается на больших массивах, и заканчивая тем, что не стоит гонять это дело по невыровненным данным.IvanLis писал(а): ↑01 июл 2024, 13:23 FMA почему то проигрывает, пробовал компилировать по разному и проверял в разных OS.
Проигрывает всегда, но разница в Linux меньше.
Снимок экрана от 2024-07-01 13-21-07.pngКод: Выделить всё
inline void TestNoFMA(float a[], float b[], float c[], float res[], int Size) { int i; for(i=0; i < Size; i++) { res[i] = a[i] * b[i] + c[i]; } } inline void TestYesFMA(float a[], float b[], float c[], float res[], int Size) { int i; for(i=0; i < Size; i++) { res[i] = fmaf(a[i], b[i], c[i]); } }
Код: Выделить всё
SINECOSINE_API int fnNoFMACompute(int n)
{
for (int i = 0; i < n; i++) dst[i] = a[i] * b[i] + c[i];
return 0;
}
SINECOSINE_API int fnFMACompute(int n)
{
// Perform FMA using AVX2
for (int i = 0; i < n; i += 16) {
__m256d va = _mm256_load_pd(&a[i]);
__m256d vb = _mm256_load_pd(&b[i]);
__m256d vc = _mm256_load_pd(&c[i]);
__m256d va1 = _mm256_load_pd(&a[i + 4]);
__m256d vb1 = _mm256_load_pd(&b[i + 4]);
__m256d vc1 = _mm256_load_pd(&c[i + 4]);
__m256d va2 = _mm256_load_pd(&a[i + 8]);
__m256d vb2 = _mm256_load_pd(&b[i + 8]);
__m256d vc2 = _mm256_load_pd(&c[i + 8]);
__m256d va3 = _mm256_load_pd(&a[i + 12]);
__m256d vb3 = _mm256_load_pd(&b[i + 12]);
__m256d vc3 = _mm256_load_pd(&c[i + 12]);
// Perform FMA: res[i] = a[i] * b[i] + c[i]
__m256d vres = _mm256_fmadd_pd(va, vb, vc);
__m256d vres1 = _mm256_fmadd_pd(va1, vb1, vc1);
__m256d vres2 = _mm256_fmadd_pd(va2, vb2, vc2);
__m256d vres3 = _mm256_fmadd_pd(va3, vb3, vc3);
_mm256_store_pd(&dst[i], vres);
_mm256_store_pd(&dst[i + 4], vres1);
_mm256_store_pd(&dst[i + 8], vres2);
_mm256_store_pd(&dst[i + 12], vres3);
}
return 0;
}
Всем спасибо за участиеAndreyDmitriev писал(а): ↑01 июл 2024, 15:26 Мож с опциями компиляции что-то не то. ИИ мне пишет что надо как-то так компилировать: gcc -mavx2 -mfma -O3 -o fma_avx2 fma_avx2.c
Я в этом году довольно много поигрался с AVX2/AVX512 и в общем да, выигрыш там не всегда большой. Идея в общем не лишена смысла, попробовать можно всегда, но особенно на AVX512 частота проца начинает дропаться из-за перегрева, потому что для вычислений по 512 битам ему банально надо переключать одномоментно слишком много транзисторов. Ну и память не успевает подгонять данные, выигрыш заметен только на многопроцессорных тачках с двенадцатиканальной памятью.IvanLis писал(а): ↑01 июл 2024, 17:57Всем спасибо за участиеAndreyDmitriev писал(а): ↑01 июл 2024, 15:26 Мож с опциями компиляции что-то не то. ИИ мне пишет что надо как-то так компилировать: gcc -mavx2 -mfma -O3 -o fma_avx2 fma_avx2.c!
Возможно на живой OS Windows результаты будут иные, но сейчас не на чем протестировать.
Заморачиваться с векторными вычислениями пока не вижу смысла, выигрыш совсем маленький, да и нет там особо где развернуться....
Тут в принципе прослеживается аналогия с CUDA.AndreyDmitriev писал(а): ↑02 июл 2024, 07:48 Я в этом году довольно много поигрался с AVX2/AVX512 и в общем да, выигрыш там не всегда большой. Идея в общем не лишена смысла, попробовать можно всегда, но особенно на AVX512 частота проца начинает дропаться из-за перегрева, потому что для вычислений по 512 битам ему банально надо переключать одномоментно слишком много транзисторов. Ну и память не успевает подгонять данные, выигрыш заметен только на многопроцессорных тачках с двенадцатиканальной памятью.
Если посмотреть ассемблерный листинг dll, то в ней достаточно много дополнительного кода. Наверное важного.
Код: Выделить всё
;DLLrus.asm
;DLL для WIN32 - перекодировщик из koi8r в cp1251
;
.486
.model flat
;Функции определяемые в этой DLL
ifdef _MASM_
public _koi2win_asm@0 ;koi2win_asm - перекодирует символ в AL
public _koi2win@4 ;CHAR WINAPI koi2win(CHAR symbol)
public _koi2wins_asm@0 ;koi2wins_asm - перекодирует строку в [EAX]
public _koi2wins@4 ;VOID WINAPI koi2wins(CHAR * string)
else
public koi2win_asm ;То же для TASM
public koi2win
public koi2wins_asm
public koi2wins
endif
.const
k2w_tbl db 16 dup(0) ;
db 16 dup(0) ;
db 00h, 00h, 00h, 38h, 00h, 00h, 00h, 00h
db 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h
db 00h, 00h, 00h, 28h, 00h, 00h, 00h, 00h
db 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h
db 7Eh, 60h, 61h, 76h, 64h, 65h, 74h, 63h
db 75h, 68h, 69h, 6Ah, 6Bh, 6Ch, 6Dh, 6Eh
db 6Fh, 7Fh, 70h, 71h, 72h, 73h, 66h, 62h
db 7Ch, 7Bh, 67h, 78h, 7Dh, 79h, 77h, 7Ah
db 5Eh, 40h, 41h, 56h, 44h, 45h, 54h, 43h
db 55h, 48h, 49h, 4Ah, 4Bh, 4Ch, 4Dh, 4Eh
db 4Fh, 5Fh, 50h, 51h, 52h, 53h, 46h, 42h
db 5Ch, 5Bh, 47h, 58h, 5Dh, 59h, 57h, 5Ah
.code
_start@12:
mov al,1 ;надо вернуть ненулевое число
ret 12
;Процедура BYTE WINAPI koi2win(BYTE symbol)
;Точка входа для вызова из С
ifdef _MASM_
_koi2win@4 proc
else
koi2win proc
endif
pop ecx ; обратный адрес в ECX
pop eax ; параметр в ECX, теперь стек очищен от параметров
push ecx ; обратный адрес надо вернуть в стек для RET
ifdef _MASM_
_koi2win@4 endp
else
koi2win endp
endif
;Процедура koi2win_asm
;Точка входа для ассемблерных программ
;ввод AL - код символа в koi
;вывод AL - код того же символа в WIN
ifdef _MASM_
_koi2win_asm@0 proc
else
koi2win_asm proc
endif
test al,80h
jz dont_decode
push ebx
mov ebx, offset k2w_tbl
sub al,80h
xlat
add al,80h
pop ebx
dont_decode:
ret
ifdef _MASM_
_koi2win_asm@0 endp
else
koi2win_asm endp
endif
;Процедура VOID WINAPI koi2wins(BYTE * koistring)
;Точка входа для вызова из С
ifdef _MASM_
_koi2wins@4 proc
else
koi2wins proc
endif
pop ecx ; обратный адрес в ECX
pop eax ; параметр в ECX, теперь стек очищен от параметров
push ecx ; обратный адрес надо вернуть в стек для RET
ifdef _MASM_
_koi2wins@4 endp
else
koi2wins endp
endif
;Процедура koi2wins_asm
;Точка входа для ассемблерных программ
;ввод EAX - адрес строки
;вывод EAX - адрес той же перекодированной строки
ifdef _MASM_
_koi2wins_asm@0 proc
else
koi2wins_asm proc
endif
push esi
push edi
push ebx
mov esi,eax
mov edi,eax
mov ebx,offset k2w_tbl
decode_string:
lodsb
test al,80h
jz dont_decode2
sub al,80h
xlat
add al,80h
dont_decode2:
stosb
test al,al
jnz decode_string
pop ebx
pop edi
pop esi
ret
ifdef _MASM_
_koi2wins_asm@4 endp
else
koi2wins_asm endp
endif
end _start@12
Код: Выделить всё
include def32.inc
include user32.inc
include kernel32.inc
.486
.model flat
.const
title_string1 db "koi2win demo: string in KOI8",0
title_string2 db "koi2win demo: string in cp1251",0
koi_win_dll db "DLLrus.dll",0
akoi2wins db 'koi2wins',0
.data
koi_string db 0F3h,0D4h,0D2h,0CFh,0CBh,0C1h,20h,0CEh,0C1h
db 20h,0EBh,0EFh,0E9h,2Dh,28h,0
dll_handler dd ?
.code
_start:
push MB_OK
push offset title_string1
push offset koi_string
push 0
call MessageBox
push offset koi_win_dll
call LoadLibrary
push offset akoi2wins
push eax
push edx
mov edx,offset dll_handler
mov [edx],eax
pop edx
call GetProcAddress
push edx
mov edx,eax
mov eax,offset koi_string
push eax
call edx
pop edx
push MB_OK
push offset title_string2
push offset koi_string
push 0
call MessageBox
push offset dll_handler
call FreeLibrary
push 0
call ExitProcess
end _start
Путь есть всегда. На самом деле современные компиляторы действительно длстаточно умные. Я ассемблером воспользовался лишь тогда, когда компилятор не смог самом понять, что данные выровнены и упорно пихал мне функции для чтения записи в невыровненную память.ujin1 писал(а): ↑02 июл 2024, 12:32В связи с этим есть путь выделить ассемблерный код из функций и скомпилировать современными ассемблерами.
Все скажут, что современные компиляторы настолько умные, что никакого выигрыша не получится. По листингу dll скомпилированного из C кода возникают сомнения.
Кроме этого на сегодняшний день для выполнения параллельных вычислений очевидно есть более подходящие инструменты.
Код: Выделить всё
AsmDLL32 PROGRAM FORMAT=DLL, MODEL=FLAT
EXPORT fnAsmCdecl
fnAsmCdecl PROC
mov eax, [esp+4]
add eax, [esp+8]
retn
ENDP fnAsmCdecl
EXPORT fnAsmStdCall
fnAsmStdCall PROC
mov eax, [esp+4]
add eax, [esp+8]
retn 8
ENDP fnAsmStdCall
ENDPROGRAM AsmDLL32
Код: Выделить всё
EUROASM CPU=X64
AsmDLL64 PROGRAM FORMAT=DLL, MODEL=FLAT, WIDTH=64
EXPORT fnAsm64
fnAsm64 PROC
mov rax, rcx
add rax, rdx
ret
ENDP fnAsm64
ENDPROGRAM AsmDLL64
Разобрался почему сломана. Нужно было починить использование Linx toolkit.