std :: ফাংশন বনাম টেমপ্লেট


161

সি ++ 11 এর জন্য ধন্যবাদ আমরা std::functionফ্যান্টারের মোড়কের পরিবার পেয়েছি । দুর্ভাগ্যক্রমে, আমি এই নতুন সংযোজন সম্পর্কে কেবল খারাপ জিনিসই শুনছি। সর্বাধিক জনপ্রিয় হ'ল তারা ভীষণ ধীর। আমি এটি পরীক্ষা করেছি এবং টেমপ্লেটগুলির সাথে তুলনায় তারা সত্যই স্তন্যপান করে।

#include <iostream>
#include <functional>
#include <string>
#include <chrono>

template <typename F>
float calc1(F f) { return -1.0f * f(3.3f) + 666.0f; }

float calc2(std::function<float(float)> f) { return -1.0f * f(3.3f) + 666.0f; }

int main() {
    using namespace std::chrono;

    const auto tp1 = system_clock::now();
    for (int i = 0; i < 1e8; ++i) {
        calc1([](float arg){ return arg * 0.5f; });
    }
    const auto tp2 = high_resolution_clock::now();

    const auto d = duration_cast<milliseconds>(tp2 - tp1);  
    std::cout << d.count() << std::endl;
    return 0;
}

111 এমএস বনাম 1241 এমএস। আমি এটি ধরে নিয়েছি কারণ টেমপ্লেটগুলি সুন্দরভাবে ইনলাইন করা যেতে পারে, তবে functionএস ভার্চুয়াল কলগুলির মাধ্যমে ইন্টার্নালগুলি কভার করে।

স্পষ্টতই টেমপ্লেটগুলির তাদের সমস্যা যেমন আমি দেখছি তা রয়েছে:

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

আমি কি এইরূপে ধরে নিতে পারি যে এসকে পাসিং ফ্যান্টেক্টরগুলির ডি-ফ্যাক্টো স্ট্যান্ডার্ড functionহিসাবে ব্যবহার করা যেতে পারে , এবং যে জায়গাগুলিতে উচ্চ কার্যকারিতা প্রত্যাশিত টেমপ্লেটগুলি ব্যবহার করা উচিত?


সম্পাদনা:

আমার সংকলকটি সিটিপি ছাড়াই ভিজ্যুয়াল স্টুডিও 2012 ।


16
std::functionকেবলমাত্র যদি আপনার কলযোগ্যযোগ্য বস্তুর বিজাতীয় সংগ্রহের প্রয়োজন হয় তবেই ব্যবহার করুন (যেমন রানটাইমে কোনও বৈষম্যমূলক তথ্য উপলব্ধ নেই)।
কেরেক এসবি

30
আপনি ভুল জিনিস তুলনা করছেন। টেম্পলেটগুলি উভয় ক্ষেত্রেই ব্যবহৃত হয় - এটি " std::functionবা টেম্পলেট" নয়। আমি মনে করি যে এখানে সমস্যাটি কেবল একটি ল্যাম্বডাকে std::functionমোড়কে বনাম কোনও ল্যাম্বদা মোড়ানো নয় std::function। এই মুহূর্তে আপনার প্রশ্ন জিজ্ঞাসার মতো "" আমি কি একটি আপেল বা একটি বাটি পছন্দ করি? "
অরবিটে হালকা ঘোড়দৌড়

7
1ns বা 10ns উভয়ই কিছুই নয়।
আইপিসি

23
@ipc: 1000% যদিও কিছুই নয়। ওপি শনাক্ত করার সাথে সাথে যে কোনও ব্যবহারিক উদ্দেশ্যেই যখন স্কেলাবিলিটিটি আসে তখন আপনি যত্ন নেওয়া শুরু করেন।
অরবিট

18
@ আইপিসি এটি 10 ​​গুণ ধীর, যা বিশাল। গতি বেসলাইন সঙ্গে তুলনা করা প্রয়োজন; এটি ন্যানোসেকেন্ডের কারণেই এটি গুরুত্বপূর্ণ নয় তা ভাবতে প্রতারণা।
পল মানতা

উত্তর:


170

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

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

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

হ্যাঁ, এটি সত্য যে টেমপ্লেট সমর্থন নিখুঁত নয় এবং সি ++ 11 এর মধ্যে এখনও ধারণাগুলির জন্য সমর্থন নেই; তবে, আমি দেখছি std::functionনা যে আপনাকে কীভাবে সেভাবে সংরক্ষণ করতে হবে। std::functionটেম্পলেটগুলির বিকল্প নয়, বরং নকশা পরিস্থিতিগুলির জন্য একটি সরঞ্জাম যেখানে টেমপ্লেটগুলি ব্যবহার করা যায় না।

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

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

অবশেষে, এমন অন্যান্য পরিস্থিতি রয়েছে যেখানে std::functionঅনিবার্য নয়, যেমন আপনি পুনরাবৃত্ত ল্যাম্বডাস লিখতে চান ; যাইহোক, এই বিধিনিষেধগুলি আমি বিশ্বাস করি ধারণাগত ভিন্নতার চেয়ে প্রযুক্তিগত সীমাবদ্ধতার দ্বারা আরও নির্ধারিত হয়।

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


23
আমি মনে করি "আপনার যখন সম্ভাব্য বিভিন্ন ধরণের কলব্যাকের সংগ্রহ থাকে তবে সাধারণত আপনি একই রকম হন;" গুরুত্বপূর্ণ বিট। আমার থাম্বের নিয়মটি হ'ল: " std::functionস্টোরেজ শেষে এবং Funইন্টারফেসে টেম্পলেট পছন্দ করুন "।
আর মার্টিনহো ফার্নান্দেস

2
দ্রষ্টব্য: কংক্রিটের ধরণের আড়াল করার কৌশলটিকে টাইপ ইরেজর বলা হয় (পরিচালিত ভাষায় টাইপ ইরেজারে বিভ্রান্ত হওয়ার দরকার নেই)। এটি প্রায়শই গতিশীল পলিমারফিজমের ক্ষেত্রে প্রয়োগ করা হয় তবে এটি আরও শক্তিশালী (উদাহরণস্বরূপ unique_ptr<void>ভার্চুয়াল ডেস্ট্রাক্টর ছাড়াই উপযুক্ত ডেস্ট্রাক্টরদের কল করা)।
ইকামত্মুর

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

2
@ ক্যাটমুর: সুতরাং একরকমভাবে গতিশীল পলিমারফিজম হল ধারণামূলক প্যাটার্ন, অন্যদিকে মুছে ফেলা এমন একটি কৌশল যা এটি উপলব্ধি করতে দেয় allows
অ্যান্ডি প্রোল

2
@ ডাউনভোটার: এই উত্তরে আপনি কী ভুল পেয়েছেন তা শুনতে আমি আগ্রহী।
অ্যান্ডি প্রোল

89

অ্যান্ডি প্রোল ডিজাইনের বিষয়গুলি সুন্দরভাবে কভার করেছেন। এটি অবশ্যই খুব গুরুত্বপূর্ণ, তবে আমি বিশ্বাস করি যে মূল প্রশ্নটি সম্পর্কিত আরও কার্য সম্পাদনের বিষয় নিয়ে উদ্বেগ প্রকাশ করে std::function

প্রথমত, পরিমাপের কৌশল সম্পর্কে একটি দ্রুত মন্তব্য: 11 টি এসএমএসের প্রাপ্তির calc1কোনও অর্থ নেই। প্রকৃতপক্ষে, উত্পন্ন সমাবেশটি (বা অ্যাসেম্বলি কোডটি ডিবাগিং) দেখে, কেউ দেখতে পাবে যে ভিএস ২০১২ এর অপ্টিমাইজারটি উপলব্ধি করতে যথেষ্ট চতুর যে কল করার ফলাফল calc1পুনরাবৃত্তির চেয়ে আলাদা এবং কলটি লুপের বাইরে নিয়ে যায়:

for (int i = 0; i < 1e8; ++i) {
}
calc1([](float arg){ return arg * 0.5f; });

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

এটি চিহ্নিত করা হয়েছে, অপ্টিমাইজারটি বুঝতে আরও বেশি সমস্যা আছে std::functionএবং কলটি লুপের বাইরে সরিয়ে দেয় না। সুতরাং 1241ms এর জন্য একটি উপযুক্ত পরিমাপ calc2

লক্ষ্য করুন যে, std::functionবিভিন্ন ধরণের কলযোগ্য বস্তু সঞ্চয় করতে সক্ষম। অতএব, এটি স্টোরেজের জন্য কিছু টাইপ-মুছে ফেলার জাদু করতে হবে। সাধারণত, এটি গতিশীল মেমরির বরাদ্দকে বোঝায় (কল করার মাধ্যমে ডিফল্টরূপে new)। এটি বেশ পরিচিত যে এটি বেশ ব্যয়বহুল অপারেশন।

স্ট্যান্ডার্ড (20.8.11.2.1 / 5) ছোট বস্তুর জন্য গতিশীল মেমরি বরাদ্দ এড়াতে বাস্তবায়নগুলিকে আবদ্ধ করে যা ধন্যবাদ, VS2012 করে (বিশেষত, মূল কোডটির জন্য)।

মেমরি বরাদ্দ জড়িত যখন এটি কত ধীর হতে পারে একটি ধারণা পেতে, আমি তিনটি ক্যাপচার ল্যাম্বডা এক্সপ্রেশন পরিবর্তন করেছি float । এটি কল করার যোগ্য অবজেক্টটিকে ছোট অবজেক্ট অপটিমাইজেশন প্রয়োগ করতে খুব বড় করে তোলে:

float a, b, c; // never mind the values
// ...
calc2([a,b,c](float arg){ return arg * 0.5f; });

এই সংস্করণের জন্য, সময়টি প্রায় 16000 মিমি (মূল কোডের জন্য 1241 মিমের তুলনায়)।

অবশেষে, লক্ষ করুন যে ল্যাম্বডাটির জীবনকালটি সেইটিকে ঘিরে রেখেছে std::function। এই ক্ষেত্রে, ল্যাম্বদার একটি অনুলিপি সঞ্চয় করার চেয়ে std::functionএটিতে একটি "রেফারেন্স" সঞ্চয় করতে পারে। "রেফারেন্স" বলতে আমি বোঝাতে চাই std::reference_wrapperযা সহজেই ফাংশন std::refএবং দ্বারা তৈরি হয় std::cref। আরও স্পষ্টভাবে, ব্যবহার করে:

auto func = [a,b,c](float arg){ return arg * 0.5f; };
calc2(std::cref(func));

সময় কমে যায় প্রায় 1860ms এ।

আমি এই সম্পর্কে লিখেছিলাম কিছুক্ষণ আগে:

http://www.drdobbs.com/cpp/efficient-use-of-lambda-expressions-and/232500059

আমি যেমন নিবন্ধে বলেছি, সি ++ 11 এর দুর্বল সমর্থনের কারণে যুক্তিগুলি VS2010 এর জন্য যথেষ্ট প্রয়োগ হয় না। লেখার সময়, শুধুমাত্র ভিএস ২০১২ এর একটি বিটা সংস্করণ উপলব্ধ ছিল তবে সি ++ 11 এর জন্য এর সমর্থন ইতিমধ্যে এই বিষয়ে যথেষ্ট ভাল ছিল।


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

@ গীতা: এই উদাহরণস্বরূপ, কোডটিকে দূরে অপ্টিমাইজ করা রোধ calc1করতে একটি floatযুক্তি নিতে পারে যা পূর্ববর্তী পুনরাবৃত্তির ফলাফল হতে পারে। কিছু একটা x = calc1(x, [](float arg){ return arg * 0.5f; });। তদ্ব্যতীত, আমাদের অবশ্যই এটি calc1ব্যবহার নিশ্চিত করতে হবে x। তবে, এটি এখনও যথেষ্ট নয়। আমাদের একটি পার্শ্ব প্রতিক্রিয়া তৈরি করতে হবে। উদাহরণস্বরূপ, পরিমাপের পরে, xস্ক্রিনে মুদ্রণ । তবুও, আমি সম্মত হই যে টাইমগ পরিমাপের জন্য খেলনা কোডগুলি ব্যবহার করা সর্বদা আসল / উত্পাদন কোডের সাথে কী ঘটতে চলেছে তার একটি নিখুঁত ইঙ্গিত দিতে পারে না।
ক্যাসিও নেরি

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

দুর্দান্ত উত্তর। ২ টি জিনিস: std::reference_wrapper(টেমপ্লেটদের জোর করে দেওয়ার জন্য এটি বৈধ ব্যবহারের দুর্দান্ত উদাহরণ ; এটি কেবলমাত্র সাধারণ স্টোরেজের জন্য নয়), এবং ভিসির অপ্টিমাইজারটি খালি লুপটি বাতিল করতে ব্যর্থ হয় তা দেখতে মজার বিষয় ... আমি এই জিসিসি বাগ পুনরায়volatile লক্ষ্য করেছি ।
আন্ডারস্কোর_ডি

