极端斐波那契


47

这个网站上已经进行了十亿次的斐波那契挑战,所以让我们通过十亿次的斐波那契挑战来为事情加分吧!

您面临的挑战是如何使用尽可能短的程序输出第1,000,000,000个斐波那契数的前1000个十进制数字。然后,可以选择在其后跟随您选择的任何其他输出,包括但不限于其余数字。

我使用的约定fib 0 = 0fib 1 = 1

您的程序必须足够快才能运行并验证其正确性。为此,这里是前1000位:

7952317874554683467829385196197148189255542185234398913453039937343246686182519370050999626136556779332482035723222451226291714456275648259499530612111301255499879639516053459789018700567439946844843034599802419924043753401950114830107234265037841426980398387360784284231996457340782784200767760907777703183185744656536253511502851715963351023990699232595471322670365506482435966586886048627159716916351448788527427435508113909167963907380398242848033980110276370544264285032744364781198451825462130529529633339813483105771370128111851128247136311414208318983802526907917787094802217750859685116363883374847428036737147882079956688807509158372249451437519320162582002000530798309887261257028201907509370554232931107084976854715833585623910450679449120011564762925649144509531904684984417002512086504020779012501356177874199605085558317190905395134468919443313026824813363234190494375599262553025466528838122639433600483849535070647711986769279568548796855207684897741771784375859496425384355879105799

Your program must be fast enough for you to run it and verify its correctness.记忆力呢?
斯蒂芬,

5
@ guest44851说谁?;)
user1502040

1
如果我a+=b;b+=a;想找一个明显的地方,那么我认为循环(也许使用Java BigInteger)是显而易见的选择,至少在您甚至在考虑性能的时候。在我看来,递归实现始终效率极低。
彼得·科德斯

2
我很想看到一种语言本身不支持大量语言!
BradC

1
@BradC:那也是我的想法。开发,调试,优化和高尔夫花费了大约2天的时间,但是现在我的x86 32位机器代码答案已经准备好(106个字节,包括转换为字符串和进行write()系统调用)。我喜欢性能要求,这对我来说更有趣。
彼得·科德斯

Answers:


34

Python 2 + sympy,72个字节

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

在线尝试!

通过删除几乎为0的-10个字节,感谢Jeff
Dege -1个字节(感谢Zacharý的为1000-> 1e3)
-2个字节通过感谢Outgolfer 的不必要的变量而删除了-2个字节,感谢Zacharý的帮助
,为移至Python 2 而删除了2个字节
-3字节,通过11'ing -11感谢ThePirateBay -3字节,通过交换str反引号感谢notjagan

现在击败了OP未发布的haskell解决方案!


@JonathanAllan我注意到,from sympy import*;sqrt没有保存任何字节import sympy;sympy.sqrt:)
HyperNeutrino

哇,速度很快,如何运作?
Kritixi Lithos

我认为这使用了斐波那契数的指数近似,并从仅需要第一个e3位数的细节中获利,因为这会自动限制任何偏离近似的问题。那是对的吗?
法比安·罗林(FabianRöling)

@Fabian sympy是Python的符号数学包,因此舍入错误没有问题,至少直到非常大的数字为止(这个数字还不够大)。然后我只计算它来给我前1e3位数字,因为否则,如果删除该.evalf(1e3)部分,它将给我一个非常短的科学符号表示形式。
HyperNeutrino

1
由于sympy不是python标准库的一部分,因此除非您在字符数中包括sympy的源代码,否则此响应似乎无效...或者我完全误解了代码高尔夫球规则吗?
thegreatemu

28

Python 2,106个字节

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

在线尝试!

没有库,只有整数算术。几乎立即运行。

核心是分而治之的身份:

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

这使我们更新(a,b) = (f(n),f(n+1))为double n -> 2*n。由于我们想要得到n=10**9,所以只需要log_2(10**9)=30迭代。我们建立n了以10**9通过反复做n->2*n+c每个数字c的二进制展开的。当时c==1,加倍的值2*n -> 2*n+1以单步斐波那契移位向上移动(a,b)=(b+a,b)

为了使值a,b易于管理,我们仅1006按底限除以存储其第一位数,10直到它们位于之下2**3340 ~ 1e1006


:在冰上!不使用花哨的预制库,哈哈。:D
HyperNeutrino

我发现打高尔夫球的复发率更高,但回弹率却更低a,b,c=a*a+b*b,a*a-c*c,b*b+c*c
尼尔

21

x86 32位机器码(使用Linux系统调用):106个 105字节

changelog:在快速版本中保存了一个字节,因为一个不加常数不会改变Fib(1G)的结果。

或102字节的速度(在Skylake上)慢18%(在内部循环中使用mov/ sub/ cmc代替lea/ cmp,以产生进位和换行,10**9而不是2**32)。对于最慢的〜5.3x版本,则为101个字节,并在最内层循环的进位处理中带有分支。(我测得的分支错误预测率为25.4%!)

或104/101字节(如果允许前导零)。(跳过1位输出硬编码需要1个额外的字节,这恰好是Fib(10 ** 9)所需要的)。

不幸的是,TIO的NASM模式似乎-felf32在编译器标志中忽略了。 无论如何这是我完整的源代码的链接,注释中包含所有混乱的实验性想法。

这是一个完整的程序。它先打印Fib(10 ** 9)的前1000位数字,然后再打印一些多余的数字(最后几位是错误的),再打印一些垃圾字节(不包括换行符)。大多数垃圾都是非ASCII的,因此您可能需要通过进行管道传输cat -v。但是,它不会破坏我的终端仿真器(KDE konsole)。“垃圾字节”存储Fib(999999999)。我已经-1024在寄存器中了,所以打印1024个字节比适当的大小便宜。

我只计算机器代码(静态可执行文件的文本段的大小),而不计算使它成为ELF可执行文件的绒毛。(很小的ELF可执行文件是可能的,但我不想为此烦恼)。事实证明,使用堆栈内存而不是BSS会更短,因此我可以证明不对二进制文件中的其他内容进行计数是合理的,因为我不依赖任何元数据。(以常规方式生成剥离的静态二进制文件使340字节的ELF可执行文件。)

您可以使用可以从C调用的代码来创建函数。保存/恢复堆栈指针(可能在MMX寄存器中)和其他一些开销将花费一些字节,但是通过返回字符串可以节省字节在内存中,而不是进行write(1,buf,len)系统调用。我认为打高尔夫球的机器代码应该让我有些懈怠,因为没有其他人甚至没有使用本机扩展精度就可以发布任何语言的答案,但是我认为该函数的功能版本仍应小于120字节,而无需重新进行整体研究事情。


算法:

蛮力a+=b; swap(a,b),根据需要截断以仅保留前导> = 1017十进制数字。它在我的计算机上运行的时间为1min13s(或3224.7亿个时钟周期+-0.05%)(如果再增加一些代码大小的字节,运行速度可能会快几个百分点,而从循环展开到更大的代码大小,运行速度可能会降低62s。聪明的数学,只需花费更少的精力就可以完成相同的工作)。它基于@AndersKaseorg的Python实现,可在我的计算机(4.4GHz Skylake i7-6700k)上运行12分35秒。这两个版本都没有丢失任何L1D缓存,因此我的DDR4-2666没关系。

与Python不同,我将扩展精度数字以一种使小数位截断成为免费的格式存储。我每32位整数存储9位十进制数字的组,因此指针偏移量将丢弃低9位数字。这实际上是10亿的基数,即10的幂。(这完全是巧合,这个挑战需要10亿斐波那契数,但是与两个独立的常数相比,它确实为我节省了几个字节)。

按照GMP术语,扩展精度数字的每个32位块都称为“肢体”。加法执行时必须通过与1e9的比较手动生成,但随后通常用作下一条肢的常规ADC指令的输入。(我还必须手动换行到该[0..999999999]范围,而不是2 ^ 32〜= 4.295e9。我使用lea+ cmov进行无分支操作,使用比较的结余结果。)

当最后一个分支产生非零进位时,外循环的接下来的两次迭代从比正常分支高1个分支中读取,但仍写入同一位置。这就像做一个memcpy(a, a+4, 114*4)右移1条肢体一样,但是作为接下来的两个加法循环的一部分来完成。这大约每18次迭代发生一次。


