সংকলকগণ কেন এখানে কলি-সেভ করা রেজিস্ট্রার ব্যবহারের জন্য জোর দিয়ে থাকেন?


10

এই সি কোড বিবেচনা করুন:

void foo(void);

long bar(long x) {
    foo();
    return x;
}

আমি যখন এটি জিসিসি 9.3 এ সংকলন করি -O3বা হয় সাথে -Os, আমি এটি পাই:

bar:
        push    r12
        mov     r12, rdi
        call    foo
        mov     rax, r12
        pop     r12
        ret

কলং থেকে আউটপুট কল -সেভ করা রেজিস্টার হিসাবে rbxপরিবর্তে বাছাই করা ছাড়া অভিন্ন r12

যাইহোক, আমি এমন সমাবেশটি দেখতে দেখতে চাই / প্রত্যাশা করি যা দেখতে আরও ভাল লাগে:

bar:
        push    rdi
        call    foo
        pop     rax
        ret

ইংরাজীতে, আমি যা ঘটতে দেখছি তা এখানে:

  • ক্যালি-সেভ করা নিবন্ধের পুরাতন মানটিকে স্ট্যাকে পুশ করুন
  • xকলি-সেভ করা রেজিস্ট্রারে যান Move
  • কল foo
  • xকলি-সংরক্ষিত রেজিস্টার থেকে রিটার্ন-ভ্যালু রেজিস্টারে সরান
  • কলি-সংরক্ষিত রেজিস্ট্রারের পুরাতন মানটি পুনরুদ্ধার করতে স্ট্যাকটি পপ করুন

কেন একেবারেই ক্যালি-সেভ করা রেজিস্ট্রার নিয়ে গোলমাল করতে বিরক্ত করবেন? পরিবর্তে এটি কেন করবেন না? এটি সংক্ষিপ্ত, সহজ এবং সম্ভবত দ্রুত বলে মনে হচ্ছে:

  • xস্ট্যাকের দিকে ধাক্কা
  • কল foo
  • xস্ট্যাক থেকে রিটার্ন-ভ্যালু রেজিস্টারে পপ করুন

আমার সমাবেশ কি ভুল? অতিরিক্ত রেজিস্টার নিয়ে গোলযোগের চেয়ে কী কোনওরকম দক্ষতা কম? যদি এই দুজনেরই উত্তর "না" হয় তবে জিসিসি বা ঝাঁকুনি এইভাবে কেন করেন না?

গডবোল্ট লিঙ্ক


সম্পাদনা করুন: এখানে ভেরিয়েবলটি অর্থবহভাবে ব্যবহার করা হলেও তা ঘটেছিল তা দেখানোর জন্য এখানে একটি তুচ্ছ উদাহরণ রয়েছে:

long foo(long);

long bar(long x) {
    return foo(x * x) - x;
}

বুঝতে পেরেছি:

bar:
        push    rbx
        mov     rbx, rdi
        imul    rdi, rdi
        call    foo
        sub     rax, rbx
        pop     rbx
        ret

আমি বরং এটি চাই:

bar:
        push    rdi
        imul    rdi, rdi
        call    foo
        pop     rdi
        sub     rax, rdi
        ret

এবার এটি বনাম দুটি কেবল একটি নির্দেশ বন্ধ, তবে মূল ধারণাটি একই।

গডবোল্ট লিঙ্ক


4
আকর্ষণীয় মিস অপটিমাইজেশন।
ফুজ

1
সম্ভবত অনুমান করা হয়েছে যে পাস করা প্যারামিটারটি ব্যবহার করা হবে যাতে আপনি একটি অস্থির রেজিস্টার সংরক্ষণ করতে চান এবং সেই প্যারামিটারের পরবর্তী অ্যাক্সেসগুলি নিবন্ধ থেকে দ্রুততর হওয়ার কারণে আপনি একটি অস্থির রেজিস্টার সংরক্ষণ করতে এবং একটি পাসপোর্ট প্যারামিটারটিকে একটি স্তরে রাখতে চান না। foo এক্স এক্স পাস এবং আপনি এটি দেখতে পাবেন। সুতরাং এটি সম্ভবত তাদের স্ট্যাক ফ্রেম সেটআপের একটি সাধারণ অংশ is
old_timer

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

আর্ম ব্যাকএন্ড এটি জিসিসিতেও করে। সম্ভবত ব্যাকএন্ডটি নয়
21_4

ঝাঁকুনি 10 একই গল্প (আর্ম ব্যাকএন্ড)।
old_timer

উত্তর:


5

টি এল: ডিআর:

  • সহজেই এই অপ্টিমাইজেশনটি সন্ধানের জন্য সংকলক অভ্যন্তরীণগুলি সম্ভবত সেট আপ করা হয় না এবং এটি সম্ভবত কেবলমাত্র ছোট ফাংশনগুলির জন্য দরকারী, কলগুলির মধ্যে বৃহত ফাংশনের অভ্যন্তরে নয়।
  • বড় ফাংশন তৈরি করতে সন্নিবেশ করা বেশিরভাগ সময় একটি ভাল সমাধান
  • fooআরবিএক্স সংরক্ষণ / পুনরুদ্ধার না হলে কিছুটা বিলম্ব বনাম থ্রুপুট ট্রেড অফ থাকতে পারে।

