সি ++ তে, কোনও ক্রিয়াকলাপ থেকে কোনও ভেক্টর ফেরত দেওয়া কি এখনও খারাপ অভ্যাস?


103

সংক্ষিপ্ত সংস্করণ: প্রচুর প্রোগ্রামিং ভাষায় বৃহত অবজেক্টগুলি যেমন ভেক্টর / অ্যারে — ফিরিয়ে দেওয়া সাধারণ। এই স্টাইলটি কি এখন C ++ 0x এ গ্রহণযোগ্য যদি শ্রেণিতে কোনও চালিকা নির্মাতা থাকে, বা সি ++ প্রোগ্রামাররা এটিকে অদ্ভুত / কুৎসিত / জঘন্য বলে বিবেচনা করে?

দীর্ঘ সংস্করণ: C ++ 0x এ এটি এখনও খারাপ ফর্ম হিসাবে বিবেচনা করা হয়?

std::vector<std::string> BuildLargeVector();
...
std::vector<std::string> v = BuildLargeVector();

Traditionalতিহ্যবাহী সংস্করণটি এর মতো দেখাবে:

void BuildLargeVector(std::vector<std::string>& result);
...
std::vector<std::string> v;
BuildLargeVector(v);

নতুন সংস্করণে, মানটি থেকে ফিরে এসেছে BuildLargeVector একটি মূল্যমান, সুতরাং v এর সরানো কনস্ট্রাক্টর ব্যবহার করে নির্মিত হবেstd::vector (এন) আরভিও সংঘটিত হয় না বলে ধরে নেওয়া, এর চলন নির্মাতা ।

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

সম্পাদনা করুন : প্রশ্নটি সত্যই অপ্টিমাইজেশন সম্পর্কিত নয়। প্রদর্শিত উভয় ফর্ম রিয়েল-ওয়ার্ল্ড প্রোগ্রামগুলিতে কাছাকাছি-অভিন্ন অভিনয় রয়েছে। অন্যদিকে, অতীতে, প্রথম ফর্মটির ক্রম-শৃঙ্খলার ক্রম খারাপ ছিল order ফলস্বরূপ প্রথম ফর্মটি দীর্ঘকাল ধরে সি ++ প্রোগ্রামিংয়ের একটি প্রধান কোড গন্ধ ছিল। আর নেই, আশা করি?


18
কে কখনও বলেছিল যে এটি খারাপ ফর্মটি দিয়ে শুরু করা উচিত?
এডওয়ার্ড স্ট্রেঞ্জ

7
এটি অবশ্যই "পুরানো দিনগুলিতে" একটি খারাপ কোড গন্ধ ছিল, যা আমি এখান থেকে এসেছি। :-)
নট

1
আমি নিশ্চিত তাই হবে! আমি পাস-বাই-মান আরও জনপ্রিয় হয়ে উঠতে দেখতে চাই। :)
সেলিবিটজে

উত্তর:


73

ডেভ আব্রাহামসের মান / গতি ফেরানোর গতি সম্পর্কে একটি বিস্তৃত বিশ্লেষণ রয়েছে ।

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


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

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

13
@ সিগটর্ম, অনেক কিছুই যা সংকলকটি করার প্রয়োজন হয় না, তবে আপনি ধরে নিই যে এটি যাইহোক হয়। কম্পাইলার পরিবর্তন করার জন্য "প্রয়োজনীয়" হয় না x / 2করতে x >> 1জন্য intগুলি, কিন্তু আপনি অনুমান এটা করবে। মানকটি রেফারেন্সগুলি বাস্তবায়নের জন্য কীভাবে সংকলকগুলির প্রয়োজন তা সম্পর্কে কিছুই বলে না, তবে আপনি ধরে নেন যে পয়েন্টারগুলি ব্যবহার করে সেগুলি দক্ষতার সাথে পরিচালিত হয়। মানটি ভি-টেবিলগুলি সম্পর্কে কিছুই বলে না, সুতরাং আপনি নিশ্চিত হতে পারবেন না যে ভার্চুয়াল ফাংশন কলগুলিও দক্ষ efficient মূলত, আপনাকে অনেক সময় সংকলকটিতে কিছুটা বিশ্বাস রাখা দরকার।
পিটার আলেকজান্ডার

16
@ সিগ: আপনার প্রোগ্রামের আসল আউটপুট ব্যতীত খুব সামান্যই গ্যারান্টিযুক্ত। যদি আপনি সময়ের 100% সময় ঘটতে চলেছে সে সম্পর্কে যদি 100% নিশ্চিততা চান তবে আপনি আলাদা ভাষায় সরে যাওয়ার চেয়ে ভাল।
ডেনিস জিকিফুজ

6
@ সিগটাইম: আমি "আসল কেস দৃশ্যে" কাজ করি। আমি সংকলকটি কী করে তা পরীক্ষা করে দেখি work কোনও "ধীরে ধীরে কাজ করতে পারে" নেই। এটি কেবল ধীর গতিতে কাজ করে না কারণ সংকলকটি আরভিও বাস্তবায়ন করে, মানকটির প্রয়োজন হয় কিনা whether কোনও আইএফ, বুট বা মায়বস নেই, এটি কেবল সাধারণ ঘটনা।
পিটার আলেকজান্ডার

37

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

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


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

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

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

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

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

18

সংক্ষেপটি হ'ল:

অনুলিপি বিলোপ এবং RVO করতে "ভীতিকর কপি" এড়ানো (কম্পাইলার এই অপ্টিমাইজেশন বাস্তবায়ন প্রয়োজন হয় না, এবং কিছু পরিস্থিতিতে এটা প্রয়োগ করা যাবে না)

সি ++ 0 এক্স আরভ্যালু রেফারেন্সগুলি স্ট্রিং / ভেক্টর বাস্তবায়নের অনুমতি দেয় যা এটির গ্যারান্টি দেয়

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

দুর্ভাগ্যক্রমে, এটি আপনার ইন্টারফেসগুলিতে বড় প্রভাব ফেলে। যদি সি ++ 0 এক্স বিকল্প না হয় এবং আপনার গ্যারান্টি দরকার হয় তবে আপনি পরিবর্তে রেফারেন্স-কাউন্ট বা কপিরাইট-অন-রাইটিং অবজেক্টগুলি কিছু পরিস্থিতিতে ব্যবহার করতে পারেন। যদিও তাদের মাল্টিথ্রেডিংয়ের সাথে ডাউনসাইড রয়েছে।

(আমি চাই সি ++ তে কেবল একটি উত্তর সহজ এবং সোজা এবং শর্ত ছাড়াই হবে)।


11

আসলে, সি ++ 11 সাল থেকে, অনুলিপি করার জন্য ব্যয়std::vector বেশিরভাগ ক্ষেত্রেই ব্যয় হয়েছে।

তবে, আপনার মনে রাখতে হবে যে নতুন ভেক্টর তৈরির জন্য ব্যয় (তারপরে এটি ধ্বংস করে দেওয়া) এখনও বিদ্যমান এবং যখন আপনি ভেক্টরের সক্ষমতা পুনরায় ব্যবহার করতে চান তখন মান দ্বারা প্রত্যাবর্তনের পরিবর্তে আউটপুট প্যারামিটারগুলি ব্যবহার করা কার্যকর। এটি C ++ কোর গাইডলাইনগুলির F.20 এ ব্যতিক্রম হিসাবে নথিভুক্ত করা হয়েছে ।

আসুন তুলনা করা যাক:

std::vector<int> BuildLargeVector1(size_t vecSize) {
    return std::vector<int>(vecSize, 1);
}

সঙ্গে:

void BuildLargeVector2(/*out*/ std::vector<int>& v, size_t vecSize) {
    v.assign(vecSize, 1);
}

এখন ধরুন, আমাদের এই পদ্ধতিগুলিকে numIterএকটি শক্ত লুপে কল করতে এবং কিছু পদক্ষেপ নেওয়া দরকার need উদাহরণস্বরূপ, আসুন সমস্ত উপাদানগুলির যোগফল গণনা করা যাক।

ব্যবহার করে BuildLargeVector1, আপনি কি করবেন:

size_t sum1 = 0;
for (int i = 0; i < numIter; ++i) {
    std::vector<int> v = BuildLargeVector1(vecSize);
    sum1 = std::accumulate(v.begin(), v.end(), sum1);
}

ব্যবহার করে BuildLargeVector2, আপনি কি করবেন:

size_t sum2 = 0;
std::vector<int> v;
for (int i = 0; i < numIter; ++i) {
    BuildLargeVector2(/*out*/ v, vecSize);
    sum2 = std::accumulate(v.begin(), v.end(), sum2);
}

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

মাপকাঠি

মান সঙ্গে আসুন খেলা vecSizeএবং numIter। আমরা ভিসি সাইজ * নামিটার অবিচ্ছিন্ন রাখব যাতে "তত্ত্ব অনুসারে" একই সময় নেওয়া উচিত (= ঠিক একই মান সহ একই সংখ্যক অ্যাসাইনমেন্ট এবং সংযোজন রয়েছে), এবং সময়ের পার্থক্য কেবলমাত্র ব্যয় থেকে আসতে পারে বরাদ্দ, ডিলোকেশন এবং ক্যাশেটির আরও ভাল ব্যবহার।

আরও সুনির্দিষ্টভাবে বলা যাক ভ্যাকসাইজ * নামিটার = 2 ^ 31 = 2147483648 ব্যবহার করুন, কারণ আমার 16 গিগাবাইট র‌্যাম রয়েছে এবং এই সংখ্যাটি নিশ্চিত করে যে 8 গিগাবাইটের বেশি আর বরাদ্দ করা হয়নি (আকারের (ইনট) = 4), আমি নিশ্চিত করেছিলাম যে আমি ডিস্কে অদলবদল করছি না ( অন্যান্য সমস্ত প্রোগ্রাম বন্ধ ছিল, পরীক্ষা চালানোর সময় আমার কাছে 15GB ডলার উপলব্ধ ছিল)।

কোডটি এখানে:

#include <chrono>
#include <iomanip>
#include <iostream>
#include <numeric>
#include <vector>

class Timer {
    using clock = std::chrono::steady_clock;
    using seconds = std::chrono::duration<double>;
    clock::time_point t_;

public:
    void tic() { t_ = clock::now(); }
    double toc() const { return seconds(clock::now() - t_).count(); }
};

std::vector<int> BuildLargeVector1(size_t vecSize) {
    return std::vector<int>(vecSize, 1);
}

void BuildLargeVector2(/*out*/ std::vector<int>& v, size_t vecSize) {
    v.assign(vecSize, 1);
}

int main() {
    Timer t;

    size_t vecSize = size_t(1) << 31;
    size_t numIter = 1;

    std::cout << std::setw(10) << "vecSize" << ", "
              << std::setw(10) << "numIter" << ", "
              << std::setw(10) << "time1" << ", "
              << std::setw(10) << "time2" << ", "
              << std::setw(10) << "sum1" << ", "
              << std::setw(10) << "sum2" << "\n";

    while (vecSize > 0) {

        t.tic();
        size_t sum1 = 0;
        {
            for (int i = 0; i < numIter; ++i) {
                std::vector<int> v = BuildLargeVector1(vecSize);
                sum1 = std::accumulate(v.begin(), v.end(), sum1);
            }
        }
        double time1 = t.toc();

        t.tic();
        size_t sum2 = 0;
        {
            std::vector<int> v;
            for (int i = 0; i < numIter; ++i) {
                BuildLargeVector2(/*out*/ v, vecSize);
                sum2 = std::accumulate(v.begin(), v.end(), sum2);
            }
        } // deallocate v
        double time2 = t.toc();

        std::cout << std::setw(10) << vecSize << ", "
                  << std::setw(10) << numIter << ", "
                  << std::setw(10) << std::fixed << time1 << ", "
                  << std::setw(10) << std::fixed << time2 << ", "
                  << std::setw(10) << sum1 << ", "
                  << std::setw(10) << sum2 << "\n";

        vecSize /= 2;
        numIter *= 2;
    }

    return 0;
}

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

$ g++ -std=c++11 -O3 main.cpp && ./a.out
   vecSize,    numIter,      time1,      time2,       sum1,       sum2
2147483648,          1,   2.360384,   2.356355, 2147483648, 2147483648
1073741824,          2,   2.365807,   1.732609, 2147483648, 2147483648
 536870912,          4,   2.373231,   1.420104, 2147483648, 2147483648
 268435456,          8,   2.383480,   1.261789, 2147483648, 2147483648
 134217728,         16,   2.395904,   1.179340, 2147483648, 2147483648
  67108864,         32,   2.408513,   1.131662, 2147483648, 2147483648
  33554432,         64,   2.416114,   1.097719, 2147483648, 2147483648
  16777216,        128,   2.431061,   1.060238, 2147483648, 2147483648
   8388608,        256,   2.448200,   0.998743, 2147483648, 2147483648
   4194304,        512,   0.884540,   0.875196, 2147483648, 2147483648
   2097152,       1024,   0.712911,   0.716124, 2147483648, 2147483648
   1048576,       2048,   0.552157,   0.603028, 2147483648, 2147483648
    524288,       4096,   0.549749,   0.602881, 2147483648, 2147483648
    262144,       8192,   0.547767,   0.604248, 2147483648, 2147483648
    131072,      16384,   0.537548,   0.603802, 2147483648, 2147483648
     65536,      32768,   0.524037,   0.600768, 2147483648, 2147483648
     32768,      65536,   0.526727,   0.598521, 2147483648, 2147483648
     16384,     131072,   0.515227,   0.599254, 2147483648, 2147483648
      8192,     262144,   0.540541,   0.600642, 2147483648, 2147483648
      4096,     524288,   0.495638,   0.603396, 2147483648, 2147483648
      2048,    1048576,   0.512905,   0.609594, 2147483648, 2147483648
      1024,    2097152,   0.548257,   0.622393, 2147483648, 2147483648
       512,    4194304,   0.616906,   0.647442, 2147483648, 2147483648
       256,    8388608,   0.571628,   0.629563, 2147483648, 2147483648
       128,   16777216,   0.846666,   0.657051, 2147483648, 2147483648
        64,   33554432,   0.853286,   0.724897, 2147483648, 2147483648
        32,   67108864,   1.232520,   0.851337, 2147483648, 2147483648
        16,  134217728,   1.982755,   1.079628, 2147483648, 2147483648
         8,  268435456,   3.483588,   1.673199, 2147483648, 2147483648
         4,  536870912,   5.724022,   2.150334, 2147483648, 2147483648
         2, 1073741824,  10.285453,   3.583777, 2147483648, 2147483648
         1, 2147483648,  20.552860,   6.214054, 2147483648, 2147483648

বেঞ্চমার্ক ফলাফল

(ইন্টেল আই 7-7700 কে @ 4.20GHz; 16 জিবি ডিডিআর 4 2400 মেগাহার্টজ; কুবুন্টু 18.04)

স্বরলিপি: আমার প্ল্যাটফর্মের স্মৃতি (v) = v.size () * সাইজফ (ইনট) = v.size () * 4।

অবাক হওয়ার মতো বিষয় নয়, যখন numIter = 1(যেমন, মেম (ভি) = 8 গিগাবাইট), সময়গুলি পুরোপুরি অভিন্ন। প্রকৃতপক্ষে, উভয় ক্ষেত্রেই আমরা একবার মাত্র 8 জিবি মেমরির বিশাল ভেক্টর বরাদ্দ করি। এটি আরও প্রমাণ করে যে বিল্ডলেজভেক্টর 1 () ব্যবহার করার সময় কোনও অনুলিপি ঘটেনি: আমার কাছে অনুলিপি করার মতো পর্যাপ্ত র‍্যাম নেই!

যখন numIter = 2, দ্বিতীয় ভেক্টরকে পুনরায় বরাদ্দকরণের পরিবর্তে ভেক্টরের সক্ষমতা পুনরায় ব্যবহার করা দ্রুততর হয় 1.37x is

