mã máy x86-64, 34 byte
Quy ước gọi = x86-64 Hệ thống V x32 ABI (thanh ghi đối số với con trỏ 32 bit ở chế độ dài).
Chữ ký hàm là void stewie_x87_1reg(float *seq_buf, unsigned Nterms);
. Hàm nhận các giá trị hạt x0 và x1 trong hai phần tử đầu tiên của mảng và mở rộng chuỗi ra ít nhất N nhiều phần tử. Bộ đệm phải được đệm ra thành 2 + N-round-up-to-next-multi-of-4. (tức là 2 + ((N+3)&~3)
, hoặc chỉ N + 5).
Yêu cầu bộ đệm đệm là lắp ráp bình thường cho các chức năng có hiệu suất cao hoặc được SIMD hóa, và vòng lặp không được kiểm soát này là tương tự, vì vậy tôi không nghĩ rằng nó uốn cong quy tắc quá xa. Người gọi có thể dễ dàng (và nên) bỏ qua tất cả các yếu tố đệm.
Truyền x0 và x1 dưới dạng một hàm arg chưa có trong bộ đệm sẽ chỉ tiêu tốn của chúng tôi 3 byte (cho một movlps [rdi], xmm0
hoặc movups [rdi], xmm0
), mặc dù đây sẽ là một quy ước gọi không chuẩn vì System V truyền struct{ float x,y; };
vào hai thanh ghi XMM riêng biệt.
Đây là objdump -drw -Mintel
đầu ra với một chút định dạng để thêm ý kiến
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
Việc triển khai tham chiếu C này biên dịch (với gcc -Os
) thành mã tương tự. gcc chọn chiến lược tương tự tôi đã làm, chỉ giữ một giá trị trước đó trong một thanh ghi.
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++;
}
}
Tôi đã thử nghiệm với các cách khác, bao gồm cả phiên bản x87 hai đăng ký có mã như:
; 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
Bạn sẽ làm theo cách này nếu bạn muốn tăng tốc (và SSE không có sẵn)
Đặt các tải từ bộ nhớ bên trong vòng lặp thay vì một lần vào mục nhập có thể có ích, vì chúng ta chỉ có thể lưu trữ các kết quả phụ và div không theo thứ tự, nhưng vẫn cần hai hướng dẫn FLD để thiết lập ngăn xếp khi nhập.
Tôi cũng đã thử sử dụng toán vô hướng SSE / AVX (bắt đầu bằng các giá trị trong xmm0 và xmm1), nhưng kích thước lệnh lớn hơn là sát thủ. Sử dụng addps
(vì đó là 1B ngắn hơn addss
) giúp một chút. Tôi đã sử dụng tiền tố AVX VEX cho các hướng dẫn không giao hoán, vì VSUBSS chỉ dài hơn một byte so với SUBPS (và có cùng độ dài với 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
Đã thử nghiệm với khai thác thử nghiệm này:
#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;
}
Biên dịch với nasm -felfx32 -Worphan-labels -gdwarf2 golf-stewie-sequence.asm &&
gcc -mx32 -o stewie -Og -g golf-stewie-sequence.c golf-stewie-sequence.o
Chạy trường hợp thử nghiệm đầu tiên với ./stewie 8 1 3
Nếu bạn chưa cài đặt thư viện x32, hãy sử dụng nasm -felf64
và để lại gcc bằng mặc định -m64
. Tôi đã sử dụng malloc
thay vì float seqbuf[seqlen+8]
(trên ngăn xếp) để có được một địa chỉ thấp mà không phải thực sự xây dựng như x32.
Sự thật thú vị: YASM có một lỗi: nó sử dụng rel32 jcc cho nhánh vòng lặp, khi mục tiêu nhánh có cùng địa chỉ với ký hiệu toàn cục.
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
lắp ráp để ... 11f: 0f 8f db ff ff ff jg 100 <stewie_x87_1reg>