2013-04-06 21 views
7

これは私の最初の投稿であり、私の母国語は英語ではありません。この投稿があなたにもたらす不便な点をお許しください。おそらくそれは少し長いので、私はあなたの忍耐を楽しみにしています。前もって感謝します!実行順序が異なるとPthreadプログラムのパフォーマンスが異なる

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

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; 
} 

パフォーマンス:

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

場合、このような順序:

count_words(ARGV [1])。

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

プログラムが高速に実行します:本当の0.014s

場合には、このような:

のpthread_create(& T1、NULL、count_words、(ボイド*)ARGV [2]);

count_words(argv [1]);

プログラムが遅い実行します:0.026s

本当の私が期待したもの:ケース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)の改善:私はファイルを拡大)

1:私は、2つの変更を私の例を改善しています。 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()最初):Ubuntuの@

管理者:〜$時間./fast_version FILE1 FILE2

12241560:合計言葉

本当0m5.057s

ユーザーが

を0m4.960s 210

のsys 0m0.048s

管理者の@のubuntu:〜$時間./slow_version FILE1 FILE2

12241560:合計言葉

ユーザーが

0m7.280s本当0m7.636s

sys 0m0.048s

"time progname file1 file2"コマンドを数回試しました。たぶん、毎回10分の1秒に何か違いがあります。しかし、違いはあまりありません。

編集2

私はいくつかのヒントに応じていくつかの実験を行った後にこの部分が追加された -

最初のスレッドが、それは実行だ完了後に2番目のスレッドを起動すると、そこにあります文脈の切り替えのオーバーヘッドはありません

--by 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 
// ... 
} 

は声明 "(%sの\ nからのprintf" 追加 "ファイル名);" ので、私はどのファイル(またはスレッド)を伝えることができますその時に走っています。高速版の出力は「file1」から20回、「file2」から20回、slow版は「from file1」と「file2」が混在して出力されます。

コンテキストの切り替えがないため、高速版の方が高速です。しかし実際、count_word()が終了した後、元のスレッドは死んでいませんでしたが、新しいスレッドを作成し、終了するのを待っていました。新しいスレッドが実行されているときにコンテキストの切り替えはないのですか?私は画面をよく見て、 "file2"の印刷速度が "file1"よりも明らかに遅いことを発見しました。どうして?それはファイル2から数えたときにコンテクストの切り替えが起こったからですか?

低速版では、 "file1"と "file2"の印刷速度が、高速バージョンの "file2"の印刷速度よりもさらに遅いことが出力からわかります。高速のバージョンでは、スレッドの1つがジョブを終了して待っているだけでコンテキストの切り替えはそれほど重くはありません。

私は主な理由は、高速版は、軽くて簡単な文脈の切り替えが遅いバージョンと考えていると思います。しかし、「印刷速度」は私の見解であり、それほど厳しくはないかもしれません。だから私はそれについては分かりません。

+0

あなたはどのマシンを実行していますか?あなたは実際に2つのコアを持っていますか? – jxh

+1

'total_words ++ 'をコメントアウトするとどうなりますか?相対的なタイミングは同じままですか? – NPE

+1

時間がかなり短いです - 正確にどのように測定していますか?非常に大きな入力で実行する方がよい。 –

答えて

10

、あなたが書いた:

をプロセッサは、インテルのCeleron(R)1.60GHz @ CPU 420です。 1つのコア。

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

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

+0

優秀な答え - これはOPのタイミングの混乱をほぼ確実に説明しています。 +1 –

+0

提供された情報をありがとう!あなたのヒントの後、私は実験を行い、自分の投稿を更新しました。あなたの答えはそれほど正確ではないかもしれないと思います。最初のcount_word()(最初のスレッドではありません)が完了した後にコンテキスト切り替えが行われているようです。その理由は、低速バージョンでのコンテキスト切り替えのコストが高速バージョンよりも高価であると考えます。しかし、私は確信していません。あなたの意見は何ですか? –

+0

@WenhaoYang:「コンテキスト切り替えのオーバーヘッドなし」という意味は、各スレッドは、スレッド間を行き来することなく完了まで実行できるということです。はい、高速版には2つあります。 2番目のスレッドに切り替えて、結合後にスイッチバックします。コンテキストスイッチ自体のコストに加えて、データキャッシュを中断するなどのコンテキストスイッチに関連するコストがあります。遅いバージョンはコンテクストを2回以上切り替えることが最も確かであり、毎回データキャッシュをスラッシングする。 – jxh

2

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

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

である、あなたは、たとえば、ループ内で10.000回あなたのスレッドを実行し、数字を追加する方法を見つける必要があります。それは、少なくともミリ秒のタイミング問題からあなたを救うでしょう。

+0

あなたのアドバイスに従ってください、私は自分のコードを変更し、ファイルを拡大し、自分の投稿を編集しました。私が編集された投稿で言ったように、count_words()がファイルの終わりに達したら、最初に戻り、もう一度カウントしてください。この手順は20回実行されます。また、ユーザーとシステムの両方の時間も記録しました。多分、測定値はそれほど厳密でも正確でもないかもしれません。しかし、我々は速いものがまだ速いものであると結論づけることができます。 –

2

プログラムを100回実行して平均時間を計算してください。そのような短い時間で、キャッシングの効果は例では無視できないほどです。コメントで

+0

はい。私はコードを編集した。 count_word()は、同じファイルに対してカウントジョブを20回繰り返します。今私は長い時間がある。しかし、申し訳ありませんが、私は "time progname file1 file2"コマンドを数回入力し、その結果を観察しました。私はその違いがそれほどではなく、私のために十分であることを発見しました。だから私は平均時間を計算しませんでした。 –

関連する問題