สุดยอดฟีโบนักชี


47

มีการท้าทาย Fibonacci ซ้ำหลายพันล้านครั้งในเว็บไซต์นี้ดังนั้นขอเชิญชวนให้ทุกท่านสนุกกับการท้าทาย Fibonacci อีกครั้งหนึ่งพันล้านครั้ง!

ความท้าทายของคุณคือการส่งออกตัวเลขทศนิยม 1,000 หลักแรกของหมายเลขฟีโบนักชีที่ 1,000,000,000 พร้อมโปรแกรมให้สั้นที่สุด ซึ่งอาจเป็นทางเลือกตามด้วยผลลัพธ์เพิ่มเติมใด ๆ ที่คุณเลือกซึ่งรวมถึง แต่ไม่ จำกัด เพียงตัวเลขที่เหลือ

ฉันใช้การประชุมว่า,fib 0 = 0fib 1 = 1

โปรแกรมของคุณจะต้องเร็วพอสำหรับคุณที่จะเรียกใช้และตรวจสอบความถูกต้องของมัน เพื่อจุดประสงค์นี้นี่คือตัวเลข 1,000 หลักแรก:

7952317874554683467829385196197148189255542185234398913453039937343246686182519370050999626136556779332482035723222451226291714456275648259499530612111301255499879639516053459789018700567439946844843034599802419924043753401950114830107234265037841426980398387360784284231996457340782784200767760907777703183185744656536253511502851715963351023990699232595471322670365506482435966586886048627159716916351448788527427435508113909167963907380398242848033980110276370544264285032744364781198451825462130529529633339813483105771370128111851128247136311414208318983802526907917787094802217750859685116363883374847428036737147882079956688807509158372249451437519320162582002000530798309887261257028201907509370554232931107084976854715833585623910450679449120011564762925649144509531904684984417002512086504020779012501356177874199605085558317190905395134468919443313026824813363234190494375599262553025466528838122639433600483849535070647711986769279568548796855207684897741771784375859496425384355879105799

Your program must be fast enough for you to run it and verify its correctness.แล้วความทรงจำล่ะ
Stephen

5
@ guest44851 บอกว่าใคร ;)
user1502040

1
ถ้าฉันเห็นได้ชัดฉันคิดว่าการa+=b;b+=a;วนซ้ำ (อาจจะด้วย Java BigInteger) เป็นตัวเลือกที่ชัดเจนอย่างน้อยถ้าคุณกำลังคิดเกี่ยวกับประสิทธิภาพ การใช้การเรียกซ้ำเกิดขึ้นกับฉันอย่างไม่มีประสิทธิภาพ
Peter Cordes

2
ฉันสนใจที่จะเห็นหนึ่งในภาษาที่ไม่รองรับจำนวนมาก!
BradC

1
@BradC: นั่นคือสิ่งที่ฉันคิดด้วย ใช้เวลาประมาณ 2 วันในการพัฒนาตรวจแก้จุดบกพร่องเพิ่มประสิทธิภาพและเล่นกอล์ฟ แต่ตอนนี้คำตอบของรหัสเครื่อง x86 32 บิตของฉันพร้อมแล้ว (106 ไบต์รวมถึงการแปลงเป็นสตริงและทำการwrite()เรียกระบบ) ฉันชอบความต้องการด้านการแสดงทำให้มันสนุกขึ้นสำหรับฉัน
Peter Cordes

คำตอบ:


34

Python 2 + sympy, 72 ไบต์

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

ลองออนไลน์!

-10 bytes โดยการลบคำที่ใช้จริง -0 ด้วย Jeff Dege
-1 byte (1000 -> 1e3 ขอบคุณZacharý)
-2 bytes โดยการลบตัวแปรที่ไม่จำเป็นขอบคุณ Erik the Outgolfer
-2 bytes โดยย้ายไปที่ Python 2 ขอบคุณZacharý
-3 bytes โดย 11'ing -11ขอบคุณ ThePirateBay -3 bytes โดยการแลกเปลี่ยนstrสำหรับ backticks ขอบคุณ notjagan

ตอนนี้เต้นแก้ปัญหา haskell ที่ยังไม่ได้โพสของ OP


@JonathanAllan ผมสังเกตเห็นว่าfrom sympy import*;sqrtช่วยประหยัดไม่มีไบต์กว่าimport sympy;sympy.sqrt:)
HyperNeutrino

ว้าวนี่เร็วไปแล้วมันใช้งานอย่างไร
Kritixi Lithos

ฉันคิดว่านี่ใช้การประมาณแบบเอ็กซ์โปเนนเชียลสำหรับตัวเลข fibonacchi และกำไรจากรายละเอียดที่ต้องการเฉพาะตัวเลข e3 ตัวแรกเพราะมันจะกำจัดปัญหาใด ๆ โดยอัตโนมัติด้วยการเบี่ยงเบนจากการประมาณ ถูกต้องไหม
Fabian Röling

@Fabian sympyเป็นแพคเกจคณิตศาสตร์สัญลักษณ์สำหรับ Python ดังนั้นจึงไม่มีปัญหาเกี่ยวกับข้อผิดพลาดของการปัดเศษอย่างน้อยก็จนกว่าตัวเลขที่มีขนาดใหญ่มาก (หมายเลขนี้มีขนาดไม่ใหญ่มากฮ่า ๆ ) จากนั้นฉันแค่คำนวณมันเพื่อให้ฉัน 1e3 หลักแรกเพราะถ้าอย่างอื่นถ้าคุณลบ.evalf(1e3)ส่วนมันจะให้สัญลักษณ์ทางวิทยาศาสตร์ที่สั้นมาก
HyperNeutrino

1
เนื่องจาก sympy ไม่ได้เป็นส่วนหนึ่งของไลบรารี่มาตรฐานของไพ ธ อนการตอบสนองนี้ดูเหมือนจะไม่ถูกต้องเว้นแต่คุณจะใส่แหล่งที่มาของ 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)) 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ด้วยการเลื่อน Fibonacci แบบขั้นตอนเดียว(a,b)=(b+a,b)

เพื่อให้ค่าa,bจัดการเราเก็บเพียงครั้งแรกของพวกเขา1006หลักโดยพื้นหารด้วยจนกว่าพวกเขาจะอยู่ภายใต้102**3340 ~ 1e1006


:บนน้ำแข็ง! ไม่ได้ใช้ไลบรารี่ที่ทำไว้ล่วงหน้าแฟนซี : D
HyperNeutrino

ฉันพบว่าการกลับเป็นซ้ำที่น่าพอใจยิ่งขึ้น แต่น้อยลงa,b,c=a*a+b*b,a*a-c*c,b*b+c*c.
Neil

21

x86 รหัสเครื่อง 32 บิต (พร้อมการเรียกระบบ Linux): 106 105 ไบต์

changelog: บันทึกไบต์ในรุ่นที่รวดเร็วเนื่องจากค่าคงที่แบบหนึ่งต่อหนึ่งจะไม่เปลี่ยนผลลัพธ์สำหรับ Fib (1G)

