209 lines
8.2 KiB
C++
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;
|
|
}
|