kode mesin x86-64, 34 byte
Konvensi Memanggil = x86-64 Sistem V x32 ABI (register args dengan pointer 32-bit dalam mode panjang).
Tanda tangan fungsi adalah void stewie_x87_1reg(float *seq_buf, unsigned Nterms);
. Fungsi menerima nilai-nilai seed x0 dan x1 dalam dua elemen pertama array, dan memperluas urutan ke setidaknya N elemen lebih banyak. Buffer harus diisi hingga 2 + N-dibulatkan ke atas-ke-kelipatan-4. (Yaitu 2 + ((N+3)&~3)
, atau hanya N + 5).
Membutuhkan buffer berlapis adalah normal dalam perakitan untuk kinerja tinggi atau fungsi vektor-SIMD, dan loop terbuka ini serupa, jadi saya tidak berpikir itu menekuk aturan terlalu jauh. Penelepon dapat dengan mudah (dan harus) mengabaikan semua elemen padding.
Melewati x0 dan x1 sebagai fungsi arg yang belum ada di buffer akan menghabiskan biaya hanya 3 byte (untuk a movlps [rdi], xmm0
atau movups [rdi], xmm0
), meskipun ini akan menjadi konvensi panggilan non-standar karena Sistem V melewati struct{ float x,y; };
dua register XMM yang terpisah.
Ini adalah objdump -drw -Mintel
output dengan sedikit pemformatan untuk menambahkan komentar
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
Implementasi referensi C ini mengkompilasi (dengan gcc -Os
) ke kode yang agak mirip. gcc memilih strategi yang sama dengan yang saya lakukan, yaitu hanya menyimpan satu nilai sebelumnya dalam register.
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++;
}
}
Saya melakukan percobaan dengan cara lain, termasuk versi x87 dua register yang memiliki kode seperti:
; 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
Anda akan melakukannya dengan cara ini jika Anda ingin kecepatan (dan SSE tidak tersedia)
Menempatkan beban dari memori di dalam loop alih-alih sekali pada entri mungkin bisa membantu, karena kita bisa saja menyimpan hasil sub dan div rusak, tetapi masih membutuhkan dua instruksi FLD untuk mengatur stack pada entri.
Saya juga mencoba menggunakan skalar matematika SSE / AVX (dimulai dengan nilai dalam xmm0 dan xmm1), tetapi ukuran instruksi yang lebih besar adalah pembunuh. Menggunakan addps
(karena itu 1B lebih pendek dari addss
) membantu sedikit. Saya menggunakan AVX VEX-awalan untuk instruksi non-komutatif, karena VSUBSS hanya satu byte lebih panjang dari SUBPS (dan panjang yang sama dengan 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
Diuji dengan test-harness ini:
#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;
}
Kompilasi dengan nasm -felfx32 -Worphan-labels -gdwarf2 golf-stewie-sequence.asm &&
gcc -mx32 -o stewie -Og -g golf-stewie-sequence.c golf-stewie-sequence.o
Jalankan test-case pertama dengan ./stewie 8 1 3
Jika Anda tidak menginstal pustaka x32, gunakan nasm -felf64
dan biarkan gcc menggunakan default -m64
. Saya menggunakan malloc
alih-alih float seqbuf[seqlen+8]
(pada stack) untuk mendapatkan alamat rendah tanpa harus benar-benar membangun sebagai x32.
Fakta menyenangkan: YASM memiliki bug: ia menggunakan rel32 jcc untuk cabang loop, ketika target cabang memiliki alamat yang sama dengan simbol global.
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
berkumpul untuk ... 11f: 0f 8f db ff ff ff jg 100 <stewie_x87_1reg>