C ++ STL:Array vs Vector:原始元素访问性能

时间:2010-04-29 19:02:58

标签: c++ stl arrays vector

我正在建立一名翻译,因为这次我的目标是原始速度,所以在这个(原始)情况下,每个时钟周期对我都很重要。

您是否有任何经验或信息两者更快:Vector或Array? 重要的是我可以访问元素的速度(操作码接收),我不关心插入,分配,排序等。

我现在要把自己从窗户里拉出来说:

  • 在访问元素i方面,数组至少比向量快一点。

这对我来说似乎很合乎逻辑。使用向量,您可以获得阵列中不存在的所有安全性和控制开销。

(为什么)我错了吗?

不,我不能忽视性能差异 - 即使它如此小 - 我已经优化并最小化了执行操作码的VM的其他部分:)

5 个答案:

答案 0 :(得分:54)

std::vector的典型实现中的元素访问时间与通过指针对象提供的普通数组中的元素访问时间相同(即运行时< / em>指针值)

std::vector<int> v;
int *pa;
...
v[i];
pa[i]; 
// Both have the same access time

但是,作为数组对象的数组元素的访问时间优于上述两种访问(相当于通过编译时访问)指针值)

int a[100];
...
a[i];
// Faster than both of the above

例如,通过运行时指针值可用的int数组的典型读取访问权限将在x86平台上的已编译代码中显示如下

// pa[i]
mov ecx, pa // read pointer value from memory
mov eax, i
mov <result>, dword ptr [ecx + eax * 4]

对vector元素的访问看起来几乎相同。

对可用作数组对象的本地int数组的典型访问如下所示

// a[i]
mov eax, i
mov <result>, dword ptr [esp + <offset constant> + eax * 4]

对可用作数组对象的全局int数组的典型访问如下所示

// a[i]
mov eax, i
mov <result>, dword ptr [<absolute address constant> + eax * 4]

性能的差异源于第一个变体中的额外mov指令,该指令必须进行额外的内存访问。

然而,差异可以忽略不计。并且它很容易被优化到在多访问上下文中完全相同的程度(通过在寄存器中加载目标地址)。

因此,当数组可以通过数组对象直接访问而不是通过指针对象访问时,关于“数组快一点”的语句是正确的。但这种差异的实际价值几乎没有。

答案 1 :(得分:8)

你可能正在咆哮错误的树。缓存未命中可能比执行的指令数量重要得多。

答案 2 :(得分:3)

没有。在引擎盖下,std::vector和C ++ 0x std::array通过将n添加到指向第一个元素的指针,找到指向元素n的指针。

vector::at可能比array::at慢,因为前者必须与变量进行比较,而后者则与常量进行比较。 那些是提供边界检查的函数,而不是operator[]

如果你的意思是C风格的数组而不是C ++ 0x std::array,那么就没有at成员,但这一点仍然存在。

编辑:如果您有操作码表,则全局数组(例如externstatic链接)可能会更快。当常量放在括号内时,全局数组的元素可以作为全局变量单独寻址,而操作码通常是常量。

无论如何,这都是过早的优化。如果你不使用任何vector的调整大小功能,它看起来就像一个数组,你应该能够轻松地在两者之间进行转换。

答案 3 :(得分:2)

您将苹果与橙子进行比较。数组具有常量大小并自动分配,而向量具有动态大小并且是动态分配的。你使用哪个取决于你需要什么。

通常,数组的分配“更快”(因为比较没有意义,因为动态分配较慢)。但是,访问元素应该是相同的。 (当然,数组可能更有可能位于缓存中,但在首次访问后无关紧要。)

另外,我不知道你在说什么“安全性”,vector有很多方法可以像数组一样获得未定义的行为。虽然它们有at(),但如果您知道索引有效,则不需要使用它。

最后,剖析并查看生成的程序集。没有人猜测会解决任何问题。

答案 4 :(得分:0)

要获得不错的结果,请使用std::vector作为后备存储,并在主循环或其他任何内容之前获取指向其第一个元素的指针:

std::vector<T> mem_buf;
// stuff
uint8_t *mem=&mem_buf[0];
for(;;) {
    switch(mem[pc]) {
    // stuff
    }
}

这避免了在operator[]中执行边界检查的过度有用的实现的任何问题,并且在代码中稍后进入诸如mem_buf[pc]的表达式时使单步执行更容易。

如果每条指令都做了足够的工作,而且代码变化足够大,那么这比使用全局数组的速度要快一些。 (如果差异很明显,则需要使操作码变得更复杂。)

与使用全局数组相比,在x86上,这种调度的指令应该更简洁(任何地方都没有32位位移字段),对于更多类似RISC的目标,应该生成更少的指令(没有TOC查找)或者是笨拙的32位常数),因为常用的值都在堆栈帧中。

我真的不相信以这种方式优化解释器的调度循环将产生良好的投入回报 - 如果这是一个问题,应该真正做更多的指示 - 但我认为它应该是'需要很长时间才能尝试一些不同的方法并测量差异。在出现意外行为时,应该查看生成的汇编语言(以及x86上的机器代码,指令长度可能是一个因素),以检查明显的低效率。