0

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:
Victor Costan
2019-09-08 04:55:15 +00:00
committed by Commit Bot
parent 0f6362a378
commit b5a0a9700e
10 changed files with 219 additions and 137 deletions

@ -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