x86-64 makine kodu, 34 bayt
Arama kuralları = x86-64 Sistem V x32 ABI (uzun modda 32-bit işaretçilerle kayıt yapın).
İşlev imzası void stewie_x87_1reg(float *seq_buf, unsigned Nterms);
. İşlev, dizinin ilk iki öğesindeki x0 ve x1 tohum değerlerini alır ve diziyi en az N ek öğeye genişletir. Tampon 2 + N-yuvarlatılmış yukarı-yukarı-çoklu-4'e kadar doldurulmalıdır. (yani 2 + ((N+3)&~3)
, veya sadece N + 5).
Dolgulu tamponların kullanılması, yüksek performanslı veya SIMD vektörlü fonksiyonlar için montajda normaldir ve bu kontrolsüz döngü benzerdir, bu yüzden kuralları çok fazla bükdüğünü sanmıyorum. Arayan tüm dolgu elemanlarını kolayca görmezden gelebilir (ve etmelidir).
Tampon içinde zaten bir işlev arg x0 ve x1 geçen bize (a sadece 3 bayt mal olacak movlps [rdi], xmm0
ya da movups [rdi], xmm0
bu standart dışı bir çağrı kuralı olacaktır, ancak sistem V geçtiği için,)struct{ float x,y; };
iki ayrı XMM kaydında .
Bu, objdump -drw -Mintel
yorum eklemek için biraz biçimlendirme çıktı
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
Bu C referans uygulaması, gcc -Os
benzer bir kodu (ile ) derler . gcc, bir kayıtta sadece bir önceki değeri tutmakla aynı stratejiyi seçti.
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++;
}
}
Gibi kod olan iki register x87 sürümü de dahil olmak üzere diğer yolları denedim:
; 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
Hız için gidiyor olsaydınız, bu şekilde yapardınız (ve SSE mevcut değildi).
Yükleri bellekten bir kez giriş yerine bir döngü yerine koymak yardımcı olabilir, çünkü alt ve div sonuçlarını sıra dışı olarak kaydedebildik, ancak yığının girişini ayarlamak için iki FLD komutuna ihtiyacı var.
Ayrıca SSE / AVX skaler matematiğini kullanmayı denedim (xmm0 ve xmm1'deki değerlerden başlayarak), ancak daha büyük komut boyutu katildir. Kullanılması addps
(1B'den kısa olduğundan addss
) küçük bir parçaya yardımcı olur. Değişken olmayan talimatlar için AVX VEX öneklerini kullandım, çünkü VSUBSS SUBPS'den sadece bir byte daha uzun (ve SUBSS ile aynı uzunlukta).
; 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
Bu test kablo demeti ile test edilmiştir:
#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;
}
İle derleyin nasm -felfx32 -Worphan-labels -gdwarf2 golf-stewie-sequence.asm &&
gcc -mx32 -o stewie -Og -g golf-stewie-sequence.c golf-stewie-sequence.o
İlk test senaryosunu çalıştırın. ./stewie 8 1 3
Yüklü x32 kitaplığınız yoksa nasm -felf64
, varsayılanı kullanarak gcc'yi kullanın ve bırakın -m64
. Kullandığım malloc
yerine float seqbuf[seqlen+8]
aslında x32 olarak inşa etmek zorunda kalmadan düşük adresini almak için (yığın).
Eğlenceli gerçek: YASM bir hataya sahip: şube hedefi global bir sembolle aynı adrese sahipken, döngü dalı için bir rel32 jcc kullanır.
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
için toplanır ... 11f: 0f 8f db ff ff ff jg 100 <stewie_x87_1reg>