Files
Renderive/tests/Memory_Pool_Benchmark.cpp
T
2026-07-31 06:21:10 +08:00

209 lines
8.2 KiB
C++

#include <QTime>
#include <vector>
#ifdef _WIN32
#include <Windows.h>
#include <Psapi.h>
#else
#include <sys/resource.h>
#endif
#include <algorithm>
#include <array>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <list>
#include <memory>
#include <memory_resource>
#include <string>
#include <utility>
#include <vector>
#include "../Renderive/architecture/Renderable.h"
namespace {
class Counting_Resource final : public std::pmr::memory_resource {
public:
std::uint64_t allocation_count{};
std::uint64_t deallocation_count{};
std::uint64_t current_bytes{};
std::uint64_t peak_bytes{};
private:
void* do_allocate(std::size_t size, std::size_t alignment) override {
void* result = std::pmr::new_delete_resource()->allocate(size, alignment);
++allocation_count;
current_bytes += size;
peak_bytes = std::max(peak_bytes, current_bytes);
return result;
}
void do_deallocate(void* address, std::size_t size, std::size_t alignment) override {
std::pmr::new_delete_resource()->deallocate(address, size, alignment);
++deallocation_count;
current_bytes -= size;
}
bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override {
return this == &other;
}
};
struct Benchmark_Waterfall_Row {
int tick{};
std::vector<double> frequency;
};
struct Benchmark_Time_Tick {
QTime time;
int tick{};
};
struct Benchmark_Result {
double renderable_ms{};
double waterfall_ms{};
double time_audio_ms{};
};
volatile std::uint64_t benchmark_sink{};
template <typename T>
std::shared_ptr<T> make_two_allocation_shared(std::pmr::memory_resource* resource) {
std::pmr::polymorphic_allocator<T> object_allocator(resource);
T* object = object_allocator.allocate(1);
try {
std::allocator_traits<decltype(object_allocator)>::construct(object_allocator, object);
}
catch (...) {
object_allocator.deallocate(object, 1);
throw;
}
auto deleter = [object_allocator](T* value) mutable {
std::allocator_traits<decltype(object_allocator)>::destroy(object_allocator, value);
object_allocator.deallocate(value, 1);
};
return std::shared_ptr<T>(object, std::move(deleter), std::pmr::polymorphic_allocator<T>(resource));
}
template <typename Handler>
double measure_milliseconds(Handler handler) {
auto begin = std::chrono::steady_clock::now();
handler();
auto end = std::chrono::steady_clock::now();
return std::chrono::duration<double, std::milli>(end - begin).count();
}
double benchmark_renderables(std::pmr::memory_resource* resource, bool two_allocations) {
return measure_milliseconds([resource, two_allocations]() {
std::pmr::vector<std::shared_ptr<renderive::Renderable>> owners(resource);
owners.reserve(20000);
for (int i = 0; i < 20000; ++i) {
if (two_allocations)
owners.push_back(make_two_allocation_shared<renderive::Renderable>(resource));
else
owners.push_back(std::allocate_shared<renderive::Renderable>(std::pmr::polymorphic_allocator<renderive::Renderable>(resource)));
}
benchmark_sink += owners.size();
owners.clear();
});
}
double benchmark_waterfall(std::pmr::memory_resource* resource) {
static constexpr std::array<int, 5> frequency_sizes{64, 512, 8192, 8193, 128};
static constexpr std::array<int, 5> time_sizes{32, 96, 48, 64, 24};
return measure_milliseconds([resource]() {
for (int round = 0; round < 32; ++round) {
std::pmr::vector<std::uint8_t> write_buffer(resource);
std::pmr::vector<std::uint8_t> record_buffer(resource);
std::pmr::vector<std::uint8_t> snapshot_buffer(resource);
std::pmr::vector<double> row_buffer(resource);
std::pmr::vector<Benchmark_Waterfall_Row> pending_rows(resource);
for (std::size_t stage = 0; stage < frequency_sizes.size(); ++stage) {
std::size_t row_bytes = static_cast<std::size_t>(frequency_sizes[stage]) * sizeof(double);
std::size_t record_bytes = row_bytes + sizeof(int);
write_buffer.resize(record_bytes);
record_buffer.resize(record_bytes);
snapshot_buffer.resize(record_bytes * static_cast<std::size_t>(time_sizes[stage]));
row_buffer.resize(frequency_sizes[stage]);
pending_rows.clear();
for (int row = 0; row < 8; ++row) {
Benchmark_Waterfall_Row value;
value.tick = row;
value.frequency.resize(frequency_sizes[stage]);
pending_rows.push_back(std::move(value));
}
write_buffer.front() = static_cast<std::uint8_t>(round);
row_buffer.front() = static_cast<double>(round);
}
benchmark_sink += write_buffer.size() + snapshot_buffer.size() + pending_rows.size();
}
});
}
double benchmark_time_audio(std::pmr::memory_resource* resource) {
static constexpr std::array<int, 4> point_sizes{32, 256, 4096, 64};
return measure_milliseconds([resource]() {
for (int round = 0; round < 64; ++round) {
std::pmr::vector<QTime> time_snapshot(resource);
std::pmr::vector<double> power_values(resource);
std::pmr::list<Benchmark_Time_Tick> time_ticks(resource);
for (int size : point_sizes) {
time_snapshot.resize(size);
power_values.resize(size);
for (int tick = 0; tick < size; ++tick)
time_ticks.push_front({QTime::fromMSecsSinceStartOfDay(tick), tick});
while (time_ticks.size() > static_cast<std::size_t>(size))
time_ticks.pop_back();
if (!power_values.empty())
power_values.back() = static_cast<double>(round);
}
benchmark_sink += time_snapshot.size() + power_values.size() + time_ticks.size();
}
});
}
std::uint64_t peak_working_set_bytes() {
#ifdef _WIN32
PROCESS_MEMORY_COUNTERS_EX counters{};
counters.cb = sizeof(counters);
GetProcessMemoryInfo(GetCurrentProcess(), reinterpret_cast<PROCESS_MEMORY_COUNTERS*>(&counters), sizeof(counters));
return counters.PeakWorkingSetSize;
#else
rusage usage{};
getrusage(RUSAGE_SELF, &usage);
return static_cast<std::uint64_t>(usage.ru_maxrss) * 1024;
#endif
}
Benchmark_Result run_benchmark(std::pmr::memory_resource* resource, bool two_allocations) {
Benchmark_Result result;
result.renderable_ms = benchmark_renderables(resource, two_allocations);
result.waterfall_ms = benchmark_waterfall(resource);
result.time_audio_ms = benchmark_time_audio(resource);
return result;
}
}
int main(int argc, char** argv) {
if (argc != 2)
return 1;
std::string name = argv[1];
Counting_Resource upstream;
std::unique_ptr<std::pmr::synchronized_pool_resource> pool;
std::pmr::memory_resource* resource = &upstream;
bool two_allocations = false;
if (name == "old") {
two_allocations = true;
}
else if (name == "default") {
pool = std::make_unique<std::pmr::synchronized_pool_resource>(&upstream);
resource = pool.get();
}
else {
std::pmr::pool_options options;
if (name == "64x64k") {
options.max_blocks_per_chunk = 64;
options.largest_required_pool_block = 64 * 1024;
}
else if (name == "32x32k") {
options.max_blocks_per_chunk = 32;
options.largest_required_pool_block = 32 * 1024;
}
else if (name == "64x128k") {
options.max_blocks_per_chunk = 64;
options.largest_required_pool_block = 128 * 1024;
}
else {
return 2;
}
pool = std::make_unique<std::pmr::synchronized_pool_resource>(options, &upstream);
resource = pool.get();
}
Benchmark_Result result = run_benchmark(resource, two_allocations);
std::cout << name << ',' << result.renderable_ms << ',' << result.waterfall_ms << ',' << result.time_audio_ms << ',' << upstream.allocation_count << ',' << upstream.current_bytes << ',' << upstream.peak_bytes << ',' << peak_working_set_bytes() << '\n';
return 0;
}