|
| 1 | +#!/usr/bin/env python |
| 2 | +# Copyright ScyllaDB, Inc. |
| 3 | +# |
| 4 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | +# you may not use this file except in compliance with the License. |
| 6 | +# You may obtain a copy of the License at |
| 7 | +# |
| 8 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | +# |
| 10 | +# Unless required by applicable law or agreed to in writing, software |
| 11 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | +# See the License for the specific language governing permissions and |
| 14 | +# limitations under the License. |
| 15 | + |
| 16 | +""" |
| 17 | +Benchmark: collection serialization - BytesIO vs b"".join(parts). |
| 18 | +
|
| 19 | +Measures end-to-end serialize_safe performance for List, Map, Tuple, and UserType |
| 20 | +with varying collection sizes. |
| 21 | +""" |
| 22 | + |
| 23 | +import timeit |
| 24 | +import sys |
| 25 | +import os |
| 26 | + |
| 27 | +# Add project root to path |
| 28 | +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
| 29 | + |
| 30 | +from cassandra.cqltypes import ( |
| 31 | + ListType, |
| 32 | + SetType, |
| 33 | + MapType, |
| 34 | + TupleType, |
| 35 | + UserType, |
| 36 | + Int32Type, |
| 37 | + UTF8Type, |
| 38 | + FloatType, |
| 39 | +) |
| 40 | + |
| 41 | +PROTOCOL_VERSION = 4 |
| 42 | + |
| 43 | +# Build parameterized types |
| 44 | +ListOfInt = ListType.apply_parameters([Int32Type]) |
| 45 | +SetOfInt = SetType.apply_parameters([Int32Type]) |
| 46 | +MapIntToStr = MapType.apply_parameters([Int32Type, UTF8Type]) |
| 47 | + |
| 48 | + |
| 49 | +# For TupleType and UserType, we need to set subtypes on a subclass |
| 50 | +class TestTupleType(TupleType): |
| 51 | + subtypes = ( |
| 52 | + Int32Type, |
| 53 | + Int32Type, |
| 54 | + Int32Type, |
| 55 | + Int32Type, |
| 56 | + Int32Type, |
| 57 | + Int32Type, |
| 58 | + Int32Type, |
| 59 | + Int32Type, |
| 60 | + Int32Type, |
| 61 | + Int32Type, |
| 62 | + ) |
| 63 | + |
| 64 | + |
| 65 | +class TestUserType(UserType): |
| 66 | + subtypes = (Int32Type, UTF8Type, FloatType, Int32Type, UTF8Type) |
| 67 | + fieldnames = ("id", "name", "score", "age", "email") |
| 68 | + typename = "test_udt" |
| 69 | + keyspace = "test_ks" |
| 70 | + mapped_class = None |
| 71 | + tuple_type = None |
| 72 | + |
| 73 | + |
| 74 | +def run_bench(label, fn, args, n): |
| 75 | + # Warm up |
| 76 | + for _ in range(min(1000, n)): |
| 77 | + fn(*args) |
| 78 | + t = timeit.timeit(lambda: fn(*args), number=n) |
| 79 | + ns_per_call = t / n * 1e9 |
| 80 | + print(f" {label:45s} {t:.3f}s ({ns_per_call:.2f} ns/call)") |
| 81 | + return t, ns_per_call |
| 82 | + |
| 83 | + |
| 84 | +# Test data |
| 85 | +list_10 = list(range(10)) |
| 86 | +list_100 = list(range(100)) |
| 87 | +list_1000 = list(range(1000)) |
| 88 | +list_with_nulls = [i if i % 3 != 0 else None for i in range(100)] |
| 89 | + |
| 90 | +map_10 = {i: f"value_{i}" for i in range(10)} |
| 91 | +map_100 = {i: f"value_{i}" for i in range(100)} |
| 92 | + |
| 93 | +tuple_10 = tuple(range(10)) |
| 94 | +udt_val = (1, "test_name", 3.14, 25, "test@example.com") |
| 95 | + |
| 96 | +N_SMALL = 500_000 |
| 97 | +N_MED = 100_000 |
| 98 | +N_LARGE = 10_000 |
| 99 | + |
| 100 | +print("Collection serialization benchmark") |
| 101 | +print("=" * 70) |
| 102 | + |
| 103 | +results = {} |
| 104 | + |
| 105 | +print("\nListType.serialize (list of int32):") |
| 106 | +_, r = run_bench( |
| 107 | + "10 elements", ListOfInt.serialize, (list_10, PROTOCOL_VERSION), N_SMALL |
| 108 | +) |
| 109 | +results["list_10"] = r |
| 110 | +_, r = run_bench( |
| 111 | + "100 elements", ListOfInt.serialize, (list_100, PROTOCOL_VERSION), N_MED |
| 112 | +) |
| 113 | +results["list_100"] = r |
| 114 | +_, r = run_bench( |
| 115 | + "1000 elements", ListOfInt.serialize, (list_1000, PROTOCOL_VERSION), N_LARGE |
| 116 | +) |
| 117 | +results["list_1000"] = r |
| 118 | +_, r = run_bench( |
| 119 | + "100 elements (33% null)", |
| 120 | + ListOfInt.serialize, |
| 121 | + (list_with_nulls, PROTOCOL_VERSION), |
| 122 | + N_MED, |
| 123 | +) |
| 124 | +results["list_100_nulls"] = r |
| 125 | + |
| 126 | +print("\nMapType.serialize (map<int32, text>):") |
| 127 | +_, r = run_bench( |
| 128 | + "10 entries", MapIntToStr.serialize, (map_10, PROTOCOL_VERSION), N_SMALL |
| 129 | +) |
| 130 | +results["map_10"] = r |
| 131 | +_, r = run_bench( |
| 132 | + "100 entries", MapIntToStr.serialize, (map_100, PROTOCOL_VERSION), N_MED |
| 133 | +) |
| 134 | +results["map_100"] = r |
| 135 | + |
| 136 | +print("\nTupleType.serialize (10 x int32):") |
| 137 | +_, r = run_bench( |
| 138 | + "10 fields", TestTupleType.serialize, (tuple_10, PROTOCOL_VERSION), N_SMALL |
| 139 | +) |
| 140 | +results["tuple_10"] = r |
| 141 | + |
| 142 | +print("\nUserType.serialize (5 fields: int, text, float, int, text):") |
| 143 | +_, r = run_bench( |
| 144 | + "5 fields", TestUserType.serialize, (udt_val, PROTOCOL_VERSION), N_SMALL |
| 145 | +) |
| 146 | +results["udt_5"] = r |
| 147 | + |
| 148 | +# Print summary for easy comparison |
| 149 | +print(f"\n{'=' * 70}") |
| 150 | +print("Summary (ns/call):") |
| 151 | +for k, v in results.items(): |
| 152 | + print(f" {k:25s}: {v:.2f} ns") |
0 commit comments