惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Project Zero
Project Zero
月光博客
月光博客
Y
Y Combinator Blog
T
The Blog of Author Tim Ferriss
O
OpenAI News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Know Your Adversary
Know Your Adversary
Last Week in AI
Last Week in AI
S
Securelist
Engineering at Meta
Engineering at Meta
博客园 - 司徒正美
P
Privacy & Cybersecurity Law Blog
T
Tailwind CSS Blog
F
Fortinet All Blogs
博客园 - 三生石上(FineUI控件)
Scott Helme
Scott Helme
MyScale Blog
MyScale Blog
P
Proofpoint News Feed
云风的 BLOG
云风的 BLOG
C
Cisco Blogs
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
小众软件
小众软件
U
Unit 42
Microsoft Azure Blog
Microsoft Azure Blog
Hacker News: Ask HN
Hacker News: Ask HN
Hugging Face - Blog
Hugging Face - Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
SecWiki News
SecWiki News
宝玉的分享
宝玉的分享
P
Proofpoint News Feed
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
H
Hackread – Cybersecurity News, Data Breaches, AI and More
L
Lohrmann on Cybersecurity
IT之家
IT之家
Security Archives - TechRepublic
Security Archives - TechRepublic
I
InfoQ
S
Security @ Cisco Blogs
Webroot Blog
Webroot Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
F
Full Disclosure
D
Darknet – Hacking Tools, Hacker News & Cyber Security
The GitHub Blog
The GitHub Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
Jina AI
Jina AI
Cyberwarzone
Cyberwarzone
人人都是产品经理
人人都是产品经理
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
B
Blog RSS Feed
Apple Machine Learning Research
Apple Machine Learning Research

博客园 - SoRoMan

Scene Management - Scene Graph 思考:矩阵的级联顺序与坐标系的关系 MFC的一些宏的整理 (DECLARE_DYNCREATE/IMPLEMENT_DYNCREATE) 关于STL Improve Performance of C++ Codes (1) -- 使用初始化列表还是赋值语句? 关于万向节死锁(Gimbal Lock)(续) 思考:矩阵及变换,以及矩阵在DirectX和OpenGL中的运用问题:左乘/右乘,行优先/列优先,... 读书笔记:Writing Solid Code (4) 读书笔记:Writing Solid Code (3) 读书笔记:Writing Solid Code (2) 读书笔记:Writing Solid Code A 3D Real Time Fluid Solver Demo (三维实时流体解算器) 笔记:Delaunay三角剖分(Delaunay Triangulation)相关知识 学习经验:逐步建立学习资源列表-计算机图形学 SL专题6:谈谈Second Life中的游戏制作(Game Dev in SL) SL专题4:Second Life畅销商品 SL专题3:Second Life市场分析 SL专题2:加入并熟悉Second Life世界 SL专题1:Second Life(第二生命)--迟早火爆世界的游戏与Web的集大成者
Improve Performance of C++ Codes (2) -- 如何消除临时对象?
SoRoMan · 2008-04-05 · via 博客园 - SoRoMan

条款1:Improve Performance of C++ Codes (1) -- 使用初始化列表还是赋值语句?

继续记录:
条款2:
--------------------------------
Q:何时会出现临时对象以及如何消除临时对象?
A:为了使函数成功调用而进行隐式类型转换以及函数返回对象时可能会出现临时对象。对于前者,可以采取方法避免隐式类型转换,对于后者,尽量配合编译器的返回值优化(RVO)来消除临时对象。
--------------------------------

在<<More Effictive C++>>中,有关于临时对象的这么一段:在C++中真正的临时对象是看不见的,它们不出现在你的源代码中。建立一个没有命名(unamed)的非堆(non-heap)对象会产生临时对象。这种未命名的对象通常在两种条件下产生:

a.为了使函数成功调用而进行隐式类型转换。
b.函数返回对象时。

a.首先考虑为使函数成功调用而建立临时对象这种情况。

测试:
--------------------------------
下面看个c++ test program:

//TestClass.h
class A
{
public:
    A();
    int i;
};

class B
{
public:
    B();
    B(const A& a);
    A a;
    int j;
};

//TestClass.cpp
#include "TestClass.h"
#include <iostream>

using namespace std;

A::A()
{
    cout<<"A:A()"<<endl;
}

B::B()
{
    cout<<"B:B()"<<endl;
}

B::B(const A& a)
{
    cout<<"B::B(const A& a)"<<endl;
}

// main.cpp

#include "LGVector.h"

#include "LGVector.h"

void test(A a)
{
}

void test1(B b)
{
}

void test2(const B& b)
{
}

void test3(B& b)
{
}

int main(int argc, char* argv[])
{    
    A a;

    test(a); // 将实参a压栈,并不会调用构造函数产生临时对象
    test1(a);
    test2(a);
    //test3(a); // Compile error!!! 原因:C++禁止为非常量引用(reference-to-non-const)产生临时对象

    return 0;
}

下面是main()的Assembly codes:
(x86, VC++ 6,Release, Optimization: Maximize Speed)

_a$ = -12
$T300 = -8
_main    PROC NEAR                    ; COMDAT

; 23   : {    

    sub    esp, 12                    ; 0000000cH

; 24   :     A a;

    lea    ecx, DWORD PTR _a$[esp+12]
    call    ??0A@@QAE@XZ                ; A::A

; 25   :
; 26   :     test(a);

    mov    eax, DWORD PTR _a$[esp+12]
    push    eax
    call    ?test@@YAXVA@@@Z            ; test

; 27   :     test1(a); // 将实参a压栈,并不会调用构造函数产生临时对象

    push    ecx
    lea    edx, DWORD PTR _a$[esp+20]
    mov    ecx, esp
    push    edx // 将实参a压栈
    call    ??0B@@QAE@ABVA@@@Z            ; B::B // 拷贝构造被调用来产生临时对象
    call    ?test1@@YAXVB@@@Z            ; test1
    add    esp, 8

; 28   :     test2(a);

    lea    eax, DWORD PTR _a$[esp+12]
    lea    ecx, DWORD PTR $T300[esp+12]
    push    eax
    call    ??0B@@QAE@ABVA@@@Z            ; B::B // 拷贝构造被调用来产生临时对象
    lea    ecx, DWORD PTR $T300[esp+12]
    push    ecx
    call    ?test2@@YAXABVB@@@Z            ; test2

; 29   :     //test3(a); // Compile error!!! 原因:C++禁止为非常量引用(reference-to-non-const)产生临时对象
; 30   :
; 31   :     return 0;

    xor    eax, eax

; 32   : }

    add    esp, 16                    ; 00000010H
    ret    0
_main    ENDP

--------------------------------
--------------------------------
小结:
VC++ 6:
当传送给函数的对象类型与参数类型不匹配时,会产生为使函数成功调用而建立临时对象这种情况。条件是仅当通过传值(by value)方式传递对象或传递常量引用(reference-to-const)参数时,才会发生这些类型转换,从而产生临时对象。
注:如果类型匹配时,即使是通过传值传递对象参数,也不会产生临时对象,只是将实参拷贝一份压栈。如:test(a);。

VC++ 2005:
待测。
--------------------------------

b.建立临时对象的第二种环境是函数返回对象时。例如operator+必须返回一个对象,以表示它的两个操作数的和
.以某种方法返回对象,能让编译器消除临时对象的开销,这样编写函数通常是很普遍的。这种技巧是返回constructor argument而不是直接返回对象,加上编译器返回值优化(return value optimization)(RVO)功能,临时对象就不会被创建。

测试:
--------------------------------
下面看个c++ test program:

//LGVector.h
class LGVector
{
public:
    // constructors
    LGVector(){}; // make the default constructor be inlined.
    //LGVector(const float* pVec);
    LGVector(float x, float y, float z);

    LGVector(const LGVector& rVec);

    // destructor
    ~LGVector(){}; // this's necessary as VC++6 will turn on RVO if the destructor exists explcitly.

    // access methods

    // operators
    operator= (const LGVector& rVec);
    LGVector operator+ (const LGVector& rVec);    

private:
    float x,y,z;
};

//LGVector.cpp
#include "LGVector.h"

LGVector::LGVector(float x, float y, float z) : x(x), y(y), z(z)
{
}

LGVector::LGVector(const LGVector& rVec)
{
    x = rVec.x;
    y = rVec.y;
    z = rVec.z;
}

LGVector::operator= (const LGVector& rVec)
{
    x = rVec.x;
    y = rVec.y;
    z = rVec.z;
}

/* case1:good style */
LGVector LGVector::operator+ (const LGVector& rVec)
{
    return LGVector(x+rVec.x, y+rVec.y, z+rVec.z);
}

/* case2:bad style
LGVector LGVector::operator+ (const LGVector& rVec)
{
    LGVector v(x+rVec.x, y+rVec.y, z+rVec.z);
    return v;
}
*/

/* case3:bad style
LGVector LGVector::operator+ (const LGVector& rVec)
{
    LGVector v = LGVector(x+rVec.x, y+rVec.y, z+rVec.z);
    return v;
}
*/

/* case4:bad style
LGVector LGVector::operator+ (const LGVector& rVec)
{
    LGVector v;
    v = LGVector(x+rVec.x, y+rVec.y, z+rVec.z);
    return v;
}
*/

//main.cpp
#include "LGVector.h"

int main(int argc, char* argv[])
{    
    LGVector v1(1,2,3);
    LGVector v2(10,20,30);

    LGVector v = v1 + v2;

    return 0;
}

下面是LGVector LGVector::operator+ (const LGVector& rVec)的x86 Assembly codes:
(x86, VC++ 6,Release, Optimization: Maximize Speed)
--------------------------------

*********************************************************************************************************
case1:RVO开启,函数中,只有一个构造函数会调用,没有额外的拷贝构造函数被调用。如下:
*********************************************************************************************************
_rVec$ = 12
___$ReturnUdt$ = 8
$T287 = -4
??HLGVector@@QAE?AV0@ABV0@@Z PROC NEAR            ; LGVector::operator+, COMDAT

; 26   : {

    push    ecx

; 27   :     return LGVector(x+rVec.x, y+rVec.y, z+rVec.z);

    mov    eax, DWORD PTR _rVec$[esp]
    push    esi
    push    ecx
    mov    esi, DWORD PTR ___$ReturnUdt$[esp+8]
    fld    DWORD PTR [eax+8]
    fadd    DWORD PTR [ecx+8]
    mov    DWORD PTR $T287[esp+12], 0
    fstp    DWORD PTR [esp]
    fld    DWORD PTR [eax+4]
    fadd    DWORD PTR [ecx+4]
    push    ecx
    fstp    DWORD PTR [esp]
    fld    DWORD PTR [eax]
    fadd    DWORD PTR [ecx]
    push    ecx
    mov    ecx, esi
    fstp    DWORD PTR [esp]
    call    ??0LGVector@@QAE@MMM@Z            ; LGVector::LGVector
    mov    eax, esi
    pop    esi

; 28   : }

    pop    ecx
    ret    8
??HLGVector@@QAE?AV0@ABV0@@Z ENDP            ; LGVector::operator+

*********************************************************************************************************
case2:函数中,一个构造函数以及一个拷贝构造函数会调用。如下:
*********************************************************************************************************
_rVec$ = 12
___$ReturnUdt$ = 8
_v$ = -12
$T288 = -16
??HLGVector@@QAE?AV0@ABV0@@Z PROC NEAR            ; LGVector::operator+, COMDAT

; 33   : {

    sub    esp, 16                    ; 00000010H

; 34   :     LGVector v(x+rVec.x, y+rVec.y, z+rVec.z);

    mov    eax, DWORD PTR _rVec$[esp+12]
    push    esi
    push    ecx
    mov    DWORD PTR $T288[esp+24], 0
    fld    DWORD PTR [eax+8]
    fadd    DWORD PTR [ecx+8]
    fstp    DWORD PTR [esp]
    fld    DWORD PTR [eax+4]
    fadd    DWORD PTR [ecx+4]
    push    ecx
    fstp    DWORD PTR [esp]
    fld    DWORD PTR [eax]
    fadd    DWORD PTR [ecx]
    push    ecx
    lea    ecx, DWORD PTR _v$[esp+32]
    fstp    DWORD PTR [esp]
    call    ??0LGVector@@QAE@MMM@Z            ; LGVector::LGVector

; 35   :     return v;

    mov    esi, DWORD PTR ___$ReturnUdt$[esp+16]
    lea    eax, DWORD PTR _v$[esp+20]
    push    eax
    mov    ecx, esi
    call    ??0LGVector@@QAE@ABV0@@Z        ; LGVector::LGVector
    mov    eax, esi
    pop    esi

; 36   : }

    add    esp, 16                    ; 00000010H
    ret    8
??HLGVector@@QAE?AV0@ABV0@@Z ENDP            ; LGVector::operator+

*********************************************************************************************************
case3:同case2。实际上,LGVector v = LGVector(x+rVec.x, y+rVec.y, z+rVec.z);只会引发构造函数的调用,等同于LGVector v(x+rVec.x, y+rVec.y, z+rVec.z);,不同的写法而已。多扯一点:会引发拷贝构造函数的调用的是形如LGVector v = v0; 这样的写法,而形如v = v0;的写法会引发operator=调用。如下:
*********************************************************************************************************
_rVec$ = 12
___$ReturnUdt$ = 8
_v$ = -12
$T289 = -16
??HLGVector@@QAE?AV0@ABV0@@Z PROC NEAR            ; LGVector::operator+, COMDAT