หรือ 102 ไบต์สำหรับรุ่นช้ากว่า 18% (สำหรับ Skylake) (ใช้mov/ sub/ cmcแทนlea/ cmpในวงในเพื่อสร้างการยกออกและห่อที่10**9แทน2**32) หรือ 101 ไบต์สำหรับรุ่นที่ช้ากว่า ~ 5.3x กับสาขาในการจัดการการถือในห่วงส่วนใหญ่ (ฉันวัดอัตราความผิดทางสาขาที่ 25.4%!)

หรือ 104/101 ไบต์ถ้าอนุญาตศูนย์นำหน้า (ใช้เวลาเพิ่ม 1 ไบต์ถึงรหัสยากที่จะข้ามไป 1 หลักซึ่งเป็นสิ่งที่เกิดขึ้นกับ Fib (10 ** 9)

น่าเสียดายที่โหมด NASM ของ TIO นั้นดูเหมือนจะไม่สนใจ-felf32ในธงคอมไพเลอร์ นี่คือลิงค์อย่างไรก็ตามด้วยซอร์สโค้ดของฉันเต็มไปด้วยความยุ่งเหยิงของแนวคิดการทดลองทั้งหมดในความคิดเห็น

นี้เป็นโปรแกรมที่สมบูรณ์ มันพิมพ์ตัวเลข 1,000 หลักแรกของ Fib (10 ** 9) ตามด้วยตัวเลขพิเศษบางอย่าง (ไม่กี่อันสุดท้ายผิด) ตามด้วยขยะบางไบต์ (ไม่รวมบรรทัดใหม่) ส่วนใหญ่ของขยะคือไม่ใช่ ASCII cat -vดังนั้นคุณอาจต้องการที่จะผ่านท่อ ถึงแม้ว่ามันจะไม่ทำลาย terminal emulator (KDE konsole) ของฉัน "ไบต์ขยะ" กำลังเก็บ Fib (999999999) ฉันมีอยู่แล้ว-1024ในการลงทะเบียนดังนั้นจึงถูกกว่าที่จะพิมพ์ 1024 ไบต์กว่าขนาดที่เหมาะสม

ฉันกำลังนับแค่รหัสเครื่อง (ขนาดของส่วนข้อความของสแตติกรันของฉัน) ไม่ใช่ปุยที่ทำให้มันใช้งานได้กับเอลฟ์ ( สามารถใช้งานโปรแกรม ELF ขนาดเล็กได้แต่ฉันไม่ต้องการรบกวนมัน) มันสั้นกว่าที่จะใช้หน่วยความจำสแต็กแทนที่จะเป็น BSS ดังนั้นฉันสามารถพิสูจน์ได้ว่าไม่นับสิ่งอื่นใดในไบนารีเนื่องจากฉันไม่ได้พึ่งพาข้อมูลเมตาใด ๆ (การสร้างไบนารีแบบคงที่ที่ถูกปล้นวิธีปกติทำให้สามารถประมวลผล ELF ขนาด 340 ไบต์ได้)

คุณสามารถทำให้ฟังก์ชั่นของรหัสนี้ที่คุณสามารถโทรจาก C มันจะเสียค่าใช้จ่ายไม่กี่ไบต์ในการบันทึก / กู้คืนตัวชี้สแต็ค (อาจจะอยู่ในการลงทะเบียน MMX) และค่าใช้จ่ายอื่น ๆ ในหน่วยความจำแทนการwrite(1,buf,len)โทรออกระบบ ฉันคิดว่าการตีกอล์ฟด้วยรหัสเครื่องควรทำให้ฉันได้รับความหย่อนเนื่องจากไม่มีใครโพสต์คำตอบในภาษาใด ๆ โดยไม่ต้องใช้ความแม่นยำมาก แต่ฉันคิดว่าเวอร์ชั่นฟังก์ชั่นนี้น่าจะต่ำกว่า 120 ไบต์โดยไม่ต้องตีกอล์ฟทั้งหมด สิ่ง.


ขั้นตอนวิธีการ:

กำลังดุร้ายa+=b; swap(a,b)ตัดทอนตามที่จำเป็นเพื่อให้เฉพาะส่วนที่นำ> = 1,017 หลักทศนิยม มันทำงานใน 1 นาที 13 วินาทีบนคอมพิวเตอร์ของฉัน (หรือ 322.47 พันล้านรอบนาฬิกา + - 0.05%) (และอาจเร็วขึ้นเล็กน้อยด้วยโค้ดขนาดไม่กี่ไบต์หรือลดลงถึง 62 วินาทีด้วยขนาดโค้ดที่ใหญ่กว่ามากจากการวนรอบ คณิตศาสตร์ที่ฉลาดเพียงแค่ทำงานเดียวกันโดยมีค่าใช้จ่ายน้อยลง) มันขึ้นอยู่กับการใช้งาน Python ของ @ AndersKaseorgซึ่งทำงานใน 12 นาที 35 วินาทีบนคอมพิวเตอร์ของฉัน (4.4GHz Skylake i7-6700k) ไม่มีเวอร์ชันใดที่ไม่มีแคช L1D เลยดังนั้น DDR4-2666 ของฉันจึงไม่สำคัญ

ซึ่งแตกต่างจากงูหลามผมเก็บหมายเลขขยายความแม่นยำในรูปแบบที่ทำให้การตัดทอนทศนิยมตัวเลขฟรี ฉันเก็บกลุ่มของทศนิยม 9 หลักต่อจำนวนเต็ม 32 บิตดังนั้นตัวชี้ชดเชยจะละทิ้งตัวเลขต่ำ 9 หลัก นี่เป็นฐานอย่างมีประสิทธิภาพ 1 พันล้านซึ่งเป็นพลังของ 10 (มันเป็นเรื่องบังเอิญโดยแท้ที่ความท้าทายนี้ต้องการหมายเลขฟีโบนักชี 1 พันล้าน แต่มันช่วยฉันสองสามไบต์เทียบกับค่าคงที่สองแบบ)

ตามคำศัพท์GMP , 32 บิตแต่ละอันของหมายเลขที่มีความแม่นยำเพิ่มเติมเรียกว่า "แขนขา" การดำเนินการในขณะที่การเพิ่มจะต้องสร้างขึ้นด้วยตนเองด้วยการเปรียบเทียบกับ 1e9 แต่จากนั้นจะใช้ตามปกติเป็นอินพุตไปยังADCคำสั่งปกติสำหรับแขนขาถัดไป (ฉันต้องตัด[0..999999999]ช่วงด้วยตนเองแทนที่จะเป็น 2 ^ 32 ~ = 4.295e9 ฉันทำแบบนี้โดยไม่มีการต่อกับlea+ cmovโดยใช้ผลลัพธ์ที่ได้จากการเปรียบเทียบ)

เมื่อแขนขาสุดท้ายก่อให้เกิดการพกพาที่ไม่เป็นศูนย์การวนรอบนอกสองครั้งถัดไปจะอ่านจาก 1 ขาที่สูงกว่าปกติ แต่ยังคงเขียนไปยังที่เดียวกัน นี่เหมือนกับการทำ a memcpy(a, a+4, 114*4)-right-shift by 1 limb แต่ทำได้โดยเป็นส่วนหนึ่งของการเพิ่มลูปสองครั้งถัดไป สิ่งนี้เกิดขึ้นซ้ำ ๆ ประมาณ 18 ครั้ง


Hacks สำหรับการประหยัดขนาดและประสิทธิภาพ:

  • สิ่งที่ตามปกติเช่นlea ebx, [eax-4 + 1]แทนที่จะเมื่อฉันรู้ว่าmov ebx, 1 eax=4และใช้loopในสถานที่ที่LOOPความเชื่องช้ามีผลกระทบเพียงเล็กน้อยเท่านั้น

  • ตัดทอน 1 limb ฟรีโดยการชดเชยตัวชี้ที่เราอ่านในขณะที่ยังเขียนถึงจุดเริ่มต้นของบัฟเฟอร์ในadcลูปด้านใน เราอ่านจากและเขียนไปยัง[edi+edx] [edi]ดังนั้นเราสามารถรับedx=0หรือ4รับออฟเซ็ตการอ่าน - เขียนสำหรับปลายทาง เราต้องทำสิ่งนี้เพื่อทำซ้ำ 2 ครั้งติดต่อกันก่อนชดเชยทั้งคู่จากนั้นจึงชดเชยเฉพาะ dst เราตรวจสอบกรณีที่ 2 โดยดูที่esp&4ก่อนรีเซ็ตตัวชี้ไปที่ด้านหน้าของบัฟเฟอร์ (ใช้&= -1024เนื่องจากบัฟเฟอร์ถูกจัดตำแหน่ง) ดูความคิดเห็นในรหัส

  • สภาพแวดล้อมการเริ่มต้นกระบวนการ Linux (สำหรับปฏิบัติการที่คงที่) เลขศูนย์ส่วนใหญ่ที่ลงทะเบียนและหน่วยความจำสแต็คด้านล่างesp/ rspเป็นศูนย์ โปรแกรมของฉันใช้ประโยชน์จากสิ่งนี้ ในรุ่นฟังก์ชั่น callable ของเรื่องนี้ (ที่กองที่ไม่ได้ปันส่วนอาจสกปรก) ฉันสามารถใช้ BSS สำหรับหน่วยความจำที่เป็นศูนย์ การ zeroing edxจะใช้เวลา 2 ไบต์ x86-64 System V ABI ไม่ได้รับประกันสิ่งใดสิ่งหนึ่ง แต่การใช้งานของ Linux นั้นไม่เป็นศูนย์ (เพื่อหลีกเลี่ยงการรั่วไหลของข้อมูลจากเคอร์เนล) ในกระบวนการที่เชื่อมโยงแบบไดนามิกให้/lib/ld.soเรียกใช้ก่อน_startและปล่อยให้การลงทะเบียนไม่ใช่ศูนย์ (และอาจทำให้ขยะในหน่วยความจำด้านล่างตัวชี้สแต็ก)

  • ฉันเก็บ-1024ในebxสำหรับใช้ภายนอกของลูป ใช้blเป็นตัวนับสำหรับลูปด้านในซึ่งลงท้ายด้วยศูนย์ (ซึ่งเป็นไบต์ต่ำของ-1024ดังนั้นจึงกู้คืนค่าคงที่สำหรับใช้นอกลูป) Intel Haswell และต่อมาไม่มีการลงทะเบียนการรวมบทลงโทษบางส่วนสำหรับการลงทะเบียนต่ำ 8 ครั้ง(และในความเป็นจริงไม่ได้เปลี่ยนชื่อแยกต่างหาก)ดังนั้นการพึ่งพาการลงทะเบียนเต็มรูปแบบเช่นเดียวกับ AMD (ไม่ใช่ปัญหาที่นี่) สิ่งนี้น่ากลัวสำหรับ Nehalem และก่อนหน้านี้ซึ่งมีแผงลอยบางส่วนเมื่อรวมเข้าด้วยกัน มีสถานที่อื่น ๆ ที่ฉันเขียน regs บางส่วนแล้วอ่าน reg เต็มโดยไม่ต้อง - xorศูนย์หรือmovzxมักจะเป็นเพราะฉันรู้ว่าโค้ดก่อนหน้าบางตัวมีค่าเป็นศูนย์ไบต์และอีกครั้งที่ดีใน AMD และ Intel SnB ตระกูล แต่ช้าใน pre-Sandybridge ของ Intel

    ฉันใช้1024เป็นจำนวนไบต์เพื่อเขียนไปยัง stdout ( sub edx, ebx) ดังนั้นโปรแกรมของฉันพิมพ์ไบต์ขยะบางส่วนหลังตัวเลขฟีโบนักชีเพราะmov edx, 1000ค่าใช้จ่ายเพิ่มเติมไบต์

  • (ไม่ได้ใช้) adc ebx,ebxกับ EBX = 0 จะได้รับ EBX = CF ประหยัด 1 setc blไบต์เทียบกับ

  • dec/ jnzภายในadcลูปเก็บ CF โดยไม่ทำให้แผงแฟลกบางส่วนเมื่อadcอ่านแฟล็กบน Intel Sandybridge และใหม่กว่า มันแย่กับซีพียูรุ่นก่อนหน้าแต่ AFAIK ฟรีบน Skylake หรือที่เลวร้ายที่สุดเป็น uop พิเศษ

  • ใช้หน่วยความจำด้านล่างespเป็นยักษ์สีแดงโซน เนื่องจากนี่เป็นโปรแกรม Linux ที่สมบูรณ์ฉันรู้ว่าฉันไม่ได้ติดตั้งตัวจัดการสัญญาณใด ๆ และไม่มีสิ่งอื่นใดที่จะทำให้หน่วยความจำสแต็กพื้นที่ผู้ใช้แบบอะซิงโครนัสไม่ตรงกัน กรณีนี้อาจไม่เกิดขึ้นในระบบปฏิบัติการอื่น

  • ใช้ประโยชน์จากstack-engineเพื่อประหยัดแบนด์วิดท์ปัญหา uop โดยใช้pop eax(1 uop + เป็นครั้งคราว stack-sync uop) แทนlodsd(2 uops บน Haswell / Skylake, 3 บน IvB และก่อนหน้าตามตารางคำแนะนำของ Agner Fog )) IIRC สิ่งนี้ทำให้เวลารันไทม์ลดลงจากประมาณ 83 วินาทีเป็น 73 ฉันอาจจะได้ความเร็วเท่าเดิมจากการใช้ a movด้วยโหมดการกำหนดที่อยู่ที่จัดทำดัชนีเช่นmov eax, [edi+ebp]ที่ebpเก็บออฟเซ็ตระหว่างบัฟเฟอร์ src และ dst (มันจะทำให้โค้ดที่อยู่นอกลูปด้านในมีความซับซ้อนมากขึ้นโดยต้องปฏิเสธการลงทะเบียนออฟเซ็ตซึ่งเป็นส่วนหนึ่งของการแลกเปลี่ยน src และ dst สำหรับการทำซ้ำฟีโบนักชี) ดูหัวข้อ "ประสิทธิภาพ" ด้านล่างเพื่อดูเพิ่มเติม

  • เริ่มต้นลำดับโดยให้การทำซ้ำครั้งแรกในการพกพา (หนึ่งไบต์stc) แทนที่จะเก็บไว้1ในหน่วยความจำทุกที่ ปัญหาเฉพาะเรื่องอื่น ๆ จำนวนมากบันทึกไว้ในความคิดเห็น

NASM รายการ (machine-รหัสเมือง + แหล่งที่มา) , 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 ชั่วโมงในช่วง 2 วันนี้ ฉันไม่ต้องการที่จะเสียสละความเร็วแม้ว่ามันจะเป็นวิธีที่มากกว่าเร็วพอและมีห้องพักที่จะทำให้มันมีขนาดเล็กในรูปแบบที่ความเร็วค่าใช้จ่าย ส่วนหนึ่งของเหตุผลของฉันสำหรับการโพสต์แสดงให้เห็นว่าฉันสามารถสร้าง 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ห่วงคือ 10 UOPs หลอมโดเมนบน Skylake (+1 สแต็คซิงค์ UOP ทุก ~ 128 ไบต์) ดังนั้นจึงสามารถออกที่หนึ่งต่อ ~ 2.5 รอบใน Skylake กับที่ดีที่สุด front-end ผ่าน (ละเลย UOPs กองซิงค์) . เวลาแฝงเส้นทางวิกฤติคือ 2 รอบสำหรับห่วงโซ่การพึ่งพาadc-> cmp-> ซ้ำของการทำซ้ำครั้งถัดไปadcดังนั้นคอขวดควรเป็นขีด จำกัด ของปัญหาส่วนหน้าประมาณ 2.5 รอบต่อการทำซ้ำ

adc eax, [edi + edx]เป็น 2 uops โดเมนที่ไม่ได้ใช้สำหรับพอร์ตการดำเนินการ: load + ALU มันไมโครฟิวส์ในถอดรหัส (UOP 1 หลอมรวมโดเมน) แต่ยกเลิกการลามิเนตในขั้นตอนปัญหา 2 UOPs หลอมโดเมนเพราะโหมดการจัดทำดัชนีแม้ใน Haswell ฉันคิดว่ามันคงอยู่แบบ micro-fused เช่นเคยadd eax, [edi + edx]แต่บางทีการคงโหมดการกำหนดดัชนีแบบ micro-fused ไม่ทำงานสำหรับ uops ที่มี 3 อินพุต (ธงหน่วยความจำและปลายทาง) เมื่อฉันเขียนมันฉันคิดว่ามันจะไม่มีข้อเสียด้านการแสดง แต่ฉันคิดผิด วิธีการจัดการการตัดปลายนี้ทำให้การวนรอบด้านในช้าลงทุกครั้งไม่ว่าจะedxเป็น 0 หรือ 4

มันจะเร็วกว่าในการจัดการออฟเซ็ตการอ่าน - เขียนสำหรับ dst โดยการออฟเซ็ตediและการใช้edxเพื่อปรับการจัดเก็บ ดังนั้นadc eax, [edi]/ ... / mov [edi+edx], eax/ แทนlea edi, [edi+4] stosdHaswell และใหม่กว่าสามารถเก็บไมโครฟิวชั่นที่จัดทำดัชนีไว้ได้ (Sandybridge / IvB ก็จะทำการปนเปื้อนด้วย)

บน Intel Haswell และก่อนหน้านี้adcและcmovc2 UOPs แต่ละมีความล่าช้า ( adc eax, [edi+edx]ยังไม่ยกเลิกการเคลือบบน Haswell และปัญหาเป็น 3 uops ผสมโดเมน) Broadwell และใหม่กว่าอนุญาตให้อินพุต uops 3 รายการเป็นมากกว่า FMA (Haswell) การสร้างadcและcmovc(และอีกสองสามอย่าง) คำแนะนำแบบ uop เดียวเช่นที่พวกเขาใช้กับ AMD มาเป็นเวลานาน (นี่คือเหตุผลหนึ่งที่ AMD ทำได้ดีในเรื่องของมาตรฐาน GMP ที่มีความแม่นยำมากขึ้นเป็นเวลานาน) อย่างไรก็ตามห่วงภายในของ Haswell ควรมี 12 uops (+1 stack-sync uop เป็นครั้งคราว) พร้อมคอขวดส่วนหน้า ~ 3c ต่อ iter-case ที่ดีที่สุดโดยไม่สนใจ uops การซิงค์แบบสแต็ก

การใช้งานpopโดยไม่มีการปรับสมดุลpushภายในลูปหมายความว่าลูปไม่สามารถเรียกใช้จาก LSD (เครื่องตรวจจับการวนซ้ำ)และต้องอ่านใหม่จากแคช uop ไปยัง IDQ ทุกครั้ง ถ้ามีอะไรที่มันเป็นสิ่งที่ดีใน Skylake ตั้งแต่วง 9 หรือ 10 UOP ไม่มากออกได้อย่างดีที่สุดที่ 4 UOPs ทุกรอบ นี่อาจเป็นส่วนหนึ่งของเหตุผลที่แทนที่lodsdด้วยความpopช่วยเหลืออย่างมาก (LSD ไม่สามารถล็อค uops ลงได้เนื่องจากจะไม่ออกจากห้องเพื่อแทรกuop แบบสแต็ปซิงค์ ) (BTW การอัปเดตไมโครโค้ดปิดใช้งาน LSD ทั้งหมดใน Skylake และ Skylake-X เพื่อแก้ไข erratum ฉันวัด ด้านบนก่อนที่จะทำการอัพเดต)

ฉันทำโพรไฟล์บน Haswell และพบว่ามันทำงานใน 381.31 พันล้านรอบสัญญาณนาฬิกา (ไม่คำนึงถึงความถี่ของ CPU เนื่องจากมันใช้เฉพาะแคช L1D ไม่ใช่หน่วยความจำ) ปริมาณงานปัญหา Front-end เท่ากับ 3.72 uops โดเมนรวมต่อนาฬิกาเทียบกับ 3.70 สำหรับ Skylake (แต่แน่นอนว่าคำแนะนำต่อรอบนั้นลดลงเหลือ 2.42 จาก 2.87 เพราะadcและcmovเป็น 2 uops บน Haswell)

pushการแทนที่stosdอาจไม่ช่วยได้มากนักเพราะadc [esp + edx]จะทริกเกอร์สแต็กซิงค์ทุกครั้ง และจะมีค่าใช้จ่ายไบต์stdเพื่อlodsdไปในทิศทางอื่น ( mov [edi], eax/ lea edi, [edi+4]เพื่อแทนที่stosdเป็นผู้ชนะโดยเริ่มจาก 32,909M ครั้งสำหรับ 100M iters เป็น 31,954M ครั้งสำหรับ 100M iters ดูเหมือนว่าจะstosdถอดรหัสเป็น 3 uops ด้วย store-address / store-data uops ไม่ใช่ micro-fused ดังนั้นpush+ stack-sync uops อาจยังเร็วกว่าstosd)

ประสิทธิภาพที่แท้จริงของ ~ 322,470,000,000 รอบสำหรับการทำซ้ำ 1G จาก 114 ขาทำงานได้ถึง 2.824 รอบต่อรอบของการวนซ้ำภายในสำหรับรุ่น 105B ที่รวดเร็วใน Skylake (ดูocperf.pyผลลัพธ์ด้านล่าง) นั่นช้ากว่าที่ฉันคาดการณ์ไว้จากการวิเคราะห์แบบคงที่ แต่ฉันไม่สนใจโอเวอร์เฮดของวงนอกและสแต็คซิงค์ใด ๆ

