From 1ba3a2b66a006291527f87e2e2bd0c6c025daf81 Mon Sep 17 00:00:00 2001 From: pikastech Date: Tue, 21 Jul 2026 15:22:57 +0800 Subject: [PATCH 1/3] bench: instrument root lifecycle costs --- port/linux/benchmark/main.cpp | 55 +++++++++++++++++++++++++++++++++++ src/dataMemory.c | 9 ++++++ src/dataMemory.h | 3 ++ 3 files changed, 67 insertions(+) diff --git a/port/linux/benchmark/main.cpp b/port/linux/benchmark/main.cpp index a1d6caab3..28c336e34 100644 --- a/port/linux/benchmark/main.cpp +++ b/port/linux/benchmark/main.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include "test_common.h" extern "C" { @@ -184,6 +186,12 @@ static void function_call_starred_1000(benchmark::State& state) { BENCHMARK(function_call_starred_1000)->Unit(benchmark::kMillisecond); static void fibonacci_recursive_20(benchmark::State& state) { + using clock = std::chrono::steady_clock; + auto nanoseconds = [](clock::duration duration) { + return std::chrono::duration_cast(duration) + .count(); + }; + auto compile_start = clock::now(); Args* buffs = New_strBuff(); char* pikaAsm = pika_lines2Asm( buffs, (char*) @@ -196,16 +204,63 @@ static void fibonacci_recursive_20(benchmark::State& state) { ByteCodeFrame bytecode_frame; byteCodeFrame_init(&bytecode_frame); byteCodeFrame_appendFromAsm(&bytecode_frame, pikaAsm); + auto compile_end = clock::now(); + uint64_t root_create_ns = 0; + uint64_t execute_ns = 0; + uint64_t root_destroy_ns = 0; + uint64_t root_create_allocations = 0; + uint64_t execute_allocations = 0; + uint64_t root_destroy_allocations = 0; + uint32_t heap_peak_bytes = 0; for (auto _ : state) { + pikaMemMaxReset(); + pikaMemAllocationCountReset(); + auto root_create_start = clock::now(); PikaObj* pikaMain = newRootObj((char*)"pikaMain", New_PikaMain); + auto root_create_end = clock::now(); + root_create_allocations += pikaMemAllocationCount(); + uint32_t before_execute_allocations = pikaMemAllocationCount(); + auto execute_start = clock::now(); pikaVM_runByteCodeFrame(pikaMain, &bytecode_frame); + auto execute_end = clock::now(); + execute_allocations += + pikaMemAllocationCount() - before_execute_allocations; if (6765 != obj_getInt(pikaMain, (char*)"result")) { state.SkipWithError("recursive fibonacci result mismatch"); } + uint32_t before_destroy_allocations = pikaMemAllocationCount(); + auto root_destroy_start = clock::now(); obj_deinit(pikaMain); + auto root_destroy_end = clock::now(); + root_destroy_allocations += + pikaMemAllocationCount() - before_destroy_allocations; + root_create_ns += nanoseconds(root_create_end - root_create_start); + execute_ns += nanoseconds(execute_end - execute_start); + root_destroy_ns += nanoseconds(root_destroy_end - root_destroy_start); + heap_peak_bytes = std::max(heap_peak_bytes, pikaMemMax()); } byteCodeFrame_deinit(&bytecode_frame); args_deinit(buffs); + state.counters["compile_once_ns"] = + nanoseconds(compile_end - compile_start); + state.counters["root_create_ns"] = benchmark::Counter( + root_create_ns, benchmark::Counter::kAvgIterations); + state.counters["execute_ns"] = benchmark::Counter( + execute_ns, benchmark::Counter::kAvgIterations); + state.counters["root_destroy_ns"] = benchmark::Counter( + root_destroy_ns, benchmark::Counter::kAvgIterations); + state.counters["root_create_allocations"] = benchmark::Counter( + root_create_allocations, benchmark::Counter::kAvgIterations); + state.counters["execute_allocations"] = benchmark::Counter( + execute_allocations, benchmark::Counter::kAvgIterations); + state.counters["root_destroy_allocations"] = benchmark::Counter( + root_destroy_allocations, benchmark::Counter::kAvgIterations); + state.counters["heap_allocations"] = benchmark::Counter( + root_create_allocations + execute_allocations + + root_destroy_allocations, + benchmark::Counter::kAvgIterations); + state.counters["heap_peak_bytes"] = heap_peak_bytes; + state.counters["heap_residual_bytes"] = pikaMemNow(); } BENCHMARK(fibonacci_recursive_20)->Unit(benchmark::kMillisecond); diff --git a/src/dataMemory.c b/src/dataMemory.c index 76c2df960..a07f04849 100644 --- a/src/dataMemory.c +++ b/src/dataMemory.c @@ -47,6 +47,7 @@ void* pikaMalloc(uint32_t size) { if (g_PikaMemInfo.heapUsedMax < g_PikaMemInfo.heapUsed) { g_PikaMemInfo.heapUsedMax = g_PikaMemInfo.heapUsed; } + g_PikaMemInfo.allocationCount++; pika_platform_disable_irq_handle(); void* mem = pika_user_malloc(size); pika_platform_enable_irq_handle(); @@ -90,6 +91,14 @@ void pikaMemMaxReset(void) { g_PikaMemInfo.heapUsedMax = 0; } +uint32_t pikaMemAllocationCount(void) { + return g_PikaMemInfo.allocationCount; +} + +void pikaMemAllocationCountReset(void) { + g_PikaMemInfo.allocationCount = 0; +} + #if PIKA_POOL_ENABLE uint32_t pool_getBlockIndex_byMemSize(Pool* pool, uint32_t size) { if (0 == size) { diff --git a/src/dataMemory.h b/src/dataMemory.h index 1dabc362f..9ed28b478 100644 --- a/src/dataMemory.h +++ b/src/dataMemory.h @@ -43,6 +43,7 @@ extern "C" { typedef struct { uint32_t heapUsed; uint32_t heapUsedMax; + uint32_t allocationCount; #if PIKA_ARG_CACHE_ENABLE uint8_t* cache_pool[PIKA_ARG_CACHE_POOL_SIZE]; uint32_t cache_pool_top; @@ -76,6 +77,8 @@ void* pikaMalloc(uint32_t size); uint32_t pikaMemNow(void); uint32_t pikaMemMax(void); void pikaMemMaxReset(void); +uint32_t pikaMemAllocationCount(void); +void pikaMemAllocationCountReset(void); BitMap bitmap_init(uint32_t size); void bitmap_set(BitMap bitmap, uint32_t index, uint8_t bit); From 430020d833f88f4bd0bc9314d04f58558604bee3 Mon Sep 17 00:00:00 2001 From: pikastech Date: Tue, 21 Jul 2026 15:45:14 +0800 Subject: [PATCH 2/3] fix: keep benchmark root across warm iterations --- port/linux/benchmark/main.cpp | 171 +++++++++++++++++++++++++++------- 1 file changed, 139 insertions(+), 32 deletions(-) diff --git a/port/linux/benchmark/main.cpp b/port/linux/benchmark/main.cpp index 28c336e34..8b36785f1 100644 --- a/port/linux/benchmark/main.cpp +++ b/port/linux/benchmark/main.cpp @@ -185,6 +185,14 @@ static void function_call_starred_1000(benchmark::State& state) { } BENCHMARK(function_call_starred_1000)->Unit(benchmark::kMillisecond); +static const char* fibonacci_source = + "def fib(n):\n" + " if n < 2:\n" + " return n\n" + " return fib(n - 1) + fib(n - 2)\n" + "result = fib(20)\n" + "\n"; + static void fibonacci_recursive_20(benchmark::State& state) { using clock = std::chrono::steady_clock; auto nanoseconds = [](clock::duration duration) { @@ -193,76 +201,175 @@ static void fibonacci_recursive_20(benchmark::State& state) { }; auto compile_start = clock::now(); Args* buffs = New_strBuff(); - char* pikaAsm = pika_lines2Asm( - buffs, (char*) - "def fib(n):\n" - " if n < 2:\n" - " return n\n" - " return fib(n - 1) + fib(n - 2)\n" - "result = fib(20)\n" - "\n"); + char* pikaAsm = pika_lines2Asm(buffs, (char*)fibonacci_source); ByteCodeFrame bytecode_frame; byteCodeFrame_init(&bytecode_frame); byteCodeFrame_appendFromAsm(&bytecode_frame, pikaAsm); auto compile_end = clock::now(); - uint64_t root_create_ns = 0; + + pikaMemAllocationCountReset(); + auto root_create_start = clock::now(); + PikaObj* pikaMain = newRootObj((char*)"pikaMain", New_PikaMain); + auto root_create_end = clock::now(); + uint64_t root_create_allocations = pikaMemAllocationCount(); + uint32_t warm_heap_baseline_bytes = pikaMemNow(); + uint64_t execute_ns = 0; + uint64_t execute_allocations = 0; + pikaMemMaxReset(); + pikaMemAllocationCountReset(); + for (auto _ : state) { + uint32_t before_execute_allocations = pikaMemAllocationCount(); + auto execute_start = clock::now(); + pikaVM_runByteCodeFrame(pikaMain, &bytecode_frame); + auto execute_end = clock::now(); + execute_allocations += + pikaMemAllocationCount() - before_execute_allocations; + if (6765 != obj_getInt(pikaMain, (char*)"result")) { + state.SkipWithError("recursive fibonacci result mismatch"); + } + execute_ns += nanoseconds(execute_end - execute_start); + } + uint32_t heap_peak_bytes = pikaMemMax(); + uint32_t before_destroy_allocations = pikaMemAllocationCount(); + auto root_destroy_start = clock::now(); + obj_deinit(pikaMain); + auto root_destroy_end = clock::now(); + uint64_t root_destroy_allocations = + pikaMemAllocationCount() - before_destroy_allocations; + byteCodeFrame_deinit(&bytecode_frame); + args_deinit(buffs); + + state.counters["compile_once_ns"] = + nanoseconds(compile_end - compile_start); + state.counters["setup_root_create_ns"] = + nanoseconds(root_create_end - root_create_start); + state.counters["warm_execute_ns"] = benchmark::Counter( + execute_ns, benchmark::Counter::kAvgIterations); + state.counters["teardown_root_destroy_ns"] = + nanoseconds(root_destroy_end - root_destroy_start); + state.counters["setup_root_create_allocations"] = + root_create_allocations; + state.counters["execute_allocations"] = benchmark::Counter( + execute_allocations, benchmark::Counter::kAvgIterations); + state.counters["teardown_root_destroy_allocations"] = + root_destroy_allocations; + state.counters["heap_allocations"] = benchmark::Counter( + execute_allocations, benchmark::Counter::kAvgIterations); + state.counters["heap_peak_bytes"] = heap_peak_bytes; + state.counters["warm_heap_baseline_bytes"] = + warm_heap_baseline_bytes; + state.counters["heap_residual_bytes"] = pikaMemNow(); + state.counters["root_destroy_count"] = 1; +} +BENCHMARK(fibonacci_recursive_20)->Unit(benchmark::kMillisecond); + +static void fibonacci_recursive_20_cold_start(benchmark::State& state) { + using clock = std::chrono::steady_clock; + auto nanoseconds = [](clock::duration duration) { + return std::chrono::duration_cast(duration) + .count(); + }; + uint64_t root_create_ns = 0; + uint64_t compile_ns = 0; + uint64_t first_execute_ns = 0; uint64_t root_destroy_ns = 0; + uint64_t cleanup_ns = 0; uint64_t root_create_allocations = 0; + uint64_t compile_allocations = 0; uint64_t execute_allocations = 0; - uint64_t root_destroy_allocations = 0; + uint64_t destroy_allocations = 0; + uint64_t cleanup_allocations = 0; uint32_t heap_peak_bytes = 0; + uint32_t heap_residual_bytes = 0; + for (auto _ : state) { pikaMemMaxReset(); pikaMemAllocationCountReset(); + auto root_create_start = clock::now(); PikaObj* pikaMain = newRootObj((char*)"pikaMain", New_PikaMain); auto root_create_end = clock::now(); - root_create_allocations += pikaMemAllocationCount(); - uint32_t before_execute_allocations = pikaMemAllocationCount(); + uint32_t after_root_allocations = pikaMemAllocationCount(); + + auto compile_start = clock::now(); + Args* buffs = New_strBuff(); + char* pikaAsm = pika_lines2Asm(buffs, (char*)fibonacci_source); + ByteCodeFrame bytecode_frame; + byteCodeFrame_init(&bytecode_frame); + byteCodeFrame_appendFromAsm(&bytecode_frame, pikaAsm); + auto compile_end = clock::now(); + uint32_t after_compile_allocations = pikaMemAllocationCount(); + auto execute_start = clock::now(); pikaVM_runByteCodeFrame(pikaMain, &bytecode_frame); auto execute_end = clock::now(); - execute_allocations += - pikaMemAllocationCount() - before_execute_allocations; + uint32_t after_execute_allocations = pikaMemAllocationCount(); if (6765 != obj_getInt(pikaMain, (char*)"result")) { - state.SkipWithError("recursive fibonacci result mismatch"); + state.SkipWithError("cold fibonacci result mismatch"); } - uint32_t before_destroy_allocations = pikaMemAllocationCount(); + auto root_destroy_start = clock::now(); obj_deinit(pikaMain); auto root_destroy_end = clock::now(); - root_destroy_allocations += - pikaMemAllocationCount() - before_destroy_allocations; - root_create_ns += nanoseconds(root_create_end - root_create_start); - execute_ns += nanoseconds(execute_end - execute_start); - root_destroy_ns += nanoseconds(root_destroy_end - root_destroy_start); + uint32_t after_destroy_allocations = pikaMemAllocationCount(); + + auto cleanup_start = clock::now(); + byteCodeFrame_deinit(&bytecode_frame); + args_deinit(buffs); + auto cleanup_end = clock::now(); + uint32_t after_cleanup_allocations = pikaMemAllocationCount(); + + root_create_ns += + nanoseconds(root_create_end - root_create_start); + compile_ns += nanoseconds(compile_end - compile_start); + first_execute_ns += nanoseconds(execute_end - execute_start); + root_destroy_ns += + nanoseconds(root_destroy_end - root_destroy_start); + cleanup_ns += nanoseconds(cleanup_end - cleanup_start); + root_create_allocations += after_root_allocations; + compile_allocations += + after_compile_allocations - after_root_allocations; + execute_allocations += + after_execute_allocations - after_compile_allocations; + destroy_allocations += + after_destroy_allocations - after_execute_allocations; + cleanup_allocations += + after_cleanup_allocations - after_destroy_allocations; heap_peak_bytes = std::max(heap_peak_bytes, pikaMemMax()); + heap_residual_bytes = std::max(heap_residual_bytes, pikaMemNow()); } - byteCodeFrame_deinit(&bytecode_frame); - args_deinit(buffs); - state.counters["compile_once_ns"] = - nanoseconds(compile_end - compile_start); + state.counters["root_create_ns"] = benchmark::Counter( root_create_ns, benchmark::Counter::kAvgIterations); - state.counters["execute_ns"] = benchmark::Counter( - execute_ns, benchmark::Counter::kAvgIterations); + state.counters["compile_ns"] = benchmark::Counter( + compile_ns, benchmark::Counter::kAvgIterations); + state.counters["first_execute_ns"] = benchmark::Counter( + first_execute_ns, benchmark::Counter::kAvgIterations); state.counters["root_destroy_ns"] = benchmark::Counter( root_destroy_ns, benchmark::Counter::kAvgIterations); + state.counters["cleanup_ns"] = benchmark::Counter( + cleanup_ns, benchmark::Counter::kAvgIterations); state.counters["root_create_allocations"] = benchmark::Counter( root_create_allocations, benchmark::Counter::kAvgIterations); + state.counters["compile_allocations"] = benchmark::Counter( + compile_allocations, benchmark::Counter::kAvgIterations); state.counters["execute_allocations"] = benchmark::Counter( execute_allocations, benchmark::Counter::kAvgIterations); state.counters["root_destroy_allocations"] = benchmark::Counter( - root_destroy_allocations, benchmark::Counter::kAvgIterations); + destroy_allocations, benchmark::Counter::kAvgIterations); + state.counters["cleanup_allocations"] = benchmark::Counter( + cleanup_allocations, benchmark::Counter::kAvgIterations); state.counters["heap_allocations"] = benchmark::Counter( - root_create_allocations + execute_allocations + - root_destroy_allocations, + root_create_allocations + compile_allocations + + execute_allocations + destroy_allocations + + cleanup_allocations, benchmark::Counter::kAvgIterations); state.counters["heap_peak_bytes"] = heap_peak_bytes; - state.counters["heap_residual_bytes"] = pikaMemNow(); + state.counters["heap_residual_bytes"] = heap_residual_bytes; } -BENCHMARK(fibonacci_recursive_20)->Unit(benchmark::kMillisecond); +BENCHMARK(fibonacci_recursive_20_cold_start) + ->Unit(benchmark::kMillisecond); static void embedded_control_loop_1000(benchmark::State& state) { Args* buffs = New_strBuff(); From 00f39e58d6c3ef33bd78024cd013e854a58b57ff Mon Sep 17 00:00:00 2001 From: pikastech Date: Tue, 21 Jul 2026 16:01:12 +0800 Subject: [PATCH 3/3] test: cover repeated benchmark execution --- port/linux/test/benchmark-lifecycle-test.cpp | 39 ++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 port/linux/test/benchmark-lifecycle-test.cpp diff --git a/port/linux/test/benchmark-lifecycle-test.cpp b/port/linux/test/benchmark-lifecycle-test.cpp new file mode 100644 index 000000000..ba85fe57f --- /dev/null +++ b/port/linux/test/benchmark-lifecycle-test.cpp @@ -0,0 +1,39 @@ +#include + +extern "C" { +#include "PikaMain.h" +#include "PikaParser.h" +#include "PikaVM.h" +#include "dataArgs.h" +#include "dataMemory.h" +} + +TEST(benchmark_lifecycle, root_survives_repeated_execution_and_one_teardown) { + ASSERT_EQ(0u, pikaMemNow()); + Args* buffers = New_strBuff(); + char* assembly = pika_lines2Asm( + buffers, (char*) + "def fib(n):\n" + " if n < 2:\n" + " return n\n" + " return fib(n - 1) + fib(n - 2)\n" + "result = fib(15)\n"); + ByteCodeFrame bytecode_frame; + byteCodeFrame_init(&bytecode_frame); + byteCodeFrame_appendFromAsm(&bytecode_frame, assembly); + PikaObj* root = newRootObj((char*)"pikaMain", New_PikaMain); + + for (int iteration = 0; iteration < 500; ++iteration) { + ASSERT_NE(nullptr, pikaVM_runByteCodeFrame(root, &bytecode_frame)); + ASSERT_EQ(610, obj_getInt(root, (char*)"result")) << iteration; + } + + ASSERT_NE(nullptr, pikaVM_run(root, (char*)"missing_name()\n")); + ASSERT_NE(nullptr, pikaVM_runByteCodeFrame(root, &bytecode_frame)); + EXPECT_EQ(610, obj_getInt(root, (char*)"result")); + + obj_deinit(root); + byteCodeFrame_deinit(&bytecode_frame); + args_deinit(buffers); + EXPECT_EQ(0u, pikaMemNow()); +}