2016-02-20 1 views
4

Я просто хочу написать простую программу на C++, которая создает два потока, и каждый из них заполняет вектор квадратами целых чисел (0, 1, 4, 9,. ..). Вот мой код:Многопоточная программа на C++ показывает ту же производительность, что и серийная.

#include <iostream> 
#include <vector> 
#include <functional> 
#include <thread> 
#include <time.h> 

#define MULTI 1 
#define SIZE 10000000 

void fill(std::vector<unsigned long long int> &v, size_t n) 
{ 
    for (size_t i = 0; i < n; ++i) { 
     v.push_back(i * i); 
    } 
} 

int main() 
{ 
    std::vector<unsigned long long int> v1, v2; 
    v1.reserve(SIZE); 
    v2.reserve(SIZE); 
    #if !MULTI 
    clock_t t = clock(); 
    fill(v1, SIZE); 
    fill(v2, SIZE); 
    t = clock() - t; 
    #else 
    clock_t t = clock(); 
    std::thread first(fill, std::ref(v1), SIZE); 
    fill(v2, SIZE); 
    first.join(); 
    t = clock() - t; 
    #endif 
    std::cout << (float)t/CLOCKS_PER_SEC << std::endl; 
    return 0; 
} 

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

+0

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

+0

@Kyle Извините, этого не может быть. Я пропустил это два вектора. В любом случае 'push_back' потребуется блокировка. – LogicStuff

+0

Программа генерирует последовательный доступ к записи в память к широко разнесенным адресам практически без использования вычислительного времени в двух потоках. Время выполнения в основном является функцией архитектуры кэша памяти оборудования. Две длинные последовательные последовательные записи, вероятно, такие же быстрые или быстрые, чем две последовательные записи чередования. – doug

ответ

2

Когда я исполняю свой код с MSVC2015 на i7, я наблюдаю:

  • в режиме отладки многопоточных является 14s по сравнению с 26s в monothread. Так что это почти в два раза быстрее. Результаты ожидаются.
  • в режиме освобождения, многопоточность составляет 0,3 по сравнению с 0,2 в монохромном режиме, поэтому он медленнее, как вы сообщили.

Это означает, что проблема связана с тем,

Обратите внимание, что даже если в fill() (например, в неоптимизированной версии) достаточно работы, многопоточность не будет умножать время на два. Многопоточность будет увеличивать общую пропускную способность в секунду на многоядерном процессоре, но каждый поток, взятый отдельно, может работать немного медленнее, чем обычно.

Edit: дополнительная информация

Производительность многопоточности зависит от многих факторов, в частности, например, количество ядер на вашем процессоре, ядро, используемое другими процессы, запущенных в ходе испытания, и в примечании doug в своем комментарии - профиль многопоточной задачи (т.е. памяти и вычислений).

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

enter image description here

Используя следующие функции для каждого потока:

// computation intensive 
void mytask(unsigned long long loops) 
{ 
    volatile double x; 
    for (unsigned long long i = 0; i < loops; i++) { 
     x = sin(sqrt(i)/i*3.14159); 
    } 
} 

//memory intensive 
void mytask2(vector<unsigned long long>& v, unsigned long long loops) 
{ 
    for (unsigned long long i = 0; i < loops; i++) { 
     v.push_back(i*3+10); 
    } 
} 
+0

Накладные расходы потока минимальны по сравнению с узким местом записи в оптимизированном коде. При многопоточности, когда память записывается в разные сегменты адресов, происходит дополнительная накладная синхронизация записи в память. Многопоточность работает наиболее эффективно, когда каждый поток вычисляется интенсивнее, а не интенсивнее. – doug

+0

@ doug да, это правда. Я отредактировал ответ, чтобы оценить оба эффекта, и показать некоторые экспериментальные измерения. – Christophe

0

Функция заполнения будет работать так быстро, что накладные расходы потока, вероятно, до тех пор, пока выполняется.

Замените заполнение на то, что занимает значительное количество времени. В качестве первого прохода, используйте std::this_thread::sleep_for

0

Большинство предложений право: резьб задачи будет улучшить время выполнения только если загрузка процессора cpu (в вашем случае умножение i * i) важнее загрузки доступа к общей памяти (в вашем случае v.push_back). Вы можете попробовать этот код. Вы увидите выигрыш в потоке. И вы можете использовать команду UNIX

>time ./a.out 

ко времени ваш код более легко.

#include <iostream> 
#include <vector> 
#include <functional> 
#include <thread> 
#include <time.h> 
#include <math.h> 

#define MULTI 1 
#define SIZE 10000000 

void fill(std::vector<unsigned long long int> &v, size_t n) 
{ 
    int sum = 0; 
    for (size_t i = 0; i < n; ++i) { 
     for (size_t j = 0; j < 100; ++j) { 
      sum += sqrt(i*j); 
     } 
    } 
    v.push_back(sum); 
} 

int main() 
{ 
    std::vector<unsigned long long int> v1, v2; 
    v1.reserve(SIZE); 
    v2.reserve(SIZE); 
    #if !MULTI 
    fill(v1, SIZE); 
    fill(v2, SIZE); 
    #else 
    std::thread first(fill, std::ref(v1), SIZE); 
    std::thread second(fill, std::ref(v2), SIZE); 

    first.join(); 
    second.join(); 
    #endif 
    return 0; 
} 
Смежные вопросы