【问题标题】:Why Maintaining Sorted Array is faster than Vector in C++为什么在 C++ 中维护有序数组比向量快
【发布时间】:2013-07-03 15:13:44
【问题描述】:

我正在创建一个大小为 100 的数组和向量并生成一个随机值并尝试将数组和向量保持为已排序。 这是我的代码

vector<int> myVector;
int arr[SIZE];
clock_t start, finish;
int random;
for(int i=0; i<SIZE;i++)
{
    myVector.push_back(0);
    arr[i] = 0;
}
    //testing for Array
start = clock(); 
for(int i=0; i<MAX;++i)
{
    random = getRandom(); //returns rand() % 100
    for(int j=0; j<SIZE;++j){
        if(random > arr[j])
        {
            for(int k = SIZE - 1; k > j ; --k)
            {
                arr[k] = arr[k-1];
            }
            arr[j] = random;
            break;
        }
    }
}
finish = clock();
cout << "Array Time " << finish - start << endl;

//Vector Processing
start = clock();
for(int i=0; i<MAX;++i)
{
    random = getRandom(); //returns rand() % 100
    for(int j=0; j<SIZE;++j){
        if(random > myVector[j])
        {
            for(int k = SIZE - 1; k > j ; --k)
            {
                myVector[k] = myVector[k-1];
            }
            myVector[j] = random;
            break;
        }
    }
}
finish = clock();
cout << "Vector Time " << finish - start << endl;

输出如下:

阵列时间:5

向量时间:83

在这种情况下,我无法理解为什么向量与数组相比如此缓慢? 这是否与优先选择向量而不是数组的经验法则相矛盾。

请帮忙!

