assembly - কেন সিস্টেম ভি/এএমডি 64 এবিআই 16 বাইট স্ট্যাক সারিবদ্ধকরণ জারি করে?



x86-64 abi (1)

নোট করুন যে লিনাক্সে ব্যবহৃত i386 সিস্টেম ভি এবিআইয়ের বর্তমান সংস্করণটির জন্য 16-বাইট স্ট্যাক সারিবদ্ধকরণ 1 প্রয়োজন । কিছু ইতিহাসের জন্য https://sourceforge.net/p/fbc/bugs/659/ দেখুন।

এসএসই 2 এক্স x86-64 এর জন্য বেসলাইন , এবং __m128 মতো ধরণের জন্য এবং এবং সংকলক স্বয়ং-ভেক্টরাইজেশনের জন্য ABI কে দক্ষ করে __m128 , এটি আমার মনে হয় of এবিআইকে কীভাবে ফাংশন আরগস হিসাবে বা রেফারেন্সের সাহায্যে পাস হয় তা নির্ধারণ করতে হয়।

16-বাইট প্রান্তিককরণ কখনও কখনও স্ট্যাকের স্থানীয় ভেরিয়েবলগুলির জন্য দরকারী (বিশেষত অ্যারেগুলি) এবং 16-বাইট প্রান্তিককরণের গ্যারান্টি দেওয়ার অর্থ সংকলকরা যখনই এটি কার্যকর হয় তা বিনামূল্যে পেতে পারে, এমনকি যদি উত্সটি স্পষ্টভাবে অনুরোধ না করে doesn't

যদি 16-বাইট সীমানার সাথে সম্পর্কিত স্ট্যাক সারিবদ্ধতাটি জানা না থাকে, তবে প্রতিটি কাজ যা একটি সারিবদ্ধ স্থানীয় চায় তার একটি and rsp, -16 , এবং rsp অজানা অফসেটের পরে rsp সংরক্ষণ / পুনরুদ্ধার করার জন্য অতিরিক্ত নির্দেশাবলীর প্রয়োজন ছিল (হয় 0 বা -8 )। যেমন একটি ফ্রেম পয়েন্টার জন্য rbp ব্যবহার।

এভিএক্স ব্যতীত, মেমরি উত্স অপারেশনগুলি 16-বাইট সারিবদ্ধ হতে হবে। উদাহরণস্বরূপ paddd xmm0, [rsp+rdi] ত্রুটিগুলি যদি মেমরি paddd xmm0, [rsp+rdi] হয়। সুতরাং যদি অ্যালাইনমেন্টটি জানা যায় না, তবে আপনাকে movups xmm1, [rsp+rdi] করতে movups xmm1, [rsp+rdi] / paddd xmm0, xmm1 ব্যবহার করতে হবে বা একটি লুপ paddd xmm0, xmm1 / paddd xmm0, xmm1 লিখতে হবে। স্থানীয় অ্যারেগুলির জন্য যে সংকলকটি স্বয়ংক্রিয়ভাবে ভেক্টরাইজ করতে চায়, এটি কেবল তাদের 16 দ্বারা প্রান্তিককরণ চয়ন করতে পারে।

আরও মনে রাখবেন যে প্রথম দিকে x86 সিপিইউতে (নেহালেম / বুলডোজারের আগে) পয়েন্টারটি movaps পরেও movaps চেয়ে ধীরগতির নির্দেশনা ছিল যা movups চেয়ে ধীর। (যেমন সারিবদ্ধ ডেটাতে আন-স্বাক্ষরযুক্ত লোড / স্টোরগুলি অতিরিক্ত ধীর গতির পাশাপাশি ALU নির্দেশিকায় ভাঁজ লোডগুলি প্রতিরোধ করা)। (উপরের সমস্ত বিষয়ে আরও জানতে অ্যাগনার ফগের অপ্টিমাইজেশন গাইড, মাইক্রোয়ার্ক গাইড এবং নির্দেশাবলী সারণী দেখুন))

এই কারণগুলি হ'ল গ্যারান্টিটি কেবল "সাধারণত" স্ট্যাকটি সারিবদ্ধ রাখার চেয়ে বেশি কার্যকর। ভুল সংকেতযুক্ত স্ট্যাকের ক্ষেত্রে আসলে ত্রুটিযুক্ত কোড তৈরি করার অনুমতি দেওয়া আরও অনুকূলিতকরণের সুযোগ দেয়।

সারিবদ্ধ অ্যারেগুলি ভেক্টরাইজড memcpy / strcmp / যে কোনও ফাংশন যা প্রান্তিককরণ অনুমান করতে পারে না, তার পরিবর্তে এটি পরীক্ষা করে সরাসরি তাদের পুরো ভেক্টর লুপগুলিতে ঝাঁপিয়ে পড়তে পারে।

X86-64 সিস্টেম ভি এবিআই (r252) এর সাম্প্রতিক সংস্করণ থেকে:

একটি অ্যারে তার উপাদানগুলির মতো একই প্রান্তিককরণ ব্যবহার করে, স্থানীয় বা বৈশ্বিক অ্যারে ভেরিয়েবলের দৈর্ঘ্য কমপক্ষে 16 বাইট বা সি 99 ভেরিয়েবল-দৈর্ঘ্যের অ্যারে ভেরিয়েবলের সর্বদা কমপক্ষে 16 বাইটের প্রান্তিককরণ থাকে। 4

অ্যারেটিতে কাজ করার সময় প্রান্তিককরণের প্রয়োজনীয়তা এসএসই নির্দেশিকাগুলি ব্যবহারের অনুমতি দেয়। সংকলকটি সাধারণত ভেরিয়েবল-দৈর্ঘ্যের অ্যারে (ভিএলএ) এর আকার গণনা করতে পারে না, তবে এটি বেশিরভাগ ভিএলএর কমপক্ষে 16 বাইটের প্রয়োজন হবে বলে আশা করা যায়, তাই ভিএলএদের কমপক্ষে একটি 16 বাইট প্রান্তিককরণ থাকা আবশ্যকীয় যুক্তিসঙ্গত।

এটি কিছুটা আক্রমণাত্মক, এবং বেশিরভাগ ক্ষেত্রে কেবল তখনই সহায়তা করে যখন অটো-ভেক্টরাইজ করা ফাংশনগুলি ইনলাইন করা যায় তবে সাধারণত সেখানে অন্য স্থানীয় লোকেরা থাকে যে সংকলকটি কোনও ফাঁকে ফাঁকে ফাঁকে ফাঁকা রাখতে পারে যাতে এটি স্ট্যাকের স্থান নষ্ট না করে। পরিচিত স্ট্যাক সারিবন্ধন না হওয়া পর্যন্ত নির্দেশাবলী নষ্ট করবেন না। (স্পষ্টতঃ এবিআই ডিজাইনাররা যদি 16-বাইট স্ট্যাক সারিবদ্ধকরণের প্রয়োজন না পড়ার সিদ্ধান্ত নিয়ে থাকে তবে এটি ছেড়ে দিতে পারত))

__m128 স্পিল / পুনরায় লোড

অবশ্যই এটি alignas(16) char buf[1024]; করতে মুক্ত করে তোলে alignas(16) char buf[1024]; বা অন্যান্য ক্ষেত্রে যেখানে উত্স 16-বাইট প্রান্তিককরণের অনুরোধ করে

এবং এছাড়াও __m128 / __m128d / __m128i স্থানীয় রয়েছে। সংকলক সমস্ত ভেক্টর স্থানীয়দের রেজিস্টারগুলিতে রাখতে সক্ষম হতে পারে না (যেমন কোনও ফাংশন কল জুড়ে ছড়িয়ে পড়ে, বা পর্যাপ্ত রেজিস্টার না), সুতরাং movaps সাহায্যে স্পিল / পুনরায় লোড করতে সক্ষম হতে হবে, বা ALU নির্দেশাবলীর জন্য একটি মেমরি উত্স অপারেন্ড হিসাবে , দক্ষতার কারণে উপরে আলোচনা করা হয়েছে।

লোড / স্টোরগুলি যা প্রকৃতপক্ষে ক্যাশে-লাইন সীমানা (by৪ বাইট) জুড়ে বিভক্ত হয় তা উল্লেখযোগ্যভাবে বিলম্বিত শাস্তি এবং আধুনিক সিপিইউগুলিতে ছোটখাটো থ্রুপুট জরিমানাও রয়েছে। লোডের জন্য 2 পৃথক ক্যাশে লাইনগুলি থেকে ডেটা প্রয়োজন, সুতরাং এটি ক্যাশে দুটি অ্যাক্সেস নেয়। (এবং সম্ভাব্যভাবে 2 টি ক্যাশে মিস হয় তবে স্ট্যাক মেমোরির ক্ষেত্রে এটি বিরল)।

আমি মনে করি movups পুরানো সিপিইউগুলিতে movups ইতিমধ্যে সেই movups বেকড ছিল যেখানে এটি ব্যয়বহুল, তবে এটি এখনও সফল হয় না। একটি 4 কে পৃষ্ঠার সীমানা বিস্তৃত করা আরও খারাপ (স্কাইলেকের আগে সিপিইউতে), লোড বা স্টোরটি 4 কে বাউন্ডারের উভয় পাশে বাইট স্পর্শ করলে ~ 100 চক্র গ্রহণ করে। (এছাড়াও 2 টিএলবি চেক প্রয়োজন)। প্রাকৃতিক সারিবদ্ধকরণ যেকোন বৃহত্তর সীমানা জুড়ে বিভক্তকে অসম্ভব করে তোলে , তাই এসএসই 2 দিয়ে আপনি যা করতে পারেন তার জন্য 16 বাইট প্রান্তিককরণ যথেষ্ট ছিল।

long double (10-বাইট / 80-বিট x87) এর কারণে x86-64 সিস্টেম ভি max_align_t 16-বাইট প্রান্তিককরণ রয়েছে। এটি কিছু অদ্ভুত কারণে 16 বাইটে প্যাডেড হিসাবে সংজ্ঞায়িত করা হয়েছে, 32-বিট কোডের বিপরীতে যেখানে sizeof(long double) == 10 । x87 10-বাইট লোড / স্টোরটি যাইহোক বেশ ধীর (যেমন 1 / তৃতীয়াংশ কোল 2 তে double বা ভাসমানের লোড আউটপুট, পি 4 তে 1/6, বা কে 8 তে 1/8 তম) তবে সম্ভবত ক্যাশে-লাইন এবং পৃষ্ঠা বিভাজন জরিমানা ছিল পুরানো সিপিইউগুলিতে এতটাই খারাপ যে তারা এটিকে সেভাবে সংজ্ঞায়িত করার সিদ্ধান্ত নিয়েছে। আমি মনে করি আধুনিক সিপিইউগুলিতে (সম্ভবত কোর 2) long double ডাবলের একটি অ্যারের উপরে লুপ করা 10 fld m80 10 ধরণের ধীরে ধীরে ধীরে ধীরে হবে না, কারণ fld m80 6.4 উপাদানগুলির ক্যাশে-লাইন বিভক্ত হওয়ার চেয়ে বড় বাধা হয়ে দাঁড়াবে।

আসলে, সিলিকন বেঞ্চমার্কে ( 2000 ডলার পিছনে ) উপলভ্য হওয়ার আগে এবিআইকে সংজ্ঞায়িত করা হয়েছিল, তবে সেই কে 8 নম্বরগুলি কে 7 (32-বিট / 64-বিট মোড এখানে অপ্রাসঙ্গিক) হিসাবে একই। এক্সএমএম রেজিস্টারগুলিতে আপনি এর সাথে কিছু করতে না movaps long double 16 বাইট তৈরি করা movaps একটি একক অনুলিপি করা সম্ভব করে তোলে। ( xorps / andps / orps সাহায্যে সাইন বিটটি andps orps )

সম্পর্কিত: এই max_align_t সংজ্ঞাটির অর্থ হল যে malloc সর্বদা x86-64 কোডে 16-বাইট প্রান্তিকৃত মেমরি দেয়। এটি আপনাকে _mm_load_ps মতো এসএসই প্রান্তিক লোডগুলির জন্য এটি ব্যবহার থেকে দূরে সরে যেতে দেয়, তবে এই জাতীয় কোডটি 32-বিটের জন্য সংকলিত হয়ে যেতে পারে যেখানে alignof(max_align_t) কেবল ৮. ( aligned_alloc বা যে aligned_alloc কিছু ব্যবহার করুন)।

অন্যান্য এবিআই বিষয়গুলির মধ্যে স্ট্যাকের __m128 মানগুলি অন্তর্ভুক্ত রয়েছে (xmm0-7 এর পরে প্রথম 8 ফ্লোট / ভেক্টর আরোগুলি রয়েছে)। স্মৃতিতে ভেক্টরগুলির জন্য 16-বাইট প্রান্তিককরণের প্রয়োজনটি বোধগম্য হয়, সুতরাং সেগুলি কলি দ্বারা দক্ষতার সাথে ব্যবহার করা যায় এবং কলার দ্বারা দক্ষতার সাথে সঞ্চয় করা যায়। সর্বদা 16-বাইট স্ট্যাক সারিবদ্ধকরণ বজায় রাখা এমন ফাংশনগুলির পক্ষে সহজ করে তোলে যেগুলি 16 দ্বারা কিছু আর্গ-পাসিং স্থান সারিবদ্ধ করতে হবে।

__m128 মতো __m128 যা এবিআই গ্যারান্টিতে 16 বাইট প্রান্তিককরণ রয়েছে । আপনি যদি কোনও স্থানীয়কে সংজ্ঞায়িত করেন এবং এর ঠিকানাটি নেন এবং সেই পয়েন্টারটি অন্য কোনও ফাংশনে পৌঁছে দেন তবে লোকালটি পর্যাপ্তভাবে প্রান্তিককরণের প্রয়োজন। সুতরাং 16 বাইট স্ট্যাক প্রান্তিককরণ বজায় রাখা কিছু ধরণের 16-বাইট প্রান্তিককরণ দেওয়ার সাথে এক সাথে চলে যায়, যা সম্ভবত একটি ভাল ধারণা।

আজকাল, এটি দুর্দান্ত যে atomic<struct_of_16_bytes> 16-বাইট প্রান্তিককরণ পেতে পারে, তাই lock cmpxchg16b কখনও ক্যাশে লাইনের সীমানা অতিক্রম করে না। সত্যই বিরল ক্ষেত্রে যেখানে আপনার স্বয়ংক্রিয় স্টোরেজ সহ একটি পারমাণবিক স্থানীয় রয়েছে এবং আপনি এটিতে পয়েন্টার একাধিক থ্রেডে পৌঁছে দিচ্ছেন ...

পাদটীকা 1: 32-বিট লিনাক্স

সমস্ত 32-বিট প্ল্যাটফর্মগুলি বিদ্যমান বাইনারিগুলির সাথে সামঞ্জস্যতা এবং লিনাক্সের মতো হাতে লিখিত asm ভাঙ্গেনি; i386 মত কিছু নেটবিএসডি এখনও i386 এসআইএসবি এবিআইয়ের মূল সংস্করণ থেকে historicalতিহাসিক 4-বাইট স্ট্যাক প্রান্তিককরণের প্রয়োজনীয়তাটি ব্যবহার করে।