37

ঝাঁকুনির সাথে দুজনের মধ্যে পারফরম্যান্সের কোনও পার্থক্য নেই

ঝনঝন (3.2, ট্রাঙ্ক 166872) (লিনাক্স -O2) ব্যবহার করে, দুটি ক্ষেত্রে বাইনারিগুলি একই রকম

- আমি পোস্টের শেষে ঝাঁকুনিতে ফিরে আসব। তবে প্রথমে, জিসিসি ৪.7.২:

ইতিমধ্যে প্রচুর অন্তর্দৃষ্টি চলছে, তবে আমি উল্লেখ করতে চাই যে ক্যালক 1 এবং ক্যালক 2 এর গণনার ফলাফল একই নয়, ইন-আস্তরণ ইত্যাদির কারণে উদাহরণস্বরূপ সমস্ত ফলাফলের যোগফলের তুলনা করুন:

float result=0;
for (int i = 0; i < 1e8; ++i) {
  result+=calc2([](float arg){ return arg * 0.5f; });
}

ক্যালক 2 দিয়ে যা হয়ে যায়

1.71799e+10, time spent 0.14 sec

যখন ক্যালক 1 দিয়ে এটি হয়ে যায়

6.6435e+10, time spent 5.772 sec

এটি গতির পার্থক্যে ~ 40 এর গুণক এবং মানগুলিতে 4 ডলার factor প্রথমটি ওপি পোস্ট করেছে (ভিজ্যুয়াল স্টুডিও ব্যবহার করে) তার চেয়ে অনেক বড় পার্থক্য। প্রকৃতপক্ষে মানটির শেষ প্রান্তে প্রিন্ট করাও কোনও দৃশ্যমান ফলাফল (যেমন নিয়ম হিসাবে) ছাড়াই কোড অপসারণ করতে সংকলককে প্রতিরোধ করা ভাল ধারণা। ক্যাসিও নেরি ইতিমধ্যে তার উত্তরে এটি বলেছিলেন। ফলাফলগুলি কতটা পৃথক রয়েছে তা দ্রষ্টব্য - কোডের গতির কারণগুলির সাথে তুলনামূলকভাবে বিভিন্ন গণনা সম্পাদনের সময় সতর্ক থাকা উচিত।

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

যদি আমি ব্যবহারকারীর ক্যালক 1 এবং 2 তে সরবরাহ করা মান যুক্তি যুক্ত করি তবে ক্যালক 1 এবং ক্যালক 2 এর মধ্যে গতির গুণক 40 থেকে 5 এর ফ্যাক্টারে নেমে আসে! ভিজ্যুয়াল স্টুডিওতে পার্থক্য 2 এর একটি ফ্যাক্টরের কাছাকাছি এবং ঝনঝনির সাথে কোনও পার্থক্য নেই (নীচে দেখুন)।

এছাড়াও, গুণগুলি দ্রুত হওয়ায়, ধীরগতির কারণগুলির বিষয়ে কথা বলা প্রায়শই আকর্ষণীয় হয় না। আরও আকর্ষণীয় প্রশ্ন হ'ল, আপনার ফাংশনগুলি কতটা ছোট এবং এগুলি কী কোনও বাস্তব প্রোগ্রামে বাধা?

ঝনঝন:

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

কার্যকর করতে সেকেন্ডের সংখ্যা (3 টির মধ্যে সেরা):

clang:        calc1:           1.4 seconds
clang:        calc2:           1.4 seconds (identical binary)

gcc 4.7.2:    calc1:           1.1 seconds
gcc 4.7.2:    calc2:           6.0 seconds

VS2012 CTPNov calc1:           0.8 seconds 
VS2012 CTPNov calc2:           2.0 seconds 

VS2015 (14.0.23.107) calc1:    1.1 seconds 
VS2015 (14.0.23.107) calc2:    1.5 seconds 

MinGW (4.7.2) calc1:           0.9 seconds
MinGW (4.7.2) calc2:          20.5 seconds 

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

আমার পরিবর্তিত পরীক্ষার কোড:

#include <functional>
#include <chrono>
#include <iostream>

