st + :: সি ++ 11 সক্ষম করার সময় ভেক্টর পারফরম্যান্স রিগ্রেশন


235

আমি যখন একটি ছোট সি ++ স্নিপেটে একটি আকর্ষণীয় পারফরম্যান্স রিগ্রেশন পেয়েছি, যখন আমি সি ++ 11 সক্ষম করি:

#include <vector>

struct Item
{
  int a;
  int b;
};

int main()
{
  const std::size_t num_items = 10000000;
  std::vector<Item> container;
  container.reserve(num_items);
  for (std::size_t i = 0; i < num_items; ++i) {
    container.push_back(Item());
  }
  return 0;
}

জি ++ (জিসিসি) 4.8.2 20131219 (প্রিরিলিজ) এবং সি ++ 03 সহ আমি পেয়েছি:

milian:/tmp$ g++ -O3 main.cpp && perf stat -r 10 ./a.out

Performance counter stats for './a.out' (10 runs):

        35.206824 task-clock                #    0.988 CPUs utilized            ( +-  1.23% )
                4 context-switches          #    0.116 K/sec                    ( +-  4.38% )
                0 cpu-migrations            #    0.006 K/sec                    ( +- 66.67% )
              849 page-faults               #    0.024 M/sec                    ( +-  6.02% )
       95,693,808 cycles                    #    2.718 GHz                      ( +-  1.14% ) [49.72%]
  <not supported> stalled-cycles-frontend 
  <not supported> stalled-cycles-backend  
       95,282,359 instructions              #    1.00  insns per cycle          ( +-  0.65% ) [75.27%]
       30,104,021 branches                  #  855.062 M/sec                    ( +-  0.87% ) [77.46%]
            6,038 branch-misses             #    0.02% of all branches          ( +- 25.73% ) [75.53%]

      0.035648729 seconds time elapsed                                          ( +-  1.22% )

অন্যদিকে C ++ 11 সক্ষম করে, কর্মক্ষমতা উল্লেখযোগ্যভাবে হ্রাস পায়:

milian:/tmp$ g++ -std=c++11 -O3 main.cpp && perf stat -r 10 ./a.out

Performance counter stats for './a.out' (10 runs):

        86.485313 task-clock                #    0.994 CPUs utilized            ( +-  0.50% )
                9 context-switches          #    0.104 K/sec                    ( +-  1.66% )
                2 cpu-migrations            #    0.017 K/sec                    ( +- 26.76% )
              798 page-faults               #    0.009 M/sec                    ( +-  8.54% )
      237,982,690 cycles                    #    2.752 GHz                      ( +-  0.41% ) [51.32%]
  <not supported> stalled-cycles-frontend 
  <not supported> stalled-cycles-backend  
      135,730,319 instructions              #    0.57  insns per cycle          ( +-  0.32% ) [75.77%]
       30,880,156 branches                  #  357.057 M/sec                    ( +-  0.25% ) [75.76%]
            4,188 branch-misses             #    0.01% of all branches          ( +-  7.59% ) [74.08%]

    0.087016724 seconds time elapsed                                          ( +-  0.50% )

কেউ এই ব্যাখ্যা করতে পারেন? এখনও অবধি আমার অভিজ্ঞতা ছিল যে এসটিএল সি ++ 11 সক্ষম করে সক্রিয় করে দ্রুত হয়। শব্দার্থবিজ্ঞান সরানোর জন্য ধন্যবাদ।

সম্পাদনা: প্রস্তাবিত হিসাবে, container.emplace_back();পরিবর্তে ব্যবহার করে পারফরম্যান্স সি ++ 03 সংস্করণের সাথে সমান হয়। সি ++ 03 সংস্করণটি কীভাবে একই অর্জন করতে পারে push_back?

milian:/tmp$ g++ -std=c++11 -O3 main.cpp && perf stat -r 10 ./a.out

Performance counter stats for './a.out' (10 runs):

        36.229348 task-clock                #    0.988 CPUs utilized            ( +-  0.81% )
                4 context-switches          #    0.116 K/sec                    ( +-  3.17% )
                1 cpu-migrations            #    0.017 K/sec                    ( +- 36.85% )
              798 page-faults               #    0.022 M/sec                    ( +-  8.54% )
       94,488,818 cycles                    #    2.608 GHz                      ( +-  1.11% ) [50.44%]
  <not supported> stalled-cycles-frontend 
  <not supported> stalled-cycles-backend  
       94,851,411 instructions              #    1.00  insns per cycle          ( +-  0.98% ) [75.22%]
       30,468,562 branches                  #  840.991 M/sec                    ( +-  1.07% ) [76.71%]
            2,723 branch-misses             #    0.01% of all branches          ( +-  9.84% ) [74.81%]

   0.036678068 seconds time elapsed                                          ( +-  0.80% )

1
আপনি যদি সমাবেশে সংকলন করেন তবে আপনি দেখতে পাবেন যে ফণাটির নীচে কী চলছে। আরও দেখুন stackoverflow.com/questions/8021874/...
Cogwheel

8
যদি আপনি পরিবর্তন কি হবে push_back(Item())থেকে emplace_back()সি ++ 11 সংস্করণে?
কগওহিল

8
উপরে দেখুন, যে "রিগ্রেশন" ঠিক করে "। আমি এখনও অবাক হয়েছি কেন পুশব্যাক সি ++ 03 এবং সি ++ 11 এর মধ্যে পারফরম্যান্সে কেন বিরত থাকে।
মিলিয়ান

1
@ মিলিয়ানআউট দেখা যাচ্ছে যে আমি ভুল প্রোগ্রামটি সংকলন করছি। আমার মন্তব্য উপেক্ষা করুন।

2
ক্ল্যাং ৩.৪ সহ সি ++ ১১ সংস্করণটি দ্রুততর, সি ++ 98 সংস্করণের জন্য 0.047s বনাম 0.058
প্রিটোরিয়ান

উত্তর:


247

আপনার পোস্টে আপনি যে বিকল্পগুলি লেখেন সেগুলি সহ আমি আমার ফলাফলগুলিতে আমার মেশিনে পুনরুত্পাদন করতে পারি।

তবে, আমি যদি লিংক টাইম অপ্টিমাইজেশনও সক্ষম করি (আমি -fltoপতাকাটি জিসিসি ৪.7.২ এও পাস করি), ফলাফলগুলি অভিন্ন:

(আমি আপনার মূল কোডটি সহ, সংকলন করছি container.push_back(Item());)

$ g++ -std=c++11 -O3 -flto regr.cpp && perf stat -r 10 ./a.out 

 Performance counter stats for './a.out' (10 runs):

         35.426793 task-clock                #    0.986 CPUs utilized            ( +-  1.75% )
                 4 context-switches          #    0.116 K/sec                    ( +-  5.69% )
                 0 CPU-migrations            #    0.006 K/sec                    ( +- 66.67% )
            19,801 page-faults               #    0.559 M/sec                  
        99,028,466 cycles                    #    2.795 GHz                      ( +-  1.89% ) [77.53%]
        50,721,061 stalled-cycles-frontend   #   51.22% frontend cycles idle     ( +-  3.74% ) [79.47%]
        25,585,331 stalled-cycles-backend    #   25.84% backend  cycles idle     ( +-  4.90% ) [73.07%]
       141,947,224 instructions              #    1.43  insns per cycle        
                                             #    0.36  stalled cycles per insn  ( +-  0.52% ) [88.72%]
        37,697,368 branches                  # 1064.092 M/sec                    ( +-  0.52% ) [88.75%]
            26,700 branch-misses             #    0.07% of all branches          ( +-  3.91% ) [83.64%]

       0.035943226 seconds time elapsed                                          ( +-  1.79% )



$ g++ -std=c++98 -O3 -flto regr.cpp && perf stat -r 10 ./a.out 

 Performance counter stats for './a.out' (10 runs):

         35.510495 task-clock                #    0.988 CPUs utilized            ( +-  2.54% )
                 4 context-switches          #    0.101 K/sec                    ( +-  7.41% )
                 0 CPU-migrations            #    0.003 K/sec                    ( +-100.00% )
            19,801 page-faults               #    0.558 M/sec                    ( +-  0.00% )
        98,463,570 cycles                    #    2.773 GHz                      ( +-  1.09% ) [77.71%]
        50,079,978 stalled-cycles-frontend   #   50.86% frontend cycles idle     ( +-  2.20% ) [79.41%]
        26,270,699 stalled-cycles-backend    #   26.68% backend  cycles idle     ( +-  8.91% ) [74.43%]
       141,427,211 instructions              #    1.44  insns per cycle        
                                             #    0.35  stalled cycles per insn  ( +-  0.23% ) [87.66%]
        37,366,375 branches                  # 1052.263 M/sec                    ( +-  0.48% ) [88.61%]
            26,621 branch-misses             #    0.07% of all branches          ( +-  5.28% ) [83.26%]

       0.035953916 seconds time elapsed  

কারণ হিসাবে, একটি উত্পন্ন সমাবেশ কোড ( g++ -std=c++11 -O3 -S regr.cpp) দেখতে হবে। সি ++ ১১ মোডে উত্পন্ন কোডটি সি ++ 98 মোডের তুলনায় উল্লেখযোগ্যভাবে আরও বিশৃঙ্খলাযুক্ত এবং ফাংশনটি ইনলাইন করা ডিফল্টর সাথে সি ++ 11 মোডে
void std::vector<Item,std::allocator<Item>>::_M_emplace_back_aux<Item>(Item&&)
ব্যর্থ হয় inline-limit

এই ব্যর্থ ইনলাইনটিতে ডোমিনো প্রভাব রয়েছে। এই ফাংশনটি ডাকা হচ্ছে বলে নয় (এটি এমনকি বলা হয় না!) তবে আমাদের প্রস্তুত থাকতে হবে: যদি এটি ডাকা হয় তবে ফাংশনটি খণ্ডগুলি ( Item.aএবং Item.b) ইতিমধ্যে সঠিক জায়গায় থাকতে হবে। এটি একটি সুন্দর অগোছালো কোড বাড়ে।

ইনলাইনিং সফল হয় সেই ক্ষেত্রে এর জন্য উত্পন্ন কোডের প্রাসঙ্গিক অংশটি এখানে রয়েছে :

.L42:
    testq   %rbx, %rbx  # container$D13376$_M_impl$_M_finish
    je  .L3 #,
    movl    $0, (%rbx)  #, container$D13376$_M_impl$_M_finish_136->a
    movl    $0, 4(%rbx) #, container$D13376$_M_impl$_M_finish_136->b
.L3:
    addq    $8, %rbx    #, container$D13376$_M_impl$_M_finish
    subq    $1, %rbp    #, ivtmp.106
    je  .L41    #,
.L14:
    cmpq    %rbx, %rdx  # container$D13376$_M_impl$_M_finish, container$D13376$_M_impl$_M_end_of_storage
    jne .L42    #,

এটি লুপের জন্য একটি দুর্দান্ত এবং কমপ্যাক্ট। এখন, এটি ব্যর্থ ইনলাইন কেসের সাথে তুলনা করি :

.L49:
    testq   %rax, %rax  # D.15772
    je  .L26    #,
    movq    16(%rsp), %rdx  # D.13379, D.13379
    movq    %rdx, (%rax)    # D.13379, *D.15772_60
.L26:
    addq    $8, %rax    #, tmp75
    subq    $1, %rbx    #, ivtmp.117
    movq    %rax, 40(%rsp)  # tmp75, container.D.13376._M_impl._M_finish
    je  .L48    #,
.L28:
    movq    40(%rsp), %rax  # container.D.13376._M_impl._M_finish, D.15772
    cmpq    48(%rsp), %rax  # container.D.13376._M_impl._M_end_of_storage, D.15772
    movl    $0, 16(%rsp)    #, D.13379.a
    movl    $0, 20(%rsp)    #, D.13379.b
    jne .L49    #,
    leaq    16(%rsp), %rsi  #,
    leaq    32(%rsp), %rdi  #,
    call    _ZNSt6vectorI4ItemSaIS0_EE19_M_emplace_back_auxIIS0_EEEvDpOT_   #

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

leaq    16(%rsp), %rsi  #,
leaq    32(%rsp), %rdi  #,
call    _ZNSt6vectorI4ItemSaIS0_EE19_M_emplace_back_auxIIS0_EEEvDpOT_   #

যদিও এটি কখনই বাস্তবায়িত হয় না, লুপটি আগে জিনিসগুলি সাজায়:

movl    $0, 16(%rsp)    #, D.13379.a
movl    $0, 20(%rsp)    #, D.13379.b

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

-finline-limitউভয় সি ++ 11 মোডে কেবল আমার তত্ত্বটি (নোটটি নোট করুন ) প্রমাণ করার জন্য :

 $ g++ -std=c++11 -O3 -finline-limit=105 regr.cpp && perf stat -r 10 ./a.out

 Performance counter stats for './a.out' (10 runs):

         84.739057 task-clock                #    0.993 CPUs utilized            ( +-  1.34% )
                 8 context-switches          #    0.096 K/sec                    ( +-  2.22% )
                 1 CPU-migrations            #    0.009 K/sec                    ( +- 64.01% )
            19,801 page-faults               #    0.234 M/sec                  
       266,809,312 cycles                    #    3.149 GHz                      ( +-  0.58% ) [81.20%]
       206,804,948 stalled-cycles-frontend   #   77.51% frontend cycles idle     ( +-  0.91% ) [81.25%]
       129,078,683 stalled-cycles-backend    #   48.38% backend  cycles idle     ( +-  1.37% ) [69.49%]
       183,130,306 instructions              #    0.69  insns per cycle        
                                             #    1.13  stalled cycles per insn  ( +-  0.85% ) [85.35%]
        38,759,720 branches                  #  457.401 M/sec                    ( +-  0.29% ) [85.43%]
            24,527 branch-misses             #    0.06% of all branches          ( +-  2.66% ) [83.52%]

       0.085359326 seconds time elapsed                                          ( +-  1.31% )

 $ g++ -std=c++11 -O3 -finline-limit=106 regr.cpp && perf stat -r 10 ./a.out

 Performance counter stats for './a.out' (10 runs):

         37.790325 task-clock                #    0.990 CPUs utilized            ( +-  2.06% )
                 4 context-switches          #    0.098 K/sec                    ( +-  5.77% )
                 0 CPU-migrations            #    0.011 K/sec                    ( +- 55.28% )
            19,801 page-faults               #    0.524 M/sec                  
       104,699,973 cycles                    #    2.771 GHz                      ( +-  2.04% ) [78.91%]
        58,023,151 stalled-cycles-frontend   #   55.42% frontend cycles idle     ( +-  4.03% ) [78.88%]
        30,572,036 stalled-cycles-backend    #   29.20% backend  cycles idle     ( +-  5.31% ) [71.40%]
       140,669,773 instructions              #    1.34  insns per cycle        
                                             #    0.41  stalled cycles per insn  ( +-  1.40% ) [88.14%]
        38,117,067 branches                  # 1008.646 M/sec                    ( +-  0.65% ) [89.38%]
            27,519 branch-misses             #    0.07% of all branches          ( +-  4.01% ) [86.16%]

       0.038187580 seconds time elapsed                                          ( +-  2.05% )

প্রকৃতপক্ষে, আমরা যদি সংকলকটিকে সেই ফাংশনটি ইনলাইন করার জন্য আরও কিছুটা কঠিন চেষ্টা করতে বলি তবে পারফরম্যান্সের পার্থক্য চলে যায়।


তাহলে কি এই গল্প থেকে দূরে রাখা হয়? ব্যর্থ ইনলাইনগুলি আপনাকে অনেক বেশি ব্যয় করতে পারে এবং আপনার সংকলক সক্ষমতার পুরো ব্যবহার করা উচিত: আমি কেবল লিঙ্ক টাইম অপ্টিমাইজেশনের প্রস্তাব দিতে পারি। এটি আমার প্রোগ্রামগুলিতে (2.5x পর্যন্ত) একটি গুরুত্বপূর্ণ পারফরম্যান্স বাড়িয়েছে এবং আমাকে যা করতে হবে তা হ'ল -fltoপতাকাটি পাস করা । এটি একটি খুব ভাল চুক্তি! ;)

তবে, আমি আপনার কোডটি ইনলাইন কীওয়ার্ডের সাথে ট্র্যাশ করার পরামর্শ দিচ্ছি না; সংকলকটি কি করতে হবে তা সিদ্ধান্ত নিতে দিন। (অপ্টিমাইজারটিকে যে কোনও উপায়ে ইনলাইন কীওয়ার্ডটিকে সাদা স্থান হিসাবে বিবেচনা করার অনুমতি দেওয়া হয়েছে))


দুর্দান্ত প্রশ্ন, +1!


3
এনবি: inlineফাংশন ইনলাইনিংয়ের সাথে কোনও সম্পর্ক নেই; এর অর্থ "সংজ্ঞায়িত ইনলাইন" নয় "দয়া করে এটি ইনলাইন করুন"। আপনি যদি আসলে ইনলাইনিং, ব্যবহার __attribute__((always_inline))বা অনুরূপ জানতে চান।
জন পূর্ব

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

1
@ জোনপুর্ডি অন্যদিকে, আপনি যদি কোনও ফাংশন ইনলাইন সংজ্ঞায়িত করেন যা কোনও শ্রেণীর সদস্য ফাংশন নয় , তবে আপনার অবশ্যই এটি ইনলাইন চিহ্নিত করার বিকল্প নেই অন্যথায় আপনি লিঙ্কারের কাছ থেকে একাধিক সংজ্ঞা ত্রুটি পাবেন। যদি আপনি এটি বোঝাতে চান তবে ঠিক আছে।
আলী

1
হ্যাঁ, এটাই আমি বোঝাতে চাইছিলাম স্ট্যান্ডার্ডটি বলে যে " inlineস্পেসিফায়ার প্রয়োগের দিকে ইঙ্গিত করে যে কল করার সময় ফাংশন বডির ইনলাইন প্রতিস্থাপনটি সাধারণ ফাংশন কল ব্যবস্থায় অগ্রাধিকার দেওয়া হয়” " (§§.১.২.২) তবে, বাস্তবায়নগুলি সেই অপ্টিমাইজেশন সম্পাদন করার প্রয়োজন হয় না, কারণ এটি মূলত একটি কাকতালীয় ঘটনা যা inlineপ্রায়শই ইনলাইনিংয়ের জন্য ভাল প্রার্থী হতে পারে। সুতরাং স্পষ্ট করে সংকলক প্রগমা ব্যবহার করা আরও ভাল better
জন পুরী

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