From 72e5b1fb1af36b35201f7378caaa18c9e2f76c3e Mon Sep 17 00:00:00 2001 From: Python Infra CI Date: Mon, 19 Oct 2026 03:46:50 +0000 Subject: [PATCH 1/4] perf: hoist the `randint` dtype-bounds table out of the per-call hot pat --- mkl_random/mklrand.pyx | 43 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/mkl_random/mklrand.pyx b/mkl_random/mklrand.pyx index 7f55da6..ea04dae 100644 --- a/mkl_random/mklrand.pyx +++ b/mkl_random/mklrand.pyx @@ -1793,6 +1793,23 @@ def _brng_id_to_name(int brng_id): return brng_name +# Bounds for `_choose_randint_type`, keyed by dtype(...).name: the largest +# half open interval `[low, high)` representable by that dtype. Built once +# at import time rather than per call, since `randint` calls into this on +# every invocation, including the scalar fast path. +_randint_bounds = { + "bool": (0, 2), + "int8": (-2**7, 2**7), + "int16": (-2**15, 2**15), + "int32": (-2**31, 2**31), + "int64": (-2**63, 2**63), + "uint8": (0, 2**8), + "uint16": (0, 2**16), + "uint32": (0, 2**32), + "uint64": (0, 2**64), +} + + cdef class _MKLRandomState: cdef irk_state *internal_state cdef object lock @@ -2142,30 +2159,12 @@ cdef class _MKLRandomState: self.internal_state, irk_double_vec, size, self.lock ) - # Set up dictionary of integer types and relevant functions. - # - # The dictionary is keyed by dtype(...).name and the values - # are a tuple (low, high, function), where low and high are - # the bounds of the largest half open interval `[low, high)` - # and the function is the relevant function to call for - # that precision. - # - # The functions are all the same except for changed types in - # a few places. It would be easy to template them. + # Resolves dtype to its `_randint_bounds` entry and the matching + # `_rand_*`/`_rand_*_broadcast` methods below. The functions are all the + # same except for changed types in a few places. It would be easy to + # template them. def _choose_randint_type(self, dtype): - _randint_bounds = { - "bool": (0, 2), - "int8": (-2**7, 2**7), - "int16": (-2**15, 2**15), - "int32": (-2**31, 2**31), - "int64": (-2**63, 2**63), - "uint8": (0, 2**8), - "uint16": (0, 2**16), - "uint32": (0, 2**32), - "uint64": (0, 2**64), - } - key = np.dtype(dtype).name if key not in _randint_bounds: raise TypeError(f'Unsupported dtype "{key}" for randint') From 88c3bc51570f565e4796bda67832d2326eafd1b4 Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Mon, 19 Oct 2026 06:53:08 -0700 Subject: [PATCH 2/4] Key randint bounds table by np.dtype --- mkl_random/mklrand.pyx | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/mkl_random/mklrand.pyx b/mkl_random/mklrand.pyx index 7bad619..e302da3 100644 --- a/mkl_random/mklrand.pyx +++ b/mkl_random/mklrand.pyx @@ -1794,20 +1794,19 @@ def _brng_id_to_name(int brng_id): return brng_name -# Bounds for `_choose_randint_type`, keyed by dtype(...).name: the largest -# half open interval `[low, high)` representable by that dtype. Built once -# at import time rather than per call, since `randint` calls into this on -# every invocation, including the scalar fast path. +# Largest half-open interval [low, high) per randint dtype. _randint_bounds = { - "bool": (0, 2), - "int8": (-2**7, 2**7), - "int16": (-2**15, 2**15), - "int32": (-2**31, 2**31), - "int64": (-2**63, 2**63), - "uint8": (0, 2**8), - "uint16": (0, 2**16), - "uint32": (0, 2**32), - "uint64": (0, 2**64), + np.dtype(name): (name, low, high) for name, low, high in ( + ("bool", 0, 2), + ("int8", -2**7, 2**7), + ("int16", -2**15, 2**15), + ("int32", -2**31, 2**31), + ("int64", -2**63, 2**63), + ("uint8", 0, 2**8), + ("uint16", 0, 2**16), + ("uint32", 0, 2**32), + ("uint64", 0, 2**64), + ) } @@ -2160,17 +2159,18 @@ cdef class _MKLRandomState: self.internal_state, irk_double_vec, size, self.lock ) - # Resolves dtype to its `_randint_bounds` entry and the matching - # `_rand_*`/`_rand_*_broadcast` methods below. The functions are all the - # same except for changed types in a few places. It would be easy to - # template them. + # Returns randint bounds and the matching `_rand_*`/`_rand_*_broadcast` + # methods for dtype. def _choose_randint_type(self, dtype): - key = np.dtype(dtype).name - if key not in _randint_bounds: - raise TypeError(f'Unsupported dtype "{key}" for randint') + _dtype = np.dtype(dtype) + try: + key, lowbnd, highbnd = _randint_bounds[_dtype] + except KeyError: + raise TypeError( + f'Unsupported dtype "{_dtype.name}" for randint' + ) from None - lowbnd, highbnd = _randint_bounds[key] return (lowbnd, highbnd, getattr(self, f"_rand_{key}"), getattr(self, f"_rand_{key}_broadcast")) From 70d05855841bc40e9ceb98eb96dea0e24b69255f Mon Sep 17 00:00:00 2001 From: "Harlow, Jordan" Date: Wed, 28 Oct 2026 12:36:22 -0600 Subject: [PATCH 3/4] Store the randint method names in the bounds table, and add a changelog entry _choose_randint_type now reads both _rand_* method names from the table instead of formatting them on every call. --- CHANGELOG.md | 1 + mkl_random/mklrand.pyx | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0890c60..f1521b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added ### Changed +* Sped up `randint` calls that return few values: the dtype bounds table is built once, keyed by `np.dtype`, and no longer rebuilt per call [gh-199](https://github.com/IntelPython/mkl_random/pull/199) ### Fixed diff --git a/mkl_random/mklrand.pyx b/mkl_random/mklrand.pyx index e302da3..b70481b 100644 --- a/mkl_random/mklrand.pyx +++ b/mkl_random/mklrand.pyx @@ -1794,9 +1794,10 @@ def _brng_id_to_name(int brng_id): return brng_name -# Largest half-open interval [low, high) per randint dtype. +# Largest half-open interval [low, high) per randint dtype, and its methods. _randint_bounds = { - np.dtype(name): (name, low, high) for name, low, high in ( + np.dtype(name): (low, high, f"_rand_{name}", f"_rand_{name}_broadcast") + for name, low, high in ( ("bool", 0, 2), ("int8", -2**7, 2**7), ("int16", -2**15, 2**15), @@ -2165,15 +2166,14 @@ cdef class _MKLRandomState: def _choose_randint_type(self, dtype): _dtype = np.dtype(dtype) try: - key, lowbnd, highbnd = _randint_bounds[_dtype] + lowbnd, highbnd, rand_name, broadcast_name = _randint_bounds[_dtype] except KeyError: raise TypeError( f'Unsupported dtype "{_dtype.name}" for randint' ) from None return (lowbnd, highbnd, - getattr(self, f"_rand_{key}"), - getattr(self, f"_rand_{key}_broadcast")) + getattr(self, rand_name), getattr(self, broadcast_name)) # generates typed random integer in [low, high] def _rand_bool(self, cnp.npy_bool low, cnp.npy_bool high, size): From d0ecacb480b8477108752d1980b76d4f8a0ed9a2 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Thu, 29 Oct 2026 14:12:10 +0100 Subject: [PATCH 4/4] Reword the randint bounds-table comment and changelog entry The table holds method names, not methods, and is keyed by np.dtype so that dtype aliases share an entry. The changelog entry now names the user-visible effect instead of implementation details. --- CHANGELOG.md | 2 +- mkl_random/mklrand.pyx | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1521b4..7c3ce52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added ### Changed -* Sped up `randint` calls that return few values: the dtype bounds table is built once, keyed by `np.dtype`, and no longer rebuilt per call [gh-199](https://github.com/IntelPython/mkl_random/pull/199) +* Reduced the per-call overhead of `randint`, which speeds up calls that return few values; generated values are unchanged [gh-199](https://github.com/IntelPython/mkl_random/pull/199) ### Fixed diff --git a/mkl_random/mklrand.pyx b/mkl_random/mklrand.pyx index b70481b..6100790 100644 --- a/mkl_random/mklrand.pyx +++ b/mkl_random/mklrand.pyx @@ -1794,7 +1794,9 @@ def _brng_id_to_name(int brng_id): return brng_name -# Largest half-open interval [low, high) per randint dtype, and its methods. +# Largest half-open interval [low, high) per randint dtype, and the names of +# its `_rand_*` and `_rand_*_broadcast` methods. Keyed by np.dtype, so aliases +# such as "long", "i4" and np.intc share an entry. _randint_bounds = { np.dtype(name): (low, high, f"_rand_{name}", f"_rand_{name}_broadcast") for name, low, high in (