যখন numIter = 256, ভেক্টর সক্ষমতা পুনরুদ্ধার করুন (বার বার 256 বার একটি ভেক্টর বরাদ্দ / বরাদ্দ দেওয়ার পরিবর্তে) 2.45x দ্রুত হয় :)

আমরা লক্ষ্য করতে পারে time1 থেকে প্রায় কাছাকাছি ধ্রুবক numIter = 1থেকে numIter = 256, যার মানে 8GB এক বিশাল ভেক্টর বণ্টন প্রায় কাছাকাছি 32MB এর 256 ভেক্টর বণ্টন হিসেবে ব্যয়বহুল। যাইহোক, 8 গিগাবাইটের একটি বিশাল ভেক্টর বরাদ্দ করা অবশ্যই 32MB এর একটি ভেক্টর বরাদ্দের চেয়ে বেশি ব্যয়বহুল, সুতরাং ভেক্টরের ক্ষমতা পুনরায় ব্যবহারের ফলে কর্মক্ষমতা লাভ হয়।

থেকে numIter = 512(Mem (উ) = 16MB) এর numIter = 8M(Mem (উ) = 1KB) মিষ্টি স্পট: উভয় পদ্ধতি হিসাবে দ্রুত ঠিক হয়, এবং দ্রুত numIter এবং vecSize অন্যান্য সব সমন্বয় থাকে। এটি সম্ভবত আমার প্রসেসরের L3 ক্যাশে আকার 8MB এর সাথে করা উচিত, যাতে ভেক্টরটি পুরোপুরি ক্যাশে পুরোপুরি ফিট করে। হঠাত্ লাফিয়ে time1মেমো (v) = 16MB এর জন্য কেন আমি সত্যিই তা ব্যাখ্যা করছি না , যখন মেমো (ভি) = 8 এমবি হবে তার ঠিক পরে ঘটবে এটি আরও যুক্তিযুক্ত মনে হবে। লক্ষ্য করুন যে আশ্চর্যরূপে, এই মিষ্টি স্পটে, পুনরায় ব্যবহারের ক্ষমতাটি না করা আসলে কিছুটা দ্রুত! আমি সত্যিই এই ব্যাখ্যা না।

যখন numIter > 8Mজিনিসগুলি কুৎসিত হতে শুরু করে। উভয় পদ্ধতি ধীর হয়ে যায় তবে মান অনুসারে ভেক্টরটি ফেরানো আরও ধীর হয়ে যায়। সবচেয়ে খারাপ ক্ষেত্রে, কেবলমাত্র একটি intভ্যাক্টর সহ একটি ভেক্টর , মান দ্বারা প্রত্যাবর্তনের পরিবর্তে পুনরায় ব্যবহারের ক্ষমতাটি দ্রুত 3.3x। সম্ভবতঃ, এটি ম্যালোক () এর স্থির ব্যয়ের কারণে হয়ে থাকে যা আধিপত্য শুরু করে।

টাইম 2 এর জন্য বক্ররেখা টাইম 1 এর বক্ররের তুলনায় কীভাবে মসৃণ তা নোট করুন: কেবলমাত্র ভেক্টর ক্ষমতা পুনরায় ব্যবহার করা সাধারণত দ্রুত হয় না, তবে সম্ভবত আরও গুরুত্বপূর্ণ এটি আরও অনুমানযোগ্য

এছাড়াও নোট করুন যে মিষ্টি স্পটে আমরা ~ 0.5 সেকেন্ডে bit৪ বিট পূর্ণসংখ্যার ২ বিলিয়ন সংযোজন করতে পেরেছি, যা ৪.২ গিগাহার্টজ bit৪ বিট প্রসেসরের উপর বেশ অনুকূল। সমস্ত 8 টি কোর ব্যবহারের জন্য আমরা গণনার সমান্তরাল করে আরও ভাল করতে পারি (উপরের পরীক্ষাটি একবারে একটি কোর ব্যবহার করে, যা আমি সিপিইউ ব্যবহারের উপর নজরদারি করার সময় পরীক্ষাটি পুনরায় চালনার মাধ্যমে যাচাই করেছি)। মেম (ভি) = 16 কেবি, যখন এল 1 ক্যাশে (i7-7700K এর জন্য L1 ডেটা ক্যাশে 4x32kB হয়) এর ক্রমগুলির ক্রম হিসাবে সর্বাধিক সম্পাদন সম্পাদিত হয় is

