Là std :: vector rất chậm so với mảng đồng bằng?


212

Tôi đã luôn nghĩ rằng đó là sự khôn ngoan chung std::vectorđược "thực hiện như một mảng", blah blah blah. Hôm nay tôi đã đi xuống và thử nghiệm nó, và có vẻ như không phải vậy:

Đây là một số kết quả thử nghiệm:

UseArray completed in 2.619 seconds
UseVector completed in 9.284 seconds
UseVectorPushBack completed in 14.669 seconds
The whole thing completed in 26.591 seconds

Đó là chậm hơn khoảng 3 - 4 lần! Không thực sự biện minh cho các bình luận " vectorcó thể chậm hơn đối với một vài nano".

Và mã tôi đã sử dụng:

#include <cstdlib>
#include <vector>

#include <iostream>
#include <string>

#include <boost/date_time/posix_time/ptime.hpp>
#include <boost/date_time/microsec_time_clock.hpp>

class TestTimer
{
    public:
        TestTimer(const std::string & name) : name(name),
            start(boost::date_time::microsec_clock<boost::posix_time::ptime>::local_time())
        {
        }

        ~TestTimer()
        {
            using namespace std;
            using namespace boost;

            posix_time::ptime now(date_time::microsec_clock<posix_time::ptime>::local_time());
            posix_time::time_duration d = now - start;

            cout << name << " completed in " << d.total_milliseconds() / 1000.0 <<
                " seconds" << endl;
        }

    private:
        std::string name;
        boost::posix_time::ptime start;
};

struct Pixel
{
    Pixel()
    {
    }

    Pixel(unsigned char r, unsigned char g, unsigned char b) : r(r), g(g), b(b)
    {
    }

    unsigned char r, g, b;
};

void UseVector()
{
    TestTimer t("UseVector");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels;
        pixels.resize(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}

void UseVectorPushBack()
{
    TestTimer t("UseVectorPushBack");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels;
            pixels.reserve(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
            pixels.push_back(Pixel(255, 0, 0));
    }
}

void UseArray()
{
    TestTimer t("UseArray");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        Pixel * pixels = (Pixel *)malloc(sizeof(Pixel) * dimension * dimension);

        for(int i = 0 ; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }

        free(pixels);
    }
}

int main()
{
    TestTimer t1("The whole thing");

    UseArray();
    UseVector();
    UseVectorPushBack();

    return 0;
}

Tôi đang làm sai hay gì đó? Hay tôi vừa làm hỏng huyền thoại hiệu suất này?

Tôi đang sử dụng chế độ Phát hành trong Visual Studio 2005 .


Trong Visual C ++ , #define _SECURE_SCL 0giảm UseVectormột nửa (giảm xuống còn 4 giây). Điều này thực sự rất lớn, IMO.


23
Một số phiên bản của vectơ khi bạn ở chế độ gỡ lỗi, thêm các hướng dẫn bổ sung để kiểm tra xem bạn không truy cập ngoài phần cuối của mảng và những thứ tương tự. Để có được thời gian thực, bạn phải xây dựng trong chế độ phát hành và bật tối ưu hóa.
Martin York

40
Thật tốt khi bạn đã đo lường thay vì tin vào những tuyên bố mà bạn đã nghe qua Internet.
P Shved

51
vector được thực hiện như một mảng. Đó không phải là "sự khôn ngoan thông thường", đó là sự thật. Bạn đã phát hiện ra rằng đó vectorlà một mảng có thể thay đổi kích thước mục đích chung. Xin chúc mừng. Như với tất cả các công cụ mục đích chung, có thể đưa ra các tình huống chuyên biệt trong đó nó là tối ưu phụ. Đó là lý do tại sao sự khôn ngoan thông thường là bắt đầu với a vectorvà xem xét các lựa chọn thay thế nếu cần thiết.
Dennis Zickefoose

37
haha, sự khác biệt về tốc độ của việc "ném bát đĩa bẩn vào bồn rửa" và "ném bát đĩa bẩn vào bồn rửa và kiểm tra xem chúng có bị vỡ không"?
Imre L

9
Trên VC2010 ít nhất có vẻ như sự khác biệt chính là malloc () nhanh hơn thay đổi kích thước (). Xóa phân bổ bộ nhớ khỏi thời gian, biên dịch với _ITERATOR_DEBUG_LEVEL == 0 và kết quả là như nhau.
Andreas Magnusson

Câu trả lời:


260

Sử dụng như sau:

g ++ -O3 Time.cpp -I <MyBoost>
./a.out
UseArray hoàn thành sau 2.196 giây
UseVector hoàn thành trong 4.412 giây
UseVectorPushBack hoàn thành trong 8.017 giây
Toàn bộ điều hoàn thành trong 14.626 giây

Vì vậy, mảng nhanh gấp đôi so với vector.

Nhưng sau khi xem mã chi tiết hơn, điều này được mong đợi; khi bạn chạy qua vectơ hai lần và mảng chỉ một lần. Lưu ý: khi bạn resize()là vectơ, bạn không chỉ phân bổ bộ nhớ mà còn chạy qua vectơ và gọi hàm tạo trên mỗi thành viên.

Sắp xếp lại mã một chút để vectơ chỉ khởi tạo mỗi đối tượng một lần:

 std::vector<Pixel>  pixels(dimensions * dimensions, Pixel(255,0,0));

Bây giờ làm lại cùng một thời gian một lần nữa:

g ++ -O3 Time.cpp -I <MyBoost>
./a.out
UseVector hoàn thành trong 2.216 giây

Hiệu suất vector bây giờ chỉ kém hơn một chút so với mảng. IMO sự khác biệt này là không đáng kể và có thể được gây ra bởi một loạt những điều không liên quan đến thử nghiệm.

Tôi cũng sẽ tính đến việc bạn không khởi tạo / Phá hủy chính xác đối tượng Pixel trong UseArrray()phương thức vì không có hàm tạo / hàm hủy nào được gọi (điều này có thể không phải là vấn đề đối với lớp đơn giản này nhưng bất cứ điều gì phức tạp hơn một chút (ví dụ với con trỏ hoặc thành viên với con trỏ) sẽ gây ra vấn đề.


48
@ kizzx2: Bạn cần sử dụng reserve()thay vì resize(). Điều này phân bổ không gian cho các đối tượng (nghĩa là nó thay đổi công suất của vectơ) nhưng không tạo ra các đối tượng (nghĩa là kích thước của vectơ không thay đổi).
James McNellis

25
Bạn đang thực hiện truy cập mảng 1 000 000 000. Chênh lệch thời gian là 0,333 giây. Hoặc chênh lệch 0,000000000333 mỗi lần truy cập mảng. Giả sử Bộ xử lý 2,33 GHz như của tôi đó là 0,7 giai đoạn đường ống lệnh cho mỗi lần truy cập mảng. Vì vậy, vector trông giống như nó đang sử dụng một hướng dẫn bổ sung cho mỗi lần truy cập.
Martin York

3
@James McNellis: Bạn không thể thay thế resize()bằng reserve(), vì điều này không điều chỉnh ý tưởng bên trong của vectơ về kích thước của chính nó, do đó, việc ghi vào các phần tử của nó về mặt kỹ thuật là "viết quá khứ" và sẽ tạo ra UB. Mặc dù trong thực tế, mọi triển khai STL sẽ "hành xử chính nó" trong vấn đề đó, làm thế nào để bạn đồng bộ lại kích thước của vectơ? Nếu bạn thử gọi resize() sau khi điền vectơ, nó hoàn toàn có thể ghi đè lên tất cả các phần tử đó bằng các giá trị được xây dựng mặc định!
j_random_hacker

8
@j_random_hacker: Không phải đó chính xác là những gì tôi đã nói sao? Tôi nghĩ rằng tôi đã rất rõ ràng rằng reservechỉ thay đổi công suất của một vectơ, không phải kích thước của nó.
James McNellis

7
Được rồi, đi con số. Có rất nhiều hành trình liên quan đến ngoại lệ trong các phương pháp vectơ. Thêm /EHscvào các công cụ biên dịch đã dọn sạch nó và assign()thực sự đánh bại mảng ngay bây giờ. Yay
Pavel Minaev

55

Câu hỏi tuyệt vời. Tôi đến đây với hy vọng tìm thấy một số sửa chữa đơn giản sẽ tăng tốc độ kiểm tra véc tơ ngay. Điều đó đã không diễn ra như tôi mong đợi!

Tối ưu hóa giúp, nhưng nó không đủ. Với tối ưu hóa trên tôi vẫn thấy sự khác biệt hiệu suất gấp 2 lần giữa UseArray và UseVector. Thật thú vị, UseVector chậm hơn đáng kể so với UseVectorPushBack mà không tối ưu hóa.

# g++ -Wall -Wextra -pedantic -o vector vector.cpp
# ./vector
UseArray completed in 20.68 seconds
UseVector completed in 120.509 seconds
UseVectorPushBack completed in 37.654 seconds
The whole thing completed in 178.845 seconds
# g++ -Wall -Wextra -pedantic -O3 -o vector vector.cpp
# ./vector
UseArray completed in 3.09 seconds
UseVector completed in 6.09 seconds
UseVectorPushBack completed in 9.847 seconds
The whole thing completed in 19.028 seconds

Ý tưởng số 1 - Sử dụng [] mới thay vì malloc

Tôi đã thử thay đổi malloc()thành new[]UseArray để các đối tượng sẽ được xây dựng. Và thay đổi từ gán trường riêng lẻ sang gán phiên bản Pixel. Oh, và đổi tên biến vòng lặp bên trong thành j.

void UseArray()
{
    TestTimer t("UseArray");

    for(int i = 0; i < 1000; ++i)
    {   
        int dimension = 999;

        // Same speed as malloc().
        Pixel * pixels = new Pixel[dimension * dimension];

        for(int j = 0 ; j < dimension * dimension; ++j)
            pixels[j] = Pixel(255, 0, 0);

        delete[] pixels;
    }
}

Đáng ngạc nhiên (với tôi), không có thay đổi nào tạo ra sự khác biệt nào. Thậm chí không có thay đổi new[]nào sẽ mặc định xây dựng tất cả các Pixels. Có vẻ như gcc có thể tối ưu hóa các cuộc gọi hàm tạo mặc định khi sử dụng new[], nhưng không phải khi sử dụng vector.

Ý tưởng # 2 - Xóa các cuộc gọi lặp lại của nhà điều hành []

Tôi cũng đã cố gắng để thoát khỏi operator[]tra cứu ba và lưu trữ tài liệu tham khảo pixels[j]. Điều đó thực sự làm chậm UseVector! Giáo sư.

for(int j = 0; j < dimension * dimension; ++j)
{
    // Slower than accessing pixels[j] three times.
    Pixel &pixel = pixels[j];
    pixel.r = 255;
    pixel.g = 0;
    pixel.b = 0;
}

# ./vector 
UseArray completed in 3.226 seconds
UseVector completed in 7.54 seconds
UseVectorPushBack completed in 9.859 seconds
The whole thing completed in 20.626 seconds

Ý tưởng số 3 - Loại bỏ các nhà xây dựng

Điều gì về việc loại bỏ hoàn toàn các nhà xây dựng? Sau đó, có lẽ gcc có thể tối ưu hóa việc xây dựng tất cả các đối tượng khi các vectơ được tạo. Điều gì xảy ra nếu chúng ta thay đổi Pixel thành:

struct Pixel
{
    unsigned char r, g, b;
};

Kết quả: nhanh hơn khoảng 10%. Vẫn chậm hơn một mảng. Hừm.

# ./vector 
UseArray completed in 3.239 seconds
UseVector completed in 5.567 seconds

Ý tưởng số 4 - Sử dụng iterator thay vì chỉ số vòng lặp

Làm thế nào về việc sử dụng vector<Pixel>::iteratorthay vì một chỉ số vòng lặp?

for (std::vector<Pixel>::iterator j = pixels.begin(); j != pixels.end(); ++j)
{
    j->r = 255;
    j->g = 0;
    j->b = 0;
}

Kết quả:

# ./vector 
UseArray completed in 3.264 seconds
UseVector completed in 5.443 seconds

Không, không khác. Ít nhất là nó không chậm hơn. Tôi nghĩ rằng điều này sẽ có hiệu suất tương tự như # 2 nơi tôi đã sử dụng một Pixel&tài liệu tham khảo.

Phần kết luận

Ngay cả khi một số cookie thông minh tìm ra cách tạo vòng lặp vector nhanh như mảng một, thì điều này không nói rõ về hành vi mặc định của std::vector. Quá nhiều cho trình biên dịch đủ thông minh để tối ưu hóa tất cả các C ++ và tạo các bộ chứa STL nhanh như các mảng thô.

Điểm mấu chốt là trình biên dịch không thể tối ưu hóa các lệnh gọi hàm tạo mặc định không op khi sử dụng std::vector. Nếu bạn sử dụng đơn giản, new[]nó sẽ tối ưu hóa chúng đi. Nhưng không phải với std::vector. Ngay cả khi bạn có thể viết lại mã của mình để loại bỏ các lệnh gọi của hàm tạo đối mặt với câu thần chú quanh đây: "Trình biên dịch thông minh hơn bạn. STL chỉ nhanh như đơn giản C. Đừng lo lắng về điều đó."


2
Một lần nữa, cảm ơn vì đã thực sự chạy mã. Đôi khi rất dễ bị bash mà không có lý do khi ai đó cố gắng thách thức các ý kiến ​​phổ biến.
kizzx2

3
"Quá nhiều cho trình biên dịch đủ thông minh để tối ưu hóa tất cả các C ++ và tạo các bộ chứa STL nhanh như các mảng thô." Bình luận tốt đẹp. Tôi có một lý thuyết cho rằng "trình biên dịch này thông minh" chỉ là một huyền thoại - phân tích cú pháp C ++ cực kỳ khó khăn và trình biên dịch chỉ là một cỗ máy.
kizzx2

3
Tôi không biết. Chắc chắn, anh ta có thể làm chậm bài kiểm tra mảng, nhưng anh ta đã không tăng tốc độ vectơ. Tôi đã chỉnh sửa ở trên nơi tôi đã loại bỏ các hàm tạo khỏi Pixel và biến nó thành một cấu trúc đơn giản và nó vẫn còn chậm. Đó là tin xấu cho bất cứ ai sử dụng các loại đơn giản như vector<int>.
John Kugelman

2
Tôi ước tôi thực sự có thể nâng cao câu trả lời của bạn hai lần. Những ý tưởng thông minh để thử (mặc dù không có ý tưởng nào thực sự hiệu quả) mà tôi thậm chí không thể nghĩ ra!
kizzx2

9
Chỉ muốn lưu ý rằng sự phức tạp của việc phân tích cú pháp C ++ (vốn cực kỳ phức tạp, vâng) không liên quan gì đến chất lượng tối ưu hóa. Điều thứ hai thường xảy ra trên các giai đoạn mà kết quả phân tích cú pháp đã được chuyển đổi nhiều lần thành một biểu diễn cấp thấp hơn nhiều.
Pavel Minaev

44

Đây là một câu hỏi cũ nhưng phổ biến.

Tại thời điểm này, nhiều lập trình viên sẽ làm việc trong C ++ 11. Và trong C ++ 11, mã OP được viết chạy nhanh như nhau cho UseArrayhoặc UseVector.

UseVector completed in 3.74482 seconds
UseArray completed in 3.70414 seconds

Vấn đề cơ bản là trong khi Pixelcấu trúc của bạn chưa được khởi tạo, std::vector<T>::resize( size_t, T const&=T() )lấy một cấu trúc mặc định Pixelsao chép nó . Trình biên dịch không nhận thấy nó đang được yêu cầu sao chép dữ liệu chưa được khởi tạo, vì vậy nó thực sự đã thực hiện sao chép.

Trong C ++ 11, std::vector<T>::resizecó hai lần quá tải. Đầu tiên là std::vector<T>::resize(size_t), cái khác là std::vector<T>::resize(size_t, T const&). Điều này có nghĩa là khi bạn gọi resizemà không có đối số thứ hai, nó chỉ đơn giản là các cấu trúc mặc định và trình biên dịch đủ thông minh để nhận ra rằng cấu trúc mặc định không làm gì cả, vì vậy nó bỏ qua bộ đệm.

(Hai tình trạng quá tải khi được thêm vào để xử lý các loại có thể di chuyển, có thể xây dựng và không thể sao chép - cải thiện hiệu suất khi làm việc trên dữ liệu chưa được khởi tạo là một phần thưởng).

Các push_backGiải pháp này cũng không kiểm tra fencepost, mà làm chậm nó xuống, do đó nó vẫn chậm hơn so với các mallocphiên bản.

ví dụ trực tiếp (tôi cũng thay thế bộ đếm thời gian bằng chrono::high_resolution_clock).

Lưu ý rằng nếu bạn có một cấu trúc thường yêu cầu khởi tạo, nhưng bạn muốn xử lý nó sau khi phát triển bộ đệm của mình, bạn có thể thực hiện việc này với một std::vectorcấp phát tùy chỉnh . Nếu bạn muốn sau đó chuyển nó thành một cách bình thường hơn std::vector, tôi tin rằng việc sử dụng cẩn thận allocator_traitsvà ghi đè ==có thể kéo nó đi, nhưng không chắc chắn.


Cũng sẽ rất thú vị để xem làm thế nào emplace_backso với push_backở đây.
Daniel

1
Tôi không thể sao chép kết quả của bạn. Biên dịch mã của bạn clang++ -std=c++11 -O3UseArray completed in 2.02e-07 secondsUseVector completed in 1.3026 seconds. Tôi cũng đã thêm một UseVectorEmplaceBackphiên bản đó là xấp xỉ. Nhanh gấp 2,5 lần UseVectorPushBack.
Daniel

1
Tỷ lệ cược @daniel là trình tối ưu hóa loại bỏ mọi thứ khỏi phiên bản mảng. Luôn luôn là một rủi ro với điểm chuẩn vi mô.
Yakk - Adam Nevraumont

4
vâng, bạn đã đúng, chỉ cần nhìn vào hội đồng (hoặc thiếu nó) .. Có lẽ nên nghĩ về điều đó với sự khác biệt ~ 6448514x! Tôi tự hỏi tại sao phiên bản vectơ không thể thực hiện tối ưu hóa tương tự .. Nó sẽ làm như vậy nếu được xây dựng với kích thước thay vì thay đổi kích thước.
Daniel

34

Để công bằng, bạn không thể so sánh việc triển khai C ++ với triển khai C, như tôi sẽ gọi phiên bản malloc của bạn. malloc không tạo đối tượng - nó chỉ phân bổ bộ nhớ thô. Sau đó, bạn coi bộ nhớ đó là các đối tượng mà không gọi hàm tạo là C ++ kém (có thể không hợp lệ - tôi sẽ để lại cho các luật sư ngôn ngữ).

Điều đó nói rằng, chỉ cần thay đổi malloc thành new Pixel[dimensions*dimensions]và miễn phí delete [] pixelssẽ không tạo ra nhiều khác biệt với việc triển khai Pixel đơn giản mà bạn có. Đây là kết quả trên hộp của tôi (E6600, 64-bit):

UseArray completed in 0.269 seconds
UseVector completed in 1.665 seconds
UseVectorPushBack completed in 7.309 seconds
The whole thing completed in 9.244 seconds

Nhưng với một thay đổi nhỏ, các bảng lần lượt:

Pixel.h

struct Pixel
{
    Pixel();
    Pixel(unsigned char r, unsigned char g, unsigned char b);

    unsigned char r, g, b;
};

Pixel.cc

#include "Pixel.h"

Pixel::Pixel() {}
Pixel::Pixel(unsigned char r, unsigned char g, unsigned char b) 
  : r(r), g(g), b(b) {}

main.cc

#include "Pixel.h"
[rest of test harness without class Pixel]
[UseArray now uses new/delete not malloc/free]

Tổng hợp theo cách này:

$ g++ -O3 -c -o Pixel.o Pixel.cc
$ g++ -O3 -c -o main.o main.cc
$ g++ -o main main.o Pixel.o

chúng tôi nhận được kết quả rất khác nhau:

UseArray completed in 2.78 seconds
UseVector completed in 1.651 seconds
UseVectorPushBack completed in 7.826 seconds
The whole thing completed in 12.258 seconds

Với một hàm tạo không nội tuyến cho Pixel, std :: vector hiện đánh bại một mảng thô.

Nó sẽ xuất hiện mà sự phức tạp của việc phân bổ thông qua std :: vector và std: cấp phát là quá nhiều để được tối ưu hóa hiệu quả như một cách đơn giản new Pixel[n]. Tuy nhiên, chúng ta có thể thấy rằng vấn đề chỉ đơn giản là việc cấp phát chứ không phải truy cập vectơ bằng cách điều chỉnh một vài hàm kiểm tra để tạo vectơ / mảng một lần bằng cách di chuyển nó ra ngoài vòng lặp:

void UseVector()
{
    TestTimer t("UseVector");

    int dimension = 999;
    std::vector<Pixel> pixels;
    pixels.resize(dimension * dimension);

    for(int i = 0; i < 1000; ++i)
    {
        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}

void UseArray()
{
    TestTimer t("UseArray");

    int dimension = 999;
    Pixel * pixels = new Pixel[dimension * dimension];

    for(int i = 0; i < 1000; ++i)
    {
        for(int i = 0 ; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
    delete [] pixels;
}

Chúng tôi nhận được những kết quả này ngay bây giờ:

UseArray completed in 0.254 seconds
UseVector completed in 0.249 seconds
UseVectorPushBack completed in 7.298 seconds
The whole thing completed in 7.802 seconds

Điều chúng ta có thể học được từ điều này là std :: vector có thể so sánh với một mảng thô để truy cập, nhưng nếu bạn cần tạo và xóa vectơ / mảng nhiều lần, việc tạo một đối tượng phức tạp sẽ tốn nhiều thời gian hơn mà tạo ra một mảng đơn giản khi hàm tạo của phần tử không được nội tuyến. Tôi không nghĩ rằng điều này là rất đáng ngạc nhiên.


3
Bạn vẫn có một hàm tạo nội tuyến - hàm tạo sao chép.
Ben Voigt

26

Hãy thử với điều này:

void UseVectorCtor()
{
    TestTimer t("UseConstructor");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels(dimension * dimension, Pixel(255, 0, 0));
    }
}

Tôi nhận được gần như chính xác hiệu suất tương tự như với mảng.

Vấn đề vectorlà nó là một công cụ tổng quát hơn nhiều so với một mảng. Và điều đó có nghĩa là bạn phải xem xét cách bạn sử dụng nó. Nó có thể được sử dụng theo nhiều cách khác nhau, cung cấp chức năng mà một mảng thậm chí không có. Và nếu bạn sử dụng nó "sai" cho mục đích của mình, bạn sẽ phải chịu rất nhiều chi phí, nhưng nếu bạn sử dụng đúng cách, thì về cơ bản, đó thường là cấu trúc dữ liệu không chi phí. Trong trường hợp này, vấn đề là bạn đã khởi tạo riêng vectơ (khiến tất cả các phần tử có ctor mặc định của chúng được gọi), sau đó ghi đè từng phần tử riêng lẻ với giá trị chính xác. Điều đó khó hơn nhiều cho trình biên dịch để tối ưu hóa đi so với khi bạn làm điều tương tự với một mảng. Đó là lý do tại sao vector cung cấp một hàm tạo cho phép bạn thực hiện chính xác điều đó:NX.

Và khi bạn sử dụng nó, vector chỉ nhanh như một mảng.

Vì vậy, không, bạn đã không tin vào huyền thoại hiệu suất. Nhưng bạn đã chỉ ra rằng nó chỉ đúng nếu bạn sử dụng vectơ một cách tối ưu, đó cũng là một điểm khá tốt. :)

Về mặt tươi sáng, đó thực sự là cách sử dụng đơn giản nhất hóa ra là nhanh nhất. Nếu bạn đối chiếu đoạn mã của tôi (một dòng) với câu trả lời của John Kugelman, chứa hàng đống các chỉnh sửa và tối ưu hóa, mà vẫn không hoàn toàn loại bỏ sự khác biệt về hiệu suất, thì rõ ràng nó vectorđược thiết kế khá thông minh. Bạn không cần phải nhảy qua các vòng để có tốc độ bằng một mảng. Ngược lại, bạn phải sử dụng giải pháp đơn giản nhất có thể.


1
Tôi vẫn đặt câu hỏi liệu đây có phải là một so sánh công bằng. Nếu bạn đang thoát khỏi vòng lặp bên trong thì mảng tương đương sẽ là xây dựng một đối tượng Pixel duy nhất và sau đó làm mờ nó trên toàn bộ mảng.
John Kugelman

1
Việc sử dụng new[]thực hiện các cấu trúc mặc định tương tự vector.resize(), nhưng nó nhanh hơn nhiều. new[]+ vòng lặp bên trong phải có cùng tốc độ với vector.resize()+ vòng lặp bên trong, nhưng thực tế không phải vậy, nó nhanh gấp gần hai lần.
John Kugelman

@ John: Đó là một so sánh công bằng. Trong mã ban đầu, mảng được phân bổ mallocmà không khởi tạo hoặc xây dựng bất cứ thứ gì, do đó, đây thực sự một thuật toán vượt qua giống như vectormẫu của tôi . Và đối new[]với câu trả lời rõ ràng là cả hai đều yêu cầu hai lần vượt qua, nhưng trong new[]trường hợp, trình biên dịch có thể tối ưu hóa chi phí bổ sung đó, điều mà nó không làm trong vectortrường hợp này. Nhưng tôi không thấy lý do tại sao điều thú vị xảy ra trong các trường hợp dưới mức tối ưu. Nếu bạn quan tâm đến hiệu suất, bạn không viết mã như thế.
jalf

@ John: Nhận xét thú vị. Nếu tôi muốn làm mờ toàn bộ mảng, tôi đoán rằng mảng lại là giải pháp tối ưu - vì tôi không thể nói vector::resize()cho tôi một khối bộ nhớ dự phòng mà không lãng phí thời gian gọi các nhà xây dựng vô dụng.
kizzx2

@ kizzx2: có và không. Một mảng thường được khởi tạo cũng như trong C ++. Trong C, bạn sẽ sử dụng mallockhông thực hiện khởi tạo, nhưng điều đó sẽ không hoạt động trong C ++ với các loại không phải POD. Vì vậy, trong trường hợp chung, một mảng C ++ sẽ tệ như vậy. Có lẽ câu hỏi là, nếu bạn sẽ thực hiện việc này thường xuyên, bạn sẽ không sử dụng lại cùng một mảng / vectơ chứ? Và nếu bạn làm điều đó, thì bạn chỉ phải trả chi phí cho "các nhà xây dựng vô dụng" một lần, ngay từ đầu. Rốt cuộc là nhanh chóng.
jalf

22

Đó không phải là một so sánh công bằng khi tôi lần đầu tiên nhìn vào mã của bạn; Tôi chắc chắn nghĩ rằng bạn không so sánh táo với táo. Vì vậy, tôi nghĩ rằng, hãy để các nhà xây dựng và phá hủy được gọi trong tất cả các thử nghiệm; rồi so sánh.

const size_t dimension = 1000;

void UseArray() {
    TestTimer t("UseArray");
    for(size_t j = 0; j < dimension; ++j) {
        Pixel* pixels = new Pixel[dimension * dimension];
        for(size_t i = 0 ; i < dimension * dimension; ++i) {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = (unsigned char) (i % 255);
        }
        delete[] pixels;
    }
}

void UseVector() {
    TestTimer t("UseVector");
    for(size_t j = 0; j < dimension; ++j) {
        std::vector<Pixel> pixels(dimension * dimension);
        for(size_t i = 0; i < dimension * dimension; ++i) {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = (unsigned char) (i % 255);
        }
    }
}

int main() {
    TestTimer t1("The whole thing");

    UseArray();
    UseVector();

    return 0;
}

Suy nghĩ của tôi là, rằng với thiết lập này, họ sẽ có chính xác như vậy. Hóa ra, tôi đã sai.

UseArray completed in 3.06 seconds
UseVector completed in 4.087 seconds
The whole thing completed in 10.14 seconds

Vậy tại sao mất hiệu suất 30% này thậm chí xảy ra? STL có mọi thứ trong các tiêu đề, do đó trình biên dịch có thể hiểu mọi thứ được yêu cầu.

Tôi nghĩ rằng đó là cách vòng lặp khởi tạo tất cả các giá trị cho hàm tạo mặc định. Vì vậy, tôi đã thực hiện một bài kiểm tra:

class Tester {
public:
    static int count;
    static int count2;
    Tester() { count++; }
    Tester(const Tester&) { count2++; }
};
int Tester::count = 0;
int Tester::count2 = 0;

int main() {
    std::vector<Tester> myvec(300);
    printf("Default Constructed: %i\nCopy Constructed: %i\n", Tester::count, Tester::count2);

    return 0;
}

Kết quả như tôi nghi ngờ:

Default Constructed: 1
Copy Constructed: 300

Đây rõ ràng là nguồn gốc của sự chậm lại, thực tế là vectơ sử dụng hàm tạo sao chép để khởi tạo các phần tử từ một đối tượng được xây dựng mặc định.

Điều này có nghĩa là, thứ tự giả hoạt động sau đây đang xảy ra trong quá trình xây dựng vectơ:

Pixel pixel;
for (auto i = 0; i < N; ++i) vector[i] = pixel;

Mà, do hàm tạo sao chép ẩn được tạo bởi trình biên dịch, được mở rộng thành như sau:

Pixel pixel;
for (auto i = 0; i < N; ++i) {
    vector[i].r = pixel.r;
    vector[i].g = pixel.g;
    vector[i].b = pixel.b;
}

Vì vậy, mặc định Pixelvẫn chưa được khởi tạo, trong khi phần còn lại được khởi tạo với mặc định Pixel's un-khởi tạo giá trị.

So với tình huống thay thế với New[]/ Delete[]:

int main() {
    Tester* myvec = new Tester[300];

    printf("Default Constructed: %i\nCopy Constructed:%i\n", Tester::count, Tester::count2);

    delete[] myvec;

    return 0;
}

Default Constructed: 300
Copy Constructed: 0

Tất cả chúng đều được để lại các giá trị chưa được khởi tạo của chúng và không có phép lặp kép trong chuỗi.

Được trang bị thông tin này, làm thế nào chúng ta có thể kiểm tra nó? Hãy thử viết lại hàm tạo sao chép ẩn.

Pixel(const Pixel&) {}

Và kết quả?

UseArray completed in 2.617 seconds
UseVector completed in 2.682 seconds
The whole thing completed in 5.301 seconds

Vì vậy, tóm lại, nếu bạn tạo ra hàng trăm vectơ rất thường xuyên: hãy nghĩ lại thuật toán của bạn .

Trong mọi trường hợp, việc triển khai STL không chậm hơn vì một số lý do không xác định, nó chỉ thực hiện chính xác những gì bạn yêu cầu; mong bạn biết rõ hơn


3
Đánh giá từ niềm vui mà chúng tôi (bạn và tôi và những người thông minh khác ở đây) đã có, "hy vọng" của STL thực sự là một yêu cầu khá khắt khe: P Về cơ bản, chúng tôi có thể phóng đại và kết luận rằng nó hy vọng tôi đã đọc và phân tích tất cả nguồn gốc của nó mã. Dù sao: P
kizzx2

1
Tuyệt vời! Trong VS 2013, điều này làm cho vector nhanh hơn mảng. Mặc dù có vẻ như đối với các hệ thống quan trọng về hiệu năng, bạn cần kiểm tra STL rất nhiều để có thể sử dụng nó một cách hiệu quả.
rozina

7

Hãy thử vô hiệu hóa các trình vòng lặp được kiểm tra và xây dựng trong chế độ phát hành. Bạn không nên thấy nhiều sự khác biệt về hiệu suất.


1
Đã thử #define _SECURE_SCL 0. Điều đó thực hiện UseVectorở đâu đó khoảng 4 giây (tương tự như gccbên dưới) nhưng vẫn chậm gấp đôi.
kizzx2

Điều này gần như chắc chắn là nguyên nhân. Microsoft ân cần cho chúng tôi gỡ lỗi theo mặc định cho cả gỡ lỗi và phát hành. Chúng tôi thấy rằng đây là nguyên nhân sâu xa của sự chậm chạp lớn sau khi nâng cấp từ năm 2003 lên năm 2008. Chắc chắn là một trong những vấn đề rắc rối nhất của phòng thu hình ảnh.
Doug T.

2
@ kizzx2 có một macro khác để vô hiệu hóa: HAS_ITERATOR_DEBUGGING hoặc một số như vậy.
Doug T.

Như @Martin và câu trả lời của tôi hiển thị, gcc hiển thị cùng một mẫu, ngay cả với tối ưu hóa tại -O3.
John Kugelman

1
@Doug: Nhìn vào tài liệu, tôi nghĩ _HAS_ITERATOR_DEBUGGINGbị vô hiệu hóa trong bản dựng phát hành: msdn.microsoft.com/en-us/l
Library / aa985939 (VS.80) .aspx

4

STL của GNU (và các loại khác), được đưa ra vector<T>(n), mặc định xây dựng một đối tượng nguyên mẫu T()- trình biên dịch sẽ tối ưu hóa hàm tạo trống - nhưng sau đó, một bản sao của bất kỳ rác nào xảy ra trong các địa chỉ bộ nhớ dành cho đối tượng được lấy bởi STL __uninitialized_fill_n_aux, mà các vòng lặp điền các bản sao của đối tượng đó làm giá trị mặc định trong vectơ. Vì vậy, "của tôi" STL không phải là xây dựng vòng lặp, mà là xây dựng sau đó lặp / sao chép. Nó phản tác dụng trực quan, nhưng tôi nên nhớ khi tôi nhận xét về một câu hỏi stackoverflow gần đây về chính điểm này: cấu trúc / bản sao có thể hiệu quả hơn cho các đối tượng đếm tham chiếu, v.v.

Vì thế:

vector<T> x(n);

hoặc là

vector<T> x;
x.resize(n);

là - trên nhiều triển khai STL - đại loại như:

T temp;
for (int i = 0; i < n; ++i)
    x[i] = temp;

Vấn đề là các trình tối ưu hóa trình biên dịch hiện tại dường như không hoạt động từ cái nhìn sâu sắc rằng temp là rác chưa được xử lý và không tối ưu hóa các yêu cầu của trình xây dựng sao chép vòng lặp và mặc định. Bạn có thể lập luận một cách đáng tin cậy rằng các trình biên dịch hoàn toàn không nên tối ưu hóa điều này, vì một lập trình viên viết ở trên có một kỳ vọng hợp lý rằng tất cả các đối tượng sẽ giống hệt nhau sau vòng lặp, ngay cả khi rác (thông thường hãy nói về 'giống hệt' / toán tử == vs memcmp / toán tử = vv áp dụng). Trình biên dịch không thể có bất kỳ hiểu biết sâu sắc nào về bối cảnh lớn hơn của std :: vector <> hoặc việc sử dụng dữ liệu sau này sẽ đề xuất tối ưu hóa này an toàn.

Điều này có thể tương phản với việc thực hiện trực tiếp, rõ ràng hơn:

for (int i = 0; i < n; ++i)
    x[i] = T();

Mà chúng ta có thể mong đợi một trình biên dịch để tối ưu hóa ra.

Để rõ ràng hơn một chút về sự biện minh cho khía cạnh này của hành vi của vectơ, hãy xem xét:

std::vector<big_reference_counted_object> x(10000);

Rõ ràng đó là một sự khác biệt lớn nếu chúng ta tạo ra 10000 đối tượng độc lập so với 10000 tham chiếu cùng một dữ liệu. Có một lập luận hợp lý rằng lợi thế của việc bảo vệ người dùng C ++ thông thường khỏi việc vô tình làm một việc gì đó quá đắt so với chi phí xây dựng bản sao khó tối ưu trong thế giới thực.

TRẢ LỜI GỐC (để tham khảo / ý nghĩa của các bình luận): Không có cơ hội. vector nhanh như một mảng, ít nhất là nếu bạn dự trữ không gian hợp lý. ...


6
Tôi thực sự không thể biện minh cho câu trả lời này là bất cứ nơi nào hơi hữu ích cho bất cứ ai. Tôi hy vọng tôi có thể downvote hai lần.
kizzx2

-1, có sự hỗ trợ của tôi trên kizzx2. vector sẽ không bao giờ nhanh như mảng do tính năng bổ sung mà nó cung cấp, quy luật của vũ trụ, mọi thứ đều có giá!
YeenFei

Bạn đang bỏ lỡ, Tony Đổi đó là một ví dụ về điểm chuẩn nhân tạo, nhưng nó chứng minh những gì nó thể hiện.
Potatoswatter

Hoa hồng có màu xanh lá cây, hoa violet có màu cam, downvote có vị đắng, nhưng câu trả lời cầu xin họ.
Pavel Minaev

3

Câu trả lời của Martin York làm phiền tôi vì có vẻ như đó là một nỗ lực để giải quyết vấn đề khởi tạo dưới thảm. Nhưng anh ta có quyền xác định xây dựng mặc định dư thừa là nguồn gốc của các vấn đề hiệu suất.

[EDIT: Câu trả lời của Martin không còn đề nghị thay đổi hàm tạo mặc định.]

Đối với vấn đề trước mắt, bạn chắc chắn có thể gọi phiên bản 2 tham số của vector<Pixel>ctor thay thế:

std::vector<Pixel> pixels(dimension * dimension, Pixel(255, 0, 0));

Điều đó hoạt động nếu bạn muốn khởi tạo với một giá trị không đổi, đó là trường hợp phổ biến. Nhưng vấn đề chung hơn là: Làm thế nào bạn có thể khởi tạo hiệu quả với một cái gì đó phức tạp hơn một giá trị không đổi?

Đối với điều này, bạn có thể sử dụng một back_insert_iterator, đó là một bộ chuyển đổi lặp. Đây là một ví dụ với vectơ ints, mặc dù ý tưởng chung cũng hoạt động tốt cho Pixels:

#include <iterator>
// Simple functor return a list of squares: 1, 4, 9, 16...
struct squares {
    squares() { i = 0; }
    int operator()() const { ++i; return i * i; }

private:
    int i;
};

...

std::vector<int> v;
v.reserve(someSize);     // To make insertions efficient
std::generate_n(std::back_inserter(v), someSize, squares());

Ngoài ra, bạn có thể sử dụng copy()hoặc transform()thay vì generate_n().

Nhược điểm là logic để xây dựng các giá trị ban đầu cần phải được chuyển sang một lớp riêng biệt, điều này không thuận tiện hơn so với việc đặt nó tại chỗ (mặc dù lambdas trong C ++ 1x làm cho điều này đẹp hơn nhiều). Ngoài ra tôi hy vọng điều này vẫn sẽ không nhanh như malloc()phiên bản không dựa trên STL, nhưng tôi hy vọng nó sẽ đóng, vì nó chỉ thực hiện một cấu trúc cho mỗi phần tử.


2

Các vectơ đang gọi thêm các nhà xây dựng Pixel.

Mỗi cái đang gây ra gần một triệu ctor chạy mà bạn đang định thời gian.

chỉnh sửa: sau đó có vòng lặp 1 ... 1000 bên ngoài, vì vậy hãy tạo ra một tỷ cuộc gọi ctor!

chỉnh sửa 2: thật thú vị khi thấy sự tháo gỡ cho trường hợp UseArray. Một trình tối ưu hóa có thể tối ưu hóa toàn bộ mọi thứ, vì nó không có tác dụng gì ngoài việc ghi CPU.


Bạn nói đúng, nhưng câu hỏi là: làm thế nào những cuộc gọi ctor vô nghĩa này có thể bị tắt? Cách tiếp cận không STL dễ dàng, nhưng khó / xấu đối với cách STL.
j_random_hacker

1

Đây là cách push_backphương thức trong vector hoạt động:

  1. Vectơ phân bổ lượng không gian X khi nó được khởi tạo.
  2. Như đã nêu dưới đây, nó kiểm tra xem có chỗ trong mảng bên dưới hiện tại cho mục không.
  3. Nó tạo một bản sao của mục trong cuộc gọi push_back.

Sau khi gọi push_backcác mục X:

  1. Vectơ phân bổ lại lượng kX không gian thành một mảng thứ 2.
  2. Nó Sao chép các mục của mảng đầu tiên lên mảng thứ hai.
  3. Loại bỏ mảng đầu tiên.
  4. Bây giờ sử dụng mảng thứ hai làm lưu trữ cho đến khi nó đạt đến các mục kX.

Nói lại. Nếu bạn không có không reservinggian, nó chắc chắn sẽ chậm hơn. Hơn thế nữa, nếu tốn kém để sao chép mục đó thì 'đẩy_back' như thế sẽ ăn thịt bạn.

Đối với điều vectorso với mảng, tôi sẽ phải đồng ý với những người khác. Chạy trong bản phát hành, bật tối ưu hóa và đặt thêm một vài cờ để những người thân thiện tại Microsoft không # @% $ ^ cho bạn.

Một điều nữa, nếu bạn không cần thay đổi kích thước, hãy sử dụng Boost.Array.


Tôi hiểu mọi người không thích đọc một loạt mã khi nó được đăng nguyên văn. Nhưng tôi đã sử dụng reservenhư tôi cần.
kizzx2

Xin lỗi tôi đã bỏ lỡ nó. Không có gì khác tôi đưa lên có hữu ích ở tất cả?
Wheaties

push_backđã khấu hao thời gian không đổi. Có vẻ như bạn đang mô tả một quá trình O (N). (Bước 1 và 3 dường như hoàn toàn ra khỏi chỗ.) Điều gì làm push_backchậm đối với OP là việc kiểm tra phạm vi để xem liệu nhu cầu phân bổ lại xảy ra, cập nhật các con trỏ, kiểm tra chống lại NULL bên trong vị trí new, và những điều nhỏ nhặt khác mà thường được át đi bởi công việc thực tế của chương trình.
Potatoswatter

Nó sẽ chậm hơn ngay cả reservekhi nó vẫn phải thực hiện kiểm tra đó (cho dù nó cần phải phân bổ lại) trên mỗi push_back.
Pavel Minaev

Tất cả những điểm tốt. Những gì tôi mô tả nghe có vẻ như là một quá trình O (N) nhưng nó không, cũng không hoàn toàn. Hầu hết những người tôi biết không hiểu cách thức vectorthực hiện chức năng thay đổi kích thước của nó, đó chỉ là "ma thuật". Ở đây, hãy để tôi làm rõ hơn một chút.
Wheaties

1

Một số dữ liệu cấu hình (pixel được căn chỉnh thành 32 bit):

g++ -msse3 -O3 -ftree-vectorize -g test.cpp -DNDEBUG && ./a.out
UseVector completed in 3.123 seconds
UseArray completed in 1.847 seconds
UseVectorPushBack completed in 9.186 seconds
The whole thing completed in 14.159 seconds

Blah

andrey@nv:~$ opannotate --source libcchem/src/a.out  | grep "Total samples for file" -A3
Overflow stats not available
 * Total samples for file : "/usr/include/c++/4.4/ext/new_allocator.h"
 *
 * 141008 52.5367
 */
--
 * Total samples for file : "/home/andrey/libcchem/src/test.cpp"
 *
 *  61556 22.9345
 */
--
 * Total samples for file : "/usr/include/c++/4.4/bits/stl_vector.h"
 *
 *  41956 15.6320
 */
--
 * Total samples for file : "/usr/include/c++/4.4/bits/stl_uninitialized.h"
 *
 *  20956  7.8078
 */
--
 * Total samples for file : "/usr/include/c++/4.4/bits/stl_construct.h"
 *
 *   2923  1.0891
 */

Trong allocator:

               :      // _GLIBCXX_RESOLVE_LIB_DEFECTS
               :      // 402. wrong new expression in [some_] allocator::construct
               :      void
               :      construct(pointer __p, const _Tp& __val)
141008 52.5367 :      { ::new((void *)__p) _Tp(__val); }

vector:

               :void UseVector()
               :{ /* UseVector() total:  60121 22.3999 */
...
               :
               :
 10790  4.0201 :        for (int i = 0; i < dimension * dimension; ++i) {
               :
   495  0.1844 :            pixels[i].r = 255;
               :
 12618  4.7012 :            pixels[i].g = 0;
               :
  2253  0.8394 :            pixels[i].b = 0;
               :
               :        }

mảng

               :void UseArray()
               :{ /* UseArray() total:  35191 13.1114 */
               :
...
               :
   136  0.0507 :        for (int i = 0; i < dimension * dimension; ++i) {
               :
  9897  3.6874 :            pixels[i].r = 255;
               :
  3511  1.3081 :            pixels[i].g = 0;
               :
 21647  8.0652 :            pixels[i].b = 0;

Hầu hết các chi phí là trong các nhà xây dựng sao chép. Ví dụ,

    std::vector < Pixel > pixels;//(dimension * dimension, Pixel());

    pixels.reserve(dimension * dimension);

    for (int i = 0; i < dimension * dimension; ++i) {

        pixels[i].r = 255;

        pixels[i].g = 0;

        pixels[i].b = 0;
    }

Nó có hiệu suất tương tự như một mảng.


2
Thật không may, sau khi "giải pháp" bạn đưa ra, pixels.size()sẽ bị phá vỡ.
kizzx2

1
điều này là sai, bạn không thể gọi dự bị và sau đó sử dụng các yếu tố, bạn vẫn phải sử dụng push_back để thêm các mục
paulm

1

Máy tính xách tay của tôi là Lenova G770 (RAM 4 GB).

HĐH là Windows 7 64 bit (phiên bản có laptop)

Trình biên dịch là MinGW 4.6.1.

IDE là Code :: Blocks .

Tôi kiểm tra mã nguồn của bài đầu tiên.

Kết quả

Tối ưu hóa O2

UseArray hoàn thành sau 2,841 giây

UseVector hoàn thành sau 2.548 giây

UseVectorPushBack hoàn thành sau 11,95 giây

Toàn bộ điều hoàn thành trong 17.342 giây

tạm dừng hệ thống

Tối ưu hóa O3

UseArray hoàn thành sau 1.452 giây

UseVector hoàn thành sau 2.514 giây

UseVectorPushBack hoàn thành sau 12.967 giây

Toàn bộ điều hoàn thành trong 16.937 giây

Có vẻ như hiệu suất của vector kém hơn khi tối ưu hóa O3.

Nếu bạn thay đổi vòng lặp thành

    pixels[i].r = i;
    pixels[i].g = i;
    pixels[i].b = i;

Tốc độ của mảng và vectơ theo O2 và O3 gần như giống nhau.


Ngay cả khi tôi thay đổi malloc thành mới, trong trường hợp thử nghiệm đầu tiên trong O3, hiệu suất của vectơ vẫn chậm hơn mảng. Nhưng khi bạn thay đổi giá trị gán từ (255, 0, 0) thành (i, i, i), hiệu suất của vectơ và mảng gần như giống nhau dưới O2 và O3, nó khá kỳ lạ
StereoMatching 20/03 '

Xin lỗi, tôi quên thay đổi miễn phí để xóa. Sau khi thay đổi miễn phí để xóa, hiệu suất theo O3 của vectơ và mảng là như nhau, có vẻ như phân bổ là lý do chính?
StereoMatching

1

Một điểm chuẩn tốt hơn (tôi nghĩ ...), trình biên dịch do tối ưu hóa có thể thay đổi mã, vì kết quả của các vectơ / mảng được phân bổ không được sử dụng ở bất cứ đâu. Các kết quả:

$ g++ test.cpp -o test -O3 -march=native
$ ./test 
UseArray inner completed in 0.652 seconds
UseArray completed in 0.773 seconds
UseVector inner completed in 0.638 seconds
UseVector completed in 0.757 seconds
UseVectorPushBack inner completed in 6.732 seconds
UseVectorPush completed in 6.856 seconds
The whole thing completed in 8.387 seconds

Trình biên dịch:

gcc version 6.2.0 20161019 (Debian 6.2.0-9)

CPU:

model name  : Intel(R) Core(TM) i7-3630QM CPU @ 2.40GHz

Và mã:

#include <cstdlib>
#include <vector>

#include <iostream>
#include <string>

#include <boost/date_time/posix_time/ptime.hpp>
#include <boost/date_time/microsec_time_clock.hpp>

class TestTimer
{
    public:
        TestTimer(const std::string & name) : name(name),
            start(boost::date_time::microsec_clock<boost::posix_time::ptime>::local_time())
        {
        }

        ~TestTimer()
        {
            using namespace std;
            using namespace boost;

            posix_time::ptime now(date_time::microsec_clock<posix_time::ptime>::local_time());
            posix_time::time_duration d = now - start;

            cout << name << " completed in " << d.total_milliseconds() / 1000.0 <<
                " seconds" << endl;
        }

    private:
        std::string name;
        boost::posix_time::ptime start;
};

struct Pixel
{
    Pixel()
    {
    }

    Pixel(unsigned char r, unsigned char g, unsigned char b) : r(r), g(g), b(b)
    {
    }

    unsigned char r, g, b;
};

void UseVector(std::vector<std::vector<Pixel> >& results)
{
    TestTimer t("UseVector inner");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel>& pixels = results.at(i);
        pixels.resize(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}

void UseVectorPushBack(std::vector<std::vector<Pixel> >& results)
{
    TestTimer t("UseVectorPushBack inner");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel>& pixels = results.at(i);
            pixels.reserve(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
            pixels.push_back(Pixel(255, 0, 0));
    }
}

void UseArray(Pixel** results)
{
    TestTimer t("UseArray inner");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        Pixel * pixels = (Pixel *)malloc(sizeof(Pixel) * dimension * dimension);

        results[i] = pixels;

        for(int i = 0 ; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }

        // free(pixels);
    }
}

void UseArray()
{
    TestTimer t("UseArray");
    Pixel** array = (Pixel**)malloc(sizeof(Pixel*)* 1000);
    UseArray(array);
    for(int i=0;i<1000;++i)
        free(array[i]);
    free(array);
}

void UseVector()
{
    TestTimer t("UseVector");
    {
        std::vector<std::vector<Pixel> > vector(1000, std::vector<Pixel>());
        UseVector(vector);
    }
}

void UseVectorPushBack()
{
    TestTimer t("UseVectorPush");
    {
        std::vector<std::vector<Pixel> > vector(1000, std::vector<Pixel>());
        UseVectorPushBack(vector);
    }
}


int main()
{
    TestTimer t1("The whole thing");

    UseArray();
    UseVector();
    UseVectorPushBack();

    return 0;
}

1

Tôi đã làm một số bài kiểm tra mở rộng mà tôi muốn trong một thời gian. Cũng có thể chia sẻ điều này.

Đây là máy khởi động kép của tôi i7-3770, 16GB Ram, x86_64, trên Windows 8.1 và trên Ubuntu 16.04. Thêm thông tin và kết luận, nhận xét dưới đây. Đã thử nghiệm cả MSVS 2017 và g ++ (cả trên Windows và Linux).

Chương trình kiểm tra

#include <iostream>
#include <chrono>
//#include <algorithm>
#include <array>
#include <locale>
#include <vector>
#include <queue>
#include <deque>

// Note: total size of array must not exceed 0x7fffffff B = 2,147,483,647B
//  which means that largest int array size is 536,870,911
// Also image size cannot be larger than 80,000,000B
constexpr int long g_size = 100000;
int g_A[g_size];


int main()
{
    std::locale loc("");
    std::cout.imbue(loc);
    constexpr int long size = 100000;  // largest array stack size

    // stack allocated c array
    std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
    int A[size];
    for (int i = 0; i < size; i++)
        A[i] = i;

    auto duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "c-style stack array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "c-style stack array size=" << sizeof(A) << "B\n\n";

    // global stack c array
    start = std::chrono::steady_clock::now();
    for (int i = 0; i < g_size; i++)
        g_A[i] = i;

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "global c-style stack array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "global c-style stack array size=" << sizeof(g_A) << "B\n\n";

    // raw c array heap array
    start = std::chrono::steady_clock::now();
    int* AA = new int[size];    // bad_alloc() if it goes higher than 1,000,000,000
    for (int i = 0; i < size; i++)
        AA[i] = i;

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "c-style heap array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "c-style heap array size=" << sizeof(AA) << "B\n\n";
    delete[] AA;

    // std::array<>
    start = std::chrono::steady_clock::now();
    std::array<int, size> AAA;
    for (int i = 0; i < size; i++)
        AAA[i] = i;
    //std::sort(AAA.begin(), AAA.end());

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::array size=" << sizeof(AAA) << "B\n\n";

    // std::vector<>
    start = std::chrono::steady_clock::now();
    std::vector<int> v;
    for (int i = 0; i < size; i++)
        v.push_back(i);
    //std::sort(v.begin(), v.end());

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::vector duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::vector size=" << v.size() * sizeof(v.back()) << "B\n\n";

    // std::deque<>
    start = std::chrono::steady_clock::now();
    std::deque<int> dq;
    for (int i = 0; i < size; i++)
        dq.push_back(i);
    //std::sort(dq.begin(), dq.end());

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::deque duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::deque size=" << dq.size() * sizeof(dq.back()) << "B\n\n";

    // std::queue<>
    start = std::chrono::steady_clock::now();
    std::queue<int> q;
    for (int i = 0; i < size; i++)
        q.push(i);

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::queue duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::queue size=" << q.size() * sizeof(q.front()) << "B\n\n";
}

Các kết quả

//////////////////////////////////////////////////////////////////////////////////////////
// with MSVS 2017:
// >> cl /std:c++14 /Wall -O2 array_bench.cpp
//
// c-style stack array duration=0.15ms
// c-style stack array size=400,000B
//
// global c-style stack array duration=0.130ms
// global c-style stack array size=400,000B
//
// c-style heap array duration=0.90ms
// c-style heap array size=4B
//
// std::array duration=0.20ms
// std::array size=400,000B
//
// std::vector duration=0.544ms
// std::vector size=400,000B
//
// std::deque duration=1.375ms
// std::deque size=400,000B
//
// std::queue duration=1.491ms
// std::queue size=400,000B
//
//////////////////////////////////////////////////////////////////////////////////////////
//
// with g++ version:
//      - (tdm64-1) 5.1.0 on Windows
//      - (Ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0 20160609 on Ubuntu 16.04
// >> g++ -std=c++14 -Wall -march=native -O2 array_bench.cpp -o array_bench
//
// c-style stack array duration=0ms
// c-style stack array size=400,000B
//
// global c-style stack array duration=0.124ms
// global c-style stack array size=400,000B
//
// c-style heap array duration=0.648ms
// c-style heap array size=8B
//
// std::array duration=1ms
// std::array size=400,000B
//
// std::vector duration=0.402ms
// std::vector size=400,000B
//
// std::deque duration=0.234ms
// std::deque size=400,000B
//
// std::queue duration=0.304ms
// std::queue size=400,000
//
//////////////////////////////////////////////////////////////////////////////////////////

Ghi chú

  • Được lắp ráp bởi trung bình 10 lần chạy.
  • Ban đầu tôi cũng thực hiện các bài kiểm tra std::sort()(bạn có thể thấy nó nhận xét) nhưng đã xóa chúng sau vì không có sự khác biệt đáng kể nào.

Kết luận và nhận xét của tôi

  • Lưu ý rằng mảng kiểu c toàn cầu mất gần như nhiều thời gian như mảng kiểu heap c
  • Trong tất cả các thử nghiệm tôi nhận thấy sự ổn định đáng chú ý trong std::array thay đổi thời gian giữa các lần chạy liên tiếp, trong khi khác đặc biệt là các cấu trúc dữ liệu std :: thay đổi dữ dội so với
  • Tối ưu hóa O3 không cho thấy bất kỳ sự khác biệt đáng chú ý về thời gian
  • Xóa tối ưu hóa trên Windows cl (no -O2) và trên g ++ (Win / Linux no -O2, no -march = bản địa) tăng số lần TÍN HIỆU. Riêng đối với std :: structs dữ liệu. Nhìn chung thời gian trên MSVS cao hơn g ++, nhưng std::arraymảng kiểu c nhanh hơn trên Windows mà không cần tối ưu hóa
  • g ++ tạo mã nhanh hơn trình biên dịch của microsoft (rõ ràng nó chạy nhanh hơn ngay cả trên Windows).

Bản án

Tất nhiên đây là mã cho một bản dựng được tối ưu hóa. Và vì câu hỏi là vềstd::vector có nên nó rất nhiều! chậm hơn các mảng đơn giản (được tối ưu hóa / không tối ưu hóa). Nhưng khi bạn đang làm một điểm chuẩn, tự nhiên bạn muốn tạo mã được tối ưu hóa.

Ngôi sao của chương trình cho tôi mặc dù đã được std::array.


0

Với các tùy chọn phù hợp, vectơ và mảng có thể tạo asm giống hệt nhau . Trong những trường hợp này, tất nhiên chúng có cùng tốc độ, bởi vì bạn nhận được cùng một tệp thực thi.


1
Trong trường hợp này, họ dường như không tạo ra cùng một hội đồng. Cụ thể, dường như không có cách nào để chặn cuộc gọi đến các nhà xây dựng bằng các vectơ. Bạn có thể tham khảo các câu trả lời ở đây cho vấn đề đó (nó đã đọc rất lâu nhưng nó giải thích tại sao có sự khác biệt về hiệu suất trong các trường hợp khác với trường hợp kiểm tra mô phỏng trong liên kết mà bạn đã chứng minh.) (Thực ra, dường như có một cách - - viết một công cụ cấp phát STL tùy chỉnh, như đã đề xuất. Cá nhân tôi thấy công việc này không cần thiết hơn là sử dụng malloc)
kizzx2

1
@ kizzx2: Việc bạn phải sử dụng các đối tượng không có cấu trúc như vậy là một điều tốt, bởi vì đó là một lỗi 99% (tôi có thể đánh giá quá thấp) thời gian. Tôi đã đọc các câu trả lời khác và tôi nhận ra rằng tôi không giải quyết tình huống cụ thể của bạn (không cần, các câu trả lời khác là đúng), nhưng tôi vẫn muốn cung cấp cho bạn ví dụ này về cách các vectơ và mảng có thể hành xử giống hệt nhau.

@Roger: thật tuyệt! Cảm ơn liên kết
kizzx2

0

Bằng cách này, làm chậm sự nhìn thấy của bạn trong các lớp bằng vectơ cũng xảy ra với các kiểu tiêu chuẩn như int. Đây là một mã đa luồng:

#include <iostream>
#include <cstdio>
#include <map>
#include <string>
#include <typeinfo>
#include <vector>
#include <pthread.h>
#include <sstream>
#include <fstream>
using namespace std;

//pthread_mutex_t map_mutex=PTHREAD_MUTEX_INITIALIZER;

long long num=500000000;
int procs=1;

struct iterate
{
    int id;
    int num;
    void * member;
    iterate(int a, int b, void *c) : id(a), num(b), member(c) {}
};

//fill out viterate and piterate
void * viterate(void * input)
{
    printf("am in viterate\n");
    iterate * info=static_cast<iterate *> (input);
    // reproduce member type
    vector<int> test= *static_cast<vector<int>*> (info->member);
    for (int i=info->id; i<test.size(); i+=info->num)
    {
        //printf("am in viterate loop\n");
        test[i];
    }
    pthread_exit(NULL);
}

void * piterate(void * input)
{
    printf("am in piterate\n");
    iterate * info=static_cast<iterate *> (input);;
    int * test=static_cast<int *> (info->member);
    for (int i=info->id; i<num; i+=info->num) {
        //printf("am in piterate loop\n");
        test[i];
    }
    pthread_exit(NULL);
}

int main()
{
    cout<<"producing vector of size "<<num<<endl;
    vector<int> vtest(num);
    cout<<"produced  a vector of size "<<vtest.size()<<endl;
    pthread_t thread[procs];

    iterate** it=new iterate*[procs];
    int ans;
    void *status;

    cout<<"begining to thread through the vector\n";
    for (int i=0; i<procs; i++) {
        it[i]=new iterate(i, procs, (void *) &vtest);
    //  ans=pthread_create(&thread[i],NULL,viterate, (void *) it[i]);
    }
    for (int i=0; i<procs; i++) {
        pthread_join(thread[i], &status);
    }
    cout<<"end of threading through the vector";
    //reuse the iterate structures

    cout<<"producing a pointer with size "<<num<<endl;
    int * pint=new int[num];
    cout<<"produced a pointer with size "<<num<<endl;

    cout<<"begining to thread through the pointer\n";
    for (int i=0; i<procs; i++) {
        it[i]->member=&pint;
        ans=pthread_create(&thread[i], NULL, piterate, (void*) it[i]);
    }
    for (int i=0; i<procs; i++) {
        pthread_join(thread[i], &status);
    }
    cout<<"end of threading through the pointer\n";

    //delete structure array for iterate
    for (int i=0; i<procs; i++) {
        delete it[i];
    }
    delete [] it;

    //delete pointer
    delete [] pint;

    cout<<"end of the program"<<endl;
    return 0;
}

Hành vi từ mã cho thấy việc khởi tạo vectơ là phần dài nhất của mã. Một khi bạn vượt qua cái cổ chai đó. Phần còn lại của mã chạy cực kỳ nhanh. Điều này đúng cho dù bạn đang chạy bao nhiêu chủ đề.

Bằng cách này bỏ qua số lượng hoàn toàn điên rồ bao gồm. Tôi đã sử dụng mã này để kiểm tra mọi thứ cho một dự án để số lượng bao gồm tiếp tục tăng lên.


0

Tôi chỉ muốn đề cập rằng vectơ (và smart_ptr) chỉ là một lớp mỏng thêm vào trên các mảng thô (và con trỏ thô). Và thực sự thời gian truy cập của một vectơ trong bộ nhớ liên tục nhanh hơn mảng. Đoạn mã sau cho thấy kết quả của việc khởi tạo và truy cập vectơ và mảng.

#include <boost/date_time/posix_time/posix_time.hpp>
#include <iostream>
#include <vector>
#define SIZE 20000
int main() {
    srand (time(NULL));
    vector<vector<int>> vector2d;
    vector2d.reserve(SIZE);
    int index(0);
    boost::posix_time::ptime start_total = boost::posix_time::microsec_clock::local_time();
    //  timer start - build + access
    for (int i = 0; i < SIZE; i++) {
        vector2d.push_back(vector<int>(SIZE));
    }
    boost::posix_time::ptime start_access = boost::posix_time::microsec_clock::local_time();
    //  timer start - access
    for (int i = 0; i < SIZE; i++) {
        index = rand()%SIZE;
        for (int j = 0; j < SIZE; j++) {

            vector2d[index][index]++;
        }
    }
    boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time();
    boost::posix_time::time_duration msdiff = end - start_total;
    cout << "Vector total time: " << msdiff.total_milliseconds() << "milliseconds.\n";
    msdiff = end - start_acess;
    cout << "Vector access time: " << msdiff.total_milliseconds() << "milliseconds.\n"; 


    int index(0);
    int** raw2d = nullptr;
    raw2d = new int*[SIZE];
    start_total = boost::posix_time::microsec_clock::local_time();
    //  timer start - build + access
    for (int i = 0; i < SIZE; i++) {
        raw2d[i] = new int[SIZE];
    }
    start_access = boost::posix_time::microsec_clock::local_time();
    //  timer start - access
    for (int i = 0; i < SIZE; i++) {
        index = rand()%SIZE;
        for (int j = 0; j < SIZE; j++) {

            raw2d[index][index]++;
        }
    }
    end = boost::posix_time::microsec_clock::local_time();
    msdiff = end - start_total;
    cout << "Array total time: " << msdiff.total_milliseconds() << "milliseconds.\n";
    msdiff = end - start_acess;
    cout << "Array access time: " << msdiff.total_milliseconds() << "milliseconds.\n"; 
    for (int i = 0; i < SIZE; i++) {
        delete [] raw2d[i];
    }
    return 0;
}

Đầu ra là:

    Vector total time: 925milliseconds.
    Vector access time: 4milliseconds.
    Array total time: 30milliseconds.
    Array access time: 21milliseconds.

Vì vậy, tốc độ sẽ gần như giống nhau nếu bạn sử dụng đúng cách. (như những người khác đã đề cập bằng cách sử dụng dự trữ () hoặc thay đổi kích thước ()).


0

Chà, bởi vì vector :: resize () xử lý nhiều hơn so với cấp phát bộ nhớ đơn giản (bởi malloc).

Cố gắng đặt một điểm dừng trong hàm tạo sao chép của bạn (xác định nó để bạn có thể dừng điểm!) Và sẽ có thêm thời gian xử lý.


0

Tôi phải nói rằng tôi không phải là một chuyên gia về C ++. Nhưng để thêm một số kết quả thí nghiệm:

biên dịch: gcc-6.2.0 / bin / g ++ -O3 -std = c ++ 14 vector.cpp

máy móc:

Intel(R) Xeon(R) CPU E5-2690 v2 @ 3.00GHz 

HĐH:

2.6.32-642.13.1.el6.x86_64

Đầu ra:

UseArray completed in 0.167821 seconds
UseVector completed in 0.134402 seconds
UseConstructor completed in 0.134806 seconds
UseFillConstructor completed in 1.00279 seconds
UseVectorPushBack completed in 6.6887 seconds
The whole thing completed in 8.12888 seconds

Ở đây, điều duy nhất tôi cảm thấy kỳ lạ là hiệu năng "UseFillConstructor" so với "UseConstructor".

Mật mã:

void UseConstructor()
{
    TestTimer t("UseConstructor");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels(dimension*dimension);
        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}


void UseFillConstructor()
{
    TestTimer t("UseFillConstructor");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels(dimension*dimension, Pixel(255,0,0));
    }
}

Vì vậy, "giá trị" bổ sung được cung cấp làm chậm hiệu năng khá nhiều, mà tôi nghĩ là do nhiều lệnh gọi để sao chép hàm tạo. Nhưng...

Biên dịch:

gcc-6.2.0/bin/g++ -std=c++14 -O vector.cpp

Đầu ra:

UseArray completed in 1.02464 seconds
UseVector completed in 1.31056 seconds
UseConstructor completed in 1.47413 seconds
UseFillConstructor completed in 1.01555 seconds
UseVectorPushBack completed in 6.9597 seconds
The whole thing completed in 11.7851 seconds

Vì vậy, trong trường hợp này, tối ưu hóa gcc là rất quan trọng nhưng nó không thể giúp bạn nhiều khi giá trị được cung cấp như mặc định. Điều này, chống lại học phí của tôi thực sự. Hy vọng nó sẽ giúp lập trình viên mới khi chọn định dạng khởi tạo vector nào.


0

Nó dường như phụ thuộc vào các cờ biên dịch. Đây là một mã điểm chuẩn:

#include <chrono>
#include <cmath>
#include <ctime>
#include <iostream>
#include <vector>


int main(){

    int size = 1000000; // reduce this number in case your program crashes
    int L = 10;

    std::cout << "size=" << size << " L=" << L << std::endl;
    {
        srand( time(0) );
        double * data = new double[size];
        double result = 0.;
        std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
        for( int l = 0; l < L; l++ ) {
            for( int i = 0; i < size; i++ ) data[i] = rand() % 100;
            for( int i = 0; i < size; i++ ) result += data[i] * data[i];
        }
        std::chrono::steady_clock::time_point end   = std::chrono::steady_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
        std::cout << "Calculation result is " << sqrt(result) << "\n";
        std::cout << "Duration of C style heap array:    " << duration << "ms\n";
        delete data;
    }

    {
        srand( 1 + time(0) );
        double data[size]; // technically, non-compliant with C++ standard.
        double result = 0.;
        std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
        for( int l = 0; l < L; l++ ) {
            for( int i = 0; i < size; i++ ) data[i] = rand() % 100;
            for( int i = 0; i < size; i++ ) result += data[i] * data[i];
        }
        std::chrono::steady_clock::time_point end   = std::chrono::steady_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
        std::cout << "Calculation result is " << sqrt(result) << "\n";
        std::cout << "Duration of C99 style stack array: " << duration << "ms\n";
    }

    {
        srand( 2 + time(0) );
        std::vector<double> data( size );
        double result = 0.;
        std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
        for( int l = 0; l < L; l++ ) {
            for( int i = 0; i < size; i++ ) data[i] = rand() % 100;
            for( int i = 0; i < size; i++ ) result += data[i] * data[i];
        }
        std::chrono::steady_clock::time_point end   = std::chrono::steady_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
        std::cout << "Calculation result is " << sqrt(result) << "\n";
        std::cout << "Duration of std::vector array:     " << duration << "ms\n";
    }

    return 0;
}

Các cờ tối ưu hóa khác nhau cho câu trả lời khác nhau:

$ g++ -O0 benchmark.cpp 
$ ./a.out 
size=1000000 L=10
Calculation result is 181182
Duration of C style heap array:    118441ms
Calculation result is 181240
Duration of C99 style stack array: 104920ms
Calculation result is 181210
Duration of std::vector array:     124477ms
$g++ -O3 benchmark.cpp
$ ./a.out 
size=1000000 L=10
Calculation result is 181213
Duration of C style heap array:    107803ms
Calculation result is 181198
Duration of C99 style stack array: 87247ms
Calculation result is 181204
Duration of std::vector array:     89083ms
$ g++ -Ofast benchmark.cpp 
$ ./a.out 
size=1000000 L=10
Calculation result is 181164
Duration of C style heap array:    93530ms
Calculation result is 181179
Duration of C99 style stack array: 80620ms
Calculation result is 181191
Duration of std::vector array:     78830ms

Kết quả chính xác của bạn sẽ thay đổi nhưng điều này khá điển hình trên máy của tôi.


0

Theo kinh nghiệm của tôi, đôi khi, chỉ đôi khi, vector<int>có thể chậm hơn nhiều lần int[]. Một điều cần lưu ý là vectơ của vectơ rất không giống nhau int[][]. Vì các yếu tố có lẽ không liền kề trong bộ nhớ. Điều này có nghĩa là bạn có thể thay đổi kích thước các vectơ khác nhau bên trong cái chính, nhưng CPU có thể không thể lưu trữ các phần tử cũng như trong trường hợp int[][].

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.