Skip to content

Commit b21a22c

Browse files
authored
Merge pull request #178 from IntelPython/refactor-uniform-bits-vec
Remove the per-element staging buffer in the BRNG fallback
2 parents 66776f3 + 8a37133 commit b21a22c

2 files changed

Lines changed: 55 additions & 63 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2323
* Extended the `memcpy`-based fast path of `shuffle` to multi-dimensional `ndarray` inputs whose first-axis items are contiguous, which is also much faster than the previous buffered path [gh-159](https://github.com/IntelPython/mkl_random/pull/159)
2424
* Updated the `randint_untyped` implementation, which now supports `array_like` (broadcastable) `low`/`high` bounds [gh-176](https://github.com/IntelPython/mkl_random/pull/176)
2525
* Sped up `randint` with `array_like` bounds by generating words in cache-sized chunks and by picking the Lemire rejection test by range width [gh-173](https://github.com/IntelPython/mkl_random/pull/173)
26+
* Reduced the memory use and sped up integer generation with `WH`, `MCG31`, `R250` and `MRG32K3A`, which lack `viRngUniformBits` support [gh-178](https://github.com/IntelPython/mkl_random/pull/178)
2627

2728
### Fixed
2829
* Fixed an out-of-range integer `brng` indexing `brng_list` past its end, which returned uninitialized memory as random values; it now warns and falls back to `MT19937` [gh-177](https://github.com/IntelPython/mkl_random/pull/177)

‎mkl_random/src/mkl_distributions.cpp‎

Lines changed: 54 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -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);

0 commit comments

Comments
 (0)