/*
* General purpose tool for examining random number distributions.
*
* Input -
* (a) a random number generator, and
* (b) the buckets:
* (1) number of buckets,
* (2) width of each bucket, in log scale,
* (3) expected mean and stddev of the count of random numbers in each
* bucket, and
* (c) number of iterations to invoke the generator.
*
* The program generates the specified amount of random numbers, and assess how
* well they conform to the expectations: for each bucket, output -
* (a) the (given) expected mean and stddev,
* (b) the actual count and any interesting level of deviation:
* (1) ~68% buckets should show no interesting deviation, meaning a
* deviation less than stddev from the expectation;
* (2) ~27% buckets should show '+' / '-', meaning a deviation in the range
* of [stddev, 2 * stddev) from the expectation;
* (3) ~4% buckets should show '++' / '--', meaning a deviation in the
* range of [2 * stddev, 3 * stddev) from the expectation; and
* (4) less than 0.3% buckets should show more than two '+'s / '-'s.
*
* Technical remarks:
* (a) The generator is expected to output uint64_t numbers, so you might need
* to define a wrapper.
* (b) The buckets must be of equal width and the lowest bucket starts at
* [0, 2^lg_bucket_width - 1).
* (c) Any generated number >= n_bucket * 2^lg_bucket_width will be counted
* towards the last bucket; the expected mean and stddev provided should
* also reflect that.
* (d) The number of iterations is advised to be determined so that the bucket
* with the minimal expected proportion gets a sufficient count.
*/
static void
fill(size_t a[], const size_t n, const size_t k) {
for (size_t i = 0; i < n; ++i) {
a[i] = k;
}
}
static void
collect_buckets(uint64_t (*gen)(void *), void *opaque, size_t buckets[],
const size_t n_bucket, const size_t lg_bucket_width, const size_t n_iter) {
for (size_t i = 0; i < n_iter; ++i) {
uint64_t num = gen(opaque);
uint64_t bucket_id = num >> lg_bucket_width;
if (bucket_id >= n_bucket) {
bucket_id = n_bucket - 1;
}
++buckets[bucket_id];
}
}
static void
print_buckets(const size_t buckets[], const size_t means[],
const size_t stddevs[], const size_t n_bucket) {
for (size_t i = 0; i < n_bucket; ++i) {
malloc_printf("%zu:\tmean = %zu,\tstddev = %zu,\tbucket = %zu",
i, means[i], stddevs[i], buckets[i]);
/* Make sure there's no overflow. */
assert(buckets[i] + stddevs[i] >= stddevs[i]);
assert(means[i] + stddevs[i] >= stddevs[i]);
if (buckets[i] + stddevs[i] <= means[i]) {
malloc_write(" ");
for (size_t t = means[i] - buckets[i]; t >= stddevs[i];
t -= stddevs[i]) {
malloc_write("-");
}
} else if (buckets[i] >= means[i] + stddevs[i]) {
malloc_write(" ");
for (size_t t = buckets[i] - means[i]; t >= stddevs[i];
t -= stddevs[i]) {
malloc_write("+");
}
}
malloc_write("\n");
}
}
/*
* Mathematical tricks to guarantee that both mean and stddev are
* integers, and that the minimal bucket mean is at least
* MIN_BUCKET_MEAN.
*/
const size_t q = 1 << QUOTIENT_CEIL(LG_CEIL(QUOTIENT_CEIL(
MIN_BUCKET_MEAN, N_BUCKET * (N_BUCKET - 1))), 2);
const size_t stddev = (N_BUCKET - 1) * q;
const size_t mean = N_BUCKET * stddev * q;
const size_t n_iter = N_BUCKET * mean;
/* Geometric random number generator; compiled only when prof is on. */
#ifdef JEMALLOC_PROF
/*
* Fills geometric proportions and returns the minimal proportion. See
* comments in test_prof_sample for explanations for n_divide.
*/
static double
fill_geometric_proportions(double proportions[], const size_t n_bucket,
const size_t n_divide) {
assert(n_bucket > 0);
assert(n_divide > 0);
double x = 1.;
for (size_t i = 0; i < n_bucket; ++i) {
if (i == n_bucket - 1) {
proportions[i] = x;
} else {
double y = x * exp(-1. / n_divide);
proportions[i] = x - y;
x = y;
}
}
/*
* The minimal proportion is the smaller one of the last two
* proportions for geometric distribution.
*/
double min_proportion = proportions[n_bucket - 1];
if (n_bucket >= 2 && proportions[n_bucket - 2] < min_proportion) {
min_proportion = proportions[n_bucket - 2];
}
return min_proportion;
}