Improving C++ Program Performance
(Dr. Dobb's Journal October 1999/10)
作者:Stanley Lippman
译者:陈崴
侯捷注:本文系北京《程序员》杂志 2001/12 的文章。
承译者陈崴先生与《程序员》杂志负责人蒋涛先生答允,
转载於此,以飨台湾读者,非常感谢。

未得陈崴先生与蒋涛先生二人之同意,任何人请勿将此文再做转载。

--------------------------------------------------------------------------------

译注:以下是本文所采用的几个术语:

instance:实体。

class, object, data member, member function, constructor, destructor, template 等术语,皆保留不译。

--------------------------------------------------------------------------------

对 C++ 程序员而言,认知什麽情况下不要定义 copy constructors, copy-assignment operators,  destructors,其重要性就像认知这些特殊member functions 什麽时候是必要的,一样重要。举个例子,考虑表格一的测试结果:

程式版本 未最佳化 (Nonoptimized) 最佳化 (Optimized)
基本型式 28.59 15.22
修改 #1 22.25 (22%) 12.27 (19%)
修改 #2 15.88 (44%) 9.50 (37%)
整合(#1 和 #2) 12.97 (54%) 7.51 (51%)

表格一:执行时间的测试结果

「基本型式」栏所呈现的,是一个拥有整组特殊化 member functions(copy constructor, copy-assignment operator, destructor)的程式的执行时间。在此基本型式中,没有任何一个 class member functions 或任何它所支援的 friend functions 被宣告为 inline。是的,不使用 inline、三个特殊 member functions 全员到齐,并且相当程度地符合普遍流行的 C++ 编程规则(例如 Taligent's Guide to Designing Programs, Addison-Wesley, 1994, ISBN 0-201-40888-0)。

为了继续讨论,我们假设这个程式的效率太差,而你的工作就是要使它快些。你该怎麽做?

常见的加速策略有三种:

1. 将目前所用的演算法以更有效率的演算法取代(但这样的演算法往往更复杂,例如以快速排序法 quicksort 取代气泡排序法 bubble sort)。

2. 将目前所用的资料结构以更有效率的资料结构取代(但这样的资料结构往往更复杂,例如以红黑树 red-black tree 取代二元树 binary tree)。

3. 将目前所用的程式码以更有效率的程式码取代(这种情况下所增加的效率往往伴之以控制流程和程式码可读性的减少)。

许多情况下,诉诸上述三个策略中的任何一个,其实都是非必要的。通常,检阅并挑出不适当的 C++ 编程手法,不但简单而且效果不错。所谓不适当的编程习惯,其中一个就是非必要地定义 copy constructor, copy-assignment operator 和 destructor。列表一表现的是一个基本型式的Vector class 和其应用程式(译注:此处Vector 并不是指 C++ 标准程式库所提供的容器,而是指数学中的三度空间向量),第一次修改只是简单地将每一个 friend function和 member function改为 inline。第二次修改移除了 copy constructor, copy-assignment operator 和 destructor 的定义。第三次修改则是第一次修改和第二次修改的结合。本文所列的这些程式都通过 SGI 7.2 版编译器,时间的量测以 timex 指令完成。

列表一
class Vector {   
    friend Vector
           operator+( const Vector&, const Vector& );
    friend Vector
           operator-( const Vector&, const Vector& );
public:
    Vector( double x=0.0,
             double y=0.0, double z=0.0 );
    Vector( const Vector& );
    Vector& operator=( const Vector& );
    ~Vector();
    bool operator==( const Vector& );
    Vector operator/( double );

double mag();
    double dot( const Vector& );
    Vector cross( const Vector& );
    void normalize();

double x() const;
    double y() const;
    double z() const;

void x( double newx );
    void y( double newy );
    void z( double newz );

private:
    double _x, _y, _z;
};
Vector::Vector( double x, double y, double z )
    { _x = x; _y = y; _z = z; }
Vector::Vector( const Vector &rhs )
    { _x = rhs._x; _y = rhs._y; _z = rhs._z; }
Vector::~Vector()
    {_x = 0.0; _y = 0.0; _z = 0.0; }
Vector& Vector::operator=( const Vector &rhs )
{
    _x = rhs._x; _y = rhs._y; _z = rhs._z;
    return *this;
}
Vector
operator+( const Vector &lhs, const Vector &rhs )
{
    Vector result;

result._x = lhs._x + rhs._x;
    result._y = lhs._y + rhs._y;
    result._z = lhs._z + rhs._z;

return result;
}
Vector
operator-( const Vector &lhs, const Vector &rhs )
{
    Vector result;

result._x = lhs._x - rhs._x;
    result._y = lhs._y - rhs._y;
    result._z = lhs._z - rhs._z;

return result;
}
#include <math.h>
double Vector::mag()
    { return sqrt( _x*_x + _y*_y +_z*_z ); }
Vector Vector::cross( const Vector &rhs )
{
    Vector result;

result._x = _y * rhs._z - rhs._y * _z;
    result._y = _z * rhs._x - rhs._z * _x;
    result._z = _x * rhs._y - rhs._z * _y;

return result;
}
void Vector::normalize()
{
    double d = mag();
    _x /= d; _y /= d; _z /= d;
}
double Vector::dot( const Vector &rhs )
    { return _x*rhs._x + _y*rhs._y + _z*rhs._z; }
bool Vector::operator==( const Vector &rhs )
{
    return _x == rhs._x &&
            _y == rhs._y && _z == rhs._z;
}
Vector
Vector::
operator /( double val )
{
    Vector result;

if ( val != 0 ) {
        result._x = _x / val;
        result._y = _y / val;
        result._z = _z / val;
    }
    return  result;
}
double Vector::x() const { return _x; }
double Vector::y() const { return _y; }
double Vector::z() const { return _z; }

void Vector::x( double newx ) {  _x = newx; }
void Vector::y( double newy ) {  _y = newy; }
void Vector::z( double newz ) {  _z = newz; }

#include <vector>
int main()
{
    Vector a( 0.231, 2.4745, 0.023 ),
           b( 1.475, 4.8916, -1.23 );
    vector< Vector > vv;
    for ( int ix = 0; ix < 2000000; ++ix )
    {
        Vector c( a.mag(), b.dot(a), ix );

vv.push_back( a.cross( c ));   
        vv.push_back( a + c );
        vv.push_back( a - c );
        vv.push_back( a / c.mag() );
        vv.push_back( a.cross( c ));

if ( c == a )
             vv.push_back( b );
        else vv.push_back( a );

c.normalize();
        vv.push_back( c );
    }
}
第一次修改:加上 Inline
第一个修改动作就是将 Vector class 的 member functions 和 friend functions 加以「inline 化」。这种改变真是太容易了:只要为每一个你想修改的目标加上关键字 inline 即可。一下子你就增加了大约 20%的效率。一般而言,一个应用程式从完全没有「inlining 化」转变为适当「inlining化」,估计可获得 20% ~ 25% 的效率提升。

如果 inlining 带来程式效率的提升,而你又不需要明显的努力,为什麽许多编程标准手册都不赞成「inlining 化」?是的,它的主要缺点就是,你必须重新编译「内含 inline 函式」的每一个档案。对於一个应用於大型专案的 class library 而言,重新编译的成本相当昂贵。

当你在 C++ 中针对一个被广泛使用的 class,增加或移除其 data member 时,或是当你修改某个既存的 data member 的型别时,同样的问题也会浮现。为了完全解决重新编译的问题,你需要转移到另一个物件模型(object model)上,例如 COM 所支援的那个物件模式(可叁考 Essential COM, by Don Box, Addison-Wesley Longman, 1998, ISBN 0-201-63446-5;以及 Inside the C++ Object Model, by Stanley Lippman, Addison-Wesley, 1996, ISBN 0-201-83454-5)。

专案编程标准手册中禁止大家使用 inlining,其实是一种误导。但是从另一个角度看,也并不是每一个 C++ 函式都应该成为 inline。inlining 的另一个潜在缺点是程式的膨胀问题。一般而言只有不大的函式(例如Vector 的 member functions)才适合被 inline 化。实际运用时究竟该如何取舍,有待你的明智判断。

第二次修改:移除非必要的 Member Functions
第二个修改是移除Vector class 之中非必要的 copy constructor, copy-assignment operator 和 destructor。(为了这项修改,我让所有的 member functions 成为 noninline。在第三次修改中,我会将两者整合起来)。这项改变甚至比加上 inline 更为直接易懂;你只要删除三个函式的宣告和定义就好。其结果是几近 40% 的效率提升。

这样的结果往往带给程序员极大的惊讶,但如果你了解编译器底部的行为,你就不会惊讶。

何时需要明显的 Copy Operators?
预设情况下,一个 class object 是以 "memberwise copy"(成员逐一拷贝)的方式被初始化或被赋值。观念上,以 Vector class 为例,首先是 x 座标,然後是 y座标,然後是 z-座标被轮番拷贝。就好像你所写的函式码,请看列表一。

然而,实际上,编译器并不是以上述方式来拷贝 objects:

它并不会产生一个明显的(explicit)copy constructor 和 copy-assignment operator。
它不会轮番拷贝每一个代表座标值的 data member,而是直接对整个 object 的所有资料做一次 "bitwise copy"(位元逐一拷贝),就像 C 的行为那样。编译器并不会为你产生预设的 memberwise functions。我们所写的实作码(列表一)和编译器暗中的行为,两者差别非常大,明显反应於效率表现上。
这对所有情况都是真的吗?不,一般而言,编译器会在四种情况下为你合成出一个 copy constructor 和一个 copy-assignment operator:

当 class 内含一个 member class object,而後者有一个相应的 copy constructor 和/或 copy-assignment operator。
当 class 衍生自一个或多个 base classes,而後者有一个相应的 copy constructor 和/或 copy-assignment operator。
当 class 宣告或继承了一个虚拟函式(virtual function)。
当 class 直接或间接衍生自一个 virtual base class。
C++ Standard 将这样的 classes 称为 nontrivial。换句话说,前述的Vector class 是 trivial,也就是说它不符合上述四种情况。(更详细的讨论,请看我的书籍 Inside the C++ Object Model。)

那麽,是不是说,你绝不应该定义一个 copy constructor 和 copy-assignment operator 呢?当然不是。某些情况下为它们提供一个显式定义是必要的。最常见的必要情况就是当你的 class 内含一个或多个 pointer members,而後者在 class object 寿命期间,不是被初始化,就是被设值为某一块 heap记忆体位址,那块记忆体随後在 destructor 内被删除。C++ 编程的基本观点之一就是对此情势有所认知。如果你需要更从容而且带实例的讨论,请看 C++ Primer, 第三版, by Stanley Lippman and Josée Lajoie, Addison-Wesley Longman, 1998, ISBN 0-201-82470-1.)(译注:也可以阅读 Effective C++ 2/e,条款11:「如果 classes 内部动态配置记忆体,请为此 class 宣告一个 copy constructor 和一个 copy-assignment operator)

面对一个「所有 data members 都以 by value方式存在」的 class(例如Vector class),编译器预设的初始化动作和拷贝动作会比我们自己撰写的动作更有效率。这对於程式和程式员而言,都是很少量的工作。对程式员而言,你的任务就是辨识什麽情况下需要自己实作一个显式 copy constructor 和 copy-assignment operator。

何时需要一个显式(explicit)Destructor?
同样道理,面对一个「所有 data members 都以 by value方式存在」的 class(例如Vector class 的三个座标值),destructor 是没有必要的。是的,并非每个 class 都需要 destructor,纵使你已经为此 class 提供了一个或多个 constructors。Destructors 的任务主要是撤回被  constructor 索取的资源,或是 class object 寿命期间索取的资源。例如释放一个多工互斥器锁定(mutual exclusion lock),或是删除「operator new; 配置而得的记忆体」,请叁考 C++ Primer。

为什麽它不是一个被推荐的编程手法?
如果「不提供显式 copy constructor 和 copy-assignment operator 和 destructor」就可以改善你的程式效率,而又不需要你多做什麽努力,为什麽许多编程标准手册都建议我们尽量提供一个显式版本呢?这个议题,我相信,牵涉到一个台面下的可能性,那就是,做为一个 C++ 程式员,你是否获得足够的信赖去发挥你的判断?(缩小范围地说, inlining 议题也是同样的情况。)要知道,万一它们(那些特殊的member functions)必须存在,而你却没有提供其实体,会导致严重的程式错误。

该如何选择?办法之一是允许你自由选择,前提是「做出正确选择之前的所有必要资讯」都必须齐备。办法之二是很单纯地墨守成规 ─ 是的,通常这些(编程标准手册上的)规则在 80% - 90% 的情况下都适用。两种解法之间的抉择关系到你究竟如何管理一个软件专案。

在Vector class 中,提供非必要的 copy constructor, copy-assignment operator, destructor 的显式实体,带来的结果并非不正确,而是效率不彰。认知此一事实,有助於你调整你的实作码而不必整个重新翻修一遍。

致谢
Josée Lajoie 检阅了本文初稿,并提供许多深刻的看法和建议。

改善C++程式的效率相关推荐

  1. 储能系统解决方案改善能源的应用效率

    随着世界局势动荡以及电力需求的增加,能源已经成为世界各国都需要面对的重要议题,除了积极提高电力的产能之外,如何善用储能系统(ESS)来保存与调节电力需求,便成为目前科技的重要发展方向之一.本文将为您介 ...

  2. 改善互联网广告营销效率的四大方式

    随着互联网行业的发展,让我们有了更加便捷的信息获取渠道.对于营销人员来说,互联网是不能缺少的存在,快节奏的生活让我们习惯于这一种信息获取的方式.为此,互联网广告营销也一并得到了发展,带来了非常好的营销 ...

  3. 如何改善C++代码项目的构建效率,降低构建时间?

    对于C++程序员而言,编译.构建是每天都要做的操作. 所以,这是一个好话题. 当然,对于小项目,或者构建时间在5分钟以内的项目,这个话题不值一提. 假如项目构建一次需要耗时1小时以上时,那么这个话题事 ...

  4. 提高IIS网站服务器的效率的八种方法 (转载)

    作者:未知  请作者速与本人联系 以下是提高IIS 5.0网站服务器的执行效率的八种方法: 1. 启用HTTP的持续作用可以改善15~20%的执行效率. 2. 不启用记录可以改善5~8%的执行效率. ...

  5. 做一个项目,平时都用到哪些工具提高效率(上)

    做一个项目,平时都用到哪些工具提高效率(上)  转载 做.NET 相关项目,Visual Studio 2008,SQL Server 2000/2005是标准的配置,但是,除此之外,还可以应用哪些工 ...

  6. 效率低下?是管理者无能?还是“太宽容”?

    效率低下?是管理者无能?还是"太宽容"? 效率低下,是当前许多企业都会面临的难题之一. 一些企业甚至还会出现这样一种"神奇"的现象:老板和员工都在努力工作,公司 ...

  7. 做一个项目,平时都用到哪些工具提高效率(上)[转]

    做.NET 相关项目,Visual Studio 2008,SQL Server 2000/2005是标准的配置,但是,除此之外,还可以应用哪些工具来提高工作效率,让项目做的更轻松. 1  需要一个代 ...

  8. 做一个项目,平时都用到哪些工具提高效率(上)【转】

    做.NET 相关项目,Visual Studio 2008,SQL Server 2000/2005是标准的配置,但是,除此之外,还可以应用哪些工具来提高工作效率,让项目做的更轻松. 1  需要一个代 ...

  9. dxf转nc代码软件_Window绝赞的6款软件,效率加班党必备,快到惊人!

    高产出,高效率.高质量.这三高标准逐渐成电脑办公党必备的核心素质,但是工作时间只有那么多,如何可以将工作质量做好做强呢?6款绝赞的windows软件,每一款都可以改善你的工作效率. 一.Double ...

最新文章

  1. More than Moore的探索
  2. Unity iOS打开AppStore评星页面,浅谈Application.OpenURL()方法。
  3. 张锋在美赢得“基因剪刀”专利判决!此前与诺奖得主纠纷多年
  4. 浅析Memcache和Redis
  5. 句子中单词首字母大写转换
  6. Brad Wilson写的 ASP.NET MVC 3 Service Location 系列文章索引
  7. 【QGIS入门实战精品教程】5.1:QGIS地理坐标转火星坐标系(GCJ02)案例教程
  8. 汇编 cmp_汇编复习
  9. Redis源码分析(二)redis-cli.c
  10. Oracle备份与恢复 关于expdp导出远程数据到本地的使用
  11. ppt复制切片器_【PPT】高端人物活动介绍页PPT创意设计制作
  12. Qt4.7中 默认的构造函数
  13. 数据库建表操作SQL代码大全
  14. php网页版进销存源码ERP多仓库管理系统源码
  15. 高通UEFI研究[三]
  16. 黑马头条登录到个人中心页面
  17. TruckSim搭仿真车跑偏解决办法
  18. bak文件转oracle文件,如何在Oracle 11g中恢复.bak文件(How to restore .bak file in oracle 11g)...
  19. 科技品牌软文营销的十个建议,提高你的写作水平
  20. 跨平台键鼠共享synergy和常见问题

热门文章

  1. 2023年山东最新水利水电施工安全员模拟试题及答案
  2. 我把毕业设计命名成“垃圾”发给了导师...
  3. empirecms php 很慢,关于帝国cms提高网站网页打开速度的方法
  4. 今天聊聊为什么说做外贸推广选谷歌比脸书好?
  5. iOS Bug解决办法:如何防止Siri读出隐藏的通知
  6. 来自台湾花友的新手养护指南(转载)
  7. 【赵强老师】什么是Docker Harbor
  8. 统计量与充分统计量的定义
  9. 此网页包含重定向循环
  10. 哈希函数(Hash Function)