সি ++ তে স্টেটমেন্ট অর্ডার প্রয়োগ করা হচ্ছে


111

ধরুন আমার কাছে বেশ কয়েকটি বিবৃতি রয়েছে যা আমি একটি নির্দিষ্ট ক্রমে কার্যকর করতে চাই। আমি অপ্টিমাইজেশন স্তর 2 সহ জি ++ ব্যবহার করতে চাই, যাতে কিছু বিবৃতি পুনরায় সাজানো যায়। কোনও কিসের কাছে বিবৃতিগুলির একটি নির্দিষ্ট ক্রম প্রয়োগ করতে হবে?

নিম্নলিখিত উদাহরণ বিবেচনা করুন।

using Clock = std::chrono::high_resolution_clock;

auto t1 = Clock::now(); // Statement 1
foo();                  // Statement 2
auto t2 = Clock::now(); // Statement 3

auto elapsedTime = t2 - t1;

এই উদাহরণে এটি গুরুত্বপূর্ণ যে বিবৃতিগুলি 1-3 প্রদত্ত ক্রমে কার্যকর করা হয়। তবে, সংকলক কি ভাবতে পারে না যে স্টেটমেন্ট 2টি 1 এবং 3 এর চেয়ে আলাদা এবং নীচে কোডটি কার্যকর করতে পারে?

using Clock=std::chrono::high_resolution_clock;

foo();                  // Statement 2
auto t1 = Clock::now(); // Statement 1
auto t2 = Clock::now(); // Statement 3

auto elapsedTime = t2 - t1;

34
কম্পাইলার যদি মনে করে যে তারা না থাকলে তারা স্বাধীন the
ডেভিড শোয়ার্জ


1
__sync_synchronize()কোন সাহায্য হতে পারে?
বনাম

3
@ হাওয়ার্ডহিন্যান্ট: স্ট্যান্ডার্ড সি এর শব্দার্থক ক্ষমতাটি উন্নত হবে যদি এই জাতীয় নির্দেশকে সংজ্ঞায়িত করা হয়, এবং যদি এলিয়াসিং বিধিগুলি পূর্বে লেখা হয়েছিল এমন ডেটা বাধার পরে সম্পাদিত পাঠকে ছাড়ের সাথে সামঞ্জস্য করা হত।
সুপারক্যাট

4
@ ডেভিডশওয়ার্টজ এই ক্ষেত্রে সময়টি পরিমাপ করতে প্রায় সময় fooলাগে যা সংকলকটিকে পুনরায় অর্ডার করার সময় উপেক্ষা করতে দেওয়া হয় ঠিক যেমন এটি কোনও ভিন্ন থ্রেড থেকে পর্যবেক্ষণকে উপেক্ষা করার অনুমতি দেয়।
কোডসইনচওস

উত্তর:


100

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

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

আমরা এটি প্রতিরোধের চেষ্টা করতে পারি, তবে এটির চূড়ান্ত নেতিবাচক ফলাফল থাকতে পারে এবং শেষ পর্যন্ত ব্যর্থ হবে।

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

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

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

তবে এই সমস্ত কিছুর কারণে আপনার আশা হারাতে হবে না। আপনি যখন মৌলিক গাণিতিক ক্রিয়াকলাপের সময় প্রয়োগ করতে চান, আমরা নির্ভরযোগ্যতার সাথে কার্যকর কৌশলগুলি অধ্যয়ন করেছি। সাধারণত এগুলি মাইক্রো-বেঞ্চমার্কিংয়ের সময় ব্যবহৃত হয় । CppCon2015 এ আমি এই বিষয়ে একটি কথা বলেছি: https://youtu.be/nXaxk27zwlk

সেখানে প্রদর্শিত কৌশলগুলি গুগলের মতো বিভিন্ন মাইক্রো-বেঞ্চমার্ক লাইব্রেরি দ্বারা সরবরাহ করা হয়েছে: https://github.com/google/benchmark#preventing-optimization

এই কৌশলগুলির মূল বিষয় হ'ল ডেটাতে ফোকাস করা। আপনি অপটিমাইজারের কাছে গণনাটিকে অস্বচ্ছ এবং ইনপুটটিকে অপ্টিমাইজারের কাছে গণনাটির অস্বচ্ছ করতে পারেন। একবার আপনি এটি করেনি, আপনি এটি নির্ভরযোগ্যভাবে সময় করতে পারেন। আসুন আসল প্রশ্নে উদাহরণের একটি বাস্তব সংস্করণটি দেখুন fooতবে বাস্তবায়নের জন্য সম্পূর্ণরূপে দৃশ্যমান সংজ্ঞা দিয়ে । আমি DoNotOptimizeগুগল বেনমার্ক লাইব্রেরি থেকে একটি (বহনযোগ্য) সংস্করণও বের করেছি যা আপনি এখানে পেতে পারেন: https://github.com/google/benchmark/blob/master/incolve/benchmark/benchmark_api.h#L208

#include <chrono>

template <class T>
__attribute__((always_inline)) inline void DoNotOptimize(const T &value) {
  asm volatile("" : "+m"(const_cast<T &>(value)));
}

// The compiler has full knowledge of the implementation.
static int foo(int x) { return x * 2; }

auto time_foo() {
  using Clock = std::chrono::high_resolution_clock;

  auto input = 42;

  auto t1 = Clock::now();         // Statement 1
  DoNotOptimize(input);
  auto output = foo(input);       // Statement 2
  DoNotOptimize(output);
  auto t2 = Clock::now();         // Statement 3

  return t2 - t1;
}

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

% ./bin/clang++ -std=c++14 -c -S -o - so.cpp -O3
        .text
        .file   "so.cpp"
        .globl  _Z8time_foov
        .p2align        4, 0x90
        .type   _Z8time_foov,@function
_Z8time_foov:                           # @_Z8time_foov
        .cfi_startproc
# BB#0:                                 # %entry
        pushq   %rbx
.Ltmp0:
        .cfi_def_cfa_offset 16
        subq    $16, %rsp
.Ltmp1:
        .cfi_def_cfa_offset 32
.Ltmp2:
        .cfi_offset %rbx, -16
        movl    $42, 8(%rsp)
        callq   _ZNSt6chrono3_V212system_clock3nowEv
        movq    %rax, %rbx
        #APP
        #NO_APP
        movl    8(%rsp), %eax
        addl    %eax, %eax              # This is "foo"!
        movl    %eax, 12(%rsp)
        #APP
        #NO_APP
        callq   _ZNSt6chrono3_V212system_clock3nowEv
        subq    %rbx, %rax
        addq    $16, %rsp
        popq    %rbx
        retq
.Lfunc_end0:
        .size   _Z8time_foov, .Lfunc_end0-_Z8time_foov
        .cfi_endproc


        .ident  "clang version 3.9.0 (trunk 273389) (llvm/trunk 273380)"
        .section        ".note.GNU-stack","",@progbits

এখানে আপনি সংকলকটি foo(input)একক নির্দেশকে কলটি অনুকূল করে তুলতে পারবেন addl %eax, %eax, কিন্তু সময়টির বাইরে না সরিয়ে বা অবিচ্ছিন্ন ইনপুট সত্ত্বেও এটি সম্পূর্ণরূপে অপসারণ না করে।

আশা করি এটি সহায়তা করে এবং সি ++ স্ট্যান্ডার্ড কমিটি DoNotOptimizeএখানকার অনুরূপ এপিআইগুলিকে মানক করার সম্ভাবনা দেখছে ।


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

5
যদিও আমি বুঝতে পেরেছি যে এটি কীভাবে ফু কে পুরোপুরি অপ্টিমাইজ হওয়া থেকে বাধা দেয়, আপনি কি কিছুটা ব্যাখ্যা করতে পারেন যে এই কলগুলিকে Clock::now()foo () এর সাথে পুনরায় সাজানো থেকে কেন বাধা দেয় ? অপ্টিমাইজারটিকে কি ধরে নিতে হবে DoNotOptimizeএবং Clock::now()অ্যাক্সেস থাকতে পারে এবং কিছু সাধারণ গ্লোবাল রাষ্ট্র পরিবর্তন করতে পারে যা ঘুরেফিরে তাদেরকে ইন-আউটপুটে বাঁধতে পারে? বা আপনি অপ্টিমাইজারের প্রয়োগের কিছু বর্তমান সীমাবদ্ধতার উপর নির্ভর করছেন?
মাইক এমবি

2
DoNotOptimizeএই উদাহরণে একটি সিনথেটিকভাবে "পর্যবেক্ষণযোগ্য" ইভেন্ট। এটি মনে হয় এটি ইনপুটটির প্রতিনিধিত্ব করে কিছু টার্মিনালে দৃশ্যমান আউটপুট প্রিন্ট করে। যেহেতু ঘড়িটি পড়াও পর্যবেক্ষণযোগ্য (আপনি সময় পার করছেন তা পর্যবেক্ষণ করছেন) প্রোগ্রামটির পর্যবেক্ষণযোগ্য আচরণ পরিবর্তন না করে এগুলি পুনরায় অর্ডার করা যাবে না।
শ্যান্ডলার কারুথ

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

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

59

সারসংক্ষেপ:

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

আসল উত্তর:

জিসিসি -O2 অপ্টিমাইজেশনের আওতায় কলগুলি পুনঃক্রম করে:

#include <chrono>
static int foo(int x)    // 'static' or not here doesn't affect ordering.
{
    return x*2;
}
int fred(int x)
{
    auto t1 = std::chrono::high_resolution_clock::now();
    int y = foo(x);
    auto t2 = std::chrono::high_resolution_clock::now();
    return y;
}

জিসিসি 5.3.0:

g++ -S --std=c++11 -O0 fred.cpp :

_ZL3fooi:
        pushq   %rbp
        movq    %rsp, %rbp
        movl    %ecx, 16(%rbp)
        movl    16(%rbp), %eax
        addl    %eax, %eax
        popq    %rbp
        ret
_Z4fredi:
        pushq   %rbp
        movq    %rsp, %rbp
        subq    $64, %rsp
        movl    %ecx, 16(%rbp)
        call    _ZNSt6chrono3_V212system_clock3nowEv
        movq    %rax, -16(%rbp)
        movl    16(%rbp), %ecx
        call    _ZL3fooi
        movl    %eax, -4(%rbp)
        call    _ZNSt6chrono3_V212system_clock3nowEv
        movq    %rax, -32(%rbp)
        movl    -4(%rbp), %eax
        addq    $64, %rsp
        popq    %rbp
        ret

কিন্তু:

g++ -S --std=c++11 -O2 fred.cpp :

_Z4fredi:
        pushq   %rbx
        subq    $32, %rsp
        movl    %ecx, %ebx
        call    _ZNSt6chrono3_V212system_clock3nowEv
        call    _ZNSt6chrono3_V212system_clock3nowEv
        leal    (%rbx,%rbx), %eax
        addq    $32, %rsp
        popq    %rbx
        ret

বাহ্যিক ফাংশন হিসাবে এখন foo () সহ:

#include <chrono>
int foo(int x);
int fred(int x)
{
    auto t1 = std::chrono::high_resolution_clock::now();
    int y = foo(x);
    auto t2 = std::chrono::high_resolution_clock::now();
    return y;
}

g++ -S --std=c++11 -O2 fred.cpp :

_Z4fredi:
        pushq   %rbx
        subq    $32, %rsp
        movl    %ecx, %ebx
        call    _ZNSt6chrono3_V212system_clock3nowEv
        movl    %ebx, %ecx
        call    _Z3fooi
        movl    %eax, %ebx
        call    _ZNSt6chrono3_V212system_clock3nowEv
        movl    %ebx, %eax
        addq    $32, %rsp
        popq    %rbx
        ret

কিন্তু, যদি এটি -ফ্ল্টোর সাথে সংযুক্ত থাকে (লিঙ্ক-টাইম অপ্টিমাইজেশন):

0000000100401710 <main>:
   100401710:   53                      push   %rbx
   100401711:   48 83 ec 20             sub    $0x20,%rsp
   100401715:   89 cb                   mov    %ecx,%ebx
   100401717:   e8 e4 ff ff ff          callq  100401700 <__main>
   10040171c:   e8 bf f9 ff ff          callq  1004010e0 <_ZNSt6chrono3_V212system_clock3nowEv>
   100401721:   e8 ba f9 ff ff          callq  1004010e0 <_ZNSt6chrono3_V212system_clock3nowEv>
   100401726:   8d 04 1b                lea    (%rbx,%rbx,1),%eax
   100401729:   48 83 c4 20             add    $0x20,%rsp
   10040172d:   5b                      pop    %rbx
   10040172e:   c3                      retq

3
এমএসভিসি এবং আইসিসিও তাই করে। কলং হ'ল একমাত্র এটি আসল ক্রমটি সংরক্ষণ করে।
কোডি গ্রে

3
আপনি কোথাও টি 1 এবং টি 2 ব্যবহার করেন না যাতে এটি ফলাফলটি ফেলে দেওয়া এবং কোডটি পুনঃক্রম করতে পারে বলে মনে করতে পারে
ফুক্লভিভি

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

3
@ লু ভেন ফ্যাক: কলগুলি নিজেরাই বন্ধ রাখেনি। আবার, আমি সন্দেহ এই কারণ কম্পাইলার তাদের পার্শ্ব প্রতিক্রিয়া হতে পারে জানি না - কিন্তু এটা নেই জানি যে ঐ পার্শ্ব প্রতিক্রিয়া foo বিন্যাস ব্যবহারের উপর প্রভাব বিস্তার করতে পারবে না ()।
জেরেমি

3
এবং একটি চূড়ান্ত নোট: নির্দিষ্টকরণ -ফ্ল্টো (লিঙ্ক-টাইম অপ্টিমাইজেশান) অন্যথায় পুনরায়-অর্ডার হওয়া ক্ষেত্রেও পুনরায় ক্রমাগত কারণ ঘটায়।
জেরেমি

20

পুনরায় ক্রম সংযোজক দ্বারা বা প্রসেসরের মাধ্যমে করা যেতে পারে।

বেশিরভাগ সংকলক পাঠক-লিখনের নির্দেশাবলীর পুনরায় ক্রম রোধ করতে একটি প্ল্যাটফর্ম-নির্দিষ্ট পদ্ধতি সরবরাহ করে। জিসিসি-তে, এটি

asm volatile("" ::: "memory");

( আরও তথ্য এখানে )

মনে রাখবেন যে এটি কেবল পরোক্ষভাবে পুনর্ব্যবহারের অপারেশনকে বাধা দেয়, যতক্ষণ না তারা পড়তে / লেখার উপর নির্ভর করে।

অনুশীলনে আমি এখনও এমন কোনও সিস্টেম দেখিনি যেখানে সিস্টেম কলটি Clock::now()যেমন বাধার মত একই প্রভাব ফেলে। আপনি নিশ্চিত হতে ফলাফলের সমাবেশটি পরিদর্শন করতে পারেন।

এটি অস্বাভাবিক কিছু নয় তবে পরীক্ষার অধীনে ফাংশনটি সংকলনের সময় মূল্যায়ন হয়। "বাস্তবসম্মত" কার্যকরকরণ প্রয়োগের জন্য, আপনাকে foo()I / O বা একটি পাঠকের কাছ থেকে ইনপুট সংগ্রহ করতে হতে পারে volatile


আরেকটি বিকল্প foo()হ'ল ইনলাইনিং অক্ষম করা - আবার, এটি সংকলক নির্দিষ্ট এবং সাধারণত বহনযোগ্য নয়, তবে একই প্রভাব থাকবে।

জিসিসিতে, এটি হবে __attribute__ ((noinline))


@ রুসলান একটি মৌলিক বিষয় নিয়ে এসেছে: এই পরিমাপটি কতটা বাস্তবসম্মত?

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

