base: Introduce generic base::PreReadFile().
https://crrev.com/c/1741782 introduced base::win::PreReadFile(). This CL moves the function to base::PreReadFile(), and adds a fadvise()-based implementation for Linux, ChromeOS and Android L+. Bug: 1001838 Change-Id: Ib81175284d883db34535bf80fc13b4f26f0ba1ad Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1781513 Commit-Queue: Victor Costan <pwnall@chromium.org> Reviewed-by: Greg Thompson <grt@chromium.org> Reviewed-by: Scott Violet <sky@chromium.org> Cr-Commit-Position: refs/heads/master@{#694610}
This commit is contained in:

committed by
Commit Bot

parent
0f6362a378
commit
b5a0a9700e
@ -1062,8 +1062,6 @@ jumbo_component("base") {
|
||||
"win/event_trace_controller.h",
|
||||
"win/event_trace_provider.cc",
|
||||
"win/event_trace_provider.h",
|
||||
"win/file_pre_reader.cc",
|
||||
"win/file_pre_reader.h",
|
||||
"win/hstring_compare.cc",
|
||||
"win/hstring_compare.h",
|
||||
"win/hstring_reference.cc",
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
|
||||
#include "base/files/file_enumerator.h"
|
||||
#include "base/files/file_path.h"
|
||||
@ -292,6 +293,45 @@ FilePath GetUniquePath(const FilePath& path) {
|
||||
return path.InsertBeforeExtensionASCII(StringPrintf(" (%d)", uniquifier));
|
||||
return uniquifier == 0 ? path : base::FilePath();
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
|
||||
bool PreReadFileSlow(const FilePath& file_path, int64_t max_bytes) {
|
||||
DCHECK_GE(max_bytes, 0);
|
||||
|
||||
File file(file_path, File::FLAG_OPEN | File::FLAG_READ |
|
||||
File::FLAG_SEQUENTIAL_SCAN |
|
||||
File::FLAG_SHARE_DELETE);
|
||||
if (!file.IsValid())
|
||||
return false;
|
||||
|
||||
constexpr int kBufferSize = 1024 * 1024;
|
||||
// Ensures the buffer is deallocated at function exit.
|
||||
std::unique_ptr<char[]> buffer_deleter(new char[kBufferSize]);
|
||||
char* const buffer = buffer_deleter.get();
|
||||
|
||||
while (max_bytes > 0) {
|
||||
// The static_cast<int> is safe because kBufferSize is int, and both values
|
||||
// are non-negative. So, the minimum is guaranteed to fit in int.
|
||||
const int read_size =
|
||||
static_cast<int>(std::min<int64_t>(max_bytes, kBufferSize));
|
||||
DCHECK_GE(read_size, 0);
|
||||
DCHECK_LE(read_size, kBufferSize);
|
||||
|
||||
const int read_bytes = file.ReadAtCurrentPos(buffer, read_size);
|
||||
if (read_bytes < 0)
|
||||
return false;
|
||||
if (read_bytes == 0)
|
||||
break;
|
||||
|
||||
max_bytes -= read_bytes;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
#endif // !defined(OS_NACL_NONSFI)
|
||||
|
||||
} // namespace base
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <limits>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@ -417,6 +418,31 @@ BASE_EXPORT FilePath GetUniquePath(const FilePath& path);
|
||||
// false.
|
||||
BASE_EXPORT bool SetNonBlocking(int fd);
|
||||
|
||||
// Hints the OS to prefetch the first |max_bytes| of |file_path| into its cache.
|
||||
//
|
||||
// If called at the appropriate time, this can reduce the latency incurred by
|
||||
// feature code that needs to read the file.
|
||||
//
|
||||
// |max_bytes| specifies how many bytes should be pre-fetched. It may exceed the
|
||||
// file's size. Passing in std::numeric_limits<int64_t>::max() is a convenient
|
||||
// way to get the entire file pre-fetched.
|
||||
//
|
||||
// |is_executable| specifies whether the file is to be prefetched as
|
||||
// executable code or as data. Windows treats the file backed pages in RAM
|
||||
// differently, and specifying the wrong value results in two copies in RAM.
|
||||
//
|
||||
// Returns false if prefetching definitely failed. A return value of true does
|
||||
// not guarantee that the entire desired range was prefetched.
|
||||
//
|
||||
// Calling this before using ::LoadLibrary() on Windows is more efficient memory
|
||||
// wise, but we must be sure no other threads try to LoadLibrary() the file
|
||||
// while we are doing the mapping and prefetching, or the process will get a
|
||||
// private copy of the DLL via COW.
|
||||
BASE_EXPORT bool PreReadFile(
|
||||
const FilePath& file_path,
|
||||
bool is_executable,
|
||||
int64_t max_bytes = std::numeric_limits<int64_t>::max());
|
||||
|
||||
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
|
||||
|
||||
// Creates a pipe. Returns true on success, otherwise false.
|
||||
@ -518,6 +544,9 @@ BASE_EXPORT bool CopyAndDeleteDirectory(const FilePath& from_path,
|
||||
const FilePath& to_path);
|
||||
#endif // defined(OS_WIN)
|
||||
|
||||
// Used by PreReadFile() when no kernel support for prefetching is available.
|
||||
bool PreReadFileSlow(const FilePath& file_path, int64_t max_bytes);
|
||||
|
||||
} // namespace internal
|
||||
} // namespace base
|
||||
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include "base/files/scoped_file.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/memory/singleton.h"
|
||||
#include "base/numerics/safe_conversions.h"
|
||||
#include "base/path_service.h"
|
||||
#include "base/posix/eintr_wrapper.h"
|
||||
#include "base/stl_util.h"
|
||||
@ -1120,6 +1121,34 @@ bool CopyFile(const FilePath& from_path, const FilePath& to_path) {
|
||||
}
|
||||
#endif // !defined(OS_MACOSX)
|
||||
|
||||
bool PreReadFile(const FilePath& file_path,
|
||||
bool is_executable,
|
||||
int64_t max_bytes) {
|
||||
DCHECK_GE(max_bytes, 0);
|
||||
|
||||
// ChromeOS is also covered by OS_LINUX.
|
||||
// posix_fadvise() is only available in the Android NDK in API 21+. Older
|
||||
// versions may have the required kernel support, but don't have enough usage
|
||||
// to justify backporting.
|
||||
#if defined(OS_LINUX) || (defined(OS_ANDROID) && __ANDROID_API__ >= 21)
|
||||
File file(file_path, File::FLAG_OPEN | File::FLAG_READ);
|
||||
if (!file.IsValid())
|
||||
return false;
|
||||
|
||||
if (max_bytes == 0) {
|
||||
// fadvise() pre-fetches the entire file when given a zero length.
|
||||
return true;
|
||||
}
|
||||
|
||||
const PlatformFile fd = file.GetPlatformFile();
|
||||
const ::off_t len = base::saturated_cast<::off_t>(max_bytes);
|
||||
return posix_fadvise(fd, /*offset=*/0, len, POSIX_FADV_WILLNEED) == 0;
|
||||
#else
|
||||
// TODO(pwnall): Fall back to madvise() for macOS.
|
||||
return internal::PreReadFileSlow(file_path, max_bytes);
|
||||
#endif // defined(OS_LINUX) || (defined(OS_ANDROID) && __ANDROID_API__ >= 21)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
namespace internal {
|
||||
|
@ -3927,6 +3927,65 @@ TEST_F(FileUtilTest, GetUniquePathNumberTooManyFiles) {
|
||||
// Verify that the limit has been reached.
|
||||
EXPECT_EQ(GetUniquePathNumber(some_file), -1);
|
||||
}
|
||||
|
||||
TEST_F(FileUtilTest, PreReadFile_ExistingFile_NoSize) {
|
||||
FilePath text_file = temp_dir_.GetPath().Append(FPL("text_file"));
|
||||
CreateTextFile(text_file, bogus_content);
|
||||
|
||||
EXPECT_TRUE(PreReadFile(text_file, /*is_executable=*/false));
|
||||
}
|
||||
|
||||
TEST_F(FileUtilTest, PreReadFile_ExistingFile_ExactSize) {
|
||||
FilePath text_file = temp_dir_.GetPath().Append(FPL("text_file"));
|
||||
CreateTextFile(text_file, bogus_content);
|
||||
|
||||
EXPECT_TRUE(PreReadFile(text_file, /*is_executable=*/false,
|
||||
base::size(bogus_content)));
|
||||
}
|
||||
|
||||
TEST_F(FileUtilTest, PreReadFile_ExistingFile_OverSized) {
|
||||
FilePath text_file = temp_dir_.GetPath().Append(FPL("text_file"));
|
||||
CreateTextFile(text_file, bogus_content);
|
||||
|
||||
EXPECT_TRUE(PreReadFile(text_file, /*is_executable=*/false,
|
||||
base::size(bogus_content) * 2));
|
||||
}
|
||||
|
||||
TEST_F(FileUtilTest, PreReadFile_ExistingFile_UnderSized) {
|
||||
FilePath text_file = temp_dir_.GetPath().Append(FPL("text_file"));
|
||||
CreateTextFile(text_file, bogus_content);
|
||||
|
||||
EXPECT_TRUE(PreReadFile(text_file, /*is_executable=*/false,
|
||||
base::size(bogus_content) / 2));
|
||||
}
|
||||
|
||||
TEST_F(FileUtilTest, PreReadFile_ExistingFile_ZeroSize) {
|
||||
FilePath text_file = temp_dir_.GetPath().Append(FPL("text_file"));
|
||||
CreateTextFile(text_file, bogus_content);
|
||||
|
||||
EXPECT_TRUE(PreReadFile(text_file, /*is_executable=*/false, /*max_bytes=*/0));
|
||||
}
|
||||
|
||||
TEST_F(FileUtilTest, PreReadFile_ExistingEmptyFile_NoSize) {
|
||||
FilePath text_file = temp_dir_.GetPath().Append(FPL("text_file"));
|
||||
CreateTextFile(text_file, L"");
|
||||
// The test just asserts that this doesn't crash. The Windows implementation
|
||||
// fails in this case, due to the base::MemoryMappedFile implementation and
|
||||
// the limitations of ::MapViewOfFile().
|
||||
PreReadFile(text_file, /*is_executable=*/false);
|
||||
}
|
||||
|
||||
TEST_F(FileUtilTest, PreReadFile_ExistingEmptyFile_ZeroSize) {
|
||||
FilePath text_file = temp_dir_.GetPath().Append(FPL("text_file"));
|
||||
CreateTextFile(text_file, L"");
|
||||
EXPECT_TRUE(PreReadFile(text_file, /*is_executable=*/false, /*max_bytes=*/0));
|
||||
}
|
||||
|
||||
TEST_F(FileUtilTest, PreReadFile_InexistentFile) {
|
||||
FilePath inexistent_file = temp_dir_.GetPath().Append(FPL("inexistent_file"));
|
||||
EXPECT_FALSE(PreReadFile(inexistent_file, /*is_executable=*/false));
|
||||
}
|
||||
|
||||
#endif // !defined(OS_NACL_NONSFI)
|
||||
|
||||
// Test that temp files obtained racily are all unique (no interference between
|
||||
|
@ -20,9 +20,11 @@
|
||||
|
||||
#include "base/files/file_enumerator.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/files/memory_mapped_file.h"
|
||||
#include "base/guid.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/metrics/histogram_functions.h"
|
||||
#include "base/numerics/safe_conversions.h"
|
||||
#include "base/process/process_handle.h"
|
||||
#include "base/rand_util.h"
|
||||
#include "base/stl_util.h"
|
||||
@ -927,6 +929,60 @@ bool SetNonBlocking(int fd) {
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// ::PrefetchVirtualMemory() is only available on Windows 8 and above. Chrome
|
||||
// supports Windows 7, so we need to check for the function's presence
|
||||
// dynamically.
|
||||
using PrefetchVirtualMemoryPtr = decltype(&::PrefetchVirtualMemory);
|
||||
|
||||
// Returns null if ::PrefetchVirtualMemory() is not available.
|
||||
PrefetchVirtualMemoryPtr GetPrefetchVirtualMemoryPtr() {
|
||||
HMODULE kernel32_dll = ::GetModuleHandleA("kernel32.dll");
|
||||
return reinterpret_cast<decltype(&::PrefetchVirtualMemory)>(
|
||||
GetProcAddress(kernel32_dll, "PrefetchVirtualMemory"));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool 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);
|
||||
|
||||
if (max_bytes == 0) {
|
||||
// PrefetchVirtualMemory() fails when asked to read zero bytes.
|
||||
// base::MemoryMappedFile::Initialize() fails on an empty file.
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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 false;
|
||||
|
||||
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};
|
||||
return (*prefetch_virtual_memory)(::GetCurrentProcess(),
|
||||
/*NumberOfEntries=*/1, &address_range,
|
||||
/*Flags=*/0);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
namespace internal {
|
||||
|
@ -1,76 +0,0 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/win/file_pre_reader.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <memoryapi.h> // NOLINT(build/include_order)
|
||||
|
||||
#include "base/files/file.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/files/memory_mapped_file.h"
|
||||
|
||||
namespace base {
|
||||
namespace win {
|
||||
|
||||
void PreReadFile(const FilePath& file_path, bool is_executable) {
|
||||
// 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. On Win7 just do a
|
||||
// simple file read as data.
|
||||
|
||||
// ::PrefetchVirtualMemory() isn't available on Win7.
|
||||
HMODULE kernel32_library = GetModuleHandleA("kernel32.dll");
|
||||
|
||||
auto prefetch_virtual_memory =
|
||||
reinterpret_cast<decltype(&::PrefetchVirtualMemory)>(
|
||||
GetProcAddress(kernel32_library, "PrefetchVirtualMemory"));
|
||||
|
||||
if (!prefetch_virtual_memory) {
|
||||
// On Win7 read in the file as data since the OS doesn't have
|
||||
// the support for better options.
|
||||
|
||||
constexpr DWORD kStepSize = 1024 * 1024;
|
||||
|
||||
File file(file_path,
|
||||
File::FLAG_OPEN | File::FLAG_READ | File::FLAG_SEQUENTIAL_SCAN);
|
||||
if (!file.IsValid())
|
||||
return;
|
||||
|
||||
char* buffer = reinterpret_cast<char*>(
|
||||
::VirtualAlloc(nullptr, kStepSize, MEM_COMMIT, PAGE_READWRITE));
|
||||
if (!buffer)
|
||||
return;
|
||||
|
||||
while (file.ReadAtCurrentPos(buffer, kStepSize) > 0) {
|
||||
}
|
||||
|
||||
::VirtualFree(buffer, 0, MEM_RELEASE);
|
||||
} else {
|
||||
// NB: Creating the file mapping before the ::LoadLibrary() of the file is
|
||||
// more efficient memory wise, but we must be sure no other threads try to
|
||||
// loadlibrary the file while we are doing the mapping and prefetching or
|
||||
// the process will get a private copy of the DLL via COW.
|
||||
|
||||
MemoryMappedFile mapped_file;
|
||||
MemoryMappedFile::Access access = is_executable
|
||||
? MemoryMappedFile::READ_CODE_IMAGE
|
||||
: MemoryMappedFile::READ_ONLY;
|
||||
|
||||
if (mapped_file.Initialize(file_path, access)) {
|
||||
_WIN32_MEMORY_RANGE_ENTRY address_range = {mapped_file.data(),
|
||||
mapped_file.length()};
|
||||
|
||||
// NB: PrefetchVirtualMemory requires the file to be opened with
|
||||
// only read access or it will fail.
|
||||
|
||||
(*prefetch_virtual_memory)(GetCurrentProcess(), 1, &address_range, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace win
|
||||
} // namespace base
|
@ -1,28 +0,0 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// This file defines a function to pre-read a file in order to avoid touching
|
||||
// the disk when it is subsequently used.
|
||||
|
||||
#include "base/base_export.h"
|
||||
|
||||
#ifndef BASE_WIN_FILE_PRE_READER_H_
|
||||
#define BASE_WIN_FILE_PRE_READER_H_
|
||||
|
||||
namespace base {
|
||||
|
||||
class FilePath;
|
||||
|
||||
namespace win {
|
||||
|
||||
// Pre-reads |file_path| to avoid touching the disk when the file is actually
|
||||
// used. |is_executable| specifies whether the file is to be prefetched as
|
||||
// executable code or as data. Windows treats the file backed pages in RAM
|
||||
// differently and specifying the wrong one results in two copies in RAM.
|
||||
BASE_EXPORT void PreReadFile(const FilePath& file_path, bool is_executable);
|
||||
|
||||
} // namespace win
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_WIN_FILE_PRE_READER_H_
|
@ -18,6 +18,7 @@
|
||||
#include "base/compiler_specific.h"
|
||||
#include "base/files/file.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/lazy_instance.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/macros.h"
|
||||
@ -27,7 +28,6 @@
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "base/trace_event/trace_event.h"
|
||||
#include "base/win/file_pre_reader.h"
|
||||
#include "base/win/scoped_handle.h"
|
||||
#include "base/win/shlwapi.h"
|
||||
#include "base/win/windows_version.h"
|
||||
@ -57,7 +57,7 @@ typedef void (*RelaunchChromeBrowserWithNewCommandLineIfNeededFunc)();
|
||||
// reference to the loaded module on success, or null on error.
|
||||
HMODULE LoadModuleWithDirectory(const base::FilePath& module) {
|
||||
::SetCurrentDirectoryW(module.DirName().value().c_str());
|
||||
base::win::PreReadFile(module, true);
|
||||
base::PreReadFile(module, /*is_executable=*/true);
|
||||
return ::LoadLibraryExW(module.value().c_str(), nullptr,
|
||||
LOAD_WITH_ALTERED_SEARCH_PATH);
|
||||
}
|
||||
|
@ -36,10 +36,6 @@
|
||||
#include "sql/vfs_wrapper.h"
|
||||
#include "third_party/sqlite/sqlite3.h"
|
||||
|
||||
#if defined(OS_WIN)
|
||||
#include "base/win/file_pre_reader.h"
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
// Spin for up to a second waiting for the lock to clear when setting
|
||||
@ -348,9 +344,6 @@ void Database::Close() {
|
||||
}
|
||||
|
||||
void Database::Preload() {
|
||||
base::Optional<base::ScopedBlockingCall> scoped_blocking_call;
|
||||
InitScopedBlockingCall(&scoped_blocking_call);
|
||||
|
||||
if (base::FeatureList::IsEnabled(features::kSqlSkipPreload))
|
||||
return;
|
||||
|
||||
@ -359,9 +352,9 @@ void Database::Preload() {
|
||||
return;
|
||||
}
|
||||
|
||||
#if defined(OS_WIN)
|
||||
base::win::PreReadFile(DbPath(), false);
|
||||
#else
|
||||
base::Optional<base::ScopedBlockingCall> scoped_blocking_call;
|
||||
InitScopedBlockingCall(&scoped_blocking_call);
|
||||
|
||||
// The constructor and set_page_size() ensure that page_size_ is never zero.
|
||||
const int page_size = page_size_;
|
||||
DCHECK(page_size);
|
||||
@ -372,25 +365,7 @@ void Database::Preload() {
|
||||
if (preload_size < 1)
|
||||
return;
|
||||
|
||||
sqlite3_file* file = nullptr;
|
||||
sqlite3_int64 file_size = 0;
|
||||
int rc = GetSqlite3FileAndSize(db_, &file, &file_size);
|
||||
if (rc != SQLITE_OK)
|
||||
return;
|
||||
|
||||
// Don't preload more than the file contains.
|
||||
if (preload_size > file_size)
|
||||
preload_size = file_size;
|
||||
|
||||
std::unique_ptr<char[]> buf(new char[page_size]);
|
||||
for (sqlite3_int64 pos = 0; pos < preload_size; pos += page_size) {
|
||||
rc = file->pMethods->xRead(file, buf.get(), page_size, pos);
|
||||
|
||||
// TODO(shess): Consider calling OnSqliteError().
|
||||
if (rc != SQLITE_OK)
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
base::PreReadFile(DbPath(), /*is_executable=*/false, preload_size);
|
||||
}
|
||||
|
||||
// SQLite keeps unused pages associated with a database in a cache. It asks
|
||||
|
Reference in New Issue
Block a user