Using pointer address as node/symbol id
Nodes and symbols currently store an atomic integer representing their ID, which is lazily assigned upon first use. I was playing with using the pointer addresses as their ID, reducing the size of those structures and avoiding atomic reads on access.
func GetNodeId(node *Node) NodeId {
return NodeId(uint64(uintptr(unsafe.Pointer(node))))
}
func GetSymbolId(symbol *Symbol) SymbolId {
return SymbolId(uint64(uintptr(unsafe.Pointer(symbol))))
}
A potential downside is that KeyBuilder will observe larger numbers, requiring additional bytes to represent various keys. It may also affect ordering as IDs will no longer be strictly increasing, but I believe this shouldn't matter given that ID assignment is already non-deterministic.
A quick attempt passes the hereby test and shows a slight reduction in overall memory usage, but I don't have good insight into actual performance benfits/downsides of this approach.
I am opening this as issue instead of PR to discuss, possibly to learn this has been considered and may have been rejected for some reason. I'd be happy to open a PR if desired.
I don't think we want to do this; we currently have no unsafe code (avoiding a compliance problem). The benefit would have to be pretty significant to want to do this.
To test, I'd do:
$ go test -run=- -bench=. -benchmem -count=10 ./internal/{parser,binder} | tee old.txt
$ git switch ....
$ go test -run=- -bench=. -benchmem -count=10 ./internal/{parser,binder} | tee new.txt
$ benchstat old.txt new.txt
Then post the results. Hoping to add some checker benchmarks soon.
With 64 bit IDs, I think it'd be a better idea to just unconditionally construct them with an ID and then never change it. It won't be lazy anymore, but we're not going to "run out".
I just read this:
Go very carefully does not guarantee that this past address is the same as the current address, although it always will be today.
Which makes this a no-go (pun intended) altogether.
Yes, they've been thinking about doing moving GC, so it's definitely a dangerous operation.
old.txtis a7e0eb479cc0c197d9858acec930272644e814e5address.txtis using pointer addresses as node and symbol IDseager.txtis eagerly assigning node and symbol IDs during construction (still an atomics add, but reads become non-atomic loads)
$ benchstat old.txt address.txt
goos: darwin
goarch: arm64
pkg: github.com/microsoft/typescript-go/internal/binder
cpu: Apple M1 Max
│ old.txt │ address.txt │
│ sec/op │ sec/op vs base │
Bind/empty.ts-10 154.2n ± 4% 153.8n ± 5% ~ (p=0.987 n=10)
Bind/checker.ts-10 12.48m ± 3% 12.26m ± 5% -1.84% (p=0.015 n=10)
Bind/dom.generated.d.ts-10 4.967m ± 3% 4.716m ± 6% ~ (p=0.052 n=10)
Bind/Herebyfile.mjs-10 194.8µ ± 5% 177.5µ ± 3% -8.88% (p=0.000 n=10)
Bind/jsxComplexSignatureHasApplicabilityError.tsx-10 86.35µ ± 5% 83.03µ ± 5% -3.84% (p=0.019 n=10)
geomean 174.3µ 167.3µ -4.03%
│ old.txt │ address.txt │
│ B/op │ B/op vs base │
Bind/empty.ts-10 192.0 ± 0% 192.0 ± 0% ~ (p=1.000 n=10) ¹
Bind/checker.ts-10 7.206Mi ± 0% 7.068Mi ± 0% -1.92% (p=0.000 n=10)
Bind/dom.generated.d.ts-10 4.873Mi ± 0% 4.707Mi ± 0% -3.39% (p=0.000 n=10)
Bind/Herebyfile.mjs-10 173.8Ki ± 0% 162.6Ki ± 0% -6.43% (p=0.000 n=10)
Bind/jsxComplexSignatureHasApplicabilityError.tsx-10 114.5Ki ± 0% 108.7Ki ± 0% -5.06% (p=0.000 n=10)
geomean 168.9Ki 163.2Ki -3.39%
¹ all samples are equal
│ old.txt │ address.txt │
│ allocs/op │ allocs/op vs base │
Bind/empty.ts-10 2.000 ± 0% 2.000 ± 0% ~ (p=1.000 n=10) ¹
Bind/checker.ts-10 13.69k ± 0% 13.70k ± 0% +0.05% (p=0.000 n=10)
Bind/dom.generated.d.ts-10 14.68k ± 0% 14.69k ± 0% +0.06% (p=0.000 n=10)
Bind/Herebyfile.mjs-10 342.0 ± 0% 342.0 ± 0% ~ (p=1.000 n=10) ¹
Bind/jsxComplexSignatureHasApplicabilityError.tsx-10 278.0 ± 0% 278.0 ± 0% ~ (p=1.000 n=10) ¹
geomean 520.5 520.6 +0.02%
¹ all samples are equal
pkg: github.com/microsoft/typescript-go/internal/parser
│ old.txt │ address.txt │
│ sec/op │ sec/op vs base │
Parse/empty.ts/tsc-10 355.2n ± 1% 360.9n ± 2% +1.59% (p=0.009 n=10)
Parse/empty.ts/server-10 369.6n ± 2% 363.9n ± 8% ~ (p=0.542 n=10)
Parse/checker.ts/tsc-10 32.25m ± 4% 31.83m ± 1% ~ (p=0.247 n=10)
Parse/checker.ts/server-10 32.28m ± 3% 31.47m ± 2% ~ (p=0.089 n=10)
Parse/dom.generated.d.ts/tsc-10 11.11m ± 4% 10.99m ± 5% ~ (p=0.123 n=10)
Parse/dom.generated.d.ts/server-10 16.32m ± 1% 16.28m ± 5% ~ (p=0.912 n=10)
Parse/Herebyfile.mjs/tsc-10 624.1µ ± 3% 585.9µ ± 4% -6.12% (p=0.000 n=10)
Parse/Herebyfile.mjs/server-10 609.4µ ± 3% 613.5µ ± 2% ~ (p=1.000 n=10)
Parse/jsxComplexSignatureHasApplicabilityError.tsx/tsc-10 194.6µ ± 2% 179.5µ ± 2% -7.76% (p=0.000 n=10)
Parse/jsxComplexSignatureHasApplicabilityError.tsx/server-10 301.0µ ± 2% 295.9µ ± 3% ~ (p=0.353 n=10)
geomean 472.2µ 462.7µ -2.03%
│ old.txt │ address.txt │
│ B/op │ B/op vs base │
Parse/empty.ts/tsc-10 816.0 ± 0% 800.0 ± 0% -1.96% (p=0.000 n=10)
Parse/empty.ts/server-10 816.0 ± 0% 800.0 ± 0% -1.96% (p=0.000 n=10)
Parse/checker.ts/tsc-10 24.21Mi ± 0% 21.97Mi ± 0% -9.23% (p=0.000 n=10)
Parse/checker.ts/server-10 24.39Mi ± 0% 22.15Mi ± 0% -9.18% (p=0.000 n=10)
Parse/dom.generated.d.ts/tsc-10 8.648Mi ± 0% 7.872Mi ± 0% -8.97% (p=0.000 n=10)
Parse/dom.generated.d.ts/server-10 11.01Mi ± 0% 10.15Mi ± 0% -7.86% (p=0.000 n=10)
Parse/Herebyfile.mjs/tsc-10 437.3Ki ± 0% 406.3Ki ± 0% -7.09% (p=0.000 n=10)
Parse/Herebyfile.mjs/server-10 437.5Ki ± 0% 406.3Ki ± 0% -7.12% (p=0.000 n=10)
Parse/jsxComplexSignatureHasApplicabilityError.tsx/tsc-10 159.5Ki ± 0% 150.6Ki ± 0% -5.58% (p=0.000 n=10)
Parse/jsxComplexSignatureHasApplicabilityError.tsx/server-10 236.8Ki ± 0% 223.2Ki ± 0% -5.76% (p=0.000 n=10)
geomean 441.9Ki 413.1Ki -6.51%
│ old.txt │ address.txt │
│ allocs/op │ allocs/op vs base │
Parse/empty.ts/tsc-10 4.000 ± 0% 4.000 ± 0% ~ (p=1.000 n=10) ¹
Parse/empty.ts/server-10 4.000 ± 0% 4.000 ± 0% ~ (p=1.000 n=10) ¹
Parse/checker.ts/tsc-10 28.80k ± 0% 28.78k ± 0% -0.10% (p=0.000 n=10)
Parse/checker.ts/server-10 29.42k ± 0% 29.39k ± 0% -0.09% (p=0.000 n=10)
Parse/dom.generated.d.ts/tsc-10 22.56k ± 0% 22.55k ± 0% -0.04% (p=0.000 n=10)
Parse/dom.generated.d.ts/server-10 29.49k ± 0% 29.48k ± 0% -0.04% (p=0.000 n=10)
Parse/Herebyfile.mjs/tsc-10 1.859k ± 0% 1.859k ± 0% ~ (p=1.000 n=10) ¹
Parse/Herebyfile.mjs/server-10 1.859k ± 0% 1.859k ± 0% ~ (p=1.000 n=10)
Parse/jsxComplexSignatureHasApplicabilityError.tsx/tsc-10 346.0 ± 0% 347.0 ± 0% +0.29% (p=0.000 n=10)
Parse/jsxComplexSignatureHasApplicabilityError.tsx/server-10 612.0 ± 0% 613.0 ± 0% +0.16% (p=0.000 n=10)
geomean 1.208k 1.208k +0.02%
¹ all samples are equal
$ benchstat old.txt eager.txt
benchstat old.txt eager.txt
goos: darwin
goarch: arm64
pkg: github.com/microsoft/typescript-go/internal/binder
cpu: Apple M1 Max
│ old.txt │ eager.txt │
│ sec/op │ sec/op vs base │
Bind/empty.ts-10 154.2n ± 4% 157.0n ± 4% ~ (p=0.123 n=10)
Bind/checker.ts-10 12.48m ± 3% 12.39m ± 2% ~ (p=0.579 n=10)
Bind/dom.generated.d.ts-10 4.967m ± 3% 4.876m ± 14% ~ (p=0.481 n=10)
Bind/Herebyfile.mjs-10 194.8µ ± 5% 186.0µ ± 3% -4.48% (p=0.043 n=10)
Bind/jsxComplexSignatureHasApplicabilityError.tsx-10 86.35µ ± 5% 86.28µ ± 5% ~ (p=0.393 n=10)
geomean 174.3µ 172.4µ -1.09%
│ old.txt │ eager.txt │
│ B/op │ B/op vs base │
Bind/empty.ts-10 192.0 ± 0% 192.0 ± 0% ~ (p=1.000 n=10) ¹
Bind/checker.ts-10 7.206Mi ± 0% 7.206Mi ± 0% ~ (p=0.275 n=10)
Bind/dom.generated.d.ts-10 4.873Mi ± 0% 4.873Mi ± 0% ~ (p=0.628 n=10)
Bind/Herebyfile.mjs-10 173.8Ki ± 0% 173.8Ki ± 0% ~ (p=1.000 n=10) ¹
Bind/jsxComplexSignatureHasApplicabilityError.tsx-10 114.5Ki ± 0% 114.5Ki ± 0% ~ (p=1.000 n=10) ¹
geomean 168.9Ki 168.9Ki +0.00%
¹ all samples are equal
│ old.txt │ eager.txt │
│ allocs/op │ allocs/op vs base │
Bind/empty.ts-10 2.000 ± 0% 2.000 ± 0% ~ (p=1.000 n=10) ¹
Bind/checker.ts-10 13.69k ± 0% 13.69k ± 0% ~ (p=1.000 n=10) ¹
Bind/dom.generated.d.ts-10 14.68k ± 0% 14.68k ± 0% ~ (p=1.000 n=10) ¹
Bind/Herebyfile.mjs-10 342.0 ± 0% 342.0 ± 0% ~ (p=1.000 n=10) ¹
Bind/jsxComplexSignatureHasApplicabilityError.tsx-10 278.0 ± 0% 278.0 ± 0% ~ (p=1.000 n=10) ¹
geomean 520.5 520.5 +0.00%
¹ all samples are equal
pkg: github.com/microsoft/typescript-go/internal/parser
│ old.txt │ eager.txt │
│ sec/op │ sec/op vs base │
Parse/empty.ts/tsc-10 355.2n ± 1% 366.6n ± 7% +3.21% (p=0.000 n=10)
Parse/empty.ts/server-10 369.6n ± 2% 375.2n ± 5% +1.54% (p=0.000 n=10)
Parse/checker.ts/tsc-10 32.25m ± 4% 33.24m ± 2% +3.06% (p=0.001 n=10)
Parse/checker.ts/server-10 32.28m ± 3% 33.95m ± 4% +5.19% (p=0.005 n=10)
Parse/dom.generated.d.ts/tsc-10 11.11m ± 4% 11.21m ± 3% ~ (p=0.089 n=10)
Parse/dom.generated.d.ts/server-10 16.32m ± 1% 16.51m ± 1% +1.19% (p=0.019 n=10)
Parse/Herebyfile.mjs/tsc-10 624.1µ ± 3% 622.0µ ± 5% ~ (p=0.971 n=10)
Parse/Herebyfile.mjs/server-10 609.4µ ± 3% 632.1µ ± 6% ~ (p=0.105 n=10)
Parse/jsxComplexSignatureHasApplicabilityError.tsx/tsc-10 194.6µ ± 2% 183.6µ ± 2% -5.63% (p=0.000 n=10)
Parse/jsxComplexSignatureHasApplicabilityError.tsx/server-10 301.0µ ± 2% 308.7µ ± 5% ~ (p=0.123 n=10)
geomean 472.2µ 479.4µ +1.51%
│ old.txt │ eager.txt │
│ B/op │ B/op vs base │
Parse/empty.ts/tsc-10 816.0 ± 0% 816.0 ± 0% ~ (p=1.000 n=10) ¹
Parse/empty.ts/server-10 816.0 ± 0% 816.0 ± 0% ~ (p=1.000 n=10) ¹
Parse/checker.ts/tsc-10 24.21Mi ± 0% 24.21Mi ± 0% -0.01% (p=0.007 n=10)
Parse/checker.ts/server-10 24.39Mi ± 0% 24.39Mi ± 0% ~ (p=0.631 n=10)
Parse/dom.generated.d.ts/tsc-10 8.648Mi ± 0% 8.647Mi ± 0% ~ (p=0.280 n=10)
Parse/dom.generated.d.ts/server-10 11.01Mi ± 0% 11.01Mi ± 0% ~ (p=0.631 n=10)
Parse/Herebyfile.mjs/tsc-10 437.3Ki ± 0% 437.3Ki ± 0% ~ (p=0.393 n=10)
Parse/Herebyfile.mjs/server-10 437.5Ki ± 0% 437.4Ki ± 0% ~ (p=0.165 n=10)
Parse/jsxComplexSignatureHasApplicabilityError.tsx/tsc-10 159.5Ki ± 0% 159.5Ki ± 0% ~ (p=0.393 n=10)
Parse/jsxComplexSignatureHasApplicabilityError.tsx/server-10 236.8Ki ± 0% 236.8Ki ± 0% ~ (p=0.971 n=10)
geomean 441.9Ki 441.8Ki -0.00%
¹ all samples are equal
│ old.txt │ eager.txt │
│ allocs/op │ allocs/op vs base │
Parse/empty.ts/tsc-10 4.000 ± 0% 4.000 ± 0% ~ (p=1.000 n=10) ¹
Parse/empty.ts/server-10 4.000 ± 0% 4.000 ± 0% ~ (p=1.000 n=10) ¹
Parse/checker.ts/tsc-10 28.80k ± 0% 28.80k ± 0% -0.00% (p=0.001 n=10)
Parse/checker.ts/server-10 29.42k ± 0% 29.42k ± 0% ~ (p=1.000 n=10)
Parse/dom.generated.d.ts/tsc-10 22.56k ± 0% 22.56k ± 0% ~ (p=1.000 n=10)
Parse/dom.generated.d.ts/server-10 29.49k ± 0% 29.49k ± 0% ~ (p=0.526 n=10)
Parse/Herebyfile.mjs/tsc-10 1.859k ± 0% 1.859k ± 0% ~ (p=1.000 n=10) ¹
Parse/Herebyfile.mjs/server-10 1.859k ± 0% 1.859k ± 0% ~ (p=1.000 n=10) ¹
Parse/jsxComplexSignatureHasApplicabilityError.tsx/tsc-10 346.0 ± 0% 346.0 ± 0% ~ (p=1.000 n=10) ¹
Parse/jsxComplexSignatureHasApplicabilityError.tsx/server-10 612.0 ± 0% 612.0 ± 0% ~ (p=1.000 n=10) ¹
geomean 1.208k 1.208k -0.00%
¹ all samples are equal
$ benchstat eager.txt address.txt
goos: darwin
goarch: arm64
pkg: github.com/microsoft/typescript-go/internal/binder
cpu: Apple M1 Max
│ eager.txt │ address.txt │
│ sec/op │ sec/op vs base │
Bind/empty.ts-10 157.0n ± 4% 153.8n ± 5% ~ (p=0.315 n=10)
Bind/checker.ts-10 12.39m ± 2% 12.26m ± 5% ~ (p=0.143 n=10)
Bind/dom.generated.d.ts-10 4.876m ± 14% 4.716m ± 6% -3.29% (p=0.029 n=10)
Bind/Herebyfile.mjs-10 186.0µ ± 3% 177.5µ ± 3% -4.61% (p=0.002 n=10)
Bind/jsxComplexSignatureHasApplicabilityError.tsx-10 86.28µ ± 5% 83.03µ ± 5% -3.77% (p=0.015 n=10)
geomean 172.4µ 167.3µ -2.97%
│ eager.txt │ address.txt │
│ B/op │ B/op vs base │
Bind/empty.ts-10 192.0 ± 0% 192.0 ± 0% ~ (p=1.000 n=10) ¹
Bind/checker.ts-10 7.206Mi ± 0% 7.068Mi ± 0% -1.92% (p=0.000 n=10)
Bind/dom.generated.d.ts-10 4.873Mi ± 0% 4.707Mi ± 0% -3.39% (p=0.000 n=10)
Bind/Herebyfile.mjs-10 173.8Ki ± 0% 162.6Ki ± 0% -6.43% (p=0.000 n=10)
Bind/jsxComplexSignatureHasApplicabilityError.tsx-10 114.5Ki ± 0% 108.7Ki ± 0% -5.06% (p=0.000 n=10)
geomean 168.9Ki 163.2Ki -3.39%
¹ all samples are equal
│ eager.txt │ address.txt │
│ allocs/op │ allocs/op vs base │
Bind/empty.ts-10 2.000 ± 0% 2.000 ± 0% ~ (p=1.000 n=10) ¹
Bind/checker.ts-10 13.69k ± 0% 13.70k ± 0% +0.05% (p=0.000 n=10)
Bind/dom.generated.d.ts-10 14.68k ± 0% 14.69k ± 0% +0.06% (p=0.000 n=10)
Bind/Herebyfile.mjs-10 342.0 ± 0% 342.0 ± 0% ~ (p=1.000 n=10) ¹
Bind/jsxComplexSignatureHasApplicabilityError.tsx-10 278.0 ± 0% 278.0 ± 0% ~ (p=1.000 n=10) ¹
geomean 520.5 520.6 +0.02%
¹ all samples are equal
pkg: github.com/microsoft/typescript-go/internal/parser
│ eager.txt │ address.txt │
│ sec/op │ sec/op vs base │
Parse/empty.ts/tsc-10 366.6n ± 7% 360.9n ± 2% -1.57% (p=0.037 n=10)
Parse/empty.ts/server-10 375.2n ± 5% 363.9n ± 8% ~ (p=0.052 n=10)
Parse/checker.ts/tsc-10 33.24m ± 2% 31.83m ± 1% -4.25% (p=0.000 n=10)
Parse/checker.ts/server-10 33.95m ± 4% 31.47m ± 2% -7.32% (p=0.000 n=10)
Parse/dom.generated.d.ts/tsc-10 11.21m ± 3% 10.99m ± 5% -1.96% (p=0.009 n=10)
Parse/dom.generated.d.ts/server-10 16.51m ± 1% 16.28m ± 5% ~ (p=0.393 n=10)
Parse/Herebyfile.mjs/tsc-10 622.0µ ± 5% 585.9µ ± 4% -5.81% (p=0.007 n=10)
Parse/Herebyfile.mjs/server-10 632.1µ ± 6% 613.5µ ± 2% ~ (p=0.052 n=10)
Parse/jsxComplexSignatureHasApplicabilityError.tsx/tsc-10 183.6µ ± 2% 179.5µ ± 2% -2.26% (p=0.001 n=10)
Parse/jsxComplexSignatureHasApplicabilityError.tsx/server-10 308.7µ ± 5% 295.9µ ± 3% -4.15% (p=0.023 n=10)
geomean 479.4µ 462.7µ -3.48%
│ eager.txt │ address.txt │
│ B/op │ B/op vs base │
Parse/empty.ts/tsc-10 816.0 ± 0% 800.0 ± 0% -1.96% (p=0.000 n=10)
Parse/empty.ts/server-10 816.0 ± 0% 800.0 ± 0% -1.96% (p=0.000 n=10)
Parse/checker.ts/tsc-10 24.21Mi ± 0% 21.97Mi ± 0% -9.22% (p=0.000 n=10)
Parse/checker.ts/server-10 24.39Mi ± 0% 22.15Mi ± 0% -9.18% (p=0.000 n=10)
Parse/dom.generated.d.ts/tsc-10 8.647Mi ± 0% 7.872Mi ± 0% -8.97% (p=0.000 n=10)
Parse/dom.generated.d.ts/server-10 11.01Mi ± 0% 10.15Mi ± 0% -7.86% (p=0.000 n=10)
Parse/Herebyfile.mjs/tsc-10 437.3Ki ± 0% 406.3Ki ± 0% -7.09% (p=0.000 n=10)
Parse/Herebyfile.mjs/server-10 437.4Ki ± 0% 406.3Ki ± 0% -7.09% (p=0.000 n=10)
Parse/jsxComplexSignatureHasApplicabilityError.tsx/tsc-10 159.5Ki ± 0% 150.6Ki ± 0% -5.59% (p=0.000 n=10)
Parse/jsxComplexSignatureHasApplicabilityError.tsx/server-10 236.8Ki ± 0% 223.2Ki ± 0% -5.76% (p=0.000 n=10)
geomean 441.8Ki 413.1Ki -6.50%
│ eager.txt │ address.txt │
│ allocs/op │ allocs/op vs base │
Parse/empty.ts/tsc-10 4.000 ± 0% 4.000 ± 0% ~ (p=1.000 n=10) ¹
Parse/empty.ts/server-10 4.000 ± 0% 4.000 ± 0% ~ (p=1.000 n=10) ¹
Parse/checker.ts/tsc-10 28.80k ± 0% 28.78k ± 0% -0.09% (p=0.000 n=10)
Parse/checker.ts/server-10 29.42k ± 0% 29.39k ± 0% -0.09% (p=0.000 n=10)
Parse/dom.generated.d.ts/tsc-10 22.56k ± 0% 22.55k ± 0% -0.04% (p=0.000 n=10)
Parse/dom.generated.d.ts/server-10 29.49k ± 0% 29.48k ± 0% -0.04% (p=0.000 n=10)
Parse/Herebyfile.mjs/tsc-10 1.859k ± 0% 1.859k ± 0% ~ (p=1.000 n=10) ¹
Parse/Herebyfile.mjs/server-10 1.859k ± 0% 1.859k ± 0% ~ (p=1.000 n=10)
Parse/jsxComplexSignatureHasApplicabilityError.tsx/tsc-10 346.0 ± 0% 347.0 ± 0% +0.29% (p=0.000 n=10)
Parse/jsxComplexSignatureHasApplicabilityError.tsx/server-10 612.0 ± 0% 613.0 ± 0% +0.16% (p=0.000 n=10)
geomean 1.208k 1.208k +0.02%
¹ all samples are equal
The effects are mostly expected; eager slows down the binder because of the atomic operations, as benefits will only become noticeable in the check phase. Exploiting address pointers does show a meaningful improvement in these tests, but this is fundamentally at odds with Go's runtime guarantees so may not be a desired way forward.
With checker benchmarks this may start to show a more faithful picture of overall impact.
I think we're going to want to do the eager version of this.
For the public API we're going to need for IDs to fit within JS's Number.MAX_SAFE_INTEGER, which means we cannot use the pointer value itself as an ID (outside of it generally not being safe).