Unreal引擎启动流程及耗时分析
引擎版本: 4.18
部分耗时流程结构概述:
Winmain
└GuardedMain
└EnginePreInit
└FEngineLoop::PreInit
└LoadPreInitModules()
└AppInit()
└ProcessNewlyLoadedUObjects()
└LoadStartupCoreModules()
└LoadPreLoadingScreedModules
└LoadStartupModules()
└FStartupPackages::LoadAll()
└EditorInit( IEngineLoop& EngineLoop )
└EngineLoop.Init()
└FUnrealEdMisc::Get().OnInit()
└MainFrameModule.CreateDefaultMainFrame()
EnginePreInit()
int32 FEngineLoop::PreInit( const TCHAR* CmdLine )
{
// 进行一些硬件/平台相关的设置
//
LoadPreInitModules();
// 11.10
AppInit();
ProcessNewlyLoadedUObjects();
LoadStartupCoreModules();
// Load up all modules that need to hook into the loading screen
if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PreLoadingScreen) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PreLoadingScreen))
{
return 1;
}
// Load PreDefault/Default/PostDefault Modules For Project and Enabled Plugins
if (!LoadStartupModules())
{
return 1;
}
// load up the seek-free startup packages
if ( !FStartupPackages::LoadAll() )
{
// At least one startup package failed to load, return 1 to indicate an error
return 1;
}
}
FEngineLoop::PreInit()整个流程有2000+行, 所以以上代码仅为简述, 列出了一些比较耗时的步骤以及关键步骤.
4.18版本没有FEngineLoop::PreInitPreStartupScreen()函数, 与4.26版本对照可以看出, FEngineLoop::PreInitPreStartupScreen对原有的FEngineLoop::EnginePreInit()进行了拆分.
AppInit()
bool FEngineLoop::AppInit()
{
// 初始化本地化文本
BeginInitTextLocalization();
// 解析Cmd命令
// 执行平台对应的PreInit
FPlatformMisc::PlatformPreInit();
FPlatformApplicationMisc::PreInit();
FPlatformProcess::SetCurrentWorkingDirectoryToBaseDir();
// make sure that the log directory exists
IFileManager::Get().MakeDirectory( *FPaths::ProjectLogDir() );
// Init logging to disk
FPlatformOutputDevices::SetupOutputDevices();
// 根据不同平台加载对应的配置文件.ini
FConfigCacheIni::InitializeConfigSystem();
// 加载PostConfigInit模块
if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostConfigInit) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PostConfigInit))
{
return false;
}
FCoreDelegates::OnInit.Broadcast();
}
该部分耗时为6~6.5s左右.
FConfigCacheIni::InitializeConfigSystem()
// https://github.com/EpicGames/UnrealEngine/blob/4.18/Engine/Source/Runtime/Core/Private/Misc/ConfigCacheIni.cpp
void FConfigCacheIni::InitializeConfigSystem()
{
FConfigCacheIni::LoadGlobalIniFile(GEngineIni, TEXT("Engine"), nullptr, bDefaultEngineIniRequired);
FConfigCacheIni::LoadGlobalIniFile(GGameIni, TEXT("Game"));
FConfigCacheIni::LoadGlobalIniFile(GInputIni, TEXT("Input"));
#if WITH_EDITOR
// 加载
/* Editor/EditorPerProjectUserSettings/EditorSettings/EditorLayout/EditorKeyBindings */
#endif
#if PLATFORM_DESKTOP
// 加载
/* Compat/Lightmass */
#endif
// Load scalability settings.
FConfigCacheIni::LoadGlobalIniFile(GScalabilityIni, TEXT("Scalability"));
// Load driver blacklist
FConfigCacheIni::LoadGlobalIniFile(GHardwareIni, TEXT("Hardware"));
// Load user game settings .ini, allowing merging. This also updates the user .ini if necessary.
FConfigCacheIni::LoadGlobalIniFile(GGameUserSettingsIni, TEXT("GameUserSettings"));
// now we can make use of GConfig
GConfig->bIsReadyForUse = true;
FCoreDelegates::ConfigReadyForUse.Broadcast();
}
加载相关的配置文件.
ProcessNewlyLoadedUObjects()
参考: 《InsideUE4》UObject(十)类型系统构造-再次触发
LoadStartupCoreModules()
bool FEngineLoop::LoadStartupCoreModules()
{
FModuleManager::Get().LoadModule(TEXT("Core"));
FModuleManager::Get().LoadModule(TEXT("Networking"));
FWindowsPlatformApplicationMisc::LoadStartupModules();
FModuleManager::LoadModuleChecked<IMessagingModule>("Messaging");
FModuleManager::LoadModuleChecked<IMRMeshModule>("MRMesh");
FModuleManager::LoadModuleChecked<IEditorStyleModule>("EditorStyle");
FModuleManager::Get().LoadModule("Slate");
FModuleManager::Get().LoadModule("SlateReflector");
FModuleManager::Get().LoadModule("UMG");
FModuleManager::Get().LoadModule("MessageLog");
FModuleManager::Get().LoadModule("CollisionAnalyzer");
FModuleManager::Get().LoadModule("FunctionalTesting");
FModuleManager::Get().LoadModule(TEXT("BehaviorTreeEditor"));
FModuleManager::Get().LoadModule(TEXT("GameplayTasksEditor"));
FModuleManager::LoadModuleChecked<IAudioEditorModule>("AudioEditor")->RegisterAssetActions();
FModuleManager::Get().LoadModule("StringTableEditor");
FModuleManager::Get().LoadModule(TEXT("VREditor"));
FModuleManager::Get().LoadModule(TEXT("EnvironmentQueryEditor"));
FModuleManager::Get().LoadModule(TEXT("IntroTutorials"));
FModuleManager::Get().LoadModule(TEXT("Blutility"));
FModuleManager::Get().LoadModule(TEXT("Overlay"));
FModuleManager::Get().LoadModule(TEXT("MediaAssets"));
FModuleManager::Get().LoadModule(TEXT("ClothingSystemRuntime"));
//FModuleManager::Get().LoadModule(TEXT("ClothingSystemEditor"));
FModuleManager::Get().LoadModule(TEXT("PacketHandler"));
return bSuccess;
}
该部分显式加载了一些核心模块.
裁剪估计会对核心功能产生影响.
LoadStartupModules()
bool FEngineLoop::LoadStartupModules()
{
// Load any modules that want to be loaded before default modules are loaded up.
if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PreDefault) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PreDefault))
{
return false;
}
// Load modules that are configured to load in the default phase
if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::Default) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::Default))
{
return false;
}
// Load any modules that want to be loaded after default modules are loaded up.
if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostDefault) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PostDefault))
{
return false;
}
return true;
}
在引擎启动过程中, 相关的模块通过LoadingPhase来控制加载时机:
| Name | Description |
|---|---|
| EarliestPossible | As soon as possible - in other words, uplugin files are loadable from a pak file (as well as right after PlatformFile is set up in case pak files aren't used) Used for plugins needed to read files (compression formats, etc) |
| PostConfigInit | Loaded before the engine is fully initialized, immediately after the config system has been initialized. |
| PostSplashScreen | The first screen to be rendered after system splash screen |
| PreEarlyLoadingScreen | Loaded before coreUObject for setting up manual loading screens, used for our chunk patching system |
| PreLoadingScreen | Loaded before the engine is fully initialized for modules that need to hook into the loading screen before it triggers |
| PreDefault | Right before the default phase |
| Default | Loaded at the default loading point during startup (during engine init, after game modules are loaded.) |
| PostDefault | Right after the default phase |
| PostEngineInit | After the engine has been initialized |
| None | Do not automatically load this module |
| Max |
通过日志打点可以观察到, PreDefault|Default|PostDefault这三种类型的模块在PreInit()阶段就已经加载完毕了, 而PostEngineInit类型的模块将在EditorInit()阶段加载, 除去None以外的LoadingPhase都是在编辑器加载完成前执行的, 在当前的编辑器中, 不同时机的加载耗时如下(引擎启动时间波动较大, 所以引擎加载时间此处做定性分析而非定量分析):
| Name | 优化前耗时(s) | 优化后耗时(s) | 差值(s) |
|---|---|---|---|
| PostConfigInit | 0.449 | 0.373 | 0.08 |
| PreLoadingScreen | 0.319 | 0.272 | 0.05 |
| PreDefault | 2.131 | 4.662 | -2.53 |
| Default | 25.232 | 17.721 | 7.51 |
| PostDefault | 0.125 | 0.120 | 0.00 |
| PostEngineInit | 8.075 | 5.813 | 2.26 |
本次引擎在PreInit()阶段耗时为68.757秒, LoadStartupModules()为27.8秒, 占40.43%, 通过观察不难发现, 一些模块/插件为仅移动平台下加载的模块, 或美术人员对模型进行优化耗时的模块, 或引擎原生但项目中未使用的模块, 可以对此处进行一些策略配置, 来减少引擎启动耗时.
EditorInit() (如果是编辑器)
int32 EditorInit( IEngineLoop& EngineLoop )
{
// 加载一些模块, 耗时8.06s
int32 ErrorLevel = EngineLoop.Init();
// 发送编辑器已启动事件: 耗时0.0s
FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.ProgramStarted"), EventAttributes);
/** The public interface for the unreal editor misc singleton. */
// 注册编辑器事件
// 注册资源编辑器/材质编辑器/寻路网格/曲线命令
// 记录操作
// 加载一些编辑器模块,详见(FEditorModeRegister::Initialize())
// 根据配置加载版本控制模块(P4 or Git)
// 加载地图
// 初始化一些构建配置
// 绑定一些日志事件监听
// 耗时0.72s
FUnrealEdMisc::Get().OnInit();
// 从.ini文件中读取默认路径, 用来加载与保存: 耗时0.0s
FEditorDirectories::Get().LoadLastDirectories();
// 设置actor folders单例
FActorFolders::Init();
// =================== CORE EDITOR INIT FINISHED ===================
// 从命令行判断是否是immersive mode以设置编辑器
// 显示编辑器窗口
IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked<IMainFrameModule>(TEXT("MainFrame"));
MainFrameModule.CreateDefaultMainFrame( bStartImmersive, bStartPIE );
// 判断是否是VRMode, 是则加载对应编辑器: 耗时0.0s
VREditorModule.EnableVREditor( true );
// 提示是否需要更新游戏工程文件: 耗时0.0s
FGameProjectGenerationModule::Get().CheckForOutOfDateGameProjectFile();
FGameProjectGenerationModule::Get().CheckAndWarnProjectFilenameValid();
// 在stat中打点
// 加载HierarchicalLODOutliner模块
return 0;
}
对于EditorInit()而言, EngineLoop::Init()耗时占比35.8%, MainFrameModule.CreateDefaultMainFrame耗时占比49.41%, 其中加载LevelEditor对应的Tab占CreateDefaultMainFrame的99%以上.
EngineLoop.Init()
int32 FEngineLoop::Init()
{
InitTime();
GEngine->Init(this);
if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostEngineInit) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PostEngineInit))
{
GIsRequestingExit = true;
return 1;
}
GEngine->Start();
// Ready to measure thread heartbeat
FThreadHeartBeat::Get().Start();
}
FUnrealEdMisc::Get().OnInit()
该部分耗时在0.7s左右.
FEditorModeRegister::Initialize()
不同版本加载模块对比:
| 序号 | 4.18 | 4.27 | 5.0 |
|---|---|---|---|
| 1 | PlacementMode | PlacementMode | |
| 2 | BspMode | ||
| 3 | TextureAlignMode | ||
| 4 | GeometryMode | ||
| 5 | ActorPickerMode | ActorPickerMode | ActorPickerMode |
| 6 | SceneDepthPickerMode | SceneDepthPickerMode | SceneDepthPickerMode |
| 7 | MeshPaintMode | MeshPaintMode | |
| 8 | LandscapeEditor | LandscapeEditor | LandscapeEditor |
| 9 | FoliageEdit | FoliageEdit | FoliageEdit |
| 10 | VirtualTexturingEditor | VirtualTexturingEditor |
4.18(我们项目编辑器也是这样)中有一行注释:
//@TODO: ROCKET: These are probably good plugin candidates, that shouldn't have to be force-loaded here but discovery loaded somehow
说明这里加载的Module可以在此后的某个时间段按需加载, 从4.25版本开始BspMode, TextureAlignMode, GeometryMode不在此处加载.
MainFrameModule.CreateDefaultMainFrame
该部分耗时在14s~15s左右, 主要负责创建编辑器窗口:
void FMainFrameModule::CreateDefaultMainFrame( const bool bStartImmersive, const bool bStartPIE )
{
if (!IsWindowInitialized())
{
// 此处为主要耗时瓶颈: 加载LevelEditor对应的Tab
TSharedRef<FTabManager::FLayout> LoadedLayout = FLayoutSaveRestore::LoadFromConfig()
MainFrameContent = FGlobalTabmanager::Get()->RestoreFrom( LoadedLayout, RootWindow, bEmbedTitleAreaContent );
bLevelEditorIsMainTab = true;
//...
RootWindow->SetContent(MainFrameContent.ToSharedRef());
// Initialize the main frame window
MainFrameHandler->OnMainFrameGenerated( MainTab, RootWindow );
// Show the window!
MainFrameHandler->ShowMainFrameWindow( RootWindow, bStartImmersive, bStartPIE );
MainFrameCreationFinishedEvent.Broadcast(RootWindow, ShouldShowProjectDialogAtStartup());
}
}
参考文章:
本文来自博客园,作者:Mosswang,转载请注明原文链接:https://www.cnblogs.com/zhang-mo/p/16657313.html

浙公网安备 33010602011771号