PyCyBase 0.1.10__cp315-cp315-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. cbase/__infra__.pxd +6 -0
  2. cbase/__init__.pxd +6 -0
  3. cbase/__init__.py +31 -0
  4. cbase/allocator_protocol/__infra__.pxd +65 -0
  5. cbase/allocator_protocol/__init__.pxd +65 -0
  6. cbase/allocator_protocol/__init__.py +45 -0
  7. cbase/allocator_protocol/c_allocator_protocol.c +31874 -0
  8. cbase/allocator_protocol/c_allocator_protocol.cp315-win_amd64.pyd +0 -0
  9. cbase/allocator_protocol/c_allocator_protocol.h +352 -0
  10. cbase/allocator_protocol/c_allocator_protocol.pxd +66 -0
  11. cbase/allocator_protocol/c_allocator_protocol.pyi +144 -0
  12. cbase/allocator_protocol/c_allocator_protocol.pyx +209 -0
  13. cbase/allocator_protocol/c_heap_allocator.c +38225 -0
  14. cbase/allocator_protocol/c_heap_allocator.cp315-win_amd64.pyd +0 -0
  15. cbase/allocator_protocol/c_heap_allocator.h +554 -0
  16. cbase/allocator_protocol/c_heap_allocator.pxd +105 -0
  17. cbase/allocator_protocol/c_heap_allocator.pyi +161 -0
  18. cbase/allocator_protocol/c_heap_allocator.pyx +303 -0
  19. cbase/allocator_protocol/c_nt_shm_allocator.c +33455 -0
  20. cbase/allocator_protocol/c_nt_shm_allocator.cp315-win_amd64.pyd +0 -0
  21. cbase/allocator_protocol/c_nt_shm_allocator.h +1105 -0
  22. cbase/allocator_protocol/c_nt_shm_allocator.pxd +110 -0
  23. cbase/allocator_protocol/c_nt_shm_allocator.pyx +223 -0
  24. cbase/allocator_protocol/c_shm_allocator.h +1270 -0
  25. cbase/allocator_protocol/c_shm_allocator.pxd +150 -0
  26. cbase/allocator_protocol/c_shm_allocator.pyi +299 -0
  27. cbase/allocator_protocol/c_shm_allocator.pyx +445 -0
  28. cbase/allocator_protocol/c_shm_comp.c +4654 -0
  29. cbase/allocator_protocol/c_shm_comp.cp315-win_amd64.pyd +0 -0
  30. cbase/allocator_protocol/c_shm_comp.h +49 -0
  31. cbase/allocator_protocol/c_shm_comp.pxd +46 -0
  32. cbase/allocator_protocol/c_shm_comp.pyx +1 -0
  33. cbase/backports/__infra__.pxd +17 -0
  34. cbase/backports/__init__.pxd +17 -0
  35. cbase/backports/__init__.py +9 -0
  36. cbase/backports/pydict.h +55 -0
  37. cbase/backports/pydict.pxd +5 -0
  38. cbase/backports/pylong.c +9965 -0
  39. cbase/backports/pylong.cp315-win_amd64.pyd +0 -0
  40. cbase/backports/pylong.h +79 -0
  41. cbase/backports/pylong.pxd +23 -0
  42. cbase/backports/pylong.pyx +61 -0
  43. cbase/bytemap/__infra__.pxd +42 -0
  44. cbase/bytemap/__init__.pxd +42 -0
  45. cbase/bytemap/__init__.py +21 -0
  46. cbase/bytemap/c_bytemap.c +52151 -0
  47. cbase/bytemap/c_bytemap.cp315-win_amd64.pyd +0 -0
  48. cbase/bytemap/c_bytemap.h +867 -0
  49. cbase/bytemap/c_bytemap.pxd +370 -0
  50. cbase/bytemap/c_bytemap.pyx +1760 -0
  51. cbase/bytemap/xxh3.h +55 -0
  52. cbase/bytemap/xxhash.c +42 -0
  53. cbase/bytemap/xxhash.h +7487 -0
  54. cbase/config_view.c +7253 -0
  55. cbase/config_view.cp315-win_amd64.pyd +0 -0
  56. cbase/config_view.pyx +85 -0
  57. cbase/env.c +11120 -0
  58. cbase/env.cp315-win_amd64.pyd +0 -0
  59. cbase/env.pxd +8 -0
  60. cbase/env.pyi +48 -0
  61. cbase/env.pyx +46 -0
  62. cbase/includes/cbase/allocator_protocol/c_allocator_protocol.c +31874 -0
  63. cbase/includes/cbase/allocator_protocol/c_allocator_protocol.h +352 -0
  64. cbase/includes/cbase/allocator_protocol/c_heap_allocator.c +38225 -0
  65. cbase/includes/cbase/allocator_protocol/c_heap_allocator.h +554 -0
  66. cbase/includes/cbase/allocator_protocol/c_nt_shm_allocator.c +33455 -0
  67. cbase/includes/cbase/allocator_protocol/c_nt_shm_allocator.h +1105 -0
  68. cbase/includes/cbase/allocator_protocol/c_shm_allocator.h +1270 -0
  69. cbase/includes/cbase/allocator_protocol/c_shm_comp.c +4654 -0
  70. cbase/includes/cbase/allocator_protocol/c_shm_comp.h +49 -0
  71. cbase/includes/cbase/backports/pydict.h +55 -0
  72. cbase/includes/cbase/backports/pylong.c +9965 -0
  73. cbase/includes/cbase/backports/pylong.h +79 -0
  74. cbase/includes/cbase/bytemap/c_bytemap.c +52151 -0
  75. cbase/includes/cbase/bytemap/c_bytemap.h +867 -0
  76. cbase/includes/cbase/bytemap/xxh3.h +55 -0
  77. cbase/includes/cbase/bytemap/xxhash.c +42 -0
  78. cbase/includes/cbase/bytemap/xxhash.h +7487 -0
  79. cbase/includes/cbase/config_view.c +7253 -0
  80. cbase/includes/cbase/env.c +11120 -0
  81. cbase/includes/cbase/int128.h +81 -0
  82. cbase/includes/cbase/intern_string/c_intern_string.c +28138 -0
  83. cbase/includes/cbase/intern_string/c_intern_string.h +614 -0
  84. cbase/includes/cbase/nt/pthread_nt_compat.h +102 -0
  85. cbase/includes/cbase/nt/regex_nt_compat.h +133 -0
  86. cbase/includes/cbase/nt/time_sched_nt_compat.h +32 -0
  87. cbase/int128.h +81 -0
  88. cbase/int128.pxd +15 -0
  89. cbase/intern_string/BENCHMARK.md +157 -0
  90. cbase/intern_string/__infra__.pxd +13 -0
  91. cbase/intern_string/__init__.pxd +13 -0
  92. cbase/intern_string/__init__.py +19 -0
  93. cbase/intern_string/c_intern_string.c +28138 -0
  94. cbase/intern_string/c_intern_string.cp315-win_amd64.pyd +0 -0
  95. cbase/intern_string/c_intern_string.h +614 -0
  96. cbase/intern_string/c_intern_string.pxd +121 -0
  97. cbase/intern_string/c_intern_string.pyi +376 -0
  98. cbase/intern_string/c_intern_string.pyx +765 -0
  99. cbase/nt/pthread_nt_compat.h +102 -0
  100. cbase/nt/regex_nt_compat.h +133 -0
  101. cbase/nt/time_sched_nt_compat.h +32 -0
  102. pycybase-0.1.10.dist-info/METADATA +190 -0
  103. pycybase-0.1.10.dist-info/RECORD +105 -0
  104. pycybase-0.1.10.dist-info/WHEEL +5 -0
  105. pycybase-0.1.10.dist-info/top_level.txt +1 -0