template <typename F>
float calc1(F f, float x) { 
  return 1.0f + 0.002*x+f(x*1.223) ; 
}

float calc2(std::function<float(float)> f,float x) { 
  return 1.0f + 0.002*x+f(x*1.223) ; 
}

int main() {
    using namespace std::chrono;

    const auto tp1 = high_resolution_clock::now();

    float result=0;
    for (int i = 0; i < 1e8; ++i) {
      result=calc1([](float arg){ 
          return arg * 0.5f; 
        },result);
    }
    const auto tp2 = high_resolution_clock::now();

    const auto d = duration_cast<milliseconds>(tp2 - tp1);  
    std::cout << d.count() << std::endl;
    std::cout << result<< std::endl;
    return 0;
}

হালনাগাদ:

Vs2015 যুক্ত হয়েছে। আমি আরও লক্ষ্য করেছি যে ক্যালক 1, ক্যালক 2 এ ডাবল-> ফ্লোট রূপান্তর রয়েছে। তাদের অপসারণ ভিজ্যুয়াল স্টুডিওর উপসংহারে পরিবর্তন করে না (উভয়ই অনেক দ্রুত তবে অনুপাত প্রায় একই)।


8
যা যুক্তিযুক্তভাবে দেখায় যে বেঞ্চমার্কটি ভুল। আইএমএইচও আকর্ষণীয় ব্যবহারের ক্ষেত্রে হ'ল কলিং কোডটি অন্য কোথাও থেকে কোনও ফাংশন অবজেক্ট গ্রহণ করে, তাই সংকলক কল সংকলন করার সময় std :: ফাংশনের মূল জানতে পারে না। এখানে, সংকলক সঠিকভাবে কল 2 ইনলাইনটিকে প্রসারিত করে স্ট্যান্ড :: ফাংশনের সংকলনটি জানে main সিপিতে ক্যালক 2 'এক্সটার্ন' তৈরি করে সহজেই ঠিক করা হয়েছে। উত্স ফাইল। তারপরে আপনি আপেল ডাব্লু / কমলার তুলনা করছেন; ক্যালক 2 এমন কিছু করছে যা ক্যালক 1 পারছে না। এবং, লুপটি ক্যালকের অভ্যন্তরে থাকতে পারে (বহু কলকে চ); ফাংশন অবজেক্টের কর্টারের কাছাকাছি নয়।
গ্রেগো 15

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

বর্ণিত সেটআপের সাথে নতুন উত্তর যুক্ত হয়েছে।
গ্রেগো 5:41

3
বিটিডাব্লু বেঞ্চমার্কটি ভুল নয়, প্রশ্ন ("স্টাড :: ফাংশন বনাম টেম্পলেট") কেবল একই সংকলনের ইউনিটের ক্ষেত্রে বৈধ। আপনি যদি ফাংশনটিকে অন্য ইউনিটে নিয়ে যান, টেমপ্লেট আর সম্ভব নয়, তাই এর সাথে তুলনা করার মতো কিছুই নেই।
রুস্টিক্স

13

ভিন্ন এক নয়।

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

void eval(const std::function<int(int)>& f) {
    std::cout << f(3);
}

int f1(int i) {
    return i;
}

float f2(double d) {
    return d;
}

int main() {
    std::function<int(int)> fun(f1);
    eval(fun);
    fun = f2;
    eval(fun);
    return 0;
}

মনে রাখবেন যে একই ফাংশন অবজেক্ট,, funউভয় কলকে দেওয়া হচ্ছে eval। এটি দুটি পৃথক ফাংশন ধারণ করে।

আপনার যদি এটি করার দরকার না হয় তবে আপনার ব্যবহার করা উচিত নয়std::function


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

8

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


6

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

স্টাড :: ফাংশন প্রক্রিয়াটি যা সরবরাহ করে তার জন্য স্বীকৃত হওয়া উচিত: যে কোনও কলযোগ্য সত্তা উপযুক্ত স্বাক্ষরের একটি স্ট্যান্ড :: ফাংশনে রূপান্তরিত হতে পারে। ধরুন আপনার কাছে এমন একটি লাইব্রেরি রয়েছে যা z = f (x, y) দ্বারা সংজ্ঞায়িত কোনও ফাংশনের সাথে একটি পৃষ্ঠের সাথে ফিট করে, আপনি এটি গ্রহণ করতে লিখতে পারেনstd::function<double(double,double)> , এবং গ্রন্থাগারের ব্যবহারকারী যে কোনও কলযোগ্য সত্তাকে সহজেই রূপান্তর করতে পারবেন; এটি কোনও সাধারণ ক্রিয়াকলাপ, শ্রেণীর উদাহরণের পদ্ধতি, বা ল্যাম্বডা বা স্ট্যান্ড :: বাইন্ড দ্বারা সমর্থিত যে কোনও কিছু হোক।

টেমপ্লেটের পদ্ধতির থেকে পৃথক, এটি বিভিন্ন ক্ষেত্রে লাইব্রেরির ফাংশনটি পুনরায় কম্পাইল না করে কাজ করে; তদনুসারে, প্রতিটি অতিরিক্ত ক্ষেত্রে সামান্য অতিরিক্ত সংকলিত কোড প্রয়োজন। এটি ঘটানো সর্বদা সম্ভব ছিল তবে এটিতে কিছু বিশ্রী ব্যবস্থা প্রয়োজন হত এবং লাইব্রেরির ব্যবহারকারীর সম্ভবত এটির কাজটি করার জন্য তাদের ফাংশনটির চারপাশে একটি অ্যাডাপ্টার তৈরি করা প্রয়োজন। std :: ফাংশন একটি সাধারণ পেতে অ্যাডাপ্টারের যা প্রয়োজন তা স্বয়ংক্রিয়ভাবে তৈরি করে সমস্ত ক্ষেত্রে রানটাইম কল ইন্টারফেস করে, যা একটি নতুন এবং খুব শক্তিশালী বৈশিষ্ট্য।

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

আমি নীচে পরীক্ষা করেছিলাম, ওপি'র মতো; তবে প্রধান পরিবর্তনগুলি হ'ল:

  1. প্রতিটি কেস 1 বিলিয়ন বার লুপ করে তবে স্টাড :: ফাংশন অবজেক্টগুলি কেবল একবারই নির্মিত হয়। আমি আউটপুট কোডটি দেখে দেখেছি যে আসল স্টাডি :: ফাংশন কল নির্মাণের সময় 'অপারেটর নতুন' ডাকে optim
  2. অনাকাঙ্ক্ষিত অপ্টিমাইজেশন প্রতিরোধের জন্য পরীক্ষা দুটি ফাইলে বিভক্ত
  3. আমার কেসগুলি হ'ল: (ক) ফাংশনটি ইনলাইনড (খ) ফাংশনটি একটি সাধারণ ফাংশন পয়েন্টার (সি) ফাংশনটি স্ট্যান্ড :: ফাংশন (ডি) ফাংশনটি একটি স্ট্যান্ড :: সাথে সামঞ্জস্যপূর্ণ একটি বেমানান ফাংশন হিসাবে আবৃত একটি সামঞ্জস্যপূর্ণ ফাংশন) বাঁধাই, স্ট্যান্ড :: ফাংশন হিসাবে মোড়ানো

আমি যে ফলাফল পেয়েছি তা হ'ল:

  • কেস (ক) (ইনলাইন) 1.3 এনসিএস

  • অন্যান্য সমস্ত ক্ষেত্রে: 3.3 এনএসসি।

কেস (ডি) সামান্য ধীর হতে থাকে তবে পার্থক্য (প্রায় 0.05 এনএসসি) শব্দে শোষিত হয়।

উপসংহারটি হল যে স্ট্যান্ড :: ফাংশনটি ফাংশন পয়েন্টারটি ব্যবহার করার সাথে ওভারহেডের (কল সময়ে) তুলনাযোগ্য, এমনকি যখন আসল ফাংশনে সহজ 'বাঁধাই' অভিযোজন থাকে when ইনলাইনটি অন্যদের চেয়ে 2 এনএস দ্রুততর তবে এটি একটি প্রত্যাশিত ট্রেড অফ since কারণ ইনলাইনটি কেবলমাত্র রান টাইমে 'হার্ড-ওয়্যার্ড' case

আমি যখন একই মেশিনে জোহান-লুন্ডবার্গের কোডটি চালাচ্ছি, তখন আমি লুপ প্রতি প্রায় 39 এনসিএস দেখতে পাচ্ছি, তবে সেখানে লুপে আরও অনেক কিছু রয়েছে, স্ট্যান্ড :: ফাংশনের প্রকৃত নির্মাতা এবং ডেস্ট্রাক্টর সহ, যা সম্ভবত বেশ উচ্চতর যেহেতু এটিতে একটি নতুন এবং মুছুন জড়িত।

-ও 2 জিসিসি 4.8.1, থেকে x86_64 লক্ষ্য (কোর আই 5)।

দ্রষ্টব্য, সংকলকটি যেখানে ডাকা হয় তার ফাংশনগুলি প্রসারিত করা থেকে বিরত রাখতে কোডটি দুটি ফাইলে বিভক্ত হয়ে গেছে (কেবল যেখানে এটির উদ্দেশ্যে তৈরি হয়েছে সেখানে বাদে)।

----- প্রথম উত্স ফাইল --------------

#include <functional>


// simple funct
float func_half( float x ) { return x * 0.5; }

// func we can bind
float mul_by( float x, float scale ) { return x * scale; }

//
// func to call another func a zillion times.
//
float test_stdfunc( std::function<float(float)> const & func, int nloops ) {
    float x = 1.0;
    float y = 0.0;
    for(int i =0; i < nloops; i++ ){
        y += x;
        x = func(x);
    }
    return y;
}

// same thing with a function pointer
float test_funcptr( float (*func)(float), int nloops ) {
    float x = 1.0;
    float y = 0.0;
    for(int i =0; i < nloops; i++ ){
        y += x;
        x = func(x);
    }
    return y;
}

// same thing with inline function
float test_inline(  int nloops ) {
    float x = 1.0;
    float y = 0.0;
    for(int i =0; i < nloops; i++ ){
        y += x;
        x = func_half(x);
    }
    return y;
}

----- দ্বিতীয় উত্স ফাইল -------------

#include <iostream>
#include <functional>
#include <chrono>

extern float func_half( float x );
extern float mul_by( float x, float scale );
extern float test_inline(  int nloops );
extern float test_stdfunc( std::function<float(float)> const & func, int nloops );
extern float test_funcptr( float (*func)(float), int nloops );

int main() {
    using namespace std::chrono;


    for(int icase = 0; icase < 4; icase ++ ){
        const auto tp1 = system_clock::now();

        float result;
        switch( icase ){
         case 0:
            result = test_inline( 1e9);
            break;
         case 1:
            result = test_funcptr( func_half, 1e9);
            break;
         case 2:
            result = test_stdfunc( func_half, 1e9);
            break;
         case 3:
            result = test_stdfunc( std::bind( mul_by, std::placeholders::_1, 0.5), 1e9);
            break;
        }
        const auto tp2 = high_resolution_clock::now();

        const auto d = duration_cast<milliseconds>(tp2 - tp1);  
        std::cout << d.count() << std::endl;
        std::cout << result<< std::endl;
    }
    return 0;
}

আগ্রহীদের জন্য, এখানে 'মুল_বি'কে একটি ভাসা (ভাসা) এর মতো দেখতে তৈরি করা সংযোজকটি এখানে তৈরি করা হয়েছে - বাঁধন (mul_by, _1,0.5) হিসাবে তৈরি ফাংশনটি যখন বলা হয় তখন এটিকে বলা হয়:

movq    (%rdi), %rax                ; get the std::func data
movsd   8(%rax), %xmm1              ; get the bound value (0.5)
movq    (%rax), %rdx                ; get the function to call (mul_by)
cvtpd2ps    %xmm1, %xmm1        ; convert 0.5 to 0.5f
jmp *%rdx                       ; jump to the func

(সুতরাং আমি যদি বাইন্ডে 0.5f লিখে থাকি তবে এটি কিছুটা দ্রুত হতে পারে ...) নোট করুন যে 'x' প্যারামিটারটি% xmm0 এ আসে এবং কেবল সেখানেই থাকে।

টেস্ট_স্টেডফুনক কল করার আগে, ফাংশনটি তৈরি করা হয়েছে সেই অঞ্চলে এখানে কোডটি দেওয়া হয়েছে - সি ++ ফিল্টের মাধ্যমে চালানো:

movl    $16, %edi
movq    $0, 32(%rsp)
call    operator new(unsigned long)      ; get 16 bytes for std::function
movsd   .LC0(%rip), %xmm1                ; get 0.5
leaq    16(%rsp), %rdi                   ; (1st parm to test_stdfunc) 
movq    mul_by(float, float), (%rax)     ; store &mul_by  in std::function
movl    $1000000000, %esi                ; (2nd parm to test_stdfunc)
movsd   %xmm1, 8(%rax)                   ; store 0.5 in std::function
movq    %rax, 16(%rsp)                   ; save ptr to allocated mem

   ;; the next two ops store pointers to generated code related to the std::function.
   ;; the first one points to the adaptor I showed above.

