2012-07-01 46 views
18

Hôm nay tôi đã quyết định điểm chuẩn và so sánh một số khác biệt về khả năng tối ưu hóa gcc của std::vectorstd::array. Nói chung, tôi đã tìm thấy những gì tôi mong đợi: thực hiện tác vụ trên mỗi bộ sưu tập các mảng ngắn nhanh hơn nhiều so với thực hiện các tác vụ trên một vectơ tương đương thu thập.Tại sao mảng <T, N> lại chậm hơn vector <T>?

Tuy nhiên, tôi tìm thấy một cái gì đó bất ngờ : sử dụng std::vector để lưu trữ các bộ sưu tập của mảng là nhanh hơn so với sử dụngstd::array. Chỉ trong trường hợp nó là kết quả của một số tạo phẩm của một lượng lớn dữ liệu trên stack, tôi cũng đã cố gắng phân bổ nó như một mảng trên heap và trong một mảng kiểu C trên heap (nhưng kết quả vẫn giống như một mảng mảng trên ngăn xếp và một vectơ của mảng).

Bất cứ ý tưởng tại sao std::vector sẽ bao giờ tốt hơn std::array (mà trên đó các trình biên dịch có nhiều thời gian biên dịch thông tin)?

Tôi đã biên soạn bằng cách sử dụng gcc-4.7 -std=c++11 -O3 (gcc-4.6 -std=c++0x -O3 cũng nên dẫn đến câu hỏi hóc búa này). Thời gian chạy được tính bằng cách sử dụng lệnh bash -native time (thời gian người dùng).

Code:

#include <array> 
#include <vector> 
#include <iostream> 
#include <assert.h> 
#include <algorithm> 

template <typename VEC> 
double fast_sq_dist(const VEC & lhs, const VEC & rhs) { 
    assert(lhs.size() == rhs.size()); 
    double result = 0.0; 
    for (int k=0; k<lhs.size(); ++k) { 
    double tmp = lhs[k] - rhs[k]; 
    result += tmp * tmp; 
    } 
    return result; 
} 

int main() { 
    const std::size_t K = 20000; 
    const std::size_t N = 4; 

    // declare the data structure for the collection 
    // (uncomment exactly one of these to time it) 

    // array of arrays 
    // runtime: 1.32s 
    std::array<std::array<double, N>, K > mat; 

    // array of arrays (allocated on the heap) 
    // runtime: 1.33s 
    // std::array<std::array<double, N>, K > & mat = *new std::array<std::array<double, N>, K >; 

    // C-style heap array of arrays 
    // runtime: 0.93s 
    // std::array<double, N> * mat = new std::array<double, N>[K]; 

    // vector of arrays 
    // runtime: 0.93 
    // std::vector<std::array<double, N> > mat(K); 

    // vector of vectors 
    // runtime: 2.16s 
    // std::vector<std::vector<double> > mat(K, std::vector<double>(N)); 

    // fill the collection with some arbitrary values 
    for (std::size_t k=0; k<K; ++k) { 
    for (std::size_t j=0; j<N; ++j) 
     mat[k][j] = k*N+j; 
    } 

    std::cerr << "constructed" << std::endl; 

    // compute the sum of all pairwise distances in the collection 
    double tot = 0.0; 
    for (std::size_t j=0; j<K; ++j) { 
    for (std::size_t k=0; k<K; ++k) 
     tot += fast_sq_dist(mat[j], mat[k]); 
    } 

    std::cout << tot << std::endl; 

    return 0; 
} 

NB 1: Tất cả các phiên bản in cùng một kết quả.

NB 2: Và chỉ để chứng minh rằng sự khác biệt giữa thời gian chạy std::array<std::array<double, N>, K>, std::vector<std::array<double, N> >, và std::vector<std::vector<double> > không chỉ đơn giản là từ hoạt động chuyển/khởi khi phân bổ, các runtimes chỉ đơn giản là phân bổ các bộ sưu tập (ví dụ cho ý kiến ​​ra việc tính toán và in ấn của tot) là 0,000, 0,000 và 0,004 giây, tương ứng.

NB 3: Mỗi phương pháp được biên dịch và chạy riêng biệt (không được định thời gian quay lại trong cùng một tệp thực thi), để ngăn chặn sự khác biệt không công bằng trong bộ nhớ đệm.

NB 4:
hội cho mảng của các mảng: http://ideone.com/SM8dB
hội vector của các mảng: http://ideone.com/vhpJv
hội vector của vector: http://ideone.com/RZTNE

NB 5: Chỉ cần để được hoàn toàn rõ ràng , Tôi không có ý định chỉ trích STL. Một STL hoàn toàn yêu thích và, không chỉ tôi sử dụng nó thường xuyên, chi tiết về việc sử dụng hiệu quả đã dạy tôi rất nhiều tính năng tinh tế và tuyệt vời của C++. Thay vào đó, đây là một sự theo đuổi trí tuệ: Tôi chỉ đơn giản là thời gian để tìm hiểu các nguyên tắc của thiết kế C++ hiệu quả.

Hơn nữa, sẽ không thể đổ lỗi cho STL, vì khó phân tích nguyên nhân của sự khác biệt thời gian chạy: Với tối ưu hóa được bật, nó có thể từ tối ưu hóa trình biên dịch làm chậm mã thay vì làm nhanh nó.Với tối ưu hóa tắt, nó có thể là từ các hoạt động sao chép không cần thiết (sẽ được tối ưu hóa và không bao giờ được thực thi trong mã sản xuất), có thể thiên vị đối với một số loại dữ liệu nhất định.

Nếu bạn tò mò như tôi, tôi rất muốn bạn giúp tìm hiểu điều này.

+5

Hãy thử chạy với số lần lặp như 1000 để xem các giá trị chính xác hơn. Những cái nhìn như họ chỉ có thể là giá trị độ trễ. –

+0

@ColeJohnson Bạn có nghĩa là 'N = 1000' hoặc' K = 1000'? Nếu bạn có nghĩa là 'N = 1000', một vec tơ của mảng gần giống với vectơ của vec-tơ (vì chi phí của việc không bỏ vòng lặp là rất cao). Sử dụng 'N = 1' dẫn đến sự khác biệt cao hơn nhiều giữa vector của mảng và vector của vectơ, bởi vì vector của mảng nên về cơ bản được chuyển đổi thành vectơ gấp đôi. Vì vậy, trường hợp thú vị nhất để so sánh mảng mảng và vectơ của mảng là 'K << N' (' << 'trong nghĩa của toán học, chứ không phải ý nghĩa thay đổi bit). – user

+0

Điều gì sẽ xảy ra nếu bạn trao đổi hai bài kiểm tra? – Mehrdad

Trả lời

0

Một điều mà bạn cần lưu ý là một vật thể lớn như vậy trên ngăn xếp trong một lần có thể kích hoạt phân bổ lại không gian ngăn xếp của hệ điều hành. Thử bán phá giá/proc/self/maps ở cuối chính

+2

Huh, đó có phải là điều mà một hệ điều hành thực sự có thể làm không? Tôi sẽ nghĩ rằng việc phân bổ lại ngăn xếp sẽ làm mất hiệu lực bất kỳ con trỏ-to-stack-đối tượng chương trình có thể có, dẫn đến một sự sụp đổ của chương trình ... –

+1

Để đảm bảo nó không được gây ra bằng cách sử dụng ngăn xếp theo cách này, Tôi có một thử nghiệm ở trên, nơi tôi phân bổ mảng mảng trên heap-- tôi nhận được cùng một thời gian chạy. – user

+0

@Jeremy: Đúng vậy. Việc phân bổ lại không phải là vấn đề vì địa chỉ của ngăn xếp nằm ở đầu kia của không gian địa chỉ bộ nhớ ảo từ đống và những thứ được phân bổ bằng mmap. Các trang vật lý chỉ có thể được ánh xạ vào cuối. – notlostyet

3

Hãy xem xét các thử nghiệm thứ hai và thứ ba. Về mặt khái niệm, chúng giống hệt nhau: Phân bổ K * N * sizeof(double) byte khỏi heap và sau đó truy cập chúng theo cách giống hệt nhau. Vậy tại sao lại là thời điểm khác nhau?

Tất cả các bài kiểm tra "nhanh hơn" của bạn có một điểm chung: new[]. Tất cả các bài kiểm tra chậm hơn được phân bổ với new hoặc trên ngăn xếp. vector có thể sử dụng new[] Under the Hood ™. Nguyên nhân rõ ràng nhất cho điều này là new[]new có các triển khai khác nhau đáng kể hơn dự kiến.

Điều tôi sẽ đề xuất là new[] sẽ quay trở lại mmap và phân bổ trực tiếp trên một ranh giới trang, giúp bạn tăng tốc liên kết, trong khi hai phương pháp khác sẽ không phân bổ trên ranh giới trang.

Cân nhắc sử dụng chức năng phân bổ OS để ánh xạ trực tiếp các trang đã cam kết và sau đó đặt std::array<std::array<double, N>, K> vào đó.

+0

Tôi đã thử 'std :: mảng , K> & mat = * new std :: mảng , K> [1];' để buộc sử dụng 'new []', nhưng nó cho cùng thời gian chạy như mảng mảng ... – user

+10

Trừ khi bạn cung cấp một bộ cấp phát để làm như vậy, 'vectơ' sẽ không sử dụng' new [] '" bên dưới mui xe ". Nó sử dụng bất cứ điều gì cung cấp cấp phát. Trừ khi bạn chỉ định khác, nó sử dụng 'std :: allocator '. Điều đó, đến lượt nó, sẽ sử dụng 'toán tử new' để cấp phát bộ nhớ thô. –

+0

Ồ vâng. Quên về những người phân bổ. – Puppy

1

Đừng tìm kiếm các giải thích phức tạp khi những giải thích đơn giản là đủ. Đó là lỗi của trình tối ưu hóa. Mảng ngăn xếp kiểu C cố định kiểu C cố định cũ cho hiệu năng tương tự như std::array, vì vậy đừng đổ lỗi cho việc thực hiện std::array.

+0

Tôi không nói bạn đã đổ lỗi cho STL. Tôi chỉ nói rằng bạn không nên, chỉ trong trường hợp. BTW Tôi đã thử nó với -O2 và tất cả các biến thể đã có performance.l hầu như giống hệt nhau trên máy tính của tôi. –

+0

Thú vị ... có lẽ nếu bạn cố gắng tăng 'K'? Tôi đang chạy trên một i7 lõi, nhưng một máy tính xách tay dù sao, do đó, nó có thể cần quy mô lớn hơn để được rõ ràng về phần cứng tốt hơn. Bất kể, tôi bị sốc rằng véc tơ của mảng không nhanh hơn vectơ của vectơ cho bạn-- cái đó có ý nghĩa trực quan với tôi (khi 'K' lớn hơn nhiều so với' N'). Điều đó có đáng ngạc nhiên với bạn không? – user

0

Sự khác biệt lớn duy nhất tôi thấy là dữ liệu của bạn được lưu trữ khác nhau. Trong hai trường hợp đầu tiên, dữ liệu của bạn được lưu trữ trong một đoạn lớn. Tất cả các trường hợp khác lưu trữ con trỏ tới các hàng trong ma trận của bạn. Tôi không hoàn toàn biết tại sao điều đó làm cho mã của bạn nhanh hơn nhưng nó có thể liên quan đến tra cứu và tìm nạp trước CPU. Hãy thử caching hàng ma trận của bạn trước khi bạn lặp lại nó vì vậy bạn không cần phải tra cứu mat[k] cho mỗi mục nhập. Điều đó có thể làm cho nó nhanh hơn và thậm chí cả tốc độ. Nó có thể là trình biên dịch của bạn có thể làm điều này trong trường hợp vector<array<T>> nhưng không phải trong trường hợp array<array<T>>.

+0

Tôi nghĩ rằng 'mảng >' và 'vector >' cả hai lưu trữ nó trong một khối lớn (trừ vector lưu trữ khối trên đống). 'mảng >' hoặc 'vector >' làm được nhiều hơn những gì bạn đang nói (lưu trữ một bộ sưu tập các con trỏ, một cho mỗi hàng). – user

+0

@Oliver: Bạn nói đúng. – Florian

1

Tôi vừa thử trên máy tính để bàn của mình với MSVC++ 2010 và tôi có cùng thời gian (1.6 giây) cho tất cả các bài kiểm tra ngoại trừ vector trong số vectors là 5.0 giây.

Tôi sẽ xem xét việc triển khai thư viện thực tế của bạn arrayvector để xem có bất kỳ sự khác biệt rõ ràng nào không.

Thử thay thế vòng kiểu chỉ mục bằng vòng lặp kiểu vòng lặp và xem điều đó có ảnh hưởng đến hiệu suất hay không.

Ngoài ra, hãy thử sử dụng clock() để thời gian chương trình của bạn từ bên trong chương trình: trong số những thứ khác, điều này sẽ cho phép bạn biết phần nào của mã đang hoạt động khác nhau. Nó thậm chí có thể có giá trị thêm vào trong một phạm vi lồng nhau để bạn có thể thời gian các đối tượng destructors là tốt.

3

Tôi nghi ngờ rằng khi phân bổ các array trên stack hay heap trình biên dịch chỉ cần có để sắp xếp cho array khi khi sử dụng vector 's cấp phát nó có thể sử dụng operator new mà phải trả lại bộ nhớ phù hợp thẳng hàng cho bất kỳ loại.Nếu bộ nhớ được cấp phát đó xảy ra để được căn chỉnh tốt hơn cho phép nhiều lần truy cập bộ nhớ cache/lần đọc lớn hơn thì có vẻ như nó có thể dễ dàng giải thích sự khác biệt về hiệu suất.

+0

+1 Ý nghĩ hay. Tôi đã thử nó với 'int' như kiểu nội bộ (với kết quả tương tự), nhưng tôi tự hỏi nếu sử dụng các loại khác sẽ sắp xếp mảng tốt hơn? Có thể đáng thử với 'float',' char', 'T *', v.v. Ngoài ra, câu trả lời của bạn sẽ giải thích tại sao sự khác biệt tốc độ vẫn xảy ra với các tối ưu hóa tại '-O0',' -O', và '-O3'. – user

Các vấn đề liên quan