奇怪的C ++模式减少了编译时间

时间:2015-12-19 10:41:46

标签: c++ design-patterns optimization tizen compilation-time

我在Tizen Project的OpenSource代码中找到了可以缩短项目编译时间的模式。它在项目的许多地方使用。

作为一个例子,我选择了一个类名ClientSubmoduleSupport。它很短。以下是他们的来源:client_submode_support.hclient_submode_support.cpp

正如您在client_submode_support.h所看到的那样,定义了一个ClientSubmoduleSupportclient_submode_support.cpp定义了ClientSubmoduleSupportImplementation类,可以为ClientSubmoduleSupport工作。

你知道那种模式吗?我很好奇这种方法的利弊。

4 个答案:

答案 0 :(得分:7)

此模式称为" Bridge",也称为" Pimpl idiom"。

  

<强>意图:   &#34;将抽象与其实现分离,以便两者可以独立变化&#34;

     

Souce:&#34;四人帮&#34;设计模式书

答案 1 :(得分:3)

正如sergej已经提到的那样, Pimpl 成语也是 Bridge 的一个子集设计模式。

但我想为这个话题提供一个C视角。我很惊讶,因为我更多地使用了C ++这个名字,因为类似的做法在C中应用了类似的优点和缺点(但由于C中缺少一些额外的专业人员)。

C Perspective

在C语言中,指向前向声明的struct的不透明指针是相当常见的做法,如下所示:

// Foo.h:
#ifndef FOO_H
#define FOO_H

struct Foo* foo_create(void);
void foo_destroy(struct Foo* foo);
void foo_do_something(struct Foo* foo);

#endif

// Foo.c:
#include "Foo.h"

struct Foo 
{
    // ...
};

struct Foo* foo_create(void)
{
    return malloc(sizeof(struct Foo));
}

void foo_destroy(struct Foo* foo)
{
    free(foo);
}

void foo_do_something(struct Foo* foo)
{
    // Do something with foo's state.
}

这对Pimpl带来了类似的优点/缺点,还有一个C的额外专业人员。在C中,private没有structs说明符,这是隐藏的唯一方法信息并阻止从外部世界访问struct内部。因此,它既成为隐藏和阻止访问内部的手段。

在C ++中,有一个很好的private说明符允许我们阻止访问内部,但我们无法完全隐藏其外部世界的可见性,除非我们使用类似Pimpl的内容基本上将这种不透明指针的C概念包含在具有一个或多个构造函数和析构函数的class形式的前向声明的UDT中。

效率

也许独立于独特上下文的一个最明显的缺点是这种表示将可能是单个连续内存块的内容分成两个块,一个用于指针,另一个用于数据字段,如下所示:

[Opaque Pointer]-------------------------->[Internal Data Fields]

......这通常被描述为引入了一个额外的间接层,但这并不是间接性的问题,而是因为参考局部的退化以及额外的性能问题。第一次堆分配和访问这些内部时,强制缓存未命中和页面错误。

  

通过这种表示,我们也不能再简单地分配   我们在堆栈上需要的一切。只能指定指针   堆栈,而内部必须在堆上分配。

如果我们要存储一堆这些句柄的数组(在C中,不透明指针本身,在C ++中,一个包含一个的对象),与此相关的性能成本往往是最明显的。在这种情况下,我们最终会得到一个数百万个指针,这些指针可能指向所有地方,我们最终会以增加的页面错误和缓存未命中以及堆(免费存储)的形式支付费用。分配/解除分配开销。

  

这最终会让我们留下类似于Java的性能,存储一百万个用户定义类型实例的通用列表并按顺序处理它们(运行和隐藏)。

效率:固定分配器

显着减轻(但不能消除)这种成本的一种方法是使用一个O(1)固定分配器,它为内部提供更连续的内存布局。这可以在我们使用Foos数组的情况下发挥显着作用,例如,通过使用允许Foo内部存储与(更多)连续内存布局的分配器(改善参考的地点)。

效率:批量接口

采用一种非常不同的设计思维方式的方法是开始在较粗糙的级别对公共接口进行建模,使其成为Foo聚合(Foo个实例容器的接口),并隐藏甚至能够从外部世界单独实例Foo的能力。这仅适用于某些情况,但在这种情况下,我们可以将成本降低到整个容器的单个指针间接,如果公共接口包含在许多隐藏{{}上运行的高级算法,则开始变得几乎免费。 {1}}一次对象。

