Умножение матриц с потоками: почему это не быстрее? - PullRequest
9 голосов
/ 07 июня 2010

Итак, я играл с pthreads, особенно пытаясь вычислить произведение двух матриц. Мой код очень грязный, потому что он должен был быть для меня небольшим забавным проектом, но теория потоков, которую я использовал, была очень похожа на:

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

#define M 3
#define K 2
#define N 3
#define NUM_THREADS 10

int A [M][K] = { {1,4}, {2,5}, {3,6} };
int B [K][N] = { {8,7,6}, {5,4,3} };
int C [M][N];

struct v {
   int i; /* row */
   int j; /* column */
};

void *runner(void *param); /* the thread */

int main(int argc, char *argv[]) {

   int i,j, count = 0;
   for(i = 0; i < M; i++) {
      for(j = 0; j < N; j++) {
         //Assign a row and column for each thread
         struct v *data = (struct v *) malloc(sizeof(struct v));
         data->i = i;
         data->j = j;
         /* Now create the thread passing it data as a parameter */
         pthread_t tid;       //Thread ID
         pthread_attr_t attr; //Set of thread attributes
         //Get the default attributes
         pthread_attr_init(&attr);
         //Create the thread
         pthread_create(&tid,&attr,runner,data);
         //Make sure the parent waits for all thread to complete
         pthread_join(tid, NULL);
         count++;
      }
   }

   //Print out the resulting matrix
   for(i = 0; i < M; i++) {
      for(j = 0; j < N; j++) {
         printf("%d ", C[i][j]);
     }
      printf("\n");
   }
}

//The thread will begin control in this function
void *runner(void *param) {
   struct v *data = param; // the structure that holds our data
   int n, sum = 0; //the counter and sum

   //Row multiplied by column
   for(n = 0; n< K; n++){
      sum += A[data->i][n] * B[n][data->j];
   }
   //assign the sum to its coordinate
   C[data->i][data->j] = sum;

   //Exit the thread
   pthread_exit(0);
}

источник: http://macboypro.com/blog/2009/06/29/matrix-multiplication-in-c-using-pthreads-on-linux/

Для не поточной версии я использовал ту же настройку (3 2-мерные матрицы, динамически распределенные структуры для хранения r / c) и добавил таймер. Первые испытания показали, что версия без резьбы была быстрее. Моей первой мыслью было, что размеры слишком малы, чтобы заметить разницу, и создание потоков заняло больше времени. Поэтому я увеличил размеры примерно до 50x50, заполнил случайным образом и запустил его, и я до сих пор не вижу никакого повышения производительности с помощью многопоточной версии.

Что мне здесь не хватает?

Ответы [ 5 ]

11 голосов
/ 07 июня 2010

Если вы не работаете с очень большими матрицами (много тысяч строк / столбцов), то вряд ли вы заметите значительное улучшение от этого подхода. Настройка потока в современном CPU / OS на самом деле довольно дорогая с точки зрения процессорного времени, гораздо больше времени, чем несколько операций умножения.

Кроме того, обычно не стоит настраивать более одного потока для каждого доступного ядра процессора. Если у вас, скажем, только два ядра, и вы настроили 2500 потоков (для матриц 50x50), то ОС будет тратить все свое время на управление и переключение между этими 2500 потоками, а не на ваши вычисления.

Если вам нужно было предварительно настроить два потока (по-прежнему предполагая двухъядерный процессор), оставляйте эти потоки доступными все время, ожидая выполнения работы, и предоставьте им продукты с 2500 точками, которые нужно рассчитать в некотором роде синхронизированной рабочей очереди, то вы могли бы начать видеть улучшение. Тем не менее, он никогда не будет более чем на 50% лучше, чем использование только одного ядра.

7 голосов
/ 07 июня 2010

Я не совсем уверен, что понимаю исходный код, но вот как это выглядит: у вас есть цикл, который выполняется M * N раз.Каждый раз в цикле вы создаете поток, который заполняет одно число в матрице результатов.Но сразу после запуска потока вы ждете его завершения.Я не думаю, что вы когда-либо выполняли более одного потока.

Даже если вы выполняли более одного потока, поток выполняет тривиальный объем работы.Даже если K было большим (вы упомянули 50), 50 умножений не так много по сравнению со стоимостью запуска потока в первую очередь.Программа должна создавать меньше потоков - конечно, не больше, чем число процессоров - и назначать больше работы каждому.

1 голос
/ 07 июня 2010

Если у вас процессор с двумя ядрами, то вам нужно просто разделить работу, которую нужно выполнить, на две половины и дать каждому потоку половину. Тот же принцип, если у вас 3, 4, 5 ядер. Схема оптимальной производительности всегда будет соответствовать количеству потоков и количеству доступных ядер (под доступными я подразумеваю ядра, которые еще не используются другими процессами).

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

Чтобы лучше понять эти проблемы, я бы порекомендовал книгу Шаблоны для параллельного программирования http://astore.amazon.com/amazon-books-20/detail/0321228111

Хотя примеры кода в большей степени ориентированы на OpenMP и MPI, а вы используете PThreads, тем не менее первая половина книги очень богата фундаментальными концепциями и внутренней работой многопоточных сред, что очень полезно, чтобы избежать большей производительности узкие места, с которыми вы столкнетесь.

1 голос
/ 07 июня 2010

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

0 голосов
/ 07 июня 2010

При условии, что код распараллеливается правильно (я не буду это проверять), вероятно, производительность повышается только тогда, когда код распараллеливается на аппаратном уровне, то есть потоки действительно параллельны (многоядерные, многопроцессорные, ... другие технологии ...)и не внешне («многозадачный» способ) параллельным.Просто идея, я не уверен, что это так.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...