সি ++ শ্রেণিতে ভার্চুয়াল পদ্ধতি থাকার পারফরম্যান্স ব্যয় কত?


107

সি ++ ক্লাসে কমপক্ষে একটি ভার্চুয়াল পদ্ধতি (বা এর কোনও প্যারেন্ট ক্লাস) থাকা মানে ক্লাসটির ভার্চুয়াল টেবিল থাকবে এবং প্রতিটি ক্ষেত্রে ভার্চুয়াল পয়েন্টার থাকবে।

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

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

আমি জিজ্ঞাসা করার কারণটি হ'ল আমি এমন একটি বাগটি পেরিয়ে এসেছি যা ঘটনাক্রমে কোনও প্রোগ্রামার কোনও পদ্ধতি ভার্চুয়াল সংজ্ঞা দিতে ভুলে গিয়েছিল। এই ধরনের ভুল আমি এই প্রথম দেখছি না। আর আমি চিন্তা কেন আমরা কি যোগ ভার্চুয়াল শব্দ যখন পরিবর্তে প্রয়োজন সরানোর ভার্চুয়াল শব্দ যখন আমরা পুরোপুরি নিশ্চিত যে এটা হয় না প্রয়োজন? যদি পারফরম্যান্সের ব্যয় কম হয় তবে আমি মনে করি আমি কেবল আমার দলে নিম্নলিখিতগুলি সুপারিশ করব: কেবলমাত্র প্রতিটি পদ্ধতিতে ডিস্ট্রাস্টার সহ প্রতিটি ক্লাসে ডিফল্ট অনুসারে ভার্চুয়াল তৈরি করুন এবং আপনার প্রয়োজন হলে কেবল এটি সরিয়ে দিন। ওটা কি তোমার কাছে পাগল লাগছে?



7
ভার্চুয়ালকে নন ভার্চুয়াল কলগুলির সাথে তুলনা করা মেনিংফুল নয়। তারা বিভিন্ন কার্যকারিতা সরবরাহ করে। আপনি যদি ভার্চুয়াল ফাংশন কলগুলি সি সিটিভিলেন্টের সাথে তুলনা করতে চান তবে আপনাকে কোডের মূল্য যুক্ত করতে হবে যা ভার্চুয়াল ফাংশনের সমতুল্য বৈশিষ্ট্যটি কার্যকর করে।
মার্টিন ইয়র্ক

যা হয় সুইচ স্টেটমেন্ট বা বড় যদি স্টেটমেন্ট হয়। আপনি যদি চালাক হন তবে আপনি ফাংশন পয়েন্টার টেবিলটি ব্যবহার করে পুনরায় বাস্তবায়ন করতে পারবেন তবে এটির ভুল হওয়ার সম্ভাবনাগুলি অনেক বেশি।
মার্টিন ইয়র্ক


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

উত্তর:


104

আমি 3 গিগাহার্টজ ইন-অর্ডার পাওয়ারপিসি প্রসেসরের কিছু সময় চালিয়েছি। সেই আর্কিটেকচারে, ভার্চুয়াল ফাংশন কলের জন্য ডাইরেক্ট (নন-ভার্চুয়াল) ফাংশন কলের চেয়ে 7 ন্যানোসেকেন্ড বেশি সময় লাগে।

সুতরাং, ফাংশনটি তুচ্ছ গেট () / সেট () অ্যাকসেসরের মতো কিছু না হওয়া পর্যন্ত ব্যয়টি নিয়ে সত্যই চিন্তা করার মতো নয়, যাতে ইনলাইন ব্যতীত অন্য কোনও জিনিসই অপচয়হীন। একটি ফাংশনটিতে একটি 7ns ওভারহেড যা 0.5ns এর সাথে ইনলাইন করে তীব্র হয়; একটি ফাংশনটিতে একটি 7ns ওভারহেড যা কার্যকর করতে 500 মিমি লাগে তা অর্থহীন।

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

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

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

মৃত্যুদন্ড কার্যকর করার ক্ষেত্রে আইচাচি মিস করার প্রভাবের জন্য আমার সময় নিয়ন্ত্রণ (ইচ্ছাকৃতভাবে, যেহেতু আমি বিচ্ছিন্নভাবে সিপিইউ পাইপলাইন পরীক্ষা করার চেষ্টা করছিলাম), সুতরাং তারা সেই ব্যয়টি ছাড় দেয়।


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

কি চেয়ে 7 সেকেন্ড ন্যানো। যদি একটি সাধারণ কলটি 1 ন্যানো সেকেন্ড হয় তবে এটি যদি সম্মানজনক হয় তবে যদি একটি সাধারণ কল 70 ন্যানো সেকেন্ড হয় it
মার্টিন ইয়র্ক

আপনি যদি সময়গুলির দিকে তাকান তবে আমি খুঁজে পেলাম যে কোনও ফাংশনের জন্য 0.66ns ইনলাইন ব্যয় হয়েছে, সরাসরি ফাংশন কলের ডিফারেনশিয়াল ওভারহেড ছিল 4.8ns এবং একটি ভার্চুয়াল ফাংশন 12.3ns (ইনলাইনের সাথে তুলনা করে)। আপনি ভাল কথাটি বলছেন যে যদি ফাংশনটি নিজেই একটি মিলিসেকেন্ডে ব্যয় করে তবে 7 এনএস এর অর্থ কিছুই নেই।
ক্রাশ ওয়ার্কস

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

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

19

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

অবশ্যই এই জরিমানাগুলি তাৎপর্যপূর্ণ কিনা বা না আপনার অ্যাপ্লিকেশন, এই কোড পাথগুলি কতবার কার্যকর করা হয় এবং আপনার উত্তরাধিকারের ধরণগুলির উপর নির্ভর করে।

যদিও আমার মতে, ডিফল্ট হিসাবে ভার্চুয়াল হিসাবে সবকিছু থাকা কোনও সমস্যার কম্বল সমাধান যা আপনি অন্যান্য উপায়ে সমাধান করতে পারেন।

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

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


সবচেয়ে বড় হারানো অপ্টিমাইজেশন হ'ল ইনলাইনিং, বিশেষত যদি ভার্চুয়াল ফাংশনটি প্রায়শই ছোট বা খালি থাকে।
জ্যান লিংস

@ অ্যান্ড্রু: দর্শনীয় দৃষ্টিকোণ আমি আপনার শেষ অনুচ্ছেদে কিছুটা দ্বিমত পোষণ করছি, যদিও: যদি একটি বেস শ্রেণীর কোনও ফাংশন থাকে saveযা বেস শ্রেণীর কোনও ফাংশনের নির্দিষ্ট প্রয়োগের উপর নির্ভর করে write, তবে আমার কাছে মনে হয় saveহয় খারাপভাবে কোডড হয়, বা writeব্যক্তিগত হওয়া উচিত।
MiniQuark

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

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

এবং একটি আইছা স্টল সত্যিই গুরুতর হতে পারে: আমার পরীক্ষায় 600 চক্র।
ক্রশ ওয়ার্কস

9

এটা নির্ভর করে. :) (আপনি কি অন্য কিছু আশা করেছিলেন?)

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

স্ট্যান্ড :: পিডিওড প্রকারের অনুলিপি (কপি) একটি সাধারণ মেকপি রুটিন অবলম্বন করতে পারে তবে নন-পিওডি টাইপগুলি আরও সতর্কতার সাথে পরিচালনা করতে হবে।

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

সবচেয়ে খারাপ ক্ষেত্রে, আপনি দেখতে পাচ্ছেন 5x ধীর চালিত কার্যকরকরণ (কয়েকটি নম্বর গ্রন্থাগার ক্লাস পুনর্নির্মাণের জন্য সম্প্রতি একটি বিশ্ববিদ্যালয় প্রকল্প থেকে এই সংখ্যাটি নেওয়া হয়েছিল Our আমাদের পাত্রে এটি সংরক্ষণ করা ডেটা টাইপের সাথে সাথেই নির্মাণে প্রায় 5x সময় লেগেছে vtable)

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

তবে এখানে পারফরম্যান্সটি আপনার প্রাথমিক বিবেচনা হওয়া উচিত নয়। অন্যান্য কারণে ভার্চুয়াল তৈরি করা নিখুঁত সমাধান নয়।

উদ্ভূত শ্রেণিতে সমস্ত কিছুকে ওভাররাইড করা মঞ্জুর করা শ্রেণীর আক্রমণকারীদের বজায় রাখা আরও কঠিন করে তোলে। কোনও শ্রেণি কীভাবে গ্যারান্টি দেয় যে এটি কোনও সামঞ্জস্যপূর্ণ অবস্থায় থাকবে যখন তার যে কোনও একটি পদ্ধতির যে কোনও সময় নতুন সংজ্ঞা দেওয়া যেতে পারে?

সমস্ত কিছু ভার্চুয়াল তৈরি করা কিছু সম্ভাব্য বাগগুলি মুছে ফেলতে পারে তবে এটি নতুনগুলিরও পরিচয় দেয়।


