-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
[red-knot] Remove Type::None
#14024
[red-knot] Remove Type::None
#14024
Conversation
917ddd1
to
0534f31
Compare
CodSpeed Performance ReportMerging #14024 will degrade performances by 4.32%Comparing Summary
Benchmarks breakdown
|
|
(Type::Instance(self_class), Type::Instance(target_class)) | ||
if self_class.is_known(db, KnownClass::NoneType) => | ||
{ | ||
target_class.is_known(db, KnownClass::NoneType) | ||
} |
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.
We should be able to remove this branch too once we understand that NoneType
is a subclass of object
(my MRO PR is coming soon!!)
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.
Note that this is about NoneType <: NoneType
. The X <: object
case is handled further above.
I'm actually surprised that NoneType <: NoneType
doesn't work without this. We have a other == self
check in is_subclass_of
on the ClassType
s. Is that not working correctly? Is it actually checking for equal classes or for some other identity. Like just comparing salsa IDs?
my MRO PR is coming soon!!
That will definitely help us remove some cases!
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.
self == other
is indeed just checking salsa IDs. But Salsa IDs are assigned by Salsa interning, which interns based on struct equality, so that should be the same thing as an Eq
check.
It would be good to understand what's happening here. Let me know if you'd like another pair of eyes on it. Maybe it's related to the fact that there are multiple NoneType
in typeshed? (The one in _typeshed
and the one in types
for more recent Python versions.)
Also, I think this branch should go into is_equivalent_to
instead of is_subtype_of
.
0ff025a
to
1735975
Compare
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.
Looks great!
I wonder how much of the perf regression is due to the fact that currently I think every None
ends up being a union of the None
defined in _typeshed
and the one defined intypes
? This could mean we spend a lot more time in recursive union handling for things like is_subtype_of
? If so, this should be largely mitigated by sys.version_info
checking.
(Type::Instance(self_class), Type::Instance(target_class)) | ||
if self_class.is_known(db, KnownClass::NoneType) => | ||
{ | ||
target_class.is_known(db, KnownClass::NoneType) | ||
} |
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.
self == other
is indeed just checking salsa IDs. But Salsa IDs are assigned by Salsa interning, which interns based on struct equality, so that should be the same thing as an Eq
check.
It would be good to understand what's happening here. Let me know if you'd like another pair of eyes on it. Maybe it's related to the fact that there are multiple NoneType
in typeshed? (The one in _typeshed
and the one in types
for more recent Python versions.)
Also, I think this branch should go into is_equivalent_to
instead of is_subtype_of
.
I took a look at the perf regression What I spotted is that this branch now spends 5.6% of total time inside Expanding the tree shows that the difference comes from @lru_cache(maxsize=None)
def cached_tz(hour_str: str, minute_str: str, sign_str: str) -> timezone:
sign = 1 if sign_str == "+" else -1
return timezone(
timedelta(
hours=sign * int(hour_str),
minutes=sign * int(minute_str),
)
) Inside I tried to debug if we now infer a more accurate type for from functools import lru_cache
from typing import reveal_type
reveal_type(lru_cache(maxsize=None)) The main thing that's puzzling me.... Why is the Ohh... never mind. I looked at the warmup phase instead of the incremental path. This is for the incremental part The finding is the same... and, for some reason, |
Looking at the salsa code. What this suggests is that we don't reuse the same salsa ids and, because of that have cache misses... |
I'm not sure what's happening and/or if this is a bug in the benchmark. I copied the tomllib and manually ran the red knot CLI in watch mode and the salsa events indicate that it uses the memoized values except for |
Okay, I might still have looked at the wrong profiles. I now created a PR to use a named closure for the incremental and warmup vs to avoid this in the future. I haven't looked at it too closely because salsa's maybe changed after profiles are a bit of a pain but I strongly suspect that the difference mainly comes from that salsa now has to validate the ingredients for the module defining |
Is this a speculation or is there evidence pointing to it? I couldn't really follow which comments here are based on looking at the wrong part of the profile :) This could be, though. I was thinking we would already have a dependence on If this is the cause of the regression, it's mostly just a weakness in our benchmark. Real-world codebases are likely to already rely on the I still think it's worth seeing whether Salsa-caching the resolution of the type of |
f5f010f
to
c7d4bdb
Compare
Performance investigation
This also explains why I didn't see a performance regression when comparing this branch with main on a larger codebase (
After talking to @MichaReiser, I removed the explicit caching of the |
503aa46
to
c7d4bdb
Compare
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've only skimmed, but this all LGTM, and I agree that a small perf "regression" here is fine given your analysis (and given the significant improvements to maintainability this gives us)
* main: (39 commits) Also remove trailing comma while fixing C409 and C419 (astral-sh#14097) Re-enable clippy `useless-format` (astral-sh#14095) Derive message formats macro support to string (astral-sh#14093) Avoid cloning `Name` when looking up function and class types (astral-sh#14092) Replace `format!` without parameters with `.to_string()` (astral-sh#14090) [red-knot] Do not panic when encountering string annotations (astral-sh#14091) [red-knot] Add MRO resolution for classes (astral-sh#14027) [red-knot] Remove `Type::None` (astral-sh#14024) Cached inference of all definitions in an unpacking (astral-sh#13979) Update dependency uuid to v11 (astral-sh#14084) Update Rust crate notify to v7 (astral-sh#14083) Update cloudflare/wrangler-action action to v3.11.0 (astral-sh#14080) Update dependency mdformat-mkdocs to v3.1.1 (astral-sh#14081) Update pre-commit dependencies (astral-sh#14082) Update dependency ruff to v0.7.2 (astral-sh#14077) Update NPM Development dependencies (astral-sh#14078) Update Rust crate thiserror to v1.0.67 (astral-sh#14076) Update Rust crate syn to v2.0.87 (astral-sh#14075) Update Rust crate serde to v1.0.214 (astral-sh#14074) Update Rust crate pep440_rs to v0.7.2 (astral-sh#14073) ...
Summary
Removes
Type::None
in favor ofKnownClass::NoneType.to_instance(…)
.closes #13670
Test Plan
Existing tests pass.