Pull to refresh
3
0
Send message

По стандарту C++, например, во всех операциях переменные целочисленных типов, меньшьх int, должны быть преобразованы к int. Так сложилось, что в x86-64 тип int 32-битный. Поэтому вся целочисленная арифметика как минимум 32-битная.

GCC и LLVM так же генерируют 32-битные операции для bool-переменных: https://godbolt.org/z/1vqnjWezs

Они и не нужны. На самом деле компиляторы практически не используют их.

Первые шесть: RSI, RDI, RDX, RCX, R8 и R9. Подробнее здесь: https://uclibc.org/docs/psABI-x86_64.pdf

Зачем вообще нужны эти R8-R15? Это что, RISC-процессор?

Когда доступно много регистров, компилятору становится проще жить. Не надо без конца спиливать переменные в память. Плюс, с добавлением дополнительных регистров пришло новое соглашение о передаче параметров в функции: первые 6 параметров передаются на регистрах.

Ну, мне иногда интересно потыкать палочкой в разные компиляторы и посмотреть, какой код они генерируют. Не то, чтобы в данном случае был какой-то практический смысл. В конце концов, если бы PL/I был кому-то нужен, компиляторы для него писали бы разработчики процессоров (-:

Ну, судя по сайту автора, это компилятор PL/I. Только там нет исходников, а у меня нет винды, поэтому я не могу запустить бинарную сборку.

Так, эта, компилятор — он таки есть или его таки нет?

Компилятор, вероятно, есть. Но Optimization Reference Manual от Intel и AMD читать, похоже, умеют не все (-:

Не всегда плотный код это хорошо. Бывают ситуации, когда код специально разбавляют nop-ами, чтобы ускорить исполнение.

В том-то и дело, что для железа выгоднее, когда код вообще не использует 8- и 16-битную арифметику

На современных процессорах x86 использование 8- и 16-битных регистров бьёт по производительности. В частности из-за того, что называется source merge. Собственно, все регистры общего назначения 64-битные. 32-битные инструкции обнуляют старшую часть регистра, а 8- и 16-битные её сохраняют --- очевидно это достигается исполнением дополнительных микроинструкций.

В общем-то, эти регистры сохранены для совместимости со старым софтом, и лучше бы их совсем не использовать. Собственно, разработчики мэйнстрим-компиляторов и не используют этих регистров, а всюду, где возможно, расширяют 8- и 16-битную арифметику до 32 бит.

FMA, конечно, точнее. Только при -ffast-math компилятор может делать некоторые другие нехорошие вещи. Например, изменять порядок суммирования, если складывается несколько чисел. Также компилятор может в этом режиме включить denormals flush to zero. А это всё не очень хорошо влияет на точность.

Сейчас ещё проверил: если подать -ffast-math, компилятор начинает генерировать FMA, но ну его к чёрту. Нельзя включать -ffast-math, если есть требования к точности:

.LBB12_3:
	vmovsd	xmm1, qword ptr [rip + .LCPI12_1] # xmm1 = mem[0],zero
	vfmadd213sd	xmm1, xmm0, qword ptr [rip + .LCPI12_794] # xmm1 = (xmm0 * xmm1) + mem
	vmovsd	xmm0, qword ptr [rip + .LCPI12_795] # xmm0 = mem[0],zero
	vfmadd213sd	xmm0, xmm1, qword ptr [rip + .LCPI12_796] # xmm0 = (xmm1 * xmm0) + mem
	vfmadd213sd	xmm0, xmm1, qword ptr [rip + .LCPI12_797] # xmm0 = (xmm1 * xmm0) + mem
	vfmadd213sd	xmm0, xmm1, qword ptr [rip + .LCPI12_798] # xmm0 = (xmm1 * xmm0) + mem
	vfmadd213sd	xmm0, xmm1, qword ptr [rip + .LCPI12_799] # xmm0 = (xmm1 * xmm0) + mem
	vfmadd213sd	xmm0, xmm1, qword ptr [rip + .LCPI12_800] # xmm0 = (xmm1 * xmm0) + mem
	vfmadd213sd	xmm0, xmm1, qword ptr [rip + .LCPI12_801] # xmm0 = (xmm1 * xmm0) + mem
	ret

Если уж смотреть на эту конкретную реализацию, то не будет ли эффективнее вместо switch на 100 веток написать таблицу коэффициентов? Как я вижу, во всех ветках выполняется одно и то же действие, только числа разные. Ну и использовать инструкции fma --- ошибка будет медленнее накапливаться.

Красивый код.

static double erfcx_y100(double y100)
{
  switch ((int) y100) {
case 0: {
double t = 2*y100 - 1;
return 0.70878032454106438663e-3 + (0.71234091047026302958e-3 + (0.35779077297597742384e-5 + (0.17403143962587937815e-7 + (0.81710660047307788845e-10 + (0.36885022360434957634e-12 + 0.15917038551111111111e-14 * t) * t) * t) * t) * t) * t;
}
...
  }
  // we only get here if y = 1, i.e. |x| < 4*eps, in which case
  // erfcx is within 1e-15 of 1..
  return 1.0;
}

LLVM 10 (-O3 -march=skylake) превращает switch со 100 ветками в переход по таблице:

_ZL10erfcx_y100d:                       # @_ZL10erfcx_y100d
	.cfi_startproc
# %bb.0:
	vcvttsd2si	eax, xmm0
	cmp	eax, 99
	ja	.LBB12_1
# %bb.2:
	jmp	qword ptr [8*rax + .LJTI12_0]
.LBB12_3:
	vaddsd	xmm0, xmm0, xmm0
	vaddsd	xmm0, xmm0, qword ptr [rip + .LCPI12_793]
	vmulsd	xmm1, xmm0, qword ptr [rip + .LCPI12_794]
	vaddsd	xmm1, xmm1, qword ptr [rip + .LCPI12_795]
	vmulsd	xmm1, xmm0, xmm1
	vaddsd	xmm1, xmm1, qword ptr [rip + .LCPI12_796]
	vmulsd	xmm1, xmm0, xmm1
	vaddsd	xmm1, xmm1, qword ptr [rip + .LCPI12_797]
	vmulsd	xmm1, xmm0, xmm1
	vaddsd	xmm1, xmm1, qword ptr [rip + .LCPI12_798]
	vmulsd	xmm1, xmm0, xmm1
	vaddsd	xmm1, xmm1, qword ptr [rip + .LCPI12_799]
	vmulsd	xmm0, xmm0, xmm1
	vaddsd	xmm0, xmm0, qword ptr [rip + .LCPI12_800]
	ret
...
	.section	.rodata,"a",@progbits
	.p2align	3
.LJTI12_0:
	.quad	.LBB12_3
	.quad	.LBB12_4
...

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

Собственно, это уже реализовано в стандартной библиотеке языка C. В частности, реализация синуса в glibc: https://github.com/lattera/glibc/blob/master/sysdeps/ieee754/dbl-64/s_sin.c

LLVM заточен на компиляцию языков C и С++, а в терминах абстрактной машины этих языков понятие стека отсутствует.

Я полагаю, эту задачу решает линкер, который не является частью LLVM в общем случае. Не знаю, как оно на Windows, на Linux есть 3 опции: GNU ld, GNU gold и LLD.

Вообще, да. Intel​ 64 and IA-32 Architectures Optimization Reference Manual именно это и рекомендует, если не нужна 80-битная точность. Intel заявляет, что программная реализация на SSE будет быстрее.

User/Source Coding Rule 15. (M impact, ML generality) Usually, math libraries take advantage of the transcendental instructions (for example, FSIN) when evaluating elementary functions. If there is no critical need to evaluate the transcendental functions using the extended precision of 80 bits, applications should consider an alternate, software-based approach, such as a look-up-table-based algorithm using interpolation techniques. It is possible to improve transcendental performance with these techniques by choosing the desired numeric precision and the size of the look-up table, and by taking advantage of the parallelism of the SSE and the SSE2 instructions.

Intel optimization manual рекомендует не использовать x87, если у вас нет требований к точности, которых можно добиться только с 80-битной арифметикой. Только это я хотел сказать, когда писал, что это legacy. Понятно, что если вам для вашей задачи нужна повышенная точность, то придётся использовать x87. Я просто призываю не использовать эти инструкции бездумно, когда без них можно обойтись.

Кроме того, вычисления всё чаще выгружают на gpu, где 80-битная арифметика не поддерживается в принципе. Если в этом случае нужна повышенная точность, приходится что-то изобретать. Может быть, эти подходы и на CPU применимы?

Часть про стек и out-of-order superscalar относилась к окончанию статьи, в котором автор упоминает про несколько стеков и переключение между ними.

x87 fpu со стеком это legacy, которое не рекомендуется к использованию. Современные компиляторы генерируют sse-инструкции. В них регистры не организованы в стек, а доступны непосредственно, по номерам. Современные процессоры с их миллиардами транзисторов реализуют out-of-order superscalar pipeline, на который стек ложится плохо.

Information

Rating
Does not participate
Registered
Activity