Problem
Packages with many global variables requiring runtime initialization compile slowly because the compiler generates a single monolithic init function containing all initialization statements.
This pattern doesn't typically occur in handwritten code, but can happen in generated code (e.g., Thrift).
Large functions are expensive to compile, especially during SSA optimization passes. For comparison, compiling a package with 500,000 items:
| Package contents |
Compile time |
| Static variables |
5 seconds |
| Functions |
23 seconds |
| Dynamic initializers |
~1 minute |
The dynamic initializer in this example is fmt.Sprintf("hello").
The dynamic initializers case is significantly slower because all 500,000 initialization statements end up in a single giant init function.
Full benchmark code: https://github.com/podtserkovskiy/go-dynamic-init-bench
We briefly discussed this problem at GopherCon 2025 and the consensus was that it's worth trying to optimize.
Proposed Solution
Split the synthetic init function into multiple smaller functions when the number of initialization statements exceeds N (let's say 1000). Statements are distributed into init.var.N functions (e.g., init.var.0, init.var.1, etc.) which are added to the .inittask structure for runtime execution in order.
I've tried to implement this. Here are the benchmark results:
| Approach |
Compile time |
| Dynamic initializers |
~1 minute |
| Manually split code |
14 seconds |
| Code split in the compiler |
14 seconds |
The numbers are better than before but not as good as pre-generated code.
I'm going to publish a CL with the implementation shortly.
Problem
Packages with many global variables requiring runtime initialization compile slowly because the compiler generates a single monolithic
initfunction containing all initialization statements.This pattern doesn't typically occur in handwritten code, but can happen in generated code (e.g., Thrift).
Large functions are expensive to compile, especially during SSA optimization passes. For comparison, compiling a package with 500,000 items:
The dynamic initializer in this example is
fmt.Sprintf("hello").The dynamic initializers case is significantly slower because all 500,000 initialization statements end up in a single giant
initfunction.Full benchmark code: https://github.com/podtserkovskiy/go-dynamic-init-bench
We briefly discussed this problem at GopherCon 2025 and the consensus was that it's worth trying to optimize.
Proposed Solution
Split the synthetic
initfunction into multiple smaller functions when the number of initialization statements exceeds N (let's say 1000). Statements are distributed intoinit.var.Nfunctions (e.g.,init.var.0,init.var.1, etc.) which are added to the.inittaskstructure for runtime execution in order.I've tried to implement this. Here are the benchmark results:
The numbers are better than before but not as good as pre-generated code.
I'm going to publish a CL with the implementation shortly.