@@ -0,0 +1,765 @@
1
+ from cpython.unicode cimport PyUnicode_AsUTF8AndSize, PyUnicode_FromString
2
+ from libc.stdint cimport uintptr_t
3
+ from libc.stdlib cimport free, calloc
4
+
5
+ from cbase.allocator_protocol.c_allocator_protocol cimport AP_DEFAULT_ALLOCATOR, AP_SHM_ALLOCATOR, AP_HEAP_ALLOCATOR
6
+
7
+
8
+ cdef class InternString:
9
+ @staticmethod
10
+ cdef InternString c_from_entry(const istr_entry* entry, InternStringPool parent_pool):
11
+ cdef InternString instance = InternString.__new__(InternString)
12
+ instance.pool = parent_pool
13
+ instance.key = <const char*> entry.key
14
+ instance.hash = entry.hash
15
+ return instance
16
+
17
+ def __gt__(self, object other):
18
+ if isinstance(other, InternString):
19
+ return self.string.__gt__(other.string)
20
+ return self.string.__gt__(other)
21
+
22
+ def __eq__(self, object other):
23
+ if isinstance(other, InternString):
24
+ return self.string.__eq__(other.string)
25
+ return self.string.__eq__(other)
26
+
27
+ def __hash__(self):
28
+ # Truncate to signed 64-bit range for Cython's tp_hash slot.
29
+ # Cython extension types route __hash__ through the C-level
30
+ # Py_hash_t slot which requires Py_ssize_t compatibility.
31
+ # The raw uint64_t hash can overflow this; masking keeps the
32
+ # low 63 bits, which is sufficient for hash tables and
33
+ # matches Python's own hash() truncation behaviour.
34
+ return self.hash & 0x7FFFFFFFFFFFFFFF
35
+
36
+ def __repr__(self):
37
+ if self.key:
38
+ return f'<{self.__class__.__name__}>({PyUnicode_FromString(self.key)})>'
39
+ return f'<{self.__class__.__name__}>(uninitialized)>'
40
+
41
+ property string:
42
+ def __get__(self):
43
+ if self.key:
44
+ return PyUnicode_FromString(self.key)
45
+ raise RuntimeError('Not initialized')
46
+
47
+ property hash_value:
48
+ def __get__(self):
49
+ if self.hash:
50
+ return self.hash
51
+
52
+ property address:
53
+ def __get__(self):
54
+ if self.key:
55
+ return f'{<uintptr_t> self.key:#0x}'
56
+ return None
57
+
58
+
59
+ cdef class InternStringPool:
60
+ def __init__(self):
61
+ self.pool = c_istr_map_new(0, AP_DEFAULT_ALLOCATOR)
62
+ self.owner = True
63
+
64
+ def __dealloc__(self):
65
+ if not self.owner:
66
+ return
67
+
68
+ if self.pool:
69
+ c_istr_map_free(self.pool)
70
+
71
+ @staticmethod
72
+ cdef InternStringPool c_from_header(const istr_map* header, bint owner=False):
73
+ cdef InternStringPool instance = InternStringPool.__new__(InternStringPool)
74
+ instance.pool = <istr_map*> header
75
+ instance.owner = owner
76
+ return instance
77
+
78
+ def __len__(self):
79
+ if not self.pool:
80
+ raise RuntimeError(f'<{self.__class__.__name__}> Not properly initialized! Missing istr_map header!')
81
+ return istr_map_size(self.pool)
82
+
83
+ def __getitem__(self, str key):
84
+ cdef Py_ssize_t key_length = 0
85
+ cdef const char* utf8_string = PyUnicode_AsUTF8AndSize(key, &key_length)
86
+ cdef const istr_entry* entry = c_istr_map_lookup_synced(self.pool, utf8_string, <size_t> key_length)
87
+ if not entry:
88
+ raise KeyError(f'{key} not interned')
89
+ return InternString.c_from_entry(entry, self)
90
+
91
+ def __contains__(self, str key):
92
+ cdef Py_ssize_t key_length = 0
93
+ cdef const char* utf8_string = PyUnicode_AsUTF8AndSize(key, &key_length)
94
+ cdef const istr_entry* entry = c_istr_map_lookup_synced(self.pool, utf8_string, <size_t> key_length)
95
+ return entry != NULL
96
+
97
+ cpdef InternString istr(self, str string):
98
+ cdef Py_ssize_t key_length = 0
99
+ cdef const char* utf8_string = PyUnicode_AsUTF8AndSize(string, &key_length)
100
+ cdef const istr_entry* out_entry = NULL
101
+ cdef const char* istr = c_istr_synced(self.pool, utf8_string, <size_t> key_length, &out_entry)
102
+ if not istr:
103
+ raise MemoryError('Failed to intern string.')
104
+ return InternString.c_from_entry(out_entry, self)
105
+
106
+ def internalized(self):
107
+ cdef istr_entry* entry = istr_map_first(self.pool)
108
+ while entry:
109
+ yield InternString.c_from_entry(entry, self)
110
+ entry = entry.next
111
+
112
+ property size:
113
+ def __get__(self):
114
+ if self.pool:
115
+ return istr_map_size(self.pool)
116
+ raise RuntimeError('Not initialized')
117
+
118
+ property address:
119
+ def __get__(self):
120
+ if self.pool:
121
+ return f'{<uintptr_t> self.pool:#0x}'
122
+ return None
123
+
124
+
125
+ cdef class IstrTestToolkit:
126
+ """Performance benchmark toolkit for InternString vs Python str.
127
+
128
+ Generates a large shared character buffer, segments it into random-length
129
+ strings, then benchmarks C-level hash / intern / lookup / equality against
130
+ equivalent Python str operations.
131
+
132
+ Parameters
133
+ ----------
134
+ buf_size : size_t
135
+ Size of the character buffer in bytes (default 2^30 ≈ 1 GiB).
136
+ n_seg : size_t
137
+ Number of segments to generate (default 100_000).
138
+ max_seg_len : size_t
139
+ Maximum length of each random segment in bytes (default 64).
140
+ n_iters : size_t
141
+ Number of benchmark iterations (default 10).
142
+ """
143
+
144
+ def __cinit__(
145
+ self,
146
+ size_t buf_size=2**30,
147
+ size_t n_seg=100_000,
148
+ size_t max_seg_len=64,
149
+ size_t n_iters=10,
150
+ size_t n_unique=0,
151
+ size_t n_ops=0
152
+ ):
153
+ from random import seed, randint
154
+ cdef size_t i
155
+
156
+ if max_seg_len < 1 or max_seg_len > buf_size // n_seg:
157
+ raise ValueError(f'Invalid max_seg_len={max_seg_len} for buf_size={buf_size}, n_seg={n_seg}')
158
+ if n_seg == 0 or buf_size == 0 or n_iters == 0:
159
+ raise ValueError('buf_size, n_seg, n_iters must be > 0')
160
+ if n_unique == 0:
161
+ n_unique = n_seg // 10
162
+ if n_unique < 1:
163
+ n_unique = 1
164
+
165
+ self.buf_size = buf_size
166
+ self.n_seg = n_seg
167
+ self.max_seg_len = max_seg_len
168
+ self.n_iters = n_iters
169
+ self.n_unique = n_unique
170
+ self.n_ops = n_ops if n_ops > 0 else n_seg
171
+ if self.n_unique > n_seg:
172
+ raise ValueError(f'n_unique={n_unique} must be <= n_seg={n_seg}')
173
+
174
+ # Allocate the character buffer (1 extra byte for NUL safety)
175
+ self.buf = <char*> calloc(buf_size + 1, 1)
176
+ if self.buf == NULL:
177
+ raise MemoryError(f'Failed to allocate buf of size {buf_size}')
178
+
179
+ # Allocate segment arrays
180
+ self.seg_offsets = <size_t*> calloc(n_seg, sizeof(size_t))
181
+ self.seg_lengths = <size_t*> calloc(n_seg, sizeof(size_t))
182
+ self.c_keys = <const char**> calloc(n_seg, sizeof(const char*))
183
+ self.c_key_lens = <size_t*> calloc(n_seg, sizeof(size_t))
184
+ # Shuffle indices for limited-pool benchmarks
185
+ self.shuffle_indices = <size_t*> calloc(self.n_ops, sizeof(size_t))
186
+ if self.shuffle_indices == NULL:
187
+ raise MemoryError(f'Failed to allocate shuffle indices for n_ops={self.n_ops}')
188
+
189
+ if (self.seg_offsets == NULL or self.seg_lengths == NULL or
190
+ self.c_keys == NULL or self.c_key_lens == NULL):
191
+ raise MemoryError(f'Failed to allocate segment arrays for n_seg={n_seg}')
192
+
193
+ # Fill buffer and generate segments
194
+ seed(42) # reproducible
195
+ self.c_gen_buf()
196
+ self.c_gen_segments()
197
+ self.c_gen_shuffle()
198
+
199
+ # Prepare Python objects and intern pool
200
+ self.py_strings = []
201
+ self.py_bytes_list = []
202
+ self.pool = InternStringPool()
203
+ self.c_prepare_py_objects()
204
+ self.c_prepare_istr_pool()
205
+
206
+ def __dealloc__(self):
207
+ if self.buf != NULL:
208
+ free(self.buf)
209
+ self.buf = NULL
210
+ if self.seg_offsets != NULL:
211
+ free(self.seg_offsets)
212
+ self.seg_offsets = NULL
213
+ if self.seg_lengths != NULL:
214
+ free(self.seg_lengths)
215
+ self.seg_lengths = NULL
216
+ if self.c_keys != NULL:
217
+ free(self.c_keys)
218
+ self.c_keys = NULL
219
+ if self.c_key_lens != NULL:
220
+ free(self.c_key_lens)
221
+ self.c_key_lens = NULL
222
+ if self.shuffle_indices != NULL:
223
+ free(self.shuffle_indices)
224
+ self.shuffle_indices = NULL
225
+
226
+ # ------------------------------------------------------------------
227
+ # Setup helpers
228
+ # ------------------------------------------------------------------
229
+
230
+ cdef inline void c_gen_buf(self):
231
+ """Fill buf with random printable ASCII characters."""
232
+ from random import randint
233
+ cdef size_t i
234
+ for i in range(self.buf_size):
235
+ self.buf[i] = <char> randint(33, 126) # printable ASCII
236
+
237
+ cdef inline void c_gen_segments(self):
238
+ """Generate random non-overlapping offsets and lengths within buf."""
239
+ from random import randint
240
+ cdef size_t i, offset, length
241
+ cdef size_t stride = self.buf_size // self.n_seg
242
+ cdef size_t half_stride = stride // 2
243
+ cdef size_t half_max = self.max_seg_len // 2
244
+ if half_max < 1:
245
+ half_max = 1
246
+
247
+ for i in range(self.n_seg):
248
+ # Base position within the i-th stride
249
+ offset = i * stride
250
+ # Add random jitter so segments aren't perfectly aligned
251
+ offset += <size_t> randint(1, <int> half_stride)
252
+ if offset + self.max_seg_len > self.buf_size:
253
+ offset = self.buf_size - self.max_seg_len
254
+ length = <size_t> randint(1, <int> self.max_seg_len)
255
+ self.seg_offsets[i] = offset
256
+ self.seg_lengths[i] = length
257
+ self.c_keys[i] = self.buf + offset
258
+ self.c_key_lens[i] = length
259
+
260
+ cdef inline void c_prepare_py_objects(self):
261
+ """Create Python str and bytes objects from every segment."""
262
+ cdef size_t i
263
+ cdef bytes b
264
+ self.py_strings = []
265
+ self.py_bytes_list = []
266
+ for i in range(self.n_seg):
267
+ b = self.buf[self.seg_offsets[i]:self.seg_offsets[i] + self.seg_lengths[i]]
268
+ self.py_bytes_list.append(b)
269
+ self.py_strings.append(b.decode('ascii'))
270
+
271
+ cdef inline void c_prepare_istr_pool(self):
272
+ """Intern every segment into the pool via Python str (warm-up)."""
273
+ cdef size_t i
274
+ for i in range(self.n_seg):
275
+ self.pool.istr(self.py_strings[i])
276
+
277
+ # ------------------------------------------------------------------
278
+ # C-level hash benchmark (fnv1a_hash)
279
+ # ------------------------------------------------------------------
280
+
281
+ cpdef double istr_hash_routine(self):
282
+ cdef size_t iter_idx, i
283
+ cdef double elapsed = 0.0
284
+ cdef double start_ts
285
+ cdef uint64_t checksum = 0
286
+
287
+ from time import perf_counter
288
+ for iter_idx in range(self.n_iters):
289
+ start_ts = perf_counter()
290
+ for i in range(self.n_seg):
291
+ checksum += fnv1a_hash(self.c_keys[i], self.c_key_lens[i])
292
+ elapsed += perf_counter() - start_ts
293
+
294
+ self.buf[0] = <char> (checksum & 0xFF)
295
+ return elapsed
296
+
297
+ # ------------------------------------------------------------------
298
+ # Intern benchmark — pure C-level, unlocked
299
+ # ------------------------------------------------------------------
300
+
301
+ cpdef double istr_intern_routine(self):
302
+ cdef size_t iter_idx, i
303
+ cdef double elapsed = 0.0
304
+ cdef double start_ts
305
+ cdef istr_map* cmap
306
+ cdef const istr_entry* entry = NULL
307
+ cdef uintptr_t checksum = 0
308
+
309
+ from time import perf_counter
310
+ for iter_idx in range(self.n_iters):
311
+ cmap = c_istr_map_new(0, AP_DEFAULT_ALLOCATOR)
312
+ if cmap == NULL:
313
+ raise MemoryError('c_istr_map_new failed')
314
+ start_ts = perf_counter()
315
+ for i in range(self.n_seg):
316
+ c_istr(cmap, self.c_keys[i], self.c_key_lens[i], &entry)
317
+ checksum += <uintptr_t> entry
318
+ elapsed += perf_counter() - start_ts
319
+ c_istr_map_free(cmap)
320
+
321
+ self.buf[0] = <char> (checksum & 0xFF)
322
+ return elapsed
323
+
324
+ # ------------------------------------------------------------------
325
+ # Intern benchmark — C-level with mutex (synced)
326
+ # ------------------------------------------------------------------
327
+
328
+ cpdef double istr_intern_synced_routine(self):
329
+ cdef size_t iter_idx, i
330
+ cdef double elapsed = 0.0
331
+ cdef double start_ts
332
+ cdef istr_map* cmap
333
+ cdef const istr_entry* entry = NULL
334
+ cdef uintptr_t checksum = 0
335
+
336
+ from time import perf_counter
337
+ for iter_idx in range(self.n_iters):
338
+ cmap = c_istr_map_new(0, AP_DEFAULT_ALLOCATOR)
339
+ if cmap == NULL:
340
+ raise MemoryError('c_istr_map_new failed')
341
+ start_ts = perf_counter()
342
+ for i in range(self.n_seg):
343
+ c_istr_synced(cmap, self.c_keys[i], self.c_key_lens[i], &entry)
344
+ checksum += <uintptr_t> entry
345
+ elapsed += perf_counter() - start_ts
346
+ c_istr_map_free(cmap)
347
+
348
+ self.buf[0] = <char> (checksum & 0xFF)
349
+ return elapsed
350
+
351
+ # ------------------------------------------------------------------
352
+ # Lookup benchmark — pure C-level, unlocked
353
+ # ------------------------------------------------------------------
354
+
355
+ cpdef double istr_lookup_routine(self):
356
+ cdef size_t iter_idx, i
357
+ cdef double elapsed = 0.0
358
+ cdef double start_ts
359
+ cdef const istr_entry* entry = NULL
360
+ cdef uintptr_t checksum = 0
361
+
362
+ from time import perf_counter
363
+ for iter_idx in range(self.n_iters):
364
+ start_ts = perf_counter()
365
+ for i in range(self.n_seg):
366
+ entry = c_istr_map_lookup(self.pool.pool, self.c_keys[i], self.c_key_lens[i])
367
+ checksum += <uintptr_t> entry
368
+ elapsed += perf_counter() - start_ts
369
+
370
+ self.buf[0] = <char> (checksum & 0xFF)
371
+ return elapsed
372
+
373
+ # ------------------------------------------------------------------
374
+ # Lookup benchmark — C-level with mutex (synced)
375
+ # ------------------------------------------------------------------
376
+
377
+ cpdef double istr_lookup_synced_routine(self):
378
+ cdef size_t iter_idx, i
379
+ cdef double elapsed = 0.0
380
+ cdef double start_ts
381
+ cdef const istr_entry* entry = NULL
382
+ cdef uintptr_t checksum = 0
383
+
384
+ from time import perf_counter
385
+ for iter_idx in range(self.n_iters):
386
+ start_ts = perf_counter()
387
+ for i in range(self.n_seg):
388
+ entry = c_istr_map_lookup_synced(self.pool.pool, self.c_keys[i], self.c_key_lens[i])
389
+ checksum += <uintptr_t> entry
390
+ elapsed += perf_counter() - start_ts
391
+
392
+ self.buf[0] = <char> (checksum & 0xFF)
393
+ return elapsed
394
+
395
+ # ------------------------------------------------------------------
396
+ # Equality benchmark — InternString Python-level eq
397
+ # ------------------------------------------------------------------
398
+
399
+ cpdef double istr_eq_routine(self):
400
+ cdef size_t iter_idx, i
401
+ cdef double elapsed = 0.0
402
+ cdef double start_ts
403
+ cdef size_t checksum = 0
404
+ cdef InternString a, b
405
+
406
+ from time import perf_counter
407
+ for iter_idx in range(self.n_iters):
408
+ start_ts = perf_counter()
409
+ for i in range(self.n_seg - 1):
410
+ a = self.pool[self.py_strings[i]]
411
+ b = self.pool[self.py_strings[i + 1]]
412
+ if a == b:
413
+ checksum += 1
414
+ else:
415
+ checksum += 2
416
+ elapsed += perf_counter() - start_ts
417
+
418
+ self.buf[0] = <char> (checksum & 0xFF)
419
+ return elapsed
420
+
421
+ # ------------------------------------------------------------------
422
+ # Python str creation benchmark
423
+ # ------------------------------------------------------------------
424
+
425
+ cpdef double py_unicode_routine(self):
426
+ cdef size_t iter_idx, i
427
+ cdef double elapsed = 0.0
428
+ cdef double start_ts
429
+ cdef uintptr_t checksum = 0
430
+ cdef bytes b
431
+ cdef str s
432
+
433
+ from time import perf_counter
434
+ for iter_idx in range(self.n_iters):
435
+ start_ts = perf_counter()
436
+ for i in range(self.n_seg):
437
+ b = self.buf[self.seg_offsets[i]:self.seg_offsets[i] + self.seg_lengths[i]]
438
+ s = b.decode('ascii')
439
+ checksum += len(s)
440
+ elapsed += perf_counter() - start_ts
441
+
442
+ self.buf[0] = <char> (checksum & 0xFF)
443
+ return elapsed
444
+
445
+ # ------------------------------------------------------------------
446
+ # Python hash benchmark
447
+ # ------------------------------------------------------------------
448
+
449
+ cpdef double py_hash_routine(self):
450
+ cdef size_t iter_idx, i
451
+ cdef double elapsed = 0.0
452
+ cdef double start_ts
453
+ cdef uint64_t checksum = 0
454
+
455
+ from time import perf_counter
456
+ for iter_idx in range(self.n_iters):
457
+ start_ts = perf_counter()
458
+ for i in range(self.n_seg):
459
+ checksum += <uint64_t> hash(self.py_strings[i])
460
+ elapsed += perf_counter() - start_ts
461
+
462
+ self.buf[0] = <char> (checksum & 0xFF)
463
+ return elapsed
464
+
465
+ # ------------------------------------------------------------------
466
+ # Python equality benchmark
467
+ # ------------------------------------------------------------------
468
+
469
+ cpdef double py_eq_routine(self):
470
+ cdef size_t iter_idx, i
471
+ cdef double elapsed = 0.0
472
+ cdef double start_ts
473
+ cdef size_t checksum = 0
474
+
475
+ from time import perf_counter
476
+ for iter_idx in range(self.n_iters):
477
+ start_ts = perf_counter()
478
+ for i in range(self.n_seg - 1):
479
+ if self.py_strings[i] == self.py_strings[i + 1]:
480
+ checksum += 1
481
+ else:
482
+ checksum += 2
483
+ elapsed += perf_counter() - start_ts
484
+
485
+ self.buf[0] = <char> (checksum & 0xFF)
486
+ return elapsed
487
+
488
+ # ------------------------------------------------------------------
489
+ # Shuffle generator — random picks from limited unique pool
490
+ # ------------------------------------------------------------------
491
+
492
+ cdef inline void c_gen_shuffle(self):
493
+ """Fill shuffle_indices with random picks from [0, n_unique)."""
494
+ from random import randint
495
+ cdef size_t i
496
+ for i in range(self.n_ops):
497
+ self.shuffle_indices[i] = <size_t> randint(0, <int> self.n_unique - 1)
498
+
499
+ # ------------------------------------------------------------------
500
+ # Limited-pool intern — realistic workload (hits + misses mixed)
501
+ # ------------------------------------------------------------------
502
+
503
+ cpdef double istr_limited_pool_routine(self):
504
+ """Repeatedly intern from a limited pool of n_unique strings.
505
+
506
+ Each iteration picks n_ops random strings (with replacement) from
507
+ the first n_unique segments and interns them. Since the pool is
508
+ small relative to n_ops, most operations are hits (already-interned
509
+ keys) — this mirrors real-world usage where a finite set of strings
510
+ (e.g. ticker symbols) is interned repeatedly.
511
+ """
512
+ cdef size_t iter_idx, i, idx
513
+ cdef double elapsed = 0.0
514
+ cdef double start_ts
515
+ cdef istr_map* cmap
516
+ cdef const istr_entry* entry = NULL
517
+ cdef uintptr_t checksum = 0
518
+
519
+ from time import perf_counter
520
+ for iter_idx in range(self.n_iters):
521
+ cmap = c_istr_map_new(0, AP_DEFAULT_ALLOCATOR)
522
+ if cmap == NULL:
523
+ raise MemoryError('c_istr_map_new failed')
524
+ start_ts = perf_counter()
525
+ for i in range(self.n_ops):
526
+ idx = self.shuffle_indices[i]
527
+ c_istr(cmap, self.c_keys[idx], self.c_key_lens[idx], &entry)
528
+ checksum += <uintptr_t> entry
529
+ elapsed += perf_counter() - start_ts
530
+ c_istr_map_free(cmap)
531
+
532
+ self.buf[0] = <char> (checksum & 0xFF)
533
+ return elapsed
534
+
535
+ cpdef double istr_limited_pool_synced_routine(self):
536
+ """Same as istr_limited_pool_routine but with c_istr_synced."""
537
+ cdef size_t iter_idx, i, idx
538
+ cdef double elapsed = 0.0
539
+ cdef double start_ts
540
+ cdef istr_map* cmap
541
+ cdef const istr_entry* entry = NULL
542
+ cdef uintptr_t checksum = 0
543
+
544
+ from time import perf_counter
545
+ for iter_idx in range(self.n_iters):
546
+ cmap = c_istr_map_new(0, AP_DEFAULT_ALLOCATOR)
547
+ if cmap == NULL:
548
+ raise MemoryError('c_istr_map_new failed')
549
+ start_ts = perf_counter()
550
+ for i in range(self.n_ops):
551
+ idx = self.shuffle_indices[i]
552
+ c_istr_synced(cmap, self.c_keys[idx], self.c_key_lens[idx], &entry)
553
+ checksum += <uintptr_t> entry
554
+ elapsed += perf_counter() - start_ts
555
+ c_istr_map_free(cmap)
556
+
557
+ self.buf[0] = <char> (checksum & 0xFF)
558
+ return elapsed
559
+
560
+ # ------------------------------------------------------------------
561
+ # Python unicode creation from limited pool
562
+ # ------------------------------------------------------------------
563
+
564
+ cpdef double py_unicode_limited_routine(self):
565
+ """Create Python str objects from random limited-pool picks."""
566
+ cdef size_t iter_idx, i, idx
567
+ cdef double elapsed = 0.0
568
+ cdef double start_ts
569
+ cdef bytes b
570
+ cdef str s
571
+ cdef uintptr_t checksum = 0
572
+
573
+ from time import perf_counter
574
+ for iter_idx in range(self.n_iters):
575
+ start_ts = perf_counter()
576
+ for i in range(self.n_ops):
577
+ idx = self.shuffle_indices[i]
578
+ b = self.buf[self.seg_offsets[idx]:self.seg_offsets[idx] + self.seg_lengths[idx]]
579
+ s = b.decode('ascii')
580
+ checksum += len(s)
581
+ elapsed += perf_counter() - start_ts
582
+
583
+ self.buf[0] = <char> (checksum & 0xFF)
584
+ return elapsed
585
+
586
+ # ------------------------------------------------------------------
587
+ # Miss-rate controlled benchmarks
588
+ # ------------------------------------------------------------------
589
+
590
+ cdef inline void c_gen_miss_pattern(self, double miss_rate):
591
+ """Fill shuffle_indices with a pattern achieving the target miss rate.
592
+
593
+ Misses (new unique strings) are evenly distributed at interval
594
+ ceil(1/miss_rate). Between misses, random picks from the
595
+ already-seen set simulate hits.
596
+ """
597
+ from random import randint
598
+ cdef size_t i, seq_ptr, miss_interval
599
+ cdef size_t n_misses
600
+
601
+ if miss_rate <= 0.0 or miss_rate > 1.0:
602
+ raise ValueError(f'miss_rate must be in (0, 1], got {miss_rate}')
603
+
604
+ n_misses = <size_t> (self.n_ops * miss_rate)
605
+ if n_misses < 1:
606
+ n_misses = 1
607
+ if n_misses > self.n_seg:
608
+ raise ValueError(f'n_misses={n_misses} exceeds n_seg={self.n_seg}')
609
+
610
+ miss_interval = self.n_ops // n_misses
611
+ if miss_interval < 1:
612
+ miss_interval = 1
613
+
614
+ seq_ptr = 0
615
+ for i in range(self.n_ops):
616
+ if i % miss_interval == 0 and seq_ptr < n_misses:
617
+ # MISS — use next unique segment
618
+ self.shuffle_indices[i] = seq_ptr
619
+ seq_ptr += 1
620
+ else:
621
+ # HIT — random pick from already-seen set
622
+ if seq_ptr > 0:
623
+ self.shuffle_indices[i] = <size_t> randint(0, <int> seq_ptr - 1)
624
+ else:
625
+ self.shuffle_indices[i] = 0
626
+
627
+ cpdef double istr_miss_rate_routine(self, double miss_rate):
628
+ """Benchmark intern with a controlled miss rate.
629
+
630
+ miss_rate=0.001 → 1/1K, miss_rate=0.0001 → 1/10K, etc.
631
+ """
632
+ cdef size_t iter_idx, i, idx
633
+ cdef double elapsed = 0.0
634
+ cdef double start_ts
635
+ cdef istr_map* cmap
636
+ cdef const istr_entry* entry = NULL
637
+ cdef uintptr_t checksum = 0
638
+
639
+ self.c_gen_miss_pattern(miss_rate)
640
+
641
+ from time import perf_counter
642
+ for iter_idx in range(self.n_iters):
643
+ cmap = c_istr_map_new(0, AP_DEFAULT_ALLOCATOR)
644
+ if cmap == NULL:
645
+ raise MemoryError('c_istr_map_new failed')
646
+ start_ts = perf_counter()
647
+ for i in range(self.n_ops):
648
+ idx = self.shuffle_indices[i]
649
+ c_istr(cmap, self.c_keys[idx], self.c_key_lens[idx], &entry)
650
+ checksum += <uintptr_t> entry
651
+ elapsed += perf_counter() - start_ts
652
+ c_istr_map_free(cmap)
653
+
654
+ self.buf[0] = <char> (checksum & 0xFF)
655
+ return elapsed
656
+
657
+ cpdef double py_unicode_miss_rate_routine(self, double miss_rate):
658
+ """Python str creation with the same miss-rate pattern."""
659
+ cdef size_t iter_idx, i, idx
660
+ cdef double elapsed = 0.0
661
+ cdef double start_ts
662
+ cdef bytes b
663
+ cdef str s
664
+ cdef uintptr_t checksum = 0
665
+
666
+ self.c_gen_miss_pattern(miss_rate)
667
+
668
+ from time import perf_counter
669
+ for iter_idx in range(self.n_iters):
670
+ start_ts = perf_counter()
671
+ for i in range(self.n_ops):
672
+ idx = self.shuffle_indices[i]
673
+ b = self.buf[self.seg_offsets[idx]:self.seg_offsets[idx] + self.seg_lengths[idx]]
674
+ s = b.decode('ascii')
675
+ checksum += len(s)
676
+ elapsed += perf_counter() - start_ts
677
+
678
+ self.buf[0] = <char> (checksum & 0xFF)
679
+ return elapsed
680
+
681
+ # ------------------------------------------------------------------
682
+ # Run all benchmarks
683
+ # ------------------------------------------------------------------
684
+
685
+ cpdef dict run_test(self):
686
+ cdef double istr_hash_t = self.istr_hash_routine()
687
+ cdef double istr_intern_t = self.istr_intern_routine()
688
+ cdef double istr_intern_s_t = self.istr_intern_synced_routine()
689
+ cdef double istr_lookup_t = self.istr_lookup_routine()
690
+ cdef double istr_lookup_s_t = self.istr_lookup_synced_routine()
691
+ cdef double istr_limited_t = self.istr_limited_pool_routine()
692
+ cdef double istr_limited_s_t = self.istr_limited_pool_synced_routine()
693
+ cdef double istr_eq_t = self.istr_eq_routine()
694
+ cdef double py_unicode_t = self.py_unicode_routine()
695
+ cdef double py_unicode_lim_t = self.py_unicode_limited_routine()
696
+ cdef double py_hash_t = self.py_hash_routine()
697
+ cdef double py_eq_t = self.py_eq_routine()
698
+
699
+ cdef size_t total_bytes = 0
700
+ cdef size_t i
701
+ for i in range(self.n_seg):
702
+ total_bytes += self.seg_lengths[i]
703
+
704
+ cdef size_t n = self.n_seg
705
+ cdef size_t n_iters = self.n_iters
706
+ cdef size_t n_ops = self.n_ops
707
+
708
+ return {
709
+ 'buf_size': self.buf_size,
710
+ 'n_seg': n,
711
+ 'n_unique': self.n_unique,
712
+ 'n_ops': n_ops,
713
+ 'max_seg_len': self.max_seg_len,
714
+ 'n_iters': n_iters,
715
+ 'total_key_bytes': total_bytes,
716
+ # Intern string — C-level all-miss (fresh pool each iter)
717
+ 'istr_hash_ns': (istr_hash_t / n_iters / n) * 1e9,
718
+ 'istr_intern_ns': (istr_intern_t / n_iters / n) * 1e9,
719
+ 'istr_intern_synced_ns':(istr_intern_s_t / n_iters / n) * 1e9,
720
+ # Intern string — C-level lookup (all hits)
721
+ 'istr_lookup_ns': (istr_lookup_t / n_iters / n) * 1e9,
722
+ 'istr_lookup_synced_ns':(istr_lookup_s_t / n_iters / n) * 1e9,
723
+ # Intern string — limited pool (realistic hit/miss mix)
724
+ 'istr_limited_ns': (istr_limited_t / n_iters / n_ops) * 1e9,
725
+ 'istr_limited_synced_ns': (istr_limited_s_t / n_iters / n_ops) * 1e9,
726
+ 'istr_limited_mutex_ns': ((istr_limited_s_t - istr_limited_t) / n_iters / n_ops) * 1e9,
727
+ # Mutex overhead (all-miss)
728
+ 'intern_mutex_ns': ((istr_intern_s_t - istr_intern_t) / n_iters / n) * 1e9,
729
+ 'lookup_mutex_ns': ((istr_lookup_s_t - istr_lookup_t) / n_iters / n) * 1e9,
730
+ # Equality
731
+ 'istr_eq_ns': (istr_eq_t / n_iters / (n - 1)) * 1e9,
732
+ # Python benchmarks
733
+ 'py_unicode_ns': (py_unicode_t / n_iters / n) * 1e9,
734
+ 'py_unicode_limited_ns':(py_unicode_lim_t / n_iters / n_ops) * 1e9,
735
+ 'py_hash_ns': (py_hash_t / n_iters / n) * 1e9,
736
+ 'py_eq_ns': (py_eq_t / n_iters / (n - 1)) * 1e9,
737
+ # Raw timings
738
+ 'istr_hash_s': istr_hash_t,
739
+ 'istr_intern_s': istr_intern_t,
740
+ 'istr_intern_synced_s': istr_intern_s_t,
741
+ 'istr_lookup_s': istr_lookup_t,
742
+ 'istr_lookup_synced_s': istr_lookup_s_t,
743
+ 'istr_limited_s': istr_limited_t,
744
+ 'istr_limited_synced_s': istr_limited_s_t,
745
+ 'istr_eq_s': istr_eq_t,
746
+ 'py_unicode_s': py_unicode_t,
747
+ 'py_unicode_limited_s': py_unicode_lim_t,
748
+ 'py_hash_s': py_hash_t,
749
+ 'py_eq_s': py_eq_t,
750
+ }
751
+
752
+
753
+ # ============================================================
754
+ # Module-level pools
755
+ # ============================================================
756
+
757
+ cdef istr_map* C_POOL = c_istr_map_new(0, AP_SHM_ALLOCATOR)
758
+ globals()['C_POOL'] = <uintptr_t> C_POOL
759
+ cdef InternStringPool POOL = InternStringPool.c_from_header(C_POOL, True)
760
+ globals()['POOL'] = POOL
761
+
762
+ cdef istr_map* C_INTRA_POOL = c_istr_map_new(0, AP_HEAP_ALLOCATOR)
763
+ globals()['C_INTRA_POOL'] = <uintptr_t> C_INTRA_POOL
764
+ cdef InternStringPool INTRA_POOL = InternStringPool.c_from_header(C_INTRA_POOL, True)
765
+ globals()['INTRA_POOL'] = INTRA_POOL