【问题讨论】:

  • 你做了哪些优化?使用优化编译器,结果应该几乎相同。
  • @HunterMcMillen,它必须连续存储在内存中。
  • 如果你需要维护一个有序的容器,你为什么要使用其中一个?使用像 std::multiset 这样的东西来保持自己的排序。
  • 我建议,如果您要比较编译与最大优化和发布库的性能(一些实现为调试和生产提供不同的容器)。同样为了更可靠的比较,您应该预先生成所有随机数(将它们存储在数组/向量中,两次运行都相同)并在两个容器中运行它们(以便在两种情况下操作顺序相同)
  • 看来 GCC 基本上正在优化整个事情,因为它意识到你不使用结果。处理数组后尝试添加cout &lt;&lt; arr[0] &lt;&lt; endl;。比较this(不带cout)和this(带cout

标签: c++ performance optimization vector


【解决方案1】:

首先:编程中的许多经验法则不是关于获得几毫秒的性能,而是关于管理复杂性,从而避免错误。在这种情况下,它是关于执行范围检查,大多数向量实现在调试模式下执行,而数组不执行。它还涉及动态数组的内存管理 - vector 确实管理它自己的内存,而您必须在数组中手动​​执行它,否则可能会导致内存泄漏(曾经忘记delete[] 或使用delete 代替?我是你有!)。它是关于易用性,例如调整向量的大小或在中间插入元素,这是手动管理数组的繁琐工作。
换句话说,性能测量永远不会与经验法则相矛盾,因为经验法则永远不会以性能为目标。性能测量只是遵守编码准则的少数可能原因之一。

乍一看,我猜你没有启用优化。向量性能损失的主要来源将是许多向量实现为调试构建启用的索引检查。那些不会在优化的构建中发挥作用,所以这应该是您首先关心的问题。 经验法则:未启用优化的性能测量毫无意义

如果启用优化仍然显示出更好的阵列性能,那么还有另一个区别:

数组存储在堆栈中,因此编译器可以在编译时直接使用地址并计算地址偏移量,而向量元素存储在堆中,编译器必须取消引用存储在向量中的指针。我希望优化器取消引用指针一次并计算从该点开始的地址偏移量。尽管如此,与编译时计算的地址偏移量相比,性能损失可能会很小,尤其是如果优化器可以稍微展开循环的话。 这仍然不违反经验法则,因为您在这里比较的是苹果和梨。经验法则说,

优先使用std::vector 胜过动态 数组,优先使用std::array 胜过固定 数组。

所以要么使用动态分配的数组(请包括某种delete[]),或者将固定大小的数组与std::array 进行比较。在 C++14 中,您必须在游戏中考虑新的候选对象,即 std::dynarray 和 C++14 VLA,不可调整大小的运行时长度数组,可与 C 的 VLA 相媲美。

更新: 正如 cmets 中所指出的,优化器擅长识别没有副作用的代码,例如您从未读取过的数组操作。 std::vector 实现非常复杂,优化器通常不会看穿这几层间接并优化所有插入,因此与向量的一些时间相比,数组的时间为零。 循环之后读取数组内容将禁用这种粗鲁的优化。

【讨论】:

  • 堆/栈不应该有区别,至少不会有一个数量级的区别。这看起来更像是一个调试库而不是一个原始数组
  • C++14 也包含 VLA。
  • @DavidRodríguez-dribeas:我相信这更像是编译器在 as-if 规则下优化了一大堆东西。在处理完数组后添加cout &lt;&lt; arr[0] 可以使处理时间具有可比性
  • @chris 感谢您的提示。查了一下,我发现它们是 VLA,但不像 C 中那样精确:lists.cs.uiuc.edu/pipermail/cfe-commits/Week-of-Mon-20130415/…
  • 如果我设置优化级别 -O 和 -O1 则向量和数组都需要相同的时间,但是如果 -O2 数组再次变得更快。知道是什么导致了差异
【解决方案2】:

向量类必须动态增长内存,这可能涉及不时复制整个事物。 它还必须为许多操作调用内部函数——比如重新分配。 它还可能具有边界检查等安全功能。

同时,您的数组已预先分配,并且您的所有操作可能不会调用任何内部函数。

这是更多功能的间接费用。

谁说向量在所有情况下都应该比数组快? 您的数组不需要增长,这是数组确实更快的特殊情况!

【讨论】:

  • 向量在计算过程中不需要增长。它只会在初始 push_back 使用期间增长。
  • 好的,对,没有看到向量最初被填满。那么我的建议是边界检查和内部函数调用
【解决方案3】:

因为数组是本机数据类型,而编译器可以直接从内存中对其进行操作,所以它们由编译后的 exec 在内部进行管理。

另一方面,你得到的向量更像是一个类,我读到的模板,它需要通过另一个头文件和库进行一些管理。

本质上,原生数据类型可以在不包含任何标题的情况下进行管理,这使得它们更容易从程序中操作,而无需使用外部代码。导致向量时间的开销是程序需要查看代码并使用与向量数据类型相关的方法。

每次你需要向你的应用添加更多代码并从中进行操作时,都会使你的应用性能下降

您可以阅读它,hereherehere

【讨论】:

  • 已经证明向量可以和数组一样快。您的链接只会加强这一点。当然,在这种情况下,我们将动态分配的内存与堆栈分配的内存进行比较。
  • 并非如此。在优化的构建中,在其他任何条件相同的情况下,向量应该具有与数组完全相同的性能。语言和库是专门为此设计的
  • 需要使用堆栈总是会增加开销,使其使用速度变慢。你不能说它和数组一样快,因为它永远不会,因为数组是可动态分配的本机数据类型,不鼓励使用它,但仍然更快。
  • 包含的标头数量不会影响程序性能。 C++ 不是解释型语言。添加更多代码也不会降低性能。
  • @SigTerm 是的,你是对的,我已经走了很多路,但是需要使用它会增加开销,不是包含它的单一动作,而是使用它。
猜你喜欢
  • 2012-12-03
  • 2013-08-12
  • 2023-04-06
  • 1970-01-01
  • 2017-04-06
  • 1970-01-01
  • 2021-12-22
  • 2015-05-13
  • 2012-10-02
相关资源
最近更新 更多