@@ -1594,85 +1594,76 @@ void irk_logseries_vec(irk_state *state,
15941594 mkl_free (Uvec);
15951595}
15961596
1597- /*
1598- * Bulk source of raw uniform words for the bounded-integer
1599- * routines below, overloaded on the word type (32/64-bit). BRNGs that lack
1600- * viRngUniformBits fall back to assembling words from viRngUniform.
1601- */
1602- static inline void
1603- irk_uniform_bits_vec (irk_state *state, npy_intp len, npy_uint32 *buf)
1597+ /* viRngUniform is the only bit source for BRNGs without viRngUniformBits, and
1598+ * it emits 32-bit integers, so each word is built from 16-bit halves. */
1599+ template <typename WT >
1600+ static void irk_uniform_bits_fallback (irk_state *state, npy_intp len, WT *buf)
16041601{
16051602 int err = 0 ;
1603+ const int words = sizeof (WT ) / 2 ;
1604+ const npy_intp tile_len = 4096 ;
1605+ /* a cache-resident tile avoids allocating 2 or 4 halves per element */
1606+ int tile[tile_len];
16061607 npy_intp i = 0 ;
1608+ int h = 0 ;
16071609
16081610 while (len > 0 ) {
1609- MKL_INT c = (len > MKL_INT_MAX ) ? (MKL_INT )MKL_INT_MAX : (MKL_INT )len;
1610- err = viRngUniformBits32 (VSL_RNG_METHOD_UNIFORMBITS32_STD ,
1611- state->stream , c, (unsigned int *)buf);
1612- if (err == VSL_RNG_ERROR_BRNG_NOT_SUPPORTED ) {
1613- /* viRngUniformBits32 unsupported for WH/MCG31/R250/MRG32K3A;
1614- * build each word from two 16-bit viRngUniform halves */
1615- npy_intp total = 2 * (npy_intp)c, rem = total, off = 0 ;
1616- int *tmp = (int *)mkl_malloc (total * sizeof (int ), 64 );
1617- assert (tmp != nullptr );
1618- /* one call unless the count exceeds MKL_INT */
1619- while (rem > 0 ) {
1620- MKL_INT cc =
1621- (rem > MKL_INT_MAX ) ? (MKL_INT )MKL_INT_MAX : (MKL_INT )rem;
1622- err = viRngUniform (VSL_RNG_METHOD_UNIFORM_STD , state->stream ,
1623- cc, tmp + off, 0 , 65536 );
1624- assert (err == VSL_STATUS_OK );
1625- off += cc;
1626- rem -= cc;
1627- }
1628- for (i = 0 ; i < c; ++i)
1629- buf[i] = ((npy_uint32)tmp[2 * i]) |
1630- (((npy_uint32)tmp[2 * i + 1 ]) << 16 );
1631- mkl_free (tmp);
1632- }
1633- else {
1634- assert (err == VSL_STATUS_OK );
1611+ const npy_intp n = (len < tile_len / words) ? len : tile_len / words;
1612+ err = viRngUniform (VSL_RNG_METHOD_UNIFORM_STD , state->stream ,
1613+ (int )(n * words), tile, 0 , 65536 );
1614+ assert (err == VSL_STATUS_OK );
1615+
1616+ for (i = 0 ; i < n; ++i) {
1617+ WT w = 0 ;
1618+
1619+ for (h = 0 ; h < words; ++h)
1620+ w |= ((WT )(npy_uint32)tile[i * words + h]) << (16 * h);
1621+
1622+ buf[i] = w;
16351623 }
1636- buf += c;
1637- len -= c;
1624+
1625+ buf += n;
1626+ len -= n;
16381627 }
16391628}
16401629
1641- static inline void
1642- irk_uniform_bits_vec (irk_state *state, npy_intp len, npy_uint64 *buf)
1630+ static inline int
1631+ irk_uniform_bits_call (irk_state *state, MKL_INT count, npy_uint32 *buf)
1632+ {
1633+ return viRngUniformBits32 (VSL_RNG_METHOD_UNIFORMBITS32_STD , state->stream ,
1634+ count, (unsigned int *)buf);
1635+ }
1636+
1637+ static inline int
1638+ irk_uniform_bits_call (irk_state *state, MKL_INT count, npy_uint64 *buf)
1639+ {
1640+ return viRngUniformBits64 (VSL_RNG_METHOD_UNIFORMBITS64_STD , state->stream ,
1641+ count, (unsigned MKL_INT64 *)buf);
1642+ }
1643+
1644+ /*
1645+ * Bulk source of raw uniform words for the bounded-integer
1646+ * routines below, templated on the word type (32/64-bit). BRNGs that lack
1647+ * viRngUniformBits fall back to assembling words from viRngUniform.
1648+ */
1649+ template <typename WT >
1650+ static inline void irk_uniform_bits_vec (irk_state *state, npy_intp len, WT *buf)
16431651{
16441652 int err = 0 ;
1645- npy_intp i = 0 ;
16461653 /* viRngUniformBits64 counts 32-bit words, so its count must fit half of
16471654 * MKL_INT; larger requests under-fill the buffer or crash */
1648- const npy_intp bits64_max = MKL_INT_MAX / 2 ;
1655+ const npy_intp max_count =
1656+ (sizeof (WT ) == 8 ) ? MKL_INT_MAX / 2 : MKL_INT_MAX ;
1657+
1658+ static_assert (sizeof (WT ) == 4 || sizeof (WT ) == 8 ,
1659+ " uniform bits are generated in 32- or 64-bit words" );
16491660
16501661 while (len > 0 ) {
1651- MKL_INT c = (len > bits64_max) ? (MKL_INT )bits64_max : (MKL_INT )len;
1652- err = viRngUniformBits64 (VSL_RNG_METHOD_UNIFORMBITS64_STD ,
1653- state->stream , c, (unsigned MKL_INT64 *)buf);
1662+ MKL_INT c = (len > max_count) ? (MKL_INT )max_count : (MKL_INT )len;
1663+ err = irk_uniform_bits_call (state, c, buf);
16541664 if (err == VSL_RNG_ERROR_BRNG_NOT_SUPPORTED ) {
1655- /* viRngUniformBits64 unsupported for WH/MCG31/R250/MRG32K3A;
1656- * build each word from four 16-bit viRngUniform halves */
1657- npy_intp total = 4 * (npy_intp)c, rem = total, off = 0 ;
1658- int *tmp = (int *)mkl_malloc (total * sizeof (int ), 64 );
1659- assert (tmp != nullptr );
1660- /* one call unless the count exceeds MKL_INT */
1661- while (rem > 0 ) {
1662- MKL_INT cc =
1663- (rem > MKL_INT_MAX ) ? (MKL_INT )MKL_INT_MAX : (MKL_INT )rem;
1664- err = viRngUniform (VSL_RNG_METHOD_UNIFORM_STD , state->stream ,
1665- cc, tmp + off, 0 , 65536 );
1666- assert (err == VSL_STATUS_OK );
1667- off += cc;
1668- rem -= cc;
1669- }
1670- for (i = 0 ; i < c; ++i)
1671- buf[i] = ((npy_uint64)(npy_uint32)tmp[4 * i]) |
1672- (((npy_uint64)(npy_uint32)tmp[4 * i + 1 ]) << 16 ) |
1673- (((npy_uint64)(npy_uint32)tmp[4 * i + 2 ]) << 32 ) |
1674- (((npy_uint64)(npy_uint32)tmp[4 * i + 3 ]) << 48 );
1675- mkl_free (tmp);
1665+ /* unsupported for WH/MCG31/R250/MRG32K3A */
1666+ irk_uniform_bits_fallback (state, (npy_intp)c, buf);
16761667 }
16771668 else {
16781669 assert (err == VSL_STATUS_OK );
@@ -2054,8 +2045,9 @@ static inline npy_uint64 irk_mulhi(npy_uint64 a, npy_uint64 b, npy_uint64 *lo)
20542045/*
20552046 * Draw res[i] uniformly from [low[i], hi[i]] (inclusive) using Lemire's
20562047 * multiply-shift method (per-element bounds, same as NumPy).
2057- * Words are generated in bulk by MKL; the rare rejected elements are
2058- * gathered into `idx` (allocated lazily) and retried on the next round.
2048+ * Words are generated by MKL in cache-sized chunks; the rejected
2049+ * elements are gathered into `idx` (allocated lazily) and retried
2050+ * locally within each chunk.
20592051 * T is the result type, UT its unsigned counterpart,
20602052 * WT the raw-word type (s wraps to 0 for a full-range draw).
20612053 */
@@ -2066,76 +2058,123 @@ static void irk_rand_bounded_broadcast(irk_state *state,
20662058 const T *low,
20672059 const T *hi)
20682060{
2069- npy_intp i = 0 ;
2070- npy_intp k = 0 ;
2071- npy_intp n_pending = 0 ;
2072- npy_intp *idx = nullptr ;
2073- WT *words = nullptr ;
2061+ npy_intp *idx = nullptr ; /* reject indices */
20742062
20752063 if (len < 1 )
20762064 return ;
20772065
2078- /* TODO: possible speedup :
2079- * generate and consume words in cache-sized chunks
2080- * instead of one full-length pass */
2081- words = (WT *)mkl_malloc (len * sizeof (WT ), 64 );
2082- assert (words != nullptr );
2066+ /* Optimized path:
2067+ * cache-sized chunks instead of one full-length pass */
2068+ const npy_intp CHUNK_SIZE = 1 << 15 ; /* ~32K elements per chunk */
2069+ npy_intp chunk_cap = (len < CHUNK_SIZE ) ? len : CHUNK_SIZE ;
20832070
2084- irk_uniform_bits_vec (state, len, words);
2071+ WT *words = (WT *)mkl_malloc (chunk_cap * sizeof (WT ), 64 );
2072+ assert (words != nullptr );
20852073
2086- for (i = 0 ; i < len; ++i) {
2087- WT w = (WT )words[i];
2088- /* diff cast back to UT so narrow types wrap (no signed promotion) */
2089- UT d = (UT )(((UT )hi[i]) - ((UT )low[i]));
2090- WT s = (WT )d + 1 ; /* 0 iff full range (32/64-bit only) */
2091- WT result = w;
2092-
2093- if (s != 0 ) {
2094- WT lo = 0 ;
2095- result = irk_mulhi (w, s, &lo);
2096- if (lo < s) { /* rare */
2097- WT t = (WT )(0 - s) % s;
2098- if (lo < t) {
2099- if (idx == nullptr ) {
2100- idx =
2101- (npy_intp *)mkl_malloc (len * sizeof (npy_intp), 64 );
2102- assert (idx != nullptr );
2074+ /* `lo < s` is free for narrow ranges, but mispredicts for wide ones:
2075+ * count it on the first chunk, then pick the cheaper test */
2076+ npy_intp n_hits = 0 ;
2077+ bool wide = false ;
2078+ /* memoized reject threshold */
2079+ WT last_s = 0 , last_t = 0 ;
2080+
2081+ for (npy_intp base = 0 ; base < len; base += chunk_cap) {
2082+ npy_intp chunk = (len - base < chunk_cap) ? (len - base) : chunk_cap;
2083+ npy_intp n_pending = 0 ;
2084+
2085+ irk_uniform_bits_vec (state, chunk, words);
2086+
2087+ if (wide) {
2088+ for (npy_intp i = 0 ; i < chunk; ++i) {
2089+ npy_intp j = base + i;
2090+ WT w = (WT )words[i];
2091+ UT d = (UT )(((UT )hi[j]) - ((UT )low[j]));
2092+ WT s = (WT )d + 1 ;
2093+ WT result = w;
2094+
2095+ if (s != 0 ) {
2096+ WT lo = 0 ;
2097+ result = irk_mulhi (w, s, &lo);
2098+ if (s != last_s) {
2099+ last_t = (WT )(0 - s) % s;
2100+ last_s = s;
2101+ }
2102+ if (lo < last_t ) {
2103+ if (idx == nullptr ) {
2104+ idx = (npy_intp *)mkl_malloc (
2105+ chunk_cap * sizeof (npy_intp), 64 );
2106+ assert (idx != nullptr );
2107+ }
2108+ idx[n_pending++] = j;
2109+ continue ;
21032110 }
2104- idx[n_pending++] = i;
2105- continue ;
21062111 }
2112+ res[j] = (T)(((UT )low[j]) + (UT )result);
21072113 }
21082114 }
2109- res[i] = (T)(((UT )low[i]) + (UT )result);
2110- }
2111-
2112- while (n_pending > 0 ) {
2113- npy_intp wpos = 0 ;
2114-
2115- irk_uniform_bits_vec (state, n_pending, words);
2115+ else {
2116+ for (npy_intp i = 0 ; i < chunk; ++i) {
2117+ npy_intp j = base + i;
2118+ WT w = (WT )words[i];
2119+ /* diff cast back to UT so narrow types wrap (no signed
2120+ * promotion) */
2121+ UT d = (UT )(((UT )hi[j]) - ((UT )low[j]));
2122+ WT s = (WT )d + 1 ; /* 0 iff full range (32/64-bit only) */
2123+ WT result = w;
2124+
2125+ if (s != 0 ) {
2126+ WT lo = 0 ;
2127+ result = irk_mulhi (w, s, &lo);
2128+ if (lo < s) { /* rare */
2129+ WT t = (WT )(0 - s) % s;
2130+ ++n_hits;
2131+ if (lo < t) {
2132+ if (idx == nullptr ) {
2133+ idx = (npy_intp *)mkl_malloc (
2134+ chunk_cap * sizeof (npy_intp), 64 );
2135+ assert (idx != nullptr );
2136+ }
2137+ idx[n_pending++] = j;
2138+ continue ;
2139+ }
2140+ }
2141+ }
2142+ res[j] = (T)(((UT )low[j]) + (UT )result);
2143+ }
21162144
2117- for (k = 0 ; k < n_pending; ++k) {
2118- npy_intp j = idx[k];
2119- WT w = (WT )words[k];
2120- UT d = (UT )(((UT )hi[j]) - ((UT )low[j]));
2121- WT s = (WT )d + 1 ;
2122- WT result = w;
2145+ if (base == 0 )
2146+ wide = n_hits > chunk / 16 ;
2147+ }
21232148
2124- if (s != 0 ) {
2125- WT lo = 0 ;
2126- result = irk_mulhi (w, s, &lo);
2127- if (lo < s) {
2128- WT t = (WT )(0 - s) % s;
2129- if (lo < t) {
2130- /* keep pending; wpos <= k so idx[k] read first */
2131- idx[wpos++] = j;
2132- continue ;
2149+ /* retry the chunk's rejects locally with fresh words */
2150+ while (n_pending > 0 ) {
2151+ npy_intp wpos = 0 ;
2152+
2153+ irk_uniform_bits_vec (state, n_pending, words);
2154+
2155+ for (npy_intp k = 0 ; k < n_pending; ++k) {
2156+ npy_intp j = idx[k];
2157+ WT w = (WT )words[k];
2158+ UT d = (UT )(((UT )hi[j]) - ((UT )low[j]));
2159+ WT s = (WT )d + 1 ;
2160+ WT result = w;
2161+
2162+ if (s != 0 ) {
2163+ WT lo = 0 ;
2164+ result = irk_mulhi (w, s, &lo);
2165+ if (lo < s) {
2166+ WT t = (WT )(0 - s) % s;
2167+ if (lo < t) {
2168+ /* keep pending; wpos <= k so idx[k] read first */
2169+ idx[wpos++] = j;
2170+ continue ;
2171+ }
21332172 }
21342173 }
2174+ res[j] = (T)(((UT )low[j]) + (UT )result);
21352175 }
2136- res[j] = (T)((( UT )low[j]) + ( UT )result) ;
2176+ n_pending = wpos ;
21372177 }
2138- n_pending = wpos;
21392178 }
21402179
21412180 if (idx != nullptr )
0 commit comments