extmod/modrandom: Add a 64-bits native PRNG for 64-bits systems.
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 5/5
- Tempo stimato
- Più di una settimana
- Idoneità per principianti
- 45/100
- Tipo di issue
- Funzionalità
- Chiarezza
- Abbastanza chiara
- Stato di attività
- Attiva
- Stack tecnologico
- c
- Ambito
- embedded-iot
Direzione di ricerca
Inizia con extmod/modrandom.c e con le macro di configurazione e l’implementazione del PRNG proposte nell’issue. Traccia come getrandbits, l’inizializzazione del seed, randrange e la generazione in virgola mobile usano il generatore corrente su target a 32 bit e a 64 bit. Il lavoro è completato quando un generatore nativo a 64 bit attivabile opzionalmente funziona sulle architetture previste, mentre Yasmarang rimane quello predefinito per compatibilità.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Description
Right now MicroPython is using a PRNG that is meant to emit numbers in the 0..2³²-1 range (Yasmarang). Whilst this is fine on most MicroPython targets, for 64-bits architecture this may be a problem. See also #6718 for an existing description of other potential issues.
For example, if I got my maths right, a naïve implementation of a 64-bits PRNG calling Yasmarang twice to fill both halves of a 64-bits word would have one chance in 2³² to emit values that have the top 32 bits set to 0 (and similarly increasing probabilities for smaller upper contiguous sequences of zeroes). This can be fine for certain situations, but there are better algorithms out there that strive to be uniform across the whole 0..2⁶⁴-1 range.
https://prng.di.unimi.it/ contains a selection of fast, lean, and portable generators that pass most statistical randomness tests, and it is my understanding that those generators have similar capabilities as Yasmarang - albeit with larger periods for 64-bits generators. It could be useful to add one of those 64-bits native generators for 64-bits architectures on a opt-in basis (ie. 64-bits Unix targets, Win64, and QEMU/RV64), and keep Yasmarang around as a default to maintain compatibility.
Code Size
As a test I've added a xoroshiro128** implementation to extmod/modrandom.c and on my Linux/x64 machine the size increase is 128 bytes [1]. Most of that extra space is taken by the state seeder method suggested by the PRNG's paper, as it uses a few 64-bits constants and the compiler unrolls the small count iterations loop as well (#pragma GCC unroll 1/_Pragma("GCC unroll 1") doesn't seem to work). Still, ~128 bytes is relatively little overhead for an already larger binary when compared to the rest of the supported targets.
[1]
diff --git a/extmod/modrandom.c b/extmod/modrandom.c
index 79a1b18ba..cacce8b8c 100644
--- a/extmod/modrandom.c
+++ b/extmod/modrandom.c
@@ -38,6 +38,17 @@
#define SEED_ON_IMPORT (0)
#endif
+#if MICROPY_PY_RANDOM_YASMARANG && MICROPY_PY_RANDOM_XOROSHIRO128SS
+#error "Must pick only one PRNG algorithm."
+#endif
+
+// If Xoshiro128** is not explicitly defined use Yasmarang as a default.
+#if !MICROPY_PY_RANDOM_YASMARANG && !MICROPY_PY_RANDOM_XOROSHIRO128SS
+#define MICROPY_PY_RANDOM_YASMARANG (1)
+#endif
+
+#if MICROPY_PY_RANDOM_YASMARANG
+
// Yasmarang random number generator
// by Ilya Levin
// http://www.literatecode.com/yasmarang
@@ -67,36 +78,103 @@ static uint32_t yasmarang(void) {
// End of Yasmarang
+#define RNDINITSEED(seed) \
+ do { \
+ yasmarang_pad = (uint32_t)seed; \
+ yasmarang_n = 69; \
+ yasmarang_d = 233; \
+ yasmarang_dat = 0; \
+ } while (0)
+#define RND() yasmarang()
+#define RNDBITS 32
#if MICROPY_PY_RANDOM_EXTRA_FUNCS
+#define RNDTYPE uint32_t
+#if MICROPY_PY_BUILTINS_FLOAT
+#define RND32() yasmarang()
+#define RND64() (((uint64_t)yasmarang() << 32) | (uint64_t)yasmarang())
+#endif
+#endif
-// returns an unsigned integer below the given argument
-// n must not be zero
-static uint32_t yasmarang_randbelow(uint32_t n) {
- uint32_t mask = 1;
- while ((n & mask) < n) {
- mask = (mask << 1) | 1;
- }
- uint32_t r;
- do {
- r = yasmarang() & mask;
- } while (r >= n);
- return r;
+#elif MICROPY_PY_RANDOM_XOROSHIRO128SS
+
+#if !MICROPY_ENABLE_DYNRUNTIME
+#if SEED_ON_IMPORT
+// If the state is seeded on import then keep these variables in the BSS.
+static uint64_t xoroshiro128_state[2];
+#else
+// Without seed-on-import these variables must be initialised via the data section.
+static uint64_t xoroshiro128_state[2] = {
+ // Generated with splitmix64 using 0xbba490ef784fbcbf as the start value.
+ 0xE8244547623F34E2, 0x2EAE10A6E969FC43
+};
+#endif
+#endif
+
+#if __has_builtin(__builtin_stdc_rotate_left)
+#define XOROSHIRO_ROL(x, k) __builtin_stdc_rotate_left(x, k)
+#else
+static uint64_t xoroshiro_rol(uint64_t x, uint64_t k) {
+ return (x << k) | (x >> (64 - k));
}
+#define XOROSHIRO_ROL(x, k) xoroshiro_rol(x, k)
+#endif
+
+// Xoroshiro128** by David Blackman and Sebastiano Vigna
+// https://prng.di.unimi.it/xoroshiro128starstar.c
+// Original implementation released as public domain
+static uint64_t xoroshiro128starstar() {
+ const uint64_t s0 = xoroshiro128_state[0];
+ uint64_t s1 = xoroshiro128_state[1];
+ const uint64_t result = XOROSHIRO_ROL(s0 * 5, 7) * 9;
+
+ s1 ^= s0;
+ xoroshiro128_state[0] = XOROSHIRO_ROL(s0, 24) ^ s1 ^ (s1 << 16);
+ xoroshiro128_state[1] = XOROSHIRO_ROL(s1, 37);
+
+ return result;
+}
+
+// Splitmix64 by Sebastiano Vigna
+// https://prng.di.unimi.it/splitmix64.c
+// Original implementation released as public domain
+#define RNDINITSEED(seed) \
+ do { \
+ uint64_t x = (uint64_t)seed; \
+ for (size_t i = 0; i < 2; ++i) { \
+ uint64_t z = (x += 0x9e3779b97f4a7c15); \
+ z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; \
+ z = (z ^ (z >> 27)) * 0x94d049bb133111eb; \
+ xoroshiro128_state[i] = z ^ (z >> 31); \
+ } \
+ } while (0)
+#define RND() xoroshiro128starstar()
+#define RNDBITS 64
+#if MICROPY_PY_RANDOM_EXTRA_FUNCS
+#define RNDTYPE uint64_t
+#if MICROPY_PY_BUILTINS_FLOAT
+#define RND32() ((uint32_t)(xoroshiro128starstar() >> 32))
+#define RND64() xoroshiro128starstar()
+#endif
+#endif
+
+#else
+
+#error "Please define either MICROPY_PY_RANDOM_YASMARANG or MICROPY_PY_RANDOM_XOROSHIRO128SS."
#endif
static mp_obj_t mod_random_getrandbits(mp_obj_t num_in) {
mp_int_t n = mp_obj_get_int(num_in);
- if (n > 32 || n < 0) {
- mp_raise_ValueError(MP_ERROR_TEXT("bits must be 32 or less"));
+ if (n > RNDBITS || n < 0) {
+ mp_raise_ValueError(MP_ERROR_TEXT("bits must be " MP_STRINGIFY(RNDBITS) " or less"));
}
if (n == 0) {
return MP_OBJ_NEW_SMALL_INT(0);
}
- uint32_t mask = ~0;
+ RNDTYPE mask = (RNDTYPE)-1;
// Beware of C undefined behavior when shifting by >= than bit size
- mask >>= (32 - n);
- return mp_obj_new_int_from_uint(yasmarang() & mask);
+ mask >>= (RNDBITS - n);
+ return mp_obj_new_int_from_uint(RND() & mask);
}
static MP_DEFINE_CONST_FUN_OBJ_1(mod_random_getrandbits_obj, mod_random_getrandbits);
@@ -111,22 +189,33 @@ static mp_obj_t mod_random_seed(size_t n_args, const mp_obj_t *args) {
} else {
seed = mp_obj_get_int_truncated(args[0]);
}
- yasmarang_pad = (uint32_t)seed;
- yasmarang_n = 69;
- yasmarang_d = 233;
- yasmarang_dat = 0;
+ RNDINITSEED(seed);
return mp_const_none;
}
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_random_seed_obj, 0, 1, mod_random_seed);
#if MICROPY_PY_RANDOM_EXTRA_FUNCS
+// returns an unsigned integer below the given argument
+// n must not be zero
+static RNDTYPE randbelow(RNDTYPE n) {
+ RNDTYPE mask = 1;
+ while ((n & mask) < n) {
+ mask = (mask << 1) | 1;
+ }
+ RNDTYPE r;
+ do {
+ r = RND() & mask;
+ } while (r >= n);
+ return r;
+}
+
static mp_obj_t mod_random_randrange(size_t n_args, const mp_obj_t *args) {
mp_int_t start = mp_obj_get_int(args[0]);
if (n_args == 1) {
// range(stop)
if (start > 0) {
- return mp_obj_new_int(yasmarang_randbelow((uint32_t)start));
+ return mp_obj_new_int(randbelow(start));
} else {
goto error;
}
@@ -135,7 +224,7 @@ static mp_obj_t mod_random_randrange(size_t n_args, const mp_obj_t *args) {
if (n_args == 2) {
// range(start, stop)
if (start < stop) {
- return mp_obj_new_int(start + yasmarang_randbelow((uint32_t)(stop - start)));
+ return mp_obj_new_int(start + randbelow(stop - start));
} else {
goto error;
}
@@ -151,7 +240,7 @@ static mp_obj_t mod_random_randrange(size_t n_args, const mp_obj_t *args) {
goto error;
}
if (n > 0) {
- return mp_obj_new_int(start + step * yasmarang_randbelow((uint32_t)n));
+ return mp_obj_new_int(start + step * randbelow(n));
} else {
goto error;
}
@@ -167,7 +256,7 @@ static mp_obj_t mod_random_randint(mp_obj_t a_in, mp_obj_t b_in) {
mp_int_t a = mp_obj_get_int(a_in);
mp_int_t b = mp_obj_get_int(b_in);
if (a <= b) {
- return mp_obj_new_int(a + yasmarang_randbelow((uint32_t)(b - a + 1)));
+ return mp_obj_new_int(a + randbelow(b - a + 1));
} else {
mp_raise_ValueError(NULL);
}
@@ -177,7 +266,7 @@ static MP_DEFINE_CONST_FUN_OBJ_2(mod_random_randint_obj, mod_random_randint);
static mp_obj_t mod_random_choice(mp_obj_t seq) {
mp_int_t len = mp_obj_get_int(mp_obj_len(seq));
if (len > 0) {
- return mp_obj_subscr(seq, mp_obj_new_int(yasmarang_randbelow((uint32_t)len)), MP_OBJ_SENTINEL);
+ return mp_obj_subscr(seq, mp_obj_new_int(randbelow(len)), MP_OBJ_SENTINEL);
} else {
mp_raise_type(&mp_type_IndexError);
}
@@ -187,27 +276,27 @@ static MP_DEFINE_CONST_FUN_OBJ_1(mod_random_choice_obj, mod_random_choice);
#if MICROPY_PY_BUILTINS_FLOAT
// returns a number in the range [0..1) using Yasmarang to fill in the fraction bits
-static mp_float_t yasmarang_float(void) {
+static mp_float_t random_float(void) {
mp_float_union_t u;
u.p.sgn = 0;
u.p.exp = (1 << (MP_FLOAT_EXP_BITS - 1)) - 1;
if (MP_FLOAT_FRAC_BITS <= 32) {
- u.p.frc = yasmarang();
+ u.p.frc = RND32();
} else {
- u.p.frc = ((uint64_t)yasmarang() << 32) | (uint64_t)yasmarang();
+ u.p.frc = RND64();
}
return u.f - 1;
}
static mp_obj_t mod_random_random(void) {
- return mp_obj_new_float(yasmarang_float());
+ return mp_obj_new_float(random_float());
}
static MP_DEFINE_CONST_FUN_OBJ_0(mod_random_random_obj, mod_random_random);
static mp_obj_t mod_random_uniform(mp_obj_t a_in, mp_obj_t b_in) {
mp_float_t a = mp_obj_get_float(a_in);
mp_float_t b = mp_obj_get_float(b_in);
- return mp_obj_new_float(a + (b - a) * yasmarang_float());
+ return mp_obj_new_float(a + (b - a) * random_float());
}
static MP_DEFINE_CONST_FUN_OBJ_2(mod_random_uniform_obj, mod_random_uniform);
Implementation
I intend to implement this feature and would submit a Pull Request if desirable
Code of Conduct
Yes, I agree
- Lingua principale
- C
- Stelle
- 22.2k
- Fork
- 9.1k
- Merge medio
- 6g 8h
- PR unite (30g)
- 33
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di micropython/micropython
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
micropython/micropython#19942 ·
I maintainer di solito rispondono entro 1 giorno
-
bug port-rp2
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
micropython/micropython#19933 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
micropython/micropython#19906 ·
I maintainer di solito rispondono entro 1 giorno
-
bug port-stm32
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
micropython/micropython#19905 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 83/100
micropython/micropython#19873 ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di micropython/micropython
Issue simili
-
快速入门那里会不会对新手不太友好,提出优化方案Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 66/100
-
-disable-symbolserver is ignored on macOS/Linux: PDBs are still downloaded from msdl.microsoft.comAperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 77/100
-
follow-up Prio 📌
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 86/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
facebookincubator/muse-gadget-sdk#384 ·
I maintainer di solito rispondono entro 5 giorni