তুলনীয় সময় পেতে আমরা সাধারণত যা করি : তা নিশ্চিত করুন যে তারা কম ত্রুটির ব্যবধানে পুনরুত্পাদনযোগ্য । এটি তাদের কিছুটা কৃত্রিম করে তোলে।

"হট ক্যাশে" বনাম "কোল্ড ক্যাশে" এক্সিকিউশন পারফরম্যান্স সহজেই একটি মাত্রার ক্রম দ্বারা পৃথক হতে পারে - তবে বাস্তবে এটি কিছুটা অভ্যন্তরীণ হবে ("নমনীয়"?)


2
আপনার হ্যাক সহ asmটাইমার কলগুলির মধ্যে বিবৃতি কার্যকর করার সময়কে প্রভাবিত করে: মেমরি ক্লোবারের পরে কোডটি মেমরি থেকে সমস্ত ভেরিয়েবল পুনরায় লোড করতে হয়।
রুসলান

@ রুসলান: তাদের হ্যাক আমার নয়। শুদ্ধ করার বিভিন্ন স্তর রয়েছে এবং এর মতো কিছু করা পুনরুত্পাদনযোগ্য ফলাফলের জন্য অনিবার্য।
পিটারচেন

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

11

সি ++ ভাষা বিভিন্ন উপায়ে যা পর্যবেক্ষণযোগ্য তা নির্ধারণ করে।

যদি foo()পর্যবেক্ষণযোগ্য কিছু না করে তবে এটি সম্পূর্ণরূপে নির্মূল করা যেতে পারে। যদি foo()কেবল এমন একটি গণনা করে যা "স্থানীয়" রাজ্যে মান সঞ্চিত করে (এটি স্ট্যাকের উপর বা কোনও কোনও বস্তুতে থাকুক) এবং সংকলক প্রমাণ করতে পারে যে নিরাপদে প্রাপ্ত কোনও পয়েন্টার কোডটিতে প্রবেশ করতে পারে না Clock::now(), তবে এর কোনও পর্যবেক্ষণযোগ্য পরিণতি নেই Clock::now()কল মুভিং ।

যদি foo()কোনও ফাইল বা প্রদর্শনের সাথে ইন্টারঅ্যাক্ট করা হয়, এবং সংকলক প্রমাণ করতে Clock::now()পারে না যে ফাইল বা ডিসপ্লেটির সাথে ইন্টারঅ্যাক্ট করে না , তবে পুনরায় অর্ডারিং করা যায় না, কারণ কোনও ফাইল বা ডিসপ্লে সহ ইন্টারঅ্যাকশনটি পর্যবেক্ষণযোগ্য আচরণ।

আপনি কোডটি চারদিকে না ঘোরতে বাধ্য করার জন্য সংকলক-নির্দিষ্ট হ্যাকগুলি ব্যবহার করতে পারেন (ইনলাইন অ্যাসেমব্লির মতো), অন্য পদ্ধতির মধ্যে রয়েছে আপনার সংযোজকটিকে আউটসামার্ট করার চেষ্টা করা।

একটি গতিশীল লোড লাইব্রেরি তৈরি করুন। প্রশ্নযুক্ত কোডের আগে এটি লোড করুন।

এই গ্রন্থাগারটি একটি জিনিস প্রকাশ করে:

namespace details {
  void execute( void(*)(void*), void *);
}

এবং এটি এইভাবে আবৃত:

template<class F>
void execute( F f ) {
  struct bundle_t {
    F f;
  } bundle = {std::forward<F>(f)};

  auto tmp_f = [](void* ptr)->void {
    auto* pb = static_cast<bundle_t*>(ptr);
    (pb->f)();
  };
  details::execute( tmp_f, &bundle );
}

যা একটি নালারি ল্যাম্বডাকে প্যাক করে এবং ডায়ামিক লাইব্রেরিটিকে এমন প্রসঙ্গে চালানোর জন্য ব্যবহার করে যা সংকলক বুঝতে পারে না।

ডায়নামিক লাইব্রেরির ভিতরে আমরা করি:

void details::execute( void(*f)(void*), void *p) {
  f(p);
}

