Mức cực cao


47

Đã có một tỷ lần lặp lại các thách thức Fibonacci trên trang web này, vì vậy, hãy cho phép mọi thứ gia vị với một thách thức Fibonacci của một tỷ lần lặp!

Thách thức của bạn là xuất ra 1000 chữ số thập phân đầu tiên của số Fibonacci thứ 1.000.000.000 với chương trình càng ngắn càng tốt. Điều này sau đó có thể được theo sau bởi bất kỳ đầu ra bổ sung nào bạn chọn, bao gồm nhưng không giới hạn ở các chữ số còn lại.

Tôi đang sử dụng quy ước rằng fib 0 = 0, fib 1 = 1.

Chương trình của bạn phải đủ nhanh để bạn chạy nó và xác minh tính đúng đắn của nó. Với mục đích này, đây là 1000 chữ số đầu tiên:

7952317874554683467829385196197148189255542185234398913453039937343246686182519370050999626136556779332482035723222451226291714456275648259499530612111301255499879639516053459789018700567439946844843034599802419924043753401950114830107234265037841426980398387360784284231996457340782784200767760907777703183185744656536253511502851715963351023990699232595471322670365506482435966586886048627159716916351448788527427435508113909167963907380398242848033980110276370544264285032744364781198451825462130529529633339813483105771370128111851128247136311414208318983802526907917787094802217750859685116363883374847428036737147882079956688807509158372249451437519320162582002000530798309887261257028201907509370554232931107084976854715833585623910450679449120011564762925649144509531904684984417002512086504020779012501356177874199605085558317190905395134468919443313026824813363234190494375599262553025466528838122639433600483849535070647711986769279568548796855207684897741771784375859496425384355879105799

Your program must be fast enough for you to run it and verify its correctness.Còn trí nhớ thì sao?
Stephen

5
@ khách44851 nói ai? ;)
user1502040

1
Nếu tôi không rõ ràng, tôi nghĩ rằng một a+=b;b+=a;vòng lặp (có thể với Java BigInteger) là sự lựa chọn rõ ràng, ít nhất là nếu bạn thậm chí đang nghĩ về hiệu suất. Một triển khai đệ quy luôn có vẻ không hiệu quả khủng khiếp đối với tôi.
Peter Cordes

2
Tôi rất muốn thấy một ngôn ngữ không hỗ trợ số lượng lớn!
BradC

1
@BradC: Đó cũng là những gì tôi đã nghĩ. Mất khoảng 2 ngày để phát triển, gỡ lỗi, tối ưu hóa và chơi gôn, nhưng giờ đây, câu trả lời mã máy 32 bit x86 của tôi đã sẵn sàng (106 byte bao gồm chuyển đổi thành chuỗi và thực hiện write()cuộc gọi hệ thống). Tôi thích yêu cầu về hiệu suất, điều đó làm cho nó trở nên thú vị hơn đối với tôi.
Peter Cordes

Câu trả lời:


34

Python 2 + sympy, 72 byte

from sympy import*
n=sqrt(5)
print'7'+`((.5+n/2)**1e9/n).evalf(1e3)`[2:]

Hãy thử trực tuyến!

-10 byte bằng cách loại bỏ thuật ngữ thực tế-0 nhờ vào
byte của Jeff Dege -1 (1000 -> 1e3 nhờ Zacharý)
-2 byte bằng cách xóa biến không cần thiết nhờ Erik Outgolfer
-2 byte bằng cách chuyển sang Python 2 nhờ Zacharý
-3 byte bằng cách -11cảm ơn ThePirateBay -3 byte bằng cách hoán đổi strcho backticks nhờ notjagan

bây giờ đánh bại giải pháp haskell chưa được đăng của OP!


from sympy import*;sqrtimport sympy;sympy.sqrt
@Jonathan ALLan

Wow điều này là nhanh chóng, làm thế nào để làm việc này?
Kritixi Lithos

Tôi nghĩ rằng điều này sử dụng xấp xỉ theo cấp số nhân cho các số nguyên và lợi nhuận từ chi tiết chỉ cần các chữ số e3 đầu tiên, bởi vì điều đó tự động loại bỏ bất kỳ vấn đề nào có độ lệch so với xấp xỉ. Đúng không?
Fabian Röling

@Fabian sympylà gói toán học tượng trưng cho Python, do đó không có vấn đề với lỗi vòng, ít nhất là cho đến khi số lượng rất lớn (con số này không đủ lớn lol). Sau đó, tôi chỉ tính toán nó để đưa cho tôi các chữ số 1e3 đầu tiên bởi vì nếu không, nếu bạn loại bỏ .evalf(1e3)phần đó, nó sẽ cho tôi một đại diện ký hiệu khoa học rất ngắn.
HyperNeutrino

1
Vì sympy không phải là một phần của thư viện tiêu chuẩn của python, nên phản hồi này có vẻ không hợp lệ trừ khi bạn bao gồm nguồn của sympy trong số ký tự của bạn ... hoặc tôi hoàn toàn hiểu sai quy tắc golf?
thegreatemu

28

Python 2 , 106 byte

a,b=0,1
for c in bin(10**9):
 a,b=2*a*b-a*a,a*a+b*b
 if'1'==c:a,b=b,a+b
 while a>>3340:a/=10;b/=10
print a

Hãy thử trực tuyến!

Không có thư viện, chỉ là số học số nguyên. Chạy gần như ngay lập tức.

Cốt lõi là bản sắc phân chia và chinh phục:

f(2*n)   = 2*f(n)*f(n+1) - f(n)^2
f(2*n+1) = f(n)^2 + f(n+1)^2

Điều này cho phép chúng tôi cập nhật (a,b) = (f(n),f(n+1))lên gấp đôi n -> 2*n. Vì chúng tôi muốn nhận n=10**9, điều này chỉ mất các log_2(10**9)=30lần lặp. Chúng tôi xây dựng nlên 10**9bằng cách liên tục làm n->2*n+ccho mỗi chữ số ccủa việc mở rộng nhị phân của nó. Khi nào c==1, giá trị nhân đôi được tăng lên 2*n -> 2*n+1với sự dịch chuyển Fibonacci một bước(a,b)=(b+a,b)

Để giữ cho các giá trị a,bcó thể quản lý được, chúng tôi chỉ lưu trữ các 1006chữ số đầu tiên của chúng bằng cách chia tầng 10cho đến khi chúng ở dưới 2**3340 ~ 1e1006.


:trên mặt băng! không sử dụng thư viện tiền đề ưa thích lol. : D
HyperNeutrino

Tôi đã tìm thấy một sự tái phát dễ chịu hơn nhưng ít golf hơn , a,b,c=a*a+b*b,a*a-c*c,b*b+c*c.
Neil

21

Mã máy x86 32 bit (với các cuộc gọi hệ thống Linux): 106 105 byte

changelog: đã lưu một byte trong phiên bản nhanh vì hằng số tắt không thay đổi kết quả cho Fib (1G).

Hoặc 102 byte cho phiên bản chậm hơn 18% (trên Skylake) (sử dụng mov/ sub/ cmcthay vì lea/ cmptrong vòng lặp bên trong, để tạo ra thực hiện và gói 10**9thay vì 2**32). Hoặc 101 byte cho phiên bản chậm hơn ~ 5,3 lần với một nhánh trong xử lý mang theo trong vòng lặp bên trong nhất. (Tôi đã đo tỷ lệ dự đoán sai 25,4% chi nhánh!)

Hoặc 104/101 byte nếu số 0 đứng đầu được cho phép. (Phải mất thêm 1 byte để mã cứng bỏ qua 1 chữ số đầu ra, đây là điều cần thiết cho Fib (10 ** 9)).

Thật không may, chế độ NASM của TIO dường như bỏ qua -felf32trong các cờ biên dịch. Dù sao đây cũng là một liên kết với mã nguồn đầy đủ của tôi, với tất cả các ý tưởng thử nghiệm trong các bình luận.

Đây là một chương trình hoàn chỉnh . Nó in 1000 chữ số đầu tiên của Fib (10 ** 9) theo sau là một số chữ số phụ (một vài chữ số cuối bị sai) theo sau là một số byte rác (không bao gồm một dòng mới). Hầu hết rác không phải là ASCII, vì vậy bạn có thể muốn chuyển qua cat -v. Tuy nhiên, nó không phá vỡ trình giả lập thiết bị đầu cuối của tôi (KDE konsole). "Các byte rác" đang lưu trữ Fib (999999999). Tôi đã có -1024trong một đăng ký, vì vậy việc in 1024 byte rẻ hơn so với kích thước phù hợp.

Tôi chỉ đếm mã máy (kích thước của đoạn văn bản của tệp thực thi tĩnh của tôi), chứ không phải là lông tơ làm cho nó có thể thực thi được ELF. ( Có thể thực thi ELF rất nhỏ , nhưng tôi không muốn bận tâm đến điều đó). Hóa ra là ngắn hơn để sử dụng bộ nhớ ngăn xếp thay vì BSS, vì vậy tôi có thể biện minh rằng không tính bất cứ điều gì khác trong nhị phân vì tôi không phụ thuộc vào bất kỳ siêu dữ liệu nào. (Sản xuất một nhị phân tĩnh bị tước theo cách thông thường làm cho ELF 340 byte có thể thực thi được.)

Bạn có thể tạo một hàm từ mã này mà bạn có thể gọi từ C. Nó sẽ tốn một vài byte để lưu / khôi phục con trỏ ngăn xếp (có thể trong một thanh ghi MMX) và một số chi phí khác, nhưng cũng lưu byte bằng cách quay lại chuỗi trong bộ nhớ, thay vì thực hiện một write(1,buf,len)cuộc gọi hệ thống. Tôi nghĩ rằng chơi golf bằng mã máy sẽ giúp tôi có được một chút chùng ở đây, vì không ai khác thậm chí đã đăng một câu trả lời bằng bất kỳ ngôn ngữ nào mà không có độ chính xác mở rộng, nhưng tôi nghĩ rằng một phiên bản chức năng của điều này vẫn phải dưới 120 byte mà không phải chơi lại toàn bộ Điều.


Thuật toán:

lực lượng vũ phu a+=b; swap(a,b), cắt ngắn khi cần thiết để chỉ giữ lại hàng đầu> = 1017 chữ số thập phân. Nó chạy trong 1 phút13 trên máy tính của tôi (hoặc 322,47 tỷ chu kỳ đồng hồ + - 0,05%) (và có thể nhanh hơn vài% với một vài byte kích thước mã bổ sung, hoặc giảm xuống còn 62 giây với kích thước mã lớn hơn nhiều từ việc hủy vòng lặp. toán thông minh, chỉ làm cùng một công việc với ít chi phí). Nó dựa trên triển khai Python của @ AndersKaseorg , chạy trong 12 phút35 trên máy tính của tôi (Skylake i7-6700k 4,4 GHz). Cả hai phiên bản đều không có bất kỳ lỗi bộ nhớ cache L1D nào, vì vậy DDR4-2666 của tôi không thành vấn đề.

Không giống như Python, tôi lưu trữ các số có độ chính xác mở rộng theo định dạng giúp cắt bớt các chữ số thập phân miễn phí . Tôi lưu trữ các nhóm gồm 9 chữ số thập phân trên mỗi số nguyên 32 bit, do đó, một con trỏ bù trừ 9 chữ số thấp. Đây thực sự là cơ sở 1 tỷ, có sức mạnh bằng 10. (Điều trùng hợp hoàn toàn là thử thách này cần số Fibonacci 1 tỷ, nhưng nó giúp tôi tiết kiệm được một vài byte so với hai hằng số riêng biệt.)

Theo thuật ngữ của GMP , mỗi đoạn 32 bit của một số có độ chính xác mở rộng được gọi là "chi". Thực hiện trong khi thêm phải được tạo thủ công với so sánh với 1e9, nhưng sau đó được sử dụng bình thường làm đầu vào cho ADChướng dẫn thông thường cho chi tiếp theo. (Tôi cũng phải tự bọc theo [0..999999999]phạm vi, thay vì ở 2 ^ 32 ~ = 4.295e9. Tôi làm điều này không phân nhánh với lea+ cmov, sử dụng kết quả thực hiện từ so sánh.)

Khi chi cuối cùng tạo ra giá trị khác không, hai lần lặp tiếp theo của vòng lặp bên ngoài đọc từ 1 chi cao hơn bình thường, nhưng vẫn ghi vào cùng một vị trí. Điều này giống như thực hiện memcpy(a, a+4, 114*4)chuyển sang phải bằng 1 chi, nhưng được thực hiện như một phần của hai vòng bổ sung tiếp theo. Điều này xảy ra cứ sau 18 lần lặp.


Hacks để tiết kiệm kích thước và hiệu suất:

  • Những thứ thông thường như lea ebx, [eax-4 + 1]thay vì mov ebx, 1, khi tôi biết điều đó eax=4. Và sử dụng loopở những nơi LOOPchậm chạp chỉ có tác động nhỏ.

  • Cắt ngắn 1 chi miễn phí bằng cách bù đắp các con trỏ mà chúng ta đọc được, trong khi vẫn ghi vào đầu bộ đệm trong adcvòng lặp bên trong. Chúng tôi đọc từ [edi+edx]và viết thư cho [edi]. Vì vậy, chúng ta có thể nhận được edx=0hoặc 4để có được một phần bù đọc-ghi cho đích. Chúng ta cần làm điều này trong 2 lần lặp liên tiếp, đầu tiên là bù đắp cả hai, sau đó chỉ bù đắp cho dst. Chúng tôi phát hiện trường hợp thứ 2 bằng cách xem xét esp&4trước khi đặt lại các con trỏ về phía trước bộ đệm (sử dụng &= -1024, vì các bộ đệm được căn chỉnh). Xem ý kiến ​​trong mã.

  • Môi trường khởi động quy trình Linux (đối với thực thi tĩnh) không có hầu hết các thanh ghi và bộ nhớ ngăn xếp bên dưới esp/ rspkhông có giá trị. Chương trình của tôi tận dụng điều này. Trong phiên bản chức năng có thể gọi được của điều này (trong đó ngăn xếp chưa được phân bổ có thể bị bẩn), tôi có thể sử dụng BSS cho bộ nhớ 0 (với chi phí có thể hơn 4 byte để thiết lập con trỏ). Zeroing edxsẽ mất 2 byte. Hệ thống x86-64 V ABI không đảm bảo một trong hai điều này, nhưng việc triển khai Linux của nó không có gì (để tránh rò rỉ thông tin ra khỏi kernel). Trong một quy trình được liên kết động, /lib/ld.sochạy trước _startvà không để các thanh ghi khác không (và có thể là rác trong bộ nhớ bên dưới con trỏ ngăn xếp).

  • Tôi giữ -1024trong ebxđể sử dụng bên ngoài của vòng. Sử dụng blnhư một bộ đếm cho các vòng lặp bên trong, kết thúc bằng 0 (là byte thấp của -1024, do đó khôi phục hằng số để sử dụng bên ngoài vòng lặp). Intel Haswell và sau đó không có hình phạt hợp nhất đăng ký một phần cho các thanh ghi low8 (và thực tế thậm chí không đổi tên chúng một cách riêng biệt) , do đó, có sự phụ thuộc vào đăng ký đầy đủ, như trên AMD (không phải là vấn đề ở đây). Điều này sẽ là khủng khiếp trên Nehalem và trước đó, có các quầy đăng ký một phần khi sáp nhập. Có những nơi khác tôi viết regs một phần và sau đó đọc toàn bộ reg mà không cần xor-zerending hoặc amovzx, thông thường bởi vì tôi biết rằng một số mã trước đó đã loại bỏ các byte trên và một lần nữa điều đó tốt cho gia đình AMD và Intel SnB, nhưng chậm trên Intel trước Sandybridge.

    Tôi sử dụng 1024làm số byte để ghi vào stdout ( sub edx, ebx), vì vậy chương trình của tôi in một số byte rác sau các chữ số Fibonacci, vì mov edx, 1000chi phí nhiều byte hơn.

  • (không được sử dụng) adc ebx,ebxvới EBX = 0 để nhận EBX = CF, tiết kiệm 1 byte so với setc bl.

  • dec/ jnzbên trong một adcvòng lặp bảo tồn CF mà không gây ra tình trạng treo cờ một phần khi adcđọc cờ trên Intel Sandybridge và sau đó. Nó không tốt cho các CPU trước đó , nhưng AFAIK miễn phí trên Skylake. Hoặc tồi tệ nhất, một uop thêm.

  • Sử dụng bộ nhớ dưới đây espnhư một vùng màu đỏ khổng lồ . Vì đây là một chương trình Linux hoàn chỉnh, tôi biết rằng tôi đã không cài đặt bất kỳ trình xử lý tín hiệu nào và sẽ không có gì khác ngăn chặn bộ nhớ ngăn xếp không gian người dùng một cách không đồng bộ. Đây có thể không phải là trường hợp trên các hệ điều hành khác.

  • Tận dụng công cụ ngăn xếp để tiết kiệm băng thông phát hành uop bằng cách sử dụng pop eax(1 uop + uop đồng bộ hóa ngăn xếp thường xuyên) thay vì lodsd(2 uops trên Haswell / Skylake, 3 trên IvB và trước đó theo bảng hướng dẫn của Agner Fog )). IIRC, điều này đã giảm thời gian chạy từ khoảng 83 giây xuống 73. Tôi có thể có cùng tốc độ từ việc sử dụng movchế độ địa chỉ được lập chỉ mục, giống như mov eax, [edi+ebp]nơi ebpgiữ độ lệch giữa bộ đệm src và dst. (Nó sẽ làm cho mã bên ngoài vòng lặp bên trong trở nên phức tạp hơn, phải phủ nhận thanh ghi bù như là một phần của việc hoán đổi src và dst cho các lần lặp Fibonacci.) Xem phần "hiệu suất" bên dưới để biết thêm.

  • bắt đầu chuỗi bằng cách lặp lại lần đầu tiên (một byte stc), thay vì lưu trữ 1trong bộ nhớ ở bất cứ đâu. Rất nhiều vấn đề cụ thể khác được ghi lại trong các bình luận.

Danh sách NASM (mã máy + nguồn) , được tạo bằng nasm -felf32 fibonacci-1G.asm -l /dev/stdout | cut -b -28,$((28+12))- | sed 's/^/ /'. (Sau đó, tôi đã gỡ bỏ một số khối nội dung nhận xét, do đó, việc đánh số dòng có khoảng trống.) Để loại bỏ các cột hàng đầu để bạn có thể đưa nó vào YASM hoặc NASM, hãy sử dụng cut -b 27- <fibonacci-1G.lst > fibonacci-1G.asm.

  1          machine      global _start
  2          code         _start:
  3 address

  4 00000000 B900CA9A3B       mov    ecx, 1000000000       ; Fib(ecx) loop counter
  5                       ;    lea    ebp, [ecx-1]          ;  base-1 in the base(pointer) register ;)
  6 00000005 89CD             mov    ebp, ecx    ; not wrapping on limb==1000000000 doesn't change the result.
  7                                              ; It's either self-correcting after the next add, or shifted out the bottom faster than Fib() grows.
  8                       
 42                       
 43                       ;    mov    esp, buf1
 44                       
 45                       ;    mov    esi, buf1   ; ungolfed: static buffers instead of the stack
 46                       ;    mov    edi, buf2

 47 00000007 BB00FCFFFF       mov    ebx, -1024
 48 0000000C 21DC             and    esp, ebx    ; alignment necessary for convenient pointer-reset
 49                       ;    sar    ebx, 1
 50 0000000E 01DC             add    esp, ebx     ; lea    edi, [esp + ebx].  Can't skip this: ASLR or large environment can put ESP near the bottom of a 1024-byte block to start with
 51 00000010 8D3C1C           lea    edi, [esp + ebx*1]
 52                           ;xchg   esp, edi   ; This is slightly faster.  IDK why.
 53                       
 54                           ; It's ok for EDI to be below ESP by multiple 4k pages.  On Linux, IIRC the main stack automatically extends up to ulimit -s, even if you haven't adjusted ESP.  (Earlier I used -4096 instead of -1024)
 55                           ; After an even number of swaps, EDI will be pointing to the lower-addressed buffer
 56                           ; This allows a small buffer size without having the string step on the number.
 57
 58                       ; registers that are zero at process startup, which we depend on:
 59                       ;    xor   edx, edx
 60                       ;;  we also depend on memory far below initial ESP being zeroed.
 61
 62 00000013 F9               stc    ; starting conditions: both buffers zeroed, but carry-in = 1
 63                       ; starting Fib(0,1)->0,1,1,2,3 vs. Fib(1,0)->1,0,1,1,2 starting "backwards" puts us 1 count behind
 66
 67                       ;;; register usage:
 68                       ;;; eax, esi: scratch for the adc inner loop, and outer loop
 69                       ;;; ebx: -1024.  Low byte is used as the inner-loop limb counter (ending at zero, restoring the low byte of -1024)
 70                       ;;; ecx: outer-loop Fibonacci iteration counter
 71                       ;;; edx: dst read-write offset (for "right shifting" to discard the least-significant limb)
 72                       ;;; edi: dst pointer
 73                       ;;; esp: src pointer
 74                       ;;; ebp: base-1 = 999999999.  Actually still happens to work with ebp=1000000000.
 75
 76                       .fibonacci:
 77                       limbcount equ 114             ; 112 = 1006 decimal digits / 9 digits per limb.  Not enough for 1000 correct digits, but 114 is.
 78                                                     ; 113 would be enough, but we depend on limbcount being even to avoid a sub
 79 00000014 B372             mov    bl, limbcount
 80                       .digits_add:
 81                           ;lodsd                       ; Skylake: 2 uops.  Or  pop rax  with rsp instead of rsi
 82                       ;    mov    eax, [esp]
 83                       ;    lea    esp, [esp+4]   ; adjust ESP without affecting CF.  Alternative, load relative to edi and negate an offset?  Or add esp,4 after adc before cmp
 84 00000016 58               pop    eax
 85 00000017 130417           adc    eax, [edi + edx*1]    ; read from a potentially-offset location (but still store to the front)
 86                        ;; jz .out   ;; Nope, a zero digit in the result doesn't mean the end!  (Although it might in base 10**9 for this problem)
 87
 88                       %if 0   ;; slower version
                          ;; could be even smaller (and 5.3x slower) with a branch on CF: 25% mispredict rate
 89                           mov  esi, eax
 90                           sub  eax, ebp  ; 1000000000 ; sets CF opposite what we need for next iteration
 91                           cmovc eax, esi
 92                           cmc                         ; 1 extra cycle of latency for the loop-carried dependency. 38,075Mc for 100M iters (with stosd).
 93                                                       ; not much worse: the 2c version bottlenecks on the front-end bottleneck
 94                       %else   ;; faster version
 95 0000001A 8DB0003665C4     lea    esi, [eax - 1000000000]
 96 00000020 39C5             cmp    ebp, eax                ; sets CF when (base-1) < eax.  i.e. when eax>=base
 97 00000022 0F42C6           cmovc  eax, esi                ; eax %= base, keeping it in the [0..base) range
 98                       %endif
 99                       
100                       %if 1
101 00000025 AB               stosd                          ; Skylake: 3 uops.  Like add + non-micro-fused store.  32,909Mcycles for 100M iters (with lea/cmp, not sub/cmc)
102                       %else
103                         mov    [edi], eax                ; 31,954Mcycles for 100M iters: faster than STOSD
104                         lea   edi, [edi+4]               ; Replacing this with ADD EDI,4 before the CMP is much slower: 35,083Mcycles for 100M iters
105                       %endif
106                       
107 00000026 FECB             dec    bl                      ; preserves CF.  The resulting partial-flag merge on ADC would be slow on pre-SnB CPUs
108 00000028 75EC             jnz .digits_add
109                           ; bl=0, ebx=-1024
110                           ; esi has its high bit set opposite to CF
111                       .end_innerloop:
112                           ;; after a non-zero carry-out (CF=1): right-shift both buffers by 1 limb, over the course of the next two iterations
113                           ;; next iteration with r8 = 1 and rsi+=4:  read offset from both, write normal.  ends with CF=0
114                           ;; following iter with r8 = 1 and rsi+=0:  read offset from dest, write normal.  ends with CF=0
115                           ;; following iter with r8 = 0 and rsi+=0:  i.e. back to normal, until next carry-out (possible a few iters later)
116                       
117                           ;; rdi = bufX + 4*limbcount
118                           ;; rsi = bufY + 4*limbcount + 4*carry_last_time
119                       
120                       ;    setc   [rdi]
123 0000002A 0F92C2           setc   dl
124 0000002D 8917             mov    [edi], edx ; store the carry-out into an extra limb beyond limbcount
125 0000002F C1E202           shl    edx, 2

139                           ; keep -1024 in ebx.  Using bl for the limb counter leaves bl zero here, so it's back to -1024 (or -2048 or whatever)
142 00000032 89E0             mov    eax, esp   ; test/setnz could work, but only saves a byte if we can somehow avoid the  or dl,al
143 00000034 2404             and    al, 4      ; only works if limbcount is even, otherwise we'd need to subtract limbcount first.

148 00000036 87FC             xchg   edi, esp   ; Fibonacci: dst and src swap
149 00000038 21DC             and    esp, ebx  ; -1024  ; revert to start of buffer, regardless of offset
150 0000003A 21DF             and    edi, ebx  ; -1024
151                       
152 0000003C 01D4             add    esp, edx             ; read offset in src

155                           ;; after adjusting src, so this only affects read-offset in the dst, not src.
156 0000003E 08C2             or    dl, al              ; also set r8d if we had a source offset last time, to handle the 2nd buffer
157                           ;; clears CF for next iter

165 00000040 E2D2             loop .fibonacci  ; Maybe 0.01% slower than dec/jnz overall

169                       to_string:

175                       stringdigits equ 9*limbcount  ; + 18
176                       ;;; edi and esp are pointing to the start of buffers, esp to the one most recently written
177                       ;;;  edi = esp +/- 2048, which is far enough away even in the worst case where they're growing towards each other
178                       ;;;  update: only 1024 apart, so this only works for even iteration-counts, to prevent overlap

180                           ; ecx = 0 from the end of the fib loop
181                           ;and   ebp, 10     ; works because the low byte of 999999999 is 0xff
182 00000042 8D690A           lea    ebp, [ecx+10]         ;mov    ebp, 10
183 00000045 B172             mov    cl, (stringdigits+8)/9
184                       .toascii:  ; slow but only used once, so we don't need a multiplicative inverse to speed up div by 10
185                           ;add   eax, [rsi]    ; eax has the carry from last limb:  0..3  (base 4 * 10**9)
186 00000047 58               pop    eax                  ; lodsd
187 00000048 B309             mov    bl, 9
188                       .toascii_digit:
189 0000004A 99               cdq                         ; edx=0 because eax can't have the high bit set
190 0000004B F7F5             div    ebp                  ; edx=remainder = low digit = 0..9.  eax/=10

197 0000004D 80C230           add    dl, '0'
198                                              ; stosb  ; clobber [rdi], then  inc rdi
199 00000050 4F               dec    edi         ; store digits in MSD-first printing order, working backwards from the end of the string
200 00000051 8817             mov    [edi], dl
201                       
202 00000053 FECB             dec    bl
203 00000055 75F3             jnz  .toascii_digit
204                       
205 00000057 E2EE             loop .toascii
206                       
207                           ; Upper bytes of eax=0 here.  Also AL I think, but that isn't useful
208                           ; ebx = -1024
209 00000059 29DA             sub  edx, ebx   ; edx = 1024 + 0..9 (leading digit).  +0 in the Fib(10**9) case
210                       
211 0000005B B004             mov   al, 4                 ; SYS_write
212 0000005D 8D58FD           lea  ebx, [eax-4 + 1]       ; fd=1
213                           ;mov  ecx, edi               ; buf
214 00000060 8D4F01           lea  ecx, [edi+1]           ; Hard-code for Fib(10**9), which has one leading zero in the highest limb.
215                       ;    shr  edx, 1 ;    for use with edx=2048
216                       ;    mov  edx, 100
217                       ;    mov byte  [ecx+edx-1], 0xa;'\n'  ; count+=1 for newline
218 00000063 CD80             int  0x80                   ; write(1, buf+1, 1024)
219                       
220 00000065 89D8             mov  eax, ebx ; SYS_exit=1
221 00000067 CD80             int  0x80     ; exit(ebx=1)
222                       
  # next byte is 0x69, so size = 0x69 = 105 bytes

Có lẽ có chỗ để chơi golf thêm vài byte trong số này, nhưng tôi đã dành ít nhất 12 giờ cho việc này hơn 2 ngày. Tôi không muốn hy sinh tốc độ, mặc dù nó đủ nhanh hơn và có chỗ để làm cho nó nhỏ hơn theo cách tốn chi phí . Một phần lý do của tôi để đăng là cho thấy tôi có thể tạo phiên bản asm brute-force nhanh như thế nào. Nếu bất cứ ai muốn thực sự đi cho kích thước tối thiểu nhưng có thể chậm hơn 10 lần (ví dụ 1 chữ số trên mỗi byte), vui lòng sao chép này làm điểm bắt đầu.

Kết quả thực thi (từ yasm -felf32 -Worphan-labels -gdwarf2 fibonacci-1G.asm && ld -melf_i386 -o fibonacci-1G fibonacci-1G.o) là 340B (tước):

size fibonacci-1G
 text    data     bss     dec     hex filename
  105       0       0     105      69 fibonacci-1G

Hiệu suất

adcVòng lặp bên trong là 10 uop ​​miền hợp nhất trên Skylake (uop đồng bộ hóa +1 mỗi ~ 128 byte), do đó, nó có thể phát hành ở một chu kỳ trên 2,5 chu kỳ trên Skylake với thông lượng đầu cuối tối ưu (bỏ qua các uops đồng bộ hóa ngăn xếp) . Độ trễ của đường dẫn quan trọng là 2 chu kỳ, đối với chuỗi phụ thuộc vòng lặp mang theo vòng lặp adc-> cmp-> vòng lặp tiếp theo adc, do đó, nút cổ chai phải là giới hạn sự cố phía trước ~ 2,5 chu kỳ trên mỗi lần lặp.

adc eax, [edi + edx]là 2 uops miền không sử dụng cho các cổng thực thi: load + ALU. Nó vi cầu chì trong bộ giải mã (1 uop miền hợp nhất), nhưng không ghép nối trong giai đoạn phát hành thành 2 uops miền hợp nhất, vì chế độ đánh địa chỉ được lập chỉ mục, ngay cả trên Haswell / Skylake . Tôi nghĩ rằng nó sẽ duy trì trạng thái hợp nhất, giống như add eax, [edi + edx]vậy, nhưng có lẽ việc giữ các chế độ địa chỉ được lập chỉ mục mà micro-fuse không hoạt động đối với các uops đã có 3 đầu vào (cờ, bộ nhớ và đích). Khi tôi viết nó, tôi đã nghĩ rằng nó sẽ không có nhược điểm về hiệu suất, nhưng tôi đã sai. Cách xử lý cắt ngắn này làm chậm vòng lặp bên trong mỗi lần, cho dù edxlà 0 hay 4.

Sẽ nhanh hơn để xử lý bù đọc-ghi cho dst bằng cách bù đắp edivà sử dụng edxđể điều chỉnh cửa hàng. Vì vậy, adc eax, [edi]/ ... / mov [edi+edx], eax/ lea edi, [edi+4]thay vì stosd. Haswell và sau này có thể giữ một cửa hàng được lập chỉ mục micro-fuse. (Sandybridge / IvB cũng sẽ kiểm tra nó.)

Trên Intel Haswell và trước đó, adccmovclà 2 uops, với độ trễ 2c . ( adc eax, [edi+edx]vẫn chưa được dán nhiều lớp trên Haswell và phát hành dưới dạng 3 uops miền hợp nhất). Broadwell và sau đó cho phép các vòng 3 đầu vào không chỉ là FMA (Haswell), thực hiện adccmovc(và một vài thứ khác) các hướng dẫn đơn lẻ, giống như chúng đã có trên AMD trong một thời gian dài. (Đây là một lý do AMD đã thực hiện tốt các tiêu chuẩn chuẩn độ mở rộng trong một thời gian dài.) iter trường hợp tốt nhất, bỏ qua các uops stack-sync.

Sử dụng popmà không có sự cân bằng pushbên trong một vòng lặp có nghĩa là vòng lặp không thể chạy từ LSD (trình phát luồng vòng lặp) và phải được đọc lại từ bộ đệm uop vào IDQ mỗi lần. Nếu bất cứ điều gì, đó là một điều tốt trên Skylake, vì vòng lặp 9 hoặc 10 uop ​​không hoàn toàn tối ưu hóa ở 4 vòng mỗi chu kỳ . Đây có lẽ là một phần lý do tại sao thay thế lodsdbằng popgiúp rất nhiều. (LSD không thể khóa các vòng lặp vì điều đó sẽ không còn chỗ để chèn một uop đồng bộ hóa ngăn xếp .) (BTW, một bản cập nhật vi mã vô hiệu hóa hoàn toàn LSD trên Skylake và Skylake-X để sửa lỗi. Tôi đã đo ở trên trước khi nhận được bản cập nhật đó.)

Tôi đã định hình nó trên Haswell và thấy rằng nó chạy trong 380,31 tỷ chu kỳ xung nhịp (bất kể tần số CPU, vì nó chỉ sử dụng bộ đệm L1D, không phải bộ nhớ). Thông lượng vấn đề mặt trước là 3,72 uops miền hợp nhất trên mỗi đồng hồ, so với 3,70 đối với Skylake. (Nhưng tất nhiên hướng dẫn trên mỗi chu kỳ đã giảm xuống 2,42 từ 2,87, bởi vì adccmovlà 2 uops trên Haswell.)

pushđể thay thế stosdcó lẽ sẽ không giúp được gì nhiều, bởi vì adc [esp + edx]sẽ kích hoạt một uop đồng bộ hóa ngăn xếp mỗi lần. Và sẽ có chi phí một byte cho stdnên lodsdđi theo một hướng khác. ( mov [edi], eax/ lea edi, [edi+4]để thay thế stosdlà một chiến thắng, đi từ 32.909 Xe máy cho 100 triệu iters đến 31.954 Xe máy cho 100 triệu iters. Có vẻ như stosdgiải mã là 3 uops, với các uops địa chỉ cửa hàng / dữ liệu lưu trữ không được hợp nhất, do đó push+ đồng bộ hóa ngăn xếp uops vẫn có thể nhanh hơn stosd)

Hiệu suất thực tế của ~ 322,47 tỷ chu kỳ cho các lần lặp 1G của 114 chi hoạt động với 2.824 chu kỳ trên mỗi lần lặp của vòng lặp bên trong , cho phiên bản 105B nhanh trên Skylake. (Xem ocperf.pyđầu ra bên dưới). Điều đó chậm hơn tôi dự đoán từ phân tích tĩnh, nhưng tôi đã bỏ qua chi phí hoạt động của vòng lặp ngoài và bất kỳ vòng lặp đồng bộ hóa nào.

Perf quầy cho branchesbranch-missescho thấy rằng mispredicts vòng lặp bên trong một lần mỗi vòng ngoài (trên lặp cuối cùng, khi nó không được thực hiện). Điều đó cũng chiếm một phần của thời gian thêm.


Tôi có thể lưu kích thước mã bằng cách tạo vòng lặp bên trong nhất có độ trễ 3 chu kỳ cho đường dẫn quan trọng, sử dụng mov esi,eax/ sub eax,ebp/ cmovc eax, esi/cmc (2 + 2 + 3 + 1 = 8B) thay vì lea esi, [eax - 1000000000]/ cmp ebp,eax/ cmovc(6 + 2 + 3 = 11B ). Các cmov/ stosdlà ra khỏi con đường quan trọng. (Công cụ tăng dần stosdcó thể chạy tách biệt khỏi cửa hàng, do đó, mỗi lần lặp sẽ loại bỏ một chuỗi phụ thuộc ngắn.) Nó đã sử dụng để tiết kiệm 1B khác bằng cách thay đổi hướng dẫn ebp init từ lea ebp, [ecx-1]sang mov ebp,eax, nhưng tôi phát hiện ra rằng đã saiebpkhông thay đổi kết quả. Điều này sẽ cho phép một chi chính xác == 1000000000 thay vì quấn và tạo ra một vật mang, nhưng lỗi này lan truyền chậm hơn so với chúng tôi Fib () phát triển, vì vậy điều này xảy ra không làm thay đổi các chữ số 1k hàng đầu của kết quả cuối cùng. Ngoài ra, tôi nghĩ rằng lỗi đó có thể tự sửa khi chúng ta chỉ cần thêm, vì có chỗ trong một chi để giữ nó mà không bị tràn. Ngay cả 1G + 1G cũng không vượt quá số nguyên 32 bit, do đó, cuối cùng nó sẽ thấm dần lên hoặc bị cắt bớt.

Phiên bản độ trễ 3c là 1 uop thêm, do đó, front-end có thể phát hành nó với tốc độ một trên 2,75c trên Skylake, chỉ nhanh hơn một chút so với back-end có thể chạy nó. (Trên Haswell, nó sẽ có tổng số 13 uops vì nó vẫn sử dụng adccmov, và nút cổ chai ở mặt trước ở mức 3,25c mỗi lần lặp).

Trong thực tế, nó chạy hệ số chậm hơn 1,18 trên Skylake (3,34 chu kỳ trên một chi), thay vì 3 / 2,5 = 1,2 mà tôi dự đoán để thay thế nút cổ chai phía trước bằng nút cổ chai trễ khi chỉ nhìn vào vòng lặp bên trong mà không đồng bộ hóa ngăn xếp ôi. Vì các nút đồng bộ hóa ngăn xếp chỉ làm tổn thương phiên bản nhanh (bị tắc nghẽn ở mặt trước thay vì độ trễ), nên không cần phải giải thích nhiều về nó. ví dụ 3 / 2.54 = 1.18.

Một yếu tố khác là phiên bản độ trễ 3c có thể phát hiện ra dự đoán sai khi rời khỏi vòng lặp bên trong trong khi đường dẫn quan trọng vẫn đang thực thi (vì giao diện người dùng có thể vượt lên phía trước, để cho việc thực hiện không theo thứ tự chạy vòng lặp- truy cập uops), vì vậy hình phạt dự đoán sai hiệu quả là thấp hơn. Mất các chu kỳ front-end cho phép back-end bắt kịp.

Nếu không phải như vậy, chúng ta có thể tăng tốc cmcphiên bản 3c bằng cách sử dụng một nhánh ở vòng ngoài thay vì xử lý không phân nhánh của carryDef -> edx và đặc biệt. Dự đoán chi nhánh + thực thi đầu cơ cho một phụ thuộc điều khiển thay vì phụ thuộc dữ liệu có thể cho phép lần lặp tiếp theo bắt đầu chạy adcvòng lặp trong khi các vòng lặp từ vòng lặp bên trong trước đó vẫn đang hoạt động. Trong phiên bản không phân nhánh, các địa chỉ tải trong vòng lặp bên trong có sự phụ thuộc dữ liệu vào CF từ chi cuối cùng adccủa chi cuối cùng.

Các nút thắt phiên bản bên trong độ trễ 2c ở mặt trước, do đó, back-end khá nhiều theo kịp. Nếu mã vòng ngoài có độ trễ cao, giao diện người dùng có thể nhận được các lệnh phát hành trước từ lần lặp tiếp theo của vòng lặp bên trong. (Nhưng trong trường hợp này, công cụ vòng ngoài có nhiều ILP và không có công cụ có độ trễ cao, do đó, back-end không bắt kịp để làm gì khi nó bắt đầu nhai qua các vòng trong trình lập lịch không theo thứ tự như đầu vào của họ trở nên sẵn sàng).

### Output from a profiled run
$ asm-link -m32 fibonacci-1G.asm && (size fibonacci-1G; echo disas fibonacci-1G) && ocperf.py stat -etask-clock,context-switches:u,cpu-migrations:u,page-faults:u,cycles,instructions,uops_issued.any,uops_executed.thread,uops_executed.stall_cycles -r4  ./fibonacci-1G
+ yasm -felf32 -Worphan-labels -gdwarf2 fibonacci-1G.asm
+ ld -melf_i386 -o fibonacci-1G fibonacci-1G.o
   text    data     bss     dec     hex filename
    106       0       0     106      6a fibonacci-1G
disas fibonacci-1G
perf stat -etask-clock,context-switches:u,cpu-migrations:u,page-faults:u,cycles,instructions,cpu/event=0xe,umask=0x1,name=uops_issued_any/,cpu/event=0xb1,umask=0x1,name=uops_executed_thread/,cpu/event=0xb1,umask=0x1,inv=1,cmask=1,name=uops_executed_stall_cycles/ -r4 ./fibonacci-1G
79523178745546834678293851961971481892555421852343989134530399373432466861825193700509996261365567793324820357232224512262917144562756482594995306121113012554998796395160534597890187005674399468448430345998024199240437534019501148301072342650378414269803983873607842842319964573407827842007677609077777031831857446565362535115028517159633510239906992325954713226703655064824359665868860486271597169163514487885274274355081139091679639073803982428480339801102763705442642850327443647811984518254621305295296333398134831057713701281118511282471363114142083189838025269079177870948022177508596851163638833748474280367371478820799566888075091583722494514375193201625820020005307983098872612570282019075093705542329311070849768547158335856239104506794491200115647629256491445095319046849844170025120865040207790125013561778741996050855583171909053951344689194433130268248133632341904943755992625530254665288381226394336004838495350706477119867692795685487968552076848977417717843758594964253843558791057997424878788358402439890396,�X\�;3�I;ro~.�'��R!q��%��X'B ��      8w��▒Ǫ�
 ... repeated 3 more times, for the 3 more runs we're averaging over
  Note the trailing garbage after the trailing digits.

 Performance counter stats for './fibonacci-1G' (4 runs):

      73438.538349      task-clock:u (msec)       #    1.000 CPUs utilized            ( +-  0.05% )
                 0      context-switches:u        #    0.000 K/sec                  
                 0      cpu-migrations:u          #    0.000 K/sec                  
                 2      page-faults:u             #    0.000 K/sec                    ( +- 11.55% )
   322,467,902,120      cycles:u                  #    4.391 GHz                      ( +-  0.05% )
   924,000,029,608      instructions:u            #    2.87  insn per cycle           ( +-  0.00% )
 1,191,553,612,474      uops_issued_any:u         # 16225.181 M/sec                   ( +-  0.00% )
 1,173,953,974,712      uops_executed_thread:u    # 15985.530 M/sec                   ( +-  0.00% )
     6,011,337,533      uops_executed_stall_cycles:u #   81.855 M/sec                    ( +-  1.27% )

      73.436831004 seconds time elapsed                                          ( +-  0.05% )

( +- x %)là độ lệch chuẩn trong 4 lần chạy cho số đó. Thật thú vị khi nó chạy một số lượng lớn các hướng dẫn. 924 tỷ đó không phải là ngẫu nhiên. Tôi đoán rằng vòng lặp bên ngoài chạy tổng cộng 924 hướng dẫn.

uops_issuedlà số lượng miền hợp nhất (có liên quan đến băng thông của sự cố phía trước), trong khi đó uops_executedlà số lượng miền không được sử dụng (số lượng uops được gửi đến các cổng thực thi). Micro-fusion gói 2 uop miền không sử dụng vào một uop miền hợp nhất, nhưng loại bỏ Mov có nghĩa là một số uops miền hợp nhất không cần bất kỳ cổng thực thi nào. Xem câu hỏi được liên kết để biết thêm về cách đếm uops và miền hợp nhất với miền không được sử dụng. (Cũng xem bảng hướng dẫn của Agner Fog và hướng dẫn uarch và các liên kết hữu ích khác trong wiki thẻ SO x86 ).

Từ một lần chạy khác, đo lường những thứ khác nhau: bộ nhớ cache L1D hoàn toàn không đáng kể, như mong đợi để đọc / ghi hai bộ đệm 456B giống nhau. Nhánh vòng lặp bên trong đánh giá sai một lần cho mỗi vòng lặp bên ngoài (khi nó không được thực hiện để rời khỏi vòng lặp). (Tổng thời gian cao hơn vì máy tính không hoàn toàn nhàn rỗi. Có lẽ lõi logic khác đã hoạt động một thời gian và nhiều thời gian hơn đã bị gián đoạn (vì tần số đo không gian của người dùng ở xa hơn 4.400GHz). Hoặc nhiều lõi đã hoạt động nhiều hơn trong thời gian ngắn, làm giảm tối đa turbo. Tôi không theo dõi cpu_clk_unhalted.one_thread_activeđể xem liệu cạnh tranh HT có phải là một vấn đề không.)

     ### Another run of the same 105/106B "main" version to check other perf counters
      74510.119941      task-clock:u (msec)       #    1.000 CPUs utilized          
                 0      context-switches:u        #    0.000 K/sec                  
                 0      cpu-migrations:u          #    0.000 K/sec                  
                 2      page-faults:u             #    0.000 K/sec                  
   324,455,912,026      cycles:u                  #    4.355 GHz                    
   924,000,036,632      instructions:u            #    2.85  insn per cycle         
   228,005,015,542      L1-dcache-loads:u         # 3069.535 M/sec
           277,081      L1-dcache-load-misses:u   #    0.00% of all L1-dcache hits
                 0      ld_blocks_partial_address_alias:u #    0.000 K/sec                  
   115,000,030,234      branches:u                # 1543.415 M/sec                  
     1,000,017,804      branch-misses:u           #    0.87% of all branches        

Mã của tôi có thể chạy tốt hơn trong các chu kỳ ít hơn trên Ryzen, có thể phát hành 5 vòng trên mỗi chu kỳ (hoặc 6 khi một số trong số chúng là các hướng dẫn 2 uop, như công cụ AVX 256b trên Ryzen). Tôi không chắc chắn phần đầu của nó sẽ làm gì stosd, đó là 3 uops trên Ryzen (giống như Intel). Tôi nghĩ rằng các hướng dẫn khác trong vòng lặp bên trong có cùng độ trễ như Skylake và tất cả các uop đơn. (Bao gồm adc eax, [edi+edx], đó là một lợi thế so với Skylake).


Điều này có thể nhỏ hơn đáng kể, nhưng có thể chậm hơn 9 lần, nếu tôi lưu trữ các số dưới dạng 1 chữ số thập phân trên mỗi byte . Tạo ra mang đi cmpvà điều chỉnh với cmovsẽ hoạt động như nhau, nhưng thực hiện 1/9 công việc. 2 chữ số thập phân trên mỗi byte (cơ sở 100, không phải BCD 4 bit chậmDAA ) cũng sẽ hoạt động và div r8/ add ax, 0x3030biến 0-99 byte thành hai chữ số ASCII theo thứ tự in. Nhưng 1 chữ số trên mỗi byte hoàn toàn không cần div, chỉ cần lặp và thêm 0x30. Nếu tôi lưu trữ các byte theo thứ tự in, điều đó sẽ làm cho vòng lặp thứ 2 thực sự đơn giản.


Sử dụng 18 hoặc 19 chữ số thập phân cho mỗi số nguyên 64 bit (ở chế độ 64 bit) sẽ giúp nó chạy nhanh gấp đôi, nhưng chi phí kích thước mã đáng kể cho tất cả các tiền tố REX và cho các hằng 64 bit. Tay chân 32 bit ở chế độ 64 bit ngăn sử dụng pop eaxthay vì lodsd. Tôi vẫn có thể tránh các tiền tố REX bằng cách sử dụng espnhư một thanh ghi cào không con trỏ (hoán đổi cách sử dụng esiesp), thay vì sử dụng r8dnhư một thanh ghi thứ 8.

Nếu tạo phiên bản chức năng có thể gọi được, chuyển đổi thành 64 bit và sử dụng r8dcó thể rẻ hơn so với lưu / khôi phục rsp. 64 bit cũng không thể sử dụng dec r32mã hóa một byte (vì đó là tiền tố REX). Nhưng chủ yếu tôi đã kết thúc bằng dec bl2 byte. (Bởi vì tôi có một hằng số ở các byte trên ebxvà chỉ sử dụng nó bên ngoài các vòng lặp bên trong, hoạt động vì byte thấp của hằng số là 0x00.)


Phiên bản hiệu suất cao

Để có hiệu suất tối đa (không phải là môn đánh gôn), bạn muốn hủy kiểm soát vòng lặp bên trong để nó chạy tối đa 22 lần lặp, đây là mô hình được thực hiện / không thực hiện đủ ngắn để các công cụ dự đoán nhánh hoạt động tốt. Trong các thí nghiệm của tôi, mov cl, 22trước một .inner: dec cl/jnz .innervòng lặp có rất ít sai sót (như 0,05%, ít hơn một lần cho mỗi lần chạy hoàn toàn của vòng lặp bên trong), nhưng lại đánh giá mov cl,23sai từ 0,35 đến 0,6 lần cho mỗi vòng lặp bên trong. 46là đặc biệt xấu, dự đoán sai ~ 1,28 lần cho mỗi vòng lặp bên trong (128M lần cho các vòng lặp vòng ngoài 100M). 114dự đoán sai chính xác một lần cho mỗi vòng lặp bên trong, giống như tôi đã tìm thấy như là một phần của vòng lặp Fibonacci.

Tôi đã tò mò và thử nó, bỏ vòng lặp bên trong bằng 6 %rep 6(vì nó chia đều 114). Điều đó chủ yếu loại bỏ các chi nhánh bỏ lỡ. Tôi đã làm edxtiêu cực và sử dụng nó như một sự bù đắp cho movcác cửa hàng, vì vậy adc eax,[edi]có thể vẫn còn hợp nhất. (Và vì vậy tôi có thể tránh stosd). Tôi đã kéo leabản cập nhật edira khỏi %repkhối, vì vậy nó chỉ thực hiện một cập nhật con trỏ trên 6 cửa hàng.

Tôi cũng đã loại bỏ tất cả những thứ đăng ký một phần ở vòng ngoài, mặc dù tôi không nghĩ đó là điều quan trọng. Nó có thể giúp một chút để có CF ở cuối vòng ngoài không phụ thuộc vào ADC cuối cùng, vì vậy một số vòng lặp trong vòng có thể bắt đầu. Mã vòng ngoài có lẽ có thể được tối ưu hóa hơn một chút, vì đó neg edxlà điều cuối cùng tôi đã làm, sau khi thay thế xchgchỉ bằng 2 movhướng dẫn (vì tôi vẫn còn 1) và sắp xếp lại chuỗi dep cùng với việc thả 8 bit đăng ký công cụ.

Đây là nguồn NASM của vòng lặp Fibonacci. Đây là phần thay thế thả xuống cho phần đó của phiên bản gốc.

  ;;;; Main loop, optimized for performance, not code-size
%assign unrollfac 6
    mov    bl, limbcount/unrollfac  ; and at the end of the outer loop
    align 32
.fibonacci:
limbcount equ 114             ; 112 = 1006 decimal digits / 9 digits per limb.  Not enough for 1000 correct digits, but 114 is.
                              ; 113 would be enough, but we depend on limbcount being even to avoid a sub
;    align 8
.digits_add:

%assign i 0
%rep unrollfac
    ;lodsd                       ; Skylake: 2 uops.  Or  pop rax  with rsp instead of rsi
;    mov    eax, [esp]
;    lea    esp, [esp+4]   ; adjust ESP without affecting CF.  Alternative, load relative to edi and negate an offset?  Or add esp,4 after adc before cmp
    pop    eax
    adc    eax, [edi+i*4]    ; read from a potentially-offset location (but still store to the front)
 ;; jz .out   ;; Nope, a zero digit in the result doesn't mean the end!  (Although it might in base 10**9 for this problem)

    lea    esi, [eax - 1000000000]
    cmp    ebp, eax                ; sets CF when (base-1) < eax.  i.e. when eax>=base
    cmovc  eax, esi                ; eax %= base, keeping it in the [0..base) range
%if 0
    stosd
%else
  mov    [edi+i*4+edx], eax
%endif
%assign i i+1
%endrep
  lea   edi, [edi+4*unrollfac]

    dec    bl                      ; preserves CF.  The resulting partial-flag merge on ADC would be slow on pre-SnB CPUs
    jnz .digits_add
    ; bl=0, ebx=-1024
    ; esi has its high bit set opposite to CF
.end_innerloop:
    ;; after a non-zero carry-out (CF=1): right-shift both buffers by 1 limb, over the course of the next two iterations
    ;; next iteration with r8 = 1 and rsi+=4:  read offset from both, write normal.  ends with CF=0
    ;; following iter with r8 = 1 and rsi+=0:  read offset from dest, write normal.  ends with CF=0
    ;; following iter with r8 = 0 and rsi+=0:  i.e. back to normal, until next carry-out (possible a few iters later)

    ;; rdi = bufX + 4*limbcount
    ;; rsi = bufY + 4*limbcount + 4*carry_last_time

;    setc   [rdi]
;    mov    dl, dh               ; edx=0.  2c latency on SKL, but DH has been ready for a long time
;    adc    edx,edx    ; edx = CF.  1B shorter than setc dl, but requires edx=0 to start
    setc   al
    movzx  edx, al
    mov    [edi], edx ; store the carry-out into an extra limb beyond limbcount
    shl    edx, 2
    ;; Branching to handle the truncation would break the data-dependency (of pointers) on carry-out from this iteration
    ;;  and let the next iteration start, but we bottleneck on the front-end (9 uops)
    ;;  not the loop-carried dependency of the inner loop (2 cycles for adc->cmp -> flag input of adc next iter)
    ;; Since the pattern isn't perfectly regular, branch mispredicts would hurt us

    ; keep -1024 in ebx.  Using bl for the limb counter leaves bl zero here, so it's back to -1024 (or -2048 or whatever)
    mov    eax, esp
    and    esp, 4               ; only works if limbcount is even, otherwise we'd need to subtract limbcount first.

    and    edi, ebx  ; -1024    ; revert to start of buffer, regardless of offset
    add    edi, edx             ; read offset in next iter's src
    ;; maybe   or edi,edx / and edi, 4 | -1024?  Still 2 uops for the same work
    ;;  setc dil?

    ;; after adjusting src, so this only affects read-offset in the dst, not src.
    or     edx, esp             ; also set r8d if we had a source offset last time, to handle the 2nd buffer
    mov    esp, edi

;    xchg   edi, esp   ; Fibonacci: dst and src swap
    and    eax, ebx  ; -1024

    ;; mov    edi, eax
    ;; add    edi, edx
    lea    edi, [eax+edx]
    neg    edx            ; negated read-write offset used with store instead of load, so adc can micro-fuse

    mov    bl, limbcount/unrollfac
    ;; Last instruction must leave CF clear for next iter
;    loop .fibonacci  ; Maybe 0.01% slower than dec/jnz overall
;    dec ecx
    sub ecx, 1                  ; clear any flag dependencies.  No faster than dec, at least when CF doesn't depend on edx
    jnz .fibonacci

Hiệu suất:

 Performance counter stats for './fibonacci-1G-performance' (3 runs):

      62280.632258      task-clock (msec)         #    1.000 CPUs utilized            ( +-  0.07% )
                 0      context-switches:u        #    0.000 K/sec                  
                 0      cpu-migrations:u          #    0.000 K/sec                  
                 3      page-faults:u             #    0.000 K/sec                    ( +- 12.50% )
   273,146,159,432      cycles                    #    4.386 GHz                      ( +-  0.07% )
   757,088,570,818      instructions              #    2.77  insn per cycle           ( +-  0.00% )
   740,135,435,806      uops_issued_any           # 11883.878 M/sec                   ( +-  0.00% )
   966,140,990,513      uops_executed_thread      # 15512.704 M/sec                   ( +-  0.00% )
    75,953,944,528      resource_stalls_any       # 1219.544 M/sec                    ( +-  0.23% )
       741,572,966      idq_uops_not_delivered_core #   11.907 M/sec                    ( +- 54.22% )

      62.279833889 seconds time elapsed                                          ( +-  0.07% )

Đó là cho cùng một Fib (1G), tạo ra cùng một đầu ra trong 62,3 giây thay vì 73 giây. (273.146G chu kỳ, so với 322.467G. Vì mọi thứ đều đạt được trong bộ đệm L1, chu kỳ xung nhịp lõi thực sự là tất cả những gì chúng ta cần xem xét.)

Lưu ý tổng số thấp hơn nhiều, thấp hơn nhiều uops_issuedso với uops_executedsố lượng. Điều đó có nghĩa là nhiều trong số chúng đã được hợp nhất vi mô: 1 uop trong miền được hợp nhất (vấn đề / ROB), nhưng 2 uops trong miền không được sử dụng (đơn vị lập lịch / thực thi)). Và số ít đó đã bị loại bỏ trong giai đoạn phát hành / đổi tên (như movsao chép đăng ký, hoặc xorxử lý, cần phát hành nhưng không cần đơn vị thực thi). Uops bị loại bỏ sẽ làm mất cân bằng cách khác.

