Skip to content

Skip the C++ construct walk with a class flag - #413

Open
DTW-Thalion wants to merge 2 commits into
gnustep:masterfrom
DTW-Thalion:perf/cxx-construct-class-flags
Open

Skip the C++ construct walk with a class flag#413
DTW-Thalion wants to merge 2 commits into
gnustep:masterfrom
DTW-Thalion:perf/cxx-construct-class-flags

Conversation

@DTW-Thalion

@DTW-Thalion DTW-Thalion commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

call_cxx_construct_for_class recurses up the superclass chain on every allocation. A class whose superclasses have no .cxx_construct between them walks the chain to do nothing, which is nearly every class in a Foundation workload.

A class flag records that neither a class nor any superclass has one. It is set where the dispatch table is created, after the superclass chain is initialised, and cleared where installMethodInDtable assigns cxx_construct, so a method installed later invalidates it. init_cxx_selectors registers the selector once, in place of a null check on every recursive call.

Minimum of 5 interleaved rounds against master, on whole workloads: Foundation churn under ARC 2.3% faster, allocation of ARC compiled classes about 20% faster, since ARC emits no .cxx_construct and the walk ends immediately. A genomic coder that is AVX2 bound is unchanged.

The matching flag for .cxx_destruct was measured and dropped. It was worth 0.3 to 0.6 points, at or below the spread of the machine, against a branch in every destruction.

ctest passes 198 of 198. Draft because the flag is read on the allocation path without synchronisation, as the other class flags are.

Comment thread dtable.c Outdated
{
if (NULL == class->cxx_destruct)
{
objc_set_class_flag(class, objc_class_flag_no_cxx_destruct);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm not quite following the logic here, it looks as if this is never set. I'd expect each class being resolved to initialise it to the superclass's value and then clear it if the superclass had it set but we found a matching method.

Comment thread runtime.c
}

if (cls->super_class)
if (cls->super_class &&

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why not do the test in cls here and then recurse at the end?

@davidchisnall

Copy link
Copy Markdown
Member

This would be good to have some tests for. These methods are generated by the compiler if an ivar is a C++ class with a constructor or destructor.

Note: I'm not 100% sure that the destruct optimisation will be a net win in modern Objective-C, because the compiler emits the .cxx_destruct method for every class with Objective-C object members in ARC, so most classes will have this. GNUstep Base is unusual in that it's using a 20-year-old Objective-C version.

@DTW-Thalion

Copy link
Copy Markdown
Contributor Author

This would be good to have some tests for. These methods are generated by the compiler if an ivar is a C++ class with a constructor or destructor.

Note: I'm not 100% sure that the destruct optimisation will be a net win in modern Objective-C, because the compiler emits the .cxx_destruct method for every class with Objective-C object members in ARC, so most classes will have this. GNUstep Base is unusual in that it's using a 20-year-old Objective-C version.

Agreed - #413 is a work in progress, I worked on this during my flight yesterday, so have some more instrumented tests I need to do - once I am comfortable I'll send you a better report and we can decide.

call_cxx_construct_for_class recurses up the superclass chain on every
allocation. A class whose superclasses have no .cxx_construct between them
walks the whole chain to do nothing, and the check is a no-op for nearly
every class in a Foundation workload.

A class flag means this class and all of its superclasses have no
.cxx_construct, so the walk ends at the first class that has one. It is set
where the dispatch table is built, by which point the superclass chain is
resolved, and cleared where a method is installed.

The selector is registered in the init phase rather than tested for null on
every recursive call.

Measured on whole workloads rather than a loop around the call. Against
master, minimum of 5 interleaved rounds: Foundation string, array and
dictionary churn under ARC 2.3% faster, and allocation of classes compiled
with ARC 19.7% faster, the latter because ARC emits no .cxx_construct at
all so the walk ends immediately. A genomic coder that is AVX2 bound with
few allocations is unchanged.

The matching flag for .cxx_destruct is not here. It measured between 0.3
and 0.6 points on the same workloads, which is at or below the run to run
spread of the machine, and it costs a conditional branch in the destruction
path.

Suite passes, 198 of 198.
@DTW-Thalion
DTW-Thalion force-pushed the perf/cxx-construct-class-flags branch from 1ee020d to ca1603f Compare August 13, 2026 14:47
@DTW-Thalion DTW-Thalion changed the title Skip the C++ construct and destruct walks with a class flag Skip the C++ construct walk with a class flag Aug 13, 2026
@DTW-Thalion
DTW-Thalion marked this pull request as ready for review August 13, 2026 16:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants