LINUX.ORG.RU

Почему gcc не использует больше xmm при оптимизации?

 , , , ,


0

2

Я использую GCC 13.2.0 и собираю приложение с флагами -msse4.2 -march=native -mtune=generic -Ofast, почему компилятор старается беречь xmm регистры и использует всего 2? У меня в проце явно больше 2-х xmm регистров.

для примера:

#include <experimental/simd>
using floatv = stdx::native_simd<float>;

void func(floatv& a, const floatv& b, const floatv& c, const floatv& d) {
  a = a * b + c * d;
}

результат для sse4.2:

movaps  xmm0, xmmword ptr [rsi]
mulps   xmm0, xmmword ptr [rdi]
movaps  xmm1, xmmword ptr [rcx]
mulps   xmm1, xmmword ptr [rdx]
addps   xmm1, xmm0
movaps  xmmword ptr [rdi], xmm1
ret

Если собирать для AVX, то будет так:

vmovaps zmm1, ZMMWORD PTR [rdi]
vmulps  zmm0, zmm1, ZMMWORD PTR [rsi]
vmovaps zmm2, ZMMWORD PTR [rdx]
vfmadd231ps     zmm0, zmm2, ZMMWORD PTR [rcx]
vmovaps ZMMWORD PTR [rdi], zmm0
vzeroupper
ret

Что это значит, для AVX оптимизатор лучше написан? В clang тот же результат, берутся только xmm0 и xmm1. Во всех местах компиль юзает только по два xmm



Последнее исправление: HPW-dev (всего исправлений: 1)

Что это значит, для AVX оптимизатор лучше написан

Это значит, что код разный. В случае AVX используется FMA и трёхоперандные формы, а в случае SSE более чем двум регистрам просто неоткуда взяться.

почему компилятор старается беречь xmm регистры и использует всего 2? У меня в проце явно больше 2-х xmm регистров

Потому что вообще насрать. Все процы, в которых эти самые регистры хотя бы существуют, давно по самые гланды OoO и все регистры внутренне переименовываются.

intelfx ★★★★★
()
Последнее исправление: intelfx (всего исправлений: 2)

xmm0 и xmm1 это аргументы куда упихались данные их кушает SIMD инструкция и больше она не скушает если ей больше не надо. xmm1/0 это 128 битные значения которые вместе берут 4 64 битных и множат, у тебя всего 4 64 битных и есть. Остальные просто тасуют потом, но это не точно надо смотреть что инструкции конкретно делают (лично я не спец). Данных побольше суть наверное и повтори опыт.

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от LINUX-ORG-RU

Данных побольше суть наверное и повтори опыт.

Ок, сделал вот так:

void func_64(float* a, const float* b, const float* c, const float* d) {
  for (int i = 0; i < 16 * 4; ++i)
    a[i] = a[i] * b[i] + c[i] * d[i];
}

Получил много кода и все xmm0-7

HPW-dev
() автор топика
Последнее исправление: HPW-dev (всего исправлений: 1)
Ответ на: комментарий от LINUX-ORG-RU

Ну вот. Я не пойму ты хочешь чтобы например addps выглядел приверно вот так addps xmm0, xmm1, xmm2, xmm3? =)

Ну я хотел чтоб компиль сам побольше данных напихал во все регистры, а не только в 2

HPW-dev
() автор топика
-msse4.2 -march=native -mtune=generic -Ofast

Что за камень читателю предлагается угадать самостоятельно.


Для начала аргументы лучше принимать по значению. Хотя бы для того, чтобы избежать проблем с потенциальным оверлапом ссылок, и чтобы избавиться от лишних лоадов.

Просто переписав ее в

floatv func(floatv a, floatv b, floatv c, floatv d) {
    return a * b + c * d;
}

я получаю красивые

g++ -c -std=c++2b -Ofast -ffast-math -march=nehalem simd.cxx

0000000000000000 <_Z4funcNSt12experimental14parallelism_v24simdIfNS0_8simd_abi11_VecBuiltinILi16EEEEES5_S5_S5_>:
   0:	0f 59 c1             	mulps  %xmm1,%xmm0
   3:	0f 59 d3             	mulps  %xmm3,%xmm2
   6:	0f 58 c2             	addps  %xmm2,%xmm0
   9:	c3                   	ret

и

g++ -c -std=c++2b -Ofast -ffast-math -march=haswell simd.cxx

0000000000000000 <_Z4funcNSt12experimental14parallelism_v24simdIfNS0_8simd_abi11_VecBuiltinILi32EEEEES5_S5_S5_>:
   0:	c5 fc 59 c1          	vmulps %ymm1,%ymm0,%ymm0
   4:	c4 e2 6d b8 c3       	vfmadd231ps %ymm3,%ymm2,%ymm0
   9:	c3                   	ret