আধুনিক সিপিইউগুলিতে দক্ষ 8-বাইট ডাবলের জন্য historicalতিহাসিক 4-বাইট স্ট্যাক সারিবদ্ধতাও অপর্যাপ্ত ছিল। fld / fstp সাধারণত ক্যাশে-লাইন সীমানা (অন্যান্য লোড / fstp মতো) অতিক্রম করার পরে সাধারণত দক্ষ হয়, সুতরাং এটি ভয়াবহ নয়, তবে প্রাকৃতিকভাবে সাজানো সুন্দর is

16-বাইট প্রান্তিককরণটি আনুষ্ঠানিকভাবে -mpreferred-stack-boundary=4 অংশ হওয়ার আগেই, জিসিসি 32-বিটে -mpreferred-stack-boundary=4 (2 ^ 4 = 16-বাইট) সক্ষম -mpreferred-stack-boundary=4 । এটি বর্তমানে ধরে নিচ্ছে যে ইনকামিং স্ট্যাক অ্যালাইনমেন্টটি 16 বাইট (এমনকি এমন ক্ষেত্রে এমনকি যদি এটি দোষী হবে না), পাশাপাশি সেই সারিবদ্ধকরণ সংরক্ষণ করে। আমি নিশ্চিত নই যে historical alignas(16) এসএসই কোড-জেন বা alignas(16) অবজেক্টের নির্ভুলতার জন্য নির্ভর করে স্ট্যাক অ্যালাইনমেন্ট সংরক্ষণের চেষ্টা করেছিল alignas(16)

ffmpeg হ'ল একটি সুপরিচিত উদাহরণ যা স্ট্যাক সারিবদ্ধকরণ দেওয়ার জন্য সংকলকটির উপর নির্ভর করে: "স্ট্যাক অ্যালাইনমেন্ট" কী? যেমন, 32-বিট উইন্ডোজ।

আধুনিক জিসিসি এখনও স্ট্যাকটি ১ by দ্বারা প্রান্তিককরণের জন্য মূলের শীর্ষে কোড নির্ধারণ করে (এমনকি লিনাক্সে যেখানে এবিআই গ্যারান্টি দেয় যে কার্নেলটি প্রান্তিক স্ট্যাকের সাহায্যে প্রক্রিয়া শুরু করে) তবে অন্য কোনও ফাংশনের শীর্ষে নয়। কোড তৈরি করার সময় স্ট্যাকটি কীভাবে -mincoming-stack-boundary করা উচিত তা বলার জন্য আপনি -mincoming-stack-boundary কে বলার জন্য -mincoming-stack-boundary ব্যবহার করতে পারেন।

প্রাচীন জিসিসি __attribute__((aligned(16))) অটোমেটিক স্টোরেজের জন্য __attribute__((aligned(16))) বা 32 প্রতি সত্যিই শ্রদ্ধা বলে মনে হচ্ছে না, যেমন এটি গডবোল্টের উপর এই উদাহরণে স্ট্যাকটিকে কোনও অতিরিক্ত বাড়িয়ে তোলা বিরক্ত করে না, সুতরাং পুরাতন জিসিসি এক ধরণের রয়েছে সারিবদ্ধকরণ স্ট্যাক করার সময় চেকার্ড অতীত। আমি মনে করি সরকারী লিনাক্স এবিআই-এর 16-বাইট প্রান্তিককরণের পরিবর্তনটি একটি সু-পরিকল্পিত পরিবর্তন নয়, প্রথমে একটি ডি-ফ্যাক্টো পরিবর্তন হিসাবে ঘটেছে। পরিবর্তনটি কখন ঘটেছিল সে বিষয়ে আমি কোনও অফিসিয়াল আপ করি নি, তবে ২০০ somewhere থেকে ২০১০ সালের মধ্যে কোথাও আমার ধারণা, x86-64 জনপ্রিয় হওয়ার পরে এবং x86-64 সিস্টেম ভি এবিআইয়ের 16-বাইট স্ট্যাক সারিবদ্ধকরণ কার্যকর প্রমাণিত হয়েছে।

