Рекурсивный Фиб с нитями, ошибка сегментации? - PullRequest
2 голосов
/ 23 февраля 2011

Есть идеи, почему он отлично работает для значений, таких как 0, 1, 2, 3, 4 ... и ошибки seg для значений, таких как> 15? #включают #включают #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;

}

}

Ответы [ 3 ]

3 голосов
/ 23 февраля 2011

Недостаточно памяти (недостаточно места для стеков) или допустимые дескрипторы потоков?

Вы запрашиваете очень много потоков, которые требуют много стека / контекста.В Windows (и Linux) есть глупая идея «большого [непрерывного] стека».

Из документации по pthreads_create: «В Linux / x86-32 размер стека по умолчанию для нового потока составляет 2 мегабайта».Если вы производите 10000 потоков, вам нужно 20 ГБ ОЗУ.Я построил версию программы OP, и она взорвалась с некоторыми 3500 (p) потоками в Windows XP64.

См. Эту ветку SO для получения дополнительной информации о том, почему большие стеки действительно плохая идея: Почемупереполнение стека по-прежнему проблема?

Если вы отказываетесь от больших стеков и внедряете параллельный язык с выделением кучи для записей активации (наш PARLANSE является одним из них) проблемауходит.

Вот первая (последовательная) программа, которую мы написали на 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

Вот вторая, который параллелен:

(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 эквивалентных процессоров):

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

Ускорение около 6+,неплохо для не совсем 8 процессоров.Один из других ответов на этот вопрос - версия 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 (тысячи pthreads каждый с 1Mb стека записывает гигабайты ОЗУ).

Но есть проблема второго порядка: 2 ^ 45 PARLANSE "зерна" сожгут всеВаша память тоже просто отслеживает зерна, даже если ваш блок управления зерном крошечный.Так что это помогает иметь планировщик, который душит вилы, когда у вас есть «много» (для некоторого определения «много» значительно меньше, чем 2 ^ 45) зерен, чтобы предотвратить взрыв параллелизма, затопляющий машину данными отслеживания «зерна»структур.Он должен разблокировать вилки, когда число зерен падает ниже порогового значения, чтобы физические процессоры всегда выполняли много логической параллельной работы.

0 голосов
/ 23 февраля 2011

Я попытался запустить ваш код и натолкнулся на несколько неожиданностей:

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;

    }
}

Всегда заботьтесь о предупреждениях компилятора: обычно, когда вы его получаете, вы действительно делаете что-то подозрительное.

Возможно, вам следует немного пересмотреть алгоритм: сейчас ваша функция возвращает сумму двух неопределенных значений, отсюда и 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.Обычно вам следует избегать изменения значения переменной между ее значением на входе и ее значением после возврата из функции.Например, здесь, на входе, переменная - это количество итераций, которое мы хотим;на выходе это результат функции.Это два совершенно разных значения, и это не очень хорошая практика.Мне не хотелось использовать динамическое распределение для возврата значения через void* возвращаемое значение.

0 голосов
/ 23 февраля 2011

Вы не проверяете ошибки - в частности, от pthread_create(). При сбое pthread_create() переменная pthread_t остается неопределенной, а последующая pthread_join() может привести к сбою.

Если вы проверите ошибки, вы обнаружите, что pthread_create() не работает. Это потому, что вы пытаетесь сгенерировать почти 2000 потоков - с настройками по умолчанию для этого потребуется 16 ГБ стеков потоков, которые должны быть выделены в одиночку.

Вам следует пересмотреть свой алгоритм, чтобы он не генерировал столько потоков.

...