节省大小和性能的技巧:

  • 当我知道时,通常的东西喜欢lea ebx, [eax-4 + 1]代替。在缓慢的地方使用只会产生很小的影响。mov ebx, 1eax=4loopLOOP

  • 通过偏移我们读取的指针,将第一个分支免费截断,同时仍写入adc内部循环中缓冲区的开头。我们从阅读[edi+edx],然后写信给[edi]。这样我们就可以获取edx=04获取目标的读写偏移量。我们需要进行两次连续的迭代,首先偏移两个,然后仅偏移dst。我们通过在esp&4重置指向缓冲区前端的指针之前查看来检测第二种情况(使用&= -1024,因为缓冲区已对齐)。查看代码中的注释。

  • Linux进程启动环境(对于静态可执行文件)将大多数寄存器清零,esp/ 下方的堆栈内存rsp清零。我的程序利用了这一点。在此函数的可调用功能版本中(未分配的堆栈可能很脏),我可以将BSS用于零位内存(以增加4个字节为代价来设置指针)。调零edx将占用2个字节。x86-64 System V ABI不保证上述任何一个,但是Linux的实现为零(以避免信息从内核泄漏出去)。在动态链接的过程中,/lib/ld.so在之前运行_start,并且确实使寄存器为非零值(并且可能在堆栈指针下方的内存中产生垃圾)。

  • 我一直-1024ebx循环外使用。使用bl作为内部循环的计数器,在零结尾(这是低字节-1024,从而恢复恒定为使用外循环)。Intel Haswell和更高版本没有对low8寄存器进行部分寄存器合并的处罚(实际上甚至没有单独重命名它们),因此存在对完整寄存器的依赖性,就像在AMD上一样(这里没有问题)。但是,这对于Nehalem及更早版本来说将是可怕的,因为合并时它们会出现部分寄存器停顿的情况。在其他地方,我编写部分reg,然后读取完整的reg,而不用xor-zeroingmovzx,通常是因为我知道先前的一些代码将高位字节清零了,这在AMD和Intel SnB系列中也可以,但是在Intel Sandybridge之前很慢。

    我将其1024用作写入stdout(sub edx, ebx)的字节数,因此我的程序在斐波那契数字后打印一些垃圾字节,因为这会mov edx, 1000花费更多的字节。

  • (未使用)adc ebx,ebx,而EBX = 0则得到EBX = CF,与相比节省了1个字节setc bl

  • dec/ jnzadc循环内可以保留CF,而不会adc在Intel Sandybridge及更高版本上读取标志时导致部分标志停顿。 这对于较早的CPU来说很糟糕,但是Skylake上免费提供AFAIK。或者最坏的情况是额外的麻烦。

  • 将下面的内存esp用作巨大的红色区域。由于这是一个完整的Linux程序,所以我知道我没有安装任何信号处理程序,并且没有别的会异步破坏用户空间堆栈内存。在其他操作系统上可能不是这种情况。

  • 利用堆栈引擎的优势,通过使用pop eax(1 uop +偶尔的堆栈同步uop)而不是lodsd(根据Agner Fog的指令表,在Haswell / Skylake上为2 uops,在IvB上为3 uop )来节省uop发行带宽。IIRC,这将运行时间从约83秒减少到了73秒。使用mov带有索引寻址模式的a可能会获得相同的速度,例如mov eax, [edi+ebp]其中ebpsrc和dst缓冲区之间的偏移量保持不变。(这会使内部循环外的代码变得更加复杂,因为在Fibonacci迭代中交换src和dst的一部分必须取消偏移量寄存器。)有关更多信息,请参见下面的“性能”部分。

  • 通过给第一次迭代带进位(一个字节stc)开始序列,而不是1在任何地方存储在内存中。评论中记录了许多其他特定于问题的内容。

NASM列表(机器代码+源),与所生成的nasm -felf32 fibonacci-1G.asm -l /dev/stdout | cut -b -28,$((28+12))- | sed 's/^/ /'。(然后,我手动删除了一些已注释的内容,因此行编号之间存在间隙。)要去除前导列,以便可以将其输入YASM或NASM,请使用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

