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

StackOverflow https://stackoverflow.com/questions/2405461

Вопрос

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

Это было полезно?

Решение

Редактировать:Добавлена подробная информация для 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, но время обработки уменьшается с каждым потоком.

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

Редактировать:Что касается Grand Central Dispatch, то Ссылка на GCD утверждает, что GCD использует gettimeofday на время установки на стене.Итак, я создаю новое приложение Cocoa, и в 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 значение of отличается в двух примерах, но это не слишком влияет на результаты.Смотрите на 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, но это не показывает, на каком ядре он работает, поскольку у меня всего восемь ядер.Посмотрев на монитор активности (в OP упоминается GCD, поэтому он должен быть на Mac - go Window/CPU Usage), вы можете видеть, как задания переключаются между ядрами, так что core!= поток.

Другие советы

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

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

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

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

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

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

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

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

Я использую 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 parallel для общего доступа по умолчанию (отсутствует) (a, b, c)

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top