เคาน์เตอร์ที่สมบูรณ์แบบสำหรับbranchesและbranch-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ถูกปิดเส้นทางที่สำคัญ (ส่วนเพิ่มที่เพิ่มขึ้นของstosdสามารถเรียกใช้แยกต่างหากจากร้านค้าดังนั้นการวนซ้ำแต่ละครั้งจะหยุดการพึ่งพาโซ่สั้น ๆ ) มันเคยช่วยประหยัดอีก 1B โดยการเปลี่ยนคำสั่ง ebp init จากlea ebp, [ecx-1]เป็นmov ebp,eaxแต่ฉันค้นพบว่ามีความผิดebpไม่ได้เปลี่ยนผลลัพธ์ สิ่งนี้จะทำให้แขนขามีค่าเท่ากับ == 1000000000 แทนที่จะห่อและผลิตให้ได้ แต่ข้อผิดพลาดนี้แพร่กระจายช้ากว่าที่เราตอแหล () เติบโตดังนั้นสิ่งนี้จะไม่เปลี่ยนตัวเลข 1k หลักของผลลัพธ์สุดท้าย นอกจากนี้ฉันคิดว่าข้อผิดพลาดสามารถแก้ไขได้เองเมื่อเราเพิ่งเพิ่มเนื่องจากมีพื้นที่เหลือเฟือที่จะเก็บมันไว้โดยไม่ล้น แม้แต่ 1G + 1G ก็ไม่ล้นจำนวนเต็ม 32 บิตดังนั้นในที่สุดมันจะไหลขึ้นหรือถูกตัดออก

เวอร์ชั่น 3c latency เป็น 1 uop พิเศษดังนั้น front-end สามารถออกได้ที่หนึ่งต่อ 2.75c รอบใน Skylake เพียงเล็กน้อยเร็วกว่า back-end สามารถเรียกใช้ (บน Haswell จะมีทั้งหมด 13 uops เนื่องจากยังคงใช้adcและcmovและคอขวดที่ส่วนหน้าอยู่ที่ 3.25c ต่อ iter)

ในทางปฏิบัติมันรันตัวประกอบของ 1.18 ช้าลงบน Skylake (3.34 รอบต่อแขนขา), แทนที่จะเป็น 3 / 2.5 = 1.2 ที่ฉันคาดการณ์สำหรับการแทนที่คอขวดด้านหน้าด้วยคอขวดแฝงจากการมองที่วงในโดยไม่สแต็คซิงค์ UOPs เนื่องจาก uops การซิงค์แบบสแต็คจะทำร้ายรุ่นที่รวดเร็วเท่านั้น (คอขวดที่ส่วนหน้าแทนที่จะเป็นความหน่วงแฝง) จึงไม่ต้องอธิบายอะไรมากมาย เช่น 3 / 2.54 = 1.18

ปัจจัยอีกประการหนึ่งคือรุ่น 3c latency อาจตรวจพบความผิดพลาดในการออกจากลูปด้านในในขณะที่พา ธ วิกฤติยังคงดำเนินการอยู่ (เนื่องจาก Front-End สามารถล้ำหน้าไปข้างหน้าของแบ็คเอนด์ได้ เคาน์เตอร์ uops) ดังนั้นบทลงโทษที่มีประสิทธิภาพจึงต่ำกว่า การสูญเสียรอบหน้าเหล่านั้นจะช่วยให้แบ็คเอนด์สามารถติดต่อได้

ถ้ามันไม่ได้เป็นเช่นนั้นเราอาจเพิ่มความเร็วในcmcรุ่น3c โดยใช้สาขาในลูปด้านนอกแทนการจัดการแบบไม่มีสาขาของ carry_out -> edx และ esp offsets Branch-prediction + การดำเนินการเก็งกำไรสำหรับการพึ่งพาการควบคุมแทนการพึ่งพาข้อมูลสามารถปล่อยให้การวนซ้ำครั้งต่อไปเริ่มต้นการทำงานadcลูปในขณะที่ uops จากลูปภายในก่อนหน้านี้ยังคงอยู่ในเที่ยวบิน ในเวอร์ชันที่ไม่มีสาขาโหลดที่อยู่ในลูปด้านในจะมีการขึ้นต่อกันของข้อมูลใน CF จากช่วงสุดท้ายadcของแขนขาสุดท้าย