可能还有空间可以打出更多字节,但是我已经在两天内花了至少12个小时。 我不想牺牲速度,尽管它的速度远远不够快,并且还有空间以降低速度的方式减小速度。我发布帖子的部分原因是,展示了我可以以多快的速度制作暴力ASM版本。如果有人真的想最小化,但可能要慢10倍(例如,每字节1位数),请随意复制此内容作为起点。

生成的可执行文件(来自yasm -felf32 -Worphan-labels -gdwarf2 fibonacci-1G.asm && ld -melf_i386 -o fibonacci-1G fibonacci-1G.o)为340B(已剥离):

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

性能

内部adc循环是Skylake上的10个融合域uops(每〜128字节+1个堆栈同步uop),因此它可以在Skylake上每2.5个周期发出一次,具有最佳的前端吞吐量(忽略栈同步uops) 。对于adc-> cmp->下一个迭代的adc循环承载依赖链,关键路径延迟为2个周期,因此瓶颈应为每次迭代约2.5个周期的前端问题限制。

adc eax, [edi + edx]对于执行端口,有2个未融合域的指令:load + ALU。它在解码器中微熔丝(1个融合域uop),但由于采用索引寻址模式,即使在Haswell / Skylake上,在发行阶段也会取消层压为2个融合域uop。我认为它会像以前一样保持微融合,add eax, [edi + edx]但是也许保持索引寻址模式微融合对已经具有3个输入(标志,内存和目标)的uops无效。当我写它的时候,我以为它不会降低性能,但是我错了。这种处理截断的方法每次都会减慢内部循环的速度,无论edx是0还是4。

通过偏移ediedx用于调整存储,可以更快地处理dst的读写偏移。所以adc eax, [edi]/ ... / mov [edi+edx], eax/ lea edi, [edi+4]代替stosd。Haswell及其以后可以保持索引存储微融合。(Sandybridge / IvB也会将其分层。)

在英特尔的Haswell和更早版本,adc并且cmovc都为2个微指令,与2C延迟。(adc eax, [edi+edx]在Haswell上仍未分层,发行为3个融合域uops)。Broadwell及其后的版本允许三输入微指令不仅用于FMA(Haswell),还可以进行单微指令adccmovc(以及其他一些事情)单微指令,就像它们在AMD上使用了很长时间一样。(这是AMD长期以来一直在扩展精度GMP基准测试中取得出色成绩的原因之一。)无论如何,Haswell的内部循环应为12 oups(有时为+1堆栈同步uop),前端瓶颈为每个3c最好的情况,忽略堆栈同步的指令。

使用pop不带平衡push环的内部意味着环路不能从LSD(循环流检测器)上运行,并已被从UOP缓存每次重新读入IDQ。如果有的话,这在Skylake上是一件好事,因为9或10 uop循环在每个周期4 uops时并不是最佳选择。这可能是为什么用替换lodsdpop很大帮助的部分原因。(LSD无法锁定uops,因为那样就不会留出空间来插入堆栈同步uop。)(顺便说一句,微代码更新完全禁用了Skylake和Skylake-X上的LSD来修复勘误。我测量了在获得更新之前)。

我在Haswell上对其进行了分析,发现它可以运行3813.1亿个时钟周期(与CPU频率无关,因为它仅使用L1D高速缓存,而不使用内存)。前端问题的吞吐量为每个时钟3.72融合域微指令,而Skylake为3.70。(但是,当然,每个周期的指令从2.87下降到2.42,因为adccmov在Haswell上是2微秒。)

push替换stosd可能没有太大帮助,因为adc [esp + edx]每次都会触发堆栈同步uop。并且将花费一个字节,std因此lodsd走了另一个方向。(mov [edi], eax/ lea edi, [edi+4]替换stosd是一个胜利,从100M iters的32,909Mcycles到100M iters的31,954Mcycles。似乎stosd解码为3 uops ,store-address / store-data uops没有微融合,因此push+堆栈同步uops可能仍然比stosd)快

在Skylake上的快速105B版本中,114条肢体的1G迭代的实际性能约为3224.7亿个周期,每次内循环迭代为2.824个周期。(请参见ocperf.py下面的输出)。这比我从静态分析中预测的速度要慢,但是我忽略了外循环和任何堆栈同步控件的开销。

