Skip to content

Optimize C++ Backend for Conditional Fields #234

Closed
AaronWebster wants to merge 13 commits intogoogle:masterfrom
AaronWebster:optimized-conditionals-v2-port
Closed

Optimize C++ Backend for Conditional Fields #234
AaronWebster wants to merge 13 commits intogoogle:masterfrom
AaronWebster:optimized-conditionals-v2-port

Conversation

@AaronWebster
Copy link
Collaborator

This change significantly optimizes the generated C++ code for
structures utilizing conditional fields.

Key optimizations include:

  • Two-Pass Generation: Implemented a strategy to identify and cache
    frequently used subexpressions (e.g., tag reads) within Ok(),
    Equals(), and UncheckedEquals() methods.
    • This involves a first pass to count subexpression usage, followed
      by a second pass that caches repeated expressions in local
      variables while inlining unique ones.
  • Reduced Redundancy: Minimizes redundant computations, leading to CPU
    performance improvements and reduced stack usage for unique
    conditions.

Performance Impact (on testdata/many_conditionals.emb with 10,000
iterations x 100 tags):

  • Runtime Speedup: ~8.7x faster (from ~1.83s to ~0.21s).
  • Binary Size Reduction: ~47% smaller (from 57848 bytes to 30472
    bytes for many_conditionals_benchmark with -c opt).

Additional updates:

  • Test infrastructure was enhanced to support passing compiler flags to
    golden tests, resolving an issue with no_enum_traits_golden_test.
  • New test data (testdata/many_conditionals.emb) and a benchmark
    (compiler/back_end/cpp/testcode/many_conditionals_benchmark.cc)
    were added to verify performance.

AaronWebster and others added 12 commits November 25, 2025 12:55
Optimized the generated C++ code for structures with conditional fields
by identifying and caching repeated subexpressions (e.g., tag reads)
in the `Ok()`, `Equals()`, and `UncheckedEquals()` methods.

This approach uses a two-pass generation strategy:
1.  Count usage of existence condition subexpressions.
2.  Generate code where frequently used subexpressions are cached in
    local variables at the method scope, while unique expressions are
    inlined.

This reduces redundant computations (CPU optimization) without
introducing excessive stack usage for unique conditions (Memory
optimization).

Also updated test infrastructure to support passing compiler flags to
golden tests, fixing `no_enum_traits_golden_test`.

Added `testdata/many_conditionals.emb` and `compiler/back_end/cpp/testcode/many_conditionals_benchmark.cc`
to verify performance of conditional fields.

Performance Analysis (10,000 iterations x 100 tags on `testdata/many_conditionals.emb`):
- Baseline Runtime: ~1.83s
- Optimized Runtime: ~0.21s (Speedup: ~8.7x)

Binary Size Impact (many_conditionals_benchmark, -c opt):
- Baseline: 57848 bytes (text: 56112)
- Optimized: 30472 bytes (text: 28736) -> ~47% reduction
Co-authored-by: AaronWebster <3766083+AaronWebster@users.noreply.github.com>
…nd-tests

Fix C++ backend conditional optimization: formatting and golden file regeneration
Copy link
Collaborator

@EricRahm EricRahm left a comment

Choose a reason for hiding this comment

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

Sorry for the post-commit review, I just want to double-check your intent. I have some minor concerns about raw code size increase and possible compile time regressions. I can run this against pigweed's emboss defs and follow up with numbers.

// Ok().
if (has_${field}.ValueOrDefault() && !${field}.Ok()) return false;
{
const auto emboss_reserved_local_field_present = ${existence_condition};
Copy link
Collaborator

Choose a reason for hiding this comment

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

So I think this is intended to inline the contents of has_<field>, is that correct? And the goal of that is to reuse a cached instance of a field that is referenced in multiple has_<field> methods? Looking at the generated code now it's...pretty verbose. I'm actually a little surprised this would reduce code size.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Check out #235 which I think is more correct as to what Ben suggested I implement and the code is much cleaner. Some compiler specific attributes could be elided, let me know.


if (!has_x().Known()) return false;
if (has_x().ValueOrDefault() && !x().Ok()) return false;
const auto emboss_reserved_local_ok_subexpr_1 = x();
Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess this is an example of what we're trying to optimize.

{
const auto emboss_reserved_local_field_present = ::emboss::support::Maybe</**/bool>(true);
if (!emboss_reserved_local_field_present.Known()) return false;
if (emboss_reserved_local_field_present.ValueOrDefault() && !x().Ok()) return false;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Although note we don't use it here.

if (has_xc().ValueOrDefault() && !xc().Ok()) return false;

{
const auto emboss_reserved_local_field_present = ::emboss::support::Equal</**/::std::int32_t, bool, ::std::int32_t, ::std::int32_t>((emboss_reserved_local_ok_subexpr_1.Ok() ? ::emboss::support::Maybe</**/::std::int32_t>(static_cast</**/::std::int32_t>(emboss_reserved_local_ok_subexpr_1.UncheckedRead())) : ::emboss::support::Maybe</**/::std::int32_t>()), ::emboss::support::Maybe</**/::std::int32_t>(static_cast</**/::std::int32_t>(0LL)));
Copy link
Collaborator

Choose a reason for hiding this comment

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

...but we do use it here

if (has_xcc().ValueOrDefault() && !xcc().Ok()) return false;

{
const auto emboss_reserved_local_field_present = ::emboss::support::And</**/bool, bool, bool, bool>(::emboss::support::Equal</**/::std::int32_t, bool, ::std::int32_t, ::std::int32_t>((emboss_reserved_local_ok_subexpr_1.Ok() ? ::emboss::support::Maybe</**/::std::int32_t>(static_cast</**/::std::int32_t>(emboss_reserved_local_ok_subexpr_1.UncheckedRead())) : ::emboss::support::Maybe</**/::std::int32_t>()), ::emboss::support::Maybe</**/::std::int32_t>(static_cast</**/::std::int32_t>(0LL))), ::emboss::support::Equal</**/::std::int32_t, bool, ::std::int32_t, ::std::int32_t>((xc().Ok() ? ::emboss::support::Maybe</**/::std::int32_t>(static_cast</**/::std::int32_t>(xc().UncheckedRead())) : ::emboss::support::Maybe</**/::std::int32_t>()), ::emboss::support::Maybe</**/::std::int32_t>(static_cast</**/::std::int32_t>(0LL))));
Copy link
Collaborator

Choose a reason for hiding this comment

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

...and here. Note how verbose this has become though. Maybe that's fine, this is generated code after all.

@AaronWebster
Copy link
Collaborator Author

Eric, can you please move your comments to #235 so we can have the discussion there?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants