Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Include/internal/pycore_dict.h
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,9 @@ typedef struct {
#define _PyFrozenDictObject_CAST(op) \
(assert(PyFrozenDict_Check(op)), _Py_CAST(PyFrozenDictObject*, (op)))

PyObject *
_PyDict_Freeze(PyObject *dict);

#ifdef __cplusplus
}
#endif
Expand Down
3 changes: 2 additions & 1 deletion Include/internal/pycore_intrinsics.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@
#define INTRINSIC_SUBSCRIPT_GENERIC 10
#define INTRINSIC_TYPEALIAS 11
#define INTRINSIC_BUILD_FROZENSET 12
#define INTRINSIC_BUILD_FROZENDICT 13

#define MAX_INTRINSIC_1 12
#define MAX_INTRINSIC_1 13


/* Binary Functions: */
Expand Down
3 changes: 2 additions & 1 deletion Include/internal/pycore_opcode_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ extern "C" {
#define CONSTANT_MINUS_ONE 11
#define CONSTANT_BUILTIN_FROZENSET 12
#define CONSTANT_EMPTY_TUPLE 13
#define NUM_COMMON_CONSTANTS 14
#define CONSTANT_BUILTIN_FROZENDICT 14
#define NUM_COMMON_CONSTANTS 15

/* Values used in the oparg for RESUME */
#define RESUME_AT_FUNC_START 0
Expand Down
2 changes: 1 addition & 1 deletion Lib/opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
builtins.set,
# Append-only — must match CONSTANT_* in
# Include/internal/pycore_opcode_utils.h.
None, "", True, False, -1, builtins.frozenset, ()]
None, "", True, False, -1, builtins.frozenset, (), builtins.frozendict]
_nb_ops = _opcode.get_nb_ops()

hascompare = [opmap["COMPARE_OP"]]
Expand Down
23 changes: 14 additions & 9 deletions Lib/test/test_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,10 @@ def f_set():
def f_frozenset():
return frozenset(2*x for x in [1,2,3])

funcs = [f_all, f_any, f_tuple, f_list, f_set, f_frozenset]
def f_frozendict():
return frozendict(2*x for x in [1,2])

funcs = [f_all, f_any, f_tuple, f_list, f_set, f_frozenset, f_frozendict]

for f in funcs:
# check that generator code object is not duplicated
Expand All @@ -276,37 +279,39 @@ def f_frozenset():

# check the overriding the builtins works

global all, any, tuple, list, set, frozenset
saved = all, any, tuple, list, set, frozenset
global all, any, tuple, list, set, frozenset, frozendict
saved = all, any, tuple, list, set, frozenset, frozendict
try:
all = lambda x : "all"
any = lambda x : "any"
tuple = lambda x : "tuple"
list = lambda x : "list"
set = lambda x : "set"
frozenset = lambda x : "frozenset"
frozendict = lambda x: "frozendict"

overridden_outputs = [f() for f in funcs]
finally:
all, any, tuple, list, set, frozenset = saved
all, any, tuple, list, set, frozenset, frozendict = saved

self.assertEqual(overridden_outputs, ['all', 'any', 'tuple', 'list', 'set', 'frozenset'])
self.assertEqual(overridden_outputs, ['all', 'any', 'tuple', 'list', 'set', 'frozenset', 'frozendict'])
# Now repeat, overriding the builtins module as well
saved = all, any, tuple, list, set, frozenset
saved = all, any, tuple, list, set, frozenset, frozendict
try:
builtins.all = all = lambda x : "all"
builtins.any = any = lambda x : "any"
builtins.tuple = tuple = lambda x : "tuple"
builtins.list = list = lambda x : "list"
builtins.set = set = lambda x : "set"
builtins.frozenset = frozenset = lambda x : "frozenset"
builtins.frozendict = frozendict = lambda x: "frozendict"

overridden_outputs = [f() for f in funcs]
finally:
all, any, tuple, list, set, frozenset = saved
builtins.all, builtins.any, builtins.tuple, builtins.list, builtins.set, builtins.frozenset = saved
all, any, tuple, list, set, frozenset, frozendict = saved
builtins.all, builtins.any, builtins.tuple, builtins.list, builtins.set, builtins.frozenset, builtins.frozendict = saved

self.assertEqual(overridden_outputs, ['all', 'any', 'tuple', 'list', 'set', 'frozenset'])
self.assertEqual(overridden_outputs, ['all', 'any', 'tuple', 'list', 'set', 'frozenset', 'frozendict'])

def test_builtin_call_async_genexpr_no_crash(self):
async def f_all():
Expand Down
29 changes: 29 additions & 0 deletions Lib/test/test_compiler_codegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,35 @@ def test_frozenset_optimization(self):
]
self.codegen_test(snippet, expected)

def test_frozendict_optimization(self):
l1 = self.Label()
snippet = "frozendict({1: 2})"
expected = [
('RESUME', 0),
('ANNOTATIONS_PLACEHOLDER', None),
('LOAD_NAME', 0),
('COPY', 1),
('LOAD_COMMON_CONSTANT', 12),
('IS_OP', 0),
('POP_JUMP_IF_FALSE', l1),
('POP_TOP', None),
('LOAD_CONST', 1),
('LOAD_CONST', 2),
('BUILD_MAP', 2),
('CALL_INTRINSIC_1', 12),
('JUMP', 0),
l1,
('PUSH_NULL', None),
('LOAD_CONST', 1),
('LOAD_CONST', 2),
('BUILD_MAP', 3),
('CALL', 1),
('POP_TOP', None),
('LOAD_CONST', 0),
('RETURN_VALUE', None)
]
self.codegen_test(snippet, expected)

def test_comp_without_target_optimization(self):
snippet = "[i for i in range(10)]"
expected = [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Improve performance of :class:`frozendict` objects by avoiding copies during
construction.
11 changes: 11 additions & 0 deletions Objects/dictobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -8595,3 +8595,14 @@ PyTypeObject PyFrozenDict_Type = {
.tp_vectorcall = frozendict_vectorcall,
.tp_version_tag = _Py_TYPE_VERSION_FROZENDICT,
};


PyObject *
_PyDict_Freeze(PyObject *dict)
{
assert(dict != NULL);
assert(PyDict_CheckExact(dict));
assert(_PyObject_IsUniquelyReferenced(dict));
dict->ob_type = &PyFrozenDict_Type;
return Py_NewRef(dict);
}
12 changes: 6 additions & 6 deletions Programs/test_frozenmain.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions Python/codegen.c
Original file line number Diff line number Diff line change
Expand Up @@ -4006,6 +4006,25 @@ maybe_optimize_function_call(compiler *c, expr_ty e, jump_target_label end)
return 1;
}

if (_PyUnicode_EqualToASCIIString(func->v.Name.id, "frozendict")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, we have similar optimization with frozenset. So it looks good to me.

&& (arg_expr->kind == Dict_kind || arg_expr->kind == DictComp_kind)) {
NEW_JUMP_TARGET_LABEL(c, skip_optimization);

ADDOP_I(c, loc, COPY, 1);
ADDOP_I(c, loc, LOAD_COMMON_CONSTANT, CONSTANT_BUILTIN_FROZENDICT);
ADDOP_COMPARE(c, loc, Is);
ADDOP_JUMP(c, loc, POP_JUMP_IF_FALSE, skip_optimization);
ADDOP(c, loc, POP_TOP);

VISIT(c, expr, arg_expr);
ADDOP_I(c, loc, CALL_INTRINSIC_1, INTRINSIC_BUILD_FROZENDICT);

ADDOP_JUMP(c, loc, JUMP, end);

USE_LABEL(c, skip_optimization);
return 1;
}

if (arg_expr->kind != GeneratorExp_kind) {
return 0;
}
Expand Down
10 changes: 10 additions & 0 deletions Python/intrinsics.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include "Python.h"
#include "pycore_compile.h" // _PyCompile_GetUnaryIntrinsicName
#include "pycore_dict.h" // _PyDict_Freeze()
#include "pycore_function.h" // _Py_set_function_type_params()
#include "pycore_genobject.h" // _PyAsyncGenValueWrapperNew
#include "pycore_interpframe.h" // _PyFrame_GetLocals()
Expand Down Expand Up @@ -216,6 +217,14 @@ make_frozenset(PyThreadState* Py_UNUSED(ignored), PyObject *set)
return _PySet_Freeze(set);
}

static PyObject *
make_frozendict(PyThreadState* Py_UNUSED(ignored), PyObject *dict)
{
assert(PyDict_CheckExact(dict));
assert(_PyObject_IsUniquelyReferenced(dict));
return _PyDict_Freeze(dict);
}


#define INTRINSIC_FUNC_ENTRY(N, F) \
[N] = {F, #N},
Expand All @@ -235,6 +244,7 @@ _PyIntrinsics_UnaryFunctions[] = {
INTRINSIC_FUNC_ENTRY(INTRINSIC_SUBSCRIPT_GENERIC, _Py_subscript_generic)
INTRINSIC_FUNC_ENTRY(INTRINSIC_TYPEALIAS, _Py_make_typealias)
INTRINSIC_FUNC_ENTRY(INTRINSIC_BUILD_FROZENSET, make_frozenset)
INTRINSIC_FUNC_ENTRY(INTRINSIC_BUILD_FROZENDICT, make_frozendict)
};


Expand Down
1 change: 1 addition & 0 deletions Python/pylifecycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,7 @@ pycore_init_builtins(PyThreadState *tstate)
common_objs[CONSTANT_BUILTIN_FROZENSET] = (PyObject *)&PyFrozenSet_Type;
common_objs[CONSTANT_EMPTY_TUPLE] =
Py_GetConstantBorrowed(Py_CONSTANT_EMPTY_TUPLE);
common_objs[CONSTANT_BUILTIN_FROZENDICT] = (PyObject *)&PyFrozenDict_Type;
for (int i = 0; i < NUM_COMMON_CONSTANTS; i++) {
assert(common_objs[i] != NULL);
_Py_SetImmortal(common_objs[i]);
Expand Down
Loading