2

0、1、2、3、4 ... などの値で正常に機能し、15 を超える値でセグメント障害が発生する理由はありますか? #include #include #include

void *fib(void *fibToFind);

main(){
pthread_t mainthread;

long fibToFind = 15;
long finalFib;

pthread_create(&mainthread,NULL,fib,(void*) fibToFind);

pthread_join(mainthread,(void*)&finalFib);

printf("The number is: %d\n",finalFib);
}


void *fib(void *fibToFind){
long retval;

long newFibToFind = ((long)fibToFind);

long returnMinusOne;
long returnMinustwo;

pthread_t minusone;
pthread_t minustwo;

if(newFibToFind == 0 || newFibToFind == 1)
return newFibToFind;

else{
long newFibToFind1 = ((long)fibToFind) - 1;
long newFibToFind2 = ((long)fibToFind) - 2;

pthread_create(&minusone,NULL,fib,(void*) newFibToFind1);
pthread_create(&minustwo,NULL,fib,(void*) newFibToFind2);

pthread_join(minusone,(void*)&returnMinusOne);
pthread_join(minustwo,(void*)&returnMinustwo);

return returnMinusOne + returnMinustwo;

}

}
4

3 に答える 3

3

メモリ不足 (スタック用のスペース不足) または有効なスレッド ハンドルが不足していますか?

多くのスタック/コンテキストを必要とする非常に多くのスレッドを要求しています。Windows (および Linux) には、ばかげた「大きな [連続した] スタック」という考えがあります。

pthreads_create のドキュメントから: 「Linux/x86-32 では、新しいスレッドのデフォルトのスタック サイズは 2 メガバイトです。」10,000 個のスレッドを製造する場合、20 GB の RAM が必要です。OP のプログラムのバージョンを作成したところ、Windows XP64 で約 3500 の (p) スレッドが爆発しました。

大きなスタックが本当に悪い考えである理由の詳細については、この SO スレッドを参照してください: Why are stack overflows still a problem?

大きなスタックをあきらめて、アクティベーション レコード用のヒープ割り当てを備えた並列言語を実装すると ( PARLANSEはその 1 つです)、問題は解決します。

以下は、PARLANSE で作成した最初の (シーケンシャル) プログラムです。

(define fibonacci_argument 45)

(define fibonacci
   (lambda(function natural natural )function 
   `Given n, computes nth fibonacci number'
      (ifthenelse (<= ? 1)
           ?
         (+ (fibonacci (-- ?))
              (fibonacci (- ? 2))
           )+
   )ifthenelse  
   )lambda
 )define

i7 で実行した実行例を次に示します。

 C:\DMS\Domains\PARLANSE\Tools\PerformanceTest>run fibonaccisequential
 Starting Sequential Fibonacci(45)...Runtime: 33.752067 seconds
 Result: 1134903170

2 つ目は並列です。

(define coarse_grain_threshold 30) ; technology constant: tune to amortize fork overhead across lots of work

(define parallel_fibonacci
   (lambda (function natural natural )function 
   `Given n, computes nth fibonacci number'
      (ifthenelse (<= ? coarse_grain_threshold)
           (fibonacci ?)
           (let (;; [n natural ] [m natural ]  )
                   (value (|| (= m (parallel_fibonacci (-- ?)) )=
                              (= n (parallel_fibonacci (- ? 2)) )=
                          )||
                          (+ m n)
                   )value
           )let
       )ifthenelse  
   )lambda
)define

並列処理を明示的にすると、プログラムの作成もはるかに簡単になります。

(parallel_fibonacci 45) を呼び出してテストする並列バージョン。これは同じ i7 で実行された実行です (これにはおそらく 8 つのプロセッサがありますが、実際には 4 つのプロセッサがハイパースレッド化されているため、実際には 8 つの同等の CPU ではありません)。

C:\DMS\Domains\PARLANSE\Tools\PerformanceTest>run fibonacciparallelcoarse
Parallel Coarse-grain Fibonacci(45) with cutoff 30...Runtime: 5.511126 seconds
Result: 1134903170

6+ 近くのスピードアップ。8 プロセッサ未満のプロセッサでは悪くありません。この質問に対する他の回答の 1 つは、pthreads バージョンを実行しました。Fib(18) の計算には「数秒」かかりましたが、これは Fib(45) では 5.5 秒です。これは、pthreads が多くの細かい並列処理を行うには根本的に悪い方法であることを示しています。(PARLANSE は、フォークのオーバーヘッドを最小限に抑えるように設計されています)。

テクノロジー定数をゼロに設定すると、次のようになります ( fib の呼び出しごとにフォークします)。