คอลเลคชั่นวงในรุ่น 2c latency ที่ส่วนหน้าของ front-end ดังนั้น back-end ก็ยังคงทำงานอยู่ หากโค้ดลูปนอกเป็นเวลาแฝงสูง Front-end สามารถเรียกใช้ uops ล่วงหน้าจากการวนซ้ำครั้งถัดไปของลูปด้านใน (แต่ในกรณีนี้สิ่งนอกวงมีมากมาย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 ครั้งสำหรับการนับนั้น น่าสนใจว่ามันจะมีคำสั่งจำนวนมาก 924 พันล้านนั้นไม่ใช่เรื่องบังเอิญ ฉันเดาว่าลูปภายนอกทำงานทั้งหมด 924 คำสั่ง

uops_issuedคือการนับโดเมนแบบผสม (เกี่ยวข้องกับแบนด์วิดท์ปัญหาส่วนหน้า) ในขณะที่uops_executedเป็นการนับโดเมนแบบไม่ได้ใช้งาน (จำนวนของ uops ที่ส่งไปยังพอร์ตดำเนินการ) Micro-fusion บรรจุ 2 uops โดเมนที่ไม่ได้ใช้ไว้ใน u fused โดเมนหนึ่งรายการ แต่การกำจัด movหมายความว่า uops ของโดเมน fused บางตัวไม่ต้องการพอร์ตการดำเนินการใด ๆ ดูคำถามที่เชื่อมโยงสำหรับข้อมูลเพิ่มเติมเกี่ยวกับการนับ uops และ fused เทียบกับโดเมนที่ไม่ได้ใช้ (ดูตารางการเรียนการสอนของ Agner Fog และคำแนะนำ uarchและลิงค์ที่มีประโยชน์อื่น ๆ ในวิกิแท็ก SO x86 )

จากการดำเนินการอื่นในการวัดสิ่งต่าง ๆ : การพลาดแคช L1D นั้นไม่มีนัยสำคัญอย่างสิ้นเชิงตามที่คาดไว้สำหรับการอ่าน / เขียนบัฟเฟอร์ 456B สองตัวเดียวกัน สาขาด้านในจะคาดการณ์ความผิดพลาดหนึ่งครั้งต่อวงด้านนอก (เมื่อไม่ได้ออกจากวง) (เวลาทั้งหมดสูงกว่าเนื่องจากคอมพิวเตอร์ไม่ได้ใช้งานโดยสิ้นเชิงอาจเป็นไปได้ว่าแกนตรรกะอื่น ๆ นั้นทำงานอยู่บางเวลาและใช้เวลามากขึ้นในการขัดจังหวะ (เนื่องจากความถี่ที่วัดโดยผู้ใช้กับพื้นที่นั้นต่ำกว่า 4.400GHz) หรือหลายคอร์ใช้งานมากขึ้นทำให้ลด max turbo ฉันไม่ได้ติดตาม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 uops ต่อรอบ (หรือ 6 เมื่อบางคนเป็นคำแนะนำ 2-uop เช่น AVX 256b สิ่งที่ Ryzen) ฉันไม่แน่ใจว่าส่วนหน้าของมันจะทำอะไรstosdซึ่งเป็น 3 uops บน Ryzen (เช่นเดียวกับ Intel) ฉันคิดว่าคำแนะนำอื่น ๆ ในลูปด้านในนั้นมีความหน่วงแฝงเท่ากับ Skylake และซิงก์เดี่ยวทั้งหมด (รวมถึงadc eax, [edi+edx]ซึ่งเป็นข้อได้เปรียบเหนือ Skylake)


นี้อาจจะมีขนาดเล็กลงอย่างมีนัยสำคัญ แต่บางที 9x ช้าถ้าผมเก็บไว้เป็นตัวเลขทศนิยม 1 บาทต่อไบต์ การสร้างการดำเนินการcmpและการปรับด้วยcmovจะทำงานเหมือนเดิม แต่ทำ 1 / 9th งาน 2 หลักทศนิยมต่อไบต์ (base-100 ไม่ใช่ BCD 4 บิตที่ช้าDAA ) จะใช้งานได้และdiv r8/ และadd ax, 0x3030เปลี่ยน 0-99 ไบต์เป็นสองหลัก ASCII ในการสั่งพิมพ์ แต่ไม่จำเป็นต้องมี 1 หลักต่อไบต์divเพียงแค่วนซ้ำและเพิ่ม 0x30 ถ้าฉันเก็บไบต์ตามลำดับการพิมพ์นั่นจะทำให้ลูปที่ 2 ง่ายมาก


การใช้ตัวเลข 18 หรือ 19 หลักต่อจำนวนเต็ม 64- บิต (ในโหมด 64- บิต) จะทำให้มันทำงานเร็วเป็นสองเท่า แต่ค่าใช้จ่ายรหัสขนาดสำคัญสำหรับคำนำหน้า REX ทั้งหมดและค่าคงที่ 64 บิต แขนขา 32 บิตในการป้องกันโหมด 64 บิตโดยใช้แทนpop eax lodsdฉันยังคงสามารถหลีกเลี่ยงคำนำหน้า REX โดยใช้espเป็นตัวลงทะเบียนรอยขีดข่วนที่ไม่ใช่ตัวชี้ (สลับการใช้งานของesiและesp) แทนที่จะใช้r8dเป็นทะเบียนที่ 8

ถ้าทำรุ่น callable ฟังก์ชั่นแปลงไปเป็น 64 บิตและใช้อาจจะมีราคาถูกกว่าประหยัดr8d / การฟื้นฟู rsp64- บิตยังไม่สามารถใช้การdec r32เข้ารหัสหนึ่งไบต์(เนื่องจากเป็นส่วนนำหน้า REX) แต่ส่วนใหญ่ฉันลงเอยด้วยการใช้dec blที่ 2 ไบต์ (เพราะฉันมีค่าคงที่ในส่วนบนของไบต์ebxและใช้เฉพาะด้านนอกของลูปภายในซึ่งทำงานได้เนื่องจากไบต์ต่ำของค่าคงที่คือ0x00)


รุ่นประสิทธิภาพสูง

เพื่อประสิทธิภาพสูงสุด (ไม่ใช่ code-golf) คุณต้องการยกเลิกการวนรอบวงในเพื่อให้ทำงานได้มากถึง 22 รอบซึ่งเป็นรูปแบบที่ถ่าย / ไม่ถ่ายสั้น ๆ เพื่อให้ตัวทำนายสาขาทำได้ดี ในการทดลองของฉันmov cl, 22ก่อนที่.inner: dec cl/jnz .innerลูปจะมีการคาดการณ์ผิดพลาดน้อยมาก (เช่น 0.05% น้อยกว่าหนึ่งต่อการรันเต็มของลูปภายใน) แต่คาดmov cl,23เดาผิดจาก 0.35 ถึง 0.6 เท่าต่อวงใน 46แย่มากโดยเฉพาะการคาดการณ์ผิดพลาดประมาณ 1.28 เท่าต่อวงใน (128M ครั้งสำหรับการวนรอบ 100M ซ้ำ) 114เข้าใจผิดครั้งเดียวต่อวงในเช่นเดียวกับที่ฉันพบว่าเป็นส่วนหนึ่งของวนฟีโบนักชี

ฉันอยากรู้อยากเห็นและลองมันคลี่วงในด้วย 6 ด้วย%rep 6(เพราะมันแบ่ง 114 เท่า ๆ กัน) ที่กำจัดส่วนใหญ่สาขาคิดถึง ฉันทำedxเชิงลบและใช้มันเป็นออฟเซ็ตสำหรับmovร้านค้าดังนั้นจึงadc eax,[edi]สามารถใช้ไมโครฟิวชั่นได้ (และฉันก็สามารถหลีกเลี่ยงได้stosd) ฉันดึงการleaอัปเดตediออกจาก%repบล็อกดังนั้นจึงมีเพียงหนึ่งตัวอัปเดตต่อ 6 ร้านค้า

ฉันยังกำจัดสิ่งที่ลงทะเบียนบางส่วนในลูปด้านนอกแม้ว่าฉันไม่คิดว่ามันสำคัญ มันอาจช่วยเล็กน้อยที่จะมี CF ที่ส่วนท้ายของลูปด้านนอกไม่ได้ขึ้นอยู่กับ ADC สุดท้ายดังนั้นบางส่วนของวงในอาจเริ่มต้น โค้ดด้านนอกลูปอาจปรับให้เหมาะสมอีกเล็กน้อยเนื่องจากneg edxเป็นสิ่งสุดท้ายที่ฉันทำหลังจากเปลี่ยนเป็นxchgเพียง 2 movคำแนะนำ (เนื่องจากฉันยังมีอยู่ 1) และจัดเรียงโซ่ dep และวาง 8 บิตอีกครั้ง ลงทะเบียนสิ่งของ

นี่คือแหล่ง NASM ของวง Fibonacci เป็นการแทนที่สำหรับส่วนนั้นของรุ่นดั้งเดิม

  ;;;; 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นับ นั่นหมายความว่าหลายคนถูก micro-fused: 1 uop ในโดเมน fused (issue / ROB) แต่ 2 uops ในโดเมนที่ไม่ได้ใช้ (scheduler / execution unit) และมีเพียงไม่กี่คนเท่านั้นที่ถูกกำจัดในขั้นตอนการออก / เปลี่ยนชื่อ (เช่นmovการคัดลอกลงทะเบียนหรือ - xorศูนย์ซึ่งจำเป็นต้องออก แต่ไม่ต้องการหน่วยดำเนินการ) uops ที่ถูกกำจัดจะทำให้เกิดความสมดุลในทางอื่น

branch-missesลดลงเหลือ ~ 400k จาก 1G ดังนั้นการคลายออกจึงทำได้ resource_stalls.anyมีความสำคัญในตอนนี้ซึ่งหมายความว่าส่วนหน้าไม่ใช่คอขวดอีกต่อไป: ส่วนหลังจะได้รับอยู่ด้านหลังและ จำกัด ส่วนหน้า idq_uops_not_delivered.coreนับเฉพาะรอบที่ส่วนหน้าไม่ได้ส่ง uops เท่านั้น แต่ส่วนหลังนั้นไม่ได้หยุดทำงาน นั่นเป็นสิ่งที่ดีและต่ำแสดงถึงปัญหาคอขวดของส่วนหน้า


ความจริงแล้วสนุก: งูหลามใช้เวลามากกว่าครึ่งในการหารด้วย 10 แทนที่จะเพิ่ม (การแทนที่a/=10ด้วยa>>=64ความเร็วจะเพิ่มขึ้นมากกว่า 2 เท่า แต่จะเปลี่ยนผลลัพธ์เนื่องจากการตัดเลขฐานสอง! = การตัดทศนิยมแบบทศนิยม)

แน่นอนรุ่น asm ของฉันได้รับการปรับให้เหมาะสมสำหรับขนาดปัญหานี้โดยเฉพาะด้วยการวนซ้ำ - นับรหัสฮาร์ดโค้ด แม้แต่การขยับตัวเลขที่มีความแม่นยำตามอำเภอใจก็สามารถคัดลอกได้ แต่เวอร์ชันของฉันสามารถอ่านจากออฟเซ็ตสำหรับการทำซ้ำสองครั้งถัดไปเพื่อข้ามไปได้

ฉันสร้างเวอร์ชันหลาม (64- บิต python2.7 บน 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

ตัวเลขใน (parens) คือเวลาที่ตัวนับ perf ถูกสุ่มตัวอย่าง เมื่อดูที่เคาน์เตอร์มากกว่าที่ HW รองรับให้หมุนระหว่างตัวนับและ extrapolates ที่แตกต่างกัน นั่นเป็นสิ่งที่ดีสำหรับการทำงานในระยะยาว

ถ้าฉันวิ่งperfหลังจากตั้ง sysctl kernel.perf_event_paranoid = 0(หรือทำงานperfเป็น root) 4.400GHzมันจะวัด cycles: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 1000000000 , F 1000000001 ) บนแล็ปท็อปของฉันมันพิมพ์พาร์เรนซ้ายและตัวเลข 1,000 ตัวแรกอย่างถูกต้องภายใน 133 วินาทีโดยใช้หน่วยความจำ 1.35 GiB

มันทำงานอย่างไร

การเกิดซ้ำ Fibonacci สามารถแก้ไขได้โดยใช้การยกกำลังเมทริกซ์:

[ F i - 1 , F i ; F i , F i + 1 ] = [0, 1; 1, 1] ฉัน ,

จากการที่เราได้รับตัวตนเหล่านี้:

[ 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 ฉัน F J + F ฉัน + 1 F J + 1

pคำนวณฟังก์ชั่น ( F ฉัน + J , F ฉัน + J + 1 ) ที่กำหนด ( F ฉัน , F ฉัน + 1 ) และ ( F J , F J + 1 ) เขียนf nสำหรับ ( F ฉัน , F ฉัน + 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&

ป้อนคำอธิบายรูปภาพที่นี่

ตัวเลข 1,000 หลักแรก (34 ไบต์): ⌊Fibonacci@1*^9/1*^208986640⌋&

ทดสอบ 1

ยาวกว่า แต่เร็วกว่าToString@Fibonacci@1*^9~StringTake~1000&:

ภาพหน้าจอการทดสอบ


1
6 วินาที! คุณใช้คอมพิวเตอร์ประเภทใด ใช้เวลาของฉัน 140 วินาที! (เช่นกันมันใช้เวลานานขึ้น 10 เท่าในการแปลงให้เป็นสตริงและรับ 1,000 อักขระแรกจากการคำนวณหรือไม่?)
numbermaniac

1
@ จำนวนช่างขอโทษฉันควรชี้แจงว่ามันใช้เวลานานกว่าส่วนหน้าในการแสดงหมายเลข
Keyu Gan

1
@ ตัวเลข: ช่วงเวลาเหล่านั้นไม่ได้ทำให้ฉันแปลกใจ ภายในผลลัพธ์ของฟีโบนักชีน่าจะอยู่ใน base2 และการคำนวณ IIRC นั้นหมายเลขฟีโบนักชีสามารถทำได้ในการปฏิบัติการ O (log (n)); Mathematica ไม่ได้เป็นเพียงแค่การบังคับให้ต้องผ่านการเพิ่มของ BigInteger IDK ภาษาที่ดี; อาจใช้การประเมินแบบขี้เกียจบางส่วนเพื่อหลีกเลี่ยงการสร้าง BigInteger ขนาด 71.5MB
Peter Cordes

2
@numbermaniac: ที่สำคัญการแสดงภายในอยู่ใน base2 และแปลงไปสตริง base10 ต้องซ้ำแบ่งตามหมวด 10. จำนวนเต็มถูกมากช้ากว่าการคูณจำนวนเต็มสำหรับจำนวนเต็ม 64 บิตและก็เช่นเดียวกับที่ไม่ดีสำหรับทั้งสองลงทะเบียนแม่นยำขยาย (ถ้าไม่ได้แย่กว่านั้นเพราะการคูณจะดีกว่าการแบ่งแม้ในซีพียู x86 ที่เพิ่งมีฮาร์ดแวร์ที่ดีกว่าในการแบ่ง) ฉันคิดว่ามันไม่ดีสำหรับความแม่นยำโดยพลการแม้แต่ตัวหารคงที่ขนาดเล็กเช่น 10
Peter Cordes

1
ฉันกำลังดูคำตอบของรหัสเครื่อง x86 สำหรับคำถามนี้และกำลังพิจารณาที่จะรักษาตัวเลขทศนิยมไว้ตลอดเวลา ส่วนใหญ่จะเป็นการย่นระยะการดำเนินการโดยไม่ต้องการวงวนการแบ่งเพิ่มเติมที่มีความแม่นยำเลย (ฉันคิดว่าอาจมี 2 หลักต่อไบต์ (0..99) หรือ 0..1e9-1 ต่อ 32 บิตอันดังนั้นแต่ละอันจะกลายเป็นจำนวนทศนิยมคงที่และฉันสามารถใช้div) ฉันหยุดเพราะคนอาจจะดูคำถามนี้ในเวลาที่ฉันมีฟังก์ชั่นกอล์ฟที่ใช้งานได้ทั้งหมด แต่เห็นได้ชัดว่ากำลังดุร้ายสามารถทำงานได้ตามคำตอบบางอย่างแสดง
Peter Cordes

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 วินาทีบนแล็ปท็อปของฉันสร้างตัวเลข 1,000 หลักที่ถูกต้องตามด้วย74100118580(ตัวเลขต่อไปนี้ถูกต้อง74248787892)


การผสมผสานที่ดีของกำลังดุร้ายและการประหยัดงาน
Peter Cordes

ตั้งแต่นี้แสดงให้เห็นว่าวิธีการเดรัจฉานบังคับง่าย ๆ ใช้งานได้ฉันคิดว่าจะใช้สิ่งนี้ในรหัสเครื่อง x86 ฉันอาจทำให้มันทำงานได้ใน 100 ถึง 200 ไบต์ทำสิ่งที่มีความแม่นยำเพิ่มเติมด้วยตนเองแน่นอน แต่มันต้องใช้เวลาในการพัฒนาที่สำคัญโดยเฉพาะอย่างยิ่งการเล่นกอล์ฟ + เพิ่มประสิทธิภาพ แผนของฉันคือชิ้นส่วน 32 บิตของ base10 ** 9 ดังนั้นจึงง่ายต่อการตัดทอนลงเหลือ 1006 หลักและง่ายต่อการแปลงเป็นสตริงทศนิยมโดยไม่ต้องมีการหารที่แม่นยำ เพียงแค่divวนรอบเพื่อให้ตัวเลขทศนิยม 9 หลักต่อชิ้น ดำเนินการระหว่างการเพิ่มด้วย cmp / cmov และ 2xADD แทน ADC
Peter Cordes

คิดว่าเพียงพอที่จะพิมพ์ความคิดเห็นก่อนหน้านี้ทำให้ฉันติดยาเสพติด ฉันลงเอยด้วยการใช้งานมันใน 106 ไบต์ของรหัสเครื่อง x86 32- บิตโดยใช้ความคิดนั้นทำงานใน 1 นาที 13s บนคอมพิวเตอร์ของฉันเทียบกับ 12min35s บนเดสก์ท็อปของฉันสำหรับรุ่นงูหลามนี้ (ซึ่งใช้เวลาส่วนใหญ่หารด้วย 10 ซึ่งไม่เร็ว สำหรับตัวเลขฐานที่แม่นยำเพิ่มขึ้น!)
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

ลองออนไลน์!

ใช้เวลา 48 วินาทีใน TIO สูตรเรียกซ้ำเช่นเดียวกับคำตอบ Python ของฉันแต่ไม่มีการตัดทอน

ค่าคงที่2143923439คือ10**9-1ผันกลับในรูปของเลขฐานสองและมีค่าเพิ่ม 1 เมื่อสิ้นสุด ทำซ้ำผ่านตัวเลขไบนารีในจำลองกลับ iterating 10**9-1ผ่านตัวเลขไบนารีของ ดูเหมือนว่าจะ hardcode สั้นกว่าการคำนวณ


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ใช้เวลาสองหมายเลข fibonacci และคำนวณตัวเลข fibonacci 10 ต่อมาในขณะที่fใช้เวลาnวันและ1 + n TH ตัวเลข fibonacci และคำนวณ2n + 20ปีบริบูรณ์และ2n + 21 TH ตัวเลข fibonacci ฉันโยงพวกเขาค่อนข้างส่งเดชเพื่อรับ 1 พันล้านและคว้า 1,000 หลักแรก


คุณสามารถบันทึกบางไบต์โดยการใช้ตัวดำเนินการซึ่งประกอบด้วยฟังก์ชันด้วยตัวเอง n ครั้ง
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 nคำนวณซ้ำnหมายเลขฟีโบนักชีโดยใช้การเกิดซ้ำจากคำตอบของ xnor ด้วยการกำจัดนิพจน์ทั่วไป แตกต่างจากโซลูชันอื่น ๆ ที่โพสต์ซึ่งใช้การคูณ O (log (n)) เรามี O (log (n)) - การเรียกซ้ำแบบลึกด้วยปัจจัยการแยก 2 สำหรับความซับซ้อนของการคูณ O (n)

อย่างไรก็ตามทั้งหมดจะไม่สูญหายไป! เนื่องจากการโทรเกือบทั้งหมดจะอยู่ใกล้กับด้านล่างของแผนภูมิการเรียกซ้ำเราจึงสามารถใช้เลขคณิตพื้นเมืองได้อย่างรวดเร็วหากทำได้ มันคายคำตอบในไม่กี่นาทีในกล่องของฉัน


8

T-SQL, 422 414 453 ไบต์ (ตรวจสอบแล้วตอนนี้แข่งขัน!)

แก้ไข 2 : เปลี่ยนเป็นรับสองสามไบต์ แต่เพิ่มความเร็วพอที่จะครบ 1 พันล้าน! เสร็จสิ้นภายใน45 ชั่วโมง 29 นาทีตรวจสอบกับสตริงที่กำหนดและแสดงอักขระเพิ่มเติม 8 ตัว (ซึ่งอาจจะใช่หรืออาจจะไม่ถูกต้องเนื่องจากข้อผิดพลาดในการปัดเศษ)INT BIGINT DECIMAL(37,0)

T-SQL ไม่มีการสนับสนุน "จำนวนมาก" แบบดั้งเดิมดังนั้นจึงต้องม้วน adder จำนวนมากตามข้อความของฉันโดยใช้สายอักขระ 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 @

โดยทั่วไปฉันด้วยตนเองจัดการ 1,008 ตัวอักษรสตริงเป็นศูนย์ที่เต็มไปด้วยตัวแทนของสองตัวแปร Fibonacci ของฉันและ@a@

ฉันจะเพิ่มพวกเขา8 18 36 ตัวเลขที่เวลาโดยการลอกออกช่วง 36 ตัวเลขแปลงเป็นชนิดที่เป็นตัวเลขที่จัดการได้ ( DECIMAL(37,0)) @cเพิ่มพวกเขาขึ้นแล้วทุบมันกลับเข้าไปอีกสายยาว จากนั้นฉัน "หมุน" @aและ@โดยการย้าย 36 หลักสุดท้ายไปยังด้านหน้าและทำซ้ำขั้นตอน 28 การหมุน * 36 หลักครอบคลุม 1,058 ทั้งหมดฉันต้อง "พกหนึ่ง" ด้วยตนเอง

เมื่อหมายเลขของเราเริ่มเกินความยาวสตริงของฉันฉัน "เลื่อนไปทางซ้าย" และเราเริ่มสูญเสียความแม่นยำบางอย่าง แต่ข้อผิดพลาดอยู่ในตัวละครพิเศษของฉัน

ฉันลองใช้ตาราง SQL ที่เต็มไปด้วย INT และ BIGINT ด้วยตรรกะที่คล้ายกันและมันก็ช้าลงอย่างมาก แปลก.


7
การใช้ทรัพยากรของ บริษัท ในทางที่ผิดอย่างน่าประทับใจ!
davidbak

6

PARI / GP ขนาด 45 ไบต์

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

อย่างใด\p1000ไม่เพียงพอ สิ่งนี้ใช้ไม่ได้กับระบบ 32 บิต ส่วนสุดท้ายคือการหลีกเลี่ยงจุดทศนิยมในสัญกรณ์ทางวิทยาศาสตร์


4

Pari / GP , 15 + 5 = 20 ไบต์

fibonacci(10^9)

เรียกใช้ด้วยตัวเลือกบรรทัดคำสั่ง-s1gเพื่อจัดสรรหน่วยความจำ 1 Gbytes


1

ทับทิม, 63 ไบต์

ผู้ชายฉันไม่ดีที่เล่นกอล์ฟทับทิม แต่คลาส BigInt ทำสิ่งมหัศจรรย์สำหรับสิ่งนี้ เราใช้อัลกอริทึมเดียวกันกับ Anders Kaseorg

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

นั่นทำให้คุณได้หลักพันจริงๆหรือเปล่า?
dfeuer
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.