; 41   : {

    sub    esp, 16                    ; 00000010H

; 42   :     LGVector v = LGVector(x+rVec.x, y+rVec.y, z+rVec.z);

    mov    eax, DWORD PTR _rVec$[esp+12]
    push    esi
    push    ecx
    mov    DWORD PTR $T289[esp+24], 0
    fld    DWORD PTR [eax+8]
    fadd    DWORD PTR [ecx+8]
    fstp    DWORD PTR [esp]
    fld    DWORD PTR [eax+4]
    fadd    DWORD PTR [ecx+4]
    push    ecx
    fstp    DWORD PTR [esp]
    fld    DWORD PTR [eax]
    fadd    DWORD PTR [ecx]
    push    ecx
    lea    ecx, DWORD PTR _v$[esp+32]
    fstp    DWORD PTR [esp]
    call    ??0LGVector@@QAE@MMM@Z            ; LGVector::LGVector

; 43   :     return v;

    mov    esi, DWORD PTR ___$ReturnUdt$[esp+16]
    lea    eax, DWORD PTR _v$[esp+20]
    push    eax
    mov    ecx, esi
    call    ??0LGVector@@QAE@ABV0@@Z        ; LGVector::LGVector
    mov    eax, esi
    pop    esi

; 44   : }

    add    esp, 16                    ; 00000010H
    ret    8
??HLGVector@@QAE?AV0@ABV0@@Z ENDP            ; LGVector::operator+

*********************************************************************************************************
case4:函数中,一个构造函数,一个赋值函数以及一个拷贝构造函数会调用(原本应该是两个构造函数调用,但是因为LGVector v;引发的构造函数是inline的,所以没有出现调用),如下:
*********************************************************************************************************
_rVec$ = 12
___$ReturnUdt$ = 8
_v$ = -24
$T288 = -12
$T292 = -28
??HLGVector@@QAE?AV0@ABV0@@Z PROC NEAR            ; LGVector::operator+, COMDAT

; 49   : {

    sub    esp, 28                    ; 0000001cH

; 50   :     LGVector v;
; 51   :     v = LGVector(x+rVec.x, y+rVec.y, z+rVec.z);

    mov    eax, DWORD PTR _rVec$[esp+24]
    push    esi
    push    ecx
    mov    DWORD PTR $T292[esp+36], 0
    fld    DWORD PTR [eax+8]
    fadd    DWORD PTR [ecx+8]
    fstp    DWORD PTR [esp]
    fld    DWORD PTR [eax+4]
    fadd    DWORD PTR [ecx+4]
    push    ecx
    fstp    DWORD PTR [esp]
    fld    DWORD PTR [eax]
    fadd    DWORD PTR [ecx]
    push    ecx
    lea    ecx, DWORD PTR $T288[esp+44]
    fstp    DWORD PTR [esp]
    call    ??0LGVector@@QAE@MMM@Z            ; LGVector::LGVector
    push    eax
    lea    ecx, DWORD PTR _v$[esp+36]
    call    ??4LGVector@@QAEHABV0@@Z        ; LGVector::operator=

; 52   :     return v;

    mov    esi, DWORD PTR ___$ReturnUdt$[esp+28]
    lea    eax, DWORD PTR _v$[esp+32]
    push    eax
    mov    ecx, esi
    call    ??0LGVector@@QAE@ABV0@@Z        ; LGVector::LGVector
    mov    eax, esi
    pop    esi

; 53   : }

    add    esp, 28                    ; 0000001cH
    ret    8
??HLGVector@@QAE?AV0@ABV0@@Z ENDP            ; LGVector::operator+
--------------------------------

--------------------------------
小结:
VC++ 6:
注意编写我们的代码,尽量消除临时对象带来的开销。case2,3,4中的额外拷贝构造函数的调用开销是因为其产生的对象是Named的,不是临时对象,所以VC++6并不能优化掉。
注:定义析构函数得目的是为了让VC6开启RVO,从而消除临时对象的开销。

VC++2005:
RVO包含两种,一种是NRVO,另一种是URVO。VC++2005貌似就支持NRVO,也就是说在case2,3,4中也能做到和case1一样的优化效果。
但是需要注意的是URVO一般是不会带来问题的,NRVO可能会导致不同的行为。
--------------------------------