作为一个明显的例子(虽然希望没有人这样做),我们不想使用Foo策略来隐藏图像的单个像素的细节。相反,我们希望在整个图像级别对我们的界面进行建模,该图像级别由一堆像素和适用于一堆像素的公共操作组成。与单个粒子与粒子系统相同的想法,甚至可能是视频游戏中的单个精灵。如果我们发现自己处于性能热点,我们总是可以扩展我们的接口,因为它们过于精细化了一个级别,并且需要支付内存或抽象惩罚(例如动态调度)。

  

&#34;如果你想要达到最佳表现,你就得抽水了!大量使用这些接口!到达choppa!&#34; - 用螺丝刀穿过某个人的颈部之后想象中Arnie的建议。

更轻的标题

可以看出,这些做法完全隐藏了来自外部世界的Pimplclass的内部。从编译时和标题的角度来看,这也可以作为一种解耦机制。

struct的内部通过头文件不再对外界可见时,构建时间会立即下降,只是因为头部较小。也许更重要的是,Foo的内部可能需要包含其他头文件,例如Foo。通过隐藏内部,我们不再需要Bar.h来包含Foo.h(只有Bar.h会包含它)。由于Foo.cpp可能还包含具有级联效果的其他标头,因此可以大大减少预处理器所需的工作量,并使我们的头文件比使用Bar.h之前的文件更轻量级

因此,虽然Pimpl有一些运行时成本,但它们可以减少构建时间成本。即使是性能最关键的领域,大多数复杂的代码库也会比最高的运行时效率更有利于生产力。从生产力的角度来看,冗长的构建时间可能是杀手锏,因此在运行时对性能的轻微性能降低可能是一个很好的权衡。

级联更改

此外,通过隐藏Pimpls内部的可见性,对其进行的更改不再影响其头文件。这允许我们现在只需更改Foo,例如,更改Foo.cpp的内部,只有这一个源文件需要在这种情况下重新编译。这也与构建时间有关,但特别是在小的(可能非常小的)变化的情况下,必须重新编译各种事物可能是真正的PITA。

  

作为奖励,如果他们不必重新编译所有内容以对私人细节进行一些小改动,这也可能会提高团队中所有队友的理智度一些课程。

有了这个,每个人都可以更快地完成他们的工作,在他们的日程安排中留出更多时间来访问他们最喜欢的酒吧并受到重创等等。

API和ABI

一个不太明显的专业人士(但在API上下文中非常重要)是当您为插件开发人员公开API时(包括第三方在您的控件之外编写源代码),例如在这种情况下,如果以某种方式公开Fooclass的内部状态,使得插件访问的句柄直接包含这些内部,我们最终会得到一个非常脆弱的ABI。二进制依赖可能开始类似于这种性质:

struct

这里最大的问题之一是,如果对这些内部状态进行任何更改,内部的ABI会破坏哪些插件直接依赖于工作。实际结果:现在我们最终得到了一堆插件二进制文件,可能是各种各样的人为我们的产品编写的,直到为新的ABI发布新版本之后它们才能工作。

这里一个不透明的指针(包括[Plugin Developer]----------------->[Internal Data Fields] )引入了一个保护我们免受此类ABI破坏的中介。

Pimpl

...当你现在可以自由更改私人内部而不会冒这样的插件破坏风险时,这可以在向后插件兼容性方面走很长的路。

优点和缺点

以下是优缺点的摘要以及一些其他的小问题:

优点:

  • 轻量级标题的结果。
  • 减少级联构建更改。内部因素只能在影响一个编译单元(也就是翻译单元,即源文件)而不是许多编辑单元时进行更改。
  • 隐藏内部,即使从审美/文档的角度来看也是有益的(不要使用公共界面向客户展示超出他们需要看到的内容以便使用它)。
  • 防止客户依赖脆弱的ABI,这会破坏修改单个内部细节的时刻,从而减少由于ABI更改而导致的二进制文件级联破坏。

缺点:

  • 运行时效率(通过体积较大的接口或高效的固定分配器来缓解)。
  • Minor:为实现者读取/写入的更多样板代码(尽管没有重复任何非平凡的逻辑)。
  • 无法应用于需要在生成代码的站点上显示完整定义的类模板。

TL; DR

所以无论如何,上面是对这个成语的简要介绍,以及与C之前的实践相似的历史和相似之处。

答案 2 :(得分:0)

您将使用此模式主要是在为第三方开发人员使用的库编写代码时,您无法更改API。它使您可以自由地更改函数的底层实现,而无需客户在使用新版本的库时重新编译代码。

(我已经看到API稳定性要求被写入法律合同)

答案 3 :(得分:0)

使用这种模式来减少编译时间将在下面进行广泛讨论 J.拉科斯。 “大规模C ++软件设计”(Addison-Wesley,1996)。

Herb Sutter也就这种方法的优点进行了一些讨论 here