Loading

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());
    }

}

参考文章:

posted @ 2022-09-05 10:50  Mosswang  阅读(4728)  评论(0)    收藏  举报