Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Homework: make PMR permeate the nlohmann JSON parser #446

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
35 changes: 35 additions & 0 deletions PMR/homework/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
cmake_minimum_required(VERSION 3.29)
project(pmr_homework_guoci)

message("Building with CMake version: ${CMAKE_VERSION}")

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_executable(bm main.cpp)

include(FetchContent)
FetchContent_Declare(googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG main)
FetchContent_MakeAvailable(googletest)

FetchContent_Declare(benchmark
GIT_REPOSITORY https://github.com/google/benchmark.git
GIT_TAG main)
FetchContent_MakeAvailable(benchmark)

FetchContent_Declare(nlohmann_json
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG master)
FetchContent_MakeAvailable(nlohmann_json)

find_package(benchmark REQUIRED)

target_link_libraries(bm PRIVATE benchmark::benchmark nlohmann_json::nlohmann_json)

target_compile_options(bm PRIVATE
-Wall -Wextra -Wshadow -Wconversion -pedantic -Wstrict-overflow
-Wnrvo
-Wuninitialized
)
69 changes: 69 additions & 0 deletions PMR/homework/allocator.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#pragma once

#include <memory_resource>
#include <string>

namespace gc {
template<class Tag/* to generate a new type to use a different memory_resource */>
inline thread_local typename Tag::memory_resource_type *mr = nullptr;

template<class T, class Tag/* to generate a new type to use a different memory_resource */>
class allocator {
public:
using value_type = T;
using propagate_on_container_move_assignment = std::true_type;
using is_always_equal = std::true_type;

allocator() = default;

template<class U>
constexpr explicit allocator(const allocator<U, Tag> &) noexcept {
}

[[nodiscard]] T *allocate(size_t n);

void deallocate(T *p, size_t n);
};

template<class T, class U, class Tag>
[[nodiscard]] constexpr bool
operator==(allocator<T, Tag> const & /*unused*/, allocator<U, Tag> const & /*unused*/) noexcept {
return true;
}

template<class T, class Tag>
T *allocator<T, Tag>::allocate(const size_t n) {
return static_cast<T *>(mr<Tag>->allocate(n * sizeof(T), alignof(T)));
}

template<class T, class Tag>
void allocator<T, Tag>::deallocate(T *p, const size_t n) {
mr<Tag>->deallocate(p, n * sizeof(T), alignof(T));
}



template<
class Tag,
class CharT,
class Traits = std::char_traits<CharT> >
using basic_string =
std::basic_string<CharT, Traits, gc::allocator<CharT, Tag> >;

template<class Tag>
using string = basic_string<Tag, char>;


struct monotonic_buffer_resource_devirt final : std::pmr::monotonic_buffer_resource {
// this class is needed to help the compiler to devirtualize the calls
using monotonic_buffer_resource::monotonic_buffer_resource;

void *allocate(size_t bytes, size_t align) {
return monotonic_buffer_resource::do_allocate(bytes, align);
}

void deallocate(void *p, std::size_t bytes, std::size_t alignment) {
monotonic_buffer_resource::do_deallocate(p, bytes, alignment);
};
};
}
113 changes: 113 additions & 0 deletions PMR/homework/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#include "allocator.hpp"

#include <benchmark/benchmark.h>
#include <memory_resource>
#include "nlohmann/json.hpp"



std::string load_file(const std::string &path) {
// std::cerr << std::filesystem::current_path() << std::endl;
FILE *f = fopen(path.c_str(), "rb");
fseek(f, 0, SEEK_END);
auto const size = ftell(f);
std::string s;
s.resize(static_cast<std::size_t>(size));
fseek(f, 0, SEEK_SET);
auto const nread = fread(&s[0], 1, static_cast<std::size_t>(size), f);
s.resize(nread);
fclose(f);
return s;
}

/*
// Your homework: get this compiling and working so that PMR permeates the json parser when desired
static void nlohmann_JSON_PMR(benchmark::State &state)
{
using pmr_json = nlohmann::basic_json<std::map, std::vector, std::pmr::string, bool, std::int64_t, std::uint64_t, double, std::pmr::polymorphic_allocator>;
auto s = load_file("citm_catalog.json");
for (auto _ : state) {
auto jv = pmr_json::parse(s.begin(), s.end());
}
}
BENCHMARK(nlohmann_JSON_PMR);
*/


struct monotonic {
using memory_resource_type = std::pmr::monotonic_buffer_resource;
};

template<class T>
using allocator_monotonic = gc::allocator<T, monotonic>;

static void nlohmann_JSON_MBR_stateless_alloc(benchmark::State &state, std::string_view s) {
constexpr size_t bufsize = 1 << 25;
thread_local auto buf = std::make_unique_for_overwrite<char[]>(bufsize);
thread_local std::pmr::monotonic_buffer_resource mbr{buf.get(), bufsize, std::pmr::null_memory_resource()};

using pmr_json = nlohmann::basic_json<std::map, std::vector, gc::string<monotonic>, bool, std::int64_t,
std::uint64_t,
double,
allocator_monotonic>;
gc::mr<monotonic> = &mbr;
for (auto _: state) {
mbr.release();
auto jv = pmr_json::parse(s.begin(), s.end());
benchmark::DoNotOptimize(jv);
}
}

struct monotonic_devirt {
using memory_resource_type = gc::monotonic_buffer_resource_devirt;
};

template<class T>
using allocator_monotonic_devirt = gc::allocator<T, monotonic_devirt>;

static void nlohmann_JSON_MBR_stateless_alloc_devirt(benchmark::State &state, std::string_view s) {
using pmr_json = nlohmann::basic_json<std::map, std::vector, gc::string<monotonic_devirt>, bool, std::int64_t,
std::uint64_t, double,
allocator_monotonic_devirt>;
constexpr size_t bufsize = 1 << 25;
thread_local auto buf = std::make_unique_for_overwrite<char[]>(bufsize);

thread_local gc::monotonic_buffer_resource_devirt mbr{buf.get(), bufsize, std::pmr::null_memory_resource()};
gc::mr<monotonic_devirt> = &mbr;
for (auto _: state) {
mbr.release();
auto jv = pmr_json::parse(s.begin(), s.end());
benchmark::DoNotOptimize(jv);
}
}

static void nlohmann_JSON_Default(benchmark::State &state, std::string_view s) {
for (auto _: state) {
auto jv = nlohmann::json::parse(s.begin(), s.end());
benchmark::DoNotOptimize(jv);
}
}

static void JSON_Perf(benchmark::State &state, void (*test)(benchmark::State &, const std::string_view),
const std::string &filename) {
auto s = load_file(filename);
test(state, s);
state.SetBytesProcessed(static_cast<long long int>(state.iterations() * s.size()));
}

#define ADD_BENCHMARK(func, filename) BENCHMARK_CAPTURE(JSON_Perf, func-filename, func, filename)

ADD_BENCHMARK(nlohmann_JSON_Default, "citm_catalog.json");
ADD_BENCHMARK(nlohmann_JSON_MBR_stateless_alloc, "citm_catalog.json");
ADD_BENCHMARK(nlohmann_JSON_MBR_stateless_alloc_devirt, "citm_catalog.json");

ADD_BENCHMARK(nlohmann_JSON_Default, "gsoc-2018.json");
ADD_BENCHMARK(nlohmann_JSON_MBR_stateless_alloc, "gsoc-2018.json");
ADD_BENCHMARK(nlohmann_JSON_MBR_stateless_alloc_devirt, "gsoc-2018.json");

ADD_BENCHMARK(nlohmann_JSON_Default, "github_events.json");
ADD_BENCHMARK(nlohmann_JSON_MBR_stateless_alloc, "github_events.json");
ADD_BENCHMARK(nlohmann_JSON_MBR_stateless_alloc_devirt, "github_events.json");


BENCHMARK_MAIN();