3

私は10年間、プログラミングをしていません。私はそれに戻りたいと思ったので、このちょっとした無意味なプログラムを練習として作りました。それが何をするかを説明する最も簡単な方法は、私の --help コードブロックの出力です:

./prng_bench --help

./prng_bench: usage: ./prng_bench $N $B [$T]

   This program will generate an N digit base(B) random number until
all N digits are the same. 

Once a repeating N digit base(B) number is found, the following statistics are displayed:
  -Decimal value of all N digits.
  -Time & number of tries taken to randomly find.

Optionally, this process is repeated T times. 
   When running multiple repititions, averages for all N digit base(B)
numbers are displayed at the end, as well as total time and total tries.

私の「問題」は、問題が「簡単」な場合、たとえば 3 桁の 10 進数の場合、grep にパイプすると「合計時間」が少なくなるということです。すなわち:

指図 ; コマンド|grepがかかった:

./prng_bench 3 10 999999 ; ./prng_bench 3 10 999999|grep took

....
Pass# 999999: All 3 base(10) digits =  3 base(10).   Time:    0.00005 secs.   Tries: 23
It took 191.86701 secs & 99947208 tries to find 999999 repeating 3 digit base(10) numbers.
An average of 0.00019 secs & 99 tries was needed to find each one. 

It took 159.32355 secs & 99947208 tries to find 999999 repeating 3 digit base(10) numbers.

同じコマンドをgrepなしで何度も実行すると、時間は常に非常に近くなります。今のところ、srand(1234) を使用してテストしています。start と stop の clock_gettime() 呼び出しの間のコードには、明らかに時間に影響するストリーム操作が含まれていません。これは無益な演習であることは理解していますが、なぜこのように動作するのかを知りたいです。以下は、プログラムの中心です。誰かがコンパイルしてテストしたい場合は、DB の完全なソースへのリンクを次に示します。https://www.dropbox.com/s/bczggar2pqzp9g1/prng_bench.cpp clock_gettime() には -lrt が必要です。

for (int pass_num=1; pass_num<=passes; pass_num++) {   //Executes $passes # of times.
  clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &temp_time);  //get time
  start_time = timetodouble(temp_time);                //convert time to double, store as start_time
  for(i=1, tries=0; i!=0; tries++) {    //loops until 'comparison for' fully completes. counts reps as 'tries'.  <------------
    for (i=0; i<Ndigits; i++)      //Move forward through array.                                                              |
      results[i]=(rand()%base);    //assign random num of base to element (digit).                                            |
    /*for (i=0; i<Ndigits; i++)     //---Debug Lines---------------                                                           |
      std::cout<<" "<<results[i];   //---a LOT of output.----------                                                           |
    std::cout << "\n";              //---Comment/decoment to disable/enable.*/   //                                           |
    for (i=Ndigits-1; i>0 && results[i]==results[0]; i--); //Move through array, != element breaks & i!=0, new digits drawn. -|
  }                                                        //If all are equal i will be 0, nested for condition satisfied.  -|
  clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &temp_time);  //get time
  draw_time = (timetodouble(temp_time) - start_time);  //convert time to dbl, subtract start_time, set draw_time to diff.
  total_time += draw_time;    //add time for this pass to total.
  total_tries += tries;       //add tries for this pass to total.
  /*Formated output for each pass:
    Pass# ---: All -- base(--) digits = -- base(10)   Time:   ----.---- secs.    Tries: ----- (LINE) */
  std::cout<<"Pass# "<<std::setw(width_pass)<<pass_num<<": All "<<Ndigits<<" base("<<base<<") digits = "
           <<std::setw(width_base)<<results[0]<<" base(10).   Time: "<<std::setw(width_time)<<draw_time
           <<" secs.   Tries: "<<tries<<"\n";
}
if(passes==1) return 0;        //No need for totals and averages of 1 pass.
/* It took ----.---- secs & ------ tries to find --- repeating -- digit base(--) numbers. (LINE)
 An average of ---.---- secs & ---- tries was needed to find each one. (LINE)(LINE) */
 std::cout<<"It took "<<total_time<<" secs & "<<total_tries<<" tries to find "
          <<passes<<" repeating "<<Ndigits<<" digit base("<<base<<") numbers.\n"
          <<"An average of "<<total_time/passes<<" secs & "<<total_tries/passes
          <<" tries was needed to find each one. \n\n";
return 0;
4

3 に答える 3

5

画面への印刷は、パイプまたは印刷なしで実行する場合と比較して非常に低速です。grep にパイプすると、それができなくなります。

于 2012-09-05T23:54:58.060 に答える
2

画面に印刷することではありません。それは、出力が端末(tty)であることについてです。

POSIX仕様によると:

