Clayman's Graphics Corner

DirectX,Shader & Game Engine Programming

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

  C++ Low level performance optimize 2 

 

    上一篇 文章讨论了一些底层代码的优化技巧,本文继续讨论一些相关的内容。

     首先,上一篇文章讨论cache missing的重要性时,用了list做比较,目的并不是说list没有用,而是说明cache missing会对性能有重要影响。如果元素不多,并且对象复制的代价很大,那么list可能就是更好的选择。其次,这里讨论的大部分是编码时一些比较底层的技巧,当遇到性能问题时,应该先考虑是否能在高层改进算法,减少运算,实在不行,在考虑这类优化技巧。性能优化的挑战就在于没有完美的永远适用的方案,了解这些技巧让我们在优化代码时有更多武器,但最终选择哪个方案还需要更加实际情况,并且以profile的实际数据为依据来做。

1.  Data Layout

调整数据布局是常见的优化手段,做此类优化时有几点需要注意:首先是内存占用,现代编译器默认大多以16或32位对齐,因此

struct BadLayout
{
         int8_t i0;
         int32_t i1;
         int8_t i2;
};

struct GoodLayout
{
         int8_t i0;
         int8_t i2;
         int32_t i1;
};

sizeof(Goodlayout) >= sizeof(BadLayout) 在vs2013默认对齐设置下,BadLayout==12 byte,GoodLayout==8byte。

      其次,经常访问或者相关的数据应该放到一起,减少cache missing。Going native2013 Andrei Alexandrescu介绍了facebook做的重要性能优化就是把php代码编译为c++代码,而在代码转换中重要的一步就是根据数据的”hotness”重新布局。

struct BadLayout
{
        auto user0_data0;
        auto user1_data1;
        auto user0_data1;
        auto user1_data0;
};

struct GoodLayout
{
    auto user0_data0;
    auto user0_data1;
    auto user1_data0;
    auto user1_data1;

};

        最后,可维护性!这一点非常重要,对于一些生命周期较长的项目来说,把数据按逻辑组织更易于维护,减少潜在bug的重要性,如果性能差别不大,我通常更愿意让代码看起来好读J

 

2.  Code cache

struct BitBool
{
         bool b0 :1;
         bool b1 :1;
         bool b2 :1;
}

struct NormalBool
{
         bool b0;
         bool b1;
         bool b2;   
}

      上次的例子中,这段代码比较有争议,让我们从时间和空间方面来分析。时间上,因为BitBool需要额外指令来访问元素,因此效率一定比NormalBool低,但差别非常小,几乎可以忽略。再看空间上,但从结构本身看,显然BitBool更小,但是由于访问元素需要额外指令,实际应用中,生成的代码一定比NormalBool多,读取访问的次数越多,生成的代码也越多(内联的结果),而代码也需要占用内存空间!!cache line中通常一半是代码,一半是数据。因此,不一定因为BitBool本身小就得到更好的cache。大部分文章在讨论cache missing时都只介绍了数据,而忽略了代码也需要占用内存,也会有cache missing。某些游戏引擎会在update entity时先把对象按照类型排序,就是为了减少代码的cache missing。

         最后,这个例子的目的是让大家了解过度优化可能并不会带来性能提升,实际应用中两种写法的虽然有性能差距,但基本可以忽略。

 3.  more about bit field

     上一个例子让我想起了bitfield另一个微妙的地方,假设f1和f2在两个不同线程中,考虑下面代码是安全的吗?

struct BitField
{
         bool b0 : 1;
         bool b1 : 1;
         bool b2 : 1;
         uint8_t i0 :3;
}

BitField bf;
std::mutex mtx1;
std::mutex mtx2;
//thread 1 void f1() { mtx1.lock(); bf.b1 = somevalue; mtx1.unlock(); } //thread 2 void f2() { mtx2.lock(); bf.i0 = somevalue; mtx2.unlock(); }

 

 

         No!!!虽然代码可以通过编译运行,但却并不是线程安全的,因为b1,i0都属于同一快内存”单元”,因此根本无法生成只更新b1,但是不写入i0的代码!!实际上c++11明确指出了这种情况会导致race,临近的bit总是被当做一个”对象” :)

 

posted on 2014-04-11 18:44  clayman  阅读(1967)  评论(6编辑  收藏  举报