c++ 数组与 std::vector 和 std::array 的效率

Posted

技术标签:

【中文标题】c++ 数组与 std::vector 和 std::array 的效率【英文标题】:efficiency of c++ arrays vs std::vector and std::array 【发布时间】:2016-03-12 06:21:40 【问题描述】:

我比较了分配一维数组或二维数组的不同类型,如下所示。我发现使用 new 运算符效率更高,也许 std::arrary 和 std::vector 是一个对象,它们是通用的和安全的但更多时间?而且,我不知道为什么调用新的外部函数比调用内部函数更有效?

#include <iostream>
#include <vector>
#include <array>
#include <ctime>


void test1 () 
int *arr = new int[10000];

for (int i=0; i<10000; ++i) 
    arr[i] = 3;


for (int i=0; i<10000; ++i) 
    int a = arr[i];

delete arr;


void test11 () 
int **arr = new int*[100];
for (int i=0; i<100; ++i) 
    arr[i] = new int[100];


for (int i=0; i<100; ++i) 
    for (int j=0; j<100; ++j) 
        arr[i][j] = 3;
    


for (int i=0; i<100; ++i) 
    for (int j=0; j<100; ++j) 
        int a = arr[i][j];
    


delete [] arr;



void test2() 
std::vector<int> arr(10000);

for (int i=0; i<10000; ++i) 
    arr[i] = 3;


for (int i=0; i<10000; ++i) 
     int a = arr[i];



void test22() 
std::vector<std::vector<int> > arr(100, std::vector<int>(100));

for (int i=0; i<100; ++i) 
    for (int j=0; j<100; ++j) 
        arr[i][j] = 3;
    


for (int i=0; i<100; ++i) 
    for (int j=0; j<100; ++j) 
        int a = arr[i][j];
    





void test3(int *arr, int n) 
for (int i=0; i<n; ++i) 
    arr[i] = 3;

for (int i=0; i<n; ++i) 
     int a = arr[i];



void test33(int **arr, int m, int n) 
for (int i=0; i<m; ++i) 
    for (int j=0; j<n; ++j) 
        arr[i][j] = 3;
    


for (int i=0; i<m; ++i) 
    for (int j=0; j<n; ++j) 
        int a = arr[i][j];
    




void test4() 
std::array<int, 10000> arr;
for (int i=0; i<10000; ++i) 
    arr[i] = 3;

for (int i=0; i<10000; ++i) 
     int a = arr[i];



void test44() 
std::array<std::array<int, 100>, 100> arr;
for (int i=0; i<100; ++i) 
    for (int j=0; j<100; ++j) 
         arr[i][j] = 3;
    

for (int i=0; i<100; ++i) 
    for (int j=0; j<100; ++j)
     int a = arr[i][j];




int main() 
clock_t start, end;
start = clock();
for (int i=0; i<1000; ++i) 
    test1();

end = clock();
std::cout << (double)(end - start) * 1000.0 / CLOCKS_PER_SEC << " ms" << std::endl;

start = clock();
for (int i=0; i<1000; ++i) 
    test11();

end = clock();
std::cout << (double)(end - start) * 1000.0 / CLOCKS_PER_SEC << " ms" << std::endl;

start = clock();
for (int i=0; i<1000; ++i) 
    test2();

end = clock();
std::cout << (double)(end - start) * 1000.0 / CLOCKS_PER_SEC << " ms" << std::endl;

start = clock();
for (int i=0; i<1000; ++i) 
    test22();

end = clock();
std::cout << (double)(end - start) * 1000.0 / CLOCKS_PER_SEC << " ms" << std::endl;

start = clock();
for (int i=0; i<1000; ++i) 
    int *arr = new int[10000];
    test3(arr, 10000);
    delete arr;

end = clock();
std::cout << (double)(end - start) * 1000.0 / CLOCKS_PER_SEC << " ms" << std::endl;

start = clock();
int **arr = new int*[100];
for (int i=0; i<100; ++i) 
    arr[i] = new int[100];


for (int i=0; i<1000; ++i) 
    test33(arr, 100, 100);

delete [] arr;
end = clock();
std::cout << (double)(end - start) * 1000.0 / CLOCKS_PER_SEC << " ms" << std::endl;

start = clock();
for (int i=0; i<1000; ++i) 
    test4();

end = clock();
std::cout << (double)(end - start) * 1000.0 / CLOCKS_PER_SEC << " ms" << std::endl;

start = clock();
for (int i=0; i<1000; ++i) 
    test44();

end = clock();
std::cout << (double)(end - start) * 1000.0 / CLOCKS_PER_SEC << " ms" << std::endl;


输出是:

90 ms
80 ms
70 ms
120 ms
50 ms
40 ms
100 ms
190 ms

