Repository navigation
GH-100930: Implement dataclass code caching - #92650
brandtbucher wants to merge 20 commits into
Conversation
|
Could this be further sped up by having dataclasses.py come with a pre-seeded code cache from inlined code that'd already be part of the .pyc file thus avoiding runtime calls to |
It could, but I'm not sure I'd want to include that in the initial implementation, mostly because it would be quite a bit of extra code and maintenance burden for a smallish gain that only pays off once (if at all). What sorts of methods would we pre-generate? def __init__(self, _field_0, _field_1):
self._field_0 = _field_0
self._field_1 = _field_1
_code_cache[<indented function source>] = __init__.__code__
def __repr__(self):
return self.__class__.__qualname__ + f"(_field_0={self._field_0}, _field_1={self._field_1})"
_code_cache[<indented function source>] = __repr__.__code__
def __eq__(self, other):
if other.__class__ is self.__class__:
return (self._field_0, self._field_1) == (other._field_0, other._field_1)
return NotImplemented
_code_cache[<indented function source>] = __eq__.__code__
<repeat for all other dataclass shapes we want to pre-seed>Sure, we could rework this to make it more ergonomic: # XXX: Make sure to regenerate this using
# Tools/scripts/generate_dataclasses_code_cache.py whenever the bytecode or the
# generated methods change!
_MARSHALLED_CODE_CACHE = b"..."
_code_cache = marshal.loads(_MARSHALLED_CODE_CACHE)But in the end, the pre-seeded cache still runs the risk of falling out of sync with the actual generated methods if they change in the future. If not properly tested and maintained, it could lead to some extremely subtle bugs. So I'm just not sure it's worth it. |
dataclass code cachingdataclass code caching
| _code_cache_hits += 1 | ||
| code = _code_cache[key] | ||
| else: | ||
| code = _code_cache[key] = _create_code(name, key, globals, locals) |
There was a problem hiding this comment.
I think that this will make regtest fail. We have a similar pattern in typing.py: https://github.com/python/cpython/blame/8dd2766d99f8f51ad62dc0fde8282483590c6cd0/Lib/typing.py#L2550-L2603
And we need to call clear_overloads to clear module level cache. Otherwise we get "Env has changed" error.
There was a problem hiding this comment.
Hm, I'm not sure I understand. I don't see any test failures locally, even when testing for refleaks.
I also only see one use of clear_overloads in the entire code base (as part of a normal test to make sure that calling it works).
There was a problem hiding this comment.
Ok, if refleak test passes, then it is fine :) Thanks for double checking!
|
Closing, since it looks like the direction we're heading right now is batching up The code is still here if we decide to do something like this in the future. :) |
|
i've been investigating dataclass performance. per https://discuss.python.org/t/improving-dataclasses-startup-performance/15442/15, would it be possible to just cache the code generated for the dataclass somewhere, and then it gets compiled to pyc like other python files? |
This is a minimal working implementation of "code-caching" for
dataclasses. It's heavily inspired by https://github.com/dabeaz/dataklasses, and works by reusing generated code objects for dataclasses that differ only in the names of their fields. "Template" code objects are lazily created with placeholder values (_field_1,_field_3) that are patched at method generation time using theirreplacemethod. Annotations and default arguments for__init__methods are assigned manually, as well.For microbenchmarks on "simple" dataclasses with 1-10 elements and no "special" fields, class generation now takes a little less than half the time it used to take. The
test_dataclassessuite, which contains lots of examples of advanced use-cases and actually does some real work with them, runs about 40% faster vs.main.I've also included a counter for measuring cache stats. These indicate that when running
test_dataclasses, 1,395 methods are generated, but only 91 don't have suitable templates in the code cache yet and need to be constructed usingexec. So even for the wide range of dataclasses present in this program, we're still able to maintain a hit rate above 90% (__init__methods are, predictably, the source of most of the misses).dataclassdefinitions #100930