依赖链接器最小化编译时间(Abusing the Linker to Minimize Com
AbstractIn this article,I will describe a technique that reduces coupling between an object that provides access to many types of objects ("the provider") and its users by moving compile-time dependencies to link-time. In doing so,we can reduce the amount of unnecessary compiling of the provider's dependencies whenever the provider is changed. I will then explore the benefits and costs to such a design. 概述 在这篇文章,我将介绍一种减少提供者与各种类型的使用者之间的连接关系通过把编译时间转移到连接时间的一种技术。这样做的话,我们可以减少不必要的编译即使提供者已经改变了。我将谈谈这样做的好处和花销。 The Problem
问题 经常在游戏中,一个提供者需要与游戏中的很多部分联系,就像是血液在人的身体流动一样。一个提供者是一种上下文对象,是游戏的当前状态和公共给其他有用的游戏对象。这样的类就像是listing 1,使用的例子就像是listing 2
////Listing1://ServiceContext.h#pragmaonce//DependenciesclassGame;classWorld;classRenderService;classResourceService;classPathfindingService;classPhysicsService;classLogService;//ServiceContext//ProvidesaccesstoavarietyofobjectsclassServiceContext{public: Game*constgame; World*constworld; RenderService*constrender; PathfindingService*constpath; PhysicsService*constphysics; AudioService*constaudio; LogService*constlog;}; Listing 1:The definition of a sample context object
///Listing2://Foobar.cpp#include"Foobar.h"#include"ServiceContext.h"#include"PathService.h"#include"LogService.h"voidFoobar::frobnicate(ServiceContext&ctx){ if(!condition()) return; current_path=ctx.path->evaluate(position,target->position); if(!current_path) ctx->log("Warning","Nopathfound!");}
Listiing 2:一个使用上下文对象的例子 TheServiceContextis the blood of the program,and many objects depend on it. If a new service is added toServiceContextorServiceContextis changed in any way,then all of its dependents will be recompiled,regardless if the dependent uses the new service. ServiceContext是程序的血液,很多的游戏对象依赖它。如果一个新的服务增加到ServiceContext或者是ServiceContext一任何的方式改变了,那么所有依赖它的类将会被重新编译,即使依赖的使用者并没有使用到新的服务。看figure 1 Figure 1:Recompilations needed when adding a service to the provider object
The Solution
我们可以通过转移编译时依赖到连接的依赖来隐藏它们的依赖关系。通过模板,我们可以写一个通过的get函数和在转换单元提供特殊的定义。
////Listing3://ServiceContext.h#pragmaonce//DependenciesstructServiceContextImpl;//ServiceContext//ProvidesaccesstoavarietyofobjectsclassServiceContext{public://Constructors ServiceContext(ServiceContextImpl&p);public://Methods template<typenameT> T*get()const;private://Members ServiceContextImpl&impl;};////ServiceContextImpl.h#pragmaonce//DependenciesclassGame;classWorld;classRenderService;classResourceService;classPathfindingService;classPhysicsService;classLogService;//ServiceContextImpl//ExposestheobjectstoServiceContext//BesuretoupdateServiceContext.cppwheneverthisdefinitionchanges!structServiceContextImpl{ Game*constgame; World*constworld; RenderService*constrender; PathfindingService*constpath; PhysicsService*constphysics; AudioService*constaudio; LogService*constlog;}; Listing 3:The declarations of the two new classes ////Listing4://ServiceContext.cpp#include"ServiceContext.h"#include"ServiceContextImpl.h"ServiceContext::ServiceContext(ServiceContextImpl&p):impl(p){}//ExposeimplbyprovidingthespecializationsforServiceContext::gettemplate<>Game*ServiceContext::get<Game>(){ returnimpl.game;}//...oruseamacro#defineSERVICECONTEXT_GET(type,name) template<> type*ServiceContext::get<type>()const{ returnimpl.name; }SERVICECONTEXT_GET(World,world);SERVICECONTEXT_GET(RenderService,render);SERVICECONTEXT_GET(PathfindingService,path);SERVICECONTEXT_GET(PhysicsService,physics);SERVICECONTEXT_GET(AudioService,audio);SERVICECONTEXT_GET(LogService,log);
在listing 3,我们可以在新的ServiceContext的类中写一个委托的结构体定义ServiceContextImpl。除此之外,我们有一个通用的可以为我们想要提供各种服务生成成员函数的get的成员函数。在listing 4中,我们为ServiceContextImpl的成员提供了get的定义。这个定义提供了在连接是将使用ServiceContext的模块连接到ServiceContext。
////Listing5://Foobar.cpp#pragmaonce#include"Foobar.h"#include"ServiceContext.h"#include"PathService.h"#include"LogService.h"voidFoobar::frobnicate(ServiceContext&ctx){ if(!condition()) return; current_path=ctx.get<PathService>()->evaluate(position,target->position); if(!current_path) ctx.get<LogService>()->log("Warning","Nopathfound!");} Listing 5:The Foobar implementation using the new ServiceContext Figure 2:Adding new services to ServiceContextImpl now has minimal impact on ServiceContext's dependants
The Caveats
下面是在使用这样的解决方案需要考虑在内的: 1该解决方案取决于对“全程序优化”链接器的支持,并可以在链接时内联ServiceContext的get定义。如果支持此优化,则没有额外的成本来使用该解决方案比较传统的方法。 MSVC和GCC对这种优化的支持。 2假定ServiceContext的开发过程中经常发生变化,但通常在早期的发展。可以说,这样一个复杂的系统在经过ServiceContext几个迭代之后是不需要的。 3假设编译时间大大胜过联时间。此解决方案可能不适合较大的项目。 4该解决方案偏向智能而不是可读性。这样的解决方案有一个增加的复杂性,它可以说,复杂性是不值得的边际储蓄编译时间。该解决方案可能不适合,如果该项目有多个开发人员。 5这个解决方案在Unity的编译上没有提供任何的好处
Conclusion
即使这种解决方案减少了不必要的重新编译,但他增加了项目的复杂性。这个解决方案适合减少小项目增加到中性项目时增加的编译时间。 About the Author(s)Tom Roe is a software developer and a pretty cool guy.Eh writs articles and eats rope and doesn't afraid of anything.LicenseGDOL (Gamedev.net Open License) (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |