-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Wrap native map to work around 2**24 element limit #50310
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Wrap native map to work around 2**24 element limit #50310
Conversation
@typescript-bot perf test this |
Heya @weswigham, I've started to run the perf test suite on this PR at 3448841. You can monitor the build here. Update: The results are in! |
@weswigham Here they are:
CompilerComparison Report - main..50310
System
Hosts
Scenarios
TSServerComparison Report - main..50310
System
Hosts
Scenarios
Developer Information: |
@typescript-bot perf test this (just in case the assertion affects things) |
Heya @weswigham, I've started to run the perf test suite on this PR at e65cf19. You can monitor the build here. Update: The results are in! |
@weswigham Here they are:
CompilerComparison Report - main..50310
System
Hosts
Scenarios
TSServerComparison Report - main..50310
System
Hosts
Scenarios
Developer Information: |
@typescript-bot perf test this again lmao |
Heya @weswigham, I've started to run the perf test suite on this PR at a54799f. You can monitor the build here. Update: The results are in! |
@weswigham Here they are:
CompilerComparison Report - main..50310
System
Hosts
Scenarios
TSServerComparison Report - main..50310
System
Hosts
Scenarios
Developer Information: |
@ahejlsberg I've added a diagnostic for source elements which trigger more than 1 million comparisons on their own. That number isn't wholly arbitrary - 1 million only takes a few seconds for simple types, which is fast enough that you may not notice the possible perf issue without the error, and is just below how many comparisons two unions of 2^10 elements (1024) would take to compare naively. (Our union element cap is 100,000, so you can go way higher in terms of union size (2^16 is fine, 2^17 is not) - but these large unions are fine to use if and only if they consistently hit our optimized codepaths or aren't compared against one another in the naive quadradic way!) |
// get better perf, or the constructs in use may be a good candidate for specialized optimizations | ||
// in the compiler itself to reduce the amount of work required. | ||
if (assignabilityCacheContribution > 4_000_000) { | ||
error(node, Diagnostics.This_source_element_is_ultimately_responsible_for_0_million_type_comparisons_It_is_likely_very_slow_and_may_impact_editor_performance_Simplify_the_types_in_use, Math.floor(assignabilityCacheContribution / 1_000_000)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As an aside, since the error is about cache sizes, it's inherently unstable - the first location to do these comparisons will get this error, others that do the same comparisons will not. Historically, location-unstable errors like this haven't been good for incremental build and the like, since the errors shifting mucks with up-to-dateness checks. This could maybe be worked around by indicating this error status in the relationship cache itself (eg, this top-level comparison triggered this overflow, so report the complexity error at every site where this comparison is performed), but given the likely rarity of this error, I'm not sure how worth it that'd be to do, given the complexity that'd entail compared to how simple this is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@weswigham I like the detailed explanation, thank you 👍 . I think, acknowledging the instability in cache sizes and its potential impact on incremental builds is crucial. Regarding the error status, I wonder if there's a simpler way to provide consistent feedback for similar comparisons without compromising complexity. Perhaps exploring a middle ground could help maintain simplicity while addressing occasional instability?
// By manufacturing the fallback in here, we guarantee it has a higher typeid than the bit strings, | ||
// and thus is sorted to the end of the union, guaranteeing relationship checking passes with a maximal | ||
// number of comparisons when a naive comparison is done (guaranteeing this test is slow) | ||
return null as any as Box<ElevenBits | (FallbackPrefix extends never ? never : `${FallbackPrefix}${string}`)>; // return type is a union |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a double-aside, while writing this speedrun-to-slow-the-compiler-down test, I've also come up with some heuristics for union comparisons that could probably make this test specifically complete in linear time/comparisons.
Specifically, the "union of specific things with a generalized fallback member" pattern is very common. If we store the first target member that passes and check it first for subsequent source members, it can shortcut iteration in these scenarios and prevent a quadratic search. Similar element-index-carryover could also be used to handle corresponding-unions-but-missing-a-element better, like comparing A | B | D
with A | B | C | D
, or, more generally, any filtered union with the original. I'll probably put up a PR for that at some point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that the same / similar to #47511?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so, since that's talking about conditional type distribution and I'm just talking about relating two unions. Specifically, in that case, it's about how once you have selected both branch types (assuming they don't use the check type in them), you can stop distributing, since further results won't change the outcome. Similar naive complexity, though.
Technically this fixes the crash in #50290, though not the underlying change causing us to make 16M more comparisons (and accompanying worse perf).