7

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

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


উত্তম উত্তর কিন্তু, আইএমও, দ্বিতীয়ার্ধে যথেষ্ট জোর দেওয়া নয়: ওভারহেড দিয়ে নিজেকে ভারী করে তোলা আপনার প্রয়োজন নেই, বেশ খোলামেলাভাবে, বাদাম - বিশেষত এই ভাষাটি ব্যবহার করার সময় যার মন্ত্রে "আপনি যা কিছু করেন না তার জন্য অর্থ প্রদান করবেন না" ব্যবহার করবেন না। " ডিফল্টরূপে সবকিছু ভার্চুয়াল তৈরি করা যতক্ষণ না কেউ নন-ভার্চুয়াল হতে পারে / কেন তা ন্যায্য প্রমাণ না করে এটি একটি জঘন্য নীতি।
আন্ডারস্কোর_২০

5

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


বাস্তবায়ন

#include <iostream>
#include <vector>

// virtual dispatch model...

struct Base
{
    virtual int f() const { return 1; }
};

struct Derived : Base
{
    virtual int f() const { return 2; }
};

// alternative: member variable encodes runtime type...

struct Type
{
    Type(int type) : type_(type) { }
    int type_;
};

struct A : Type
{
    A() : Type(1) { }
    int f() const { return 1; }
};

struct B : Type
{
    B() : Type(2) { }
    int f() const { return 2; }
};

struct Timer
{
    Timer() { clock_gettime(CLOCK_MONOTONIC, &from); }
    struct timespec from;
    double elapsed() const
    {
        struct timespec to;
        clock_gettime(CLOCK_MONOTONIC, &to);
        return to.tv_sec - from.tv_sec + 1E-9 * (to.tv_nsec - from.tv_nsec);
    }
};

int main(int argc)
{
  for (int j = 0; j < 3; ++j)
  {
    typedef std::vector<Base*> V;
    V v;

    for (int i = 0; i < 1000; ++i)
        v.push_back(i % 2 ? new Base : (Base*)new Derived);

    int total = 0;

    Timer tv;

    for (int i = 0; i < 100000; ++i)
        for (V::const_iterator i = v.begin(); i != v.end(); ++i)
            total += (*i)->f();

    double tve = tv.elapsed();

    std::cout << "virtual dispatch: " << total << ' ' << tve << '\n';

    // ----------------------------

    typedef std::vector<Type*> W;
    W w;

    for (int i = 0; i < 1000; ++i)
        w.push_back(i % 2 ? (Type*)new A : (Type*)new B);

    total = 0;

    Timer tw;

    for (int i = 0; i < 100000; ++i)
        for (W::const_iterator i = w.begin(); i != w.end(); ++i)
        {
            if ((*i)->type_ == 1)
                total += ((A*)(*i))->f();
            else
                total += ((B*)(*i))->f();
        }

    double twe = tw.elapsed();

    std::cout << "switched: " << total << ' ' << twe << '\n';

    // ----------------------------

    total = 0;

    Timer tw2;

    for (int i = 0; i < 100000; ++i)
        for (W::const_iterator i = w.begin(); i != w.end(); ++i)
            total += (*i)->type_;

    double tw2e = tw2.elapsed();

    std::cout << "overheads: " << total << ' ' << tw2e << '\n';
  }
}

কর্মক্ষমতা ফলাফল

আমার লিনাক্স সিস্টেমে:

~/dev  g++ -O2 -o vdt vdt.cc -lrt
~/dev  ./vdt                     
virtual dispatch: 150000000 1.28025
switched: 150000000 0.344314
overhead: 150000000 0.229018
virtual dispatch: 150000000 1.285
switched: 150000000 0.345367
overhead: 150000000 0.231051
virtual dispatch: 150000000 1.28969
switched: 150000000 0.345876
overhead: 150000000 0.230726

এটি প্রস্তাব দেয় যে একটি ইনলাইন টাইপ-সংখ্যা-স্যুইচ করা পদ্ধতির প্রায় (1.28 - 0.23) / (0.344 - 0.23) = 9.2 গতি দ্রুত। অবশ্যই এটি পরীক্ষিত / সংকলক পতাকা ও সংস্করণ ইত্যাদির সঠিক সিস্টেমের সাথে নির্দিষ্ট, তবে সাধারণত নির্দেশক।


মন্তব্যগুলি পুনরায় ভার্চুয়াল ডিস্প্যাচ

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


আমি আপনার কোড সম্পর্কিত একটি প্রশ্ন জিজ্ঞাসা করেছি কারণ আমার g++/ clangএবং ব্যবহার করে কিছু "অদ্ভুত" ফলাফল রয়েছে -lrt। আমি ভেবেছিলাম ভবিষ্যতের পাঠকদের জন্য এটি এখানে উল্লেখযোগ্য।
হল্ট

@ হোল্ট: রহস্যজনক ফলাফল দেওয়া ভাল প্রশ্ন! আমি যদি অর্ধেক সুযোগ পাই তবে কয়েকদিনের মধ্যে এটি আরও ঘনিষ্ঠভাবে জানাব। চিয়ার্স।
টনি ডেলরয়

3

অতিরিক্ত ব্যয় বেশিরভাগ পরিস্থিতিতে কার্যত কিছুই নয়। (পাপ ক্ষমা করুন) ইজাক ইতিমধ্যে সংবেদনশীল আপেক্ষিক পদক্ষেপ পোস্ট করেছে।

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


অপ্টিমাইজেশন সম্পর্কিত:
আপনার ভাষার নির্মাণের তুলনামূলক ব্যয়টি জানা এবং বিবেচনা করা গুরুত্বপূর্ণ। বড় ও স্বরলিপি গল্পের অর্ধেক - আপনার অ্যাপ্লিকেশনটি কীভাবে স্কেল হয় । অন্য অর্ধেকটি এর সামনে ধ্রুবক ফ্যাক্টর।

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


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

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


2

অন্য সবাই ভার্চুয়াল পদ্ধতিগুলির যেমন পারফরম্যান্স সম্পর্কে সঠিক, আমি মনে করি আসল সমস্যাটি টিমটি সি ++ এর ভার্চুয়াল কীওয়ার্ডের সংজ্ঞা সম্পর্কে জানে কিনা।

এই কোডটি বিবেচনা করুন, আউটপুটটি কী?

#include <stdio.h>

class A
{
public:
    void Foo()
    {
        printf("A::Foo()\n");
    }
};

class B : public A
{
public:
    void Foo()
    {
        printf("B::Foo()\n");
    }
};

int main(int argc, char** argv)
{    
    A* a = new A();
    a->Foo();

    B* b = new B();
    b->Foo();

    A* a2 = new B();
    a2->Foo();

    return 0;
}

এখানে অবাক হওয়ার মতো কিছু নেই:

A::Foo()
B::Foo()
A::Foo()

ভার্চুয়াল হিসাবে কিছুই। যদি ভার্চুয়াল কীওয়ার্ডটি এ এবং বি উভয় শ্রেণিতে ফু এর সামনের অংশে যুক্ত হয় তবে আমরা এটি আউটপুটটির জন্য পাই:

A::Foo()
B::Foo()
B::Foo()

সবাই অনেক কি প্রত্যাশা করে।

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

#include <stdio.h>

class A
{
public:
    virtual void Foo()
    {
        printf("A::Foo()\n");
    }
};

class B : public A
{
public:
    void Foo()
    {
        printf("B::Foo()\n");
    }
};

int main(int argc, char** argv)
{    
    A* a = new A();
    a->Foo();

    B* b = new B();
    b->Foo();

    A* a2 = new B();
    a2->Foo();

    return 0;
}

উত্তর: ভার্চুয়াল কীওয়ার্ডটি বি তে যুক্ত হলে একই? কারণটি হ'ল বি :: ফু এর স্বাক্ষর হ'ল A :: Foo () এর সাথে মেলে এবং কারণ A এর Foo ভার্চুয়াল, তাই B এরও।

এখন কেসের ক্ষেত্রে বিবেচনা করুন যেখানে বি এর ফু ভার্চুয়াল এবং এ এর ​​নয়। আউটপুট তাহলে কি? এই ক্ষেত্রে, আউটপুট হয়

A::Foo()
B::Foo()
A::Foo()

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

ভুলে যাবেন না যে ভার্চুয়াল পদ্ধতিগুলির অর্থ এই শ্রেণিটি ভবিষ্যতের ক্লাসগুলিকে তার কিছু আচরণকে ওভাররাইড / পরিবর্তন করার ক্ষমতা দিচ্ছে।

সুতরাং আপনার যদি ভার্চুয়াল কীওয়ার্ড সরানোর কোনও নিয়ম থাকে তবে এটির উদ্দেশ্যযুক্ত প্রভাব নাও থাকতে পারে।

