设为首页 加入收藏

TOP

C++ 性能剖析 (四):Inheritance 对性能的影响
2015-07-20 17:45:48 来源: 作者: 【 】 浏览:2
Tags:性能 剖析 Inheritance 影响
Inheritance 是OOP 的一个重要特征。虽然业界有许多同行不喜欢inheritance,但是正确地使用inheritance是一个应用层面和架构层面的重要设计决定。 大量使用inheritance,尤其在类似std container 中使用,会对程序性能产生何等影响呢?
?
从我个人的经验来看,constructor对创建具有深层inheritance链的class,有很大的影响。 如果应用容许,最好使用没有constructor的基类。下面举个例子:
?
struct __declspec(novtable) ITest1
?
{ virtual void AddRef() = 0;
?
? ? virtual void Release() = 0;
?
? ? virtual void DoIt(int x) = 0; };
?
class CTest: public ITest1
?
{
?
int ref;
?
public: inline CTest() { ref = 0; }
?
inline void AddRef() { ++ref; }
?
inline void Release() {--ref; }
?
inline void DoIt(int x) {ref *= x; }
?
inline void AddRef2() { ++ref; }
?
inline void Release2() {--ref; }
?
inline void DoIt2(int x) {ref *= x; }
?
static void TestPerf(int loop); };
?
这是个dummy程序,然而在COM中确是再常见不过。如果我们要大量创建并使用CTest,有经验的程序员应该看出,ITest1 完全不需要constructor。 根据C++ 说明书,ITest1因为有虚拟函数,属于“非简单构造类”,编译必须产生一个constructor,其唯一的目的是设置ITest1的vtbl (虚拟函数表)。
?
然而interface的唯一作用是被继承,所以其vtbl一定是被其继承类设置。编译在这种情况下没必要生成constructor。 微软在设计ATL时认识到这一点,推出自己的方案来躲避C++官方SPEC的缺陷:VC++提供了novtable的class modifier,告诉编译:我不需要你的constructor. 然而我在VS 2010中的测试结果却令人失望:
?
ITest1的constructor 仍然被生成了,只是它没有将vtbl赋值而已,这对增进基类构造的性能实为杯水车薪之举。 下面我们看看这个“毫无用处的constructor”对性能的影响。 我们权且拿出另一个不需要虚拟函数的ITestPOD (POD的意思是“数据而已”)来做比较:
?
struct ITest1POD
?
{ inline void AddRef() { }
?
inline void Release() { }
?
inline void DoIt(int x) { } };
?
ITestPOD当然不能完全作interface用(interface必须用虚拟函数),仅仅为了测试。然后,我们设计一个继承类,和上面的CTest功能完全一样:
?
class CTestPOD: public ITest1POD
?
{
?
int ref;
?
public: inline CTestPOD() { ref = 0; }
?
inline void AddRef() { ++ref; }
?
inline void Release() {--ref; }
?
inline void DoIt(int x) {ref *= x; }
?
};
?
我们的目的是用这个CTestPOD来和CTest作一番苹果与苹果的比较:
?
void CTest::TestPerf(int loop)
?
{
?
clock_t begin = clock();
?
for(int i = 0; i < loop; ++i) //loop1
?
{
?
CTestPOD testPOD; // line1
?
testPOD.AddRef();
?
testPOD.DoIt(0);
?
testPOD.Release();
?
}
?
clock_t end = clock();
?
printf("POD time: %f \n",double(end - begin) / CLOCKS_PER_SEC);
?
begin = clock();
?
for(int i = 0; i < loop; ++i) //loop2
?
{
?
CTest test; // line2
?
test.AddRef2();
?
test.DoIt2(0);
?
test.Release2();
?
}
?
end = clock();
?
printf("Interface time: %f \n",double(end - begin) / CLOCKS_PER_SEC);
?
}
?
上面的loop1和loop2的唯一区别在line1和line2,为了避免用虚拟函数,我特意给CTest准备了AddRef2,DoIt2,Release2,三个同样的但却是非虚拟的函数,为的是遵循性能测试的一大原理:compare apple to apple。
?
我将loop设为10万,测试结果显示,loop2比loop1的速度低了20% 左右。从生成的代码来看,唯一的区别是CTest的constructor调用了编译自动生成的ITest1 的constructor。这个constructor没有任何作用,却白占了许多CPU周期。一个好的编译,应该是可以把这个constructor裁剪掉的,这个靠我们自己去搜索了。
?
总结
?
在应用inheritance时,除去基类里无用的constructor,对大量构造的object的性能来说,会有明显的影响。不幸的是,微软的__declspec(novtable) class modifier对解决这个问题没有提供任何帮助。在设计海量存储的object的应用中,我们应该尽量用POD来做其基类,避免上面CTest类那样明显的性能 漏洞
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇ZOJ 3789 Abs Problem 下一篇POJ 1442-Black Box(优先队列)

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容:

·你必须要弄懂的多线 (2025-12-25 04:22:35)
·如何在 Java 中实现 (2025-12-25 04:22:32)
·Java【多线程】单例 (2025-12-25 04:22:29)
·C++中智能指针的性能 (2025-12-25 03:49:29)
·如何用智能指针实现c (2025-12-25 03:49:27)