2014-10-17 1 views
2

Я заинтересован в портировании кода, который я написал, используя в основном библиотеку Thrust GPU для многоядерных процессоров. К счастью, the website говорит, что код тяги может использоваться с потоковыми средами, такими как OpenMP/Intel TBB.Использование тяги с openmp: не получено существенной скорости

Я написал простой код для сортировки большого массива, чтобы увидеть ускорение, используя машину, которая может поддерживать до 16 открытых потоков MP.

Таймингов, полученные на этой машине для сортировки случайного массива размера 16 миллионов являются

СТЛ: 1,47 сек
Упорные (16 нитей): 1.21 с

Там, кажется, едва ли Скорость- вверх. Я хотел бы знать, как добиться существенного ускорения сортировки массивов с помощью OpenMP, как и с графическими процессорами.

Код ниже (файл sort.cu). Компиляция была выполнена следующим образом:

NVCC -O2 -o рода sort.cu -Xcompiler -fopenmp -DTHRUST_DEVICE_SYSTEM = THRUST_DEVICE_BACKEND_OMP -lgomp

Версия NVCC 5.5 библиотека версия Thrust используется в v1. 7,0

#include <iostream> 
#include <iomanip> 
#include <cmath> 
#include <cstdlib> 
#include <stdio.h> 
#include <algorithm>  
#include <ctime> 
#include <time.h> 
#include "thrust/sort.h"  

int main(int argc, char *argv[]) 
{ 
    int N = 16000000; 
    double* myarr = new double[N]; 

    for (int i = 0; i < N; ++i) 
    { 
     myarr[i] = (1.0*rand())/RAND_MAX; 
    } 
    std::cout << "-------------\n"; 

    clock_t start,stop; 
    start=clock(); 
    std::sort(myarr,myarr+N); 
    stop=clock(); 

    std::cout << "Time taken for sorting the array with STL is " << (stop-start)/(double)CLOCKS_PER_SEC; 

    //-------------------------------------------- 

    srand(1); 
    for (int i = 0; i < N; ++i) 
    { 
     myarr[i] = (1.0*rand())/RAND_MAX; 
     //std::cout << myarr[i] << std::endl; 
    } 

    start=clock(); 
    thrust::sort(myarr,myarr+N); 
    stop=clock(); 

    std::cout << "------------------\n"; 


    std::cout << "Time taken for sorting the array with Thrust is " << (stop-start)/(double)CLOCKS_PER_SEC; 
    return 0; 
} 
+0

Не используйте clock(), используйте 'omp_get_wtime()'. Сортировка - это то, что я имел в виду, но поскольку это «nlog (n)», я предполагаю, что операция связана с пропускной способностью полосы пропускания и поэтому не может много выиграть от нескольких быстрых ядер. Ситуация на графическом процессоре (или XeonPhi) отличается от того, что соотношение между «основной» скоростью и скоростью памяти намного ниже. –

ответ

2

device backend refers to the behavior of operations performed on a thrust::device_vector или аналогичные ссылки. Thrust интерпретирует массив/указатель, который вы передаете ему как указатель узла, и выполняет на нем операции на основе хоста, на которые не влияет настройка брандмауэра устройства.

Существует множество способов устранить эту проблему. Если вы прочитаете документацию по серверу, вы найдете общие примеры и примеры, основанные на omp. Я думаю, вы даже можете указать другой host backend, который должен иметь желаемое поведение (использование OMP).

Как только вы исправите это, вы получите дополнительный сюрприз, возможно: толчок, по-видимому, быстро сортирует массив, но сообщает о очень длительном времени выполнения. Я считаю, что это связано (на linux, во всяком случае) до the clock() function being affected by the number of OMP threads in use.

Следующий пробел кода/образца имеет эти проблемы, и, кажется, дает мне 3-кратное ускорение для 4 потоков.

$ cat t592.cu 
#include <iostream> 
#include <iomanip> 
#include <cmath> 
#include <cstdlib> 
#include <stdio.h> 
#include <algorithm> 
#include <ctime> 
#include <sys/time.h> 
#include <time.h> 
#include <thrust/device_ptr.h> 
#include <thrust/sort.h> 

int main(int argc, char *argv[]) 
{ 
    int N = 16000000; 
    double* myarr = new double[N]; 

    for (int i = 0; i < N; ++i) 
    { 
     myarr[i] = (1.0*rand())/RAND_MAX; 
    } 
    std::cout << "-------------\n"; 

    timeval t1, t2; 
    gettimeofday(&t1, NULL); 
    std::sort(myarr,myarr+N); 
    gettimeofday(&t2, NULL); 
    float et = (((t2.tv_sec*1000000)+t2.tv_usec)-((t1.tv_sec*1000000)+t1.tv_usec))/float(1000000); 

    std::cout << "Time taken for sorting the array with STL is " << et << std::endl;; 

    //-------------------------------------------- 

    srand(1); 
    for (int i = 0; i < N; ++i) 
    { 
     myarr[i] = (1.0*rand())/RAND_MAX; 
     //std::cout << myarr[i] << std::endl; 
    } 
    thrust::device_ptr<double> darr = thrust::device_pointer_cast<double>(myarr); 
    gettimeofday(&t1, NULL); 
    thrust::sort(darr,darr+N); 
    gettimeofday(&t2, NULL); 
    et = (((t2.tv_sec*1000000)+t2.tv_usec)-((t1.tv_sec*1000000)+t1.tv_usec))/float(1000000); 

    std::cout << "------------------\n"; 


    std::cout << "Time taken for sorting the array with Thrust is " << et << std::endl ; 
    return 0; 
} 

$ nvcc -O2 -o t592 t592.cu -Xcompiler -fopenmp -DTHRUST_DEVICE_SYSTEM=THRUST_DEVICE_BACKEND_OMP -lgomp 
$ OMP_NUM_THREADS=4 ./t592 
------------- 
Time taken for sorting the array with STL is 1.31956 
------------------ 
Time taken for sorting the array with Thrust is 0.468176 
$ 

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

+0

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

+0

На самом деле пропускная способность памяти является наиболее вероятной причиной того, что производительность увеличивается примерно до 4 ядер, но затем выравнивается. Современный Intel Xeon не может насытить шину памяти от запросов, исходящих из одного ядра. Чтобы использовать полную пропускную способность памяти, доступную в одном сокете Xeon, необходимо отправлять запросы с нескольких ядер. Но примерно 4 ядра должны быть достаточными для насыщения шины, подключенной к одной розетке. Мой процессор в этом случае был Intel Ivybridge Xeon. –

+0

Я согласен, что несколько потоков необходимы, чтобы максимизировать пропускную способность даже в одной системе сокетов, я понял, что это [измерение-память-пропускная способность-из-точка-продукт-из-двух-массивов] (http: // stackoverflow .com/вопросы/25179738/измерительно-памяти полосы пропускания-из-дот-продукт-из-двух массивов). Но по моему опыту несколько потоков увеличивают пропускную способность менее чем в два раза, и вы получаете коэффициент в три, что больше, чем я ожидал. –