যা বেশ সহজ।

এখন কলগুলিকে পুনঃক্রম করতে execute, এটি অবশ্যই ডায়নামিক লাইব্রেরিটি বুঝতে হবে যা এটি আপনার পরীক্ষার কোডটি সংকলনের সময় করতে পারে না।

এটি এখনও foo()শূন্য পার্শ্ব প্রতিক্রিয়া সহ গুলি নির্মূল করতে পারে , তবে আপনি কিছু জিতেছেন, কিছু হারিয়েছেন।


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

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

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

4

না এটা পারে না। সি ++ স্ট্যান্ডার্ড অনুসারে [অন্তর্ভুক্তকরণ]:

14 একটি পূর্ণ-এক্সপ্রেশন সঙ্গে যুক্ত প্রতিটি মান গণনা এবং পার্শ্ব প্রতিক্রিয়া মূল্যায়ন করার জন্য পরবর্তী পূর্ণ এক্সপ্রেশন সঙ্গে যুক্ত প্রতিটি মান গণনা এবং পার্শ্ব প্রতিক্রিয়া আগে ক্রমযুক্ত।

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

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


12
যদিও এখনও
এমএম

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

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

3
আপনি কীভাবে সময়কে পরিমাপ করেন তার উপর নির্ভর করে। স্ট্যান্ডার্ড সি ++ তে কোডের কয়েকটি বডি কার্যকর করতে নেওয়া ঘড়ির চক্রের সংখ্যা পরিমাপ করা সম্ভব নয়।
পিটার

3
@ ডিবিএ আপনি কয়েকটি জিনিস একসাথে মিশ্রিত করছেন। লিঙ্কারটি আর উইন 16 অ্যাপ্লিকেশনগুলি তৈরি করতে পারে না, এটি যথেষ্ট সত্য, তবে এর কারণ তারা এই ধরণের বাইনারি তৈরির জন্য সমর্থন সরিয়ে ফেলেছে। WIn16 অ্যাপ্লিকেশনগুলি পিই ফর্ম্যাট ব্যবহার করে না। এটি বোঝায় না যে সংকলক বা লিংককারী উভয়েরই এপিআই ফাংশন সম্পর্কে বিশেষ জ্ঞান রয়েছে। অন্য সমস্যাটি রানটাইম লাইব্রেরির সাথে সম্পর্কিত। এনটি ৪ এ চলমান বাইনারি তৈরি করতে এমএসভিসির সর্বশেষতম সংস্করণ পাওয়া একেবারেই সমস্যা নেই I সিআরটি-তে লিঙ্ক দেওয়ার চেষ্টা করার সাথে সাথেই সমস্যাটি আসে, যা কল করে যে ফাংশনগুলি উপলব্ধ নেই।
কোডি গ্রে

2

না।

কখনও কখনও, "হিসাবে হিসাবে" বিধি দ্বারা, বিবৃতিগুলি পুনরায় অর্ডার করা যেতে পারে। এটি এ কারণে নয় যে তারা যৌক্তিকভাবে একে অপরের থেকে স্বতন্ত্র, তবে যে স্বাধীনতার ফলে প্রোগ্রামটির শব্দার্থবিজ্ঞান পরিবর্তন না করে এ জাতীয় পুনঃ-আদেশের অনুমতি দেওয়া হয়।

এমন একটি সিস্টেম কল সরানো যা বর্তমান সময়টি গ্রহণ করে স্পষ্টতই এই শর্তটি পূরণ করে না। একটি সংকলক যা জেনেশুনে বা অজান্তে এটি করে তা হ'ল অনুযোগী এবং সত্যই নির্বোধ।

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


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

4
@ রিভলবার_ ওসেলোট: আপনি যে সম্মত হোন বা না থাকুক না কেন প্রোগ্রামটির শব্দার্থক শব্দগুলি পরিবর্তন করে (ঠিক আছে, অনুলিপি কপির জন্য সংরক্ষণ করুন) সেই মানটির সাথে সম্মতিযুক্ত নয়।
অরবিট