C:\DMS\Domains\PARLANSE\Tools\PerformanceTest>run fibonacciparallel
Starting Parallel Fibonacci(45)...Runtime: 15.578779 seconds
Result: 1134903170

フォークが高速であっても、フォークのオーバーヘッドを償却することは良い考えであることがわかります。

Fib(45) は多くの粒子を生成します。アクティベーション レコードのヒープ割り当てにより、OP の一次問題が解決されます (それぞれ 1Mb のスタックを持つ数千の pthread がギガバイトの RAM を消費します)。

しかし、二次的な問題があります: 2^45 PARLANSE "グレイン" は、グレイン制御ブロックが小さい場合でも、グレインを追跡するだけですべてのメモリを消費します。そのため、並列処理の爆発が「グレイン」追跡データでマシンを圧倒するのを防ぐために、グレインが「たくさん」(「たくさん」の定義によっては 2^45 より大幅に少ない) になったら、フォークを調整するスケジューラを用意すると役立ちます。構造。グレインの数がしきい値を下回った場合も、フォークのスロットルを解除して、物理 CPU が実行する論理的で並列な作業が常に多数あることを確認する必要があります。

于 2011-02-23T01:29:46.693 に答える
0

私はあなたのコードを実行しようとしましたが、いくつかの驚きに遭遇しました:

printf("The number is: %d\n", finalFib);

この行には小さなエラーがあります。%dつまり、がprintf期待されますintが、が渡されlong intます。ほとんどのプラットフォームでは、これは同じであるか、とにかく同じ動作をしますが、慎重に言えば(または、警告が表示されないようにしたい場合は、非常に高貴な理想です)、%ld代わりに使用する必要があります。 long int。_

fib一方、あなたの機能は機能していないようです。私のマシンでテストすると、クラッシュしませんが1047、フィボナッチ数ではないが得られます。よく見ると、プログラムはいくつかの点で正しくないようです。

void *fib(void *fibToFind)
{
    long retval; // retval is never used

    long newFibToFind = ((long)fibToFind);

    long returnMinusOne; // variable is read but never initialized
    long returnMinustwo; // variable is read but never initialized

    pthread_t minusone; // variable is never used (?)
    pthread_t minustwo; // variable is never used

    if(newFibToFind == 0 || newFibToFind == 1)
        // you miss a cast here (but you really shouldn't do it this way)
        return newFibToFind;        
    else{
        long newFibToFind1 = ((long)fibToFind) - 1; // variable is never used
        long newFibToFind2 = ((long)fibToFind) - 2; // variable is never used
        // reading undefined variables (and missing a cast)
        return returnMinusOne + returnMinustwo;

    }
}

コンパイラの警告には常に注意してください。警告を受け取ったとき、通常、あなたは本当に何か怪しいことをしています。

アルゴリズムを少し修正する必要があるかもしれません。現在、関数が行うのは2つの未定義の値の合計を返すことだけなので、以前に取得した1047です。

再帰的アルゴリズムを使用してフィボナッチスイートを実装すると、関数を再度呼び出す必要があります。他の人が指摘したように、それは非常に非効率的な方法ですが、簡単なので、すべてのコンピュータサイエンスの教師が例としてそれを使用していると思います。

通常の再帰的アルゴリズムは次のようになります。

int fibonacci(int iteration)
{
    if (iteration == 0 || iteration == 1)
        return 1;

    return fibonacci(iteration - 1) + fibonacci(iteration - 2);
}

スレッドをどの程度使用することになっていたのかわかりません。セカンダリスレッドでアルゴリズムを実行するか、呼び出しごとに新しいスレッドを作成しますか?それははるかに簡単なので、今のところ最初のものを想定しましょう。

整数をポインタにキャストしたり、その逆を行ったりすることは悪い習慣です。より高いレベルで物事を見ようとすると、それらは大きく異なるはずだからです。整数は数学を行い、ポインタはメモリアドレスを解決します。それらが同じように表現されているため、たまたま機能しますが、実際には、これを行うべきではありません。代わりに、新しいスレッドを実行するために呼び出された関数が引数を受け入れることに気付くかもしれません。これを使用して、入力の場所と出力の場所void*の両方を伝えることができます。

したがって、以前のfibonacci関数に基づいて、このコードをスレッドのメインルーチンとして使用できます。

void* fibonacci_offshored(void* pointer)
{
    int* pointer_to_number = pointer;
    int input = *pointer_to_number;
    *pointer_to_number = fibonacci(input);
    return NULL;
}

整数へのポインタを期待し、そこから入力を受け取り、そこに出力を書き込みます。1次に、次のようなスレッドを作成します。