感谢您的帮助,也许我没有正确描述我的问题,我写了一个会多次调用的函数,这个函数新建一个数组然后删除它:

void fun() 
   int *arr = new int[10000]; //maybe very big
   //todo something else
   delete arr; 

有人告诉我效率不高,因为它每次都新建和删除,现在我有两个问题:

1.内存管理的正确方法是什么?

int *arr = new int[]; delete arr;
int **arr = new int*[]; delete [] arr;

错了?可能是这样的:

for (int i=0; i<n; ++i)
  delete [] arr;

delete arr;

2.我编写这个函数的最佳方式是什么

【问题讨论】:

在比较任何东西之前,首先修复你的代码:你用new[]分配的所有东西必须用delete[]释放。 您是否也在发布模式下编译并进行了全面优化? "内存管理的正确方法是什么?" - 使用std::vector. @Galik 在下面的 cmets 中说这些是调试构建基准。 【参考方案1】:

我不认为你的测试是正确的。也许您正在调试模式下运行?我看不出 test11() 比 test1() 更快(即使它没有释放所有内存)。

此外,在上述许多情况下,发布模式编译器会优化您的代码,因为它实际上并没有做任何事情:

for (int i=0; i<100; ++i) 
    for (int j=0; j<100; ++j) 
        int a = arr[i][j];
    

几乎可以肯定,任何编译器都会消除这种情况,因为不使用“a”,这意味着不使用“i”和“j”。

for (int i=0; i<100; ++i) 
    for (int j=0; j<100; ++j) 
        arr[i][j] = 3;
    

一些编译器甚至可以很好地消除该代码,因为内存不再被读取,但很可能不会。

我建议不要担心 vector 与 new int[] 的任何性能开销。在调试模式下,您将获得免费的调试帮助(边界检查),而在发布模式下,只要您不调用抛出超出范围的函数,代码在实际性能上将基本相同。另外,您不必担心内存管理(test1() 和 test11() 都不完全正确)。

【讨论】:

测试 1 与测试 11?测试 1 应该优于测试 11。测试 11 的局部性很差,并且会更频繁地丢失缓存,无论是否调试构建。动态二维数组很烂。对天真的评论看起来很不错。优化器将丢弃大部分代码。 再检查一下,test11() 是二维的。 test1() 是一个直单数组。 是的,脑子有问题。已编辑,但您似乎来得太快了。 对不起,我的错。我应该给你时间把它拿回来。 不。我的错。应该在发布之前进行校对。【参考方案2】:

让我们做一些工作来改进测试,并通过正确使用它给标准库一个机会......

#include <iostream>
#include <vector>
#include <array>
#include <ctime>
#include <memory>
#include <algorithm>
#include <iterator>

void test1 () 
    auto arr = std::make_unique<int[]>(10000);
    std::fill(arr.get(), arr.get() + 10000, 3);

    for (int i=0; i<10000; ++i) 
        int a = arr[i];
    


void test11 () 
    auto arr = std::make_unique<std::unique_ptr<int[]>[]>(100);
    for (auto i = 0 ; i < 100 ; ++i) 
        arr[i] = std::make_unique<int[]>(100);
    

    for (int i=0; i<100; ++i) 
        for (int j=0; j<100; ++j) 
            arr[i][j] = 3;
        
    

    for (int i=0; i<100; ++i) 
        for (int j=0; j<100; ++j) 
            int a = arr[i][j];
        
    




void test2() 
    std::vector<int> arr(10000);
    std::fill(std::begin(arr), std::end(arr), 3);

    for (int i=0; i<10000; ++i) 
        int a = arr[i];
    


void test22() 
    std::vector<std::vector<int> > arr(100, std::vector<int>(100));
    std::for_each(begin(arr),
                  end(arr),
                  [](auto& inner) 
                      std::fill(std::begin(inner), std::end(inner), 3);
                  );

    for (int i=0; i<100; ++i) 
        for (int j=0; j<100; ++j) 
            int a = arr[i][j];
        
    




void test3(int *arr, int n) 
    std::fill(arr, arr + n, 3);

    for (int i=0; i<n; ++i) 
        int a = arr[i];
    


void test33(const std::unique_ptr<std::unique_ptr<int[]>[]>& arr, int m, int n) 
    for (int i=0; i<m; ++i) 
        for (int j=0; j<n; ++j) 
            arr[i][j] = 3;
        
    

    for (int i=0; i<m; ++i) 
        for (int j=0; j<n; ++j) 
            int a = arr[i][j];
        
    



void test4() 
    std::array<int, 10000> arr;
    std::fill(std::begin(arr), std::end(arr), 3);

    for (int i=0; i<10000; ++i) 
        int a = arr[i];
    


void test44() 
    std::array<std::array<int, 100>, 100> arr;
    std::for_each(begin(arr),
                  end(arr),
                  [](auto& inner) 
                      std::fill(std::begin(inner), std::end(inner), 3);
                  );

    for (int i=0; i<100; ++i) 
        for (int j=0; j<100; ++j)
            int a = arr[i][j];
    



int main() 
    clock_t start, end;
    start = clock();
    for (int i=0; i<1000; ++i) 
        test1();
    
    end = clock();
    std::cout << "test 1 " << (double)(end - start) * 1000.0 / CLOCKS_PER_SEC << " ms" << std::endl;

    start = clock();
    for (int i=0; i<1000; ++i) 
        test11();
    
    end = clock();
    std::cout << "test 11 " << (double)(end - start) * 1000.0 / CLOCKS_PER_SEC << " ms" << std::endl;

    start = clock();
    for (int i=0; i<1000; ++i) 
        test2();
    
    end = clock();
    std::cout << "test 2 " << (double)(end - start) * 1000.0 / CLOCKS_PER_SEC << " ms" << std::endl;

    start = clock();
    for (int i=0; i<1000; ++i) 
        test22();
    
    end = clock();
    std::cout << "test 22 " << (double)(end - start) * 1000.0 / CLOCKS_PER_SEC << " ms" << std::endl;

    start = clock();
    for (int i=0; i<1000; ++i) 
        int *arr = new int[10000];
        test3(arr, 10000);
        delete [] arr;
    
    end = clock();
    std::cout << "test 3 " << (double)(end - start) * 1000.0 / CLOCKS_PER_SEC << " ms" << std::endl;

    start = clock();
    auto arr = std::make_unique<std::unique_ptr<int[]>[]>(100);
    for (auto i = 0 ; i < 100 ; ++i) 
        arr[i] = std::make_unique<int[]>(100);
    

    for (int i=0; i<1000; ++i) 
        test33(arr, 100, 100);
    
    arr.reset();

    end = clock();
    std::cout << "test 33 " << (double)(end - start) * 1000.0 / CLOCKS_PER_SEC << " ms" << std::endl;

    start = clock();
    for (int i=0; i<1000; ++i) 
        test4();
    
    end = clock();
    std::cout << "test 4 " << (double)(end - start) * 1000.0 / CLOCKS_PER_SEC << " ms" << std::endl;

    start = clock();
    for (int i=0; i<1000; ++i) 
        test44();
    
    end = clock();
    std::cout << "test 44 " << (double)(end - start) * 1000.0 / CLOCKS_PER_SEC << " ms" << std::endl;


使用 -O2 编译时在我的计算机上的结果:

test 1 0.002 ms
test 11 13.506 ms
test 2 2.753 ms
test 22 13.738 ms
test 3 1.42 ms
test 33 1.552 ms
test 4 0 ms
test 44 0 ms

我们还要注意,数组是“小”的,并且被重复分配和释放。如果可以重复使用缓冲区,那么时间上的差异将完全消失。

另请注意:test33 速度很快,因为它从不重新分配内存 - 您正在重新使用缓冲区。

【讨论】:

int a = arr[i][j]; 不是优化构建中省略的主要候选者吗?这可以解释测试 1、4 和 44 吗? 你不能像这样分析代码。您的所有代码都将被删除。结果和海报一样,都是假的。 @kfsone 优化器会看穿 @RobL 明白了。这真正测试的是内存分配。【参考方案3】:

在纯 C 数组上进行操作时,编译器能够识别展开循环的机会,从而节省少量的迭代开销。可能(不太可能)编译器不会优化 STL 容器的访问循环,因为它不知道它们是否修改了任何成员变量。

关于为什么 test11 优于 test1,我的最佳猜测是它交织了外部循环的多次迭代(利用现代 x86/x64 处理器是超标量的事实),IE:

for(int j = 0; j < 100; ++j)

    for(int i = 0; i < 100; i+=4)
    
        arr[i][j] = 3;
        arr[i+1][j] = 3;
        arr[i+2][j] = 3;
        arr[i+4][j] = 3;
    

或者可能完全是别的东西。编译器可以进行一些非常复杂的循环转换来获得每一点性能。

【讨论】:

确实如此,但它并不能轻易解决二维数组内存分配分散带来的问题。一维数组应该拆除二维数组,除非发生了一些真正偷偷摸摸的模式识别并给二维数组一个局部性改造。

以上是关于c++ 数组与 std::vector 和 std::array 的效率的主要内容,如果未能解决你的问题,请参考以下文章

std::vector 与 C++ 中的原始数组有多相似?

在 C++ std::vector 和 C 数组之间转换而不复制

C++ STL应用与实现2: 如何使用std::vector

C++ STL应用与实现2: 如何使用std::vector

C++ STL应用与实现2: 如何使用std::vector

Java 使用数组比 C++ 中的 std::vector 快 8 倍。我做错了啥?