অবশ্যই, পার্থক্যগুলি কম এবং কম প্রাসঙ্গিক হয়ে উঠবে যত বেশি সংখ্যক আপনাকে ডেটাতে করতে হবে utation নিচের ফলাফল যদি আমরা প্রতিস্থাপন হয় sum = std::accumulate(v.begin(), v.end(), sum);দ্বারা for (int k : v) sum += std::sqrt(2.0*k);:

বেঞ্চমার্ক 2

উপসংহার

  1. মান দ্বারা প্রত্যাবর্তনের পরিবর্তে আউটপুট প্যারামিটারগুলি ব্যবহার করে পুনরায় ব্যবহারের ক্ষমতা দ্বারা পারফরম্যান্স লাভ সরবরাহ করতে পারে।
  2. একটি আধুনিক ডেস্কটপ কম্পিউটারে, এটি কেবলমাত্র বড় ভেক্টর (> 16 এমবি) এবং ছোট ভেক্টর (<1 কেবি) এর জন্য প্রযোজ্য বলে মনে হয়।
  3. কয়েক মিলিয়ন / বিলিয়ন ছোট ভেক্টর (<1 কেবি) বরাদ্দ এড়িয়ে চলুন। যদি সম্ভব হয় তবে পুনরায় ব্যবহার ক্ষমতা, বা আরও ভাল, আপনার আর্কিটেকচারটি আলাদাভাবে ডিজাইন করুন।

অন্যান্য প্ল্যাটফর্মগুলিতে ফলাফলগুলি পৃথক হতে পারে। যথারীতি, যদি পারফরম্যান্সের বিষয়টি গুরুত্বপূর্ণ হয় তবে আপনার নির্দিষ্ট ব্যবহারের ক্ষেত্রে মানদণ্ড লিখুন।


6

আমি এখনও এটি একটি খারাপ অনুশীলন বলে মনে করি তবে এটি লক্ষণীয় যে আমার দলটি এমএসভিসি ২০০৮ এবং জিসিসি ৪.১ ব্যবহার করে, তাই আমরা সর্বশেষতম সংকলকগুলি ব্যবহার করছি না।

এর আগে এমএসভিসি ২০০৮-এর সাথে ভিটিউনে প্রদর্শিত হটস্পটগুলি স্ট্রিং অনুলিপিটিতে নেমে আসে। আমাদের মতো কোড ছিল:

String Something::id() const
{
    return valid() ? m_id: "";
}

... নোট করুন যে আমরা আমাদের নিজস্ব স্ট্রিং প্রকারটি ব্যবহার করেছি (এটি প্রয়োজনীয় ছিল কারণ আমরা একটি সফ্টওয়্যার ডেভলপমেন্ট কিট সরবরাহ করছি যেখানে প্লাগইন লেখকরা বিভিন্ন সংকলক ব্যবহার করতে পারেন এবং সেইজন্য স্ট্যান্ড :: স্ট্রিং / এসটিডি :: wstring এর বেমানান বাস্তবায়ন)।

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

আমি যে পরিবর্তনটি করেছি তা সহজ ছিল:

static String null_string;
const String& Something::id() const
{
    return valid() ? m_id: null_string;
}

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

উপসংহার: আমরা নিখুঁত সর্বশেষ সংকলকগুলি ব্যবহার করছি না, তবে আমরা এখনও নির্ভরযোগ্যভাবে মান দ্বারা প্রত্যাবর্তনের জন্য অনুলিপিটি অপসারণের সংস্থাপকটির উপর নির্ভর করে বলে মনে করতে পারি না (অন্তত সমস্ত ক্ষেত্রে নয়)। এমএসভিসি ২০১০ এর মতো নতুন সংকলক যারা ব্যবহার করছেন তাদের ক্ষেত্রে এটি নাও হতে পারে I'm মান দ্বারা ক্লাস।

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


