使用FastBuild给UnrealEngine编译Shader加速

如果你有遇到编译UE4项目代码或者Shader时又慢又卡,那看这篇文章就对了

前言

使用环境:

  • 引擎UE4.27
  • VS版本:2019
  • 系统:Windows
  • 内网千兆带宽

文章内容:

  • 修改FastBuild源码并编译
  • FastBuild联编UE4项目并缓存
  • FastBuild 联编 UE4 Shader
  • 以上都是基于Windows平台

本文重点讲述实操过程,FastBuild原理部分这里不过多讲述,感兴趣的可以去网上查找,网上有不少FastBuild加速UE的相关文章,但是基本都太老,使用的版本也很旧,涉及的内容不全面,这里会全面讲述所有方面,包括可行不可行的方案,还有其他新方案等

如果你的UE引擎版本是4.26以及更早,可参考这个 修改代码,如果你是UE5以上版本也可以参考这篇文章,我有看过引擎在FastBulid这一块基本没有修改过

准备工作

  • 机器:同一局域网内的多台主机,本文就演示两台机器,先说明我们后面会以发起机命名需要发起编译任务的主机,远程机来命名执行远程联编的主机,首先发起机需要有编译环境Visual Studio,如果需要联编Shader 所有机器需要安装DirectX
  • 网络最好是千兆带宽,如果你的带宽实在太小,参考下 官网文档参数,看下压缩相关的参数
  • FastBuild源码:Github ,我用的是最新的1.11版本
  • FastBuild 可执行文件:官网下载
  • FastBuild Dashboard: GitHub,下载完只保留FBDashboard.exe,FBDashboard.exe.config,删除FBuild,里面是老版本文件了

环境变量

  • FASTBUILD_BROKERAGE_PATH : (重要) FastBuild最主要的网络共享位置,需要这个共享文件夹具有所有主机的读写权限
  • PATH : (次重要)加入FastBuild可执行文件的路径
  • FASTBUILD_CACHE_PATH : (可选)指定Cache的路径,可是本地路径或网络路径

大纲

本文会以功能划分,按照功能来展开讲述,FastBuild在虚幻中基本就是编译项目编译Shader两个用处,下面会以这两个方面来说,因为UE4.27引擎已经集成了FastBuild的主要代码,如果我们是编译Shader的话基本不需要改什么东西,即可直接使用,如果你只是编译Shader只需要看这一段即可,我会把编译Shader放到前面来说

  1. 编译Shader
  2. 编译项目

编译Shader

基本步骤

1.下载可执行文件

FastBuild官网下载, 直接点击下图Windows平台即可

解压出来得到一个FBuild.exe和FBuildWorker.exe,前者是编译发起主要文件,后者是联机工作主要文件

2.替换引擎目录文件

把上述两个文件拷贝到 Engine\Extras\ThirdPartyNotUE\FASTBuild\Win64 下,替换现有的,其实引擎里也会去找环境变量配置的位置,但是默认先找这个目录下,而且自带的这个是很老的版本了,所以要替换成新的

3.创建网络共享文件夹

也可以使用现有的网络文件夹,创建大概步骤如下图,很简单,这个共享文件夹放在哪一台机子上都行,一份就够了

网络路径在这里查看

4.配置环境变量

系统变量中添加 FASTBUILD_BROKERAGE_PATH,值为 上面创建的网络路径

5.运行测试联机环境

如果网络文件夹不在发起端机,可以先在文件浏览器输入 网络路径看是不是可以访问

在发起机端运行FBuildWorker.exe,如果界面如下图所示并且网络文件夹中出现发起机IP为名的文件,即为成功

当出现红框内你的HostName时既说明写入成功

网络文件夹下会出现文件结构为 main/xx.windows/IP 的文件

注意: 如果运行后没有连接成功,请先ping一下有网络文件夹的机器是否能通,保证31264端口没有被占用,如果不通除了网络问题以外就是防火墙,关闭防火墙或者添加防火墙规则即可

6.打开编译Shader开关

在项目 DefaultEngine.ini 中添加

[ConsoleVariables]
r.FASTBuildShaderCompile=1

7.远程机配置

把直接官网下载解压的两个exe拷贝到远程机,随便放在哪个目录,如果本地有装360等杀毒软件请务必把这个目录添加到信任区,然后添加环境变量 FASTBUILD_BROKERAGE_PATH ,值跟前面要保持一样的,都是那个网络路径,最后运行 FBuildWorker.exe ,跟第5步一样,只要界面和网络路径下出现远程机的IP即代表连接成功

8.运行需要编译Shader的项目

项目保证打开了第6步的开关以后,直接运行编译Shader,想测试的话就把引擎和项目的 DerivedDataCache文件夹删除,进去一定会编译,如果Log出现

[2024.02.01-06.41.30:292][  0]LogShaderCompilers: Display: Submitted 2498 shader compile jobs with 'High' priority
[2024.02.01-06.41.30:841][  0]LogShaderCompilers: Display: Worker (1/5): shaders left to compile 2498
[2024.02.01-06.41.30:841][  0]LogShaderCompilers: Display: Started 2498 'FASTBuild' shader compile jobs with 'High' priority
[2024.02.01-06.41.31:379][  0]LogShaderCompilers: Display: 4 workers found in '\\SKUSER-NT0JOV5I\FASTBuildBroker\main\22.windows\'
[2024.02.01-06.41.31:379][  0]LogShaderCompilers: Display: Distributed Compilation : 3 Workers in pool '\\SKUSER-NT0JOV5I\FASTBuildBroker\main\22.windows\'
[2024.02.01-06.41.31:931][  0]LogShaderCompilers: Display: 1> Obj: C:\Users\admin\AppData\Local\Temp\UnrealShaderWorkingDir\2321D58C4C662D9B23F6858C1EE79224\FASTBuild\0\208\Shader-Batch-27336-208.out

远程机上的 Work 界面会显示 多少个连接,和正在联编的任务详情等

也可在发起机打开之前下载的 FASTBuild-Dashboard,可以查看有哪些机子用了多少核在帮你编等详细信息

进阶步骤

上面这些做完基本联编Shader就可以了,但是有一个情况就是不出意外的话现在发起机的ShaderCompileWork 进程会把CPU给吃满,会导致以下几点

  • 发起者被吃满可能会导致连接timeout,因为编译的文件还需要上传和下载,甚至可能还会有压缩解压的过程,这些都是需要CPU的,被吃满反而会导致联编变慢
  • CPU吃满会导致机子基本没法做其他的,间歇性卡死

已知前面这些问题后,我们可以通过修改引擎源码给FBuild传参来做到指定核数来编译

修改 ShaderCompileFASTBuild.cpp

Engine\Source\Runtime\Engine\Private\ShaderCompiler\Experimental\ShaderCompileFASTBuild.cpp 新增命令行参数

int32 ShaderCompileCoreCount = -1;
FAutoConsoleVariableRef CVarFBShaderCompileCoreCount(
  TEXT("r.FBShaderCompileCoreCount"),
  ShaderCompileCoreCount,
  TEXT("Specifies the number of fast build shader cores.\n")
  TEXT("Default = -1\n"),
  ECVF_Default);

给FBuild 增加参数 -jX

int32 CVarCoreCount = FASTBuildShaderCompilerVariables::ShaderCompileCoreCount;
const FString CoreCount = CVarCoreCount > 0 ? FString::Printf(TEXT("-j%d"), CVarCoreCount) : TEXT("");
const FString FASTBuildConsoleArgs = CoreCount + TEXT(" -config \"") + ScriptFilename + TEXT("\" -clean -dist -monitor ");

然后同样在 DefaultEngine.ini 中指定即可,我这里是指定了5个核心参与编译,我一共有8个核,建议一半左右即可,因为本地一般还会有其他任务在执行,反正可以把任务分摊给其他多个远程机

[ConsoleVariables]
r.FASTBuildShaderCompile=1
r.FBShaderCompileCoreCount=5

当编译时查看 FASTBuild-Dashboard,下图中local 项后面有几列就是用了几个核,也可以查看任务管理器有没有吃满CPU

总结

注意

📌基本到这里编译Shader就没什么问题了,只要特别注意几点就可以了

  • 网络共享文件夹一定要发起机远程机都能读写
  • Work起来后没有连接成功的就去查看防火墙和31264端口
  • 装了杀毒工具的一定要把exe路径添加到信任区
  • 保证发起机远程机CPU都不要被吃满,发起机就使用进阶步骤中调整核数,远程机在 FBuildWorker.exe中调整,后面会有专门一个段落介绍

优点

这里说一下FastBuild编译Shader的效率,我使用了一个半远程机,一共在33个核左右,8000个Shader只用了2分钟左右即可编译完成,当然这些都会因网络环境和CPU型号有差异,但是以我测试来说,对比之前纯本地编快了不是一点半点

基本会编译Shader的地方都能用,包括安卓打包,IOS打包还没测,等后面Mac环境的时候我一起补充

缺点

目前FastBuild不支持Shader文件的缓存,我尝试修改源码最后失败,源码中貌似只支持缓存代码编译中间文件,有兴趣的可以去研究一下,下面是官方说的Cache支持事项

因为我现在编译速度已经足够快了,我是觉得没有也没关系,需要缓存的可以去研究下虚幻自带的 DDC共享

编译UE4项目代码

编译代码的话就稍微要麻烦一些了,需要去修改FastBuild的源码才可以,正好我们也可以熟悉一下FastBuild的源码,有什么需要也可以直接修改源码定制,有一些步骤跟编译Shader重复我可能不会赘述

1.下载源码和可执行文件

认准1.11版本,后面依次是可执行文件和源码,可执行文件如果前面下载过了就不用下了,下载完以后解压出来

2.配置环境

远程机配置和环境变量配置包括网络文件夹等可参考前面编译Shader段落3,4,5,6,7步骤

编译代码需要额外环境变量path,把可执行文件目录加入到系统环境变量path中

然后运行命令 FBuild测试是否成功,控制台如果在配置环境变量之前打开的需重新打开才会加载新的环境变量

如果出现上图错误即代表成功,错误是正常的,我们要切换到源码目录执行才行

3.编译FastBuild源码

我们进入到源码目录下的dist_v1.11/Code目录

Shift + 右键 打开 PowerShell,然后运行命令

FBuild.exe All-x64-Release -dist -clean

如果有环境问题的话你可能会报错

这个是因为我本地没有clang-cl.exe,但是我们Windows不需要clang编译,我们使用的是VS,所以直接把/External/SDK/Clang/Windows/Clang11.bff中做以下修改,就是把报错的那一行赋空

或者你直接在 /Code/fbuild.bff中把 clang.bff 注了也行

把这一行直接注了也可以

完事再继续执行 命令编译,可能还会遇到这个报错

这个是因为这个路径不存在,FastBuild默认去1033这个路径找了,自己找一下你本地这个是多少,我的是2052

在/External/SDK/VisualStudio/VS2019.bff 中搜索1033,然后改成你的即可

如果顺利的话再执行命令即可编译通过了,如下图所示即表示没问题

特别注意:我这里用的是VS2019,如果你本地环境是2022或者其他版本,请在\External\SDK\VisualStudio\VisualStudio.bff 中放开对应你版本的宏,默认是19,如果你是比VS 2017还早的版本请去参考FastBuild的更早版本,里面有处理更早VS版本的

其他版本如果遇到报错基本可参考上面的修改方式,基本遵守下面几个规则就没啥问题,核心逻辑就是FastBuild寻找本地编译环境的问题,毕竟每个人的环境都是不一样的

编译通过以后会在dist_v1.11/tmp/x64-Release/Tools/FBuild/FBuilddist_v1.11/tmp/x64-Release/Tools/FBuild/FBuildWorker 下生成新的两个exe(大家已经看出来我们现在相当于用自己生成出了自己,往后看这么干是为了什么)

