Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions benchmarks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ to only run some of the benchmarks:
./build/benchmarks/all/reflect-cpp-all-format-benchmarks --benchmark_filter=canada_read
```

The `BM_load_file` cases measure the shared binary and text file loaders with
1 KiB, 1 MiB, and 32 MiB inputs. Fixture creation and content checks happen before
timing; timed work includes opening, reading, closing, and destroying the result.
The files are freshly written and read once for validation, so these measure
loading with a warm filesystem cache, not cold-storage throughput.

```
./build/benchmarks/all/reflect-cpp-all-format-benchmarks --benchmark_filter=BM_load_file
```

## Results summary

### How reflect-cpp compares to other JSON libraries
Expand Down
85 changes: 85 additions & 0 deletions benchmarks/all/file_io.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#include <benchmark/benchmark.h>

#include <chrono>
#include <filesystem>
#include <fstream>
#include <rfl/io/load_bytes.hpp>
#include <rfl/io/load_string.hpp>
#include <stdexcept>
#include <string>

namespace file_io {

class InputFile {
public:
explicit InputFile(std::size_t size) {
const auto stamp =
std::chrono::steady_clock::now().time_since_epoch().count();
for (unsigned i = 0; i < 100; ++i) {
dir_ = std::filesystem::temp_directory_path() /
("reflectcpp-io-bench-" + std::to_string(stamp) + "-" +
std::to_string(i));
if (std::filesystem::create_directory(dir_)) {
name_ = (dir_ / "input").string();
std::ofstream out;
out.exceptions(std::ios::badbit | std::ios::failbit);
out.open(name_, std::ios::binary);
const std::string data(size, 'x');
out.write(data.data(), static_cast<std::streamsize>(data.size()));
out.close();
return;
}
}
throw std::runtime_error("Cannot create benchmark directory");
}
~InputFile() {
std::error_code ec;
std::filesystem::remove_all(dir_, ec);
}
const std::string& name() const { return name_; }

private:
std::filesystem::path dir_;
std::string name_;
};

template <bool Binary>
void BM_load_file(benchmark::State& state) {
const auto size = static_cast<std::size_t>(state.range(0));
const InputFile file(size);
const auto load = [&] {
if constexpr (Binary)
return rfl::io::load_bytes(file.name());
else
return rfl::io::load_string(file.name());
};
{
const auto result = load();
if (!result || result->size() != size) {
state.SkipWithError("File loading failed");
return;
}
for (const char c : *result) {
if (c != 'x') {
state.SkipWithError("File contents changed");
return;
}
}
}
for (auto _ : state) {
auto result = load();
benchmark::DoNotOptimize(result);
}
state.SetBytesProcessed(state.iterations() * state.range(0));
}

BENCHMARK_TEMPLATE(BM_load_file, true)
->Arg(1024)
->Arg(1024 * 1024)
->Arg(32 * 1024 * 1024);
BENCHMARK_TEMPLATE(BM_load_file, false)
->Arg(1024)
->Arg(1024 * 1024)
->Arg(32 * 1024 * 1024);

} // namespace file_io
13 changes: 2 additions & 11 deletions include/rfl/io/load_bytes.hpp
Original file line number Diff line number Diff line change
@@ -1,26 +1,17 @@
#ifndef RFL_IO_LOAD_BYTES_HPP_
#define RFL_IO_LOAD_BYTES_HPP_

#include <fstream>
#include <iostream>
#include <string>
#include <vector>

#include "../Result.hpp"
#include "load_file.hpp"

namespace rfl {
namespace io {

inline Result<std::vector<char>> load_bytes(const std::string& _fname) {
std::ifstream input(_fname, std::ios::binary);
if (input.is_open()) {
std::istreambuf_iterator<char> begin(input), end;
const auto bytes = std::vector<char>(begin, end);
input.close();
return bytes;
} else {
return error("File '" + _fname + "' not found!");
}
return internal::load_file<std::vector<char>>(_fname, std::ios::binary);
}

} // namespace io
Expand Down
66 changes: 66 additions & 0 deletions include/rfl/io/load_file.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#ifndef RFL_IO_LOAD_FILE_HPP_
#define RFL_IO_LOAD_FILE_HPP_

#include <array>
#include <cstdint>
#include <exception>
#include <filesystem>
#include <fstream>
#include <limits>
#include <string>
#include <system_error>

#include "../Result.hpp"

namespace rfl::io::internal {

template <class Container>
Result<Container> load_file(const std::string& _fname,
const std::ios::openmode _mode) {
try {
std::ifstream input(_fname, _mode);
if (!input.is_open()) {
return error("Unable to open file '" + _fname + "' for reading.");
}
Container bytes;
std::error_code ec;
const auto size = std::filesystem::file_size(_fname, ec);
if (!ec) {
if (size > bytes.max_size() ||
size > static_cast<std::uintmax_t>(
(std::numeric_limits<std::streamsize>::max)())) {
return error("File '" + _fname + "' is too large to read.");
}
bytes.resize(static_cast<std::size_t>(size));
if (!bytes.empty()) {
input.read(bytes.data(), static_cast<std::streamsize>(bytes.size()));
bytes.resize(static_cast<std::size_t>(input.gcount()));
if (input.bad() || (input.fail() && !input.eof())) {
return error("Unable to read file '" + _fname + "'.");
}
if (input.eof()) {
return bytes;
}
}
} else if (std::filesystem::is_directory(_fname, ec)) {
return error("Cannot read directory '" + _fname + "' as a file.");
}
// The size is only a hint: text conversion, growing files, and special
// files can produce a different number of bytes. Never require seeking.
std::array<char, 64 * 1024> buffer;
do {
input.read(buffer.data(), static_cast<std::streamsize>(buffer.size()));
if (input.bad() || (input.fail() && !input.eof())) {
return error("Unable to read file '" + _fname + "'.");
}
bytes.insert(bytes.end(), buffer.data(), buffer.data() + input.gcount());
} while (!input.eof());
return bytes;
} catch (const std::exception& e) {
return error("Unable to read file '" + _fname + "': " + e.what());
}
}

} // namespace rfl::io::internal

#endif
13 changes: 2 additions & 11 deletions include/rfl/io/load_string.hpp
Original file line number Diff line number Diff line change
@@ -1,25 +1,16 @@
#ifndef RFL_IO_LOAD_STRING_HPP_
#define RFL_IO_LOAD_STRING_HPP_

#include <fstream>
#include <string>

#include "../Result.hpp"
#include "load_file.hpp"

namespace rfl {
namespace io {

inline Result<std::string> load_string(const std::string& _fname) {
std::ifstream infile(_fname);
if (infile.is_open()) {
auto r = std::string(std::istreambuf_iterator<char>(infile),
std::istreambuf_iterator<char>());
infile.close();
return r;
} else {
return error("Unable to open file '" + _fname +
"' or file could not be found.");
}
return internal::load_file<std::string>(_fname, std::ios::in);
}

} // namespace io
Expand Down
6 changes: 4 additions & 2 deletions include/rfl/io/save_bytes.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
#define RFL_IO_SAVE_BYTES_HPP_

#include <fstream>
#include <iostream>
#include <string>

#include "../Result.hpp"
Expand All @@ -20,7 +19,10 @@ Result<Nothing> save_bytes(const std::string& _fname, const T& _obj,
}
_write(_obj, output);
output.close();
} catch (std::exception& e) {
if (!output) {
return error("Unable to write or close file '" + _fname + "'.");
}
} catch (const std::exception& e) {
return error(e.what());
}
return Nothing{};
Expand Down
5 changes: 4 additions & 1 deletion include/rfl/io/save_string.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ Result<Nothing> save_string(const std::string& _fname, const T& _obj,
}
_write(_obj, outfile);
outfile.close();
} catch (std::exception& e) {
if (!outfile) {
return error("Unable to write or close file '" + _fname + "'.");
}
} catch (const std::exception& e) {
return error(e.what());
}
return Nothing{};
Expand Down
Loading
Loading