成员与全局阵列访问性能

时间:2010-07-28 20:22:51

标签: c++ performance

考虑以下情况:

class MyFoo {
public:
  MyFoo();
  ~MyFoo();
  void doSomething(void);
private:
  unsigned short things[10]; 
};

class MyBar {
public:
  MyBar(unsigned short* globalThings);
  ~MyBar();
   void doSomething(void);
private:
  unsigned short* things;
};

MyFoo::MyFoo() {
  int i;
  for (i=0;i<10;i++) this->things[i] = i;
};

MyBar::MyBar(unsigned short* globalThings) {
  this->things = globalThings;
};

void MyFoo::doSomething() {
  int i, j;
  j = 0;
  for (i = 0; i<10; i++) j += this->things[i];
};

void MyBar::doSomething() {
  int i, j;
  j = 0;
  for (i = 0; i<10; i++) j += this->things[i];
};


int main(int argc, char argv[]) {
  unsigned short gt[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

  MyFoo* mf = new MyFoo();
  MyBar* mb = new MyBar(gt);

  mf->doSomething();
  mb->doSomething();
}

有没有先天的理由相信mf.doSomething()会比mb.doSomething()运行得更快?如果可执行文件是100MB,那会改变吗?

5 个答案:

答案 0 :(得分:2)

没有理由相信一个会明显快于另一个。如果gt(例如)足够重要,那么您可能会从以下方面获得更好的性能:

int j = std::accumulate(gt, gt+10, 0);

然而,只有10个元素,似乎不太可能存在可衡量的差异。

答案 1 :(得分:2)

因为任何东西都可以修改你的gt数组,所以可能会对MyFoo执行一些不可用于MyBar的优化(但是,在这个特定的例子中,我没有看到任何优化)

由于gt生活在本地(我们曾经称之为DATA段,但我不确定它是否仍然适用),而things仍然存在于堆中(以及mf和mb的其他部分) )可能有一些内存访问&amp;处理things的缓存问题。但是,如果您在本地创建了mf(MyFoo mf = MyFoo()),那么这将是一个问题(即thingsgf在这方面将处于平等地位。)

可执行文件的大小应该有所不同。数据的大小可能,但在大多数情况下,在第一次访问后,两个数组都将在CPU缓存中,并且应该没有区别。

答案 2 :(得分:2)

MyFoo::DoSomething可能比MyBar::DoSomething稍微快一点 这是因为当事物本地存储在数组中时,我们只需要取消引用它来获取内容,我们就可以立即访问数组。当事物存储在外部时,我们首先需要取消引用它,然后我们需要在我们访问数组之前取消引用。所以我们有两条加载指令。

我已将您的源代码编译为汇编程序(使用-O0),MyFoo::DoSomething的循环如下所示:

    jmp .L14
.L15:
    movl    -4(%ebp), %edx 
    movl    8(%ebp), %eax //Load this into %eax
    movzwl  (%eax,%edx,2), %eax //Load this->things[i] into %eax
    movzwl  %ax, %eax
    addl    %eax, -8(%ebp)
    addl    $1, -4(%ebp)
.L14:
    cmpl    $9, -4(%ebp)
    setle   %al
    testb   %al, %al
    jne .L15

现在DoSomething::Bar我们有:

    jmp .L18
.L19:
    movl    8(%ebp), %eax //Load this
    movl    (%eax), %eax //Load this->things
    movl    -4(%ebp), %edx
    addl    %edx, %edx
    addl    %edx, %eax
    movzwl  (%eax), %eax //Load this->things[i]
    movzwl  %ax, %eax
    addl    %eax, -8(%ebp)
    addl    $1, -4(%ebp)
.L18:
    cmpl    $9, -4(%ebp)
    setle   %al
    testb   %al, %al
    jne .L19

从上面可以看出有双重负荷。如果thisthis->things地址差异很大,则问题可能会更加复杂。然后它们将存在于不同的缓存页面中,并且CPU可能必须在主存储器中进行两次拉取才能访问this->things。当它们属于同一个对象时,当我们得到此对象时,我们会在this->things的同时获得this

Caveate - 优化器可能会提供一些我没想过的快捷方式。

答案 3 :(得分:1)

最有可能的额外取消引用(MyBar,必须获取成员指针的值)在性能上是无意义的,特别是如果数据数组非常大。

答案 4 :(得分:0)

可能会慢一些。问题是您访问的频率。你应该考虑的是你的机器有一个固定的缓存。当加载MyFoo以调用DoSomething时,处理器可以将整个数组加载到缓存中并读取它。但是,在MyBar中,处理器首先必须加载指针,然后加载它指向的地址。当然,在你的示例main中,它们可能都在相同的缓存行中或者足够接近,对于更大的数组,负载的数量不会随着一个额外的解引用而显着增加。

然而,总的来说,这种影响远非可以忽略。当您考虑取消引用指针时,与实际加载指向的内存相比,该成本几乎为零。如果指针指向某个已经加载的内存,则差异可以忽略不计。如果没有,你有一个缓存未命中,这是非常糟糕和昂贵的。此外,指针引入了别名问题,这基本上意味着您的编译器可以对其执行更少的乐观优化。

尽可能在对象内分配。