读书摘要-Efficient C++ performance programming techniques

Chp 1 The Tracing war story
 
    当你的代码规模超过几千行后,tracing就变得很必要了。

    当在一个很小却被频繁调用的函数中加入tracing 机制时,如果不注意的话,tracing可能会成数量级的降低系统的性能

    在C++程序中,不必要的对象构造和销毁,会带来非常大的开销。

    那些适合inline调用的函数,往往不适合当作tracing的目标。



Chp 3 Virtual Function

    程序语言的发展倾向于通过将更多的工作转移到编译器、解释器、汇编器和连接器上,来使得编程的任务更为轻松。虚函数机制就是将类型解析的任务转移到了编译器身上。

    虚函数机制带来的时间开销有以下三点:

    1.构造函数中需要正确设置vptr
    2.函数调用需要通过指针间接进行
    3.虚函数的特性与inline难以协同工作

    这三点中,第三点对于性能的影响因素是最大的。

    当某个虚函数成为性能瓶颈时,解决方案有两种──手动硬编码执行类型判断,或者通过模板参数机制。

    模板机制往往有较好的性能,因为它将类型解析从运行期前推至编译期。



Chp 4 Return Value Optimization


    RVO的 基本思路是消除函数内部的局部变量,所有的操作都变为在要返回的临时变量上直接进行。

    编译器优化的基本前提是保持正确性;对于RVO,这并不总是件容易的事情;当函数中有多个return 语句,且返回的是不同的局部变量时,RVO显然无法维持语意的正确性──因此,在这种情况下,RVO是不被启用的。

    简单的说,RVO的前提是函数的所有return 语句都返回同一个局部变量。

    虽然标准规定named or unamed return value 都可以被优化掉,但是从实现的角度来看,unamed return value 被优化掉的几率更大。
   
    此外,启用RVO还需要class 提供一个copy constructor,否则RVO会被静悄悄的关闭。


Chp 5 Temporaries

    函数的RVO按照激进程度,大致可分为两个程度

    1.    消除函数内部的局部变量,直接在要返回的临时对象上执行各种操作;

    2.    在例如" T t=foo ()"的表达式中,将函数foo()要返回的临时对象也消除掉,直接在最终对象上执行各种操作(这种优化的前提是"="的语意是初始化,而不是赋值)



Chp 8  Inline Basics

    Inline的代价之一是导致了编译时间的增长。


深刻理解函数调用的开销

    IP( Instruction Pointer):通常也被称为PC(program counter,程序记数器),包含下一条指令的地址

    LR(Link Register):存放当前函数执行完返回后要执行的下一条指令的地址

    SP(Stack Pointer):记录栈的使用情况

    AP(Arugment Pointer):指明参数在栈中的位置

    FP(Frame Pointer) :用于分隔栈上不同的 function frame

    一般来说,函数调用和返回的开销为25~100 cycle

    异常的存在会降低启用RVO的可能性。

    Inline的一个重要优点在于减少了分支条件的影响;分支条件的存在,对于大量采用指令流水线预取技术的现代处理器,会造成显著的性能下降。

    一方面,Inline消除了函数调用和返回对应的机器码,程序的尺寸倾向于变小;另一方面,由于函数体的代码重复出现,又倾向于增大程序尺寸。

   
Chp 9 Inlining—Performance Considerations

    Inline的优点在于消除函数调用的开销;然而这只是故事的一面。inline的意义还在于允许编译器掌握更多的信息,从而更好的进行"cross-all optimization",在信息更丰富的上下文环境下更好的进行优化。

    cross-call optimizaition 一般表现在将原本在运行期执行的运算提前在编译期完成。

    与消除函数调用开销相比,cross-call optimization 所产生的性能提高要更为明显;然而另一方面,cross-call 严重依赖与编译器的性能,可靠性不如前者。

    用类比的方法来说,对于Inline所能获得的性能提升,cross-call optimization是兔子,而函数调用开销的消除则是那只乌龟。
   
    字面常量在最常见的优化中起着关键作用。

    要警惕inline可能导致的代码膨胀,进而降低程序的局部性特性,增大cache miss的几率,从而完全抵消甚至超出了inline所带来的优点。

    此外,inline还会增加编译依赖,任何对实现的修改都会导致依赖于该函数的模块的重新编译。



Chp 12 引用计数

    毫无疑问,在减少内存消耗方面,引用计数是很好的机制;但涉及到速度问题,则没有确定的答案。

    如果对象并不占用很多资源,且系统中共享对象的情况并不频繁,那么引用计数会导致性能的下降──因为引用计数意味着原本在栈上分配的空间改为在堆上分配,这显然相比之下是个更昂贵的操作,即引用计数对象在第一次创建时开销是很大的。

    反过来,如果对象本身占用的资源较大,且系统中共享对象的情形很频繁,那么采取引用计数则是最佳选择。


Chp 14 Design Optimizations

    从根本上说,程序的灵活性和高效性是相互对立的两个目标。


Chp 15 Scalability

SMP的基本特点

    1.    Multiple Processer
    2.    所有Processor在系统中处于同等地位
    3.    其它一切仍然是single的,如单一内存,单一内核代码,单一运行队列

    SMP的瓶颈在于Bus,常见的解决机制在于为每个Processor引入独立的Cache,然而这又导致了Cache Coherence 问题,通常通过硬件级的更新协议来解决,对于软件而言是透明的。

Thundering Herd(轰鸣的兽群)

    当多个Thread都在同一资源(例如lock)上suspend时,需要考虑当该资源被当前拥有者释放时,应该如何处理这些suspend的Thread?

    1.    只唤醒其中一个Thread,例如优先级最高,或者suspend时间最久的那个

    2.    唤醒在该资源上suspend的所有Thread,这将导致Thundering Hurd现象──虽然所有Thread都被唤醒,但只有一个Thread最终能获得资源,其它Thread在执行完开销高昂的进程切换获得CPU控制权后,能做得的只是再次suspend,这严重降低了性能。

    当Thundering Hurd发生的概率较大时(资源较少而Thread很多),更多的Thread只会意味着更差的性能。

    Thundering Hurd的本质在于大量Thread在竞争少量资源。


Chp 16 System Architecture Dependency

Memory Hierarchies

    Access time is a latency issue: How long does it take to start getting data?  

    Width is bandwidth issues: How much data can I get once it starts arriving?

    This is akin to the notion of a fire hose: How long does it take to open the valve, once it is open how much water does the hose deliver per second, and how much water is in the trunk?
   
    Latency的提高速度不如Bandwidth的提高那么快,对于内存和硬盘,都是如此。

    通常,L1 Cache和寄存器相比,在Latency上差别不大,但在Bandwidth上处于劣势。


   

      
   
   


 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值