Skip to content

Compilation time improvements for PolymodBaseClassMacro - #523

Merged
AbnormalPoof merged 6 commits into
developfrom
perf/base-class-macro
Oct 5, 2026
Merged

AbnormalPoof merged 6 commits into
developfrom
perf/base-class-macro

Conversation

@EliteMasterEric

Copy link
Copy Markdown
Member

This PR makes several changes to the PolymodBaseClassMacro to improve readability and performance. The changes are focused primarily around optimizing the length of the macro expressions inserted into every class, since if any of these expressions are long or have complicated typing, the effect is multiplied since this macro injects itself into every class.

  • Lookups to locate the PolymodScriptClass are now done in a static function in PolymodScriptBridge. This drastically reduces the complexity of the macro expression, which is important considering that this expression is currently replacing the expression for every function in every class in the project. Reducing roughly forty lines of Haxe down to two dramatically improves the typing step.

  • scriptInit() now always calls PolymodScriptBridge.instantiate(). The code is functionally the same, while being a single line of Haxe instead of 30+. This reduces the amount of code that needs to be typed for every class, improving build performance.

  • buildBaseClass() was calling Context.getBuildFields() every time, and returning the list unmodified if a class were to be skipped by the macro. This is redundant, since you can simply return null to have Haxe continue normally, reducing macro time by not having to compile the list of build fields for every class.

  • removeInlinedFunctionCalls() has been removed entirely. Since the first change means the macro no longer includes a return in a while loop, we no longer impose a non-final return on every function.

  • The @:hscriptAscRoot metadata is added to the class that has had the _asc field added to it. The macro then checks itself and its parents for that metadata instead of calling cls.findField(); this rewritten check is cheaper than the previous one.

The benchmarks I ran showed build times reducing by about 10-15 seconds, with most of the savings being in the execution of buildBaseClass with smaller savings in the typing and C++ code generation steps.

@EliteMasterEric EliteMasterEric self-assigned this Oct 2, 2026
@nykwono

nykwono commented Oct 2, 2026

Copy link
Copy Markdown

I'll test this out when I'm home and update this with my personal results, the only thing I'm wondering is do you know why HL build actions are failing? That's the only thing I'm personally concerned about

@NotHyper-474

Copy link
Copy Markdown
Contributor

I'll test this out when I'm home and update this with my personal results, the only thing I'm wondering is do you know why HL build actions are failing? That's the only thing I'm personally concerned about

That would be caused by these forced inline constructor uses within Heaps. It's just an optimization so that these classes are initialized as values in the stack rather than on the heap(s) (pun intended).
image

The macro ends up adding ASC fields to the class it attempts to inline, and since those are not initialized, Haxe doesn't know what to do and complains about it. To fix it we could either enforce null as the initial value for these fields, or skip these types of classes.

@Kade-github Kade-github left a comment •

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.

Clean compile for me took 10 minutes 36.1 seconds. (Base is surprisingly 9 minutes and 56.8 seconds)
Warm compile for me took 2 minutes and 1.6 seconds. (Base 2 minutes 30 seconds)

A definite improvement (for warm compiles, at least from me), although still super slow.

@nykwono

nykwono commented Oct 2, 2026 •

Copy link
Copy Markdown

I'll test this out when I'm home and update this with my personal results, the only thing I'm wondering is do you know why HL build actions are failing? That's the only thing I'm personally concerned about

That would be caused by these forced inline constructor uses within Heaps. It's just an optimization so that these classes are initialized as values in the stack rather than on the heap(s) (pun intended). image

The macro ends up adding ASC fields to the class it attempts to inline, and since those are not initialized, Haxe doesn't know what to do and complains about it. To fix it we could either enforce null as the initial value for these fields, or skip these types of classes.

Yeah I figured. I remember kolo talking about this exact thing, which makes me wonder if it's really worth it removing removeInlineFunctionCalls entirely? But I don't know how much it affects performance.

@realvirtu

Copy link
Copy Markdown
Contributor

Finally man we needed this

@nykwono nykwono left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Note: This results are tested within Funkin` public-playtest
Develop:

  • Debug (Clean):
    • PolymodBaseClassMacro.buildBaseClass - 10.265s
    • Build Time - 2m 32.1s
  • Debug (Warm):
    • PolymodBaseClassMacro.buildBaseClass - 10.045s
    • Build Time - 1m 5.7s
  • Release (Clean):
    • PolymodBaseClassMacro.buildBaseClass - 10.943s
    • Build Time - 6m 46.9s
  • Release (Warm):
    • PolymodBaseClassMacro.buildBaseClass - 9.888s
    • Build Time - 1m 8s

This PR:

  • Debug (Clean):
    • PolymodBaseClassMacro.buildBaseClass - 1.031s (holy shit)
    • Build Time - 2m 21.3s
  • Debug (Warm):
    • PolymodBaseClassMacro.buildBaseClass - 1.008s
    • Build Time - 57s
  • Release (Clean):
    • PolymodBaseClassMacro.buildBaseClass - 1.007s
    • Build Time - 6m 21.6s
  • Release (Warm):
    • PolymodBaseClassMacro.buildBaseClass - 1.215s (second attempt: 0.957s)
    • Build Time - 58.2s (second attempt: 52.3s)

@NotHyper-474

NotHyper-474 commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Yeah I figured. I remember kolo talking about this exact thing, which makes me wonder if it's really worth it removing removeInlineFunctionCalls entirely? But I don't know how much it affects performance.

Well, I'd say the problem would be the macro trying to make a data container class scriptable (not sure if we would want to be able to like, make a class extend a Matrix, for instance?). But just for the hell of it I decided to add removeInlineFunctionCalls back and... apparently it's not even that expensive? Maybe the way I did it is more efficient, but I dunno.

This PR
image
This PR, but removing inline calls was readded
image
image

@nykwono

nykwono commented Oct 3, 2026

Copy link
Copy Markdown

OKAY? Yeah maybe it's a bit of a bad idea.. I'm wondering what specifically the inline calls are from

@JackXson-Real JackXson-Real left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Went from 2:38 to 1:53 on non-clean compiles!

@realvirtu realvirtu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

better

@AbnormalPoof
AbnormalPoof force-pushed the perf/base-class-macro branch from 021e02f to 802ec5a Compare October 5, 2026 00:46
@AbnormalPoof
AbnormalPoof merged commit adb8517 into develop Oct 5, 2026
9 checks passed
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.

7 participants