2012-06-22 29 views
5

Tôi đang chơi xung quanh với các chủ đề trong C++, đặc biệt là sử dụng chúng để song song một hoạt động bản đồ.C++ thread overhead

Dưới đây là các mã:

#include <thread> 
#include <iostream> 
#include <cstdlib> 
#include <vector> 
#include <math.h> 
#include <stdio.h> 

double multByTwo(double x){ 
    return x*2; 
} 

double doJunk(double x){ 
    return cos(pow(sin(x*2),3)); 
} 

template <typename T> 
void map(T* data, int n, T (*ptr)(T)){ 
    for (int i=0; i<n; i++) 
    data[i] = (*ptr)(data[i]); 
} 

template <typename T> 
void parallelMap(T* data, int n, T (*ptr)(T)){ 
    int NUMCORES = 3; 
    std::vector<std::thread> threads; 
    for (int i=0; i<NUMCORES; i++) 
    threads.push_back(std::thread(&map<T>, data + i*n/NUMCORES, n/NUMCORES, ptr)); 
    for (std::thread& t : threads) 
    t.join(); 
} 

int main() 
{ 
    int n = 1000000000; 
    double* nums = new double[n]; 
    for (int i=0; i<n; i++) 
    nums[i] = i; 

    std::cout<<"go"<<std::endl; 

    clock_t c1 = clock(); 

    struct timespec start, finish; 
    double elapsed; 

    clock_gettime(CLOCK_MONOTONIC, &start); 

    // also try with &doJunk 
    //parallelMap(nums, n, &multByTwo); 
    map(nums, n, &doJunk); 

    std::cout << nums[342] << std::endl; 

    clock_gettime(CLOCK_MONOTONIC, &finish); 

    printf("CPU elapsed time is %f seconds\n", double(clock()-c1)/CLOCKS_PER_SEC); 

    elapsed = (finish.tv_sec - start.tv_sec); 
    elapsed += (finish.tv_nsec - start.tv_nsec)/1000000000.0; 

    printf("Actual elapsed time is %f seconds\n", elapsed); 
} 

Với multByTwo phiên bản song song thực sự là hơi chậm (1,01 giây so với 0,95 thời gian thực), và với doJunk nhanh hơn (51 so với 136 thời gian thực) của nó. Điều này ngụ ý với tôi rằng

  1. sự song song đang làm việc, và
  2. có một trên không thực sự lớn với tuyên bố chủ đề mới. Bất kỳ suy nghĩ nào về lý do tại sao chi phí quá lớn, và làm thế nào tôi có thể tránh được nó?
+2

Lưu ý rằng điều này không nhất thiết phải cụ thể đối với * chuỗi gốc trong C++ *, nhưng * triển khai * và trình biên dịch bạn sử dụng. Như vậy, thật khó để đưa ra một câu trả lời dứt khoát. – zxcdw

+0

Bạn đang chạy mã phần cứng nào? Loại bộ xử lý và số lượng ổ cắm? RAM? Hệ điều hành? Phiên bản trình biên dịch? –

Trả lời

7

Chỉ cần đoán: những gì bạn có thể thấy là mã multByTwo quá nhanh đến nỗi bạn đạt được độ bão hòa của bộ nhớ. Mã sẽ không bao giờ chạy nhanh hơn bất kể sức mạnh xử lý bạn ném vào nó, bởi vì nó đã đi nhanh như nó có thể nhận được các bit đến và từ RAM.

+0

Điều này có vẻ chính xác. OP có bộ dữ liệu 8GB. 8 GB trong 1,01 giây âm thanh về quyền cho một Nehalem cao cấp hoặc một bộ xử lý thế hệ Sandy Bridge cấp thấp. – Mysticial

0

Tạo chủ đề mới có thể là hoạt động tốn kém tùy thuộc vào nền tảng. Cách dễ nhất để tránh chi phí này là để sinh ra một vài luồng khi khởi chạy chương trình và có một số loại hàng đợi công việc. Tôi tin rằng std :: async sẽ làm điều này cho bạn.

+1

OP chỉ sinh sản chúng một lần - và nhiệm vụ là khá lớn 'n = 1000000000'. Vì vậy, tôi không nghĩ rằng đây là trường hợp. – Mysticial

+0

Tệ của tôi.Không đọc đủ chặt chẽ :-P –

+0

Tôi tin rằng kết quả cuối cùng sẽ giống nhau, nếu số lượng chủ đề nhỏ hơn số được trả về bởi std :: thread :: hardware_concurrency() – manasij7479

2

Nhiều luồng chỉ có thể hoạt động nhiều hơn trong thời gian ít hơn trên máy đa lõi.

Những người khôn ngoan khác chỉ thay phiên nhau theo kiểu Round-Robin.

+0

KHÔNG PHẢI TRẢ LỜI NÀY LÀ BÁO CÁO TRUE !!! Nhìn vào câu trả lời của tôi !!! – trumpetlicks

+0

Tôi không nói về hiệu suất 'nhận thức' và giao diện người dùng. Tôi đang nói về công việc thực sự. Nếu chỉ có một bộ xử lý, chỉ có một luồng có thể chạy cùng một lúc. –

+0

Điều này đúng, nhưng cách hệ điều hành gán thời gian cho các chủ đề tạo nên sự khác biệt lớn. Tôi đã thấy điều này trong thế giới thực với các ứng dụng mà tôi đã buộc phải viết trong trường học (cách trước khi đa lõi) mà hiệu suất đã được tăng lên rất nhiều bằng cách luồng chúng. Nhìn vào câu trả lời của tôi, tôi thực hiện hiệu ứng robin tròn, tôi không sử dụng thuật ngữ đó, nhưng nó được giải thích tại sao một ứng dụng đa luồng sẽ được cung cấp thêm thời gian lát thời gian xử lý !!! – trumpetlicks

3

Bạn không chỉ định phần cứng mà bạn kiểm tra chương trình cũng như phiên bản trình biên dịch và hệ điều hành. Tôi đã thử mã của bạn trên bốn hệ thống Intel Xeon của chúng tôi theo 64-bit Scientific Linux với g++ 4.7 được biên dịch từ nguồn.

đầu tiên trên một hệ thống Xeon X7350 cũ tôi đã timings sau:

multByTwo với map

CPU elapsed time is 6.690000 seconds 
Actual elapsed time is 6.691940 seconds 

multByTwo với parallelMap trên 3 lõi

CPU elapsed time is 7.330000 seconds 
Actual elapsed time is 2.480294 seconds 

Việc tăng tốc song song là 2,7x.

doJunk với map

CPU elapsed time is 209.250000 seconds 
Actual elapsed time is 209.289025 seconds 

doJunk với parallelMap trên 3 lõi

CPU elapsed time is 220.770000 seconds 
Actual elapsed time is 73.900960 seconds 

Việc tăng tốc song song là 2.83x.

Lưu ý rằng X7350 là từ gia đình trước đây khá cũ của Nehalem "Tigerton" với xe buýt FSB và bộ điều khiển bộ nhớ dùng chung nằm ở cầu bắc. Đây là một hệ thống SMP thuần túy không có hiệu ứng NUMA.

Sau đó, tôi chạy mã của bạn trên bốn ổ cắm Intel X7550. Đây là những Xeon Nehalem ("Beckton") với bộ điều khiển bộ nhớ tích hợp vào CPU và do đó có hệ thống NUMA 4 nút. Các luồng chạy trên một socket và bộ nhớ truy cập nằm trên một socket khác sẽ chạy chậm hơn một chút. Điều này cũng đúng đối với một quá trình nối tiếp có thể di chuyển đến một socket khác bằng một số quyết định lập lịch ngu ngốc. Ràng buộc trong một hệ thống như vậy là rất quan trọng khi bạn có thể nhìn thấy từ timings:

multByTwo với map

CPU elapsed time is 4.270000 seconds 
Actual elapsed time is 4.264875 seconds 

multByTwo với map ràng buộc để nút NUMA 0

CPU elapsed time is 4.160000 seconds 
Actual elapsed time is 4.160180 seconds 

multByTwo với map ràng buộc để NUMA nút 0 và ổ cắm CPU 1

CPU elapsed time is 5.910000 seconds 
Actual elapsed time is 5.912319 seconds 

mutlByTwo với parallelMap trên 3 lõi

CPU elapsed time is 7.530000 seconds 
Actual elapsed time is 3.696616 seconds 

tăng tốc song song là chỉ 1.13x (liên quan đến việc thực hiện hàng loạt nút-bound nhanh nhất). Bây giờ với ràng buộc:

multByTwo với parallelMap trên 3 lõi ràng buộc để nút NUMA 0

CPU elapsed time is 4.630000 seconds 
Actual elapsed time is 1.548102 seconds 

tăng tốc song song là 2.69x - càng nhiều càng tốt cho CPU Tigerton.

multByTwo với parallelMap trên 3 lõi ràng buộc để NUMA nút 0 và ổ cắm CPU 1

CPU elapsed time is 5.190000 seconds 
Actual elapsed time is 1.760623 seconds 

tăng tốc song song là 2.36x - 88% trường hợp trước.

(Tôi đã quá thiếu kiên nhẫn để chờ đợi cho mã doJunk để kết thúc trên Nehalems tương đối chậm nhưng tôi sẽ mong đợi hiệu suất hơi tốt hơn như là trong trường hợp Tigerton)

Có một caveat với NUMA ràng buộc mặc dù. Nếu bạn ép buộc, ví dụ: gắn với NUMA node 0 với numactl --cpubind=0 --membind=0 ./program điều này sẽ hạn chế cấp phát bộ nhớ cho nút này và trên hệ thống cụ thể của bạn, bộ nhớ gắn với CPU 0 có thể không đủ và có thể xảy ra lỗi thời gian chạy.

Như bạn có thể thấy có các yếu tố, ngoài chi phí tạo chuỗi, có thể ảnh hưởng đáng kể đến thời gian thực thi mã của bạn. Cũng trên các hệ thống rất nhanh, chi phí có thể quá cao so với công việc tính toán được thực hiện bởi mỗi luồng. Đó là lý do tại sao khi đặt câu hỏi liên quan đến hiệu suất song song, người ta phải luôn bao gồm càng nhiều chi tiết càng tốt về phần cứng và môi trường được sử dụng để đo lường hiệu suất.

+0

Cảm ơn bạn đã phản hồi chi tiết! – andyInCambridge