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

Таким образом, я понимаю, что этот вопрос звучит глупым (и да я использую двухъядерное), но я попробовал две различных библиотеки (Центральная Отправка и OpenMP), и при использовании часов () ко времени код с и без строк, которые заставляют его быть параллельным, скорость является тем же. (для записи они оба использовали свою собственную форму параллели для). Они сообщают о том, чтобы быть выполненным относительно различных потоков, но возможно они работают на том же ядре? Там какой-либо путь состоит в том, чтобы проверить? (Обе библиотеки для C, мне неудобно на нижних уровнях.) Это супер странно. Какие-либо идеи?

6
задан skaffman 8 March 2010 в 23:05
поделиться

6 ответов

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

Хотя другие ответы здесь полезны в целом, конкретный ответ на ваш вопрос заключается в том, что вам не следует использовать 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 без прагмы , поэтому использование openMP с одним потоком немного медленнее, чем использование openMP вообще, но время стены уменьшается с каждым потоком.

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

РЕДАКТИРОВАТЬ: Для 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 , в этих двух примерах различается, но это не слишком сильно влияет на результаты. См. STRIDE на странице руководства для dispatch_apply , чтобы узнать, как это сделать правильно и почему итераций в целом сопоставимы с 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 Использование окна / ЦП ), вы можете увидеть, как задания переключаются между ядрами, так что core! = Thread.

19
ответ дан 8 December 2019 в 03:09
поделиться

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

0
ответ дан 8 December 2019 в 03:09
поделиться

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

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

8
ответ дан 8 December 2019 в 03:09
поделиться

Я использую функцию omp_get_thread_num () в моем параллельном цикле, чтобы распечатать, на каком ядре он работает , если вы этого не сделаете » t укажите 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 for default (none) shared (a, b, c)

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

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

0
ответ дан 8 December 2019 в 03:09
поделиться

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

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

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

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

1
ответ дан 8 December 2019 в 03:09
поделиться

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

2
ответ дан 8 December 2019 в 03:09
поделиться
Другие вопросы по тегам:

Похожие вопросы: