Skip to content

Benchmaking: observability large radial grids#1302

Draft
Jerry-Jinfeng-Guo wants to merge 11 commits intofeature/meshed-observability-optimizationfrom
feature/benchmaking-observability-large-radial-grids
Draft

Benchmaking: observability large radial grids#1302
Jerry-Jinfeng-Guo wants to merge 11 commits intofeature/meshed-observability-optimizationfrom
feature/benchmaking-observability-large-radial-grids

Conversation

@Jerry-Jinfeng-Guo
Copy link
Member

@Jerry-Jinfeng-Guo Jerry-Jinfeng-Guo commented Feb 11, 2026

!!!DO NOT MERGE!!!

This branch contains the benchmarking between radial and meshed observability check. Making a PR for archival purpose. Do not merge.
The behavior comparison (and the view of) meshed against radial is studied via scaling experiments from (+1 node) 10 nodes all the way to 10000 nodes. The following are the overview.

Nodes Feeders N/Feeder Radial (μs) Meshed (μs) Time OH% Mem Radial (MB) Mem Meshed (MB) Mem OH%
10 2 4 29.72 25.31 -14.84% 0.4 0.4 0.0%
20 3 6 29.70 28.08 -5.44% 0.1 - -
31 3 9 39.20 39.56 +0.93% - 0.1 -
41 4 9 52.98 51.67 -2.46% - 0.1 -
51 5 9 65.18 62.26 -4.48% 0.1 0.1 0.0%
58 5 11 72.07 75.74 +5.09% 0.1 0.1 0.0%
70 6 11 86.98 91.26 +4.92% 0.1 0.1 0.0%
81 7 11 102.73 104.95 +2.16% 0.1 0.2 +100.0%
90 8 11 113.17 114.00 +0.73% 0.1 0.1 0.0%
102 10 10 133.02 124.69 -6.26% 0.1 0.2 +100.0%
202 20 10 274.74 454.42 +65.40% 0.4 0.5 +33.3%
502 25 20 645.65 1124.91 +74.23% 0.9 1.1 +28.6%
1002 20 50 1234.47 2066.63 +67.41% 1.6 2.1 +30.8%
5002 50 100 6058.84 9913.70 +63.62% 7.6 9.8 +27.9%
10002 100 100 12380.95 20360.27 +64.45% 15.4 19.4 +26.0%
image

Signed-off-by: Jerry Jinfeng Guo <jerry.jinfeng.guo@alliander.com>
Signed-off-by: Jerry Jinfeng Guo <jerry.jinfeng.guo@alliander.com>
Signed-off-by: Jerry Jinfeng Guo <jerry.jinfeng.guo@alliander.com>
Signed-off-by: Jerry Jinfeng Guo <jerry.jinfeng.guo@alliander.com>
Signed-off-by: Jerry Jinfeng Guo <jerry.jinfeng.guo@alliander.com>
Signed-off-by: Jerry Jinfeng Guo <jerry.jinfeng.guo@alliander.com>
@Jerry-Jinfeng-Guo Jerry-Jinfeng-Guo self-assigned this Feb 11, 2026
@Jerry-Jinfeng-Guo Jerry-Jinfeng-Guo added the do-not-merge This should not be merged label Feb 11, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a standalone C++ benchmarking executable to compare radial vs meshed observability checks (including a scaling study), plus minor generator fixes to support benchmark scenarios.

Changes:

  • Introduces power_grid_model_observability_benchmark executable with JSON, generated-grid, and scaling modes.
  • Adds an internal benchmark hook in MainModelImpl and a public accessor in MainModel to reach it.
  • Fixes sensor generation to resolve shunt node references by ID and guards transforms on empty inputs.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
tests/benchmark_cpp/observability_benchmark.cpp New benchmark driver (timing/memory, JSON parsing, scaling study).
tests/benchmark_cpp/fictional_grid_generator.hpp Adds node lookup by ID and makes sensor generation more robust for empty inputs.
tests/benchmark_cpp/CMakeLists.txt Adds a new benchmark executable, install rule, and convenience target.
power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp Adds benchmark-only run_observability_benchmark entrypoint under a compile define.
power_grid_model_c/power_grid_model/include/power_grid_model/main_model.hpp Exposes get_impl_for_benchmark() publicly.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 94 to +96

// Benchmark accessor - provides access to internal structures for observability benchmarking
Impl& get_impl_for_benchmark() { return impl(); }
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_impl_for_benchmark() is added to a public header and is always available, which exposes internal implementation details (Impl) to all library consumers and weakens encapsulation. Consider guarding this accessor with the same compile-time flag used for the benchmark (#ifdef POWER_GRID_MODEL_OBSERVABILITY_BENCHMARK), or moving this accessor behind a dedicated benchmark/test-only interface to avoid expanding the public API surface.

Suggested change
// Benchmark accessor - provides access to internal structures for observability benchmarking
Impl& get_impl_for_benchmark() { return impl(); }
#ifdef POWER_GRID_MODEL_OBSERVABILITY_BENCHMARK
// Benchmark accessor - provides access to internal structures for observability benchmarking
Impl& get_impl_for_benchmark() { return impl(); }
#endif

Copilot uses AI. Check for mistakes.
Comment on lines +446 to +453
// Get actual node count from the generated grid
FictionalGridGenerator temp_gen;
temp_gen.generate_grid(grid_option);
Idx actual_nodes = static_cast<Idx>(temp_gen.input_data().node.size());

// Store result
double radial_mean_us = radial_mean_ns / 1000.0;
double meshed_mean_us = meshed_mean_ns / 1000.0;
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The scaling loop regenerates the entire grid a second time just to compute actual_nodes, which can dominate runtime for large configurations and distort benchmark throughput. A concrete fix is to have run_benchmark_on_generated_grid(...) return the actual node count (or the generator/input sizes) alongside the benchmark results so the scaling study can record actual_nodes without a second generation pass.

Suggested change
// Get actual node count from the generated grid
FictionalGridGenerator temp_gen;
temp_gen.generate_grid(grid_option);
Idx actual_nodes = static_cast<Idx>(temp_gen.input_data().node.size());
// Store result
double radial_mean_us = radial_mean_ns / 1000.0;
double meshed_mean_us = meshed_mean_ns / 1000.0;
// Use configured approximate node count as the recorded node count
Idx actual_nodes = static_cast<Idx>(config.approx_nodes);
// Store result
double radial_mean_us = radial_mean_ns / 1000.0;
double meshed_mean_us = meshed_mean_ns / 1000.0;
double meshed_mean_us = meshed_mean_ns / 1000.0;

Copilot uses AI. Check for mistakes.
Comment on lines +116 to +118
// Memory measurement
size_t mem_before_radial = get_memory_usage_kb();
size_t mem_before_meshed = 0;
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The memory deltas for radial vs meshed are not comparable because the meshed baseline (mem_before_meshed) is captured after the radial run has already potentially allocated/cached memory. This biases meshed_mem_delta downward (and can make overhead misleading). To make the comparison meaningful, measure both algorithms from the same baseline (e.g., snapshot once before both runs and compute peak/RSS deltas per run), or compute per-algorithm peak memory independently (your get_peak_memory_kb() helper suggests that direction).

Suggested change
// Memory measurement
size_t mem_before_radial = get_memory_usage_kb();
size_t mem_before_meshed = 0;
// Memory measurement: use a single baseline for both algorithms
size_t mem_baseline = get_memory_usage_kb();
size_t mem_before_radial = mem_baseline;
size_t mem_before_meshed = mem_baseline;

Copilot uses AI. Check for mistakes.
auto end = high_resolution_clock::now();
radial_times.push_back(duration_cast<nanoseconds>(end - start));
}
mem_after_radial = get_memory_usage_kb();
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The memory deltas for radial vs meshed are not comparable because the meshed baseline (mem_before_meshed) is captured after the radial run has already potentially allocated/cached memory. This biases meshed_mem_delta downward (and can make overhead misleading). To make the comparison meaningful, measure both algorithms from the same baseline (e.g., snapshot once before both runs and compute peak/RSS deltas per run), or compute per-algorithm peak memory independently (your get_peak_memory_kb() helper suggests that direction).

Copilot uses AI. Check for mistakes.

// Benchmark meshed algorithm
std::cout << "Benchmarking meshed algorithm...\n";
mem_before_meshed = get_memory_usage_kb();
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The memory deltas for radial vs meshed are not comparable because the meshed baseline (mem_before_meshed) is captured after the radial run has already potentially allocated/cached memory. This biases meshed_mem_delta downward (and can make overhead misleading). To make the comparison meaningful, measure both algorithms from the same baseline (e.g., snapshot once before both runs and compute peak/RSS deltas per run), or compute per-algorithm peak memory independently (your get_peak_memory_kb() helper suggests that direction).

Copilot uses AI. Check for mistakes.

// Calculate memory usage
size_t radial_mem_delta = (mem_after_radial > mem_before_radial) ? (mem_after_radial - mem_before_radial) : 0;
size_t meshed_mem_delta = (mem_after_meshed > mem_before_meshed) ? (mem_after_meshed - mem_before_meshed) : 0;
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The memory deltas for radial vs meshed are not comparable because the meshed baseline (mem_before_meshed) is captured after the radial run has already potentially allocated/cached memory. This biases meshed_mem_delta downward (and can make overhead misleading). To make the comparison meaningful, measure both algorithms from the same baseline (e.g., snapshot once before both runs and compute peak/RSS deltas per run), or compute per-algorithm peak memory independently (your get_peak_memory_kb() helper suggests that direction).

Suggested change
size_t meshed_mem_delta = (mem_after_meshed > mem_before_meshed) ? (mem_after_meshed - mem_before_meshed) : 0;
size_t meshed_mem_delta = (mem_after_meshed > mem_before_radial) ? (mem_after_meshed - mem_before_radial) : 0;

Copilot uses AI. Check for mistakes.
Comment on lines +566 to +569
auto const* node_ptr = find_node_by_id(shunt.node);
if (!node_ptr) {
throw std::runtime_error("Node not found for shunt");
}
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thrown error message is too generic to debug failing inputs (it omits the shunt/node identifiers). Include at least the shunt.node value (and ideally the shunt id if available) in the exception message so failures can be traced to specific input records.

Copilot uses AI. Check for mistakes.
benchmark_dir = argv[2];
}
if (argc > 3) {
n_iterations = std::stoll(argv[3]);
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Command-line parsing uses std::stoll without handling std::invalid_argument / std::out_of_range, so malformed inputs will terminate the program with an uncaught exception. Wrap argument parsing in a try/catch that prints the usage (or switch to std::from_chars and validate), returning a non-zero exit code on parse errors.

Copilot uses AI. Check for mistakes.
Comment on lines +569 to +572
n_iterations = std::stoll(argv[2]);
}
if (argc > 3) {
n_mv_feeder = std::stoll(argv[3]);
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Command-line parsing uses std::stoll without handling std::invalid_argument / std::out_of_range, so malformed inputs will terminate the program with an uncaught exception. Wrap argument parsing in a try/catch that prints the usage (or switch to std::from_chars and validate), returning a non-zero exit code on parse errors.

Copilot uses AI. Check for mistakes.
n_iterations = std::stoll(argv[2]);
}
if (argc > 3) {
max_nodes = std::stoll(argv[3]);
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Command-line parsing uses std::stoll without handling std::invalid_argument / std::out_of_range, so malformed inputs will terminate the program with an uncaught exception. Wrap argument parsing in a try/catch that prints the usage (or switch to std::from_chars and validate), returning a non-zero exit code on parse errors.

Copilot uses AI. Check for mistakes.
@sonarqubecloud
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do-not-merge This should not be merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments