7

これは、stackoverflow に関する私の最初の投稿であり、私の母国語は英語ではありません。この投稿でご迷惑をおかけしますことをお許しください。少し長くなるかもしれませんが、よろしくお願いします。前もって感謝します!

C 言語のコード スニペットがあります。ジョブは 2 つのファイルの単語数をカウントしています。この問題を解決するために pthreads を使用します。しかし、私はこれら2つのステートメントの順序を見つけます

count_words(argv[1]);

pthread_create(&t1, NULL, count_words, (void *)argv[2]);

プログラムのパフォーマンスに影響しますが、これは私が期待したものとは逆です。コードは次のとおりです。

#include <stdio.h>
#include <pthread.h>
#include <ctype.h>
#include <stdlib.h>

int total_words;

int main(int argc, char *argv[]) {
    pthread_t t1;
    void *count_words(void *);

    if (argc != 3) {
        printf("usage: %s file1 file2\n", argv[0]);
        exit(1);
    }
    total_words = 0;

    count_words(argv[1]); // program runs faster when executing this first
    pthread_create(&t1, NULL, count_words, (void *)argv[2]);

    pthread_join(t1, NULL);
    printf("%5d: total words\n", total_words);
    return 0;
}

void *count_words(void *f) {
    char *filename = (char *)f;
    FILE *fp;
    int c, prevc = '\0';

    if ((fp = fopen(filename, "r")) == NULL) {
        perror(filename);
        exit(1);
    }
    while ((c = getc(fp)) != EOF) {
        if (!isalnum(c) && isalnum(prevc))
            total_words++;
        prevc = c;
    }
    fclose(fp);
    return NULL;
}

パフォーマンス:

コマンドラインで「test program_name」を使用してプログラムを実行し、実行速度をテストします。出力は次のとおりです。

次のような順序の場合:

count_words(argv[1]);

pthread_create(&t1, NULL, count_words, (void *)argv[2]);

プログラムの実行速度: 実数 0.014 秒

このような場合:

pthread_create(&t1, NULL, count_words, (void *)argv[2]);

count_words(argv[1]);

プログラムの実行速度が遅い: 実数 0.026 秒

私が期待したこと:

ケース 1 では、プログラムは最初に count_word() を実行します。カウント ジョブの完了後、引き続き pthread_create() を実行します。その時点で、新しいスレッドがカウント ジョブを実行するのに役立ちます。したがって、元のスレッドがジョブを完了した後、新しいスレッドがジョブを実行します。これは、並列実行ではなく順次実行されます。ケース 2 では、プログラムはカウントの前に最初に pthread_create() を実行するため、その後は 2 つのスレッドが並行してカウントを行います。したがって、ケース 2 はケース 1 よりも高速であると期待しています。しかし、私は間違っています。ケース 2 は遅くなります。誰かがこれに関する有用な情報を教えてくれますか?

ノート

グローバル変数 total_words にミューテックス ロックを設定していないことを無視してください。これは私が気にしている部分ではありません。そして、プログラムはテスト用です。その不完全さを許してください。


編集 1

以下は、いくつかの提案を読んだ後の補足と改善です。

a) 補足: プロセッサは Intel® Celeron(R) CPU 420 @ 1.60GHz です。1 つのコア。

b)改善:私の例を改善しました.2つの変更:

1) ファイルを拡大しました。file1 は 2080651 バイト (約 2M)、file2 は file1 のコピーです。

2) count_words() を修正しました。ファイルの終わりに到達したら、fseek() を使用して fp を先頭に設定し、再度カウントします。繰り返し COUNT 回カウントします。COUNT 20 を定義します。変更後のコードは次のとおりです。

#define COUNT 20

// other unchanged codes ...

void *count_words(void *f) {
    // other unchanged codes ...
  int i;
  for (i = 0; i < COUNT; i++) {
      while ((c = getc(fp)) != EOF) {
          if (!isalnum(c) && isalnum(prevc))
              total_words++;
          prevc = c;
      }
      fseek(fp, 0, SEEK_SET);
  }
  fclose(fp);
  return NULL;
}

fast_version (count_word() が最初) と slow_version (pthread_create() が最初) の出力:

administrator@ubuntu:~$ time ./fast_version file1 file2

12241560: 総単語数

実質 0m5.057s

ユーザー 0m4.960s

システム 0m0.048s

administrator@ubuntu:~$ time ./slow_version file1 file2

12241560: 総単語数

実質 0m7.636s

ユーザー 0m7.280s

システム 0m0.048s

「time progname file1 file2」コマンドを数回試しました。実行ごとに 10 分の 1 秒または 100 分の 1 秒の差がある可能性があります。しかし、違いはそれほど大きくありません。

編集 2

この部分は、いくつかのヒントに従っていくつかの実験を行った後に追加されます-

最初のスレッドの実行が完了した後に 2 番目のスレッドを起動すると、コンテキスト切り替えのオーバーヘッドはありません。

--user315052 作。

実験は、count_word() を改善したことです。

void *count_word(void *f) {
// unchanged codes
// ...
    for (i = 0; i < COUNT; i++) {
        while ((c = getc(fp)) != EOF) {
            if (!isalnum(c) && isalnum(prevc))
                total_words++;
            prevc = c;
        }
        fseek(fp, 0, SEEK_SET);
        printf("from %s\n", filename); // This statement is newly added.
    }
// unchanged codes
// ...
}

ステートメント " printf("from %s\n", filename); " を追加して、その時点でどのファイル (またはスレッド) が実行されているかを確認できるようにします。高速バージョンの出力は「file1から」20回、次に「file2から」20回、低速バージョンは「file1から」と「file2から」が混在して出力されます。

コンテキストの切り替えがないため、高速バージョンの方が高速のようです。しかし実際には、count_word() が終了した後、元のスレッドは死んでおらず、新しいスレッドを作成して終了するのを待っていました。新しいスレッドが実行されているときにコンテキストの切り替えはありませんか? 画面をよく見てみると、「 from file2 」の印刷速度が「 from file1 」より明らかに遅いことがわかりました。なんで?file2 からカウントするときにコンテキストの切り替えが発生したためでしょうか。

遅いバージョンの出力から、「 from file1 」と「 from file2 」の印刷速度は、高速バージョンの「 from file2 」の印刷速度よりもさらに遅いことがわかります。これは、コンテキストの切り替えが並列カウントでより多くの時間を消費するためです。 、高速バージョンでは、スレッドの1つがジョブを終了して待機しているだけなので、コンテキストの切り替えはそれほど重くありません。

その主な理由は、高速バージョンが低速バージョンに対して軽くて簡単なコンテキスト切り替えを備えているためだと思います。ただし、「印刷速度」は私の観察によるものであり、それほど厳密ではない場合があります。だから私はそれについて確信が持てません。

4

3 に答える 3

10

コメントで、次のように書きました。

プロセッサは Intel® Celeron(R) CPU 420 @ 1.60GHz です。1 つのコア。

コアが 1 つしかないため、スレッドの実行はとにかくシリアル化されます。2 つのスレッドが同時に実行されているプログラムでは、それぞれがブロッキング I/O を実行するため、スレッド コンテキストの切り替えのオーバーヘッドが発生します。

最初のスレッドの実行が完了した後に 2 番目のスレッドを起動すると、コンテキスト切り替えのオーバーヘッドはありません。

于 2013-04-06T10:40:50.030 に答える
2

同じ測定を試みますが、プログラムを 100 回実行して平均時間を計算します。このような短い時間では、例としてキャッシュの効果は無視できません。

于 2013-04-06T09:56:49.277 に答える
2

どのように測定しましたか?

リアルタイムは、プログラムの実行時間を示すものではありません。ユーザー+システム時間を測定する必要があります。さらに、ミリ秒レベルでの意味のあるタイミングは、タイミング クロックの粒度に大きく依存します。たとえば、60Hz で動作する場合は、問題があります。意味のあるベンチマークを考え出すことは芸術です...

まず、スレッドをループで実行する方法を見つける必要があります。たとえば、10.000 回実行して、数値を合計します。これにより、少なくともミリ秒のタイミングの問題から抜け出すことができます。

于 2013-04-06T09:52:54.440 に答える