mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-03 07:05:49 +01:00
The standard filesystem API lacks significant functionality, ranging from correct and secure creation of directories and files within them by using `openat` and avoiding [TOCTOU] issues, to support for filesystem locking. [TOCTOU]: https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use The LLVM filesystem library has more functionality, but uses an API that is increasingly diverging from the standard, and also fails to defend against TOCTOU. This library is designed to carefully model the Unix or POSIX filesystem concepts of `openat` to avoid TOCTOU. However, it also tries to limit itself to an API subset that LLVM's filesystem library has also implemneted and so we have a strong reason to expect to be possible to port to Windows reasonably. This PR included several benchmarks that show that this implementation is also faster for the majority of operations than the C++ standard library. The only places where there is a consistent regression is in recursively creating directories, and this is directly connected to the approach of using `openat` as the basis. Even there, while the wall time regresses, the cycles and instructions are significantly improved. There are a number of operations not yet included here, I've focused on a core set of opening, closing, creating, and removing, and then adding those that I saw the current toolchain code using actively. I'll plan to expand the operations as needed going forward. A follow-up PR that I'll finish polishing and send next ports `//toolchain/install` to consistently use this library and `std::filesystem::path` to both exercise the library and showcase its use. I'll be working systematically across the toolchain to converge all the code, extending this library as needed. For reference, benchmark results on my macOS laptop: https://gist.github.com/chandlerc/29d1f4d465a835b8be5174a48dad2e8f Benchmark results on a Asahi Linux M1 Mac Mini: https://gist.github.com/chandlerc/c42d43dd6b9b91746ab314b2afa152f7 Benchmark results on a Linux server with weirdly slow FS operations: https://gist.github.com/chandlerc/48301a7383eb3972d53351b7e35e0561 --------- Co-authored-by: Dana Jansens <danakj@orodu.net>
545 lines
20 KiB
C++
545 lines
20 KiB
C++
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
|
// Exceptions. See /LICENSE for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
|
|
#include <benchmark/benchmark.h>
|
|
|
|
#include <fstream>
|
|
#include <system_error>
|
|
|
|
#include "absl/random/random.h"
|
|
#include "common/filesystem.h"
|
|
#include "llvm/ADT/Sequence.h"
|
|
#include "llvm/ADT/StringExtras.h"
|
|
|
|
namespace Carbon::Filesystem {
|
|
namespace {
|
|
|
|
// Alternative implementation strategies to allow comparing performance.
|
|
//
|
|
// WHen implementing benchmarks below, we try to make them templates on this
|
|
// enum and then switch in the body between different implementations. This
|
|
// allows us to share the framework of each benchmark but select different
|
|
// implementations for different instantiations. The different instantiations
|
|
// get these enumerators in their names in the output, so we keep them short.
|
|
enum BenchmarkComparables {
|
|
Carbon,
|
|
Std,
|
|
};
|
|
|
|
// Filler text.
|
|
constexpr llvm::StringLiteral Text =
|
|
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
|
|
"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim "
|
|
"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
|
|
"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate "
|
|
"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint "
|
|
"occaecat cupidatat non proident, sunt in culpa qui officia deserunt "
|
|
"mollit anim id est laborum.";
|
|
|
|
// Gets the filler text repeated up to a specific length.
|
|
static auto GetText(int length) -> std::string {
|
|
std::string content;
|
|
content.reserve(length);
|
|
while (static_cast<int>(content.size()) < length) {
|
|
content += Text.substr(0, length - content.size());
|
|
}
|
|
CARBON_CHECK(static_cast<int>(content.size()) == length);
|
|
return content;
|
|
}
|
|
|
|
// We build a collection of file paths to use across different benchmarks in
|
|
// batches to avoid looking at the same file over and over again. We can even
|
|
// shuffle the file orders to further avoid hiding performance cost. If there
|
|
// are specific cases where we want to measure the cached / predicted speed, we
|
|
// can write those benchmarks against a specific file, but most often we instead
|
|
// look at the worst case scenario for wall-clock time and use cycle counters
|
|
// and instruction counters to measure aspects of the best case. The exact
|
|
// number here was chosen arbitrarily to not make running benchmarks excessively
|
|
// slow due to the large batches.
|
|
constexpr int NumFiles = 64;
|
|
|
|
// A common set of context used in benchmarks below. A separate context object
|
|
// works better than the benchmark fixture support in practice.
|
|
//
|
|
// This is a struct as there are no invariants or contracts enforced. This is
|
|
// just a container of commonly useful data and commonly useful helper
|
|
// functions.
|
|
struct BenchContext {
|
|
RemovingDir tmpdir;
|
|
absl::BitGen rng;
|
|
std::array<std::filesystem::path, NumFiles> file_paths;
|
|
std::array<std::filesystem::path, NumFiles> missing_paths;
|
|
|
|
BenchContext() : tmpdir(std::move(*MakeTmpDir())) {
|
|
for (int i : llvm::seq(NumFiles)) {
|
|
file_paths[i] = llvm::formatv("file_{0}", i).str();
|
|
auto result = tmpdir.WriteFileFromString(file_paths[i], Text);
|
|
CARBON_CHECK(result.ok(), "{0}", result.error());
|
|
missing_paths[i] = llvm::formatv("missing_{0}", i).str();
|
|
}
|
|
ShuffleFilePaths();
|
|
ShuffleMissingPaths();
|
|
}
|
|
|
|
auto ShuffleFilePaths() -> void {
|
|
std::shuffle(file_paths.begin(), file_paths.end(), rng);
|
|
}
|
|
|
|
auto ShuffleMissingPaths() -> void {
|
|
std::shuffle(missing_paths.begin(), missing_paths.end(), rng);
|
|
}
|
|
|
|
// Create a tree of files and directories starting from a `base` new directory
|
|
// in our tmp directory, and containing `entries` total entries with
|
|
// `entries_per_dir` in each directory. These will be a mixture of further
|
|
// subdirectories and files.
|
|
auto CreateTree(std::filesystem::path base, int entries, int entries_per_dir)
|
|
-> void {
|
|
CARBON_CHECK(entries >= 1);
|
|
CARBON_CHECK(entries_per_dir >= 1);
|
|
int num_subdirs = std::max<int>(entries_per_dir / 2, 1);
|
|
struct DirStackEntry {
|
|
Dir dir;
|
|
int num_entries;
|
|
int subdir_count;
|
|
};
|
|
llvm::SmallVector<DirStackEntry> dir_stack;
|
|
auto d = tmpdir.OpenDir(base, CreationOptions::CreateNew);
|
|
CARBON_CHECK(d.ok(), "{0}", d.error());
|
|
dir_stack.push_back({std::move(*d), entries, 0});
|
|
|
|
while (!dir_stack.empty()) {
|
|
auto& [dir, num_entries, subdir_count] = dir_stack.back();
|
|
|
|
// We want `num_entries` transitively in this directory, and
|
|
// `entries_per_dir` directly. Spread the remaining entries across
|
|
// `num_subdirs`.
|
|
int entries_per_subdir = ((num_entries - entries_per_dir) / num_subdirs);
|
|
CARBON_CHECK(entries_per_subdir < num_entries);
|
|
|
|
// While we'll still put entries in a subdirectory, and we still need more
|
|
// subdirectories in this directory, create another subdirectory, push it
|
|
// on the stack, and recurse to it by continuing.
|
|
if (entries_per_subdir >= entries_per_dir && subdir_count < num_subdirs) {
|
|
auto name = llvm::formatv("dir_{0}", subdir_count).str();
|
|
auto subdir = dir.OpenDir(name, CreationOptions::CreateNew);
|
|
CARBON_CHECK(subdir.ok(), "{0}", subdir.error());
|
|
++subdir_count;
|
|
|
|
// Note we have to continue after `push_back` as this will invalidate
|
|
// the current references.
|
|
dir_stack.push_back({std::move(*subdir), entries_per_subdir, 0});
|
|
continue;
|
|
}
|
|
|
|
// Otherwise, we're finished with subdirectories and just need to create
|
|
// direct files.
|
|
int num_files = entries_per_dir - subdir_count;
|
|
CARBON_CHECK(num_files >= 0);
|
|
for (int i = 0; i < num_files; ++i) {
|
|
auto name = llvm::formatv("file_{0}", i).str();
|
|
auto f = dir.OpenWriteOnly(name, CreationOptions::CreateNew);
|
|
CARBON_CHECK(f.ok(), "{0}", f.error());
|
|
auto close_result = std::move(*f).Close();
|
|
CARBON_CHECK(close_result.ok(), "{0}", close_result.error());
|
|
}
|
|
dir_stack.pop_back();
|
|
}
|
|
}
|
|
};
|
|
|
|
template <BenchmarkComparables Comp>
|
|
auto BM_Access(benchmark::State& state) -> void {
|
|
BenchContext context;
|
|
while (state.KeepRunningBatch(NumFiles)) {
|
|
for (int i : llvm::seq(NumFiles)) {
|
|
if constexpr (Comp == Carbon) {
|
|
auto result = context.tmpdir.Access(context.file_paths[i]);
|
|
CARBON_CHECK(result.ok(), "{0}", result.error());
|
|
} else if constexpr (Comp == Std) {
|
|
std::error_code ec;
|
|
bool exists = std::filesystem::exists(
|
|
context.tmpdir.abs_path() / context.file_paths[i], ec);
|
|
CARBON_CHECK(!ec, "{0}", ec.message());
|
|
CARBON_CHECK(exists);
|
|
} else {
|
|
static_assert(false, "Invalid benchmark comparable");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
BENCHMARK(BM_Access<Carbon>)->UseRealTime();
|
|
BENCHMARK(BM_Access<Std>)->UseRealTime();
|
|
|
|
template <BenchmarkComparables Comp>
|
|
auto BM_AccessMissing(benchmark::State& state) -> void {
|
|
BenchContext context;
|
|
while (state.KeepRunningBatch(NumFiles)) {
|
|
for (int i : llvm::seq(NumFiles)) {
|
|
if constexpr (Comp == Carbon) {
|
|
auto result = context.tmpdir.Access(context.missing_paths[i]);
|
|
CARBON_CHECK(result.error().no_entity());
|
|
} else if constexpr (Comp == Std) {
|
|
std::error_code ec;
|
|
auto exists = std::filesystem::exists(
|
|
context.tmpdir.abs_path() / context.missing_paths[i], ec);
|
|
CARBON_CHECK(!ec, "{0}", ec.message());
|
|
CARBON_CHECK(!exists);
|
|
} else {
|
|
static_assert(false, "Invalid benchmark comparable");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
BENCHMARK(BM_AccessMissing<Carbon>)->UseRealTime();
|
|
BENCHMARK(BM_AccessMissing<Std>)->UseRealTime();
|
|
|
|
template <BenchmarkComparables Comp>
|
|
auto BM_Stat(benchmark::State& state) -> void {
|
|
BenchContext context;
|
|
while (state.KeepRunningBatch(NumFiles)) {
|
|
for (int i : llvm::seq(NumFiles)) {
|
|
if constexpr (Comp == Carbon) {
|
|
auto status = context.tmpdir.Stat(context.file_paths[i]);
|
|
CARBON_CHECK(status.ok(), "{0}", status.error());
|
|
benchmark::DoNotOptimize(status->permissions());
|
|
} else if constexpr (Comp == Std) {
|
|
std::error_code ec;
|
|
auto status = std::filesystem::status(
|
|
context.tmpdir.abs_path() / context.file_paths[i], ec);
|
|
CARBON_CHECK(!ec, "{0}", ec.message());
|
|
benchmark::DoNotOptimize(status.permissions());
|
|
} else {
|
|
static_assert(false, "Invalid benchmark comparable");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
BENCHMARK(BM_Stat<Carbon>)->UseRealTime();
|
|
BENCHMARK(BM_Stat<Std>)->UseRealTime();
|
|
|
|
template <BenchmarkComparables Comp>
|
|
auto BM_StatMissing(benchmark::State& state) -> void {
|
|
BenchContext context;
|
|
while (state.KeepRunningBatch(NumFiles)) {
|
|
for (int i : llvm::seq(NumFiles)) {
|
|
if constexpr (Comp == Carbon) {
|
|
auto status = context.tmpdir.Stat(context.missing_paths[i]);
|
|
CARBON_CHECK(status.error().no_entity());
|
|
} else if constexpr (Comp == Std) {
|
|
std::error_code ec;
|
|
auto status = std::filesystem::status(
|
|
context.tmpdir.abs_path() / context.missing_paths[i], ec);
|
|
CARBON_CHECK(ec.value() == ENOENT, "{0}", ec.message());
|
|
} else {
|
|
static_assert(false, "Invalid benchmark comparable");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
BENCHMARK(BM_StatMissing<Carbon>)->UseRealTime();
|
|
BENCHMARK(BM_StatMissing<Std>)->UseRealTime();
|
|
|
|
template <BenchmarkComparables Comp>
|
|
auto BM_OpenMissing(benchmark::State& state) -> void {
|
|
BenchContext context;
|
|
while (state.KeepRunningBatch(NumFiles)) {
|
|
for (int i : llvm::seq(NumFiles)) {
|
|
if constexpr (Comp == Carbon) {
|
|
auto f = context.tmpdir.OpenReadOnly(context.missing_paths[i]);
|
|
CARBON_CHECK(f.error().no_entity());
|
|
} else if constexpr (Comp == Std) {
|
|
std::ifstream f(context.tmpdir.abs_path() / context.missing_paths[i]);
|
|
CARBON_CHECK(!f.is_open());
|
|
} else {
|
|
static_assert(false, "Invalid benchmark comparable");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
BENCHMARK(BM_OpenMissing<Carbon>)->UseRealTime();
|
|
BENCHMARK(BM_OpenMissing<Std>)->UseRealTime();
|
|
|
|
template <BenchmarkComparables Comp>
|
|
auto BM_OpenClose(benchmark::State& state) -> void {
|
|
BenchContext context;
|
|
while (state.KeepRunningBatch(NumFiles)) {
|
|
for (int i : llvm::seq(NumFiles)) {
|
|
if constexpr (Comp == Carbon) {
|
|
auto f = context.tmpdir.OpenReadOnly(context.file_paths[i]);
|
|
CARBON_CHECK(f.ok(), "{0}", f.error());
|
|
auto close_result = std::move(*f).Close();
|
|
CARBON_CHECK(close_result.ok(), "{0}", close_result.error());
|
|
} else if constexpr (Comp == Std) {
|
|
std::ifstream f(context.tmpdir.abs_path() / context.file_paths[i]);
|
|
CARBON_CHECK(f.is_open());
|
|
} else {
|
|
static_assert(false, "Invalid benchmark comparable");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
BENCHMARK(BM_OpenClose<Carbon>)->UseRealTime();
|
|
BENCHMARK(BM_OpenClose<Std>)->UseRealTime();
|
|
|
|
template <BenchmarkComparables Comp>
|
|
auto BM_CreateRemove(benchmark::State& state) -> void {
|
|
BenchContext context;
|
|
while (state.KeepRunningBatch(NumFiles)) {
|
|
for (int i : llvm::seq(NumFiles)) {
|
|
if constexpr (Comp == Carbon) {
|
|
// Create the file by opening it.
|
|
auto f = context.tmpdir.OpenWriteOnly(context.missing_paths[i],
|
|
CreationOptions::CreateNew);
|
|
CARBON_CHECK(f.ok(), "{0}", f.error());
|
|
// Close it right away.
|
|
auto close_result = std::move(*f).Close();
|
|
CARBON_CHECK(close_result.ok(), "{0}", close_result.error());
|
|
// Remove it.
|
|
auto remove_result = context.tmpdir.Unlink(context.missing_paths[i]);
|
|
CARBON_CHECK(remove_result.ok(), "{0}", remove_result.error());
|
|
} else if constexpr (Comp == Std) {
|
|
auto path = context.tmpdir.abs_path() / context.missing_paths[i];
|
|
// Create the file by opening it.
|
|
std::ofstream f(path);
|
|
CARBON_CHECK(f.is_open());
|
|
// Close it right away.
|
|
f.close();
|
|
// Remove it.
|
|
std::error_code ec;
|
|
std::filesystem::remove(path, ec);
|
|
CARBON_CHECK(!ec, "{0}", ec.message());
|
|
} else {
|
|
static_assert(false, "Invalid benchmark comparable");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
BENCHMARK(BM_CreateRemove<Carbon>)->UseRealTime();
|
|
BENCHMARK(BM_CreateRemove<Std>)->UseRealTime();
|
|
|
|
template <BenchmarkComparables Comp>
|
|
auto BM_Read(benchmark::State& state) -> void {
|
|
BenchContext context;
|
|
int length = state.range(0);
|
|
std::string content = GetText(length);
|
|
for (int i : llvm::seq(NumFiles)) {
|
|
auto result =
|
|
context.tmpdir.WriteFileFromString(context.file_paths[i], content);
|
|
CARBON_CHECK(result.ok(), "{0}", result.error());
|
|
}
|
|
while (state.KeepRunningBatch(NumFiles)) {
|
|
// Re-shuffle the order of the files for each batch to avoid exact cache
|
|
// hits.
|
|
state.PauseTiming();
|
|
context.ShuffleFilePaths();
|
|
state.ResumeTiming();
|
|
|
|
for (int i : llvm::seq(NumFiles)) {
|
|
if constexpr (Comp == Carbon) {
|
|
auto read_result =
|
|
context.tmpdir.ReadFileToString(context.file_paths[i]);
|
|
CARBON_CHECK(read_result.ok(), "{0}", read_result.error());
|
|
benchmark::DoNotOptimize(*read_result);
|
|
} else if constexpr (Comp == Std) {
|
|
std::ifstream f(context.tmpdir.abs_path() / context.file_paths[i],
|
|
std::ios::binary);
|
|
CARBON_CHECK(f.is_open());
|
|
// This may be a somewhat surprising implementation, but benchmarking
|
|
// against several other ways of reading the file with `std::ifstream`
|
|
// all have the same or worse performance.
|
|
std::string read_content((std::istreambuf_iterator<char>(f)),
|
|
(std::istreambuf_iterator<char>()));
|
|
benchmark::DoNotOptimize(read_content);
|
|
} else {
|
|
static_assert(false, "Invalid benchmark comparable");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
BENCHMARK(BM_Read<Carbon>)->Range(4, 1024LL * 1024)->UseRealTime();
|
|
BENCHMARK(BM_Read<Std>)->Range(4, 1024LL * 1024)->UseRealTime();
|
|
|
|
template <BenchmarkComparables Comp>
|
|
auto BM_Write(benchmark::State& state) -> void {
|
|
BenchContext context;
|
|
int length = state.range(0);
|
|
std::string content = GetText(length);
|
|
while (state.KeepRunningBatch(NumFiles)) {
|
|
// Re-shuffle the order of the files for each batch to avoid exact cache
|
|
// hits.
|
|
state.PauseTiming();
|
|
context.ShuffleFilePaths();
|
|
state.ResumeTiming();
|
|
|
|
for (int i : llvm::seq(NumFiles)) {
|
|
if constexpr (Comp == Carbon) {
|
|
auto write_result =
|
|
context.tmpdir.WriteFileFromString(context.file_paths[i], content);
|
|
CARBON_CHECK(write_result.ok(), "{0}", write_result.error());
|
|
} else if constexpr (Comp == Std) {
|
|
std::ofstream f(context.tmpdir.abs_path() / context.file_paths[i],
|
|
std::ios::binary | std::ios::trunc);
|
|
CARBON_CHECK(f.is_open());
|
|
f.write(content.data(), content.length());
|
|
} else {
|
|
static_assert(false, "Invalid benchmark comparable");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
BENCHMARK(BM_Write<Carbon>)->Range(4, 1024LL * 1024)->UseRealTime();
|
|
BENCHMARK(BM_Write<Std>)->Range(4, 1024LL * 1024)->UseRealTime();
|
|
|
|
template <BenchmarkComparables Comp>
|
|
auto BM_Rmtree(benchmark::State& state) -> void {
|
|
BenchContext context;
|
|
int entries = state.range(0);
|
|
int depth = state.range(1);
|
|
|
|
// Configure our batch size based on the number of entries. Creating large
|
|
// numbers of entries in the filesystem can cause problems, and is also very
|
|
// slow. We don't need that much accuracy once the trees get large.
|
|
int batch_size = entries <= 1024 ? 10 : entries <= (32 * 1024) ? 5 : 1;
|
|
|
|
while (state.KeepRunningBatch(batch_size)) {
|
|
state.PauseTiming();
|
|
for (int i : llvm::seq(batch_size)) {
|
|
context.CreateTree(llvm::formatv("tree_{0}", i).str(), entries, depth);
|
|
}
|
|
state.ResumeTiming();
|
|
|
|
for (int i : llvm::seq(batch_size)) {
|
|
std::string tree = llvm::formatv("tree_{0}", i).str();
|
|
if constexpr (Comp == Carbon) {
|
|
auto rmdir_result = context.tmpdir.Rmtree(tree);
|
|
CARBON_CHECK(rmdir_result.ok(), "{0}", rmdir_result.error());
|
|
} else if constexpr (Comp == Std) {
|
|
std::error_code ec;
|
|
std::filesystem::remove_all(context.tmpdir.abs_path() / tree, ec);
|
|
CARBON_CHECK(!ec, "{0}", ec.message());
|
|
} else {
|
|
static_assert(false, "Invalid benchmark comparable");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
BENCHMARK(BM_Rmtree<Carbon>)
|
|
->Ranges({{1, 256}, {1, 32}})
|
|
->Ranges({{2 * 1024, 256 * 1024}, {512, 1024}})
|
|
->Unit(benchmark::kMicrosecond)
|
|
->UseRealTime();
|
|
BENCHMARK(BM_Rmtree<Std>)
|
|
->Ranges({{1, 256}, {1, 32}})
|
|
->Ranges({{2 * 1024, 256 * 1024}, {512, 1024}})
|
|
->Unit(benchmark::kMicrosecond)
|
|
->UseRealTime();
|
|
|
|
template <BenchmarkComparables Comp>
|
|
auto BM_CreateDirectories(benchmark::State& state) -> void {
|
|
BenchContext context;
|
|
int depth = state.range(0);
|
|
int existing_depth = state.range(1);
|
|
CARBON_CHECK(existing_depth <= depth);
|
|
CARBON_CHECK(depth > 0);
|
|
|
|
// Use a batch size of 10 to get avoid completely swamping the measurements
|
|
// with overhead from creating existing directories and cleaning up.
|
|
constexpr int BatchSize = 10;
|
|
|
|
// Pre-build both the paths and the existing paths. Note that we use
|
|
// relatively short paths here, which if anything makes the benefits of the
|
|
// Carbon library smaller.
|
|
llvm::SmallVector<std::string> paths;
|
|
llvm::SmallVector<std::string> existing_paths;
|
|
for (int i : llvm::seq(BatchSize)) {
|
|
RawStringOstream path;
|
|
llvm::ListSeparator sep("/");
|
|
for (int j = 0; j < existing_depth; ++j) {
|
|
path << sep << "exists_" << (j == 0 ? i : j);
|
|
}
|
|
existing_paths.push_back(path.TakeStr());
|
|
path << existing_paths.back();
|
|
for (int k = existing_depth; k < depth; ++k) {
|
|
path << sep << "dir_" << (k == 0 ? i : k);
|
|
}
|
|
paths.push_back(path.TakeStr());
|
|
}
|
|
|
|
while (state.KeepRunningBatch(BatchSize)) {
|
|
state.PauseTiming();
|
|
for (int i : llvm::seq(BatchSize)) {
|
|
if (existing_depth > 0) {
|
|
auto result = context.tmpdir.CreateDirectories(existing_paths[i]);
|
|
CARBON_CHECK(result.ok(), "{0}", result.error());
|
|
}
|
|
}
|
|
state.ResumeTiming();
|
|
|
|
for (int i : llvm::seq(BatchSize)) {
|
|
if constexpr (Comp == Carbon) {
|
|
auto result = context.tmpdir.CreateDirectories(paths[i]);
|
|
CARBON_CHECK(result.ok(), "Failed to create '{0}': {1}", paths[i],
|
|
result.error());
|
|
|
|
// Create a file in the provided directory. This adds some baseline
|
|
// overhead but matches the realistic use case and ensures that there
|
|
// isn't some laziness that makes just creating a directory have an
|
|
// unusually low cost.
|
|
auto f = result->OpenWriteOnly("test", CreationOptions::CreateNew);
|
|
CARBON_CHECK(f.ok(), "{0}", f.error());
|
|
auto close_result = std::move(*f).Close();
|
|
CARBON_CHECK(close_result.ok(), "{0}", close_result.error());
|
|
} else if constexpr (Comp == Std) {
|
|
std::filesystem::path path = context.tmpdir.abs_path() / paths[i];
|
|
std::error_code ec;
|
|
std::filesystem::create_directories(path, ec);
|
|
CARBON_CHECK(!ec, "{0}", ec.message());
|
|
|
|
// Create a file in the directory, similar to above. This has a (much)
|
|
// bigger effect though because the C++ APIs don't open the created
|
|
// directory, and so the creation cost of it can very much be hidden
|
|
// from the benchmark if we don't use it. This also lets us see the
|
|
// benefit of not needing to re-walk the path to create the file.
|
|
std::ofstream f(path / "test");
|
|
CARBON_CHECK(f.is_open());
|
|
f.close();
|
|
} else {
|
|
static_assert(false, "Invalid benchmark comparable");
|
|
}
|
|
}
|
|
|
|
state.PauseTiming();
|
|
for (int i : llvm::seq(BatchSize)) {
|
|
auto result = context.tmpdir.Rmtree(
|
|
llvm::formatv("{0}_{1}", existing_depth > 0 ? "exists" : "dir", i)
|
|
.str());
|
|
CARBON_CHECK(result.ok(), "{0}", result.error());
|
|
}
|
|
state.ResumeTiming();
|
|
}
|
|
}
|
|
static auto CreateDirectoriesBenchArgs(benchmark::internal::Benchmark* b) {
|
|
// The first argument is the depth of directory to create. We mostly care
|
|
// about reasonably small depths here. It must be >= 1 for there to be
|
|
// something to benchmark. The second number is the depth of pre-existing
|
|
// directories which can vary from 0 to equal to the depth to benchmark the
|
|
// case of no new directory being needed.
|
|
for (int i = 1; i <= 8; i *= 2) {
|
|
b->Args({i, 0});
|
|
for (int j = 1; j <= i; j *= 2) {
|
|
b->Args({i, j});
|
|
}
|
|
}
|
|
}
|
|
BENCHMARK(BM_CreateDirectories<Carbon>)
|
|
->Apply(CreateDirectoriesBenchArgs)
|
|
->UseRealTime();
|
|
BENCHMARK(BM_CreateDirectories<Std>)
|
|
->Apply(CreateDirectoriesBenchArgs)
|
|
->UseRealTime();
|
|
|
|
} // namespace
|
|
} // namespace Carbon::Filesystem
|