Perf进行计数branchesbranch-misses显示出,每个外部循环都会对内部循环进行一次错误的预测(在上一次迭代中,如果不采用)。这也占了额外时间的一部分。


我可以通过使最内侧环保存代码大小具有关键路径,使用3个周期的等待时间mov esi,eax/ sub eax,ebp/ cmovc eax, esi/cmc(2 + 2 + 3 + 1 = 8B)代替lea esi, [eax - 1000000000]/ cmp ebp,eax/ cmovc(6 + 2 + 3 = 11B )。的cmov/ stosd已关闭的关键路径。(增量-EDI的微指令stosd可以从商店单独运行,所以每次迭代叉关闭短依赖链。)它使用从改变EBP INIT指令来保存另一个1B lea ebp, [ecx-1]mov ebp,eax,但我发现,具有错ebp没有改变结果。这将使肢体精确地== 1000000000,而不是包裹并产生一个进位,但是此错误的传播速度比我们Fib()的增长慢,因此这种情况不会改变最终结果的前1k位。另外,我认为在添加时该错误可以自行纠正,因为肢体中有足够的空间容纳它而不会溢出。即使1G + 1G也不会溢出32位整数,因此最终它会向上渗透或被截断。

3c延​​迟版本额外增加了1个uop,因此前端可以在Skylake上每2.75c周期发布一次,仅比后端运行它快一点。(在Haswell上,由于它仍使用adccmov,因此总共为13 ups,并且瓶颈在每iter 3.25c处在前端)。

在实践中,它在Skylake上的运行速度要慢1.18倍(每肢3.34个周期),而不是我预测的3 / 2.5 = 1.2,这是通过仅通过查看内部循环而不使用堆栈同步来将延迟瓶颈替换为延迟瓶颈哎呀。由于堆栈同步控件仅会损害快速版本(前端出现瓶颈而不是延迟),因此无需过多解释。例如3 / 2.54 = 1.18。

另一个因素是3c延迟版本可能会在关键路径仍在执行时检测到离开内部循环的错误预测(因为前端可以领先于后端,从而让乱序执行可以运行循环-计数器误操作),因此有效的误判惩罚较低。失去那些前端周期会使后端追上来。

如果不是那样的话,我们可以cmc通过在外循环中使用分支来加快3c 版本的速度,而不是对进位-> edx和esp偏移量进行无分支处理。控制依赖而不是数据依赖的分支预测+投机执行可以使下一个迭代开始运行该adc循环,而上一个内部循环的运行仍在进行中。在无adc分支版本中,内部循环中的加载地址从最后一个分支的最后一个开始对CF具有数据依赖性。

2c延迟内环版本在前端出现瓶颈,因此后端几乎可以保持正常运行。如果外循环代码是高延迟的,则前端可以从内循环的下一次迭代中提前发出微指令。(但是在这种情况下,外循环的东西有很多ILP,而没有高延迟的东西,所以当后端开始通过无序调度程序中的uops进行咀嚼时,后端并没有太多的工作要做。他们的输入准备就绪)。

### 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 %)是该次数的4次运行的标准偏差。有趣的是,它运行了如此多的指令。这9,240亿不是巧合。我猜想外循环总共运行924条指令。

uops_issued是一个融合域计数(与前端发布带宽有关),uops_executed而是一个非融合域计数(发送到执行端口的微指令数)。微融合将2个未融合域的uops打包到一个融合域的uop中,但是移动消除意味着某些融合域的uops不需要任何执行端口。请参阅链接的问题,以获取有关计数uops和融合与未融合域的更多信息。(另请参阅Agner Fog的说明表和uarch指南,以及SO x86标签Wiki中的其他有用链接)。

从另一次测量不同情况的运行来看:L1D高速缓存未命中完全无关紧要,这与读取/写入相同的两个456B缓冲区一样。内循环分支会在每个外循环中误预测一次(当不离开循环时)。(总时间较长,因为计算机没有完全处于空闲状态。可能另一个逻辑核心在某些时间处于活动状态,并且在中断上花费了更多时间(因为用户空间测量的频率远低于4.400GHz)。还是更多时间有多个内核处于活动状态,从而降低了最大涡轮增压速度。我没有cpu_clk_unhalted.one_thread_active看到HT竞争是否是一个问题。)

     ### 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        

我的代码很可能在Ryzen上以较少的周期运行,每个周期可以发出5微指令(或者其中一些是2 uop指令时发出6微指令,例如Ryzen上的AVX 256b东西)。我不确定它的前端将如何处理stosd,这是Ryzen(与Intel相同)上的3微秒。我认为内循环中的其他指令与Skylake和所有单联指令的延迟相同。(包括adc eax, [edi+edx],这是优于Skylake的优势)。


如果我将数字存储为每个字节1个小数位数,则可能会小很多,但可能慢9倍。用产生进位cmp和用进行调整cmov的效果相同,但是做1/9的工作。每字节2个十进制数字(以100为基数,不是4比特的BCD带有慢速DAA)也可以,并且div r8/ 将按add ax, 0x3030打印顺序将0-99字节转换为两个ASCII数字。但是,每个字节完全不需要1位数字div,只需循环并添加0x30。如果我按打印顺序存储字节,那将使第二个循环真的很简单。


每个64位整数使用18或19个十进制数字(在64位模式下)将使其运行速度快大约两倍,但对于所有REX前缀和64位常量而言,其代码大小成本都很高。64位模式下的32位肢体禁止使用pop eax代替lodsd。我仍然可以通过将其esp用作非指针暂存寄存器(交换esiand 的用法esp),而不是r8d用作第8个寄存器来避免REX前缀。

如果制作的是可调用函数版本,则转换为64位并使用r8d可能比保存/恢复便宜rsp。64位也不能使用一字节dec r32编码(因为它是REX前缀)。但是大多数情况下我最终使用了dec bl2个字节。(因为我在的高字节中有一个常量,所以ebx只能在内部循环之外使用它,因为常量的低字节是,所以可以使用它0x00。)


高性能版本

为了获得最佳性能(而不是代码性能),您需要展开内部循环,以使其最多运行22次迭代,这对于分支预测变量而言,要采用的足够短的采用/不采用的模式非常有效。在我的实验中,循环mov cl, 22之前.inner: dec cl/jnz .inner很少有错误的预测(例如0.05%,远远小于内部循环的每次运行的mov cl,23错误预测),但是每个内部循环的错误预测是从0.35到0.6倍。 46尤其糟糕,每个内部循环的预测误差约为1.28倍(对于100M外循环的迭代,预测误差为128M倍)。 114每个内部循环只被一次错误地预测,与我在斐波那契循环中发现的一样。

我很好奇并尝试了一下,将内循环以6展开%rep 6(因为它平均分配了114)。多数情况下消除了分支遗漏。我将edx负值用作mov商店的抵消额,因此adc eax,[edi]可以保持微融合。(所以我可以避免stosd)。我将leaupdate edi拖出了该%rep块,因此每6个存储只执行一次指针更新。

我也摆脱了外循环中的所有部分寄存器的内容,尽管我认为这并不重要。使外部循环末端的CF不依赖于最终ADC可能会有所帮助,因此可以开始一些内部循环的操作。外循环代码可能会进行更多优化,这neg edx是我做的最后一件事xchg,仅用2 mov条指令替换(因为我已经有1条指令),然后重新排列dep链并删除8位注册东西。

这就是斐波纳契循环的NASM来源。它是原始版本该部分的直接替代。

  ;;;; 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

性能:

 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% )

这是相同的Fib(1G),在62.3秒而不是73秒内产生相同的输出。(273.146G个周期,而322.467G。由于所有内容都命中L1缓存,因此我们真正需要关注的就是核心时钟周期。)

请注意,总计uops_issued数要低得多,远低于uops_executed计数。这意味着它们中的许多是微融合的:在融合域(issue / ROB)中为1 uop,而在非融合域(调度器/执行单元)中为2 uop。并且在发布/重命名阶段中消除了极少的内容(例如mov寄存器复制或xor-zeroing,它们需要发布但不需要执行单元)。消除了的微词会反过来使计数不平衡。

branch-misses从1G下降到了约40万,所以展开工作了。 resource_stalls.any现在意义重大,这意味着前端不再是瓶颈:相反,后端落后了并限制了前端。 idq_uops_not_delivered.core仅计算前端未传递uops,但后端没有停止的周期。不错,很低,表明前端瓶颈很少。


有趣的事实:python版本花费其一半以上的时间除以10而不是相加。(更换a/=10a>>=64其加速超过2倍,但改变的结果,因为二进制截断!=小数截断。)

我的asm版本当然针对此问题大小进行了优化,并使用了硬编码的循环迭代次数。即使移位任意精度的数字也会复制它,但是我的版本可以从偏移量中读取,以便在接下来的两次迭代中跳过。

我介绍了python版本(在Arch Linux上为64位python2.7):

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

(parens)中的数字是对perf计数器进行采样的时间。当查看的计数器超出硬件支持范围时,性能会在不同的计数器之间旋转并外推。对于同一任务的长期运行来说,这完全没问题。

如果我perf在设置sysctl后kernel.perf_event_paranoid = 0运行(或perf以root用户身份运行),它将进行测量4.400GHzcycles:u不计算中断(或系统调用)所花费的时间,仅计算用户空间周期。我的桌面几乎完全空闲,但这是典型的。


20

Haskell,83 61字节

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)

输出( F 1000000000F 1000000001)。在我的笔记本电脑上,它使用1.35 GiB内存可以在133秒内正确打印左括号和前1000位数字。

这个怎么运作

斐波那契递归可以使用矩阵求幂来解决:

[ F i -1F i ; ˚F ˚F + 1 ] = [0,1; 1,1]

从中我们得出这些身份:

[ F i + j − 1F i + j ; F i + jF i + j + 1 ] = [ F i − 1F i ; F iF i + 1 ]⋅[ F j − 1F j ; F jF 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

p函数计算(˚F + Ĵ˚F + Ĵ + 1给出)(˚F ˚F + 1)和(˚F Ĵ˚F Ĵ + 1)。写f nF iF i + 1),我们有p (f i) (f j)= f (i + j)

然后,

(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)

然后插入f 1= (1,1)


12

Mathematica,15岁 34 个字节

Fibonacci 本身在我的计算机上大约需要6s。95(+/- 5)s用于前端显示它。

Fibonacci@1*^9&

在此处输入图片说明

前1000个数字(34个字节): ⌊Fibonacci@1*^9/1*^208986640⌋&

测试1

更长但更快ToString@Fibonacci@1*^9~StringTake~1000&

测试截图


1
6秒?您正在运行哪种计算机?我花了140秒!(同样,将其转换为字符串并获得前1000个字符真的比只计算它还要花费10倍多吗?)
numbermaniac

1
@numbermaniac对不起,我应该澄清一下,前端显示数字需要花费更长的时间。
Keyu Gan

1
@numbermaniac:那些时候并没有让我感到惊讶。在内部,斐波那契结果可能在base2中,并且IIRC计算第N个斐波那契数可以在O(log(n))操作中完成;Mathematica当然不只是通过大量添加BigInteger来强行使用。IDK的语言说得很好;也许它使用了部分惰性的评估以避免实际创建71.5MB的BigInteger。
彼得·科德斯

2
@numbermaniac:更重要的是,内部表示是在BASE2,并转换为base10串需要反复分裂通过10.整数除法是比整数为64位整数乘法慢,并且它只是作为坏了两个寄存器扩展精度(即使不是更糟,因为即使在具有相当好的除法硬件的最新x86 CPU中,乘法也比除法更好地流水线化)。我想这是对任意精度差,即使是一个小常数因子类似10
彼得·科德斯

1
我一直在寻找这个问题的x86机器码答案,并且一直在考虑将我的数字始终保持十进制。这主要是因为根本不需要扩展精度的除法循环,从而缩短了实现时间。(我当时想也许每字节2位数字(0..99),或者每32位块为0..1e9-1,所以每个块都变成恒定数量的十进制数字,我只能使用div)。我停下了脚步,因为当我拥有一个功能完备的功能完成所有工作时,人们可能已经可以解决这个问题。但是,正如一些答案所示,强力显然可以奏效。
彼得·科德斯

11

Python 2,70个字节

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