সি ++ এর ভার্চুয়াল কীওয়ার্ডটি একটি শক্তিশালী ধারণা। আপনার অবশ্যই নিশ্চিত হওয়া উচিত যে দলের প্রতিটি সদস্য এই ধারণাটি সত্যই জানে যাতে এটি নকশাকৃত হিসাবে ব্যবহার করা যায়।


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

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

1

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

আমি এই কারণে আপনার পরামর্শের বিরুদ্ধে সুপারিশ করব, তবে এটি যদি আপনাকে বাগগুলি প্রতিরোধ করতে সহায়তা করে তবে এটি বাণিজ্য বন্ধের পক্ষে মূল্যবান হতে পারে। আমি সাহায্য করতে পারি না তবে ভাবতে পারি যে এমন কোনও মাঝারি স্থল অবশ্যই রয়েছে যা খুঁজে পাওয়ার মতো worth


-1

ভার্চুয়াল পদ্ধতিতে কল করতে এটির জন্য কেবলমাত্র আরও কয়েকটি asm নির্দেশ প্রয়োজন।

তবে আমি মনে করি না যে আপনি মজাদার (ইন্ট এ, ইনট বি) মজাদার () এর সাথে তুলনা করে বেশ কয়েকটি অতিরিক্ত 'পুশ' নির্দেশনা দিয়েছেন worry সুতরাং ভার্চুয়ালগুলি সম্পর্কেও চিন্তা করবেন না, যতক্ষণ না আপনি বিশেষ পরিস্থিতিতে থাকেন এবং না দেখেন যে এটি সত্যই সমস্যার দিকে পরিচালিত করে।

পিএস যদি আপনার ভার্চুয়াল পদ্ধতি থাকে তবে আপনার ভার্চুয়াল ডেস্ট্রাক্টর রয়েছে তা নিশ্চিত করুন। এইভাবে আপনি সম্ভাব্য সমস্যাগুলি এড়াতে পারবেন


'Xtofl' এবং 'টম' মন্তব্যের জবাবে। আমি 3 টি ফাংশন দিয়ে ছোট পরীক্ষা করেছি:

  1. অপার্থিব
  2. সাধারণ
  3. 3 ইনট প্যারামিটার সহ সাধারণ

আমার পরীক্ষাটি ছিল একটি সাধারণ পুনরাবৃত্তি:

for(int it = 0; it < 100000000; it ++) {
    test.Method();
}

এবং ফলাফল এখানে:

  1. 3,913 সেকেন্ড
  2. 3,873 সেকেন্ড
  3. 3,970 সেকেন্ড

এটি ভিসি ++ দ্বারা ডিবাগ মোডে সংকলিত হয়েছিল। আমি প্রতি পদ্ধতিতে মাত্র 5 টি পরীক্ষা দিয়েছি এবং গড় মান গণনা করেছি (সুতরাং ফলাফলগুলি বেশ ত্রুটিযুক্ত হতে পারে) ... যাইহোক, মানগুলি প্রায় 100 মিলিয়ন কল ধরে সমান। এবং 3 অতিরিক্ত পুশ / পপ সহ পদ্ধতিটি ধীর ছিল।

মূল কথাটি হ'ল আপনি যদি ধাক্কা / পপটির সাথে সাদৃশ্যটি পছন্দ না করেন তবে আপনার কোডে / অন্যথায় অতিরিক্ত ভাবেন? আপনি / সি; পি; অতিরিক্ত অতিরিক্ত যুক্ত করার সময় কি সিপিইউ পাইপলাইন সম্পর্কে ভাবেন? কোডটি কোন সিপিইউতে চলবে তা আপনি কখনই জানতে পারবেন না ... সাধারণ সংকলক একটি সিপিইউর জন্য আরও অনুকূল এবং অন্যটির জন্য কম অনুকূল জেনারেট করতে পারে ( ইন্টেল) সি ++ সংকলক )


2
অতিরিক্ত অ্যাসেম কেবল একটি পৃষ্ঠা ত্রুটি ট্রিগার করতে পারে (এটি অ-ভার্চুয়াল ফাংশনগুলির জন্য হবে না) - আমি মনে করি আপনি সমস্যাটিকে অত্যধিকভাবে প্রশংসনীয় করেছেন।
xtofl

2
Xtofl এর মন্তব্য +1। ভার্চুয়াল ফাংশনগুলি ইন্ডিয়ারেশন পরিচয় করে, যা পাইপলাইন "বুদবুদ" প্রবর্তন করে এবং ক্যাচিং আচরণকে প্রভাবিত করে।
টম

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

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

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