branch-missesgiảm xuống ~ 400k, từ 1G, do đó, unrolling hoạt động. resource_stalls.anybây giờ rất quan trọng, điều đó có nghĩa là front-end không còn là nút cổ chai nữa: thay vào đó, back-end đang ở phía sau và giới hạn front-end. idq_uops_not_delivered.corechỉ tính các chu kỳ trong đó mặt trước không cung cấp các vòng, nhưng mặt sau không bị đình trệ. Đó là tốt đẹp và thấp, chỉ ra vài nút thắt phía trước.


Sự thật thú vị: phiên bản python dành hơn một nửa thời gian chia cho 10 thay vì thêm. (Thay thế a/=10bằng a>>=64tốc độ tăng hơn 2 lần, nhưng thay đổi kết quả vì cắt nhị phân! = Cắt thập phân.)

Phiên bản asm của tôi tất nhiên được tối ưu hóa đặc biệt cho kích thước vấn đề này, với số lần lặp lặp được tính mã hóa cứng. Ngay cả việc thay đổi một số chính xác tùy ý sẽ sao chép nó, nhưng phiên bản của tôi chỉ có thể đọc từ một phần bù cho hai lần lặp tiếp theo để bỏ qua điều đó.

Tôi đã định hình phiên bản python (python2.7 64 bit trên Arch Linux):

ocperf.py stat -etask-clock,context-switches:u,cpu-migrations:u,page-faults:u,cycles,instructions,uops_issued.any,uops_executed.thread,arith.divider_active,branches,branch-misses,L1-dcache-loads,L1-dcache-load-misses python2.7 ./fibonacci-1G.anders-brute-force.py
795231787455468346782938519619714818925554218523439891345303993734324668618251937005099962613655677933248203572322245122629171445627564825949953061211130125549987963951605345978901870056743994684484303459980241992404375340195011483010723426503784142698039838736078428423199645734078278420076776090777770318318574465653625351150285171596335102399069923259547132267036550648243596658688604862715971691635144878852742743550811390916796390738039824284803398011027637054426428503274436478119845182546213052952963333981348310577137012811185112824713631141420831898380252690791778709480221775085968511636388337484742803673714788207995668880750915837224945143751932016258200200053079830988726125702820190750937055423293110708497685471583358562391045067944912001156476292564914450953190468498441700251208650402077901250135617787419960508555831719090539513446891944331302682481336323419049437559926255302546652883812263943360048384953507064771198676927956854879685520768489774177178437585949642538435587910579974100118580

 Performance counter stats for 'python2.7 ./fibonacci-1G.anders-brute-force.py':

     755380.697069      task-clock:u (msec)       #    1.000 CPUs utilized          
                 0      context-switches:u        #    0.000 K/sec                  
                 0      cpu-migrations:u          #    0.000 K/sec                  
               793      page-faults:u             #    0.001 K/sec                  
 3,314,554,673,632      cycles:u                  #    4.388 GHz                      (55.56%)
 4,850,161,993,949      instructions:u            #    1.46  insn per cycle           (66.67%)
 6,741,894,323,711      uops_issued_any:u         # 8925.161 M/sec                    (66.67%)
 7,052,005,073,018      uops_executed_thread:u    # 9335.697 M/sec                    (66.67%)
   425,094,740,110      arith_divider_active:u    #  562.756 M/sec                    (66.67%)
   807,102,521,665      branches:u                # 1068.471 M/sec                    (66.67%)
     4,460,765,466      branch-misses:u           #    0.55% of all branches          (44.44%)
 1,317,454,116,902      L1-dcache-loads:u         # 1744.093 M/sec                    (44.44%)
        36,822,513      L1-dcache-load-misses:u   #    0.00% of all L1-dcache hits    (44.44%)

     755.355560032 seconds time elapsed

Các số trong (parens) là bao nhiêu thời gian mà bộ đếm hoàn hảo đã được lấy mẫu. Khi nhìn vào nhiều quầy hơn so với hỗ trợ CTNH, sự hoàn hảo xoay giữa các quầy khác nhau và ngoại suy. Điều đó hoàn toàn tốt cho một nhiệm vụ dài.

Nếu tôi chạy perfsau khi thiết lập sysctl kernel.perf_event_paranoid = 0(hoặc chạy perfbằng root), nó sẽ đo 4.400GHz. cycles:ukhông tính thời gian bị gián đoạn (hoặc cuộc gọi hệ thống), chỉ có chu kỳ không gian người dùng. Máy tính để bàn của tôi gần như hoàn toàn nhàn rỗi, nhưng đây là điển hình.


20

Haskell, 83 61 byte

p(a,b)(c,d)=(a*d+b*c-a*c,a*c+b*d)
t g=g.g.g
t(t$t=<<t.p)(1,1)

Đầu ra ( F 1000000000 , F 1000000001 ). Trên máy tính xách tay của tôi, nó in chính xác paren trái và 1000 chữ số đầu tiên trong vòng 133 giây, sử dụng 1,35 GiB bộ nhớ.

Làm thế nào nó hoạt động

Sự tái phát Fibonacci có thể được giải quyết bằng cách sử dụng lũy ​​thừa ma trận:

[ F i - 1 , F i ; F i , F i + 1 ] = [0, 1; 1, 1] tôi ,

từ đó chúng tôi rút ra những bản sắc này:

[ F i + j - 1 , F i + j ; F i + j , F i + j + 1 ] = [ F i - 1 , F i ; F i , F i + 1 ] ⋅ [ F j - 1 , F j ; F j , F j + 1 ],
F i + j = F i+ 1 F j + 1 - F i - 1 F j - 1 = F i + 1 F j + 1 - ( F i + 1 - F i ) ( F j + 1 - F j ),
F i + j + 1 = F i F j + F i + 1 F j + 1 .

Các ptính chức năng ( F i + j , F i + j + 1 ) cho ( F i , F i + 1 ) và ( F j , F j + 1 ). Viết f ncho ( F i , F i + 1 ), chúng ta có p (f i) (f j)= f (i + j).

Sau đó,

(t=<<t.p) (f i)
= t ((t.p) (f i)) (f i)
= t (p (f i).p (f i).p (f i)) (f i)
= (p (f i).p (f i).p (f i).p (f i).p (f i).p (f i).p (f i).p (f i).p (f i)) (f i)
= f (10 * i),

(t$t=<<t.p) (f i)
= ((t=<<t.p).(t=<<t.p).(t=<<t.p)) (f i)
= f (10^3 * i),

t(t$t=<<t.p) (f i)
= ((t$t=<<t.p).(t$t=<<t.p).(t$t=<<t.p)) (f i)
= f (10^9 * i),

và chúng tôi cắm vào f 1= (1,1).


12

Toán học, 15 34 byte

Fibonacci tự nó mất ~ 6s trên máy tính của tôi. Và 95 (+/- 5) s cho frontend để hiển thị nó.

Fibonacci@1*^9&

nhập mô tả hình ảnh ở đây

1000 chữ số đầu tiên (34 byte): ⌊Fibonacci@1*^9/1*^208986640⌋&

kiểm tra 1

Dài hơn nhưng nhanh hơn ToString@Fibonacci@1*^9~StringTake~1000&:

kiểm tra ảnh chụp màn hình


1
6 giây?! Bạn đang chạy loại máy tính nào? Tôi mất 140 giây! (ngoài ra, bạn có thực sự mất nhiều thời gian hơn 10 lần để biến nó thành một chuỗi và nhận 1000 ký tự đầu tiên so với chỉ tính toán không?)
numbermaniac

1
@numbermaniac Xin lỗi tôi nên làm rõ rằng phải mất nhiều thời gian hơn để frontend hiển thị số.
Keyu Gan

1
@numbermaniac: Những lần đó không thực sự làm tôi ngạc nhiên. Trong nội bộ, kết quả Fibonacci có lẽ là ở cơ sở 2, và tính toán IIRC, số Fibonacci thứ N có thể được thực hiện trong các hoạt động O (log (n)); Mathematica chắc chắn không chỉ đơn thuần là cố gắng vượt qua các bổ sung BigInteger lớn. IDK ngôn ngữ tốt; có thể đó là sử dụng đánh giá lười biếng một phần để tránh thực sự tạo ra BigInteger 71,5 MB.
Peter Cordes

2
@numbermaniac: Quan trọng hơn, các đại diện nội bộ là trong base2, và chuyển đổi thành một chuỗi base10 đòi hỏi lặp đi lặp lại bộ phận bằng cách phân chia 10 Integer được nhiều chậm hơn so với nhân số nguyên cho số nguyên 64-bit, và nó chỉ là xấu cho hai đăng ký chính xác mở rộng (nếu không tệ hơn, vì bội số được tạo đường ống tốt hơn so với chia ngay cả trong các CPU x86 rất gần đây với phần cứng phân chia khá tốt). Tôi cho rằng nó tệ cho độ chính xác tùy ý, ngay cả đối với một ước số nhỏ không đổi như 10.
Peter Cordes

1
Tôi đã xem xét một câu trả lời mã máy x86 cho câu hỏi này và đang xem xét giữ số thập phân của tôi trong toàn bộ thời gian. Điều đó chủ yếu là để rút ngắn việc thực hiện bằng cách không cần một vòng phân chia độ chính xác mở rộng. (Tôi đã nghĩ có thể với 2 chữ số trên một byte (0,99) hoặc 0,, 1, 9 mỗi đoạn 32 bit, vì vậy mỗi đoạn biến thành một số chữ số thập phân không đổi và tôi chỉ có thể sử dụng div). Tôi đã dừng lại vì mọi người có lẽ sẽ hoàn thành việc xem xét câu hỏi này vào thời điểm tôi có một chức năng chơi golf tốt đã làm tất cả công việc đó. Nhưng rõ ràng brute-force có thể hoạt động, như một số câu trả lời cho thấy.
Peter Cordes

