【问题标题】:How many different pointers/levels of indirection are accessed here?这里访问了多少不同的指针/间接级别?
【发布时间】:2014-05-19 23:52:49
【问题描述】:

我有四个表示继承和组合层次结构的类:

class A{
//Structure here not important
}

class B : public A{
    int a;
    shared_ptr<C> c;
}

class C{
   shared_ptr<D> d;
}

class D{
    std::list<int> e;
}

然后我有一个vector&lt;shared_ptr&lt;A&gt;&gt;,但我迭代并从两个D std::list&lt;int&gt; 对象中求和*begin() 值:

for(int i = 0; i< vec.size(); i++){
    shared_ptr<B> b = vec[i];
    shared_ptr<C> c = b->c;
    sum += *(c->d->e.begin());
}

我正在尝试计算每次循环迭代可以进行多少次单独的缓存行访问(如果我们假设每个级别的间接/指针存储在不同的缓存行中的最坏情况)。

到目前为止,我已经计算出每次迭代有 7.25 个不同的缓存行:

  1. 访问shared_ptr&lt;A&gt;vec[i](这是0.25,因为sizeof(shared_ptr&lt;A&gt;)/64
  2. 访问A对象vec[i]指向
  3. 访问shared_ptr&lt;C&gt; c 指向
  4. 访问C对象c指向
  5. 访问 d 的 shared_ptr&lt;D&gt; 对象
  6. 访问对象D d
  7. 访问dstd::list&lt;int&gt; e指针
  8. 访问d*begin()数据

有什么我错过的吗?我不确定在循环内的堆栈上创建的对象(bc)是否可以存储在不同的缓存行中,指向它们正在访问的指针(vec[i]b-&gt;c)。

【问题讨论】:

  • 一个 shared_ptr 对象管理 2 个指针——一个指向共享对象的指针加上一个指向共享状态信息的指针。共享状态包含 2 个原子值:强引用数和弱引用数。
  • @RichardHodges 啊,所以当访问 shared_ptrs 时,指向它们的引用计数对象的指针也可能导致单独的缓存行加载?
  • 当然,如果您正在复制 shared_ptr 并因此增加共享计数。如果它只是纯粹的访问,我怀疑不是。这一行:shared_ptr&lt;C&gt; c = b-&gt;c 将导致复制,因此会导致间接 + 原子增量。通过返回参考来避免这种情况。
  • 不,您只是取消引用智能指针,而不是在该行上创建它的副本。
  • 注意make_shared&lt;T&gt;分配T和ref counf数据是相邻的,而shared_ptr&lt;T&gt;(new T(blah))进行2次分配。

标签: c++ performance pointers cpu cpu-cache


【解决方案1】:

添加答案以补充 cmets 中的对话

这是带有一些 cmets 的循环:

for(int i = 0; i< vec.size(); i++){
    shared_ptr<B> b = vec[i];  // create 1 copy of vec[i] - increments share cout
    shared_ptr<C> c = b->c;    // create 1 copy of b->c - increments share cout
    sum1 += *(c->d1->e.begin());  // merely dereference pointer
    sum2 += *(c->d2->e.begin());  // merely dereference pointer
}

如果你这样写,你可以保存一些副本,因此一些缓存行未命中:

for(int i = 0; i< vec.size(); i++){
    // take reference only - no copy. 
    //const means I promise not to modify the pointer object.
    const shared_ptr<B>& b = vec[i];  

    // take reference only - no copy. 
    //const means I promise not to modify the pointer object.
    const shared_ptr<C>& c = b->c;  // dereference b (which is really vec[i])

    sum1 += *(c->d1->e.begin());  // merely dereference pointer
    sum2 += *(c->d2->e.begin());  // merely dereference pointer
}

【讨论】:

  • 它可能存储在寄存器中,也可能在堆栈中。我感谢您对效率的追求。让我给你一些 30 年前的建议:性能是硬件问题,优雅是软件问题。使您的代码优雅、易于阅读和简洁。如果您清楚自己的意图并使用标准惯用语,您会惊讶于编译器可以做什么来优化它。如果您尝试再次猜测编译器,您将阻止它的优化器正常工作。
  • 我只是想了解“幕后”发生了什么
  • 嗯,答案是:“视情况而定”。如果您使用 -O0 进行编译,将生成几乎完全按照您编写的指令的指令。如果您使用 -O2 或 -O3 进行编译,然后查看生成的机器代码,您会发现编译器看到了优化您的意图的方法,这些方法是您在一千年内都不会发现的。它将重新排序您的代码以优化流水线和缓存,它将重新排序代码以从循环中删除冗余操作,如果它可以证明您不需要它,它将完全省略代码。
  • 建议您使用 -S 选项进行编译(生成汇编源代码),然后查看。查看启用了不同级别优化的代码。
  • 问题是从A 向下转换到B 需要显式的动态或静态转换——所以原始代码在编写时是非法的,你不能在没有shared_ptr&lt;B&gt; &amp; 的情况下使用一份副本,因为没有 shared_ptr&lt;B&gt; 可供参考...
猜你喜欢
  • 2019-02-14
  • 2012-04-22
  • 2012-02-07
  • 2011-03-19
  • 2010-11-13
  • 1970-01-01
  • 2011-02-14
  • 1970-01-01
相关资源
最近更新 更多