]> granicus.if.org Git - postgresql/commitdiff
Make pgbench use erand48() rather than random().
authorRobert Haas <rhaas@postgresql.org>
Wed, 3 Aug 2011 20:26:40 +0000 (16:26 -0400)
committerRobert Haas <rhaas@postgresql.org>
Wed, 3 Aug 2011 20:26:40 +0000 (16:26 -0400)
glibc renders random() thread-safe by wrapping a futex lock around it;
testing reveals that this limits the performance of pgbench on machines
with many CPU cores.  Rather than switching to random_r(), which is
only available on GNU systems and crashes unless you use undocumented
alchemy to initialize the random state properly, switch to our built-in
implementation of erand48(), which is both thread-safe and concurrent.

Since the list of reasons not to use the operating system's erand48()
is getting rather long, rename ours to pg_erand48() (and similarly
for our implementations of lrand48() and srand48()) and just always
use those.  We were already doing this on Cygwin anyway, and the
glibc implementation is not quite thread-safe, so pgbench wouldn't
be able to use that either.

Per discussion with Tom Lane.

12 files changed:
configure
configure.in
contrib/pgbench/pgbench.c
src/backend/optimizer/geqo/geqo_random.c
src/backend/optimizer/geqo/geqo_selection.c
src/include/optimizer/geqo.h
src/include/pg_config.h.in
src/include/port.h
src/port/Makefile
src/port/erand48.c
src/port/random.c
src/port/srandom.c

index 0e537d94f8b7b6c1823a9862813601a9e3fb314c..ddc25d53ac1a52f3238ba96a7b58bb40a99f2264 100755 (executable)
--- a/configure
+++ b/configure
@@ -20493,8 +20493,7 @@ LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'`
 
 
 
-
-for ac_func in crypt erand48 getopt getrusage inet_aton random rint srandom strdup strerror strlcat strlcpy strtol strtoul
+for ac_func in crypt getopt getrusage inet_aton random rint srandom strdup strerror strlcat strlcpy strtol strtoul
 do
 as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
 { $as_echo "$as_me:$LINENO: checking for $ac_func" >&5
@@ -21010,17 +21009,6 @@ esac
 
 fi
 
-# Cygwin's erand48() is broken (always returns zero) in some releases,
-# so force use of ours.
-if test "$PORTNAME" = "cygwin"; then
-  case " $LIBOBJS " in
-  *" erand48.$ac_objext "* ) ;;
-  *) LIBOBJS="$LIBOBJS erand48.$ac_objext"
- ;;
-esac
-
-fi
-
 # Win32 support
 if test "$PORTNAME" = "win32"; then
 
index 150f9a53d9b81eb8e523352856ab6fd26750a87b..a844afc67fc99ebd0f1801b7034f2a029407443c 100644 (file)
@@ -1311,7 +1311,7 @@ fi
 pgac_save_LIBS="$LIBS"
 LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'`
 
-AC_REPLACE_FUNCS([crypt erand48 getopt getrusage inet_aton random rint srandom strdup strerror strlcat strlcpy strtol strtoul])
+AC_REPLACE_FUNCS([crypt getopt getrusage inet_aton random rint srandom strdup strerror strlcat strlcpy strtol strtoul])
 
 case $host_os in
 
@@ -1362,12 +1362,6 @@ if test "$PORTNAME" = "win32"; then
   AC_LIBOBJ(getopt_long)
 fi
 
-# Cygwin's erand48() is broken (always returns zero) in some releases,
-# so force use of ours.
-if test "$PORTNAME" = "cygwin"; then
-  AC_LIBOBJ(erand48)
-fi
-
 # Win32 support
 if test "$PORTNAME" = "win32"; then
   AC_REPLACE_FUNCS(gettimeofday)
index 9d57436aeb77030af26b63687416d4410a83cadd..56dab6192db11001aade1988cccde9a6a20704fe 100644 (file)
@@ -198,6 +198,7 @@ typedef struct
        instr_time      start_time;             /* thread start time */
        instr_time *exec_elapsed;       /* time spent executing cmds (per Command) */
        int                *exec_count;         /* number of cmd executions (per Command) */
+       unsigned short random_state[3]; /* separate randomness for each thread */
 } TState;
 
 #define INVALID_THREAD         ((pthread_t) 0)
@@ -380,13 +381,18 @@ usage(const char *progname)
 
 /* random number generator: uniform distribution from min to max inclusive */
 static int
-getrand(int min, int max)
+getrand(TState *thread, int min, int max)
 {
        /*
         * Odd coding is so that min and max have approximately the same chance of
         * being selected as do numbers between them.
+        *
+        * pg_erand48() is thread-safe and concurrent, which is why we use it
+        * rather than random(), which in glibc is non-reentrant, and therefore
+        * protected by a mutex, and therefore a bottleneck on machines with many
+        * CPUs.
         */
-       return min + (int) (((max - min + 1) * (double) random()) / (MAX_RANDOM_VALUE + 1.0));
+       return min + (int) ((max - min + 1) * pg_erand48(thread->random_state));
 }
 
 /* call PQexec() and exit() on failure */
@@ -901,7 +907,7 @@ top:
                if (commands[st->state] == NULL)
                {
                        st->state = 0;
-                       st->use_file = getrand(0, num_files - 1);
+                       st->use_file = getrand(thread, 0, num_files - 1);
                        commands = sql_files[st->use_file];
                }
        }
@@ -1068,9 +1074,9 @@ top:
                        }
 
 #ifdef DEBUG
-                       printf("min: %d max: %d random: %d\n", min, max, getrand(min, max));
+                       printf("min: %d max: %d random: %d\n", min, max, getrand(thread, min, max));
 #endif
-                       snprintf(res, sizeof(res), "%d", getrand(min, max));
+                       snprintf(res, sizeof(res), "%d", getrand(thread, min, max));
 
                        if (!putVariable(st, argv[0], argv[1], res))
                        {
@@ -2242,6 +2248,9 @@ main(int argc, char **argv)
                thread->tid = i;
                thread->state = &state[nclients / nthreads * i];
                thread->nstate = nclients / nthreads;
+               thread->random_state[0] = random();
+               thread->random_state[1] = random();
+               thread->random_state[2] = random();
 
                if (is_latencies)
                {
@@ -2384,7 +2393,7 @@ threadRun(void *arg)
                Command   **commands = sql_files[st->use_file];
                int                     prev_ecnt = st->ecnt;
 
-               st->use_file = getrand(0, num_files - 1);
+               st->use_file = getrand(thread, 0, num_files - 1);
                if (!doCustom(thread, st, &result->conn_time, logfile))
                        remains--;                      /* I've aborted */
 
@@ -2583,17 +2592,6 @@ pthread_create(pthread_t *thread,
        if (duration > 0)
                setalarm(duration);
 
-       /*
-        * Set a different random seed in each child process.  Otherwise they all
-        * inherit the parent's state and generate the same "random" sequence. (In
-        * the threaded case, the different threads will obtain subsets of the
-        * output of a single random() sequence, which should be okay for our
-        * purposes.)
-        */
-       INSTR_TIME_SET_CURRENT(start_time);
-       srandom(((unsigned int) INSTR_TIME_GET_MICROSEC(start_time)) +
-                       ((unsigned int) getpid()));
-
        ret = start_routine(arg);
        write(th->pipes[1], ret, sizeof(TResult));
        close(th->pipes[1]);
index 6d04c8ec2f9aee0d4b0c499291b9aec96069225b..2d8cb6e2426ff399d4559ffaf2965abeb7fd5a6e 100644 (file)
@@ -36,5 +36,5 @@ geqo_rand(PlannerInfo *root)
 {
        GeqoPrivateData *private = (GeqoPrivateData *) root->join_search_private;
 
-       return erand48(private->random_state);
+       return pg_erand48(private->random_state);
 }
index af70c735df2c21fb75db634a6ceb2e0142ded415..642ac9319a142c0a07887d9ff6a2c0bce918e53f 100644 (file)
@@ -64,9 +64,9 @@ geqo_selection(PlannerInfo *root, Chromosome *momma, Chromosome *daddy,
         * Ensure we have selected different genes, except if pool size is only
         * one, when we can't.
         *
-        * This code has been observed to hang up in an infinite loop when the
-        * platform's implementation of erand48() is broken.  We consider that a
-        * feature: it lets you know you'd better fix the random-number generator.
+        * This code was observed to hang up in an infinite loop when the
+        * platform's implementation of erand48() was broken.  We now always
+        * use our own version.
         */
        if (pool->size > 1)
        {
index 62f1fd9418ecf053e6e94badfe314fc22282d335..2dd28253e1b6ca91f270bdc3aa1b14e0a67e2aab 100644 (file)
@@ -73,7 +73,7 @@ extern double Geqo_seed;              /* 0 .. 1 */
 typedef struct
 {
        List       *initial_rels;       /* the base relations we are joining */
-       unsigned short random_state[3];         /* state for erand48() */
+       unsigned short random_state[3];         /* state for pg_erand48() */
 } GeqoPrivateData;
 
 
index 24b951e7bd866cf895ea6589065e0d4e394d5ece..09a014b38531cd61583b35bc0aa0e00634521937 100644 (file)
 /* Define to 1 if you have the <editline/readline.h> header file. */
 #undef HAVE_EDITLINE_READLINE_H
 
-/* Define to 1 if you have the `erand48' function. */
-#undef HAVE_ERAND48
-
 /* Define to 1 if you have the `ERR_set_mark' function. */
 #undef HAVE_ERR_SET_MARK
 
index 2cab65fbde76e518d540625d78d60d24802ef90a..d9d61d0083d350950e529bd87ae2a8e392f4f143 100644 (file)
@@ -380,12 +380,9 @@ extern off_t ftello(FILE *stream);
 #endif
 #endif
 
-#ifndef HAVE_ERAND48
-/* we assume all of these are present or missing together */
-extern double erand48(unsigned short xseed[3]);
-extern long lrand48(void);
-extern void srand48(long seed);
-#endif
+extern double pg_erand48(unsigned short xseed[3]);
+extern long pg_lrand48(void);
+extern void pg_srand48(long seed);
 
 #ifndef HAVE_FSEEKO
 #define fseeko(a, b, c) fseek(a, b, c)
index 60295dcdefde103dc5aa52e6062940acef56b894..ffbe95effc3afb227a3f96a9ca7a0aaaefa9c489 100644 (file)
@@ -30,8 +30,8 @@ include $(top_builddir)/src/Makefile.global
 override CPPFLAGS := -I$(top_builddir)/src/port -DFRONTEND $(CPPFLAGS)
 LIBS += $(PTHREAD_LIBS)
 
-OBJS = $(LIBOBJS) chklocale.o dirmod.o exec.o inet_net_ntop.o noblock.o \
-       path.o pgcheckdir.o pgmkdirp.o pgsleep.o pgstrcasecmp.o \
+OBJS = $(LIBOBJS) chklocale.o dirmod.o erand48.o exec.o inet_net_ntop.o \
+       noblock.o path.o pgcheckdir.o pgmkdirp.o pgsleep.o pgstrcasecmp.o \
        qsort.o qsort_arg.o sprompt.o thread.o
 
 # foo_srv.o and foo.o are both built from foo.c, but only foo.o has -DFRONTEND
index 64db7a5376c88a82607db10c24fa27b1505f8b6e..1c0eb467f6df30c71fe7f091f591b3feaed09a33 100644 (file)
@@ -2,10 +2,14 @@
  *
  * erand48.c
  *
- * This file supplies versions of erand48(), lrand48(), and srand48()
- * for machines that lack them.  (These are all the members of the drand48
- * family that Postgres currently requires.  We name the file after erand48
- * because that is the one that configure tests for.)
+ * This file supplies pg_erand48(), pg_lrand48(), and pg_srand48(), which
+ * are just like erand48(), lrand48(), and srand48() except that we use
+ * our own implementation rather than the one provided by the operating
+ * system.  We used to test for an operating system version rather than
+ * unconditionally using our own, but (1) some versions of Cygwin have a
+ * buggy erand48() that always returns zero and (2) as of 2011, glibc's
+ * erand48() is strangely coded to be almost-but-not-quite thread-safe,
+ * which doesn't matter for the backend but is important for pgbench.
  *
  *
  * Copyright (c) 1993 Martin Birgmeier
@@ -72,7 +76,7 @@ _dorand48(unsigned short xseed[3])
 
 
 double
-erand48(unsigned short xseed[3])
+pg_erand48(unsigned short xseed[3])
 {
        _dorand48(xseed);
        return ldexp((double) xseed[0], -48) +
@@ -81,14 +85,14 @@ erand48(unsigned short xseed[3])
 }
 
 long
-lrand48(void)
+pg_lrand48(void)
 {
        _dorand48(_rand48_seed);
        return ((long) _rand48_seed[2] << 15) + ((long) _rand48_seed[1] >> 1);
 }
 
 void
-srand48(long seed)
+pg_srand48(long seed)
 {
        _rand48_seed[0] = RAND48_SEED_0;
        _rand48_seed[1] = (unsigned short) seed;
index 0fe492b2c67c3db5fcfcd561bda0246bf2978fd6..973108e6c45a327683d18b7841a02d61c8f20654 100644 (file)
@@ -21,5 +21,5 @@
 long
 random()
 {
-       return lrand48();
+       return pg_lrand48();
 }
index d70e3d855f6bff7b91a2f8839ddd1a2791b10183..8a25a21e926e412f56d892a5b820d6d199b670bc 100644 (file)
@@ -21,5 +21,5 @@
 void
 srandom(unsigned int seed)
 {
-       srand48((long int) seed);
+       pg_srand48((long int) seed);
 }