11

Python 2, 70 byte

a,b=0,1
i=1e9
while i:
 a,b=b,a+b;i-=1
 if a>>3360:a/=10;b/=10
print a

Việc này diễn ra trong 18 phút và 31 giây trên máy tính xách tay của tôi, tạo ra 1000 chữ số chính xác theo sau 74100118580(các chữ số chính xác sau 74248787892).


Kết hợp tốt đẹp của vũ phu và tiết kiệm công việc.
Peter Cordes

Vì điều này cho thấy một cách tiếp cận vũ phu khá đơn giản hoạt động, tôi đã nghĩ đến việc thực hiện điều này trong mã máy x86. Tôi có thể có thể làm cho nó hoạt động trong 100 đến 200 byte, thực hiện tất cả các công cụ có độ chính xác mở rộng theo cách thủ công, nhưng sẽ mất thời gian phát triển đáng kể, đặc biệt là để chơi golf + tối ưu hóa nó. Kế hoạch của tôi là các khối 32 bit của cơ sở10 ** 9, do đó, dễ dàng cắt giảm xuống còn 1006 chữ số và dễ dàng chuyển đổi thành một chuỗi thập phân mà không cần phân chia chính xác tùy ý. Chỉ cần một divvòng lặp để tạo 9 chữ số thập phân trên mỗi chunk. Thực hiện trong khi thêm với cmp / cmov và 2xADD thay vì ADC.
Peter Cordes

Suy nghĩ về nó đủ để gõ bình luận trước đó khiến tôi bị cuốn hút. Tôi đã kết thúc việc triển khai nó trong 106 byte mã máy x86 32 bit bằng ý tưởng đó, chạy trong 1 phút13 trên máy tính của tôi so với 12 phút35 trên máy tính để bàn của tôi cho phiên bản python này (dành phần lớn thời gian chia cho 10, không nhanh cho các số cơ sở chính xác mở rộng!)
Peter Cordes

10

Haskell , 78 byte

(a%b)n|n<1=b|odd n=b%(a+b)$n-1|r<-2*a*b-a*a=r%(a*a+b*b)$div n 2
1%0$2143923439

Hãy thử trực tuyến!

Mất 48 giây trên TIO. Công thức đệ quy tương tự như câu trả lời Python của tôi , nhưng không cắt ngắn.

Hằng số 214392343910**9-1, đảo ngược ở dạng nhị phân và có thêm 1 ở cuối. Lặp lại thông qua các chữ số nhị phân của nó ngược lại mô phỏng lặp qua các chữ số nhị phân của 10**9-1. Dường như ngắn hơn để mã hóa này hơn là tính toán nó.


9

Haskell , 202 184 174 173 170 168 164 162 byte

(a,b)!(c,d)=a*c+b*d
l x=((34,55)!x,(55,89)!x)
f(a,b)|x<-l(a,b)=(x!l(b-a,a),x!x)
r=l.f
k=f.f.f
j=f.r.r.r.r
main=print$take 1000$show$fst$f$r$k$k$r$k$j$f$r$j$r(0,1)

Hãy thử trực tuyến!

Giải trình

Điều này sử dụng một cách khá nhanh để tính toán số lượng MySpace. Chức năng lmất hai fibonacci số và tính toán các con số fibonacci 10 sau đó, trong khi flấy n th và n + 1 ngày số fibonacci và tính toán các 2n + 20 ngày và 2n + 21 số thứ fibonacci. Tôi xâu chuỗi chúng một cách khá ngớ ngẩn để có được 1 tỷ và lấy 1000 chữ số đầu tiên.


Bạn có thể lưu một số byte bằng cách thực hiện một toán tử bao gồm một hàm với n lần.
user1502040

@ user1502040 tức là số nhà thờ.
Florian F

8

Haskell, 81 byte

f n|n<3=1|even n=fk*(2*f(k+1)-fk)|1>0=f(k+1)^2+fk^2 where k=n`div`2;fk=f k
f$10^9

Giải trình

f nđệ quy tính toán số nthứ tự sử dụng số lần lặp lại từ câu trả lời của xnor với việc loại bỏ phổ biến phụ. Không giống như các giải pháp khác đã được đăng, sử dụng phép nhân O (log (n)), chúng tôi có phép đệ quy độ sâu O (log (n)) với hệ số phân nhánh là 2, cho độ phức tạp của phép nhân O (n).

Tuy nhiên, tất cả là không bị mất! Bởi vì hầu hết tất cả các cuộc gọi sẽ ở gần dưới cùng của cây đệ quy, chúng tôi có thể sử dụng số học bản địa nhanh nếu có thể và tránh được nhiều thao tác của các khối lớn. Nó thốt ra câu trả lời trong vài phút trên hộp của tôi.


8

T-SQL, 422 414 453 byte (Đã xác minh, hiện đang cạnh tranh!)

EDIT 2 : Thay đổi thành , Đạt được một vài byte nhưng tăng tốc độ đủ để hoàn thành tới 1 tỷ! Hoàn thành sau 45 giờ 29 phút , xác minh theo chuỗi đã cho và hiển thị thêm 8 ký tự (có thể đúng hoặc có thể không đúng do lỗi làm tròn).INT BIGINT DECIMAL(37,0)

T-SQL không có hỗ trợ "số lượng lớn" nguyên gốc, do đó phải cuộn trình bổ sung số lượng lớn dựa trên văn bản của riêng tôi bằng chuỗi 1008 ký tự:

DECLARE @a char(1008)=REPLICATE('0',1008),@ char(1008)=REPLICATE('0',1007)+'1',@c varchar(max),@x bigint=1,@y int,@t varchar(37),@f int=0o:SELECT @x+=1,@c='',@y=1i:SELECT @t=CONVERT(DECIMAL(37,0),RIGHT(@a,36))+CONVERT(DECIMAL(37,0),RIGHT(@,36))+@f,@a=RIGHT(@a,36)+@a,@=RIGHT(@,36)+@,@c=RIGHT(REPLICATE('0',36)+@t,36)+@c,@y+=1IF LEN(@t)>36SET @f=1 ELSE SET @f=0IF @y<29GOTO i
IF @f=1SELECT @a='0'+@,@='1'+@c ELSE SELECT @a=@,@=@c
If @x<1e9 GOTO o
PRINT @

Đây là phiên bản được định dạng với các bình luận:

DECLARE @a char(1008)=REPLICATE('0',1008)       --fib(a), have to manually fill
       ,@ char(1008)=REPLICATE('0',1007)+'1'    --fib(b), shortened variable
       ,@c varchar(max), @x bigint=1, @y int, @t varchar(37), @f int=0
o:  --outer loop
    SELECT @x+=1, @c='', @y=1
    i:  --inner loop
        SELECT @t=CONVERT(DECIMAL(37,0),RIGHT(@a,36))      --adds last chunk of string
                 +CONVERT(DECIMAL(37,0),RIGHT(@,36)) + @f
              ,@a=RIGHT(@a,36)+@a                          --"rotates" the strings
              ,@=RIGHT(@,36)+@
              ,@c=RIGHT(REPLICATE('0',36)+@t,36)+@c        --combines result
              ,@y+=1
        IF LEN(@t)>36 SET @f=1 ELSE SET @f=0               --flag for carrying the 1
     IF @y<29 GOTO i                                       --28 * 36 digits = 1008 places
     IF @f=1 SELECT @a='0'+@, @='1'+@c                     --manually carries the 1
        ELSE SELECT @a=@, @=@c
If @x<1e9 GOTO o
PRINT @

Về cơ bản, tôi đang thao tác thủ công các chuỗi không có ký tự 1008 ký tự đại diện cho hai biến số Fibonacci của tôi @a@.

Tôi thêm chúng 8 18 36 chữ số cùng một lúc, bằng cách tước 36 chữ số cuối cùng, chuyển đổi thành loại số có thể quản lý ( DECIMAL(37,0)), thêm chúng lên, sau đó đập lại thành một chuỗi dài khác @c. Sau đó tôi "xoay" @a@bằng cách di chuyển 36 chữ số cuối cùng ra phía trước và lặp lại quy trình. 28 phép quay * 36 chữ số bao gồm tất cả 1008. Tôi phải "mang theo một cách thủ công".

Khi số của chúng tôi bắt đầu vượt quá độ dài chuỗi của tôi, tôi "dịch chuyển sang trái" và chúng tôi bắt đầu mất một số độ chính xác, nhưng lỗi cũng nằm trong các ký tự phụ của tôi.

Tôi đã thử sử dụng một bảng SQL chứa đầy INT và BIGINT, với logic tương tự, và nó chậm hơn đáng kể . Kỳ dị.


7
Lạm dụng ấn tượng tài nguyên công ty!
davidbak

6

PARI / GP, 45 byte

\p1100
s=sqrt(5)
((1+s)/2)^1e9/s/1e208986640

Bằng cách nào đó \p1000không đủ. Điều này không hoạt động với các hệ thống 32 bit. Sự phân chia cuối cùng là để tránh dấu thập phân trong ký hiệu khoa học.


4

Pari / GP , 15 + 5 = 20 byte

fibonacci(10^9)

Chạy với tùy chọn dòng lệnh -s1gđể phân bổ 1 Gbyte bộ nhớ.


1

Ruby, 63 byte

người đàn ông, tôi rất tệ trong việc chơi golf ruby; nhưng lớp BigInt làm điều kỳ diệu cho loại công cụ này. Chúng tôi sử dụng thuật toán tương tự như Anders Kaseorg.

require 'matrix'
m=Matrix
puts m[[1,1],[1,0]]**10**9*m[[1],[1]]

Điều đó thực sự có được bạn một ngàn chữ số?
dfeuer
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.