-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
Slowdown in >=0.981 #14867
Comments
I tried narrowing it down more: from __future__ import annotations
from typing import Any
from typing import Protocol
import numpy as np
class _TensorLike(Protocol):
# Operator must be __add_, __sub__, __mul__.
# Does not reproduce with binary operators (__add__, __xor__ etc.)
def __mul__(self, other: Any) -> _TensorLike:
...
def f(arg: dict[str, _TensorLike] | dict[str, np.ndarray]) -> None:
arg.get('foo') # or most other dict methods -- SLOW LINE HERE |
See reproduction without numpy.
from __future__ import annotations
from typing import Any
from typing import Generic
from typing import Protocol
from typing import TypeVar
from typing import overload
class V1:
pass
class V2:
pass
class V3:
pass
class V4:
pass
class V5:
pass
class V6:
pass
class V7:
pass
class V8:
pass
class V9:
pass
class V10:
pass
T = TypeVar("T")
class A(Generic[T]):
@overload
def foo(self: A[V1], other: V1) -> A[V1]:
return A()
@overload
def foo(self: A[V2], other: V2) -> A[V2]:
return A()
@overload
def foo(self: A[V3], other: V3) -> A[V3]:
return A()
@overload
def foo(self: A[V4], other: V4) -> A[V4]:
return A()
@overload
def foo(self: A[V5], other: V5) -> A[V5]:
return A()
@overload
def foo(self: A[V6], other: V6) -> A[V6]:
return A()
@overload
def foo(self: A[V7], other: V7) -> A[V7]:
return A()
# @overload
# def foo(self: A[V8], other: V8) -> A[V8]:
# return A()
#
# @overload
# def foo(self: A[V9], other: V9) -> A[V9]:
# return A()
#
# @overload
# def foo(self: A[V10], other: V10) -> A[V10]:
# return A()
def foo(self, other: Any) -> Any:
return A()
class B(Protocol):
def foo(self, other: Any) -> B:
...
x: A | B
x.__repr__() |
I used (I bisected with non-compiled mypy btw, so my results might not be 100% accurate I suppose.) |
That matches with the previous commit I bisected to when trying to find the "bad" one; wasn't able to identify what was slower at that point though |
Hm, just to understand this better. What is the performance of something like x: B = A() (using the code by @ikonst) before and after the bad commit? IIUC this should be very slow even before the bad commit. |
Yup, the time taken on my machine checking that is virtually identical between the two commits. (It seems to be much faster checking that code than it is checking |
Hm, interesting. Before that commit y: A | B
x: B = y before vs after (and vs the original example by @ikonst)? |
That might be very slightly slower between the two commits, but still nothing like the dramatic slowdown on @ikonst's benchmark (which appears to get around 3x slower between the two commits). |
I think it gets exponentially slower with more overrides. |
OK, I figured it out, it indeed exposes pre-existing bad performance to more cases. Consider same example as @ikonst but with class B(Protocol):
def foo(self, other: object) -> B: # Note object here
...
y: A | B
x: B = y this is extremely slow even before 0.981, and the bad commit simply exposes the problem to |
OK, well, there is a solution: negative subtype cache (tested it locally, works very fast for this case), but IIRC that was dangerous for some reason, @JukkaL what do you think? |
Also negative cache doesn't seem to cause any slow down on self-check. Here I opened a PR if someone wants to play with it #14884. cc @JMMarchant |
Tested this on my toy example and it's substantially better than 1.1.1 (12.313s). Still not as fast as 0.971 but much improved. |
A possible solution for #14867 (I just copy everything from positive caches).
@JMMarchant could you please check the current master? If the perf is acceptable, you can close the issue. |
Current Thank you very much for all your work on this! |
Bug Report
There has been a sizeable slowdown (33x) in areas of our codebase in recent versions of
mypy
(since 0.981); using the latest version I was finally able to track down and profile the culprits to create a minimal example which seems to be related toUnion
typechecking.To Reproduce
Expected Behavior
Timings between versions are substantially different:
0.971
= 3.042s1.1.1
= 1m44.30sPer-line timings give:
Given this (fairly) minimal example in which no typechecking errors are found I would expect there to not be such a slowdown.
Is there a paradigm shift or issue that would explain this and how can we go about making the typechecker happier and faster?
Actual Behavior
Substantial slowdown after
0.971
Your Environment
--line-checking-stats
for 1.1.1 runmypy.ini
(and other config files): NoneThe text was updated successfully, but these errors were encountered: