2017-04-23 15 views
11

this linkで指定されたコーディングコンペでは、多くのデータをstdinに読み込み、いくつかの計算を行い、多くのデータをstdoutに表示する必要があります。Fast I/O in c、stdin/out

私のベンチマークでは、できるだけ最適化しようとしていますが、ほとんどの時間はかかります。

あなたが入力として持っているものは、文字列(1 <= len <= 100'000)であり、qの列はqのもと1 <= q <= 100'000です。

私は100倍大きいデータセットで私のコードをベンチマーク(LEN = 10M、Q = 10M)、これが結果です:私自身の整形と数の解析インラインを実装することにより

Activity   time  accumulated 

Read text:   0.004  0.004 
Read numbers:  0.146  0.150 
Parse numbers:  0.200  0.350 
Calc answers:  0.001  0.351 
Format output:  0.037  0.388 
Print output:  0.143  0.531 

私は得ることができましたprintfscanfを使用した場合、時間の1/3まで減少します。

私のソリューションをコンペティションのウェブページにアップロードしたとき、私のソリューションは1.88秒かかりました(22データセットを超える合計時間だと思います)。高得点を見ると、0.05秒で完了したいくつかの実装(C++で)が、私より約40倍速いです!そんなことがあるものか?

私は2スレッドを使用して速度を上げることができたと思いますが、stdoutから読み込みながら、計算とstdoutへの書き込みを開始できます。しかし、私の大規模なデータセットの理論的な最良のケースでは、min(0.150, 0.143)に時間が短縮されます。私はまだ最高得点には至っていません。

以下の画像では、消費時間の統計を見ることができます。

Statistics of the consumed time

プログラムは、このオプションを使用してウェブサイトでコンパイルさ:

gcc -g -O2 -std=gnu99 -static my_file.c -lm 

と、次のようにタイミング:

time ./a.out <sample.in> sample.out 

私のコードは次のようになります。

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 

#define MAX_LEN (100000 + 1) 
#define ROW_LEN (6 + 1) 
#define DOUBLE_ROW_LEN (2*ROW_LEN) 

int main(int argc, char *argv[]) 
{ 
    int ret = 1; 

    // Set custom buffers for stdin and out 
    char stdout_buf[16384]; 
    setvbuf(stdout, stdout_buf, _IOFBF, 16384); 
    char stdin_buf[16384]; 
    setvbuf(stdin, stdin_buf, _IOFBF, 16384); 

    // Read stdin to buffer 
    char *buf = malloc(MAX_LEN); 
    if (!buf) { 
     printf("Failed to allocate buffer"); 
     return 1; 
    } 
    if (!fgets(buf, MAX_LEN, stdin)) 
     goto EXIT_A; 

    // Get the num tests 
    int m ; 
    scanf("%d\n", &m); 

    char *num_buf = malloc(DOUBLE_ROW_LEN); 
    if (!num_buf) { 
     printf("Failed to allocate num_buffer"); 
     goto EXIT_A; 
    } 

    int *nn; 
    int *start = calloc(m, sizeof(int)); 
    int *stop = calloc(m, sizeof(int)); 
    int *staptr = start; 
    int *stpptr = stop; 
    char *cptr; 
    for(int i=0; i<m; i++) { 
     fgets(num_buf, DOUBLE_ROW_LEN, stdin); 
     nn = staptr++; 
     cptr = num_buf-1; 
     while(*(++cptr) > '\n') { 
      if (*cptr == ' ') 
       nn = stpptr++; 
      else 
       *nn = *nn*10 + *cptr-'0'; 
     } 
    } 


    // Count for each test 
    char *buf_end = strchr(buf, '\0'); 
    int len, shift; 
    char outbuf[ROW_LEN]; 
    char *ptr_l, *ptr_r, *out; 
    for(int i=0; i<m; i++) { 
     ptr_l = buf + start[i]; 
     ptr_r = buf + stop[i]; 
     while(ptr_r < buf_end && *ptr_l == *ptr_r) { 
      ++ptr_l; 
      ++ptr_r; 
     } 

     // Print length of same sequence 
     shift = len = (int)(ptr_l - (buf + start[i])); 
     out = outbuf; 
     do { 
      out++; 
      shift /= 10; 
     } while (shift); 
     *out = '\0'; 
     do { 
      *(--out) = ""[len%10]; 
      len /= 10; 
     } while(len); 
     puts(outbuf); 
    } 



    ret = 0; 

    free(start); 
    free(stop); 
EXIT_A: 
    free(buf); 
    return ret; 
} 
+0

なぜ個々のintにメモリを割り当てていますか?あなたはどんなシステムにいますか? Linuxでは、stdioはWindows上ではiostreamsより速く高速ですが、Windowsではiostreamがstdioをoutpeformします。 POSIXではstdioが呼び出しに対して再帰的ロックを使用する必要があるため、iostream(AFAIK)にはそのような要件は存在しないため、IO関数のロック解除されたバリアント(putsの代わりにputs_unlockedなど)を使用すると、stdioをいくぶん高速にすることができます。 – PSkocik

+0

ループのたびに出力をしているようです。高速化のためにメモリを交換し、より大きなバッファを割り当ててから、出力全体を一度に印刷するとどうなりますか?また、実行可能な出力が多すぎる場合でも、実質的にバッファリングによって出力を統合することができます。 'puts'が実際にあなたのボトルネックになっていれば、これは問題を解決します。私はそれらの時代に到着するためにどのように測定しているのか分かりません。すべての操作は、例えば「印刷出力」の測定に含まれていますか? –

+0

マイナー: 'cptr = num_buf-1;'は未定義の振る舞いです。 – chux

答えて

0

すべてのバッファを連続して割り当てる必要があります。 すべてのバッファのサイズ(num_buff、start、stop)のバッファを割り当て、ポイントを対応するオフセットにそのサイズで再配置します。 これにより、キャッシュミスやページ違反を減らすことができます。

読み込みと書き込みの操作に多くの時間がかかるようであるため、スレッドの追加を検討する必要があります。 1つのスレッドはI \ Oを処理し、別のスレッドは計算を処理する必要があります。 (プリントのための別のスレッドが物事をスピードアップできるかどうかチェックする価値があります)。これを行う際にロックを使用しないようにしてください。

0

最適化が問題に大きく左右されるため、この質問への回答は難しいです。 あなたが読もうとしているファイルの内容を見て、好きなパターンやものがあるかどうかを確認することです。 あなたが書いたコードは、ファイルから読み込み、何かを実行してからファイルに書き込むための「一般的な」ソリューションです。しかし、ファイルが毎回無作為に生成されておらず、その内容が常に同じ場合、なぜそのファイルの解決策を書こうとしないのですか?

一方、低レベルのシステム機能を使用することもできます。私の考えになるのはmmapで、scanffgetsの代わりにファイルを直接メモリにマップし、そのメモリにアクセスすることができます。

私が見つけたもう一つのことは、あなたのsolutinに2つのwhileループがあることです。なぜなら、1つしか使用しないでください。もう1つの方法は、非同期I/Oの読み込みを行うことです。ループ内のファイル全体を読み込んで別のループで計算する代わりに、最初に部分を読み込み、非同期処理を開始して続行することができます読書 これはlinkが非同期部分に役立つかもしれません

1

あなたの質問のおかげで、私は自分自身で問題を解決しました。あなたの時間は私より優れていますが、私はまだいくつかのstdio関数を使用しています。

私は、単に0.05秒という高いスコアが真実だとは思わない。私はそれがその結果を間違って返した高度に自動化されたシステムの製品だとは思っていません。

アサーションを守るには?実際のアルゴリズムの複雑さはありません。問題はO(n)です。 「トリック」は、入力の各側面に特化したパーサーを作成することです(デバッグモードでの作業は避けてください)。 22回の試行の合計時間は50ミリ秒で、各試行の平均は2.25ミリ秒ですか?我々は測定可能性の限界近くにいる。

あなた自身に対処した問題のようなコンテストは、ある意味では残念です。パフォーマンスはプログラムの最終的な尺度であるという素朴なアイデアを強調しています(明確にするためのスコアはありません)。さらに悪いことに、実際の人生では、プログラムを正しく正確に実行させることは、基本的にstdioを避けたり、調整したりすることは決してありません。複雑なシステムでは、のようなものは、 I/Oを避け、データを1回だけ通過させ、コピーを最小限に抑えるような処理が行われます。 DBMSを効果的に使用することはしばしば重要ですが、そのようなことは決してプログラミング上の課題に現れません。

数字をテキストとして解析して書式設定するには時間がかかり、まれにボトルネックになることがあります。しかし、答えはパーサーを書き換えることはほとんどありません。むしろ答えは、テキストを便利なバイナリ形式に解析し、それを使用することです。要するに:コンパイル。

つまり、いくつかの観察が役に立ちます。

この問題ではダイナミックメモリは必要ありませんが、それは役に立ちません。問題の声明によると、入力配列は最大100,000要素になる可能性があり、試行回数は10万回にもなる可能性があります。各試行は、スペースで区切られ、改行で終了する最大6桁の2つの整数ストリングです。6 + 1 + 6 + 1 = 14.合計入力は最大100,000 + 1 + 6 + 1 + 100,000 * 14: 16 KB。 1 GBのメモリが許可されています。

私はちょうど1つの16KBバッファを割り当て、それを一度にread(2)で読み込みました。それから私はその入力を1回通過しました。

非同期I/Oとスレッドを使用するように提案があります。問題の声明では、CPU時間を測定していると言われています。 2点間の最短距離は直線です。静的に割り当てられたメモリへの1回の読み出しで動きが無駄になります。

パフォーマンスを測定する方法がばかばかしいのは、gcc -gです。これは、パフォーマンスの測定値であるコードでアサート(3)が呼び出されたことを意味します。私は自分の主張を取り除くまで、テスト22で4秒以下になることはできませんでした。

あなたはうまくやったことがあります。私はあなたが困惑している勝者がファントムだと思っています。あなたのコードは少しばかり気になります。動的メモリとチューニングを省くことができます。私はあなたの時間がそれを単純化することによって調整できると確信しています。パフォーマンスが重要である限り、それがあなたの注意を向けるところです。