C - потоки используют только одно ядро

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

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

Вот программа, использующая pthreads:

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

// This complicated ternary statement does the bulk of our work.
// Basically depending on whether or not we're at an even number in our
// sequence we'll call the function with x/32000 multiplied by 2 or 4.
#define TERN_STMT(x) (((int)x%2==0)?2*func(x/32000):4*func(x/32000)) 

// Set to 0 for no 100,000 runs
#define SPEED_TEST 1

struct func_range {
  double start;
  double end;
};

// The function defined in the assignment
double func(double x)
{
  return 4 / (1 + x*x);
}

void *partial_sum(void *r) 
{
  double *ret = (double *)malloc(sizeof(double));
  struct func_range *range = r;
#if SPEED_TEST
  int k;
  double begin = range->start;
  for (k = 0; k < 25000; k++)
  {
    range->start = begin;
    *ret = 0;
#endif
    for (; range->start <= range->end; ++range->start)
      *ret += TERN_STMT(range->start);
#if SPEED_TEST
  }
#endif

  return ret;
}

int main()
{
  // An array for our threads.
  pthread_t threads[4];
  double total_sum = func(0);
  void *temp;
  struct func_range our_range;
  int i;

  for (i = 0; i < 4; i++)
  {
    our_range.start = (i == 0) ? 1 : (i == 1) ? 8000 : (i == 2) ? 16000 : 24000;
    our_range.end = (i == 0) ? 7999 : (i == 1) ? 15999 : (i == 2) ? 23999 : 31999;
    pthread_create(&threads[i], NULL, &partial_sum, &our_range);
    pthread_join(threads[i], &temp);
    total_sum += *(double *)temp;
    free(temp);
  }

  total_sum += func(1);

  // Final calculations
  total_sum /= 3.0;
  total_sum *= (1.0/32000.0);

  // Print our result
  printf("%f\n", total_sum);

  return EXIT_SUCCESS;
}

Вот использование дочерних процессов:

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

// This complicated ternary statement does the bulk of our work.
// Basically depending on whether or not we're at an even number in our
// sequence we'll call the function with x/32000 multiplied by 2 or 4.
#define TERN_STMT(x) (((int)x%2==0)?2*func(x/32000):4*func(x/32000)) 

// Set to 0 for no 100,000 runs
#define SPEED_TEST 1

// The function defined in the assignment
double func(double x)
{
  return 4 / (1 + x*x);
}

int main()
{
  // An array for our subprocesses.
  pid_t pids[4];
  // The pipe to pass-through information
  int mypipe[2];
  // Counter for subproccess loops
  double j;
  // Counter for outer loop
  int i;
  // Number of PIDs
  int n = 4;
  // The final sum
  double total_sum = 0;
  // Temporary variable holding the result from a subproccess
  double temp;
  // The partial sum tallied by a subproccess.
  double sum = 0;
  int k;

  if (pipe(mypipe))
  {
    perror("pipe");
    return EXIT_FAILURE;
  }

  // Create the PIDs
  for (i = 0; i < 4; i++)
  {
    // Abort if something went wrong
    if ((pids[i] = fork()) < 0)
    {   
      perror("fork");
      abort();
    }   
    else if (pids[i] == 0)
  // Depending on what PID number we are we'll only calculate
      // 1/4 the total.
#if SPEED_TEST
      for (k = 0; k < 25000; ++k)
      {
        sum = 0;
#endif
        switch (i)
        {
          case 0:
            sum += func(0);
            for (j = 1; j <= 7999; ++j)
              sum += TERN_STMT(j);
            break;
          case 1:
            for (j = 8000; j <= 15999; ++j)
              sum += TERN_STMT(j);
            break;
          case 2:
            for (j = 16000; j <= 23999; ++j)
              sum += TERN_STMT(j);
            break;
          case 3:
            for (j = 24000; j < 32000; ++j)
              sum += TERN_STMT(j);
            sum += func(1);
            break;
        }
#if SPEED_TEST
      }
#endif
      // Write the data to the pipe
      write(mypipe[1], &sum, sizeof(sum));
      exit(0);
    }
  }

  int status;
  pid_t pid;
  while (n > 0)
  {
    // Wait for the calculations to finish
    pid = wait(&status);
    // Read from the pipe
    read(mypipe[0], &temp, sizeof(total_sum));
    // Add to the total
    total_sum += temp;
    n--;
  }

  // Final calculations
  total_sum /= 3.0;
  total_sum *= (1.0/32000.0);

  // Print our result
  printf("%f\n", total_sum);

  return EXIT_SUCCESS;
}

Вот результат time для версии pthreads, запущенной 100 000 раз:

real 11.15
user 11.15
sys 0.00

А вот версия дочернего процесса:

real 5.99
user 23.81
sys 0.00

Пользовательское время 23,81 означает, что это сумма времени, которое потребовалось каждому ядру для выполнения кода. При анализе pthread реальное/пользовательское время совпадает, что означает, что используется только одно ядро. Почему он не использует все 4 ядра? Я думал, что по умолчанию он может делать это лучше, чем дочерние процессы.

Надеюсь, этот вопрос имеет смысл, я впервые программирую с помощью pthreads, и я довольно новичок в программировании на уровне ОС в целом.

Спасибо, что нашли время прочитать этот длинный вопрос.


person LainIwakura    schedule 14.02.2014    source источник


Ответы (1)


Когда вы говорите pthread_join сразу после pthread_create, вы фактически сериализуете все потоки. Не присоединяйте потоки до тех пор, пока вы не создадите все потоки и не выполните всю остальную работу, для которой не требуется результат вычислений с потоками.

person Kerrek SB    schedule 14.02.2014
comment
Я переместил pthread_join за пределы цикла for в его собственный цикл for вместе с добавлением и бесплатным, а что нет, но теперь ответы каждый раз разные... ясно, что я что-то не понимаю в потоках, есть ли что-нибудь, что я могу прочитать это показывает, как решить эту проблему? - person LainIwakura; 14.02.2014
comment
@LainIwakura: Вы имеете в виду, пожалуйста, объясните, как работает многопоточное программирование, в двухстрочном комментарии? Боюсь, это немного выходит за рамки. В вашем классе этому не учат? - person Kerrek SB; 14.02.2014
comment
Ненавижу это говорить, но класс отстой. Я понял, почему я получаю разные значения - потоки делили структуру диапазона. Я исправил это, имея массив структур... теперь я получаю правильный ответ, но программа работает еще медленнее! (16,52 реального времени, 65,05 пользовательского времени!) Похоже, что мои ядра используются, но производительность не превосходит версию программы, использующую fork() - person LainIwakura; 14.02.2014
comment
@LainIwakura: Что ж, как говорится, параллельное программирование легко, если вас не волнует производительность... - person Kerrek SB; 14.02.2014
comment
@LainIwakura: Вы, вероятно, видите ложное совместное использование в своих структурах func_range, поскольку 4 из них, вероятно, помещаются в одну строку кэша. Возьмите локальную копию структуры func_range в функции partial_sum и используйте ее. - person caf; 14.02.2014