启动速度对软件用户体验的影响是显而易见的,实验也表明,启动速度可影响用户对软件的使用时长。
就性能优化策略而言,预读(Prefetching)在提升应用程序启动速度方面有显著的作用。预读是如何运作的?为什么它可以加速应用的启动?如何适当地利用预读技术来优化我们的应用程序?
在本文中,我们将深入研究这些问题,详细介绍预读技术,并解释其如何提升启动速度。
1、缺页对启动速度的影响
在进程启动时,操作系统并不会将 PE 文件的所有内容全部加载到物理内存当中。而是等程序开始执行代码后,才会去加载该代码所在的文件内容。然而,如果这时相关代码所在的地址还没有被加载到内存中,系统就会引发一次缺页(Page Fault)错误。
这将导致系统必须去执行一个IO操作,将对应的代码从磁盘中读出加载到内存中,然后才能继续执行代码。如下图,在 Cpu Usage 视图可以看到 electron-demo.exe 在执行过程中触发了缺页错误,在107us后才恢复执行。在 “Hard Faults” 视图,可以看到此时发生了一个缺页事件,对应的文件名是 electron-demo.exe,该事件耗时118us。在”Disk Usage”视图中,我们代码执行前产生了一个IO 操作,耗时109us。
关于这几个时间为何不完全一致,我猜测可能是 ETW 事件的统计问题。但这并不妨碍我们看到 IO 事件完成后,CPU 才继续执行的事实。
MainDllLoader::LoadResult MainDllLoader::LoadModuleWithDirectory(
const base::FilePath& module) {
::SetCurrentDirectoryW(module.DirName().value().c_str());
base::PrefetchResultCode prefetch_result_code =
base::PreReadFile(module, /*is_executable=*/true).code_;
HMODULE handle = ::LoadLibraryExW(module.value().c_str(), nullptr,
LOAD_WITH_ALTERED_SEARCH_PATH);
return {handle, prefetch_result_code};
}
file_util_win.cc 中的 PreReadFile 会调用 PrefetchVirtualMemory 来预读了整个文件,需要注意的是 PrefetchVirtualMemory 在 win8 以后的系统上才有,在 win7 上 chromium 是直接调用的 ReadFile。
PrefetchResult PreReadFile(const FilePath& file_path,
bool is_executable,
int64_t max_bytes) {
DCHECK_GE(max_bytes, 0);
// On Win8 and higher use ::PrefetchVirtualMemory(). This is better than a
// simple data file read, more from a RAM perspective than CPU. This is
// because reading the file as data results in double mapping to
// Image/executable pages for all pages of code executed.
static PrefetchVirtualMemoryPtr prefetch_virtual_memory =
GetPrefetchVirtualMemoryPtr();
if (prefetch_virtual_memory == nullptr)
return internal::PreReadFileSlow(file_path, max_bytes)
? PrefetchResult{PrefetchResultCode::kSlowSuccess}
: PrefetchResult{PrefetchResultCode::kSlowFailed};
if (max_bytes == 0) {
// PrefetchVirtualMemory() fails when asked to read zero bytes.
// base::MemoryMappedFile::Initialize() fails on an empty file.
return PrefetchResult{PrefetchResultCode::kSuccess};
}
// PrefetchVirtualMemory() fails if the file is opened with write access.
MemoryMappedFile::Access access = is_executable
? MemoryMappedFile::READ_CODE_IMAGE
: MemoryMappedFile::READ_ONLY;
MemoryMappedFile mapped_file;
if (!mapped_file.Initialize(file_path, access)) {
return internal::PreReadFileSlow(file_path, max_bytes)
? PrefetchResult{PrefetchResultCode::kMemoryMapFailedSlowUsed}
: PrefetchResult{PrefetchResultCode::kMemoryMapFailedSlowFailed};
}
const ::SIZE_T length =
std::min(base::saturated_cast<::SIZE_T>(max_bytes),
base::saturated_cast<::SIZE_T>(mapped_file.length()));
::_WIN32_MEMORY_RANGE_ENTRY address_range = {mapped_file.data(), length};
if (!prefetch_virtual_memory(::GetCurrentProcess(),
/*NumberOfEntries=*/1, &address_range,
/*Flags=*/0)) {
return internal::PreReadFileSlow(file_path, max_bytes)
? PrefetchResult{PrefetchResultCode::kFastFailedSlowUsed}
: PrefetchResult{PrefetchResultCode::kFastFailedSlowFailed};
}
return PrefetchResult{PrefetchResultCode::kSuccess};
}
4、chromium 的预读是否有改进的空间?
a、精细化预读:虽然 chromium 对自己的 DLL 文件进行了预读,但对于 electron 应用来说,可能并不需要所有的功能,因此某些代码根本不会被执行。那么,这些代码所在位置真的需要进行预加载么?如果我们通过捕获 ETW 缺页事件,确定何处会引发缺页,然后根据收集的数据做出精细化的预读,是否能缩短预读的时间呢?
b、预读更多的文件。chrome 只对部分文件做了预读,但是从实际使用上来看,还有一些其他的文件会产生缺页,我们也可以进行预读。