開いたとき、標準エラーストリームは完全にバッファリングされていません。標準入力ストリームと標準出力ストリームは、ストリームが対話型デバイスを参照していないと判断できる場合にのみ、完全にバッファリングされます。

Linuxはこれを解釈して、出力がtty(ターミナルウィンドウなど)の場合はFILE *(つまりstdio)stdoutラインバッファリングし、それ以外の場合(パイプなど)はブロックバッファリングします。

違いが生じる理由sync_with_stdioは、有効にすると、C++coutストリームがこの動作を継承するためです。に設定するとfalse、その動作に拘束されなくなり、ブロックバッファリングされます。

ブロックバッファリングは、すべての改行でバッファをフラッシュするオーバーヘッドを回避するため、より高速です。

catの代わりにに配管することで、これをさらに確認できますgrep。違いは、スクリーン自体ではなく、パイプ自体です。

于 2012-09-07T03:36:06.487 に答える
0

コリンとニモ、ありがとう。開始時刻と停止時刻の間に std::cout を呼び出していなかったので、効果がないことは確かでした。そうではありません。これは、-O0 または 'defaults' を使用してもコンパイラが実行する最適化によるものだと思います。

何が起こっていると思います...?Collin が示唆したように、コンパイラは TTY に書き込むタイミングを賢くしようとしていると思います。そして、Nemo が指摘したように、cout は stdio のライン バッファ プロパティを継承します。

以下を使用して、効果を減らすことはできますが、排除することはできません。

std::cout.sync_with_stdio(false); 

これに関する私の限られた読みから、出力操作が完了する前に呼び出す必要があります。no_sync バージョンのソースは次のとおりです: https://www.dropbox.com/s/wugo7hxvu9ao8i3/prng_bench_no_sync.cpp

./no_sync 3 10 999999;./no_sync 3 10 999999|grep かかった

-O0 でコンパイル

999999: All 3 base(10) digits =  3 base(10)  Time:    0.00004 secs.  Tries: 23
It took 166.30801 secs & 99947208 tries to find 999999 repeating 3 digit base(10) numbers.
An average of 0.00017 secs & 99 tries was needed to find each one. 

It took 163.72914 secs & 99947208 tries to find 999999 repeating 3 digit base(10) numbers.

-O3に準拠

999999: All 3 base(10) digits =  3 base(10)  Time:    0.00003 secs.  Tries: 23
It took 143.23234 secs & 99947208 tries to find 999999 repeating 3 digit base(10) numbers.
An average of 0.00014 secs & 99 tries was needed to find each one. 

It took 140.36195 secs & 99947208 tries to find 999999 repeating 3 digit base(10) numbers.

stdio と同期しないように指定すると、パイプと非パイプの間のデルタが30 秒以上から 3 未満に変わりました。元のデルタの元の質問を参照してください。それは ~191 - ~160 でした

さらにテストするために、構造体を使用して各パスに関する統計を保存する別のバージョンを作成しました。このメソッドは、すべてのパスが完了した後にすべての出力を行います。これはおそらくひどい考えだということを強調しておきます。コマンド ライン引数を使用して、動的に割り当てられた int、double、および unsigned long を含む構造体の配列のサイズを決定できるようにしています。このバージョンを 999,999 パスで実行することさえできません。セグメンテーション違反が発生します。https://www.dropbox.com/s/785ntsm622q9mwd/prng_bench_struct.cpp

./struct_prng 3 10 99999;./struct_prng 3 10 99999|grep かかった

Pass# 99999: All 3 base(10) digits =  6 base(10)  Time:    0.00025 secs.  Tries: 193
It took 13.10071 secs & 9970298 tries to find 99999 repeating 3 digit base(10) numbers.
An average of 0.00013 secs & 99 tries was needed to find each one. 

It took 13.12466 secs & 9970298 tries to find 99999 repeating 3 digit base(10) numbers.

このことから私が学んだことは、コーディングした順序が実行される順序であることに期待できないということです。将来のプログラムでは、おそらく独自の parse_args 関数を記述する代わりに getopt を実装するでしょう。これにより、ユーザーが見たい場合は -v スイッチを使用する必要があるため、繰り返しの多いループで無関係な出力を抑制することができます。

パイピングとループでの出力について疑問に思っている人にとって、さらなるテストが役立つことを願っています。私が投稿したすべての結果は、RasPi で取得されました。リンクされているソース コードはすべて GPL です。これは、私が考えた最初のライセンスだからです... GPL のコピーレフト条項を自己拡大する必要はまったくありません。それが無料であることを明確にしたかったのですが、保証または責任。

リンクされているすべてのソースで srand(...) への呼び出しがコメント化されていることに注意してください。そのため、疑似乱数の結果はすべてまったく同じになります。

于 2012-09-07T02:22:35.493 に答える