有没有技术可以大大改善3D应用程序的C ++构建时间?

时间:2015-04-09 13:48:04

标签: c++ c++11 makefile

有许多超薄笔记本电脑价格便宜且易于使用。编程的优点是可以在任何有沉默和舒适的地方完成,因为长时间的集中是能够做有效工作的重要因素。

我有点老式,因为我喜欢我的静态编译的C或C ++,这些语言在那些功耗受限的笔记本电脑上编译可能相当长,尤其是C ++ 11和C ++ 14。

我喜欢做3D编程,我使用的库可能很大而且不会宽容:子弹物理,Ogre3D,SFML,更不用说现代IDE的强大功能了。

有几种解决方案可以让构建更快:

  • 解决方案A:不要使用那些大型库,并自己想出更轻松的东西来减轻编译器的负担。编写适当的makefile,不要使用IDE。

  • 解决方案B:在其他地方设置构建服务器,在肌肉发达的机器上设置makefile,然后自动下载生成的exe。我不认为这是一个随意的解决方案,因为你必须瞄准笔记本电脑的CPU。

  • 解决方案C:使用非官方C ++模块

  • ???

还有其他建议吗?

4 个答案:

答案 0 :(得分:18)

如果您知道如何,编译速度是可以真正提升的。仔细考虑项目的设计(特别是在大型项目的情况下,由多个模块组成)并对其进行修改总是明智的,因此编译器可以有效地生成输出。

<强> 1。预编译的标题。

预编译头是一个普通头(.h文件),它包含最常见的声明,typedef和includes。在编译期间,它只被解析一次 - 在编译任何其他源之前。在此过程中,编译器生成一些内部(最可能是二进制)格式的数据,然后,它使用此数据来加速代码生成。

这是一个示例:

#pragma once

#ifndef __Asx_Core_Prerequisites_H__
#define __Asx_Core_Prerequisites_H__

//Include common headers
#include "BaseConfig.h"
#include "Atomic.h"
#include "Limits.h"
#include "DebugDefs.h"
#include "CommonApi.h"
#include "Algorithms.h"
#include "HashCode.h"
#include "MemoryOverride.h"
#include "Result.h"
#include "ThreadBase.h"
//Others...

namespace Asx
{

    //Forward declare common types
    class String;
    class UnicodeString;

    //Declare global constants
    enum : Enum
    {
        ID_Auto     = Limits<Enum>::Max_Value,
        ID_None     = 0
    };

    enum : Size_t
    {
        Max_Size            = Limits<Size_t>::Max_Value,
        Invalid_Position    = Limits<Size_t>::Max_Value
    };

    enum : Uint
    {
        Timeout_Infinite    = Limits<Uint>::Max_Value
    };

    //Other things...

}

#endif /* __Asx_Core_Prerequisites_H__ */

在项目中,当使用PCH时,每个源文件通常都包含#include到此文件(我不了解其他文件,但在VC ++中这实际上是一个要求 - 每个源都附加到项目配置为使用PCH,必须以:#include PrecompiledHedareName.h)开头。预编译头的配置非常依赖于平台,超出了本答案的范围。

注意一个重要问题:PCH中定义/包含的内容只有在绝对必要时才会更改 - 每个chnge都会导致重新编译整个项目(和其他依赖模块)!

关于PCH的更多信息:

Wiki
GCC Doc
MSDN

<强> 2。前瞻性声明。

当您不需要全班定义时,请将其声明为删除代码中不必要的依赖项。这也意味着在可能的情况下广泛使用指针和引用。例如:

#include "BigDataType.h"

class Sample
{
protected:
    BigDataType _data;
};

您真的需要存储_data作为价值吗?为什么不这样:

class BigDataType; //That's enough, #include not required

class Sample
{
protected:
    BigDataType* _data; //So much better now
};

对于大型类型而言,这尤其有利可图。

第3。不要过度使用模板。

元编程是开发人员工具箱中非常强大的工具。但是,如果没有必要,请不要尝试使用它们。

它们非常适合诸如特征,编译时评估,静态反射等内容。但他们引入了很多麻烦:

  • 错误消息 - 如果您曾经看到由std::迭代器或容器(尤其是复杂的迭代器或容器)(例如std::unordered_map)的不当使用而导致的错误,而不是您知道这是什么。
  • 可读性 - 复杂的模板很难阅读/修改/维护。
  • 怪癖 - 许多技术,模板都用于,不是那么出名,所以维护这些代码会更加困难。
  • 编译时间 - 对我们来说最重要的是:

请记住,如果您将函数定义为:

template <class Tx, class Ty>
void sample(const Tx& xv, const Ty& yv)
{
    //body
}

将针对TxTy的每个独占组合进行编译。如果经常使用这样的函数(对于许多这样的组合),它确实会减慢编译过程。现在想象一下,如果你开始过度使用全班的模板,会发生什么......

<强> 4。使用PIMPL idiom

这是一项非常有用的技术,它允许我们:

  • 隐藏实施细节
  • 加快代码生成
  • 轻松更新,无需破坏客户端代码

它是如何工作的?考虑包含大量数据的类(例如,代表人)。它看起来像这样:

class Person
{
protected:
    string name;
    string surname;
    Date birth_date;
    Date registration_date;
    string email_address;
    //and so on...
};

我们的应用程序不断发展,我们需要扩展/更改Person定义。我们添加一些新字段,删除其他字段......并且所有内容都崩溃:人员更改大小,字段名称更改...大灾变。特别是,需要更改/更新/修复依赖于Person定义的每个客户端代码。不好。

但我们可以巧妙地做到这一点 - 隐藏Person的详细信息:

class Person
{
protected:
    class Details;
    Details* details;
};

现在,我们做了很多好事:

  • 客户端无法创建代码,具体取决于Person的定义方式
  • 只要我们不修改客户端代码使用的公共接口
  • ,就不需要重新编译
  • 我们减少了编译时间,因为stringDate的定义不再需要存在(在以前的版本中,我们必须包含这些类型的适当标头,这会增加额外的依赖关系)。

<强> 5。 #pragma once directive

虽然it may give no speed boost,但它更清晰,更不容易出错。它与使用包含警卫基本相同:

#ifndef __Asx_Core_Prerequisites_H__
#define __Asx_Core_Prerequisites_H__

//Content

#endif /* __Asx_Core_Prerequisites_H__ */

它阻止同一文件的多次解析。虽然#pragma once不是标准的(事实上,没有编译指示 - 编译指示保留用于编译器特定的指令),但它得到了广泛的支持(例如:VC ++,GCC,CLang,ICC),可以毫无顾虑地使用 - 编译器应该忽略未知的编译指示(或多或少地默默地)。

<强> 6。不必要的依赖性消除。

非常重要的一点!当代码被重构时,依赖性经常会改变。例如,如果您决定进行一些优化并使用指针/引用而不是值(此答案的视点 2 4 ),则某些包含可能变得不必要。考虑:

#include "Time.h"
#include "Day.h"
#include "Month.h"
#include "Timezone.h"

class Date
{
protected:
    Time time;
    Day day;
    Month month;
    Uint16 year;
    Timezone tz;

    //...
};

此类已更改为隐藏实现详细信息:

//These are no longer required!
//#include "Time.h"
//#include "Day.h"
//#include "Month.h"
//#include "Timezone.h"

class Date
{
protected:
    class Details;
    Details* details;

    //...
};

最好跟踪这些冗余包含,使用大脑,内置工具(如VS Dependency Visualizer)或外部实用程序(例如,GraphViz)。

Visual Studio也有一个非常好的选择 - 如果你在任何文件上点击人民币,你会看到一个选项&#39;生成包含文件的图表&#39; - 它将生成一个漂亮,可读的图形,可以轻松分析并用于跟踪不必要的依赖关系。

在我的String.h文件中生成的示例图:

Sample Graph

答案 1 :(得分:4)

正如Yellow先生在评论中指出的那样,改善编译时间的最佳方法之一就是要特别注意使用头文件。特别是:

  • 对任何您不希望更改的标头使用预编译标头,包括操作系统标头,第三方库标头等。
  • 将其他标头中包含的标头数量减少到必要的最小值。
    • 确定标头中是否需要包含或是否可以将其移动到cpp文件。这有时会导致涟漪效应,因为其他人依赖于你来包含它的标题,但从长远来看,将include移动到实际需要的位置会更好。
    • 使用前向声明的类等通常可以消除包含声明该类的标头的需要。当然,您仍然需要在cpp文件中包含标题,但这只会发生一次,而不是每次包含相应的头文件时都会发生。
  • 使用#pragma一次(如果编译器支持),而不是包含保护符号。这意味着编译器甚至不需要打开头文件来发现包含保护。 (当然,许多现代编译器都会为你找到答案。)

一旦控制了头文件,请检查make文件以确保不再存在不必要的依赖项。目标是重建您需要的一切,但不再需要。有时人们会因为建造太多而犯错误,因为这比建造太少更安全。

答案 2 :(得分:1)

如果您已经尝试了上述所有内容,假设您的局域网上有一些可用的PC,那么就会有一个奇迹出现的商业产品。我们曾经在以前的工作中使用它。它被称为Incredibuild(www.incredibuild.com),它将我们的构建时间从一个多小时(C ++)缩短到大约10分钟。来自他们的网站:

IncrediBuild通过高效的并行计算加速构建时间。通过利用网络上的空闲CPU资源,IncrediBuild将PC和服务器网络转换为私有计算云,最好将其描述为“虚拟超级计算机”。将进程分发到远程CPU资源以进行并行处理,从而大大缩短构建时间<90%或更多。

答案 3 :(得分:0)

其他答案中未提及的另一点:模板。模板可以是一个很好的工具,但它们有根本的缺点:

  • 必须包含模板及其所依赖的所有模板。前瞻性声明不起作用。

  • 模板代码经常被编译多次。在多少.cpp文件中使用std::vector<>?这就是编译器需要多少次编译它!

    (我不主张反对std::vector<>的使用,相反,你经常使用它;它只是这里经常使用的模板的一个例子。)

  • 更改模板的实现时,必须重新编译使用该模板的所有内容。

使用模板繁重的代码,您经常拥有相对较少的编译单元,但每个单元都很庞大。当然,您可以使用全模板并且只有一个.cpp文件可以提取所有内容。这样可以避免多次编译模板代码,但是它会使make变得无用:任何编译都需要在清理后进行编译。

我建议采用相反的方向:避免使用模板大量或仅模板库,并避免创建复杂的模板。模板越相互依赖,重复编译就越多,更改模板时需要重建更多的.cpp文件。理想情况下,您拥有的任何模板都不应该使用任何其他模板(除非其他模板是std::vector<>,当然......)。