movq    std::_Function_handler<float (float), std::_Bind<float (*(std::_Placeholder<1>, double))(float, float)> >::_M_invoke(std::_Any_data const&, float), 40(%rsp)
movq    std::_Function_base::_Base_manager<std::_Bind<float (*(std::_Placeholder<1>, double))(float, float)> >::_M_manager(std::_Any_data&, std::_Any_data const&, std::_Manager_operation), 32(%rsp)


call    test_stdfunc(std::function<float (float)> const&, int)

1
ঝনঝন 3.4.1 x64 এর সাথে ফলাফলগুলি: (ক) 1.0, (খ) 0.95, (সি) 2.0, (ডি) 5.0।
rustyx

4

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

template <typename F>
float calc1(F f, float i) { return -1.0f * f(i) + 666.0f; }
float calc2(std::function<float(float)> f, float i) { return -1.0f * f(i) + 666.0f; }
int main() {
    const auto tp1 = system_clock::now();
    for (int i = 0; i < 1e8; ++i) {
        t += calc2([&](float arg){ return arg * 0.5f + t; }, i);
    }
    const auto tp2 = high_resolution_clock::now();
}

কোডটিতে এই পরিবর্তনটি দিয়েছি আমি gcc 4.8 -O3 দিয়ে সংকলন করেছি এবং ক্যালক 1 এর জন্য 330ms এবং ক্যালক 2 এর জন্য 2702 এর সময় পেয়েছি। সুতরাং টেমপ্লেটটি ব্যবহার করা 8 গুন দ্রুত ছিল, এই সংখ্যাটি আমার কাছে সন্দেহজনক বলে মনে হয়েছিল, 8 টির শক্তির গতি প্রায়শই নির্দেশ করে যে সংকলকটি কিছু ভেক্টরাইজ করেছে। আমি যখন টেমপ্লেটগুলির সংস্করণটির জন্য উত্পন্ন কোডটি দেখলাম তখন এটি স্পষ্টভাবে ভেক্টোরাইজড ছিল

.L34:
cvtsi2ss        %edx, %xmm0
addl    $1, %edx
movaps  %xmm3, %xmm5
mulss   %xmm4, %xmm0
addss   %xmm1, %xmm0
subss   %xmm0, %xmm5
movaps  %xmm5, %xmm0
addss   %xmm1, %xmm0
cvtsi2sd        %edx, %xmm1
ucomisd %xmm1, %xmm2
ja      .L37
movss   %xmm0, 16(%rsp)

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

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

float calc3(float i) {  return -1.0f * f2(i) + 666.0f; }
std::function<float(float)> f2 = [](float arg){ return arg * 0.5f; };

int main() {
    const auto tp1 = system_clock::now();
    for (int i = 0; i < 1e8; ++i) {
        t += calc3([&](float arg){ return arg * 0.5f + t; }, i);
    }
    const auto tp2 = high_resolution_clock::now();
}

এই সংস্করণটির সাথে আমরা দেখতে পাচ্ছি যে সংকলকটি এখন একইভাবে কোডটিকে ভেক্টরাইজ করেছে এবং আমি একই বেঞ্চমার্কের ফলাফল পেয়েছি।

  • টেমপ্লেট: 330ms
  • std :: ফাংশন: 2702ms
  • গ্লোবাল স্টাড :: ফাংশন: 330 মিমি

সুতরাং আমার উপসংহারটি একটি স্টেডিয়ামের কাঁচা গতি :: ফাংশন বনাম একটি টেম্পলেট ফান্টারের চেয়ে প্রায় একই। তবে এটি অপ্টিমাইজারের কাজটিকে আরও অনেক কঠিন করে তোলে।


1
পুরো পয়েন্টটি প্যারামিটার হিসাবে কোনও ফান্টারকে পাস করা। আপনার calc3কেস কোন অর্থ দেয় না; ক্যালকা 3 এখন f2 কল করতে হার্ডকডড। অবশ্যই এটি অনুকূলিত করা যেতে পারে।
rustyx

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