সি ++ এ কেন এবং কীভাবে ভার্চুয়াল ফাংশনগুলি ধীর হয়?


38

যে কেউ ভার্চুয়াল টেবিলটি ঠিক কীভাবে কাজ করে এবং ভার্চুয়াল ফাংশনগুলি যখন ডাকা হয় তখন কী পয়েন্টার যুক্ত হয় তা বিশদে ব্যাখ্যা করতে পারে।

যদি সেগুলি আসলে ধীর হয়, আপনি কি সেই সময়টি দেখাতে পারেন যে ভার্চুয়াল ফাংশনটি কার্যকর করতে সময় লাগে সাধারণ শ্রেণির পদ্ধতির চেয়ে বেশি? কিছু কোড না দেখে কীভাবে কী ঘটছে তার ট্র্যাক হারানো সহজ।


5
কোনও ভিটিবেল থেকে সঠিক পদ্ধতি কলটি সন্ধান করা অবশ্যই সরাসরি পদ্ধতিটি কল করার চেয়ে বেশি সময় নিতে পারে, কারণ আরও অনেক কিছু করার রয়েছে। আপনার নিজের প্রোগ্রামের প্রেক্ষাপটে আর কতক্ষণ বা অতিরিক্ত সময় তাৎপর্যপূর্ণ কিনা তা অন্য প্রশ্ন। en.wikedia.org/wiki/ ভার্চুয়াল_মোথড_ টেবিল
রবার্ট হার্ভে

10
ঠিক কি চেয়ে ধীর? আমি এমন কোড দেখেছি যা প্রচুর স্যুইচ স্টেটমেন্টের সাথে গতিশীল আচরণের একটি ভাঙ্গা, ধীর বাস্তবায়ন হয়েছে কারণ কিছু প্রোগ্রামার শুনেছিলেন যে ভার্চুয়াল ফাংশনগুলি ধীর গতিতে রয়েছে।
ক্রিস্টোফার ক্রিউটজিগ

7
প্রায়শই, ভার্চুয়াল কলগুলি তাদের মন্থর হয় না এমনটি নয়, তবে যে সংকলকটি তাদের ইনলাইন করার ক্ষমতা রাখে না।
কেভিন হসু

4
@ কেভিন হু: হ্যাঁ এটি একেবারে। প্রায়শই যে কোনও সময় কেউ আপনাকে বলবে যে তারা কিছু "ভার্চুয়াল ফাংশন কল ওভারহেড" মুছে ফেলার থেকে গতি পেয়েছে, আপনি যদি এটি দেখেন যে সমস্ত স্পিডআপটি আসলে এসেছে যেখানে আশা করা সম্ভব হবে যা এখন সম্ভব হয়েছে কারণ সংকলকটি পুরোপুরি অনুকূলিত করতে পারেনি অনির্দিষ্ট কল আগে
টিমডে

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

উত্তর:


55

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

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

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

সম্পাদনা: কল নির্দেশাবলীর পার্থক্য অন্যান্য উত্তরে বর্ণিত হয়েছে described মূলত, একটি স্ট্যাটিক ("সাধারণ") কলের কোডটি হ'ল:

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

ভার্চুয়াল কল ঠিক একই কাজ করে, ব্যতীত ফাংশন ঠিকানাটি সংকলন সময়ে জানা যায় না। পরিবর্তে, কয়েকটি নির্দেশাবলী ...

  • ভ্যাটেবল পয়েন্টারটি পান, যা বস্তু থেকে প্রতিটি ভার্চুয়াল ফাংশনের জন্য একটি ফাংশন পয়েন্টার (ফাংশন ঠিকানা) এর অ্যারে নির্দেশ করে।
  • ভিটিবেল থেকে একটি নিবন্ধে সঠিক ফাংশন ঠিকানা পান (সঠিক ফাংশন ঠিকানাটি যে সূচকটি সংরক্ষণ করা হয় তা সংকলন-সময়ে সিদ্ধান্ত নেওয়া হয়)।
  • হার্ডকডযুক্ত ঠিকানায় ঝাঁপিয়ে না গিয়ে that নিবন্ধের ঠিকানায় যান।

শাখাগুলি হিসাবে: একটি শাখা এমন কিছু যা কেবলমাত্র পরবর্তী নির্দেশনাটি কার্যকর করার পরিবর্তে অন্য নির্দেশে ঝাঁপিয়ে পড়ে। এর মধ্যে রয়েছে if, switchবিভিন্ন লুপের কিছু অংশ, ফাংশন কল ইত্যাদি and দেখুন কেন একটি বাছাই করা অ্যারের চেয়ে দ্রুত বাছাই করা অ্যারে প্রক্রিয়াকরণ হচ্ছে? কেন এটি ধীর হতে পারে, সিপিইউগুলি এই মন্দাকে মোকাবেলায় কী করবে এবং কীভাবে এটি নিরাময়যোগ্য নয়।


6
@ JörgWMittag তারা সব ব্যাখ্যাকারী কাপড়, এবং তারা এখনও ধীর সি ++ কম্পাইলার দ্বারা উত্পন্ন বাইনারি কোড তুলনায়
স্যাম

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

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

3
দুর্দান্ত উত্তর, +1। তবে একটি বিষয় লক্ষণীয় যে কখনও কখনও শাখার ফলাফলগুলি সংকলন সময়ে জানা যায়, উদাহরণস্বরূপ আপনি যখন কাঠামোগত ক্লাসগুলি লিখেন যা বিভিন্ন ব্যবহারকে সমর্থন করার প্রয়োজন হয় তবে একবার যদি অ্যাপ্লিকেশন কোডগুলি এই শ্রেণীর সাথে যোগাযোগ করে তবে নির্দিষ্ট ব্যবহারটি ইতিমধ্যে জানা যায়। এই ক্ষেত্রে, ভার্চুয়াল ফাংশনগুলির বিকল্প, সি ++ টেম্পলেট হতে পারে। : গুড উদাহরণ CRTP, যা কোনো vtables ছাড়া ভার্চুয়াল ফাংশন আচরণ অনুকরণ হবে en.wikipedia.org/wiki/Curiously_recurring_template_pattern
DXM

3
@ জেমস আপনার একটি বিষয় আছে। আমি যা বলার চেষ্টা করেছি তা হ'ল: যে কোনও ইন্ডিয়ারেশনে একই সমস্যা রয়েছে, এটি নির্দিষ্ট কিছু নয় virtual

23

এখানে যথাক্রমে ভার্চুয়াল ফাংশন কল এবং একটি অ-ভার্চুয়াল কল থেকে কিছু আসল বিচ্ছিন্ন কোড রয়েছে:

mov    -0x8(%rbp),%rax
mov    (%rax),%rax
mov    (%rax),%rax
callq  *%rax

callq  0x4007aa

আপনি দেখতে পাচ্ছেন যে ভার্চুয়াল কলটির সঠিক ঠিকানাটি খুঁজতে তিনটি অতিরিক্ত নির্দেশাবলীর প্রয়োজন রয়েছে, যেখানে অ-ভার্চুয়াল কলটির ঠিকানাটি সংকলন করা যেতে পারে।

তবে নোট করুন যে অতিরিক্ত সময় অনুসন্ধানের সময়টিকে বেশিরভাগ সময় নগণ্য বলে বিবেচনা করা যেতে পারে। লুপের মতো দেখার ক্ষেত্রে সময়টি উল্লেখযোগ্য হবে এমন পরিস্থিতিতে লুপের আগে প্রথম তিনটি নির্দেশাবলীর সাহায্যে মানটি ক্যাশে করা যায়।

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


1
বোঝার বিষয়টি হ'ল প্রায় সব ক্ষেত্রেই ভিটিবেল লুক এবং অপ্রত্যক্ষ কল কল করা পদ্ধতির মোট চলমান সময়ের উপর নগণ্য প্রভাব ফেলবে।
জন আর স্ট্রোহম

11
@ জনআর.স্ট্রোহম একজন মানুষের নগণ্য হ'ল অন্য ব্যক্তির বাধা
জেমস

1
-0x8(%rbp)। ওহ আমার ... এটি এবং টি সিনট্যাক্স।
অ্যাবিক্স

" তিনটি অতিরিক্ত নির্দেশাবলী " না, কেবল দুটি: ভিটিপিআর লোড করা এবং ফাংশন পয়েন্টার লোড করা
কৌতূহলী

@ কুরিয়াসগুয়ে আসলে এটি তিনটি অতিরিক্ত নির্দেশ। আপনি ভুলে গেছেন যে ভার্চুয়াল পদ্ধতিটি সর্বদা পয়েন্টারে কল করা হয় , তাই আপনাকে প্রথমে একটি রেজিস্টারটিতে পয়েন্টারটি লোড করতে হবে। সংক্ষেপে বলতে গেলে, প্রথম পদক্ষেপটি হল পয়েন্টার ভেরিয়েবলটি রেকর্ড% র্যাকসটিতে রাখা ঠিকানাটি লোড করা, তারপরে নিবন্ধের ঠিকানা অনুসারে% rax রেজিস্টার করতে এই ঠিকানায় ভিটিপিআর লোড করুন, তারপরে এই ঠিকানা অনুসারে নিবন্ধভুক্ত করুন,% র্যাকসে কল করার পদ্ধতিটির ঠিকানাটি লোড করুন, তারপরে কল করুন *% র্যাকস !.
গ্যাব 好人

18

কি ধীরে ধীরে ?

ভার্চুয়াল ফাংশন এমন একটি সমস্যা সমাধান করে যা সরাসরি ফাংশন কলগুলির মাধ্যমে সমাধান করা যায় না। সাধারণভাবে, আপনি কেবল দুটি প্রোগ্রামের তুলনা করতে পারেন যা একই জিনিসটি গণনা করে। "এই রশ্মির ট্রেসারটি সেই সংকলকটির চেয়ে দ্রুততর" অর্থবোধ করে না এবং এই নীতিটি এমনকি স্বতন্ত্র ফাংশন বা প্রোগ্রামিং ল্যাঙ্গুয়েজ কনস্ট্রাক্টের মতো ছোট ছোট জিনিসগুলিতেও সাধারণীকরণ করে।

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

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

অন্য কথায়, ভার্চুয়াল ফাংশনগুলিতে ভার্চুয়াল প্রেরণের প্রয়োজন না হলে আপনার সি ++ সংকলক কাজ করতে পারে, তাই অ-ভার্চুয়াল ফাংশনগুলির সাথে তুলনামূলকভাবে তাদের কর্মক্ষমতা সম্পর্কে চিন্তা করার কোনও কারণ নেই।

নতুন: এছাড়াও, আমরা অবশ্যই ভাগ করা লাইব্রেরিগুলি ভুলে যাব না। আপনি যদি একটি ভাগ করা লাইব্রেরিতে থাকা কোনও শ্রেণি ব্যবহার করছেন তবে সাধারণ সদস্য ফাংশনে কলটি কোনও দুর্দান্ত নির্দেশের মতো নয় callq 0x4007aa। এটি কয়েকটি প্রোগ্রামের মধ্য দিয়ে যেতে হবে যেমন "প্রোগ্রামের লিঙ্ক টেবিল" বা এমন কোনও কাঠামোর মাধ্যমে পরোক্ষ। অতএব, ভাগ করা লাইব্রেরি ইন্ডিরেশন কিছুটা (পুরোপুরি না হলে) ভার্চুয়াল কল এবং সরাসরি কলের মধ্যে দামের পার্থক্যকে সমান করতে পারে। সুতরাং ভার্চুয়াল ফাংশন ট্রেডঅফগুলি সম্পর্কে যুক্তিযুক্তভাবে প্রোগ্রামটি কীভাবে তৈরি করা হবে তা অবশ্যই বিবেচনায় নিতে হবে: টার্গেট অবজেক্টের ক্লাসটি একচেটিয়াভাবে প্রোগ্রামটি যুক্ত করেছে যা কল করছে।


4
"কি ধীরে ধীরে?" - আপনি যদি এমন কোনও পদ্ধতি ভার্চুয়াল তৈরি করেন যা এমনটি না হয় তবে আপনার কাছে তুলনা করার উপযুক্ত উপাদান রয়েছে।
টিডামার্স

2
ভার্চুয়াল ফাংশনগুলিতে কলগুলি সর্বদা গতিশীল হয় না তা নির্দেশ করার জন্য আপনাকে ধন্যবাদ। এখানকার প্রতিটি প্রতিক্রিয়া এটিকে ফাংশন ভার্চুয়াল ঘোষণার মতো দেখায় যেমন পরিস্থিতি নির্বিশেষে একটি স্বয়ংক্রিয় পারফরম্যান্স হিট।
সিন্ডোগ

12

কারণ একটি ভার্চুয়াল কল সমান

res_t (*foo)(arg_t);
foo = (obj->vtable[foo_offset]);
foo(obj,args)

যেখানে একটি অ-ভার্চুয়াল ফাংশন সহ সংকলকটি প্রথম লাইনকে ধ্রুবকভাবে ভাঁজ করতে পারে, এটি হ'ল একটি যুক্তি এবং একটি গতিশীল কল কেবল একটি স্থির কলে রূপান্তরিত

এটি এটিকে ফাংশনটি ইনলাইন করতে দেয় (সমস্ত প্রযোজ্য অপ্টিমাইজেশনের ফলাফল সহ)

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.