Effect of "static constexpr" in functions

114 views
Skip to first unread message

Joe Mason

unread,
Aug 28, 2025, 4:54:55 PMAug 28
to cxx
https://chromium.googlesource.com/chromium/src/+/HEAD/styleguide/c++/defining_compile_time_const.md says:

At function scope, make constants static to help the optimizer avoid unnecessary codegen.

// Do this.
void Good() {
  static constexpr int kA = 1;         // Will be optimized away.
  ...
}

// Don't do these.
void Bad() {
  ...
  constexpr int kC = 3;                // Might not be optimized away if ODR-used.


However, I just changed some pre-C++17 code in the perfetto lib from "constexpr auto" to "static constexpr auto", and it increased the Chrome android binary size by 124 kiB (size diff here). The precise Perfetto diff I applied is this, which changes code inside every TRACE_EVENT macro.

Note the perfetto code always used "static constexpr auto" with MSVC, to work around a compiler bug, but "constexpr auto" with clang, with the comment: 

// On the other hand, if we add static with clang, binary size of the chromium
// build will increase dramatically.

Which turns out to be correct.

Do we need to revisit the constexpr advice?

Joe


Daniel Cheng

unread,
Aug 28, 2025, 5:36:57 PMAug 28
to Joe Mason, cxx
Without the context of the change, it's hard to say exactly why this happened. It's probably context-dependent whether or not it affects binary size. In some cases, instantiating it at runtime might be better for binary size, e.g. this case.

But I think I would still use 'static' where it makes sense, i.e. the lifetime of the constant is not really tied to the scope, and only omit the static where it's justified (with a comment).

Daniel

--
You received this message because you are subscribed to the Google Groups "cxx" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cxx+uns...@chromium.org.
To view this discussion visit https://groups.google.com/a/chromium.org/d/msgid/cxx/CAH%3DT95T6VuewCzeQo_77SdSzs19Gu7SkM9b6NdMne_%3DTOGKkuA%40mail.gmail.com.

Adam Rice

unread,
Sep 1, 2025, 11:46:52 AMSep 1
to Daniel Cheng, Joe Mason, cxx
In this case, the static variable is a small integer which never has its address taken, so the compiler can use an immediate operand to load it into a register directly in a single instruction.

Since the address of the variable is never taken, there's no need for the compiler to keep around a separate copy of the value. From the binary size growth, it appears that clang is keeping a separate copy anyway, which could be considered a compiler bug.

I don't think it's worth adding an extra clause to the advice saying "... except if your value is a small integer that never gets its address taken and clang fails to optimize it away". Most people aren't writing macros that are used in thousands of places, and the advice is complicated enough already.

Mark Mentovai

unread,
Sep 1, 2025, 4:24:43 PMSep 1
to Adam Rice, Daniel Cheng, Joe Mason, cxx
Adam Rice wrote:
In this case, the static variable is a small integer which never has its address taken, so the compiler can use an immediate operand to load it into a register directly in a single instruction.

Since the address of the variable is never taken,

I was planning a much longer response to explain the situation fully (and may still yet write that), but I wanted to nip this in the bud: the address of the variable is taken! That means that when the variable is given static storage duration, it needs a home in .rodata (hence the growth in that section). That, in turn, means that the pointer load must be pc-relative (and cross segments), which on most architectures will either be more instructions, or a longer instruction encoding (hence the growth in .text).

The path that I see follows PERFETTO_INTERNAL_TRACK_EVENT_WITH_METHOD’s passing of its PERFETTO_UID(…) variable, initialized to PERFETTO_GET_CATEGORY_INDEX(…). That’s the result of a call to perfetto_track_event::internal::kConstExprCategoryRegistry.Find, where perfetto_track_event::internal::kConstExprCategoryRegistry is established by PERFETTO_INTERNAL_DECLARE_CATEGORIES as perfetto::internal::TrackEventCategoryRegistry (instantiated by PERFETTO_DEFINE_CATEGORIES_IN_NAMESPACE_WITH_ATTRS, and from there, in Chrome code, by base and v8). perfetto::internal::TrackEventCategoryRegistry::Find returns size_t, so that’s what PERFETTO_UID(…) is, so the auto’s type is now understood. The address-taking of PERFETTO_UID(…) happens in the lambda call, which is further macro-parameterized via method. There might be several entry points to this, but a good example is TRACE_EVENT_BEGIN, which uses TraceForCategory as its method. So, we’re looking at perfetto_track_event::internal::TrackEvent::TraceForCategory, which accepts its second parameter, category (from the calling macro’s PERFETTO_UID(…)) as CategoryType &. That’s a reference: address taken!

Adam Rice

unread,
Sep 1, 2025, 9:23:20 PMSep 1
to Mark Mentovai, Daniel Cheng, Joe Mason, cxx
Sorry about that. I failed to fully untangle the macros. Since the address is taken it makes more sense.

So in this case it takes less bytes of instructions to materialize a small integer onto the stack and calculate its address than to calculate the address of a static variable at a much larger offset.

Joe Mason

unread,
Sep 2, 2025, 12:17:05 PMSep 2
to Mark Mentovai, Adam Rice, Daniel Cheng, cxx
Wow, that is quite a bit more complicated than I expected.

How about adding a caveat to the doc that `static constexpr` is *usually* the best choice, but there are rare corner cases where plain `constexpr` causes better code-gen, without getting into the weeds of what those are?

Joe Mason

unread,
Sep 2, 2025, 12:28:34 PMSep 2
to Mark Mentovai, Adam Rice, Daniel Cheng, cxx
A related oddity: I just noticed that doc says:

> For example, Chrome contains many variables that are declared constexpr in a header file but omit inline. This gives every .cc file using these 
> constants its own copies, which violates the one-definition rule (“ODR”) and is technically undefined behavior (“UB”).

I found that when I added `inline` to a `constexpr` that's used in another macro, I was still getting a separate constant definition for every .cc file. See the APK size breakdown at https://chromium-review.googlesource.com/c/chromium/src/+/6895718?checksPatchset=4&tab=checks.

This adds a reference to a constexpr `kCategories` array to a common macro, and also changes the array def from `constexpr` to `inline constexpr`. With that a 0.35 KiB copy of the array is added to each .cc file that uses the macro. This is still an improvement over an earlier change that modified the macro without adding `inline`, which added a copy of the array for each *usage* of the macro (multiple copies per .cc file): https://crrev.com/c/6892973?tab=checks.

Does this suggest a compiler issue, where these should be de-deduped still further?

Joe Mason

unread,
Sep 2, 2025, 12:30:30 PMSep 2
to Mark Mentovai, Adam Rice, Daniel Cheng, cxx
Just noticed those chromium-review links don't include the actual perfetto change being tested: it's https://github.com/google/perfetto/pull/2800/files

K. Moon

unread,
Sep 2, 2025, 1:27:20 PMSep 2
to Joe Mason, Mark Mentovai, Adam Rice, Daniel Cheng, cxx
Just to clarify something, the point of "inline" is not to avoid duplicating definitions in multiple translation units, it's to allow the linker to consolidate duplicate definitions. The number of definitions you see in individual object files doesn't matter.

(This only works for symbols that have the same name. Anonymous namespaces and inline are a bad idea, for example, but anonymous namespaces in headers was already a bad idea.)

K. Moon

unread,
Sep 2, 2025, 1:28:33 PMSep 2
to Joe Mason, Mark Mentovai, Adam Rice, Daniel Cheng, cxx
...and the other big footgun with inline is that it doesn't work across shared libraries, since those don't get processed by the static linker.

Joe Mason

unread,
Sep 2, 2025, 2:03:01 PMSep 2
to K. Moon, Mark Mentovai, Adam Rice, Daniel Cheng, cxx
So is the Android binary size bot showing the size breakdown before the linker consolidates duplicates? That doesn't seem very useful.

K. Moon

unread,
Sep 2, 2025, 2:41:22 PMSep 2
to Joe Mason, Mark Mentovai, Adam Rice, Daniel Cheng, cxx
Android linking isn't an area I'm super familiar with, but on first inspection, the results for patchset 3 (https://chrome-supersize.firebaseapp.com/viewer.html?load_url=https://storage.googleapis.com/chromium-binary-size-trybot-results/android-binary-size/2025/08/28/2403959/Trichrome32.ssargs.sizediff) look reasonable to me.

The effects look like the pre-inline change to me, though: lots of extra code everywhere, increasing the final binary size. I'm sure everything would make sense with a deeper analysis.

Joe Mason

unread,
Sep 2, 2025, 3:32:54 PMSep 2
to K. Moon, Mark Mentovai, Adam Rice, Daniel Cheng, cxx
dcheng pointed out I was misreading the APK Size line: it's +16 bytes, not +16 KB. So yes, a completely negligible change.

I'm pretty sure now that I'm just reading the output wrong. The only reason I'm looking at the breakdown here is that I want to understand what the effects of adding "inline" are. It looks to me like it's adding multiple copies of a symbol, but since the net size diff is so small I'm not really sure how to interpret this:

image.png

Since they're so small, I guess that each "kCategories" symbol is actually just individual array entires referenced in that file with the rest optimized out? And thus, they're not coalesced by the linker because they're each a different subset?

Andrew Grieve

unread,
Sep 2, 2025, 3:37:53 PMSep 2
to Joe Mason, K. Moon, Mark Mentovai, Adam Rice, Daniel Cheng, cxx
The UI isn't great here, but for inline symbols, every .cc file that uses them is marked as an owner of it, and so the same symbol shows in each of them. In order to not double-count, the symbol's size is divided by the number of owners.

Joe Mason

unread,
Sep 2, 2025, 5:39:23 PMSep 2
to Andrew Grieve, K. Moon, Mark Mentovai, Adam Rice, Daniel Cheng, cxx
Thanks, everything is working as intended then. I was just confused by the UI.
Reply all
Reply to author
Forward
0 new messages