random numbers No 3 (#18940)
prefer arc4 functions; use arc4 for unbiased number < UINT32_MAX; use bitmasking when the max is power of two
Costa Tsaousis committed
Nov 5, 2024 at 09:27 UTC
8af00b9dbe3d3a668144a43bcc01bcdb766109af
3 files changed
+57
-50
CMakeLists.txt
+1
@@ -389,6 +389,7 @@ check_function_exists(close_range HAVE_CLOSE_RANGE)
389
check_function_exists(backtrace HAVE_BACKTRACE)
390
391
check_function_exists(arc4random_buf HAVE_ARC4RANDOM_BUF)
392
+check_function_exists(arc4random_uniform HAVE_ARC4RANDOM_UNIFORM)
393
check_function_exists(getrandom HAVE_GETRANDOM)
394
395
#
packaging/cmake/config.cmake.h.in
+1
@@ -70,6 +70,7 @@
70
#cmakedefine HAVE_LIBCURL
71
72
#cmakedefine HAVE_ARC4RANDOM_BUF
73
+#cmakedefine HAVE_ARC4RANDOM_UNIFORM
74
#cmakedefine HAVE_RAND_S
75
#cmakedefine HAVE_GETRANDOM
76
src/libnetdata/os/random.c
+55
-50
@@ -15,7 +15,7 @@ static __attribute__((constructor)) void random_seed() {
15
srandom(seed);
16
}
17
18
-static void random_get_bytes(void *buf, size_t bytes) {
18
+static inline void random_bytes(void *buf, size_t bytes) {
19
spinlock_lock(&random_lock);
20
while (bytes > 0) {
21
if (bytes >= sizeof(uint32_t)) {
@@ -45,10 +45,10 @@ static void random_get_bytes(void *buf, size_t bytes) {
45
46
#if defined(HAVE_GETRANDOM)
47
#include <sys/random.h>
48
-void inline getrandom_get_bytes(void *buf, size_t buflen) {
48
+static inline void getrandom_bytes(void *buf, size_t bytes) {
49
ssize_t result;
50
- while (buflen > 0) {
51
- result = getrandom(buf, buflen, 0);
50
+ while (bytes > 0) {
51
+ result = getrandom(buf, bytes, 0);
52
if (result == -1) {
53
if (errno == EINTR) {
54
// Interrupted, retry
@@ -59,19 +59,19 @@ void inline getrandom_get_bytes(void *buf, size_t buflen) {
59
continue;
60
} else {
61
// fallback to RAND_bytes
62
- random_get_bytes(buf, buflen);
62
+ random_bytes(buf, bytes);
63
return;
64
}
65
}
66
buf = (uint8_t *)buf + result;
67
- buflen -= result;
67
+ bytes -= result;
68
}
69
}
70
#endif // HAVE_GETRANDOM
71
#endif // !HAVE_ARC4RANDOM_BUF && !HAVE_RAND_S
72
73
#if defined(HAVE_RAND_S)
74
-static inline void rand_s_get_bytes(void *buf, size_t bytes) {
74
+static inline void rand_s_bytes(void *buf, size_t bytes) {
75
while (bytes > 0) {
76
if (bytes >= sizeof(unsigned int)) {
77
unsigned int temp;
@@ -103,17 +103,20 @@ static inline void rand_s_get_bytes(void *buf, size_t bytes) {
103
#endif
104
105
inline void os_random_bytes(void *buf, size_t bytes) {
106
+#if defined(HAVE_ARC4RANDOM_BUF)
107
+ arc4random_buf(buf, bytes);
108
+#else
109
+
110
if(RAND_bytes((unsigned char *)buf, bytes) == 1)
111
return;
112
109
-#if defined(HAVE_ARC4RANDOM_BUF)
110
- arc4random_buf(buf, bytes);
111
-#elif defined(HAVE_GETRANDOM)
112
- getrandom_get_bytes(buf, bytes);
113
+#if defined(HAVE_GETRANDOM)
114
+ getrandom_bytes(buf, bytes);
115
#elif defined(HAVE_RAND_S)
114
- rand_s_get_bytes(buf, bytes);
116
+ rand_s_bytes(buf, bytes);
117
#else
116
- random_get_bytes(buf, bytes);
118
+ random_bytes(buf, bytes);
119
+#endif
120
#endif
121
}
122
@@ -145,51 +148,53 @@ uint64_t os_random64(void) {
148
return value;
149
}
150
148
-#define MAX_RETRIES 10 // Limit retries to avoid an infinite loop
151
+/*
152
+ * Rejection Sampling
153
+ * To reduce bias, we can use rejection sampling without creating an infinite loop.
154
+ * This technique works by discarding values that would introduce bias, but limiting
155
+ * the number of retries to avoid infinite loops.
156
+*/
157
+
158
+// Calculate an upper limit so that the range evenly divides into max.
159
+// Any values greater than this limit would introduce bias, so we discard them.
160
+#define MAX_RETRIES 10
161
+#define os_random_rejection_sampling_X(type, type_max, func, max) \
162
+ ({ \
163
+ size_t retries = 0; \
164
+ type value, upper_limit = type_max - (type_max % (max)); \
165
+ while ((value = func()) >= upper_limit && retries++ < MAX_RETRIES); \
166
+ value % (max); \
167
+ })
168
+
169
uint64_t os_random(uint64_t max) {
170
if (max <= 1) return 0;
171
152
- uint64_t value;
153
- uint64_t upper_limit;
154
- int retries = 0;
155
-
156
- /*
157
- * Rejection Sampling
158
- * To reduce bias, we can use rejection sampling without creating an infinite loop.
159
- * This technique works by discarding values that would introduce bias, but limiting
160
- * the number of retries to avoid infinite loops.
161
- */
162
-
163
- // Calculate an upper limit so that the range evenly divides into max.
164
- // Any values greater than this limit would introduce bias, so we discard them.
172
+#if defined(HAVE_ARC4RANDOM_UNIFORM)
173
+ if(max <= UINT32_MAX)
174
+ // this is not biased
175
+ return arc4random_uniform(max);
176
+#endif
177
166
- if (max <= UINT8_MAX)
167
- upper_limit = UINT8_MAX - (UINT8_MAX % max);
168
- else if (max <= UINT16_MAX)
169
- upper_limit = UINT16_MAX - (UINT16_MAX % max);
170
- else if (max <= UINT32_MAX)
171
- upper_limit = UINT32_MAX - (UINT32_MAX % max);
172
- else
173
- upper_limit = UINT64_MAX - (UINT64_MAX % max);
178
+ if ((max & (max - 1)) == 0) {
179
+ // max is a power of 2
180
+ // use bitmasking to directly generate an unbiased random number
181
175
- do {
176
- // Generate a random number with the appropriate number of bits
182
if (max <= UINT8_MAX)
178
- value = os_random8();
183
+ return os_random8() & (max - 1);
184
else if (max <= UINT16_MAX)
180
- value = os_random16();
185
+ return os_random16() & (max - 1);
186
else if (max <= UINT32_MAX)
182
- value = os_random32();
187
+ return os_random32() & (max - 1);
188
else
184
- value = os_random64();
185
-
186
- // Retry if the generated value is biased (i.e., exceeds upper_limit)
187
- if (value < upper_limit)
188
- return value % max; // Value is unbiased, return directly
189
-
190
- retries++;
191
- } while (retries < MAX_RETRIES);
189
+ return os_random64() & (max - 1);
190
+ }
191
193
- // Fallback to modulo after MAX_RETRIES, accepting minor bias
194
- return value % max;
192
+ if (max <= UINT8_MAX)
193
+ return os_random_rejection_sampling_X(uint8_t, UINT8_MAX, os_random8, max);
194
+ else if (max <= UINT16_MAX)
195
+ return os_random_rejection_sampling_X(uint16_t, UINT16_MAX, os_random16, max);
196
+ else if (max <= UINT32_MAX)
197
+ return os_random_rejection_sampling_X(uint32_t, UINT32_MAX, os_random32, max);
198
+ else
199
+ return os_random_rejection_sampling_X(uint64_t, UINT64_MAX, os_random64, max);
200
}