x86 32-বিট মেশিন কোড (লিনাক্স সিস্টেম কল সহ): 106 105 বাইট
চেঞ্জলগ: দ্রুত সংস্করণে একটি বাইট সংরক্ষণ করেছে কারণ অফ বাই-ওয়ান ধ্রুবক ফাইব (1 জি) এর জন্য ফলাফল পরিবর্তন করে না।
অথবা (Skylake দিকে) সংস্করণটি 18% ধীর জন্য 102 বাইট (ব্যবহার mov
/ sub
/ cmc
পরিবর্তে lea
/ cmp
ভেতরের লুপ, বহন আউট এবং মোড়ানো এ জেনারেট করতে 10**9
পরিবর্তে 2**32
)। বা অভ্যন্তরের সর্বাধিক লুপের ক্যারি হ্যান্ডলিংয়ের একটি শাখা সহ ~ 5.3x ধীর সংস্করণে 101 বাইট (আমি একটি 25.4% শাখা-ভুল অনুমানের হার পরিমাপ করেছি!)
বা 104/101 বাইটস যদি একটি অগ্রণী শূন্য অনুমোদিত হয়। (আউটপুটটির 1 ডিজিটের হার্ড-কোড এড়ানোর জন্য এটি 1 টি অতিরিক্ত বাইট লাগবে, যা ফিবের জন্য প্রয়োজন হবে যা হয় (10 ** 9%))।
দুর্ভাগ্যক্রমে, টিআইওর এনএএসএম মোডটি -felf32
সংকলক পতাকাগুলিতে উপেক্ষা করছে বলে মনে হচ্ছে । মন্তব্যে পরীক্ষামূলক ধারণাগুলির সমস্ত গণ্ডগোল সহ আমার সম্পূর্ণ উত্স কোডের সাথে এখানে একটি লিঙ্ক রয়েছে ।
এটি একটি সম্পূর্ণ প্রোগ্রাম । এটি ফিবের প্রথম 1000 অঙ্কগুলি প্রিন্ট করে (10 ** 9) তার পরে কিছু অতিরিক্ত অঙ্ক (যার মধ্যে কয়েকটি শেষ ভুল) এর পরে কিছু আবর্জনা বাইট (কোনও নতুন লাইন সহ নয়) অনুসরণ করে। বেশিরভাগ আবর্জনা অ-এএসসিআইআই, তাই আপনি পাইপটি পেতে চাইতে পারেন cat -v
। konsole
যদিও এটি আমার টার্মিনাল এমুলেটর (কেডিএ ) ভাঙবে না । "আবর্জনা বাইটস" ফাইব (999999999) সংরক্ষণ করছে। আমার ইতিমধ্যে -1024
একটি রেজিস্টার ছিল, সুতরাং এটি সঠিক আকারের চেয়ে 1024 বাইট প্রিন্ট করা সস্তা ছিল aper
আমি কেবল মেশিন-কোড গণনা করছি (আমার স্ট্যাটিক এক্সিকিউটেবলের টেক্সট বিভাগের আকার), এমন ফ্লাফ নয় যা এটিকে একটি ELF কার্যকর করার যোগ্য করে তোলে। ( খুব ক্ষুদ্র ELF এক্সিকিউটেবলগুলি সম্ভব , তবে আমি এটি নিয়ে বিরক্ত করতে চাইনি)। এটি বিএসএসের পরিবর্তে স্ট্যাক মেমোরিটি ব্যবহার করার জন্য আরও সংক্ষিপ্ত আকারে পরিণত হয়েছিল, সুতরাং আমি কোনও ধরণের মেটাডেটার উপর নির্ভরশীল না হওয়ায় আমি বাইনারিটিতে অন্য কিছু গণনা না করে ন্যায়সঙ্গত করতে পারি। (স্ট্রিপ স্ট্যাটিক বাইনারি স্বাভাবিক উপায়ে উত্পাদন 340 বাইট ইএলএফ এক্সিকিউটেবল করে তোলে))
আপনি এই কোডটির বাইরে একটি ফাংশন তৈরি করতে পারেন যা আপনি সি থেকে কল করতে পারেন স্ট্যাক পয়েন্টারটি সংরক্ষণ করতে / পুনরুদ্ধার করতে (সম্ভবত কোনও এমএমএক্স রেজিস্টারে) এবং কিছু অন্যান্য ওভারহেড ব্যয় করতে পারে তবে স্ট্রিং সহ ফিরে এসে বাইটগুলিও সংরক্ষণ করতে পারে মেমরিতে, পরিবর্তে একটি write(1,buf,len)
সিস্টেম কল করার জন্য। আমি মনে করি মেশিন কোডে গল্ফিংয়ের ফলে আমাকে এখানে কিছুটা শিথিল হওয়া উচিত, যেহেতু অন্য কেউ কোনও ভাষায় উত্তরও বর্ধিত-নির্ভুলতা ছাড়াই পোস্ট করেনি, তবে আমি মনে করি যে এর ফাংশন সংস্করণটি পুরো পুনরায় গল্ফ না করে 120 বাইটের নিচে থাকা উচিত জিনিস।
অ্যালগরিদম:
ব্রুট ফোর্স a+=b; swap(a,b)
, কেবল শীর্ষস্থানীয়> = 1017 দশমিক সংখ্যা রাখার জন্য প্রয়োজন অনুসারে কাটা। এটি আমার কম্পিউটারে 1 মিমি 13 এ চলেছে (বা 322.47 বিলিয়ন ক্লক চক্র + - 0.05%) (এবং কোড-আকারের কয়েকটি অতিরিক্ত বাইট সহ কয়েক% দ্রুত হতে পারে বা লুপ আন্রোলিং থেকে অনেক বড় কোড আকারের সাথে 62 এর নিচে হতে পারে No চতুর গণিত, কেবল কম ওভারহেড দিয়ে একই কাজ করা)। এটি @ অ্যান্ডারক্যাসেরগের পাইথন বাস্তবায়নের উপর ভিত্তি করে , যা আমার কম্পিউটারে (৪.৪ গিগাহার্টজ স্কাইলেক আই --6700০০ কে) ১২ মিনিটে চলে। কোনও সংস্করণে কোনও এল 1 ডি ক্যাশে মিস হয় না, তাই আমার ডিডিআর 4-2666 কোনও বিষয় নয়।
পাইথনের বিপরীতে, আমি প্রসারিত-যথার্থ নম্বরগুলি এমন ফর্ম্যাটে সংরক্ষণ করি যা দশমিক অঙ্ককে ছাঁটাই করে তোলে । আমি 32-বিট পূর্ণসংখ্যার প্রতি 9 দশমিক সংখ্যার গোষ্ঠীগুলি সঞ্চয় করি, সুতরাং একটি পয়েন্টার অফসেট কম 9 টি সংখ্যা বাদ দেয়। এটি কার্যকরভাবে 1-বিলিয়নের ভিত্তি, যা 10 এর শক্তি ((এটি শুদ্ধ কাকতালীয় বিষয় যে এই চ্যালেঞ্জটির জন্য 1-বিলিয়ন ফিবোনাচি নম্বর প্রয়োজন, তবে এটি আমাকে দুটি বাইট বনাম দুটি পৃথক ধ্রুবক সংরক্ষণ করতে পারে))
জিএমপি পরিভাষা অনুসরণ করে , বর্ধিত-নির্ভুলতার সংখ্যার প্রতিটি 32-বিট অংশকে "অঙ্গ" বলা হয়। যোগ করার সময় ক্যারি-আউটটি 1e9 এর সাথে তুলনা করে ম্যানুয়ালি উত্পন্ন করতে হবে তবে পরের অঙ্গটির জন্য সাধারণ ADC
নির্দেশের ইনপুট হিসাবে সাধারণত ব্যবহৃত হয় । (আমাকে [0..999999999]
2 ^ 32 ~ = 4.295e9 এর চেয়ে নিজে নিজেও পরিসীমাতে আবৃত করতে হবে the আমি তুলনা থেকে বহনকারী ফলাফলটি ব্যবহার করে lea
+ এর সাথে এই শাখাবিহীনভাবে করি cmov
))
যখন সর্বশেষ অঙ্গটি শূন্য-বহন না করে, বাহ্যিক লুপের পরবর্তী দুটি পুনরাবৃত্তি 1 অঙ্গ থেকে সাধারণের চেয়ে বেশি পড়ে, তবে এখনও একই জায়গায় লিখুন। এটি memcpy(a, a+4, 114*4)
1 টি অঙ্গ দ্বারা ডান-শিফট করার মতো , তবে পরবর্তী দুটি সংযোজন লুপগুলির অংশ হিসাবে এটি করা। এটি প্রতি ~ 18 পুনরাবৃত্তি ঘটে।
আকার-সংরক্ষণ এবং পারফরম্যান্সের জন্য হ্যাকস:
আমি জানি যখন lea ebx, [eax-4 + 1]
তার পরিবর্তে পছন্দসই স্টাফ । এবং এমন জায়গাগুলি ব্যবহার করা যেখানে অলসতা কেবলমাত্র একটি ক্ষুদ্র প্রভাব ফেলে।mov ebx, 1
eax=4
loop
LOOP
adc
অভ্যন্তরীণ লুপে বাফার শুরুতে লিখতে গিয়ে আমরা যে পয়েন্টগুলি পড়েছি সেগুলি অফসেট করে বিনামূল্যে 1 টি অঙ্গ দিয়ে কাটুন । আমরা থেকে পড়েছি [edi+edx]
, এবং লিখতে [edi]
। তাই আমরা গন্তব্যটির জন্য পঠন-লিখনের অফসেট পেতে edx=0
বা পেতে পারি 4
। আমাদের এটি দুটি ক্রমাগত পুনরাবৃত্তির জন্য করতে হবে, প্রথমে উভয়কে অফসেট করে, তারপরে কেবল ডিএসটি অফসেট করে। esp&4
বাফার্সের সামনের দিকে পয়েন্টারগুলি পুনরায় সেট করার আগে আমরা দ্বিতীয় কেসটি সনাক্ত করি (ব্যবহার করে &= -1024
, কারণ বাফারগুলি সারিবদ্ধ হয়েছে)। কোডে মন্তব্য দেখুন।
(ক স্ট্যাটিক এক্সিকিউটেবল জন্য) লিনাক্স প্রক্রিয়া-সূচনার পরিবেশ শূন্য সবচেয়ে রেজিস্টার, এবং স্ট্যাকের মেমরি নিচে esp
/ rsp
zeroed করা হয়। আমার প্রোগ্রাম এর সুবিধা নেয়। এটির কল-ফাংশন সংস্করণে (যেখানে আনলোকেটেড স্ট্যাকটি নোংরা হতে পারে), আমি জিরো মেমরির জন্য পয়েন্ট করতে পারি (পয়েন্টার সেট আপ করার জন্য সম্ভবত আরও 4 বাইট ব্যয়ে)। জিরোয়িং edx
নিতে 2 বাইট লাগবে। X86-64 সিস্টেম ভি এবিআই এর কোনওটির গ্যারান্টি দেয় না, তবে লিনাক্স এর প্রয়োগটি শূন্য করে (কার্নেলের বাইরে তথ্য-ফাঁস এড়াতে)। একটি গতিশীলভাবে সংযুক্ত প্রক্রিয়াতে, /lib/ld.so
আগে চালিত হয় _start
এবং শূন্যহীন (এবং সম্ভবত স্ট্যাক পয়েন্টারের নীচে স্মৃতিতে আবর্জনা) নিবন্ধগুলি ছেড়ে যায়।
আমি রাখা -1024
মধ্যে ebx
লুপ ব্যবহার বাহিরে জন্য। bl
অভ্যন্তরীণ লুপগুলির জন্য কাউন্টার হিসাবে ব্যবহার করুন , শূন্যে শেষ হওয়া (যা এর নিম্ন বাইট যা -1024
এইভাবে লুপের বাইরে ব্যবহারের জন্য ধ্রুবকটি পুনরুদ্ধার করে)। ইন্টেল হাসওয়েল এবং পরে কম 8 রেজিস্টারগুলির জন্য আংশিক-নিবন্ধক একত্রিত করার জরিমানা নেই (এবং বাস্তবে তাদের আলাদা আলাদা নামকরণও করবেন না) , সুতরাং সম্পূর্ণ রেজিস্ট্রারের উপর নির্ভরতা রয়েছে, যেমন এএমডি-তে (এখানে কোনও সমস্যা নয়)। এটি নেহালেম এবং এর আগেও ভয়াবহ হবে, যদিও মার্জ হওয়ার সময় এর আংশিক-নিবন্ধক স্টল রয়েছে। এমন অন্যান্য জায়গাগুলি রয়েছে যেখানে আমি আংশিক রেগগুলি লিখি এবং তারপরে সম্পূর্ণ xor
জেনারেশনটি পড়ি বা এmovzx
, সাধারণত কারণ আমি জানি যে কিছু পূর্ববর্তী কোডটি উচ্চতর বাইটগুলি শূন্য করে এবং আবার এটি এএমডি এবং ইন্টেল এসএনবি-পরিবারে ভাল, তবে ইন্টেল প্রি-স্যান্ডিব্রিজে ধীর হয়।
আমি 1024
স্টাডাউট ( sub edx, ebx
) তে লিখতে বাইটের সংখ্যা হিসাবে ব্যবহার করি , তাই আমার প্রোগ্রামটি ফিবোনাকির অঙ্কগুলির পরে কিছু আবর্জনা বাইট প্রিন্ট করে, কারণ mov edx, 1000
বেশি বাইট খরচ হয়।
adc ebx,ebx
EBX = সিএফ পেতে EBX = 0 এর সাথে (ব্যবহৃত নয়) , 1 বাইট বনাম সংরক্ষণ করা setc bl
।
dec
/ jnz
একটি adc
লুপের অভ্যন্তরে সিএফ সংরক্ষণ করে আংশিক-পতাকা স্টল তৈরি না করে যখন adc
ইন্টেল স্যান্ডিব্রিজে এবং পরে পতাকাগুলি পড়ে। এটি পূর্বের সিপিইউতে খারাপ , তবে এফএইকে স্কাইলেকে মুক্ত। বা সবচেয়ে খারাপভাবে, একটি অতিরিক্ত op
esp
দানবীয় রেড-জোন হিসাবে নীচে মেমরিটি ব্যবহার করুন । যেহেতু এটি একটি সম্পূর্ণ লিনাক্স প্রোগ্রাম, আমি জানি যে আমি কোনও সিগন্যাল হ্যান্ডলার ইনস্টল করি নি, এবং অন্য কোনও কিছুই অবিচ্ছিন্নভাবে ব্যবহারকারী-স্থান স্ট্যাক মেমরিটিকে ক্লিনবার করবে না। অন্যান্য ওএসের ক্ষেত্রে এটি নাও হতে পারে।
ইউপ ইস্যু ব্যান্ডউইদথকে সংরক্ষণ করার জন্য স্ট্যাক ইঞ্জিনের সুবিধা নিন pop eax
(1 টি ইউওপি + মাঝে মাঝে স্ট্যাক-সিঙ্ক ইউওপ) পরিবর্তে lodsd
(হাসওয়েল / স্কাইলেকে 2 আউপ, আইভিবিতে 3 এবং এর আগে অ্যাগনার ফগের নির্দেশ সারণী অনুসারে )। IIRC, এই 73. 83 সম্পর্কে সেকেন্ড থেকে রান-টাইম বাদ আমি সম্ভবত একটি ব্যবহার করা থেকে একই গতি পেতে পারে mov
একটি ইন্ডেক্স অ্যাড্রেসিং মোড সঙ্গে, মত mov eax, [edi+ebp]
যেখানে ebp
src এবং DST বাফার মধ্যে অফসেট ঝুলিতে। (এটি অভ্যন্তরীণ লুপের বাইরের কোডটিকে আরও জটিল করে তুলবে, এসিআরসি অদলবদলের অংশ হিসাবে অফসেট নিবন্ধকে অস্বীকার করার জন্য এবং ফিবোনাচি পুনরাবৃত্তির জন্য ডিএসটি।) আরও জানার জন্য নীচের "পারফরম্যান্স" বিভাগটি দেখুন।
প্রথম পুনরাবৃত্তিটি যে কোনও জায়গায় স্মৃতিতে stc
সঞ্চয় করার পরিবর্তে একটি ক্যারি-ইন (এক বাইট ) দিয়ে ক্রম শুরু করুন 1
। মন্তব্যগুলিতে নথিভুক্ত অনেকগুলি অন্যান্য সমস্যা-নির্দিষ্ট স্টাফ।
এর সাথে উত্পন্ন এনএএসএম তালিকা (মেশিন-কোড + উত্স)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
এর বাইরে আরও কিছু বাইটের গল্ফ দেওয়ার সম্ভবত জায়গা আছে তবে আমি ইতিমধ্যে 2 দিনের বেশি সময় ধরে কমপক্ষে 12 ঘন্টা ব্যয় করেছি। আমি গতি ত্যাগ করতে চাই না, যদিও এটি যথেষ্ট দ্রুতগতির চেয়ে অনেক বেশি উপায় এবং ব্যয়ের গতির ক্ষেত্রে এটি আরও ছোট করার মতো জায়গা রয়েছে । আমার পোস্ট করার কারণের একটি অংশটি দেখায় যে আমি কতটা দ্রুত ব্রুট ফোর্স asm সংস্করণ তৈরি করতে পারি। যদি কেউ সত্যিকার অর্থে ন্যূনতম আকারের জন্য যেতে চান তবে সম্ভবত 10x ধীর (যেমন বাইটে 1 ডিজিট), এটিকে একটি বিন্দু হিসাবে অনুলিপি করুন।
ফলাফল নির্বাহযোগ্য (থেকে yasm -felf32 -Worphan-labels -gdwarf2 fibonacci-1G.asm && ld -melf_i386 -o fibonacci-1G fibonacci-1G.o
) হ'ল 340 বি (ফেলা):
size fibonacci-1G
text data bss dec hex filename
105 0 0 105 69 fibonacci-1G
কর্মক্ষমতা
আভ্যন্তরীণ adc
লুপটি স্কাইলেকে 10 টি ফিউজড-ডোমেন উপস (প্রতি 128 বাইটে +1 স্ট্যাক-সিঙ্ক ইউওপ) হয়, সুতরাং এটি স্কাইলেকে প্রতি ফ্রন্ট-এন্ড থ্রুপুট (স্ট্যাক-সিঙ্ক ইউপগুলিকে উপেক্ষা করে) দ্বারা প্রতি 2.5 ~ 2.5 চক্রের একটিতে ইস্যু করতে পারে) । পরের পুনরাবৃত্তির লুপ বহনকারী নির্ভরতা শৃঙ্খলার জন্য adc
- cmp
- - -> সমালোচনামূলক-পথের বিলম্বটি 2 চক্র, adc
সুতরাং প্রতিবন্ধকটি পুনরাবৃত্তির প্রতি 2.5 চক্রের সামনের প্রান্তের ইস্যু সীমা হতে হবে।
adc eax, [edi + edx]
এক্সিকিউশন পোর্টগুলির জন্য 2 অব্যবহৃত-ডোমেন উফ: লোড + এএলইউ। এটি ডিকোডারগুলিতে (1 টি ফিউজড-ডোমেন ইউওপ) মাইক্রো ফিউজ করে তবে ইন্ডেক্সযুক্ত অ্যাড্রেসিং মোড এমনকি হ্যাসওয়েল / স্কাইলেকে ইস্যু পর্যায়ে আন-লেমিনেটগুলি 2 টি ফিউজড-ডোমেন উপগুলিতে স্থান দেয় । আমি ভেবেছিলাম এটি মাইক্রো-ফিউজড থাকবে, যেমন add eax, [edi + edx]
করে তবে এটি ইন্ডেক্সড অ্যাড্রেসিং মোডগুলি মাইক্রো-ফিউজড রাখলে উওসের পক্ষে ইতিমধ্যে 3 টি ইনপুট (পতাকা, স্মৃতি এবং গন্তব্য) কার্যকর হয় না। আমি যখন এটি লিখেছিলাম, আমি ভেবেছিলাম এটির পারফরম্যান্স খারাপ হবে না, তবে আমি ভুল ছিল। ছাঁটাইয়ের পরিচালনা করার এই পদ্ধতিটি edx
0 বা 4 হ'ল প্রতিবার অভ্যন্তরীণ লুপকে ধীর করে দেয় ।
ডিস্টে অফসেট edi
করে edx
এবং স্টোর সামঞ্জস্য করে রিড-রাইটিং অফসেটটি পরিচালনা করা আরও দ্রুত হবে । সুতরাং adc eax, [edi]
/ ... / mov [edi+edx], eax
/ lea edi, [edi+4]
পরিবর্তে stosd
। হাসওয়েল এবং পরবর্তীকালে একটি সূচিবদ্ধ স্টোর মাইক্রো-ফিউজড রাখতে পারে। (স্যান্ডিব্রিজ / আইভিবিও এটিকে মুক্ত করে দেবে))
ইনটেল হাসওলে adc
এবং cmovc
এর আগে এবং 2 সি ল্যাটেন্সি সহ প্রতিটি 2 টি উফ । ( adc eax, [edi+edx]
এখনও হাসওলে আন-লেমিনেটেড এবং 3 টি ফিউজড-ডোমেন উওস হিসাবে ইস্যু করা হয়েছে)। ব্রডওয়েল এবং পরে দীর্ঘকাল ধরে এএমডি থাকায় কেবল এফএমএ (হাসওয়েল), তৈরি adc
এবং cmovc
(এবং কয়েকটি অন্যান্য জিনিস) একক-উওপ নির্দেশাবলীর চেয়ে আরও বেশি 3-ইনপুট উফগুলিকে অনুমতি দিন। (এটি একটি কারণ যা এএমডি দীর্ঘ সময়ের জন্য প্রসারিত-নির্ভুলতা GMP বেনমার্কগুলিতে ভাল করেছে)) যাইহোক, হাসওয়ের অভ্যন্তরীণ লুপটি 12 টি উওস (মাঝেমধ্যে +1 স্ট্যাক-সিঙ্ক উওপ) হওয়া উচিত, প্রতি ফ্রন্ট-এন্ড বাধা ব্যয় প্রতি c 3 সি with এটি সর্বোত্তম ক্ষেত্রে, স্ট্যাক সিঙ্ক সিওগুলিকে উপেক্ষা করে।
একটি লুপের মধ্যে pop
ভারসাম্য ছাড়াই ব্যবহার করার push
অর্থ লুপটি এলএসডি (লুপ স্ট্রিম ডিটেক্টর) থেকে চলতে পারে না এবং প্রতিবার ইউওপ ক্যাশে থেকে আইডিকিউতে পুনরায় পড়তে হয়। যদি কিছু হয় তবে এটি স্কাইলেকের পক্ষে একটি ভাল জিনিস, যেহেতু 9 বা 10 টি ইউওপ লুপ প্রতিটি চক্র 4 টি উওপে অনুকূলভাবে ইস্যু করে না । এটি কেন এতটা সাহায্যের lodsd
সাথে প্রতিস্থাপন করা তার সম্ভবত একটি অংশ pop
। (এলএসডি উওপগুলিকে তালাবদ্ধ করতে পারে না কারণ এটি স্ট্যাক-সিঙ্ক ইউওপ প্রবেশের জন্য ঘর ছাড়বে না )) ( বিটিডাব্লু , একটি মাইক্রোকোড আপডেট এলআরডি পুরোপুরি স্কাইলেক এবং স্কাইলেক-এক্সকে নিষ্ক্রিয় করতে পারে। উপরে যে আপডেট পাওয়ার আগে।)
আমি এটি হ্যাসওয়েলে প্রোফাইলে রেখেছি এবং দেখেছি এটি 381.31 বিলিয়ন ক্লক চক্র (সিপিইউ ফ্রিকোয়েন্সি নির্বিশেষে, যেহেতু এটি কেবলমাত্র L1D ক্যাশে ব্যবহার করে, স্মৃতি নয়) তে চলে in ফ্রন্ট-এন্ড ইস্যু থ্রুটপুট ছিল প্রতি ঘড়ি প্রতি 3.72 ফিউজড-ডোমেন উফস, স্কাইলেকের জন্য বনাম 3.70। (তবে অবশ্যই চক্র প্রতি নির্দেশাবলী ২.8787 থেকে ২.৪২ এ নেমেছিল , কারণ adc
ও cmov
হাসওলে ২ জন উওপ।)
push
প্রতিস্থাপন stosd
সম্ভবত এতটা সাহায্য করবে না, কারণ adc [esp + edx]
প্রতিবার স্ট্যাক-সিঙ্ক ইউওপকে ট্রিগার করবে। আর বাইট খরচ জন্য হবে std
, যাতে lodsd
অন্য দিক যায়। ( mov [edi], eax
/ lea edi, [edi+4]
প্রতিস্থাপন stosd
করা একটি জয়, ১০০ এম ইটারের জন্য ৩২,৯৯৯ টি সাইকেল থেকে ১০০ এম ইটারের জন্য ৩১,৯৯৪ মাইকেল পর্যন্ত চলেছে It stosd
স্টোর-ঠিকানা / স্টোর-ডেটা উফগুলি মাইক্রো-ফিউজড নয়, তাই push
+ স্ট্যাক-সিঙ্কের সাথে 3 টি উওপ হিসাবে ডেকেড হয় বলে মনে হয়) উফস এখনও তত দ্রুত হতে পারে stosd
)
স্কাইলেকে দ্রুত 105 বি সংস্করণের জন্য 114 টি অঙ্গগুলির 1G পুনরাবৃত্তির জন্য ~ 322.47 বিলিয়ন চক্রের প্রকৃত কর্মক্ষমতা অভ্যন্তরীণ লুপের পুনরাবৃত্তির প্রতি 2.824 চক্রের কাজ করে । ( ocperf.py
নীচে আউটপুট দেখুন)। স্থির বিশ্লেষণ থেকে আমার পূর্বাভাসের তুলনায় এটি আস্তে আস্তে রয়েছে, তবে আমি বাইরের লুপের ওভারহেড এবং কোনও স্ট্যাক-সিঙ্ক ইউপগুলিকে উপেক্ষা করছি।
নিখুঁত কাউন্টারগুলি branches
এবং branch-misses
দেখায় যে অভ্যন্তরীণ লুপ প্রতি বাহিরের লুপের প্রতি একবার ভুল ভবিষ্যদ্বাণী করে (শেষ পুনরাবৃত্তিতে, যখন এটি নেওয়া হয় না)। এটি অতিরিক্ত সময়ের অংশ হিসাবেও দায়ী।
অভ্যন্তরীণ-সর্বাধিক লুপটিকেmov esi,eax
sub eax,ebp
cmovc eax, esi
cmc
lea esi, [eax - 1000000000]
/ cmp ebp,eax
/ cmovc
(6 + 2 + 3 = 11 বি এর পরিবর্তে / / / (2 + 2 + 3 + 1 = 8 বি) ব্যবহার করে সমালোচনামূলক পথে 3-চক্রের বিলম্বিত করে কোড-আকার সংরক্ষণ করতে পারতাম )। cmov
/ stosd
জটিল পথ বন্ধ। (এর ইনক্রিমেন্ট-এডি উওপ stosd
স্টোর থেকে পৃথকভাবে চলতে পারে, সুতরাং প্রতিটি পুনরাবৃত্তি একটি স্বল্প নির্ভরতা শৃঙ্খলা থেকে কাঁটাচামচ করে)) এটি ebp সূচনার নির্দেশকে পরিবর্তন করে অন্য 1 বি বাঁচাতে ব্যবহৃত lea ebp, [ecx-1]
হয়েছিল mov ebp,eax
, কিন্তু আমি আবিষ্কার করেছি যে ভুল আছেebp
ফলাফল পরিবর্তন হয়নি। এটি একটি অঙ্গকে কেয়ার মোড়ানোর ও উত্পাদন করার পরিবর্তে ঠিক == 1000000000 হতে দেবে, তবে এই ত্রুটিটি আমরা ফাইব () এর বাড়ার চেয়ে ধীর গতিতে প্রচার করে, সুতরাং চূড়ান্ত ফলাফলের শীর্ষস্থানীয় 1 কে সংখ্যা পরিবর্তন না করার জন্য এটি ঘটে। এছাড়াও, আমি মনে করি যে ত্রুটিটি কেবল তখনই সংশোধন করতে পারে যখন আমরা কেবল যুক্ত করব, যেহেতু অতিরিক্ত প্রবাহ ছাড়াই এটিকে ধরে রাখার জন্য একটি অঙ্গ রয়েছে। এমনকি 1G + 1G একটি 32-বিট পূর্ণসংখ্যার উপরি প্রবাহ করে না, সুতরাং এটি শেষ পর্যন্ত উপরের দিকে ঘুরতে থাকবে বা দূরে ছাঁটাই হবে।
3 সি ল্যাটেন্সি সংস্করণটি 1 অতিরিক্ত ইউওপ, সুতরাং সামনের প্রান্তটি স্কাইলেকে প্রতি 2.75c চক্রের একটিতে ইস্যু করতে পারে, এটি ব্যাক-এন্ডের চেয়ে সামান্য দ্রুত চালানো যায়। (হাসওলে, এটি 13 টি উওপ হবে যেহেতু এটি এখনও ব্যবহার করে adc
এবং cmov
, এবং ফ্রন্ট-এন্ড প্রতি ইটার প্রতি 3.25c এ বাধা)।
অনুশীলনে এটি স্কাইলেকে (প্রতি অঙ্গ প্রতি ৩.৪৪ চক্র) ধীরে ধীরে ১.১18 ধীর গতির একটি ফ্যাক্টর চালায়, 3 / 2.5 = 1.2 এর পরিবর্তে আমি প্রান্ত-প্রান্তের বাটনেলেকটি স্ট্যাক-সিঙ্ক ছাড়াই কেবলমাত্র অভ্যন্তরীণ লুপের দিকে তাকানো থেকে প্রতিস্থাপনের জন্য বাধা পেয়েছি pred uops। যেহেতু স্ট্যাক-সিঙ্ক উওপগুলি কেবল দ্রুত সংস্করণে আঘাত করে (লেটেন্সি না করে সামনের দিকে প্রান্তিকভাবে আটকা পড়ে) তাই এটি ব্যাখ্যা করতে বেশি লাগে না। যেমন 3 / 2.54 = 1.18।
আরেকটি বিষয় হ'ল 3 সি ল্যাটেন্সি সংস্করণটি সমালোচনামূলক পথটি চালিত হওয়ার সময় অভ্যন্তরীণ লুপটি ছেড়ে দেওয়ার ক্ষেত্রে ভুল ধারণাটি সনাক্ত করতে পারে (কারণ সামনের প্রান্তটি পিছনের দিকের সামনে এগিয়ে যেতে পারে, আদেশের বাইরে থাকা কার্যকরভাবে লুপটি চালিয়ে দেয়) উফ কাউন্টার), সুতরাং কার্যকর ভুল অনুমানের জরিমানা কম হয়। এই ফ্রন্ট-এন্ড চক্রটি হারাতে ব্যাক-এন্ডটি ধরতে দেয়।
এটি যদি না হয় তবে আমরা cmc
বাহ্যিক লুপের একটি শাখা ব্যবহার করে ক্যারি_আউট -> এডএক্স এবং ইএসপি অফসেটের শাখাবিহীন হ্যান্ডলিংয়ের পরিবর্তে 3 সি সংস্করণটি দ্রুততর করতে পারি । adc
পূর্ববর্তী অভ্যন্তরীণ লুপ থেকে উওপগুলি যখন ফ্লাইটে ছিল তখন কোনও ডেটা নির্ভরতার পরিবর্তে নিয়ন্ত্রণ নির্ভরতার জন্য শাখা-পূর্বাভাস + অনুমানমূলক সম্পাদনা পরবর্তী পুনরাবৃত্তিটি লুপটি চালানো শুরু করতে পারে । শাখাবিহীন সংস্করণে, অভ্যন্তরীণ লুপের লোড ঠিকানাগুলির adc
সর্বশেষ অঙ্গটির শেষ থেকে সিএফ এর উপর ডেটা নির্ভরতা থাকে ।
সামনের প্রান্তে 2 সি ল্যাটেন্সির অভ্যন্তরীণ-লুপ সংস্করণটি বাধা দেয়, তাই পিছনের প্রান্তটি বেশ রাখে। যদি বাইরের-লুপ কোডটি উচ্চ-বিলম্বিত হয় তবে সম্মুখ-প্রান্তটি অভ্যন্তরীণ লুপের পরবর্তী পুনরাবৃত্তি থেকে উওস জারি করতে পারে। (তবে এক্ষেত্রে বহিরাগত লুপের স্টাফ প্রচুর পরিমাণে আইএলপি এবং কোনও উচ্চ-বিন্দুযুক্ত সামগ্রী নেই, তাই ব্যাক-এন্ডে অর্ডার শিডিয়ুলারের হিসাবে উওফগুলির মাধ্যমে চিবানো শুরু করার সময় তেমন কিছু করার দরকার নেই) as তাদের ইনপুট প্রস্তুত হয়ে যায়)।
### 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 টি নির্দেশাবলী চালায় runs
uops_issued
হ'ল একটি ফিউজড-ডোমেন কাউন্ট (ফ্রন্ট-এন্ড ইস্যু ব্যান্ডউইথের জন্য প্রাসঙ্গিক), তবে uops_executed
এটি একটি অব্যবহৃত-ডোমেন গণনা (এক্সিকিউশন পোর্টগুলিতে প্রেরিত উফ সংখ্যা)। মাইক্রো-লয় প্যাকগুলি এক নিলীন ডোমেন-uop মধ্যে 2 unfused ডোমেন-uops কিন্তু যৌন-বর্জন মানে কিছু নিলীন ডোমেন-uops কোনো মৃত্যুদন্ড পোর্ট প্রয়োজন হবে না যে। উওপ্স এবং ফিউজড বনাম অপ্রয়োজনীয় ডোমেন গণনা সম্পর্কে আরও তথ্যের জন্য লিঙ্কিত প্রশ্নটি দেখুন। (এছাড়াও আগ্নার ফগের নির্দেশাবলী সারণী এবং উর্চ গাইড এবং এসও x86 ট্যাগ উইকির অন্যান্য দরকারী লিঙ্কগুলি দেখুন )।
বিভিন্ন জিনিস পরিমাপ করার অন্য একটি রান থেকে: একই দুটি 456 বি বাফার পড়ার / লেখার জন্য যেমন প্রত্যাশা করা হয়েছিল, L1D ক্যাশে মিস করা সম্পূর্ণ তুচ্ছ। অভ্যন্তরীণ-লুপ শাখা প্রতি বাহিরের লুপের জন্য একবার ভুল ধারণা দেয় (যখন লুপটি ছেড়ে যাওয়ার জন্য নেওয়া হয় না)। (মোট সময় বেশি, কারণ কম্পিউটারটি সম্পূর্ণ অলস ছিল না ably সম্ভবত অন্যান্য লজিক্যাল কোর কিছুটা সময় সক্রিয় ছিল, এবং আরও বেশি সময় ব্যঘাতগুলিতে ব্যয় করা হয়েছিল (যেহেতু ব্যবহারকারী-স্থান পরিমাপের ফ্রিকোয়েন্সিটি 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
আমার কোডটি রাইজনে কম চক্রের মধ্যে ভালভাবে চলতে পারে, যা চক্র প্রতি 5 টি উপ জারি করতে পারে (বা 6 টি যখন 2-উওপ নির্দেশিকা থাকে যেমন রাইজনে অ্যাভিএক্স 256 বি স্টাফ থাকে)। আমি নিশ্চিত নই যে এর ফ্রন্ট- stosd
এন্ডটি কী করবে , যা রাইজেনের 3 টি উপ (ইন্টেলের মতো)। আমি মনে করি অভ্যন্তরীণ লুপের অন্যান্য নির্দেশাবলী হ'ল স্কাইলাক এবং সমস্ত একক-উওপের মতো একই বিলম্ব। (সহ adc eax, [edi+edx]
, যা স্কাইলেকের চেয়ে সুবিধাজনক)।
এটি সম্ভবত উল্লেখযোগ্যভাবে ছোট হতে পারে তবে 9x ধীর হতে পারে, যদি আমি প্রতি বাইটে 1 দশমিক অঙ্ক হিসাবে সংখ্যাগুলি সঞ্চয় করি । সাথে ক্যারি আউট তৈরি করা cmp
এবং সাথে সামঞ্জস্য করা cmov
একই কাজ করবে তবে 1/9 কাজটি করুন। বাইট প্রতি 2 দশমিক অঙ্ক (বেস -100, ধীর গতিতেDAA
4-বিট বিসিডি নয় ) কাজ করবে এবং div r8
/ add ax, 0x3030
0-99 বাইটকে মুদ্রণ ক্রমে দুটি ASCII অঙ্কগুলিতে পরিণত করে। তবে প্রতি বাইটে 1 ডিজিটের মোটেই প্রয়োজন div
হয় না, কেবল লুপিং এবং 0x30 যুক্ত করা। আমি যদি প্রিন্টিং অর্ডারে বাইটগুলি সঞ্চয় করি তবে এটি ২ য় লুপটিকে সত্যিই সহজ করে তুলবে।
64৪-বিট পূর্ণসংখ্যা (18৪-বিট মোডে) -এর জন্য 18 বা 19 দশমিক সংখ্যা ব্যবহার করা এটি প্রায় দ্বিগুণ দ্রুত চালিত হতে পারে, তবে সমস্ত REX উপসর্গের জন্য এবং 64-বিট ধ্রুবকগুলির জন্য উল্লেখযোগ্য কোড-আকারের ব্যয় করতে হবে। 64-বিট মোডে 32-বিট অঙ্গগুলির pop eax
পরিবর্তে ব্যবহার করা বাধা দেয় lodsd
। আমি এখনও ব্যবহার করে রেক্স উপসর্গ এড়াতে পারে esp
একটি অ-পয়েন্টার আঁচড়ের দাগ রেজিস্টার হিসাবে (ব্যবহার সোয়াপিং esi
এবং esp
), পরিবর্তে ব্যবহার r8d
একটি 8th রেজিস্টার হিসাবে।
যদি কলযোগ্য ফাংশন সংস্করণ তৈরি করা হয়, 64৪-বিটে রূপান্তর করা এবং ব্যবহার r8d
করা সংরক্ষণ / পুনরুদ্ধারের চেয়ে সস্তা হতে পারে rsp
। -৪-বিট ওয়ান-বাইট dec r32
এনকোডিং ব্যবহার করতে পারে না (যেহেতু এটি একটি রেক্স উপসর্গ)। তবে বেশিরভাগ ক্ষেত্রে আমি dec bl
2 বাইট ব্যবহার করে শেষ করেছি । (কারণ আমার উপরের বাইটগুলির ধ্রুবক রয়েছে ebx
এবং কেবল এটি কেবল অভ্যন্তরীণ লুপগুলির বাইরে ব্যবহার করি যা ধ্রুবকের নিম্ন বাইট হওয়ায় কাজ করে 0x00
))
উচ্চ-সম্পাদনা সংস্করণ
সর্বাধিক পারফরম্যান্সের জন্য (কোড-গল্ফ নয়), আপনি অভ্যন্তরীণ লুপটি আনরোল করতে চান যাতে এটি প্রায় 22 টি পুনরাবৃত্তিতে সঞ্চালিত হয়, যা শাখা-ভবিষ্যদ্বাণীকারীদের ভাল করার জন্য একটি সংক্ষিপ্ত পর্যায়ে নেওয়া / না নেওয়া প্যাটার্ন। আমার পরীক্ষায়, mov cl, 22
একটি .inner: dec cl/jnz .inner
লুপের আগে খুব কম অল্পবিস্তর হয় (যেমন 0.05%, অভ্যন্তরীণ লুপের সম্পূর্ণ রান প্রতি একের চেয়ে অনেক কম) তবে mov cl,23
অভ্যন্তরীণ লুপের প্রতি 0.35 থেকে 0.6 বার পর্যন্ত ভুল ধারণা করা হয়। 46
বিশেষত খারাপ, ইন্টারনো-লুপ প্রতি ~ 1.28 বার (100 মিটার আউট-লুপের পুনরাবৃত্তির জন্য 128M বার) ভুল অনুমান করা। 114
অভ্যন্তরীণ লুপের প্রতি ঠিক একবার অনুমান করা হয়েছিল, যেমনটি আমি ফিবোনাচি লুপের অংশ হিসাবে পেয়েছি।
আমি কৌতূহলী হয়েছি এবং এটি ব্যবহার করে, একটি দিয়ে 6 দিয়ে অভ্যন্তরীণ লুপটি আনলোলিং করেছি %rep 6
(কারণ এটি 114 টি সমানভাবে বিভক্ত করে)। যা বেশিরভাগ শাখা-মিসগুলি নির্মূল করে। আমি edx
নেতিবাচক তৈরি করেছি এবং এটি স্টোরের অফসেট হিসাবে ব্যবহার করেছি mov
, তাই adc eax,[edi]
মাইক্রো-ফিউজড থাকতে পারি। (এবং তাই আমি এড়াতে পারে stosd
)। আমি ব্লকটি lea
আপডেট edi
করার জন্য %rep
টানলাম, সুতরাং এটি 6 টি দোকানে কেবল একটি পয়েন্টার-আপডেট করে।
আমি বাইরের লুপের সমস্ত আংশিক-নিবন্ধকৃত জিনিসগুলি থেকেও মুক্তি পেয়েছি, যদিও আমি তা গুরুত্বপূর্ণ বলে মনে করি না। এটি চূড়ান্ত এডিসির উপর নির্ভর না করে বাইরের লুপের শেষে সিএফ রাখতে কিছুটা সহায়তা করেছে, তাই অভ্যন্তরীণ লুপের কিছু উপ শুরু করতে পারে। বাইরের লুপ কোডটি সম্ভবত কিছুটা আরও অনুকূল করা যেতে পারে, যেহেতু কেবলমাত্র 2 টি নির্দেশাবলী (যেহেতু আমার কাছে এখনও 1 টি ছিল) দিয়ে neg edx
প্রতিস্থাপন xchg
করার পরে আমি শেষ কাজটি করেছি mov
এবং 8-বিট বাদ দেওয়ার সাথে সাথে ডিপ চেইনগুলি পুনরায় সাজিয়েছি স্টাফ নিবন্ধ।
এটি কেবল ফিবোনাচি লুপের এনএএসএম উত্স। এটি মূল সংস্করণের that বিভাগটির জন্য একটি ড্রপ-ইন প্রতিস্থাপন।
;;;; 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% )
এটি একই ফাইব (1 জি) এর জন্য 73৩ সেকেন্ডের পরিবর্তে .3২.৩ সেকেন্ডে একই আউটপুট উত্পাদন করে। (২3৩.১4646 জি চক্র, 322২.৪6767 জি বনাম। যেহেতু সমস্ত কিছু এল 1 ক্যাশে হিট হয়েছে তাই মূল ঘড়ির চক্রগুলি আমাদের দেখার দরকার আছে))
uops_issued
গণনার নীচে খুব নিচের মোট সংখ্যা uops_executed
গণনা করুন। এর অর্থ তাদের মধ্যে অনেকেই মাইক্রো-ফিউজড ছিলেন: ফিউজড ডোমেনে 1 ইওপ (ইস্যু / আরওবি), তবে অব্যবহৃত ডোমেনের 2 টি উওপ (শিডিয়ুলার / এক্সিকিউশন ইউনিট)। এবং ইস্যু / পুনর্নামকরণের পর্যায়ে এই কয়েকজনকে বাদ দেওয়া হয়েছিল (যেমন mov
রেজিস্টার অনুলিপি করা, বা- xor
জেরোইং, যা ইস্যু করা দরকার তবে একটি এক্সিকিউশন ইউনিটের দরকার নেই)। অপসৃত উফগণ অন্য উপায়ে গণনা ভারসাম্যহীন হবে।
branch-misses
1 জি থেকে নিচে 400 ডলারে নেমেছে, তাই আনআرولিং কাজ করেছে। resource_stalls.any
এখন তাৎপর্যপূর্ণ, যার অর্থ ফ্রন্ট-এন্ডটি আর কোনও বাধা নয়: পরিবর্তে পিছনের দিকের অংশটি পিছন দিকে চলে আসছে এবং সামনের প্রান্তটি সীমাবদ্ধ করে চলেছে। idq_uops_not_delivered.core
শুধুমাত্র চক্র যেখানে ফ্রন্ট-এন্ড uops উদ্ধার করা হয়নি বড়, মোট ছাত্র, কিন্তু ব্যাক এন্ড হয়নি স্থগিত। এটি দুর্দান্ত এবং নিম্ন, কয়েকটি ফ্রন্ট-এন্ড বাধাগুলি নির্দেশ করে।
মজাদার ঘটনা: পাইথন সংস্করণটি যোগ করার চেয়ে 10 দ্বারা ভাগ করে অর্ধেকের বেশি সময় ব্যয় করে। (প্রতিস্থাপন a/=10
সঙ্গে a>>=64
2 এর উত্পাদকের চেয়ে বেশি এটা গতি আপ, কিন্তু বাইনারি ছাঁটাই ফলাফলের পরিবর্তন! = দশমিক ছাঁটাই।)
আমার asm সংস্করণ অবশ্যই এই সমস্যা আকারের জন্য বিশেষত অনুকূলিত হয়েছে, লুপের পুনরাবৃত্তি-গণনাগুলি হার্ড কোডিং সহ। এমনকি একটি স্বেচ্ছাসেবী-নির্ভুলতা নম্বর স্থানান্তর করা এটিকে অনুলিপি করবে তবে আমার সংস্করণটি কেবলমাত্র এড়াতে পরবর্তী দুটি পুনরাবৃত্তির জন্য একটি অফসেট থেকে পড়তে পারে।
আমি পাইথন সংস্করণটি পোস্ট করেছি (আর্চ লিনাক্সে -৪-বিট পাইথন ২..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
(পেরেনস) নাম্বারগুলি হ'ল পারফ কাউন্টারটি কত সময় স্যাম্পল করা হচ্ছিল। এইচডাব্লু সমর্থনের চেয়ে আরও কাউন্টারে দেখার সময় পার্ফ বিভিন্ন কাউন্টার এবং এক্সট্রাপোলেটগুলির মধ্যে ঘোরে। একই টাস্কটির দীর্ঘ সময় ধরে এটি পুরোপুরি ঠিক।
আমি যদি perf
সিস্টেস্টেল kernel.perf_event_paranoid = 0
(বা perf
মূল হিসাবে চলমান ) সেট করার পরে দৌড়ে যাই তবে এটি পরিমাপ করবে 4.400GHz
। cycles:u
বাধা (বা সিস্টেম কল) এ ব্যয় করা সময় গণনা করে না, কেবল ব্যবহারকারী-স্থানচক্র। আমার ডেস্কটপটি প্রায় সম্পূর্ণ নিষ্ক্রিয় ছিল, তবে এটি সাধারণ।
Your program must be fast enough for you to run it and verify its correctness.
স্মৃতি কি?