x86 বিধানসভা, 9 বাইট (প্রতিযোগিতার প্রবেশের জন্য)
যারা উচ্চ-স্তরের ভাষাগুলিতে এই চ্যালেঞ্জটির চেষ্টা করছেন তারা কাঁচা বিটগুলিতে হেরফেরের আসল মজাদার অনুপস্থিত । এটি করার উপায়গুলিতে অনেকগুলি সূক্ষ্ম প্রকরণ রয়েছে, এটি উন্মাদ — এবং ভাবতে অনেক মজাদার। এখানে কয়েকটি সমাধান রয়েছে যা আমি 32-বিট x86 সমাবেশের ভাষায় তৈরি করেছি।
আমি আগে থেকে ক্ষমা চেয়ে নিচ্ছি যে এটি সাধারণ কোড-গল্ফ উত্তর নয়। আমি পুনরাবৃত্তিমূলক অপ্টিমাইজেশনের চিন্তার প্রক্রিয়া (আকারের জন্য) সম্পর্কে প্রচুর ঝাঁকুনি দিতে যাচ্ছি। আশা করি এটি বৃহত্তর দর্শকদের কাছে আকর্ষণীয় এবং শিক্ষামূলক তবে আপনি যদি টিএল; ডিআর টাইপ হন তবে আপনি যদি শেষ দিকে না যান তবে আমি বিরক্ত হব না।
সুস্পষ্ট এবং দক্ষ সমাধান হ'ল মানটি বিজোড় বা এমনকি (যা সর্বনিম্ন-তাত্পর্যপূর্ণ বিটটি দেখে দক্ষতার সাথে করা যেতে পারে) তা পরীক্ষা করা এবং তারপরে n + 1 বা n − 1 এর মধ্যে নির্বাচন করুন । ধরে নিচ্ছি যে ইনপুটটি ECX
নিবন্ধের পরামিতি হিসাবে পাস হয়েছে এবং ফলাফলটি EAX
রেজিস্টারে ফিরে আসে , আমরা নিম্নলিখিত ফাংশনটি পাই:
F6 C1 01 | test cl, 1 ; test last bit to see if odd or even
8D 41 01 | lea eax, DWORD PTR [ecx + 1] ; set EAX to n+1 (without clobbering flags)
8D 49 FF | lea ecx, DWORD PTR [ecx - 1] ; set ECX to n-1 (without clobbering flags)
0F 44 C1 | cmovz eax, ecx ; move in different result if input was even
C3 | ret
(১৩ বাইট)
তবে কোড-গল্ফের উদ্দেশ্যে, সেই LEA
নির্দেশাবলী দুর্দান্ত নয়, যেহেতু তারা এনকোড করতে 3 বাইট নেয়। এর একটি সাধারণ DEC
পুনর্নির্মাণগুলি ECX
আরও সংক্ষিপ্ত হবে (কেবলমাত্র একটি বাইট), তবে এটি পতাকাগুলি প্রভাবিত করে, তাই আমরা কোডটি কীভাবে সাজিয়ে তুলি সে সম্পর্কে আমাদের কিছুটা চালাক হতে হবে। আমরা প্রথমে হ্রাস এবং বিজোড় / এমনকি দ্বিতীয়টি পরীক্ষা করতে পারি , তবে তারপরে আমাদের বিজোড় / এমনকি পরীক্ষার ফলাফলটি উল্টাতে হবে।
এছাড়াও, আমরা শর্তাধীন শর্তের নির্দেশকে একটি শাখায় পরিবর্তন করতে পারি, যা কোডটি আরও ধীরে ধীরে চালিত করতে পারে (শাখাটি কতটা অনুমানযোগ্য তার উপর নির্ভর করে — যদি ইনপুটটি বিজোড় এবং এমনকি এর মধ্যে বিযুক্ত হয়ে থাকে, তবে একটি শাখা ধীরে ধীরে হবে; প্যাটার্ন, এটি দ্রুত হবে), যা আমাদের অন্য একটি বাইট সংরক্ষণ করবে।
প্রকৃতপক্ষে, এই সংশোধনীর মাধ্যমে, কেবলমাত্র একটি একক নিবন্ধক ব্যবহার করে পুরো অপারেশনটি জায়গায় জায়গায় করা যেতে পারে। আপনি যদি এই কোডটি কোথাও সন্নিবেশ করিয়ে থাকেন তবে এটি দুর্দান্ত (এবং সম্ভাবনাগুলি হ'ল, আপনি হবেন, যেহেতু এটি খুব ছোট)।
48 | dec eax ; decrement first
A8 01 | test al, 1 ; test last bit to see if odd or even
75 02 | jnz InputWasEven ; (decrement means test result is inverted)
40 | inc eax ; undo the decrement...
40 | inc eax ; ...and add 1
InputWasEven: ; (two 1-byte INCs are shorter than one 3-byte ADD with 2)
(ইনলাইনড: 7 বাইট; ফাংশন হিসাবে: 10 বাইট)
তবে আপনি যদি এটি একটি ফাংশন করতে চান না? কোনও স্ট্যান্ডার্ড কলিং কনভেনশন ফেরত মূল্য হিসাবে প্যারামিটারগুলি পাস করার জন্য একই রেজিস্টার ব্যবহার করে না, সুতরাং আপনাকে MOV
ফাংশনের শুরু বা শেষের জন্য নিবন্ধক-নিবন্ধকের নির্দেশ যুক্ত করতে হবে। এটির গতিতে কার্যত কোনও দাম নেই, তবে এটি 2 বাইট যুক্ত করে। (এই RET
নির্দেশিকাটি আরও একটি বাইট যুক্ত করে, এবং একটি ফাংশন কল থেকে ফিরে আসার প্রয়োজনে কিছুটা ওভারহেড চালু হয়েছিল, যার অর্থ এটি একটি উদাহরণ যেখানে ইনলাইনিং কেবল একটি ক্লাসিক গতির চেয়ে বরং গতি এবং আকার উভয়ই উপকার করে - স্পেস ট্রেডঅফের জন্য) সব মিলিয়ে, একটি ফাংশন হিসাবে লিখিত, এই কোডটি 10 বাইটে ফুলে যায়।
10 বাইটে আমরা আর কী করতে পারি? আমরা যদি পারফরম্যান্স সম্পর্কে কিছুটা যত্ন নিই (অন্তত, অনুমানযোগ্য পারফরম্যান্স), তবে এই শাখাটি থেকে মুক্তি পেয়ে ভাল লাগবে। এখানে একটি শাখাবিহীন, বিট-টুইডলিং সমাধান যা বাইটগুলির মধ্যে একই আকার। মৌলিক ভিত্তিটি সহজ: আমরা শেষ বিটটি ফ্লিপ করতে একটি বিটওয়্যার এক্সওআর ব্যবহার করি, একটি বিজোড় মানকে একটিতে রূপান্তর করি এবং তদ্বিপরীত। তবে একটি নিগল রয়েছে od বিজোড় ইনপুটগুলির জন্য, যা আমাদের এন -1 দেয় , এমনকি ইনপুটগুলির জন্যও এটি আমাদের এন + 1 দেয় - আমরা যা চাই তার সম্পূর্ণ বিপরীতে। সুতরাং, এটি ঠিক করতে, আমরা কার্যকরভাবে সাইনটি উল্টিয়ে একটি নেতিবাচক মানের উপর অপারেশন সঞ্চালন করি।
8B C1 | mov eax, ecx ; copy parameter (ECX) to return register (EAX)
|
F7 D8 | neg eax ; two's-complement negation
83 F0 01 | xor eax, 1 ; XOR last bit to invert odd/even
F7 D8 | neg eax ; two's-complement negation
|
C3 | ret ; return from function
(ইনলাইনড: 7 বাইট; ফাংশন হিসাবে: 10 বাইট)
বেশ স্লিট; এটি কীভাবে উন্নত করা যায় তা দেখা মুশকিল। একটা জিনিস আমার নজর কেড়েছে, যদিও: এই দুটি 2 বাইট NEG
নির্দেশাবলী। সত্য কথা বলতে গেলে, দুটি বাইট মনে হয় একটি সাধারণ বেনিফিটকে এনকোড করার জন্য অনেকগুলি বাইটের মতো, তবে এটি আমাদের সাথে কাজ করতে হবে এমন নির্দেশিকা সেট। কোন workaround আছে? নিশ্চিত! যদি আমরা XOR
-2-এর দ্বারা, আমরা দ্বিতীয় tionিশনকে NEG
একটি INC
পুনঃস্থাপনের সাথে প্রতিস্থাপন করতে পারি:
8B C1 | mov eax, ecx
|
F7 D8 | neg eax
83 F0 FE | xor eax, -2
40 | inc eax
|
C3 | ret
(ইনলাইনড: 6 বাইট; ফাংশন হিসাবে: 9 বাইট)
এক্স 86 টি নির্দেশের সেটগুলির আরও একটি অদ্ভুততা হ'ল বহুমুখী LEA
নির্দেশ , যা একটি নিবন্ধ-নিবন্ধক পদক্ষেপ, একটি রেজিস্টার-রেজিস্টার সংযোজন, একটি ধ্রুবক দ্বারা অফসেট করে এবং সমস্ত একক নির্দেশায় স্কেলিং করতে পারে!
8B C1 | mov eax, ecx
83 E0 01 | and eax, 1 ; set EAX to 1 if even, or 0 if odd
8D 44 41 FF | lea eax, DWORD PTR [ecx + eax*2 - 1]
C3 | ret
(10 বাইট)
AND
নির্দেশ মত হল TEST
, নির্দেশ আমরা পূর্বে ব্যবহৃত উভয় যে অনুযায়ী একটি bitwise-এবং এবং সেট পতাকা না, কিন্তু AND
আসলে গন্তব্য আপডেট অপার্যান্ড। LEA
নির্দেশ তারপর 2 দ্বারা এই দাঁড়িপাল্লা, 1. করে মূল ইনপুট মান, এবং decrements যোগ তাহলে ইনপুট মান বিজোড় ছিল, subtracts 1 - তা থেকে (2 × 0 থেকে 1 = -1); যদি ইনপুট মানটি সমান হয় তবে এটি এতে 1 (2 × 1 - 1 = 1) যোগ করে।
কোডটি লেখার এটি একটি খুব দ্রুত এবং কার্যকর উপায়, যেহেতু কার্যকরকরণের বেশিরভাগ অংশ ফ্রন্ট-এন্ডে করা যেতে পারে তবে এটি কোনও বাইটের পথে আমাদের বেশি কিছু কিনে না, কারণ কোনও জটিলকে এনকোড করতে অনেকগুলি লাগে LEA
নির্দেশ. এই সংস্করণটি ইনলাইনিংয়ের উদ্দেশ্যেও কাজ করে না, কারণ এটির জন্য প্রয়োজনীয় ইনপুট মানটি LEA
নির্দেশের একটি ইনপুট হিসাবে সংরক্ষণ করা উচিত । সুতরাং এই সর্বশেষ অপ্টিমাইজেশান প্রয়াসের সাহায্যে আমরা আসলে পিছনের দিকে চলে গিয়েছি, সম্ভবত এটি থামার সময় হবে।
সুতরাং, চূড়ান্ত প্রতিযোগিতামূলক প্রবেশের জন্য, আমাদের কাছে একটি 9-বাইট ফাংশন রয়েছে যা নিবন্ধরে ইনপুট মান ECX
( 32-বিট x86-তে একটি আধা-মানের রেজিস্টার-ভিত্তিক কলিং কনভেনশন ) গ্রহণ করে এবং EAX
রেজিস্টারে ফলাফল ফেরত দেয় (যেমনটি সমস্ত x86 কলিং কনভেনশন):
SwapParity PROC
8B C1 mov eax, ecx
F7 D8 neg eax
83 F0 FE xor eax, -2
40 inc eax
C3 ret
SwapParity ENDP
এমএএসএমের সাথে একত্রিত হতে প্রস্তুত; সি থেকে কল করুন:
extern int __fastcall SwapParity(int value); // MSVC
extern int __attribute__((fastcall)) SwapParity(int value); // GNU