This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fury.git
The following commit(s) were added to refs/heads/main by this push:
new d1eecf04 perf(python): Improve tuple and list serializer performance
(#1933)
d1eecf04 is described below
commit d1eecf0422f5b9f221e65918a93a34713de3b5b0
Author: penguin_wwy <[email protected]>
AuthorDate: Fri Nov 8 00:09:26 2024 +0800
perf(python): Improve tuple and list serializer performance (#1933)
## What does this PR do?
Pre-allocate memory for sequence containers based on the data size to
avoid resizing and improve deserialization performance.
## Related issues
## Does this PR introduce any user-facing change?
- [ ] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
python format
```
python -m pyperf compare_to base.json opt.json
fury_large_tuple: Mean +- std dev: [base] 104 ms +- 2 ms -> [opt] 92.7 ms
+- 5.5 ms: 1.13x faster
fury_large_list: Mean +- std dev: [base] 98.5 ms +- 3.7 ms -> [opt] 92.8 ms
+- 5.3 ms: 1.06x faster
Benchmark hidden because not significant (2): fury_tuple, fury_list
```
xlang format
```
python -m pyperf compare_to base_xlang.json opt_xlang.json
fury_tuple: Mean +- std dev: [base_xlang] 262 us +- 6 us -> [opt_xlang] 259
us +- 5 us: 1.01x faster
fury_large_tuple: Mean +- std dev: [base_xlang] 104 ms +- 4 ms ->
[opt_xlang] 90.0 ms +- 4.6 ms: 1.16x faster
fury_large_list: Mean +- std dev: [base_xlang] 97.6 ms +- 3.7 ms ->
[opt_xlang] 90.0 ms +- 4.3 ms: 1.08x faster
Benchmark hidden because not significant (1): fury_list
```
---
.../cpython_benchmark/fury_benchmark.py | 10 ++-
python/pyfury/_serialization.pyx | 86 ++++++++++++----------
2 files changed, 57 insertions(+), 39 deletions(-)
diff --git a/integration_tests/cpython_benchmark/fury_benchmark.py
b/integration_tests/cpython_benchmark/fury_benchmark.py
index e83f64e6..8b493aec 100644
--- a/integration_tests/cpython_benchmark/fury_benchmark.py
+++ b/integration_tests/cpython_benchmark/fury_benchmark.py
@@ -90,9 +90,11 @@ TUPLE = (
],
60,
)
+LARGE_TUPLE = tuple(range(2**20 + 1))
LIST = [[list(range(10)), list(range(10))] for _ in range(10)]
+LARGE_LIST = [i for i in range(2**20 + 1)]
def mutate_dict(orig_dict, random_source):
@@ -169,7 +171,7 @@ def benchmark_args():
def micro_benchmark():
args = benchmark_args()
runner = pyperf.Runner()
- if args.disable_cython:
+ if args and args.disable_cython:
os.environ["ENABLE_FURY_CYTHON_SERIALIZATION"] = "0"
sys.argv += ["--inherit-environ", "ENABLE_FURY_CYTHON_SERIALIZATION"]
runner.parse_args()
@@ -179,7 +181,13 @@ def micro_benchmark():
"fury_dict_group", fury_object, language, not args.no_ref, DICT_GROUP
)
runner.bench_func("fury_tuple", fury_object, language, not args.no_ref,
TUPLE)
+ runner.bench_func(
+ "fury_large_tuple", fury_object, language, not args.no_ref, LARGE_TUPLE
+ )
runner.bench_func("fury_list", fury_object, language, not args.no_ref,
LIST)
+ runner.bench_func(
+ "fury_large_list", fury_object, language, not args.no_ref, LARGE_LIST
+ )
runner.bench_func(
"fury_complex", fury_object, language, not args.no_ref, COMPLEX_OBJECT
)
diff --git a/python/pyfury/_serialization.pyx b/python/pyfury/_serialization.pyx
index 3f2c6041..1e2280ac 100644
--- a/python/pyfury/_serialization.pyx
+++ b/python/pyfury/_serialization.pyx
@@ -45,6 +45,8 @@ from libc.stdint cimport *
from libcpp.vector cimport vector
from cpython cimport PyObject
from cpython.ref cimport *
+from cpython.list cimport PyList_New, PyList_SET_ITEM
+from cpython.tuple cimport PyTuple_New, PyTuple_SET_ITEM
from libcpp cimport bool as c_bool
from libcpp.utility cimport pair
from cython.operator cimport dereference as deref
@@ -1688,53 +1690,55 @@ cdef class ListSerializer(CollectionSerializer):
cpdef read(self, Buffer buffer):
cdef MapRefResolver ref_resolver = self.fury.ref_resolver
cdef ClassResolver class_resolver = self.fury.class_resolver
- cdef list list_ = []
+ cdef int32_t len_ = buffer.read_varint32()
+ cdef list list_ = PyList_New(len_)
ref_resolver.reference(list_)
- populate_list(buffer, list_, ref_resolver, class_resolver)
+ for i in range(len_):
+ elem = get_next_elenment(buffer, ref_resolver, class_resolver)
+ Py_INCREF(elem)
+ PyList_SET_ITEM(list_, i, elem)
return list_
cpdef xread(self, Buffer buffer):
cdef int32_t len_ = buffer.read_varint32()
- cdef list collection_ = []
+ cdef list collection_ = PyList_New(len_)
self.fury.ref_resolver.reference(collection_)
for i in range(len_):
- collection_.append(self.fury.xdeserialize_ref(
+ elem = self.fury.xdeserialize_ref(
buffer, serializer=self.elem_serializer
- ))
+ )
+ Py_INCREF(elem)
+ PyList_SET_ITEM(collection_, i, elem)
return collection_
-cdef populate_list(
+cdef inline get_next_elenment(
Buffer buffer,
- list list_,
MapRefResolver ref_resolver,
ClassResolver class_resolver):
cdef int32_t ref_id
cdef ClassInfo classinfo
- cdef int32_t len_ = buffer.read_varint32()
- for i in range(len_):
- ref_id = ref_resolver.try_preserve_ref_id(buffer)
- if ref_id < NOT_NULL_VALUE_FLAG:
- list_.append(ref_resolver.get_read_object())
- continue
- # indicates that the object is first read.
- classinfo = class_resolver.read_classinfo(buffer)
- cls = classinfo.cls
- # Note that all read operations in fast paths of
list/tuple/set/dict/sub_dict
- # ust match corresponding writing operations. Otherwise, ref tracking
will
- # error.
- if cls is str:
- list_.append(buffer.read_string())
- elif cls is int:
- list_.append(buffer.read_varint64())
- elif cls is bool:
- list_.append(buffer.read_bool())
- elif cls is float:
- list_.append(buffer.read_double())
- else:
- o = classinfo.serializer.read(buffer)
- ref_resolver.set_read_object(ref_id, o)
- list_.append(o)
+ ref_id = ref_resolver.try_preserve_ref_id(buffer)
+ if ref_id < NOT_NULL_VALUE_FLAG:
+ return ref_resolver.get_read_object()
+ # indicates that the object is first read.
+ classinfo = class_resolver.read_classinfo(buffer)
+ cls = classinfo.cls
+ # Note that all read operations in fast paths of
list/tuple/set/dict/sub_dict
+ # ust match corresponding writing operations. Otherwise, ref tracking will
+ # error.
+ if cls is str:
+ return buffer.read_string()
+ elif cls is int:
+ return buffer.read_varint64()
+ elif cls is bool:
+ return buffer.read_bool()
+ elif cls is float:
+ return buffer.read_double()
+ else:
+ o = classinfo.serializer.read(buffer)
+ ref_resolver.set_read_object(ref_id, o)
+ return o
@cython.final
@@ -1742,18 +1746,24 @@ cdef class TupleSerializer(CollectionSerializer):
cpdef inline read(self, Buffer buffer):
cdef MapRefResolver ref_resolver = self.fury.ref_resolver
cdef ClassResolver class_resolver = self.fury.class_resolver
- cdef list list_ = []
- populate_list(buffer, list_, ref_resolver, class_resolver)
- return tuple(list_)
+ cdef int32_t len_ = buffer.read_varint32()
+ cdef tuple tuple_ = PyTuple_New(len_)
+ for i in range(len_):
+ elem = get_next_elenment(buffer, ref_resolver, class_resolver)
+ Py_INCREF(elem)
+ PyTuple_SET_ITEM(tuple_, i, elem)
+ return tuple_
cpdef inline xread(self, Buffer buffer):
cdef int32_t len_ = buffer.read_varint32()
- cdef list collection_ = []
+ cdef tuple tuple_ = PyTuple_New(len_)
for i in range(len_):
- collection_.append(self.fury.xdeserialize_ref(
+ elem = self.fury.xdeserialize_ref(
buffer, serializer=self.elem_serializer
- ))
- return tuple(collection_)
+ )
+ Py_INCREF(elem)
+ PyTuple_SET_ITEM(tuple_, i, elem)
+ return tuple_
@cython.final
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]