6
এর তুচ্ছ যদি int x = 0; clock(); x = y*2; clock();আছে কোন জন্য সংজ্ঞায়িত উপায়ে clock()কোডের রাষ্ট্র সাথে যোগাযোগ করার জন্য x। সি ++ স্ট্যান্ডার্ডের অধীনে, এটি কী clock()করে তা জানতে হবে না - এটি স্ট্যাকটি পরীক্ষা করতে পারে (এবং গণনাটি ঘটেছিল তখন লক্ষ্য করুন), তবে এটি সি ++ এর সমস্যা নয়
ইয়াক্ক - অ্যাডাম নেভ্রামুমন্ট

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

1
.. আবার আলগাভাবে বলতে গেলে এটি কারণ যে কোনও গ্যারান্টি নেই যে আসল বাস্তবায়ন (বিমূর্ত মেশিন না হলেও) স্পষ্টতই y*yসিস্টেম কল করার আগে গণনা করবে না , কেবল মজাদার জন্য। এছাড়া যে কোন গ্যারান্টি প্রকৃত বাস্তবায়ন পরে যাই হোক না কেন সময়ে এই ফটকামূলক হিসাব ফল ব্যবহার করবে না হয় xব্যবহার করা হয়, তাই কল মধ্যে অসম্মতির clock()। একটি অন্তর্নিহিত ফাংশন যা-ই fooকরে, একই রকম হয় তবে শর্ত থাকে যে এর কোনও পার্শ্ব-প্রতিক্রিয়া নেই এবং পরিবর্তিত হতে পারে এমন রাজ্যের উপর নির্ভর করতে পারে না clock()
স্টিভ জেসোপ

0

noinline ফাংশন + ইনলাইন সমাবেশ ব্ল্যাক বক্স + সম্পূর্ণ ডেটা নির্ভরতা

এটি https://stackoverflow.com/a/38025837/895245 এর উপর ভিত্তি করে তবে কেন ::now()সেখানে পুনরায় অর্ডার করা যায় না তার কোনও স্পষ্ট যুক্তি আমি দেখতে পেলাম না , বরং আমি ভৌত ​​হয়ে থাকব এবং এটিকে একসাথে একটি নাইনলাইন ফাংশনটিতে রেখে দেব এ এস এম।

এইভাবে আমি নিশ্চিত যে পুনরায় অর্ডারিংটি ঘটতে পারে না, যেহেতু noinline" এবং " ::nowডেটা নির্ভরতা "সংযোগ" করে ।

main.cpp

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

// noinline ensures that the ::now() cannot be split from the __asm__
template <class T>
__attribute__((noinline)) auto get_clock(T& value) {
    // Make the compiler think we actually use / modify the value.
    // It can't "see" what is going on inside the assembly string.
    __asm__ __volatile__ ("" : "+g" (value));
    return std::chrono::high_resolution_clock::now();
}

template <class T>
static T foo(T niters) {
    T result = 42;
    for (T i = 0; i < niters; ++i) {
        result = (result * result) - (3 * result) + 1;
    }
    return result;
}

int main(int argc, char **argv) {
    unsigned long long input;
    if (argc > 1) {
        input = std::stoull(argv[1], NULL, 0);
    } else {
        input = 1;
    }

    // Must come before because it could modify input
    // which is passed as a reference.
    auto t1 = get_clock(input);
    auto output = foo(input);
    // Must come after as it could use the output.
    auto t2 = get_clock(output);
    std::cout << "output " << output << std::endl;
    std::cout << "time (ns) "
              << std::chrono::duration_cast<std::chrono::nanoseconds>(t2 - t1).count()
              << std::endl;
}

গিটহাব উজানের দিকে

সংকলন এবং চালান:

g++ -ggdb3 -O3 -std=c++14 -Wall -Wextra -pedantic -o main.out main.cpp
./main.out 1000
./main.out 10000
./main.out 100000

এই পদ্ধতির একমাত্র গৌণ ক্ষতি হ'ল আমরা callqকোনও inlineপদ্ধতির উপরে একটি অতিরিক্ত নির্দেশ যুক্ত করি । objdump -CDঅনুষ্ঠান mainরয়েছে:

    11b5:       e8 26 03 00 00          callq  14e0 <auto get_clock<unsigned long long>(unsigned long long&)>
    11ba:       48 8b 34 24             mov    (%rsp),%rsi
    11be:       48 89 c5                mov    %rax,%rbp
    11c1:       b8 2a 00 00 00          mov    $0x2a,%eax
    11c6:       48 85 f6                test   %rsi,%rsi
    11c9:       74 1a                   je     11e5 <main+0x65>
    11cb:       31 d2                   xor    %edx,%edx
    11cd:       0f 1f 00                nopl   (%rax)
    11d0:       48 8d 48 fd             lea    -0x3(%rax),%rcx
    11d4:       48 83 c2 01             add    $0x1,%rdx
    11d8:       48 0f af c1             imul   %rcx,%rax
    11dc:       48 83 c0 01             add    $0x1,%rax
    11e0:       48 39 d6                cmp    %rdx,%rsi
    11e3:       75 eb                   jne    11d0 <main+0x50>
    11e5:       48 89 df                mov    %rbx,%rdi
    11e8:       48 89 44 24 08          mov    %rax,0x8(%rsp)
    11ed:       e8 ee 02 00 00          callq  14e0 <auto get_clock<unsigned long long>(unsigned long long&)>

সুতরাং আমরা দেখতে পাই যে fooইনলাইনড ছিল তবে এটি get_clockছিল না এবং এটি চারপাশে ছিল।

get_clock নিজেই তবে অত্যন্ত দক্ষ, একটি একক পাত কল অপ্টিমাইজড নির্দেশনা সমন্বিত যা এমনকি স্ট্যাকটিকে স্পর্শ করে না:

00000000000014e0 <auto get_clock<unsigned long long>(unsigned long long&)>:
    14e0:       e9 5b fb ff ff          jmpq   1040 <std::chrono::_V2::system_clock::now()@plt>

যেহেতু ঘড়ির যথার্থতা নিজেই সীমাবদ্ধ তাই আমি মনে করি যে এটির অতিরিক্ত কোনওটির সময়কালের প্রভাবগুলি আপনি লক্ষ্য করতে সক্ষম হবেন না jmpq। নোট করুন callযেহেতু ::now()একটি ভাগ করা লাইব্রেরিতে রয়েছে সেহেতু একটি প্রয়োজন required

::now()ডেটা নির্ভরতার সাথে ইনলাইন অ্যাসেমব্লিং থেকে কল করুন

এটি সম্ভবত সবচেয়ে কার্যকর সমাধান হবে, jmpqউপরে বর্ণিত অতিরিক্তকেও কাটিয়ে ওঠা ।

দুর্ভাগ্যক্রমে এখানে প্রদর্শিত হিসাবে সঠিকভাবে করা অত্যন্ত চূড়ান্ত: প্রসারিতকে প্রসারিত ইনলাইন এএসএম-কল করা

যদি আপনার সময় পরিমাপ কল ছাড়াই সরাসরি ইনলাইনে সমাবেশে করা যায় তবে এই কৌশলটি ব্যবহার করা যেতে পারে। এটি উদাহরণস্বরূপ রত্ন 5 যাদু যন্ত্র নির্দেশাবলী , x86 আরডিটিএসসি (এটি আর প্রতিনিধি কিনা তা নিশ্চিত নয়) এবং সম্ভবত অন্যান্য পারফরম্যান্স কাউন্টার।

সম্পর্কিত থ্রেড:

GCC 8.3.0, উবুন্টু 19.04 দিয়ে পরীক্ষিত।


1
আপনি সাধারণত সঙ্গে একটি বিষ্ফোরণের / রিলোড বলপূর্বক প্রয়োজন হবে না "+m"ব্যবহার করে, "+r"অনেক কার্যকর উপায় কম্পাইলার একটি মান সত্যে পরিণত এবং তারপর অনুমান পরিবর্তনশীল পরিবর্তিত হয়েছে করা।
পিটার
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.