4.尝试编译UE4项目

我们后面所有端和路径下的两个exe都以我们自己编出来的为准,所以我们把编出来的两个exe拷贝到之前path环境变量配的路径下覆盖,和Engine\Extras\ThirdPartyNotUE\FASTBuild\Win64下覆盖,至于为什么要覆盖引擎目录下前面有说原因,主要要保证引擎目录下是自己编出来的exe

然后在保证环境变量 FASTBUILD_BROKERAGE_PATH 和 exe覆盖了的情况下直接再编译项目,现在应该就已经使用FastBuild编译了,如果看见如下log就证明开启了,特别是一直出现Obj: ......的时候就说明在用FB编了

3>Detected build type as Windows from 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x64\rc.exe' using search term 'Windows'
3>Using path : {C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC/Redist/MSVC\14.29.30133} for vccorlib_.dll (MSVC redist)...
3>  ...Add an entry for MsvcCRTRedistVersion in BuildConfiguration.xml to specify a version number
3>FBuild Command Line Arguments: '-j5 -monitor -summary -distverbose -cache -ide -clean -config "F:\EqicGames\UE4Engine_4.27\Engine\Intermediate\Build\fbuild.bff"
3>BFF file 'F:\EqicGames\UE4Engine_4.27\Engine\Intermediate\Build\fbuild.bff' has changed (reparsing will occur).
3>3 workers found in '\\SKUSER-xxxxxx\FASTBuildBroker\main\22.windows\'
3>Distributed Compilation : 3 Workers in pool '\\SKUSER-NT0JOV5I\FASTBuildBroker\main\22.windows\'
3>2> Obj: F:\EqicGames\UE4Engine_4.27\Engine\Intermediate\Build\Win64\UnrealHeaderTool\Development\Default.rc2.res
3>5> Obj: F:\EqicGames\UE4Engine_4.27\Engine\Intermediate\Build\Win64\UnrealHeaderTool\Development\UnrealHeaderTool\Module.UnrealHeaderTool.cpp.obj

如果你要是没有启用成功,请查看 Engine\Source\Programs\UnrealBuildTool\Executors\Experimental\FASTBuild.cs 文件里的IsAvailable 方法,这个方法是在ActionGraph.cs ExecuteActions方法里面调用的,这个条件只要通过了就会启用,FASTBuild.cs就是FB编译UE项目的核心代码

但是不出意外你现在应该会出现下面这个报错

3>EXEC : 1> Failed to build Object. error : 4294967295 (0xffffffff) Target: 'F:\EqicGames\UE4Engine_4.27\Engine\Intermediate\Build\Win64\UnrealHeaderTool\Development\CoreUObject\Module.CoreUObject.4_of_9.cpp.obj'
3>1> PROBLEM: F:\EqicGames\UE4Engine_4.27\Engine\Intermediate\Build\Win64\UnrealHeaderTool\Development\CoreUObject\Module.CoreUObject.4_of_9.cpp.obj
3>EXEC : error : Unable to create child process

5.修改源码以可以编译UE4项目

事先说明:如果你不想跟着下面一步步修改可直接到尾部下载我修改过的FB源码通过对比工具合并

出现这个报错是因为要编译虚幻项目,需要在FB源码里做一些特殊处理,根据参考 虚幻 \Engine\Extras\ThirdPartyNotUE\FASTBuild\fbuild_0.99.diff 的处理,我们对源码做以下修改

Code\Core\Core.bff

// Library
//--------------------------------------------------------------------------
ObjectList( '$ProjectName$-CPP-$Platform$-$BuildConfigName$' )
{
    // Input
    .CompilerInputUnity         = '$ProjectName$-Unity-$Platform$-$BuildConfigName$'
    .CompilerInputFiles         = .MemTrackerFile

    // Output
    .CompilerOutputPath         = '$OutputBase$/$ProjectPath$/'
}
Alias( '$ProjectName$-Lib-$Platform$-$BuildConfigName$' )
{
    .Targets = { '$ProjectName$-CPP-$Platform$-$BuildConfigName$' }
}

Code\Tools\FBuild\FBuildCore\Graph\CompilerNode.h

const AString & GetExtraFile( size_t index ) const { return m_StaticDependencies[ index + 1 ].GetNode()->GetName(); }

Code\Tools\FBuild\FBuildCore\Graph\ObjectNode.cpp

本来这一块只需要简简单单在ObjectNode.cpp BuildArgs 方法 中间 加一段就完事

但是引擎是根据很早的FB版本改的了,最新的1.11已经被改的全都不一样了,所以我们只能想办法自己把这个逻辑加上去

通过寻找我发现之前BuildArgs 方法里的这段逻辑被拆分到每个子类去执行了,我们要跟在%4后面,现在放在Code\Tools\FBuild\FBuildCore\ExeDrivers\Compiler\CompilerDriver_CL.cpp 中的

ProcessArg_BuildTimeSubstitution 方法中了,现在在ObjectNode.cpp BuildArgs 方法中通过调用继承方法ProcessArg_BuildTimeSubstitution实现的, 我们还是依然先把修改过的代码放到这个%4后面

// %4 -> CompilerForceUsing list
    {
        const char * const found = token.Find( "%4" );
        if ( found )
        {
            const AStackString<> pre( token.Get(), found );
            const AStackString<> post( found + 2, token.GetEnd() );
            m_ObjectNode->ExpandCompilerForceUsing( outFullArgs, pre, post );
            outFullArgs.AddDelimiter();
            return true;
        }
    }

    // %5 -> FirstExtraFile
    {
        const char * const found = token.Find( "%5" );
        if ( found )
        {
            AStackString<> extraFile;
            if ( job->IsLocal() == false )
            {
                job->GetToolManifest()->GetRemoteFilePath( 1, extraFile );
            }

            outFullArgs += AStackString<>( token.Get(), found );
            outFullArgs += job->IsLocal() ? m_ObjectNode->GetCompiler()->GetExtraFile( 0 ) : extraFile;
            outFullArgs += AStackString<>( found + 2, token.GetEnd() );
            outFullArgs.AddDelimiter();
            return true;
        }
    }
    {
        // %CLFilterDependenciesOutput -> file name Unreal Engine's cl-filter -dependencies param
        // MSVC's /showIncludes option doesn't output anything when compiling a preprocessed file,
        // so in that case we change the file name so that it doesn't override the file generated
        // during preprocessing pass.
        const char * const found = token.Find( "%CLFilterDependenciesOutput" );
        if ( found )
        {
            AString nameWithoutExtension( m_ObjectNode->GetName() );
            PathUtils::StripFileExtension( nameWithoutExtension );

            outFullArgs += AStackString<>( token.Get(), found );
            outFullArgs += nameWithoutExtension;
            outFullArgs += isPassPreprocessed ? ".empty" : ".txt";
            outFullArgs += AStackString<>( found + 27, token.GetEnd() );
            outFullArgs.AddDelimiter();
            return true;
        }
    }

    return CompilerDriverBase::ProcessArg_BuildTimeSubstitution( token, index, outFullArgs, job, isPassPreprocessed );
}

请注意,这里这段代码是我修改过的,直接把引擎里面的原始修改拿过来是会有很多错误的,我下面会依次把所有文件的修改铺上来,核心就是这一段,剩下都是为了把 ObjectNode.cpp BuildArgs 之前的逻辑直接拿过来所做的修改

Code\Tools\FBuild\FBuildCore\Graph\ObjectNode.cpp

const AString & ObjectNode::GetName() const
{
    return m_Name;
}

bool isPassPreprocessed = (pass == PASS_COMPILE_PREPROCESSED);
        // Handle build-time substitutions
        if ( driver->ProcessArg_BuildTimeSubstitution( token, i, fullArgs, job, isPassPreprocessed ) )
        {
            continue;
        }

Code\Tools\FBuild\FBuildCore\Graph\ObjectNode.h

const AString & GetName() const;

Code\Tools\FBuild\FBuildCore\ExeDrivers\Compiler\CompilerDriverBase.h

// Inject build-time substitutions (%1 etc)
    virtual bool ProcessArg_BuildTimeSubstitution( const AString & token,
                                                   size_t & index,
                                                   Args & outFullArgs,
                                                   const Job * job,
                                                   bool isPassPreprocessed ) const;

Code\Tools\FBuild\FBuildCore\ExeDrivers\Compiler\CompilerDriverBase.cpp

因为FB的警告等级很高,不引用参数会报错

// ProcessArg_BuildTimeSubstitution
//------------------------------------------------------------------------------
/*virtual*/ bool CompilerDriverBase::ProcessArg_BuildTimeSubstitution( const AString & token,
                                                                       size_t & /*index*/,
                                                                       Args & outFullArgs,
                                                                       const Job * job,
                                                                       bool isPassPreprocessed ) const
{
    bool a = job->IsLocal();
    bool b = isPassPreprocessed;
    if (a && b)
    {
        
    }
    // %1 -> InputFile

Code\Tools\FBuild\FBuildCore\ExeDrivers\Compiler\CompilerDriver_CL.h

// Inject build-time substitutions (%1 etc)
    virtual bool ProcessArg_BuildTimeSubstitution( const AString & token,
                                                   size_t & index,
                                                   Args & outFullArgs,
                                                   const Job * job,
                                                   bool isPassPreprocessed ) const override;

Code\Tools\FBuild\FBuildCore\ExeDrivers\Compiler\CompilerDriver_CL.cpp

#include "Tools/FBuild/FBuildCore/WorkerPool/Job.h"

// ProcessArg_BuildTimeSubstitution
//------------------------------------------------------------------------------
/*virtual*/ bool CompilerDriver_CL::ProcessArg_BuildTimeSubstitution( const AString & token,
                                                                      size_t & index,
                                                                      Args & outFullArgs,
                                                                      const Job * job,
                                                                      bool isPassPreprocessed ) const

然后最后就是最上面的那一段核心代码就完事了

然后执行 FBuild.exe All-x64-Release -dist -clean 命令编译

把编出来的两个exe放到引擎目录和path目录下,再继续编译UE4项目,就会开始启用FastBuild编译项目了,如果远程机连进来了,就会一起在联编了

6.进阶步骤

上面基本就已经可以正常联编代码了,但是,我先放出我联编的时间数据

  • 发起机: Intel(R) Core(TM) i7-9700K CPU @ 3.60GHz SSD硬盘
  • 远程机1 Intel(R) Core(TM) i7-8700K CPU @ 3.70GHz
  • 远程机2 12th Gen Intel(R) Core(TM)i9-12900KF 3.19GHZ

都是重新编译引擎,纯本地编译时间 53分钟左右

启用FB,一共33个核,编译时间在43分钟左右

这里可以发现,其实好像提升并不是特别大,当然我这里用的机子不是很多,你们也可以使用更多的机子测一测,我看网上有人说是因为pch的原因,FB并没有开启pch,一个是我没找到怎么开启,二个是说是开启以后代码无法Cache了,所以我暂时放弃开启pch方案,转而说到我们接下来的方法,开启编译代码Cache

开启编译代码Cache

下面主要用到 BuildConfiguration.xml 配置,引擎已经帮忙定义好了挺多参数,在 [User]/AppData/Roaming/Unreal Engine/UnrealBuildTool/BuildConfiguration.xml

下面这些目录也有这个文件

  • My Documents/Unreal Engine/UnrealBuildTool/BuildConfiguration.xml

  • Engine/Saved/UnrealBuildTool/BuildConfiguration.xml

    自已根据情况修改哪个目录下的这个文件,我这里直接贴出来我的配置代码

    <?xml version="1.0" encoding="utf-8" ?>
    <Configuration xmlns="https://www.unrealengine.com/BuildConfiguration">
      <BuildConfiguration>
        <bAllowFASTBuild>true</bAllowFASTBuild>
        </BuildConfiguration>
      <FASTBuild>
        <CacheMode>ReadWrite</CacheMode>
        <FBuildCachePath>F:\Worker\FastBuildCache</FBuildCachePath>
        <bStopOnError>true</bStopOnError>
        </FASTBuild>
    </Configuration>
    

    这里面跟cache相关的就是<CacheMode>ReadWrite</CacheMode><FBuildCachePath>F:\Worker\FastBuildCache</FBuildCachePath> ,bAllowFASTBuild是总开关,虽然默认是开的,但是建议加一个这个,如果你不想使用FB编译了直接把这个设为false就可以了,路径设置成你自己想cache的路径,也可以是网络路径,那样别人也可以到里面去查找缓存

    然后再编译就会发现这个路径下有一堆缓存文件

当你缓存过一次以后,再次重新编译引擎Obj: 后面会出现 Cache 字样,代表Cache Hit了,也可以打开FastBuild Dashboard.exe 看到详细Cache Hit信息

我再次重新编译时间只花了 7分钟左右,开启Cache提升还是非常明显的

Cache可以限制磁盘空间,详情见 官方参数 ,可以设置超过多少M以后自动删除老的Cache,感觉很有必要

增加本地编译核数限制

还是跟编译Shader一样,我们需要限制本地编译代码的核心数,不让CPU吃满,在FASTBuild里面增加以下代码即可

/// <summary>
    /// Specifies the maximum number of CPU cores at fastbuild compile time, with -1 indicating a full run
    /// </summary>
    [XmlConfigFile]
    public static int MaxProcessorCount = -1;

string DistArgument        = bEnableDistribution ? "-distverbose" : "";
      string ForceRemoteArgument    = bForceRemote ? "-forceremote" : "";
      string NoStopOnErrorArgument  = bStopOnError ? "" : "-nostoponerror";
      string IDEArgument        = IsApple() ? "" : "-ide";
      string MaxProcessCount      = MaxProcessorCount != -1 ? $"-j{MaxProcessorCount}" : "";

      // Interesting flags for FASTBuild:
      // -nostoponerror, -verbose, -monitor (if FASTBuild Monitor Visual Studio Extension is installed!)
      // Yassine: The -clean is to bypass the FASTBuild internal
      // dependencies checks (cached in the fdb) as it could create some conflicts with UBT.
      // Basically we want FB to stupidly compile what UBT tells it to.
      string FBCommandLine  = $"{MaxProcessCount} -monitor -summary {DistArgument} {CacheArgument} {IDEArgument} -clean -config \"{BffFilePath}\" {NoStopOnErrorArgument} {ForceRemoteArgument}";

然后在 BuildConfiguration.xml 中配置一下即可,我就用了5个核

<FASTBuild>
  <MaxProcessorCount>5</MaxProcessorCount>
</FASTBuild>

总结

编译UE4代码的篇幅较长,而且本人体验下来,发现如果要是开启Cache编译引擎的情况下还是有不错的提升

缺点:

  • 感觉开启FB以后编代码可能还是会有一些bug,比如改了某些地方的代码不会编译的情况,不确定是不是FB导致的,包括Cache是否会导致其他问题,调试有没有问题等都还没完整测试过,大家可以测试一下
  • pch不会开启导致很慢的问题
  • 目前只有桌面平台编译项目有用,像安卓打包是不可以的,IOS和Mac好像可以,我后面Mac平台会补充测试

综合以上我觉得FB是否要开启代码编译大家自己酌情判断,本人是觉得Shader编译就够用了,如果经常使用源码版引擎编译可以考虑

如果你要是不用FB编译,但是有编译时机子卡死的请注意了

只需要在 BuildConfiguration.xml 中加入以下,就可以使用XGE编译也不会卡死电脑,我们可以指定核数

ProcessorCountMultiplier: 本地执行的处理器计数乘数。设为小于1可为其他任务保留CPU。使用本地执行器(非XGE)时,在每个CPU核心上运行单一操作。注意: 可将该属性设为较大的值,在多数情况下可稍微提高编译速度,但电脑在编译期间的响应速度将变慢。

MaxProcessorCount: 本地执行的最大处理器数量。

不设置MaxProcessorCount是不会生效的。

<?xml version="1.0" encoding="utf-8" ?>
<Configuration xmlns="https://www.unrealengine.com/BuildConfiguration">
  <LocalExecutor>
    <ProcessorCountMultiplier>0.8</ProcessorCountMultiplier>
    <MaxProcessorCount>5</MaxProcessorCount>
  </LocalExecutor>
  <ParallelExecutor>
    <ProcessorCountMultiplier>0.8</ProcessorCountMultiplier>
    <MaxProcessorCount>5</MaxProcessorCount>
  </ParallelExecutor>
</Configuration>

设置了上面的以后使用默认编译代码时就不会吃满CPU了

下面我贴一下完整的 BuildConfiguration.xml 配置,你可以通过控制 bAllowFASTBuild来使用哪一个编译,并且每个都不会占满CPU

<?xml version="1.0" encoding="utf-8" ?>
<Configuration xmlns="https://www.unrealengine.com/BuildConfiguration">
  <BuildConfiguration>
    <bAllowFASTBuild>true</bAllowFASTBuild>
    </BuildConfiguration>
  <FASTBuild>
    <CacheMode>ReadWrite</CacheMode>
    <FBuildCachePath>F:\Worker\FastBuildCache</FBuildCachePath>
    <MaxProcessorCount>5</MaxProcessorCount>
    <bStopOnError>true</bStopOnError>
    </FASTBuild>
  <LocalExecutor>
    <ProcessorCountMultiplier>0.8</ProcessorCountMultiplier>
    <MaxProcessorCount>5</MaxProcessorCount>
  </LocalExecutor>
  <ParallelExecutor>
    <ProcessorCountMultiplier>0.8</ProcessorCountMultiplier>
    <MaxProcessorCount>5</MaxProcessorCount>
  </ParallelExecutor>
</Configuration>

FBuildWorker.exe

这里大概介绍一下Worker的界面信息

CurrentMode:

  • 不执行其他主机分发的编译任务
  • 空闲时执行其他主机分发的编译任务
  • 总是执行其他主机分发的编译任务
  • 按资源比例执行其他主机分发的编译任务

Threshold: 为联编任务分配的资源阈值

Using: 参与联编任务的核心数

FBDashboard.exe

这个界面信息没什么好说的,这里特别说明几个小点,这个从官网下载下来以后FBuild文件夹下的Worker可以删掉然后替换成你自己编出来的FBuildWorker.exe,这样子在你启动 FBDashboard的时候会自动启动Worker,这样别人就也能连你的机子了,当然你也可以分别启动,然后这个FBDashboard点右上角的X是直接退出,缩小化按钮是缩到工作栏,点击左上角三个杠里面有Worker信息

结语

上面演示了各种方式的FastBuild编译,文章写的比较仓促,有什么问题可以下方留言交流,上面基本涵盖了所有方面,后续有什么补充的会更新,我最后再啰嗦几句可选使用方案

  • 可以使用FB编译Shader,支持基本所有平台
  • 代码编译建议开启Cache使用,速度有质的提升,不支持安卓平台,还可参考这个提升速度
  • 代码分布式编译我后面会研究一下 UE5的新方案UBA,现在好像还没正式发布,后面会关注一下

我修改的FB源码下载

直接比对合过去要比前面一个个改要快,看自己需求

源码下载

后续补充计划

Mac端FastBuild编译加速...

参考文献:

  1. FASTBuild 文档
  2. 使用FASTBuild加速Unreal Engine编译
  3. 保姆式教你使用FASTBuild对UE4进行联机编译 - 知乎 (zhihu.com)
  4. 使用Fastbuild加快UnrealEngine编译速度
  5. 提升FastBuild编译UnrealEngine的速度