diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml index 03e1212d50..64c3922134 100644 --- a/doc/src/sgml/ref/pgbench.sgml +++ b/doc/src/sgml/ref/pgbench.sgml @@ -1013,6 +1013,14 @@ pgbench options dbname random_gaussian(1, 10, 2.5) an integer between 1 and 10 + + random_zipfian(lb, ub, parameter) + integer + Zipfian-distributed random integer in [lb, ub], + see below + random_zipfian(1, 10, 0.5) + an integer between 1 and 10 + sqrt(x) double @@ -1094,6 +1102,23 @@ f(x) = PHI(2.0 * parameter * (x - mu) / (max - min + 1)) / of the Box-Muller transform. + + + random_zipfian generates only approximate distribution and degree of proximity depends on parameter. + For parameter in range (0, 1) random number generated by computing Harmonic numbers(algorithm taken from + "Quickly Generating Billion-Record Synthetic Databases", Jim Gray et al, SIGMOD 1994). + And for parameter bigger than 1 rejection method used(described in "Non-Uniform Random Variate Generation", Luc Devroye, p. 550-551, Springer 1986). + It's recommended to pick parameter in range (0, 1) for better accuracy. + + + + parameter + defines how skewed the distribution is. The larger the parameter, the more + frequently value values to the beginning of the interval are drawn. + The closer to 0 parameter is, + the flatter (more uniform) the access distribution + + diff --git a/src/bin/pgbench/exprparse.y b/src/bin/pgbench/exprparse.y index b3a2d9bfd3..25d5ad48e5 100644 --- a/src/bin/pgbench/exprparse.y +++ b/src/bin/pgbench/exprparse.y @@ -191,6 +191,9 @@ static const struct { "random_exponential", 3, PGBENCH_RANDOM_EXPONENTIAL }, + { + "random_zipfian", 3, PGBENCH_RANDOM_ZIPFIAN + }, /* keep as last array element */ { NULL, 0, 0 diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c index ae78c7b1d4..5f30b07d5b 100644 --- a/src/bin/pgbench/pgbench.c +++ b/src/bin/pgbench/pgbench.c @@ -93,7 +93,10 @@ static int pthread_join(pthread_t th, void **thread_return); #define LOG_STEP_SECONDS 5 /* seconds between log messages */ #define DEFAULT_NXACTS 10 /* default nxacts */ +#define ZIPF_CACHE_SIZE 15 /* cache cells number */ + #define MIN_GAUSSIAN_PARAM 2.0 /* minimum parameter for gauss */ +#define MAX_ZIPFIAN_PARAM 1000 /* maximum parameter for zipfian */ int nxacts = 0; /* number of transactions per client */ int duration = 0; /* duration in seconds */ @@ -333,6 +336,36 @@ typedef struct int ecnt; /* error count */ } CState; +/* + * Cache cell for zipfian_random call + */ +typedef struct +{ + /* cell keys */ + double s; /* s - parameter of zipfan_random function */ + int64 n; /* number of elements in range (max - min + 1) */ + + double harmonicn; /* zipfGeneralizedHarmonic(n, s) */ + double alpha; + double beta; + double eta; + + uint64 last_touch_time; /* time when cell was used last time */ +} ZipfCell; + +/* + * Zipf cache for zeta values + */ +typedef struct +{ + uint64 current_time; /* counter for LRU cache replacement algorithm */ + + int cells_inited; /* number of filled cells */ + ZipfCell cells[ZIPF_CACHE_SIZE]; +} ZipfCache; + +bool zipfCacheOverflowMessagePrinted = false; + /* * Thread state */ @@ -345,6 +378,8 @@ typedef struct unsigned short random_state[3]; /* separate randomness for each thread */ int64 throttle_trigger; /* previous/next throttling (us) */ FILE *logfile; /* where to log, or NULL */ + ZipfCache zipf_cache; /* for thread-safe zipfian random number + * generation */ /* per thread collected stats */ instr_time start_time; /* thread start time */ @@ -737,6 +772,143 @@ getPoissonRand(TState *thread, int64 center) return (int64) (-log(uniform) * ((double) center) + 0.5); } +/* helper function for getZipfianRand */ +static double +zipfGeneralizedHarmonic(int64 n, double s) +{ + int i; + double ans = 0.0; + + for (i = n; i > 1; i--) + ans += pow(i, -s); + return ans + 1.; +} + +/* helper function for getZipfianRand. Computes random variable in range 1..n */ +static int64 +zipfn(double uniform_random, ZipfCell * zipf, int64 n, double s) +{ + double uz = uniform_random * zipf->harmonicn; + + if (uz < 1.) + return 1; + if (uz < 1. + zipf->beta) + return 2; + return 1 + (int64) (n * pow(zipf->eta * uniform_random - zipf->eta + 1., zipf->alpha)); +} + +/* set harmonicn and other parameters to cache cell */ +static void +zipfSetCacheCell(ZipfCell * cell, int64 n, double s) +{ + double harmonic2; + + cell->n = n; + cell->s = s; + + harmonic2 = zipfGeneralizedHarmonic(2, s); + cell->harmonicn = zipfGeneralizedHarmonic(n, s); + + cell->alpha = 1.0 / (1.0 - s); + cell->beta = pow(0.5, s); + cell->eta = (1.0 - pow(2.0 / n, 1.0 - s)) / (1.0 - harmonic2 / cell->harmonicn); +} + +/* + * search for cache cell with keys (n, s) + * and create new cell if it is not exist + */ +static ZipfCell * +zipfFindOrCreateCacheCell(ZipfCache * cache, int64 n, double s) +{ + int i, + least_recently_used_i = 0; + ZipfCell *cell; + + /* search cached cell for given parameters */ + for (i = 0; i < cache->cells_inited; i++) + { + cell = &cache->cells[i]; + if (cell->n == n && cell->s == s) + return &cache->cells[i]; + + if (cell->last_touch_time < cache->cells[least_recently_used_i].last_touch_time) + least_recently_used_i = i; + } + + /* create new one if it is not exist */ + if (cache->cells_inited != ZIPF_CACHE_SIZE) + i = cache->cells_inited++; + else + { + /* replace LRU cell if cache is full */ + i = least_recently_used_i; + if (!zipfCacheOverflowMessagePrinted) + { + /* + * flag can be accessed in several threads simultaneously and + * message can be printed more than one time + */ + zipfCacheOverflowMessagePrinted = true; + fprintf(stderr, "zipfian cache array overflowed\n"); + } + } + + zipfSetCacheCell(&cache->cells[i], n, s); + + return &cache->cells[i]; +} + +static int64 +computeIterativeZipfian(unsigned short random_state[3], int64 n, double s) +{ + /* computing zipfian using rejection method */ + double b = pow(2.0, s - 1.0); + double x, + t, + u, + v; + + while (true) + { + /* random variates */ + u = pg_erand48(random_state); + v = pg_erand48(random_state); + + x = floor(pow(u, -1.0 / (s - 1.0))); + + t = pow(1.0 + 1.0 / x, s - 1.0); + /* reject if too large or out of bound */ + if (v * x * (t - 1.0) / (b - 1.0) <= t / b && x <= n) + break; + } + return (int64) x; +} + +static int64 +computeHarmonicZipfian(TState *thread, int64 n, double s) +{ + ZipfCell *cell = zipfFindOrCreateCacheCell(&thread->zipf_cache, n, s); + + cell->last_touch_time = thread->zipf_cache.current_time++; + double uniform = pg_erand48(thread->random_state); + + return zipfn(uniform, cell, n, s); +} + +/* random number generator: zipfian distribution from min to max inclusive */ +static int64 +getZipfianRand(TState *thread, int64 min, int64 max, double s) +{ + int64 n = max - min + 1; + + if (s > 1) + /* it is better to use iterative way to compute when s > 1 */ + return min + computeIterativeZipfian(thread->random_state, n, s) - 1; + + return min + computeHarmonicZipfian(thread, n, s) - 1; +} + /* * Initialize the given SimpleStats struct to all zeroes */ @@ -1567,6 +1739,7 @@ evalFunc(TState *thread, CState *st, case PGBENCH_RANDOM: case PGBENCH_RANDOM_EXPONENTIAL: case PGBENCH_RANDOM_GAUSSIAN: + case PGBENCH_RANDOM_ZIPFIAN: { int64 imin, imax; @@ -1617,6 +1790,18 @@ evalFunc(TState *thread, CState *st, setIntValue(retval, getGaussianRand(thread, imin, imax, param)); } + else if (func == PGBENCH_RANDOM_ZIPFIAN) + { + if (param <= 0.0 || param == 1.0 || param > MAX_ZIPFIAN_PARAM) + { + fprintf(stderr, + "zipfian parameter must be in range (0, 1) U (1, %d]" + " (got %f)\n", MAX_ZIPFIAN_PARAM,param); + return false; + } + setIntValue(retval, + getZipfianRand(thread, imin, imax, param)); + } else /* exponential */ { if (param <= 0.0) @@ -4273,6 +4458,8 @@ main(int argc, char **argv) thread->random_state[2] = random(); thread->logfile = NULL; /* filled in later */ thread->latency_late = 0; + thread->zipf_cache.cells_inited = 0; + thread->zipf_cache.current_time = 0; initStats(&thread->stats, 0); nclients_dealt += thread->nstate; diff --git a/src/bin/pgbench/pgbench.h b/src/bin/pgbench/pgbench.h index abc13e9463..1a29f1260c 100644 --- a/src/bin/pgbench/pgbench.h +++ b/src/bin/pgbench/pgbench.h @@ -75,7 +75,8 @@ typedef enum PgBenchFunction PGBENCH_SQRT, PGBENCH_RANDOM, PGBENCH_RANDOM_GAUSSIAN, - PGBENCH_RANDOM_EXPONENTIAL + PGBENCH_RANDOM_EXPONENTIAL, + PGBENCH_RANDOM_ZIPFIAN } PgBenchFunction; typedef struct PgBenchExpr PgBenchExpr;