这在我的笔记本电脑上运行了18分31秒,产生了正确的1000位数字,后跟74100118580(正确的以下数字是74248787892)。


蛮力和省力的完美结合。
彼得·科德斯

由于这表明一个相当简单的蛮力方法有效,因此我考虑在x86机器代码中实现此方法。我可能可以使它工作在100到200字节之间,当然可以手动完成所有扩展精度的工作,但是这将花费大量的开发时间,尤其是高尔夫+优化它。我的计划是使用base10 ** 9的32位块,因此很容易将其截断为1006位,并且易于转换为不带任意精度除法的十进制字符串。只是一个div循环,每个块使9个十进制数字。在进行期间,使用cmp / cmov和2xADD代替ADC进行加法。
彼得·科德斯

考虑到足以打入以前的评论让我着迷了。我最终使用该想法在106字节的x86 32位机器代码中实现了该命令,在此python版本上,我的计算机运行时间为1min13s,而台式机运行时间为12min35s(大部分时间除以10,这并不快)扩展精度为base2的数字!)
Peter Cordes

10

Haskell,78个字节

(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

在线尝试!

在TIO上花费了48秒。与我的Python答案相同的递归公式,但不被截断。

常量214392343910**9-1,以二进制形式反转,最后加一个1。反向遍历其二进制数字可模拟对的二进制数进行迭代10**9-1。对其进行硬编码似乎比对其进行计算要短。


9

Haskell中202 184 174 173 170 168 164 162字节

(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)

在线尝试!

说明

这使用一种相当快速的方法来计算斐波那契数。该函数l采用两个斐波那契数,然后在10之后计算斐波那契数,而f采用第nn + 1个斐波那契数,并计算2n + 202n + 21斐波那契数。我很随意地将它们链接起来,以获得10亿个并抓住前1000个数字。


您可以通过实现一个运算符来节省一些字节,该运算符本身会构成一个函数。
user1502040

@ user1502040,即教堂数字。
Florian F

8

Haskell,81个字节

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

说明

f nn使用xnor答案的递归和common-subexpression消除来递归计算th斐波那契数。与其他已发布的使用O(log(n))乘法的解决方案不同,对于O(n)乘法的复杂性,我们有一个O(log(n))深度递归,分支因子为2。

但是,一切并没有丢失!因为几乎所有调用都将在递归树的底部附近,所以我们可以在可能的情况下使用快速本机算法,并避免对巨大的bignum进行大量操作。几分钟后,它在我的盒子上吐出了一个答案。


8

T-SQL,422个414 453字节(验证,现在竞争!)

编辑2:更改为,获得了几个字节,但速度提高到足以完成10亿个!在45小时29分钟内完成,根据给定的字符串进行验证,并显示另外8个字符(由于四舍五入错误而可能正确或错误)。INT BIGINT DECIMAL(37,0)

T-SQL没有本地“大数”支持,因此不得不使用1008个字符的字符串来滚动我自己的基于文本的大数加法器:

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 @

这是带注释的格式化版本:

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 @

基本上,我是手动操作代表两个Fibonacci变量@a和的1008个字符的零填充字符串@

我通过剥去最后的36位,转换为可管理的数字类型(),将它们加起来,然后将其粉碎成另一个长字符串,来一次添加8 18 36位。然后,我“旋转” 并通过将最后36位数字移到最前面,然后重复该过程。28圈* 36位数字涵盖了所有1008。我必须手动“携带一个”。DECIMAL(37,0)@c@a@

一旦我们的数字开始超过我的字符串长度,我就“向左移”,并且开始失去一些精度,但是错误完全在我的多余字符范围内。

我尝试使用充满INT和BIGINT的SQL表,并使用类似的逻辑,它的运行速度明显慢得多。奇怪的。


7
令人印象深刻的滥用公司资源!
davidbak

6

PARI / GP,45字节

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

以某种\p1000方式还不够。这不适用于32位系统。最后的划分是避免科学计数法中的小数点。



1

Ruby,63个字节

男人,我不擅长打红宝石。但是BigInt类确实为这种东西带来了奇迹。我们使用与Anders Kaseorg相同的算法。

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

那真的能使你成千上万个数字吗?
dfeuer
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.