С шлангом аналогично. Никакие std::simd здесь не нужны, но предположим что они данность. Касательно того, почему принимаются такие решения – есть intel vtune и llvm-mca. Возможно, что то, что ты счел неоптимальным, по каким-то причинам оптимальнее ожидаемого тобой варианта – или, по крайней мере, его считает более оптимальным компилятор.

Siborgium ★★★★★
()
Последнее исправление: Siborgium (всего исправлений: 1)
Ответ на: комментарий от intelfx

Все процы, в которых эти самые регистры хотя бы существуют, давно по самые гланды OoO и все регистры внутренне переименовываются.

Ага. Только здесь никаким ОоО не пахнет и пахнуть не может, потому что здесь data dependency.

Siborgium ★★★★★
()
Ответ на: комментарий от Siborgium

Здесь, естественно, не может. Но переименование регистров всё равно есть. На самом деле может, OoO здесь будет между началом исполнения addps и retirement второго mulps, т. к. физически запись результата сложения будет производиться в другой регистр. Чего бы не было возможно без переименования.

intelfx ★★★★★
()
Последнее исправление: intelfx (всего исправлений: 2)
Ответ на: комментарий от HPW-dev

Кроме уже написанного, все твои эксперименты практически не имеют смысла, потому что ты пытаешься оценивать код, генерируемый для отдельных, очень маленьких функций.

При инлайне (а он обязан быть, иначе ты априори занимаешься ерундой) все может поменяться кардинально, так как будет учитываться контекст встраивания.

Siborgium ★★★★★
()
Ответ на: комментарий от HPW-dev

Ну, можно херануть вот так gcc -c ../test.c -O3 -ffast-math -msse -msse2 -msse4 -S -o test.s

#include <stdint.h>

typedef uint64_t vec16 __attribute__ ((vector_size (128)));

vec16 func(vec16 * vars)
{
  return vars[0] * vars[1] * vars[2] * vars[3];
}

И бууубааах :)

	.file	"test.c"
	.text
	.p2align 4
	.globl	func
	.type	func, @function
func:
.LFB0:
	.cfi_startproc
	movdqa	16(%rsi), %xmm3
	movq	%rdi, %rax
	movdqa	144(%rsi), %xmm4
	movdqa	192(%rsi), %xmm7
	movdqa	208(%rsi), %xmm8
	movdqa	%xmm3, %xmm0
	movdqa	%xmm4, %xmm1
	movdqa	%xmm3, %xmm2
	movdqa	224(%rsi), %xmm9
	psrlq	$32, %xmm0
	psrlq	$32, %xmm1
	movdqa	240(%rsi), %xmm10
	movdqa	128(%rsi), %xmm11
	pmuludq	%xmm3, %xmm1
	pmuludq	%xmm4, %xmm0
	pmuludq	%xmm4, %xmm2
	movdqa	272(%rsi), %xmm4
	movdqa	%xmm4, %xmm3
	psrlq	$32, %xmm3
	paddq	%xmm1, %xmm0
	psllq	$32, %xmm0
	paddq	%xmm0, %xmm2
	movdqa	%xmm2, %xmm0
	movdqa	%xmm2, %xmm1
	psrlq	$32, %xmm0
	pmuludq	%xmm3, %xmm2
	movdqa	400(%rsi), %xmm3
	pmuludq	%xmm4, %xmm0
	pmuludq	%xmm4, %xmm1
	movdqa	160(%rsi), %xmm4

...

	movaps	%xmm0, (%rdi)
	movaps	%xmm6, 16(%rdi)
	movaps	%xmm5, 32(%rdi)
	movaps	%xmm4, 48(%rdi)
	movaps	%xmm3, 64(%rdi)
	movaps	%xmm2, 80(%rdi)
	movaps	%xmm1, 96(%rdi)
	movaps	%xmm9, 112(%rdi)
	ret
	.cfi_endproc
.LFE0:
	.size	func, .-func
	.ident	"GCC: (Debian 13.2.0-5) 13.2.0"
	.section	.note.GNU-stack,"",@progbits

Но это получается носом ткнуть, а вот как просто это же код но вот в таком виде


uint64_t func(uint64_t * vars)
{
  return vars[0]  * vars[1]  * vars[2]  * vars[3]  +
         vars[4]  * vars[5]  * vars[6]  * vars[7]  +
         vars[8]  * vars[9]  * vars[10] * vars[11] +
         vars[12] * vars[13] * vars[14] * vars[15] ;
}

Автоматически векторизовать я сам понятия не имею.

LINUX-ORG-RU ★★★★★
()
Последнее исправление: LINUX-ORG-RU (всего исправлений: 1)