машинний код x86-64, 34 байти
Конвенція виклику = x86-64 Система V x32 ABI (зареєструйте аргументи з 32-бітовими вказівниками у тривалому режимі).
Підпис функції є void stewie_x87_1reg(float *seq_buf, unsigned Nterms);
. Функція отримує значення x0 і x1 насіння в перших двох елементах масиву і розширює послідовність на щонайменше ще N елементів. Буфер повинен бути зафіксований до 2 + N-округленням до наступного-кратного-4. (тобто 2 + ((N+3)&~3)
, або просто N + 5).
Потрібні вкладені буфери - це нормально в зборах для високоефективних функцій або функцій, що векторують SIMD, і цей розкручений цикл схожий, тому я не думаю, що він занадто сильно згинає правила. Абонент може легко (і повинен) ігнорувати всі елементи підкладки.
Передача x0 та x1 як аргумент функції, що вже не знаходиться в буфері, коштувала б нам всього 3 байтів (для a movlps [rdi], xmm0
або movups [rdi], xmm0
), хоча це було б нестандартним умовою виклику, оскільки System V проходить struct{ float x,y; };
у двох окремих регістрах XMM.
Це objdump -drw -Mintel
виводиться з невеликим форматуванням для додавання коментарів
0000000000000100 <stewie_x87_1reg>:
;; load inside the loop to match FSTP at the end of every iteration
;; x[i-1] is always in ST0
;; x[i-2] is re-loaded from memory
100: d9 47 04 fld DWORD PTR [rdi+0x4]
103: d8 07 fadd DWORD PTR [rdi]
105: d9 57 08 fst DWORD PTR [rdi+0x8]
108: 83 c7 10 add edi,0x10 ; 32-bit pointers save a REX prefix here
10b: d8 4f f4 fmul DWORD PTR [rdi-0xc]
10e: d9 57 fc fst DWORD PTR [rdi-0x4]
111: d8 6f f8 fsubr DWORD PTR [rdi-0x8]
114: d9 17 fst DWORD PTR [rdi]
116: d8 7f fc fdivr DWORD PTR [rdi-0x4]
119: d9 5f 04 fstp DWORD PTR [rdi+0x4]
11c: 83 ee 04 sub esi,0x4
11f: 7f df jg 100 <stewie_x87_1reg>
121: c3 ret
0000000000000122 <stewie_x87_1reg.end>:
## 0x22 = 34 bytes
Ця реалізація посилань на C компілюється (з gcc -Os
) дещо схожим кодом. gcc вибирає ту саму стратегію, що і я, зберігаючи лише одне попереднє значення в реєстрі.
void stewie_ref(float *seq, unsigned Nterms)
{
for(unsigned i = 2 ; i<Nterms ; ) {
seq[i] = seq[i-2] + seq[i-1]; i++;
seq[i] = seq[i-2] * seq[i-1]; i++;
seq[i] = seq[i-2] - seq[i-1]; i++;
seq[i] = seq[i-2] / seq[i-1]; i++;
}
}
Я експериментував з іншими способами, включаючи версію x87 з двома реєстраціями, яка має код, наприклад:
; part of loop body from untested 2-register version. faster but slightly larger :/
; x87 FPU register stack ; x1, x2 (1-based notation)
fadd st0, st1 ; x87 = x3, x2
fst dword [rdi+8 - 16] ; x87 = x3, x2
fmul st1, st0 ; x87 = x3, x4
fld st1 ; x87 = x4, x3, x4
fstp dword [rdi+12 - 16] ; x87 = x3, x4
; and similar for the fsubr and fdivr, needing one fld st1
Ви зробили б це так, якби їхали на швидкість (а SSE недоступний)
Виведення навантажень із пам’яті всередині циклу, а не один раз при вході, могло б допомогти, оскільки ми могли просто зберігати підзаконні та ділові результати не в порядку, але все-таки для встановлення стека на запис потрібні дві інструкції FLD.
Я також спробував використовувати скалярну математику SSE / AVX (починаючи зі значень у xmm0 та xmm1), але більший розмір інструкцій є вбивчим. Використання addps
(оскільки це на 1В коротше addss
) допомагає крихітно. Я використовував AVX VEX-префікси для некомутативних інструкцій, оскільки VSUBSS лише на один байт довший, ніж SUBPS (і така ж довжина, що і SUBSS).
; untested. Bigger than x87 version, and can spuriously raise FP exceptions from garbage in high elements
addps xmm0, xmm1 ; x3
movups [rdi+8 - 16], xmm0
mulps xmm1, xmm0 ; xmm1 = x4, xmm0 = x3
movups [rdi+12 - 16], xmm1
vsubss xmm0, xmm1, xmm0 ; not commutative. Could use a value from memory
movups [rdi+16 - 16], xmm0
vdivss xmm1, xmm0, xmm1 ; not commutative
movups [rdi+20 - 16], xmm1
Випробуваний за допомогою цього тестового джгута:
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
int main(int argc, char**argv)
{
unsigned seqlen = 100;
if (argc>1)
seqlen = atoi(argv[1]);
float first = 1.0f, second = 2.1f;
if (argc>2)
first = atof(argv[2]);
if (argc>3)
second = atof(argv[3]);
float *seqbuf = malloc(seqlen+8); // not on the stack, needs to be in the low32
seqbuf[0] = first;
seqbuf[1] = second;
for(unsigned i=seqlen ; i<seqlen+8; ++i)
seqbuf[i] = NAN;
stewie_x87_1reg(seqbuf, seqlen);
// stewie_ref(seqbuf, seqlen);
for (unsigned i=0 ; i< (2 + ((seqlen+3)&~3) + 4) ; i++) {
printf("%4d: %g\n", i, seqbuf[i]);
}
return 0;
}
Компілювати з nasm -felfx32 -Worphan-labels -gdwarf2 golf-stewie-sequence.asm &&
gcc -mx32 -o stewie -Og -g golf-stewie-sequence.c golf-stewie-sequence.o
Запустіть перший тестовий зразок за допомогою ./stewie 8 1 3
Якщо у вас не встановлені бібліотеки x32, використовуйте nasm -felf64
та залишайте gcc, використовуючи типовий параметр -m64
. Я використовував malloc
замість float seqbuf[seqlen+8]
(у стеці), щоб отримати низьку адресу, не будуючи насправді x32.
Факт забави: YASM має помилку: він використовує rel32 jcc для гілки циклу, коли ціль гілки має таку ж адресу, що і глобальний символ.
global stewie_x87_1reg
stewie_x87_1reg:
;; ended up moving all prologue code into the loop, so there's nothing here
.loop:
...
sub esi, 4
jg .loop
збирає ... 11f: 0f 8f db ff ff ff jg 100 <stewie_x87_1reg>