Skip to content

Commit c7cf97e

Browse files
authored
Implement a part of multidict python helpers in C (#1096)
1 parent 2d96de3 commit c7cf97e

6 files changed

Lines changed: 180 additions & 186 deletions

File tree

.github/workflows/ci-cd.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ jobs:
365365
- build-wheels-for-tested-arches
366366
- pre-setup # transitive, for accessing settings
367367
runs-on: ubuntu-latest
368-
timeout-minutes: 5
368+
timeout-minutes: 10
369369
steps:
370370
- name: Checkout project
371371
uses: actions/checkout@v4

CHANGES/1096.misc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Implemented views comparison and disjoints in C instead of Python helpers.
2+
3+
The performance boost is about 40%.

multidict/_multidict.c

Lines changed: 9 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -326,20 +326,20 @@ _multidict_extend(MultiDictObject *self, PyObject *args, PyObject *kwds,
326326
}
327327

328328
static inline PyObject *
329-
_multidict_copy(MultiDictObject *self, PyTypeObject *multidict_tp_object)
329+
multidict_copy(MultiDictObject *self)
330330
{
331331
MultiDictObject *new_multidict = NULL;
332332

333333
PyObject *arg_items = NULL,
334334
*items = NULL;
335335

336336
new_multidict = (MultiDictObject*)PyType_GenericNew(
337-
multidict_tp_object, NULL, NULL);
337+
Py_TYPE(self), NULL, NULL);
338338
if (new_multidict == NULL) {
339339
return NULL;
340340
}
341341

342-
if (multidict_tp_object->tp_init(
342+
if (Py_TYPE(self)->tp_init(
343343
(PyObject*)new_multidict, NULL, NULL) < 0)
344344
{
345345
return NULL;
@@ -782,12 +782,6 @@ multidict_add(
782782
Py_RETURN_NONE;
783783
}
784784

785-
static inline PyObject *
786-
multidict_copy(MultiDictObject *self)
787-
{
788-
return _multidict_copy(self, &multidict_type);
789-
}
790-
791785
static inline PyObject *
792786
multidict_extend(MultiDictObject *self, PyObject *args, PyObject *kwds)
793787
{
@@ -1163,27 +1157,6 @@ cimultidict_tp_init(MultiDictObject *self, PyObject *args, PyObject *kwds)
11631157
return 0;
11641158
}
11651159

1166-
static inline PyObject *
1167-
cimultidict_copy(MultiDictObject *self)
1168-
{
1169-
return _multidict_copy(self, &cimultidict_type);
1170-
}
1171-
1172-
PyDoc_STRVAR(cimultidict_copy_doc,
1173-
"Return a copy of itself.");
1174-
1175-
static PyMethodDef cimultidict_methods[] = {
1176-
{
1177-
"copy",
1178-
(PyCFunction)cimultidict_copy,
1179-
METH_NOARGS,
1180-
cimultidict_copy_doc
1181-
},
1182-
{
1183-
NULL,
1184-
NULL
1185-
} /* sentinel */
1186-
};
11871160

11881161
PyDoc_STRVAR(CIMultDict_doc,
11891162
"Dictionary with the support for duplicate case-insensitive keys.");
@@ -1199,7 +1172,6 @@ static PyTypeObject cimultidict_type = {
11991172
.tp_traverse = (traverseproc)multidict_tp_traverse,
12001173
.tp_clear = (inquiry)multidict_tp_clear,
12011174
.tp_weaklistoffset = offsetof(MultiDictObject, weaklist),
1202-
.tp_methods = cimultidict_methods,
12031175
.tp_base = &multidict_type,
12041176
.tp_init = (initproc)cimultidict_tp_init,
12051177
.tp_alloc = PyType_GenericAlloc,
@@ -1607,18 +1579,15 @@ static inline void
16071579
module_free(void *m)
16081580
{
16091581
Py_CLEAR(multidict_str_lower);
1582+
Py_CLEAR(viewbaseset_and_func);
1583+
Py_CLEAR(viewbaseset_or_func);
1584+
Py_CLEAR(viewbaseset_sub_func);
1585+
Py_CLEAR(viewbaseset_xor_func);
16101586
}
16111587

16121588
static PyMethodDef multidict_module_methods[] = {
1613-
{
1614-
"getversion",
1615-
(PyCFunction)getversion,
1616-
METH_O
1617-
},
1618-
{
1619-
NULL,
1620-
NULL
1621-
} /* sentinel */
1589+
{"getversion", (PyCFunction)getversion, METH_O},
1590+
{NULL, NULL} /* sentinel */
16221591
};
16231592

16241593
static PyModuleDef multidict_module = {
@@ -1659,21 +1628,6 @@ PyInit__multidict(void)
16591628
goto fail;
16601629
}
16611630

1662-
#define WITH_MOD(NAME) \
1663-
Py_CLEAR(module); \
1664-
module = PyImport_ImportModule(NAME); \
1665-
if (module == NULL) { \
1666-
goto fail; \
1667-
}
1668-
1669-
#define GET_MOD_ATTR(VAR, NAME) \
1670-
VAR = PyObject_GetAttrString(module, NAME); \
1671-
if (VAR == NULL) { \
1672-
goto fail; \
1673-
}
1674-
1675-
Py_CLEAR(module); \
1676-
16771631
/* Instantiate this module */
16781632
module = PyModule_Create(&multidict_module);
16791633
if (module == NULL) {
@@ -1746,7 +1700,4 @@ PyInit__multidict(void)
17461700
Py_XDECREF(multidict_str_lower);
17471701

17481702
return NULL;
1749-
1750-
#undef WITH_MOD
1751-
#undef GET_MOD_ATTR
17521703
}

multidict/_multidict_base.py

Lines changed: 2 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,16 @@
11
import sys
2-
from collections.abc import (
3-
Container,
4-
ItemsView,
5-
Iterable,
6-
KeysView,
7-
Sequence,
8-
Set,
9-
)
10-
from typing import Literal, Union
2+
from collections.abc import ItemsView, Iterable, KeysView, Sequence
3+
from typing import Union
114

125
if sys.version_info >= (3, 10):
136
from types import NotImplementedType
147
else:
158
from typing import Any as NotImplementedType
169

17-
if sys.version_info >= (3, 11):
18-
from typing import assert_never
19-
else:
20-
from typing_extensions import assert_never
21-
2210

2311
_ViewArg = Union[KeysView[str], ItemsView[str, object]]
2412

2513

26-
def _viewbaseset_richcmp(
27-
view: _ViewArg, other: object, op: Literal[0, 1, 2, 3, 4, 5]
28-
) -> Union[bool, NotImplementedType]:
29-
if not isinstance(other, Set):
30-
return NotImplemented # type: ignore[no-any-return]
31-
if op == 0: # <
32-
return len(view) < len(other) and view <= other
33-
elif op == 1: # <=
34-
if len(view) > len(other):
35-
return False
36-
for elem in view:
37-
if elem not in other:
38-
return False
39-
return True
40-
elif op == 2: # ==
41-
return len(view) == len(other) and view <= other
42-
elif op == 3: # !=
43-
return not view == other
44-
elif op == 4: # >
45-
return len(view) > len(other) and view >= other
46-
elif op == 5: # >=
47-
if len(view) < len(other):
48-
return False
49-
for elem in other:
50-
if elem not in view:
51-
return False
52-
return True
53-
else: # pragma: no cover
54-
assert_never(op)
55-
56-
5714
def _viewbaseset_and(
5815
view: _ViewArg, other: object
5916
) -> Union[set[Sequence[object]], NotImplementedType]:
@@ -92,19 +49,3 @@ def _viewbaseset_xor(
9249
lft = set(iter(view))
9350
rgt = set(iter(other))
9451
return lft ^ rgt
95-
96-
97-
def _itemsview_isdisjoint(view: Container[object], other: Iterable[object]) -> bool:
98-
"Return True if two sets have a null intersection."
99-
for v in other:
100-
if v in view:
101-
return False
102-
return True
103-
104-
105-
def _keysview_isdisjoint(view: Container[object], other: Iterable[object]) -> bool:
106-
"Return True if two sets have a null intersection."
107-
for k in other:
108-
if k in view:
109-
return False
110-
return True

0 commit comments

Comments
 (0)