-
-
Notifications
You must be signed in to change notification settings - Fork 33.7k
gh-92810: Reduce memory usage by ABCMeta.__subclasscheck__ (alternative) #141171
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?
gh-92810: Reduce memory usage by ABCMeta.__subclasscheck__ (alternative) #141171
Conversation
|
Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool. If this change has little impact on Python users, wait for a maintainer to apply the |
|
Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool. If this change has little impact on Python users, wait for a maintainer to apply the |
1 similar comment
|
Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool. If this change has little impact on Python users, wait for a maintainer to apply the |
|
Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool. If this change has little impact on Python users, wait for a maintainer to apply the |
b3bbd15 to
a7ef5b0
Compare
a7ef5b0 to
6869658
Compare
|
pyperformance results doesn't show a much difference: sudo ./venv/bin/python -m pyperf system tune
./venv/bin/python -m pyperformance run --rigorous --affinity 0,1,2,3,4,5main_cd4d0ae75c_rigorous_affinity.json DetailsBenchmarks with tag 'apps':
Benchmark hidden because not significant (4): 2to3, docutils, html5lib, tornado_http Benchmarks with tag 'asyncio':
Benchmark hidden because not significant (11): async_tree_none_tg, async_tree_eager, async_tree_memoization, async_tree_eager_memoization_tg, asyncio_websockets, async_tree_io_tg, asyncio_tcp_ssl, async_tree_memoization_tg, async_tree_io, async_tree_none, async_tree_cpu_io_mixed_tg Benchmarks with tag 'math':
Benchmark hidden because not significant (1): float Benchmarks with tag 'regex':
Benchmark hidden because not significant (1): regex_compile Benchmarks with tag 'serialize':
Benchmark hidden because not significant (4): pickle_pure_python, pickle_dict, pickle, unpickle Benchmarks with tag 'startup':
Benchmarks with tag 'template':
All benchmarks:
Benchmark hidden because not significant (36): bench_mp_pool, html5lib, meteor_contest, scimark_sor, docutils, pprint_pformat, regex_compile, pickle_pure_python, async_tree_none_tg, pickle_dict, async_tree_eager, tornado_http, dask, pyflate, go, scimark_sparse_mat_mult, async_tree_memoization, pickle, async_tree_eager_memoization_tg, asyncio_websockets, many_optionals, sqlglot_v2_normalize, pprint_safe_repr, 2to3, float, unpickle, async_tree_io_tg, asyncio_tcp_ssl, async_tree_memoization_tg, async_tree_io, sqlite_synth, sqlalchemy_imperative, sqlglot_v2_optimize, sqlglot_v2_transpile, async_tree_none, async_tree_cpu_io_mixed_tg |
|
If class Number(ABC): ...
class Real(Number): ...
Real.register(int)
Real.register(float)
assert issubclass(int, Number)
assert issubclass(float, Number)then it is not required anymore. In this case I haven't found any uses of the such overrides with ABC/ABCMeta, other than implementing custom non-ABC metaclasses or using this as typeshed annotations in |
_abc._abc_subclasscheckhas very poor performance and (I think) a memory leak #92810Alternative implementation of #131914 which doesn't modify class attributes on every
__subclasscheck__call. Instead it checks in__new__does current class havedef __subclasses__(cls)overriden, and if so, enables casefor scls in cls.__subclasses__(), which is disabled by default.To handle cases like:
which previously were implemented via
__subclasses__, methodcls.register(subclass)is callingsuper(cls).register(subclass)recursively ("bubble-up" registration to all the parents).For benchmark from #131914:
isinstance(child, Parent)2MiB...15MiB
2MiB...15MiB
10MiB...24MiB
10MiB...24MiB
issubclass(Child, Parent)0MiB...1MiB
0MiB...1MiB
6MiB...8MiB
5MiB...8MiB
isinstance(child, Grandparent)0MiB...2MiB
0MiB...1MiB
4MiB...7MiB
4MiB...7MiB
issubclass(Child, Grandparent)0MiB
0MiB
1MiB...2MiB
0MiB...1MiB
not isinstance(child, Sibling)1MiB...14MiB
1MiB...14MiB
14MiB...24MiB
13MiB...22MiB
not issubclass(Child, Sibling)0MiB
0MiB...1MiB
9MiB...11MiB
8MiB...11MiB
not isinstance(child, Cousin)0MiB
1MiB...2MiB
8MiB...10MiB
6MiB...8MiB
not issubclass(Child, Cousin)0MiB
0MiB...1MiB
5MiB...6MiB
2MiB...3MiB
not isinstance(child, Uncle)5706MiB...6331MiB
0MiB...1MiB
3796MiB...4423MiB
5MiB
not issubclass(Child, Uncle)5704MiB
0MiB
3794MiB...3795MiB
3MiB
Memory increment is measured during
isinstance()/issubclass()calls, not during preparation, like class creation or registration where actual registry allocation is performed. So memory usage in tables below is almost always 0.Timing drop in _py_abc implementation for 2 first rows is due to
if subclass in cls._abc_registry:check added to match _abc.c implementation.isinstance(child, Parent.register)0MiB
0MiB
0MiB
0MiB
issubclass(Child, Parent.register)0MiB
0MiB
0MiB
0MiB
isinstance(child, Grandparent.register)0MiB
0MiB
0MiB
0MiB
issubclass(Child, Grandparent.register)0MiB
0MiB
0MiB
0MiB
not isinstance(child, Sibling.register)0MiB
1MiB
0MiB
2MiB
not issubclass(Child, Sibling.register)0MiB
1MiB
0MiB
2MiB
not isinstance(child, Cousin.register)0MiB
2MiB
0MiB
3MiB
not issubclass(Child, Cousin.register)0MiB
2MiB
0MiB
3MiB
not isinstance(child, Uncle.register)0MiB
2MiB...3MiB
0MiB
4MiB
not issubclass(Child, Uncle.register)0MiB
2MiB
0MiB
4MiB
isinstance(child, Parent.__subclasses__)0MiB
0MiB
0MiB
0MiB
issubclass(Child, Parent.__subclasses__)0MiB
0MiB
0MiB
0MiB
isinstance(child, Grandparent.__subclasses__)0MiB
0MiB
0MiB
0MiB
issubclass(Child, Grandparent.__subclasses__)0MiB
0MiB
0MiB
0MiB
not isinstance(child, Sibling.__subclasses__)0MiB
0MiB
0MiB
1MiB
not issubclass(Child, Sibling.__subclasses__)0MiB
0MiB
0MiB
1MiB
not isinstance(child, Cousin.__subclasses__)0MiB
0MiB
0MiB
1MiB
not issubclass(Child, Cousin.__subclasses__)0MiB
0MiB
0MiB
1MiB
not isinstance(child, Uncle.__subclasses__)0MiB
1MiB
0MiB
2MiB
not issubclass(Child, Uncle.__subclasses__)0MiB
1MiB
0MiB
2MiB
Flamegraphs for
_py_abcimpl and testissubclass_uncle(the most time and memory consuming case onmain):main_vs_pr141171.tar.gz