Skip to content

Commit 4d2a9d7

Browse files
committed
WIP cache freed generator object instance to speed up allocation
1 parent ed88d82 commit 4d2a9d7

File tree

5 files changed

+73
-2
lines changed

5 files changed

+73
-2
lines changed

mypyc/codegen/emit.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1115,6 +1115,31 @@ def emit_gc_clear(self, target: str, rtype: RType) -> None:
11151115
else:
11161116
assert False, "emit_gc_clear() not implemented for %s" % repr(rtype)
11171117

1118+
def emit_reuse_clear(self, target: str, rtype: RType) -> None:
1119+
"""Emit attribute clear before object is added into freelist.
1120+
1121+
Assume that 'target' represents a C expression that refers to a
1122+
struct member, such as 'self->x'.
1123+
1124+
Unlike emit_gc_clear(), initialize attribute value to match a freshly
1125+
allocated object.
1126+
"""
1127+
if isinstance(rtype, RTuple):
1128+
for i, item_type in enumerate(rtype.types):
1129+
self.emit_reuse_clear(f"{target}.f{i}", item_type)
1130+
elif not rtype.is_refcounted:
1131+
self.emit_line(f"{target} = {rtype.c_undefined};")
1132+
elif isinstance(rtype, RPrimitive) and rtype.name == "builtins.int":
1133+
self.emit_line(f"if (CPyTagged_CheckLong({target})) {{")
1134+
self.emit_line(f"CPyTagged __tmp = {target};")
1135+
self.emit_line(f"{target} = {self.c_undefined_value(rtype)};")
1136+
self.emit_line("Py_XDECREF(CPyTagged_LongAsObject(__tmp));")
1137+
self.emit_line("} else {")
1138+
self.emit_line(f"{target} = {self.c_undefined_value(rtype)};")
1139+
self.emit_line("}")
1140+
else:
1141+
self.emit_gc_clear(target, rtype)
1142+
11181143
def emit_traceback(
11191144
self, source_path: str, module_name: str, traceback_entry: tuple[str, int]
11201145
) -> None:

mypyc/codegen/emitclass.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,16 @@ def generate_class_type_decl(
186186
)
187187

188188

189+
def generate_class_reuse(
190+
cl: ClassIR, c_emitter: Emitter, external_emitter: Emitter, emitter: Emitter
191+
) -> None:
192+
assert cl.reuse_freed_instance
193+
context = c_emitter.context
194+
name = cl.name_prefix(c_emitter.names) + "_free_instance"
195+
struct_name = cl.struct_name(c_emitter.names)
196+
context.declarations[name] = HeaderDeclaration(f"{struct_name} *{name};", needs_export=True)
197+
198+
189199
def generate_class(cl: ClassIR, module: str, emitter: Emitter) -> None:
190200
"""Generate C code for a class.
191201
@@ -557,7 +567,17 @@ def generate_setup_for_class(
557567
emitter.emit_line("static PyObject *")
558568
emitter.emit_line(f"{func_name}(PyTypeObject *type)")
559569
emitter.emit_line("{")
560-
emitter.emit_line(f"{cl.struct_name(emitter.names)} *self;")
570+
struct_name = cl.struct_name(emitter.names)
571+
emitter.emit_line(f"{struct_name} *self;")
572+
573+
prefix = cl.name_prefix(emitter.names)
574+
if cl.reuse_freed_instance:
575+
emitter.emit_line(f"if ({prefix}_free_instance != NULL) {{")
576+
emitter.emit_line(f"self = {prefix}_free_instance;")
577+
emitter.emit_line(f"{prefix}_free_instance = NULL;")
578+
emitter.emit_line("return (PyObject *)self;")
579+
emitter.emit_line("}")
580+
561581
emitter.emit_line(f"self = ({cl.struct_name(emitter.names)} *)type->tp_alloc(type, 0);")
562582
emitter.emit_line("if (self == NULL)")
563583
emitter.emit_line(" return NULL;")
@@ -786,6 +806,8 @@ def generate_dealloc_for_class(
786806
emitter.emit_line("if (!PyObject_GC_IsFinalized((PyObject *)self)) {")
787807
emitter.emit_line("Py_TYPE(self)->tp_finalize((PyObject *)self);")
788808
emitter.emit_line("}")
809+
if cl.reuse_freed_instance:
810+
emit_reuse_dealloc(cl, emitter)
789811
emitter.emit_line("PyObject_GC_UnTrack(self);")
790812
# The trashcan is needed to handle deep recursive deallocations
791813
emitter.emit_line(f"CPy_TRASHCAN_BEGIN(self, {dealloc_func_name})")
@@ -795,6 +817,22 @@ def generate_dealloc_for_class(
795817
emitter.emit_line("}")
796818

797819

820+
def emit_reuse_dealloc(cl: ClassIR, emitter: Emitter) -> None:
821+
prefix = cl.name_prefix(emitter.names)
822+
emitter.emit_line(f"if ({prefix}_free_instance == NULL) {{")
823+
emitter.emit_line(f"{prefix}_free_instance = self;")
824+
emitter.emit_line("Py_INCREF(self);")
825+
826+
# TODO: emit_clear_bitmaps(cl, emitter)
827+
828+
for base in reversed(cl.base_mro):
829+
for attr, rtype in base.attributes.items():
830+
emitter.emit_reuse_clear(f"self->{emitter.attr(attr)}", rtype)
831+
832+
emitter.emit_line("return;")
833+
emitter.emit_line("}")
834+
835+
798836
def generate_finalize_for_class(
799837
del_method: FuncIR, finalize_func_name: str, emitter: Emitter
800838
) -> None:

mypyc/codegen/emitmodule.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
from mypy.util import hash_digest, json_dumps
3030
from mypyc.codegen.cstring import c_string_initializer
3131
from mypyc.codegen.emit import Emitter, EmitterContext, HeaderDeclaration, c_array_initializer
32-
from mypyc.codegen.emitclass import generate_class, generate_class_type_decl
32+
from mypyc.codegen.emitclass import generate_class, generate_class_reuse, generate_class_type_decl
3333
from mypyc.codegen.emitfunc import generate_native_function, native_function_header
3434
from mypyc.codegen.emitwrapper import (
3535
generate_legacy_wrapper_function,
@@ -609,6 +609,8 @@ def generate_c_for_modules(self) -> list[tuple[str, str]]:
609609
self.declare_finals(module_name, module.final_names, declarations)
610610
for cl in module.classes:
611611
generate_class_type_decl(cl, emitter, ext_declarations, declarations)
612+
if cl.reuse_freed_instance:
613+
generate_class_reuse(cl, emitter, ext_declarations, declarations)
612614
self.declare_type_vars(module_name, module.type_var_names, declarations)
613615
for fn in module.functions:
614616
generate_function_declaration(fn, declarations)

mypyc/ir/class_ir.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,11 @@ def __init__(
204204
# If this is a generator environment class, what is the actual method for it
205205
self.env_user_function: FuncIR | None = None
206206

207+
# If True, keep one freed, cleared instance available for immediate reuse to
208+
# speed up allocations. This helps if many objects are freed quickly, before
209+
# other instances of the same class are allocated.
210+
self.reuse_freed_instance = False
211+
207212
def __repr__(self) -> str:
208213
return (
209214
"ClassIR("

mypyc/irbuild/generator.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ def setup_generator_class(builder: IRBuilder) -> ClassIR:
156156
name = f"{builder.fn_info.namespaced_name()}_gen"
157157

158158
generator_class_ir = ClassIR(name, builder.module_name, is_generated=True, is_final_class=True)
159+
generator_class_ir.reuse_freed_instance = True
159160
if builder.fn_info.can_merge_generator_and_env_classes():
160161
builder.fn_info.env_class = generator_class_ir
161162
else:

0 commit comments

Comments
 (0)