সংকলকগুলি জটিল যন্ত্রের টুকরো। তারা কোনও মানুষের মতো "স্মার্ট" নয় এবং প্রতিটি সম্ভাব্য অপটিমাইজেশন খুঁজে পেতে ব্যয়বহুল অ্যালগরিদমগুলি প্রায়শই অতিরিক্ত সংকলনের সময় ব্যয়যোগ্য নয়।

আমি এটি জিসিসি বাগ 69986 হিসাবে জানিয়েছি - 2016 সালে ফিরে স্পিল / পুনরায় লোড করতে পুশ / পপ ব্যবহার করে -ও এর সাথে আরও ছোট কোড সম্ভব ; জিসিসি ডেভস থেকে কোনও ক্রিয়াকলাপ বা উত্তর পাওয়া যায় নি। : /

সামান্যভাবে সম্পর্কিত: জিসিসি বাগ 70408 - একই কল-সংরক্ষিত রেজিস্টারটি পুনরায় ব্যবহার করা কিছু ক্ষেত্রে ছোট কোড দেবে - সংকলক দেবগণ আমাকে বলেছেন যে জিসিসি সেই অপটিমাইজেশনটি করতে সক্ষম হতে পারে কারণ এটি মূল্যায়নের ক্রমবর্ধমান ক্রম প্রয়োজন foo(int)লক্ষ্যটিকে কীভাবে সহজ করে তুলবে তার ভিত্তিতে দুটি কল।


যদি নিজেকে fooসংরক্ষণ / পুনরুদ্ধার না rbxকরে, তবে x-> রেভালাল নির্ভরতা শৃঙ্খলে একটি অতিরিক্ত স্টোর / পুনরায় লোড বিলম্বের মাধ্যমে থ্রুপুট (নির্দেশের গণনা) এর মধ্যে একটি বাণিজ্য রয়েছে ।

কম্পাইলার সাধারণত লেটেন্সি থ্রুপুট উপর যেমন পরিবর্তে 2x কর্মদিবসের ব্যবহার পক্ষপাতী, imul reg, reg, 10(3-চক্র লেটেন্সি, 1 / ঘড়ি থ্রুপুট), কারণ অধিকাংশ কোড গড় উল্লেখযোগ্যভাবে কম 4 uops / Skylake মত টিপিক্যাল 4 ব্যাপী পাইপলাইনগুলি উপর ঘড়ি। (আরও নির্দেশাবলী / উফগুলি আরওবিতে আরও বেশি জায়গা নেয়, একই আউট-অফ-অর্ডার উইন্ডোটি আরও কতটা দেখতে পারে তা হ্রাস করে এবং বাস্তবায়ন কার্যকরভাবে স্টলগুলি দিয়ে ফেটে যায় সম্ভবত 4-এরও কম ইউপগুলির জন্য অ্যাকাউন্টিং / ঘড়ির গড়।)

fooআরবিএক্সকে যদি ধাক্কা দেয় / পপ করে, তবে বিলম্বের জন্য বেশি কিছু পাওয়ার নেই। পুনরুদ্ধারের ঠিক ঠিক retপরিবর্তে হওয়ার আগেই retঘটানো সম্ভবত প্রাসঙ্গিক নয়, যদি না কোনও ভুল অনুমান বা আই-ক্যাশে মিস না করে যা ফেরতের ঠিকানায় কোড আনতে বিলম্ব করে।

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


সুতরাং হ্যাঁ push rdi/ এই ক্ষেত্রে pop raxআরও দক্ষ হবে , এবং সম্ভবত সম্ভবত ক্ষুদ্র নন-পাত ফাংশনগুলির জন্য একটি মিসড অপ্টিমাইজেশন যা নির্ভর করে কলারের সংরক্ষণ / পুনরুদ্ধার করার জন্য বনামের আরও নির্দেশাবলীর জন্য অতিরিক্ত স্টোর / পুনরায় লোডের বিলম্বের মধ্যে ভারসাম্য ।fooxrbx

