x86-64 मशीन कोड, 34 बाइट्स
सम्मेलन बुलाना = x86-64 सिस्टम V x32 ABI (लंबे मोड में 32-बिट पॉइंटर्स के साथ रजिस्टर होता है)।
फ़ंक्शन हस्ताक्षर है void stewie_x87_1reg(float *seq_buf, unsigned Nterms);
। फ़ंक्शन सरणी के पहले दो तत्वों में x0 और X1 बीज मान प्राप्त करता है, और अनुक्रम को कम से कम एन अधिक तत्वों तक बढ़ाता है। बफर को 2 + एन-राउंडेड-अप-टू-नेक्स्ट-ऑफ-मल्टीपल ऑफ -4 में से एक होना चाहिए। (यानी 2 + ((N+3)&~3)
, या सिर्फ N + 5)।
उच्च प्रदर्शन या SIMD- वेक्टरकृत कार्यों के लिए असेंबली में गद्देदार बफ़र्स सामान्य है, और यह अनियंत्रित लूप समान है, इसलिए मुझे नहीं लगता कि यह नियमों को बहुत दूर झुका रहा है। कॉल करने वाला आसानी से (और चाहिए) सभी पैडिंग तत्वों को अनदेखा करता है।
एक फ़ंक्शन के रूप में x0 और X1 पास करना पहले से ही बफर में नहीं है, हमें केवल 3 बाइट्स ( movlps [rdi], xmm0
या एक के लिए movups [rdi], xmm0
) खर्च होंगे, हालांकि यह एक गैर-मानक कॉलिंग कन्वेंशन होगा क्योंकि सिस्टम वी struct{ float x,y; };
दो अलग-अलग एक्सएमएम रजिस्टरों में गुजरता है।
यह 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
। मैं वास्तव में x32 के रूप में बनाने के लिए एक कम पता प्राप्त करने के लिए (ढेर पर) के malloc
बजाय इस्तेमाल किया float seqbuf[seqlen+8]
।
मजेदार तथ्य: 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>