int main()
{
    int value = 15;
    pthread_t thread;

    // on input, value should contain the number of iterations;
    // after the end of the function, it will contain the result of
    //  the fibonacci function
    int result = pthread_create(&thread, NULL, fibonacci_offshored, &value);

    // error checking is important! try to crash gracefully at the very least
    if (result != 0)
    {
        perror("pthread_create");
        return 1;
    }

    if (pthread_join(thread, NULL)
    {
        perror("pthread_join");
        return 1;
    }

    // now, value contains the output of the fibonacci function
    // (note that value is an int, so just %d is fine)
    printf("The value is %d\n", value);
    return 0;
}

新しい個別のスレッドからフィボナッチ関数を呼び出す必要がある場合(注意:これは私がアドバイスすることではなく、他の人も私に同意しているようです。十分な量の反復で爆発するだけです)、最初にfibonacci関数を関数とマージする必要がありfibonacci_offshoredます。スレッドの処理は通常の関数の処理よりも重いため、かなり大きくなります。

void* threaded_fibonacci(void* pointer)
{
    int* pointer_to_number = pointer;
    int input = *pointer_to_number;

    if (input == 0 || input == 1)
    {
        *pointer_to_number = 1;
        return NULL;
    }

    // we need one argument per thread
    int minus_one_number = input - 1;
    int minus_two_number = input - 2;

    pthread_t minus_one;
    pthread_t minus_two;
    // don't forget to check! especially that in a recursive function where the
    // recursion set actually grows instead of shrinking, you're bound to fail
    // at some point
    if (pthread_create(&minus_one, NULL, threaded_fibonacci, &minus_one_number) != 0)
    {
        perror("pthread_create");
        *pointer_to_number = 0;
        return NULL;
    }
    if (pthread_create(&minus_two, NULL, threaded_fibonacci, &minus_two_number) != 0)
    {
        perror("pthread_create");
        *pointer_to_number = 0;
        return NULL;
    }

    if (pthread_join(minus_one, NULL) != 0)
    {
        perror("pthread_join");
        *pointer_to_number = 0;
        return NULL;
    }

    if (pthread_join(minus_two, NULL) != 0)
    {
        perror("pthread_join");
        *pointer_to_number = 0;
        return NULL;
    }

    *pointer_to_number = minus_one_number + minus_two_number;
    return NULL;
}

このかさばる関数ができたので、main関数の調整は非常に簡単になります。参照をfibonacci_offshoredに変更するだけthreaded_fibonacciです。

int main()
{
    int value = 15;
    pthread_t thread;

    int result = pthread_create(&thread, NULL, threaded_fibonacci, &value);

    if (result != 0)
    {
        perror("pthread_create");
        return 1;
    }
    pthread_join(thread, NULL);

    printf("The value is %d\n", value);
    return 0;
}

スレッドは並列プロセスを高速化すると言われたかもしれませんが、スレッドの内容を実行するよりもスレッドをセットアップする方がコストがかかるところには限界があります。これは、このような状況の非常に良い例です。スレッド化されたバージョンのプログラムは、スレッド化されていないバージョンよりもはるかに低速で実行されます。

教育目的で、このプログラムは、必要な反復回数が18のときに私のマシンのスレッドを使い果たし、実行に数秒かかります。比較すると、反復実装を使用すると、スレッドが不足することはなく、数ミリ秒で答えが得られます。また、かなり簡単です。これは、より優れたアルゴリズムを使用して多くの問題を解決する方法の良い例です。

また、好奇心から、それがあなたのマシンでクラッシュするかどうか、そしてどこで/どのようにクラッシュするかを見るのは興味深いでしょう。

1.通常、変数の意味を入力時の値と関数の戻り後の値の間で変更しないようにする必要があります。たとえば、ここでは、入力時に、変数は必要な反復回数です。出力では、それは関数の結果です。これらは2つの非常に異なる意味であり、それは実際には良い習慣ではありません。動的割り当てを使用して戻り値を介して値を返すことは気が進まなかったvoid*

于 2011-02-23T04:58:50.477 に答える
0

エラーをチェックしていません。特にpthread_create(). pthread_create()失敗すると、変数pthread_tは未定義のままになり、後続pthread_join()がクラッシュする可能性があります。

エラーをチェックすると、それpthread_create()が失敗していることがわかります。これは、ほぼ 2000 のスレッドを生成しようとしているためです。デフォルト設定では、16 GB のスレッド スタックを単独で割り当てる必要があります。

あまり多くのスレッドが生成されないように、アルゴリズムを修正する必要があります。

于 2011-02-23T01:36:12.273 に答える