Почему мой компьютер не показывает ускорение при использовании параллельного кода? - PullRequest
6 голосов
/ 09 марта 2010

Так что я понимаю, что этот вопрос звучит глупо (и да, я использую двухъядерное ядро), но я пробовал две разные библиотеки (Grand Central Dispatch и OpenMP), и при использовании clock () время кода с и без Линии, которые делают это параллельно, скорость одинакова. (для записи они оба использовали свою собственную форму параллели для). Они сообщают, что работают в разных потоках, но, возможно, они работают на одном и том же ядре? Есть ли способ проверить? (Обе библиотеки для C, мне неудобно на нижних уровнях.) Это супер странно. Есть идеи?

Ответы [ 6 ]

19 голосов
/ 09 марта 2010

РЕДАКТИРОВАТЬ: Добавлены детали для Grand Central Dispatch в ответ на комментарий OP.

Хотя остальные ответы здесь в целом полезны, конкретный ответ на ваш вопрос заключается в том, что вам не следует использовать clock() для сравнения времени. clock() измеряет время процессора, которое складывается между потоками. Когда вы разделяете работу между ядрами, она использует как минимум столько же процессорного времени (как правило, немного больше из-за непроизводительных издержек). Выполните поиск clock () на этой странице, чтобы найти «Если процесс многопоточный, добавляется время процессора, потребляемое всеми отдельными потоками процесса».

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

#include <omp.h>
#include <time.h>
#include <math.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
    int i, nthreads;
    clock_t clock_timer;
    double wall_timer;
    for (nthreads = 1; nthreads <=8; nthreads++) {
        clock_timer = clock();
        wall_timer = omp_get_wtime();
        #pragma omp parallel for private(i) num_threads(nthreads)
        for (i = 0; i < 100000000; i++) cos(i);
        printf("%d threads: time on clock() = %.3f, on wall = %.3f\n", \
            nthreads, \
            (double) (clock() - clock_timer) / CLOCKS_PER_SEC, \
            omp_get_wtime() - wall_timer);
    }
}

Результаты:

1 threads: time on clock() = 0.258, on wall = 0.258
2 threads: time on clock() = 0.256, on wall = 0.129
3 threads: time on clock() = 0.255, on wall = 0.086
4 threads: time on clock() = 0.257, on wall = 0.065
5 threads: time on clock() = 0.255, on wall = 0.051
6 threads: time on clock() = 0.257, on wall = 0.044
7 threads: time on clock() = 0.255, on wall = 0.037
8 threads: time on clock() = 0.256, on wall = 0.033

Вы можете видеть, что время clock() мало что меняет. Я получаю 0,254 без pragma, поэтому использование openMP с одним потоком немного медленнее, чем вообще без использования openMP, но время задержки уменьшается с каждым потоком.

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

EDIT: Для Grand Central Dispatch ссылка GCD гласит, что GCD использует gettimeofday для настенного времени. Итак, я создаю новое приложение Какао, и в applicationDidFinishLaunching я положил:

struct timeval t1,t2;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int iterations = 1; iterations <= 8; iterations++) {
    int stride = 1e8/iterations;
    gettimeofday(&t1,0);
    dispatch_apply(iterations, queue, ^(size_t i) { 
        for (int j = 0; j < stride; j++) cos(j); 
    });
    gettimeofday(&t2,0);
    NSLog(@"%d iterations: on wall = %.3f\n",iterations, \
                t2.tv_sec+t2.tv_usec/1e6-(t1.tv_sec+t1.tv_usec/1e6));
}

и я получаю следующие результаты на консоли:

2010-03-10 17:33:43.022 GCDClock[39741:a0f] 1 iterations: on wall = 0.254
2010-03-10 17:33:43.151 GCDClock[39741:a0f] 2 iterations: on wall = 0.127
2010-03-10 17:33:43.236 GCDClock[39741:a0f] 3 iterations: on wall = 0.085
2010-03-10 17:33:43.301 GCDClock[39741:a0f] 4 iterations: on wall = 0.064
2010-03-10 17:33:43.352 GCDClock[39741:a0f] 5 iterations: on wall = 0.051
2010-03-10 17:33:43.395 GCDClock[39741:a0f] 6 iterations: on wall = 0.043
2010-03-10 17:33:43.433 GCDClock[39741:a0f] 7 iterations: on wall = 0.038
2010-03-10 17:33:43.468 GCDClock[39741:a0f] 8 iterations: on wall = 0.034

что примерно так же, как я получал выше.

Это очень надуманный пример. Фактически, вы должны быть уверены, что оптимизация будет сохранена на уровне -O0, иначе компилятор поймет, что мы не выполняем какие-либо вычисления и вообще не выполняем цикл. Кроме того, целое число, которое я беру cos, отличается в двух примерах, но это не сильно влияет на результаты. См. STRIDE на странице руководства для dispatch_apply, чтобы узнать, как это сделать правильно, и почему iterations в целом сопоставимо с num_threads в этом случае.

РЕДАКТИРОВАТЬ: я отмечаю, что ответ Иакова включает

Я использую omp_get_thread_num () функция внутри моего параллельного цикла распечатать какое ядро ​​работает на ... Таким образом, вы можете быть уверены, что он работает на обоих ядрах.

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

#include <omp.h>
#include <stdio.h>

int main() {
    int i;
    #pragma omp parallel for private(i) num_threads(50)
    for (i = 0; i < 50; i++) printf("%d\n", omp_get_thread_num());
}

распечатывает, что он использует потоки от 0 до 49, но это не показывает, на каком ядре он работает, так как у меня только восемь ядер. Посмотрев на Activity Monitor (OP упоминал GCD, поэтому должен быть на Mac - go Window/CPU Usage), вы можете увидеть, как задания переключаются между ядрами, поэтому core! = Thread.

8 голосов
/ 09 марта 2010

Скорее всего, ваше время выполнения не связано с теми циклами, которые вы распараллелили.

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

2 голосов
/ 09 марта 2010

Трудно угадать без каких-либо подробностей. Возможно, ваше приложение даже не связано с процессором. Вы смотрели загрузку процессора во время работы вашего кода? Удалось ли ему 100% хотя бы на одном ядре?

1 голос
/ 09 марта 2010

В вашем вопросе отсутствуют некоторые очень важные детали, такие как характер вашего приложения, какую часть вы пытаетесь улучшить, профилирование результатов (если есть) и т. Д. *

Сказав, что вы должны помнить несколько критических моментов при приближении к усилию по повышению производительности:

  • Усилия всегда должны концентрироваться на областях кода, которые были доказаны, профилированием , чтобы быть неэффективными
  • Распараллеливание кода, связанного с процессором, почти никогда не улучшит производительность (на одноядерном компьютере). Вы будете тратить драгоценное время на ненужные переключения контекста и получать ничто . Делая это, вы можете очень просто ухудшить производительность .
  • Даже если вы распараллеливаете связанный с ЦП код на многоядерном компьютере, вы должны помнить, что у вас никогда не будет никакой гарантии параллельного выполнения.

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

0 голосов
/ 09 марта 2010

Я использую функцию omp_get_thread_num() в моем параллельном цикле, чтобы распечатать, на каком ядре он работает , если вы не укажете num_threads. Например,

printf("Computing bla %d on core %d/%d ...\n",i+1,omp_get_thread_num()+1,omp_get_max_threads());

Вышеупомянутое будет работать для этой прагмы #pragma omp параллельный для default (нет) общий (a, b, c)

Таким образом, вы можете быть уверены, что он работает на обоих ядрах, поскольку будут созданы только 2 потока.

Кстати, включен ли OpenMP при компиляции? В Visual Studio его необходимо включить на страницах свойств , C++ -> Language и установить OpenMP Support на Yes

0 голосов
/ 09 марта 2010

Если вы используете много памяти внутри цикла, это может помешать ее ускорению. Также вы можете заглянуть в библиотеку pthread, чтобы вручную обрабатывать потоки.

...