3
এটি জিনিস: আরভিও সংকলক-নির্ভর, তবে একটি সি ++ 0 এক্স সংকলক অবশ্যই আরভিও ব্যবহার না করার সিদ্ধান্ত নেওয়ার ক্ষেত্রে অবশ্যই মুভ সেমেন্টিক্স ব্যবহার করতে হবে (ধরে নেওয়া যায় যে কোনও মুভ কনস্ট্রাক্টর রয়েছে)। ট্রাইগ্রাফ অপারেটর ব্যবহার করে আরভিওকে পরাস্ত করে। দেখুন cpp-next.com/archive/2009/09/move-it-with-rvalue-references যা পিটার পরিচিত। তবে আপনার উদাহরণ যাইহোক শব্দার্থবিজ্ঞানের জন্য উপযুক্ত নয় কারণ আপনি কোনও অস্থায়ী ফিরিয়ে দিচ্ছেন না।
নট

@ স্টিংকি ৪72২: মান অনুসারে সদস্য ফিরিয়ে দেওয়া সবসময়ই রেফারেন্সের চেয়ে ধীর হয়ে যায়। মূল সদস্যের রেফারেন্স ফিরিয়ে দেওয়ার তুলনায় মূল্য রেফারেন্সগুলি এখনও ধীর হবে (যদি কলার একটি অনুলিপি প্রয়োজনের পরিবর্তে একটি রেফারেন্স নিতে পারেন)। এছাড়াও, এখনও অনেক বার আছে যেগুলি আপনি মূল্য সংরক্ষণের রেফারেন্সগুলিতে সংরক্ষণ করতে পারেন, কারণ আপনার প্রসঙ্গ রয়েছে। উদাহরণস্বরূপ, আপনি স্ট্রিং নতুন স্ট্রিং করতে পারেন; newstring.resize (string1.size () + string2.size () + ...); newstring + = স্ট্রিং 1; newstring + = স্ট্রিং 2; ইত্যাদি। এটি এখনও মূল্যের তুলনায় যথেষ্ট পরিমাণে সঞ্চয়।
পপি

@ ডেডএমজি বাইনারি অপারেটর + এমনকি সি ++ 0 এক্স সংকলক আরভিও বাস্তবায়ন করে কি না? যদি তা হয় তবে তা লজ্জাজনক। তারপরে আবার সেই মাক্সেস ইন্দ্রিয়টি যেহেতু আমরা এখনও কনটেনেটেড স্ট্রিং গণনার জন্য একটি অস্থায়ী তৈরি করতে পেরেছি যেখানে + = সরাসরি নিউস্ট্রিংয়ের সাথে সংমিশ্রণ করতে পারে।
stinky472

যেমন একটি কেস সম্পর্কে: স্ট্রিং newstr = str1 + str2; সংকলন প্রয়োগকারী পদক্ষেপ শব্দার্থকগুলিতে, এটির মতো মনে হয় এটির চেয়ে দ্রুত বা আরও দ্রুত হওয়া উচিত: স্ট্রিং নিউস্ট্রার; newstr + = str1; newstr + = str2; কোনও রিজার্ভ নেই, তাই কথা বলার জন্য (আমি ধরে নিচ্ছি যে আপনি আপনাকে পুনরায় আকার দেওয়ার পরিবর্তে রিজার্ভ বলতে চাইছেন)।
stinky472

5
@Nate: আমার মনে হয় আপনি বিভ্রান্তিকর হয় trigraphs মত <::বা ??!সঙ্গে শর্তসাপেক্ষ অপারেটর ?: (কখনও কখনও বলা তিন অপারেটর )।
ফ্রেডওভারফ্লো

3

শুধু কিছুটা নিটপিক করতে: অনেক প্রোগ্রামিং ভাষায় ফাংশন থেকে অ্যারে ফিরিয়ে দেওয়া সাধারণ নয়। তাদের বেশিরভাগ ক্ষেত্রে অ্যারের একটি রেফারেন্স ফিরে আসে। সি ++ এ, নিকটতম উপমাটি ফিরে আসবেboost::shared_array


4
@ বিলি: স্টাডি :: ভেক্টর কপির শব্দার্থবিজ্ঞানের সাথে একটি মান ধরণের। বর্তমান সি ++ স্ট্যান্ডার্ড কোনও গ্যারান্টি দেয় না যে (এন) আরভিও কখনও প্রয়োগ হয় এবং বাস্তবে এমনটি হয় না যখন বাস্তব জীবনে ঘটে থাকে।
নেমানজা ত্রিফুনোভিচ

3
@ বিলি: আবারও কিছু বাস্তব পরিস্থিতি রয়েছে যেখানে সর্বশেষতম সংকলকরা এনআরভিও প্রয়োগ করে না: efnetcpp.org/wiki/Return_value_optimization# নাম_আরভিও
নেমানজা ত্রিফুনোভিচ

3
@ বিলি ওনিল: 99% যথেষ্ট নয়, আপনার 100% প্রয়োজন। মারফির আইন - "যদি কিছু ভুল হতে পারে তবে তা হয়ে যাবে"। আপনি যদি কোনও ধরণের ফাজি যুক্তি নিয়ে কাজ করেন তবে অনিশ্চয়তা ঠিক আছে তবে traditionalতিহ্যবাহী সফ্টওয়্যারটি লেখার পক্ষে এটি ভাল ধারণা নয়। যদি এমন 1% সম্ভাবনাও থাকে যে কোডটি আপনার মনে হয় এমনভাবে কাজ করে না, তবে আপনার এই প্রত্যাশা করা উচিত যে কোডটি আপনাকে বরখাস্ত করে দেবে এমন সমালোচনামূলক ত্রুটি প্রবর্তন করবে। প্লাস এটি মানক বৈশিষ্ট্য নয়। অননুমোদিত বৈশিষ্ট্যগুলি ব্যবহার করা একটি খারাপ ধারণা - যদি জানা এক বছরের মধ্যে সংকলকটি বৈশিষ্ট্যটি বাদ দেয় (এটি স্ট্যান্ডার্ডের প্রয়োজন হয় না , তাই না?), আপনি সমস্যার মধ্যে একজন হবেন।
সিগটার্ম

4
@ সিগটার্ম: আমরা যদি আচরণের সঠিকতার বিষয়ে কথা বলি তবে আমি আপনার সাথে একমত হব। তবে, আমরা একটি পারফরম্যান্স অপটিমাইজেশন সম্পর্কে কথা বলছি। 100% এরও কম নিশ্চিততার সাথে এই জাতীয় জিনিসগুলি ভাল।
বিলি ওনিল

2
@ নিমঞ্জা: আমি দেখতে পাচ্ছি না এখানে কি "নির্ভর" করা হচ্ছে। আরভিও বা এনআরভিও ব্যবহার করা হয় না কেন আপনার অ্যাপ্লিকেশনটি একই রকম চলে। সেগুলি যদিও ব্যবহৃত হয়, এটি দ্রুত চালিত হবে। যদি আপনার অ্যাপ্লিকেশনটি কোনও নির্দিষ্ট প্ল্যাটফর্মে খুব মন্থর হয়ে থাকে এবং আপনি মান অনুলিপি ফেরত পেতে এটিটিকে আবার সন্ধান করেন তবে তা সর্বদাই পরিবর্তন করুন, তবে এটি যে পরিবর্তনের মানটি এখনও ব্যবহার করতে পারে তা সেরা পরিবর্তন করে না। আপনার যদি একেবারে কোনও অনুলিপি না ঘটে তা নিশ্চিত করার দরকার হয় একটিতে ভেক্টরটি মুড়ে shared_ptrদিন এবং এটি একটি দিন কল করুন।
বিলি ওনিল

2

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


1
সরানো কনস্ট্রাক্টর যুক্ত করা হওয়ায় এনআরভিও চলে যায় না।
বিলি ওনিল

1
বিলি, সত্য তবে অপ্রাসঙ্গিক, প্রশ্নটি ছিল সি ++ 0x সেরা পদ্ধতিগুলি পরিবর্তন করেছে এবং এনআরভিও সি ++ 0x এর কারণে পরিবর্তিত হয়নি
মোটি
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.