向量::: operator []开销

问题描述

| 显然,在分析(科学计算)C ++代码之后,有25%(!)的时间花费在调用calls0ѭ上。没错,我的代码将所有时间都花在ѭ1s(还有一些
vector<int>
s)上,但我仍然想知道,与C风格相比,是否应该有significant3 significant的大量开销数组? (我已经看到了另一个有关SO的问题,但是关于
[]
vs
at()
,但是显然even4ѭ对我来说太慢了?!) 谢谢, 安东尼 (编辑:仅供参考:在Ubuntu上使用g ++ -O3 4.5.2版)     

解决方法

std::vector::operator[]
应该相当高效,但是编译器必须是偏执狂,对于函数的每次调用,都必须假定向量可能已移至内存中的其他位置。 例如此代码
for (int i=0,n=v.size(); i<n; i++)
{
    total += v[i] + foo();
}
如果事先不知道
foo
的代码,则每次编译器都被迫重新加载向量起始地址,因为向量可能由于
foo()
中的代码而被重新分配。 如果您确定该向量不会在内存中移动或重新分配,则可以使用以下方法将查找操作排除在外:
double *vptr = &v[0]; // Address of first element
for (int i=0,n=v.size(); i<n; i++)
{
    total += vptr[i] + foo();
}
采用这种方法,可以节省一次存储器查找操作(整个循环可能会在寄存器中存入
vptr
)。 效率低下的另一个原因可能是缓存破坏。要查看这是否是一个问题,一个简单的技巧就是通过一些数量不均匀的元素来过度分配向量。 原因是因为如果您有很多向量,例如所有4096个元素的地址中都将具有相同的低位,并且由于高速缓存行无效,您可能最终会失去很多速度。 例如在我的电脑上的这个循环
std::vector<double> v1(n),v2(n),v3(n),v4(n),v5(n);
for (int i=0; i<1000000; i++)
    for (int j=0; j<1000; j++)
    {
        v1[j] = v2[j] + v3[j];
        v2[j] = v3[j] + v4[j];
        v3[j] = v4[j] + v5[j];
        v4[j] = v5[j] + v1[j];
        v5[j] = v1[j] + v2[j];
    }
如果
n == 8191
,则执行约8.1秒;如果
n == 10000
,则执行3.2秒。注意,内部循环始终从0到999,与
n
的值无关;不同的只是内存地址。 根据处理器/体系结构的不同,我已经观察到由于缓存破坏而导致的速度甚至降低了10倍。     ,在现代的编译器中,在释放模式下且启用了优化,与原始指针相比,使用
operator []
不会产生开销:调用完全内联,并且解析为仅指针访问。 我猜想您是以某种方式复制了赋值中的返回值,这实际上导致了在指令中花费了25%的时间。[与
float
int
不相关] 或者,您的其余代码简直是飞快的。     ,是的,会有一些开销,因为通常“ѭ20”将包含指向动态分配的数组的指针,其中数组只是\“ there \”。这意味着在数组上使用
[]
时,通常在
vector::operator[]
中会有额外的内存取消引用。 (请注意,如果您有一个指向数组的指针,通常不如
vector
好。) 如果您正在同一代码段中通过相同的
vector
或指针执行多次访问而又不导致向量可能需要重新分配,则这种额外取消引用的开销可能会在多次访问中分担,并且可以忽略不计。 例如。
#include <vector>
extern std::vector<float> vf;
extern float af[];
extern float* pf;

float test1(long index)
{
        return vf[index];
}

float test2(long index)
{
        return af[index];
}

float test3(long index)
{
        return pf[index];
}
在g ++上为我生成以下代码(修剪了一些guff):
.globl _Z5test1i
        .type   _Z5test1i,@function
_Z5test1i:
        movq    vf(%rip),%rax
        movss   (%rax,%rdi,4),%xmm0
        ret
        .size   _Z5test1i,.-_Z5test1i

.globl _Z5test2i
        .type   _Z5test2i,@function
_Z5test2i:
        movss   af(,%xmm0
        ret
        .size   _Z5test2i,.-_Z5test2i

.globl _Z5test3i
        .type   _Z5test3i,@function
_Z5test3i:
        movq    pf(%rip),%xmm0
        ret
        .size   _Z5test3i,.-_Z5test3i
请注意,指针和向量版本如何产生完全相同的代码,而仅使用数组版本“ winning \”。     ,通常,应该没有显着差异。差异可以 实际上,由于各种原因,这取决于 编译器优化特定的代码位。一个重大的可能 区别:您正在分析,这意味着您正在执行 检测代码。我不知道您使用的是什么探查器,但是 编译器经常出于各种原因关闭内联 进行性能分析的工具。您确定不是这种情况吗 在这里,这是人为地导致索引出现 比内联要花费更多的时间。     ,纯数组访问是(几乎)直接内存读取,而operator []是vector <>的成员方法。 如果内联正确,则应该相同,否则,对于计算密集型工作,开销非常大。