স্ট্যাক-আনওয়াইন্ড মেটাডেটা এখানে আরএসপিতে পরিবর্তনের প্রতিনিধিত্ব করা সম্ভব, ঠিক যেমন এটি স্ট্যাক স্লটে sub rsp, 8স্পিল / পুনরায় লোড ব্যবহার করত x। (তবে সংকলকগণও এই অপ্টিমাইজেশনটি জানেন না, pushস্থান সংরক্ষণ এবং একটি ভেরিয়েবল আরম্ভ করার জন্য ব্যবহার করছেন C কোন সি / সি ++ সংকলক কেবল একবার এসএসপি বাড়ানোর পরিবর্তে স্থানীয় ভেরিয়েবলগুলি তৈরি করার জন্য পুশ পপ নির্দেশাবলী ব্যবহার করতে পারে? এবং এর চেয়ে আরও বেশি কিছু করার জন্য একটি স্থানীয় ভেরি বড় .eh_frameস্ট্যাক আনওয়াইন্ড মেটাডেটা বাড়ে কারণ আপনি স্ট্যাক পয়েন্টারকে প্রতিটি ধাক্কা দিয়ে আলাদাভাবে সরিয়ে নিয়ে যাচ্ছেন That এটি কল-সংরক্ষিত রেজিগুলি সংরক্ষণ / পুনরুদ্ধার করতে পুশ / পপ ব্যবহার করা থেকে বিরত রাখে না))


আইডি কে এই অপটিমাইজেশনটি সন্ধান করার জন্য কম্পাইলারদের শেখানো উপযুক্ত হবে

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

যদি আপনি প্রচুর কোডে (যেমন ফাংশনগুলির মধ্যে একক ফাংশন কলগুলির আশেপাশে) হতাশাবাদী অনুমান করা শুরু করেন, আপনি আরবিএক্স সংরক্ষণ করা / পুনরুদ্ধার করা হয়নি এমন আরও মামলা পেতে শুরু করেছেন এবং আপনি সুবিধা নিতে পারেন।

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

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

ক্ষুদ্র ফাংশনগুলি যেমন bar()তাদের কলকারীদের মধ্যে অন্তর্ভুক্ত করা ভাল। সংকলন করুন -fltoযাতে এটি বেশিরভাগ ক্ষেত্রে ফাইলের সীমানা জুড়েও ঘটতে পারে। (ফাংশন পয়েন্টার এবং ভাগ-লাইব্রেরি সীমানা এটি পরাস্ত করতে পারে।)


আমি মনে করি যে একটি কারণ সংকলকগণ এই অপ্টিমাইজেশানগুলি করার চেষ্টা করতে বিরক্ত করেননি তা হ'ল এটির জন্য সাধারণ স্ট্যাক বনাম রেজিস্টার-বরাদ্দ কোডের চেয়ে পৃথক সংস্থাগুলির বিভিন্ন কোডের পুরো গোছা প্রয়োজন হবে যা কল-সংরক্ষিত কীভাবে সংরক্ষণ করতে জানে knows নিবন্ধ এবং তাদের ব্যবহার।

অর্থাত্ এটি বাস্তবায়নের জন্য অনেক কাজ হবে এবং অনেকগুলি কোড বজায় রাখতে হবে এবং এটি করার বিষয়ে যদি এটি অত্যধিক উত্সাহী হয় তবে এটি আরও খারাপ কোড তৈরি করতে পারে ।

এবং এটিও (আশাকরি) তাৎপর্যপূর্ণ নয়; যদি এটি গুরুত্বপূর্ণ barহয় তবে আপনার এটির কলারের সাথে সন্নিবেশ fooকরাতে বা ইনলাইন করা উচিত bar। এটি ঠিক আছে যদি না প্রচুর রকমের barমতো ফাংশন থাকে এবং fooএটি বড় না হয় এবং কোনও কারণে তারা তাদের কলকারীদের সাথে ইনলাইন করতে না পারে।


অনুবাদে ত্রুটি না হলে কোনও সংকলক কোডটি কেন সেইভাবে অনুবাদ করেন, যখন আরও ভাল ব্যবহার হতে পারে sure উদাহরণস্বরূপ জিজ্ঞাসা করুন কেন ঝাঁকুনি এত অদ্ভুত (অপ্টিমাইজড নয়) এই লুপটি থ্রান্সলেটেড , জিসিসি , আইসিসি এবং এমনকি এমএসভিসি
র সাথে

1
@ আরবিএমএম: আমি আপনার বক্তব্য বুঝতে পারি না। এটিকে ঝাঁকুনির জন্য সম্পূর্ণ পৃথক মিস অপটিমাইজেশনের মতো দেখায়, এই প্রশ্নটি যা সম্পর্কিত তা সম্পর্কিত নয়। মিসড অপটিমাইজেশন বাগগুলি বিদ্যমান এবং বেশিরভাগ ক্ষেত্রেই এটি ঠিক করা উচিত। এগিয়ে যান এবং এটি bugs.llvm.org
পিটার

হ্যাঁ, আমার কোড উদাহরণটি সম্পূর্ণ প্রশ্নের সাথে সম্পর্কিত নয়। অদ্ভুত (আমার চেহারা জন্য) অনুবাদটি কেবলমাত্র অন্য একটি উদাহরণ (এবং কেবল একক ঝনঝন কম্পাইলারের জন্য)। তবে ফলাফল asm কোড যাইহোক সঠিক। না শুধুমাত্র সেরা এবং ইভেন নেটিভ তুলনা করুন জিসিসি / আইসিসি / এমএসভিসি
আরবিএমএম
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.