Самый эффективный способ порождения n pthreads с одинаковыми параметрами в C - PullRequest
0 голосов
/ 11 марта 2019

У меня 32 потока, которым я заранее знаю входные параметры, внутри функции ничего не меняется (кроме буфера памяти, с которым взаимодействует каждый поток).

В псевдо-C-коде это мой дизайнpattern:

// declare 32 pthreads as global variables

void dispatch_32_threads() {
   for(int i=0; i < 32; i++) {
      pthread_create( &thread_id[i], NULL, thread_function, (void*) thread_params[i] );
   }
   // wait until all 32 threads are finished
   for(int j=0; j < 32; j++) {
      pthread_join( thread_id[j], NULL); 
   }
}

int main (crap) {

    //init 32 pthreads here

    for(int n = 0; n<4000; n++) {
        for(int x = 0; x<100< x++) {
            for(int y = 0; y<100< y++) {
                dispatch_32_threads();
                //modify buffers here
            }
        }
    }
}

Я звоню dispatch_32_threads 100*100*4000= 40000000 раз.thread_function и (void*) thread_params[i] не меняются.Я думаю, что pthread_create продолжает создавать и уничтожать потоки, у меня 32 ядра, ни одно из них не загружено на 100%, оно колеблется около 12%.Более того, когда я уменьшаю количество потоков до 10, все 32 ядра остаются на 5-7% загрузки, и я не вижу замедления во время выполнения.Выполнение менее чем 10 медленных процессов.

Выполнение 1 потока, однако, является чрезвычайно медленным, поэтому многопоточность помогает.Я профилировал свой код, я знаю, что thread_func медленный, а thread_func распараллеливаемый.Это наводит меня на мысль, что pthread_create продолжает порождать и уничтожать потоки на разных ядрах, и после 10 потоков я теряю эффективность, и это становится медленнее, thread_func по сути "менее сложен", чем порождение более 10 потоков.

Верна ли эта оценка?Каков наилучший способ использования 100% всех ядер?

1 Ответ

1 голос
/ 11 марта 2019

Создание темы стоит дорого.Это зависит от различных параметров, но редко ниже 1000 циклов.И синхронизация потоков и уничтожение аналогично.Если объем работы в вашей функции thread_function не очень высок, она в значительной степени будет влиять на время вычислений.

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

int outer=4000;
int nthreads=32;
int perthread=outer/nthreads;

// add an integer with thread_id to thread_param struct
void thread_func(whatisrequired *thread_params){
  // runs perthread iteration of the loop beginning at start
    int start = thread_param->thread_id;
    for(int n = start; n<start+perthread; n++) {
        for(int x = 0; x<100< x++) {
            for(int y = 0; y<100< y++) {
                //do the work
            }
        }
    }
}

int main(){
   for(int i=0; i < 32; i++) {
      thread_params[i]->thread_id=i;
      pthread_create( &thread_id[i], NULL, thread_func, 
              (void*) thread_params[i]);
   }
   // wait until all 32 threads are finished
   for(int j=0; j < 32; j++) {
      pthread_join( thread_id[j], NULL); 
   }
}

При таком виде распараллеливания вы можете рассмотреть возможность использованияOpenMP.Предложение parallel for поможет вам легко поэкспериментировать с наилучшей схемой распараллеливания.

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

Редактировать: В качестве альтернативы вы можете
1. поместить все свои циклы в функцию потока
2. вначало (или конец) внутреннего цикла добавьте барьер для синхронизации ваших потоков.Это гарантирует, что все потоки завершили свою работу.
3. В main создайте все потоки и дождитесь завершения.
Барьеры дешевле, чем создание потоков, и результат будет идентичным.

...