প্রথমে এটি ছিল জিসিসির কোড-জেনে এবিআইর প্রয়োজনীয় সংখ্যার চেয়ে বেশি প্রান্তিককরণ ব্যবহার করা (যেমন, জিসিসি-সংকলিত কোডের জন্য কঠোর এবিআই ব্যবহার করা), তবে পরে এটি https রক্ষণযোগ্য i386 সিস্টেম ভি এবিআইয়ের সংস্করণে লেখা হয়েছিল : //github.com/hjl-tools/x86-psABI/wiki/X86-psABI (যা কমপক্ষে লিনাক্সের জন্য অফিসিয়াল)।

@ মিশেলপ্যাচ এবং @ থমাস জাজার রিপোর্ট করেছেন যে জিসিসি 4.5 প্রথম সংস্করণ হতে পারে -mpreferred-stack-boundary=4 -বিটের পাশাপাশি 64-বিটের জন্য -mpreferred-stack-boundary=4 gcc4.1.2 এবং gcc4.4.7 গডবোল্টে সেভাবে আচরণ করে বলে মনে হচ্ছে, তাই সম্ভবত পরিবর্তনটি ব্যাকপোর্ট করা হয়েছে, বা ম্যাট গডবোল্ট আরও আধুনিক কনফিগারেশনে পুরানো জিসিসি কনফিগার করেছেন।

আমি বিভিন্ন জায়গায় পড়েছি যে এটি "পারফরম্যান্সের কারণে" সম্পন্ন হয়েছে তবে আমি এখনও অবাক হয়েছি যে এই বিশেষ ক্ষেত্রে কী ঘটে যেখানে এই 16-বাইট প্রান্তিককরণের দ্বারা কর্মক্ষমতা উন্নত হয়। অথবা, যে কোনও ক্ষেত্রে এটি কেন বেছে নেওয়া হয়েছিল তার কারণগুলি।

সম্পাদনা : আমি ভাবছি আমি বিভ্রান্তিমূলকভাবে প্রশ্নটি লিখেছি। প্রসেসর কেন 16-বাইট প্রান্তিকৃত মেমরির সাহায্যে দ্রুত কাজ করে সে সম্পর্কে আমি জিজ্ঞাসা করছিলাম না, এটি ডক্সে সর্বত্র ব্যাখ্যা করা হয়েছে। পরিবর্তে আমি যা জানতে চেয়েছিলাম, তা হল কীভাবে প্রয়োগকৃত 16-বাইট প্রান্তিককরণ কেবল প্রয়োজনের সময় প্রোগ্রামারদের স্ট্যাকটি নিজেরাই সাজিয়ে রাখার চেয়ে আরও ভাল। আমি এটি জিজ্ঞাসা করছি কারণ সমাবেশ সম্পর্কিত আমার অভিজ্ঞতা থেকে, স্ট্যাক প্রয়োগকারী দুটি সমস্যা রয়েছে: এটি কার্যকর করা কোডের কম 1% শতাংশ দ্বারা কার্যকর হয় (সুতরাং অন্যান্য 99% আসলে ওভারহেড হয়); এবং এটি বাগগুলির একটি খুব সাধারণ উত্স। সুতরাং আমি অবাক হই যে এটি কীভাবে শেষ পর্যন্ত পরিশোধ করে। যদিও আমি এখনও এ সম্পর্কে সন্দেহের মধ্যে রয়েছি, আমি পিটারের উত্তরটি গ্রহণ করছি কারণ এটিতে আমার মূল প্রশ্নের সর্বাধিক বিস্তারিত উত্তর রয়েছে।





abi