Fixes #18: lrand48() is not thread-safe

and
LP#1412488: lrand48() doesn't scale well on highly concurrent platforms

Use re-entrant versions of standard RNG functions with thread-local
states. Seed thread-local RNGs from the RNG of the thread invoking
sb_thread_create() (i.e. the main thread).

The main thread RNG is now always seeded from timer, unless --rand-seed
is specified. Which obsoletes --rand-init, so it has been removed.
This commit is contained in:
Alexey Kopytov
2016-02-24 00:05:05 +08:00
parent 3e0a29c57f
commit e24a028409
15 changed files with 310 additions and 71 deletions

View File

@ -73,7 +73,6 @@ The table below lists the supported common options, their descriptions and defau
| `--max-requests` | Limit for total number of requests. 0 means unlimited | 10000 |
| `--max-time` | Limit for total execution time in seconds. 0 (default) means unlimited | 0 |
| `--thread-stack-size` | Size of stack for each thread | 32K |
| `--init-rng` | Specifies if random numbers generator should be initialized from timer before the test start | off |
| `--report-interval` | Periodically report intermediate statistics with a specified interval in seconds. Note that statistics produced by this option is per-interval rather than cumulative. 0 disables intermediate reports | 0 |
| `--test` | Name of the test mode to run | *Required* |
| `--debug` | Print more debug info | off |

View File

@ -264,6 +264,9 @@ AC_C_INLINE
AC_TYPE_OFF_T
AC_TYPE_SIZE_T
AC_HEADER_TIME
AX_TLS([], [
AC_MSG_ERROR([Thread-local storage is not suppored by the target platform!])
])
if test "$enable_largefile" = yes; then
AC_SYS_LARGEFILE
@ -295,8 +298,8 @@ alarm \
directio \
fdatasync \
gettimeofday \
lrand48 \
drand48 \
lrand48_r
rand_r \
memalign \
memset \
mkstemp \

74
m4/ax_tls.m4 Normal file
View File

@ -0,0 +1,74 @@
# ===========================================================================
# http://www.gnu.org/software/autoconf-archive/ax_tls.html
# ===========================================================================
#
# SYNOPSIS
#
# AX_TLS([action-if-found], [action-if-not-found])
#
# DESCRIPTION
#
# Provides a test for the compiler support of thread local storage (TLS)
# extensions. Defines TLS if it is found. Currently knows about GCC/ICC
# and MSVC. I think SunPro uses the same as GCC, and Borland apparently
# supports either.
#
# LICENSE
#
# Copyright (c) 2008 Alan Woodland <ajw05@aber.ac.uk>
# Copyright (c) 2010 Diego Elio Petteno` <flameeyes@gmail.com>
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
# As a special exception, the respective Autoconf Macro's copyright owner
# gives unlimited permission to copy, distribute and modify the configure
# scripts that are the output of Autoconf when processing the Macro. You
# need not follow the terms of the GNU General Public License when using
# or distributing such scripts, even though portions of the text of the
# Macro appear in them. The GNU General Public License (GPL) does govern
# all other use of the material that constitutes the Autoconf Macro.
#
# This special exception to the GPL applies to versions of the Autoconf
# Macro released by the Autoconf Archive. When you make and distribute a
# modified version of the Autoconf Macro, you may extend this special
# exception to the GPL to apply to your modified version as well.
#serial 11
AC_DEFUN([AX_TLS], [
AC_MSG_CHECKING([for thread local storage (TLS) class])
AC_CACHE_VAL([ac_cv_tls],
[for ax_tls_keyword in __thread '__declspec(thread)' none; do
AS_CASE([$ax_tls_keyword],
[none], [ac_cv_tls=none ; break],
[AC_TRY_COMPILE(
[#include <stdlib.h>
static void
foo(void) {
static ] $ax_tls_keyword [ int bar;
exit(1);
}],
[],
[ac_cv_tls=$ax_tls_keyword ; break],
ac_cv_tls=none
)])
done
])
AC_MSG_RESULT([$ac_cv_tls])
AS_IF([test "$ac_cv_tls" != "none"],
[AC_DEFINE_UNQUOTED([TLS],[$ac_cv_tls],[If the compiler supports a TLS storage class define it to that here])
m4_ifnblank([$1],[$1])],
[m4_ifnblank([$2],[$2])])
])

View File

@ -49,7 +49,8 @@ endif
sysbench_SOURCES = sysbench.c sysbench.h sb_timer.c sb_timer.h \
sb_options.c sb_options.h sb_logger.c sb_logger.h sb_list.h db_driver.h \
db_driver.c sb_percentile.c sb_percentile.h
db_driver.c sb_percentile.c sb_percentile.h sb_rnd.c sb_rnd.h \
sb_thread.c sb_thread.h
sysbench_LDADD = tests/fileio/libsbfileio.a tests/threads/libsbthreads.a \
tests/memory/libsbmemory.a tests/cpu/libsbcpu.a \

27
sysbench/sb_rnd.c Normal file
View File

@ -0,0 +1,27 @@
/*
Copyright (C) 2016 Alexey Kopytov <akopytov@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef HAVE_LRAND48_R
TLS struct drand48_data sb_rng_state;
#elif defined(HAVE_RAND_R)
TLS unsigned int sb_rng_state;
#endif

56
sysbench/sb_rnd.h Normal file
View File

@ -0,0 +1,56 @@
/*
Copyright (C) 2016 Alexey Kopytov <akopytov@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef SB_RND_H
#define SB_RND_H
#include <stdlib.h>
/* Pick the best available re-entrant PRNG */
#if defined(HAVE_LRAND48_R)
extern TLS struct drand48_data sb_rng_state;
static inline long sb_rnd(void)
{
long result;
lrand48_r(&sb_rng_state, &result);
return result;
}
static inline double sb_rnd_double(void)
{
double result;
drand48_r(&sb_rng_state, &result);
return result;
}
#define sb_srnd(seed) srand48_r(seed, &sb_rng_state)
#elif defined(HAVE_RAND_R)
extern TLS unsigned int sb_rng_state;
# define sb_rnd() (rand_r(&sb_rng_state))
# define sb_srnd(seed) do { sb_rng_state = seed; } while(0)
/* On some platforms rand() may return values larger than RAND_MAX */
# define sb_rnd_double() ((double) (sb_rnd() % RAND_MAX) / RAND_MAX)
#else
# error No re-entrant PRNG function found.
#endif
#endif /* SB_RND_H */

56
sysbench/sb_thread.c Normal file
View File

@ -0,0 +1,56 @@
/*
Copyright (C) 2016 Alexey Kopytov <akopytov@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
Wrappers around pthread_create() and friends to provide necessary
(de-)initialization.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef _WIN32
#include "sb_win.h"
#endif
#ifdef HAVE_PTHREAD_H
# include <pthread.h>
#endif
#include "sb_thread.h"
#include "sb_rnd.h"
int sb_thread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg)
{
/* Initialize thread-local RNG state */
sb_srnd(sb_rnd());
return pthread_create(thread, attr, start_routine, arg);
}
int sb_thread_join(pthread_t thread, void **retval)
{
return pthread_join(thread, retval);
}
int sb_thread_cancel(pthread_t thread)
{
return pthread_cancel(thread);
}

46
sysbench/sb_thread.h Normal file
View File

@ -0,0 +1,46 @@
/*
Copyright (C) 2016 Alexey Kopytov <akopytov@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
Wrappers around pthread_create() and friends to provide necessary
(de-)initialization.
*/
#ifndef SB_THREAD_H
#define SB_THREAD_H
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef _WIN32
#include "sb_win.h"
#endif
#ifdef HAVE_PTHREAD_H
# include <pthread.h>
#endif
int sb_thread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
int sb_thread_join(pthread_t thread, void **retval);
int sb_thread_cancel(pthread_t thread);
#endif /* SB_THREAD_H */

View File

@ -223,6 +223,11 @@ int pthread_join(pthread_t pthread, void **value_ptr)
}
pthread_t pthread_self(void)
{
return GetCurrentThreadId();
}
/*
One time initialization. For simplicity, we assume initializer thread
does not exit within init_routine().

View File

@ -100,6 +100,7 @@ extern int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *
extern int pthread_cancel(pthread_t thread);
extern int pthread_attr_setstacksize( pthread_attr_t *attr, size_t stacksize);
extern int pthread_join(pthread_t thread, void **value_ptr);
extern int pthread_self(void);
extern int gettimeofday(struct timeval * tp, void * tzp);
extern int random();

View File

@ -26,8 +26,8 @@
#include "lauxlib.h"
#include "sb_script.h"
#include "db_driver.h"
#include "sb_rnd.h"
#define EVENT_FUNC "event"
#define PREPARE_FUNC "prepare"

View File

@ -68,6 +68,8 @@
#include "sb_options.h"
#include "scripting/sb_script.h"
#include "db_driver.h"
#include "sb_rnd.h"
#include "sb_thread.h"
#define VERSION_STRING PACKAGE" "PACKAGE_VERSION
@ -92,8 +94,6 @@ typedef struct {
sb_list_item_t listitem;
} event_queue_elem_t;
/* If we should initialize random numbers generator */
static int rand_init;
static rand_dist_t rand_type;
static int (*rand_func)(int, int); /* pointer to random numbers generator */
static unsigned int rand_iter;
@ -138,7 +138,6 @@ sb_arg_t general_args[] =
{"validate", "perform validation checks where possible", SB_ARG_TYPE_FLAG, "off"},
{"help", "print help and exit", SB_ARG_TYPE_FLAG, NULL},
{"version", "print version and exit", SB_ARG_TYPE_FLAG, "off"},
{"rand-init", "initialize random number generator", SB_ARG_TYPE_FLAG, "off"},
{"rand-type", "random numbers distribution {uniform,gaussian,special,pareto}",
SB_ARG_TYPE_STRING, "special"},
{"rand-spec-iter", "number of iterations used for numbers generation", SB_ARG_TYPE_INT, "12"},
@ -429,12 +428,6 @@ void print_run_mode(sb_test_t *test)
if (sb_globals.validate)
log_text(LOG_NOTICE, "Additional request validation enabled.\n");
if (rand_init)
{
log_text(LOG_NOTICE, "Initializing random number generator from timer.\n");
sb_srnd(time(NULL));
}
if (rand_seed)
{
log_text(LOG_NOTICE, "Initializing random number generator from seed (%d).\n", rand_seed);
@ -442,7 +435,9 @@ void print_run_mode(sb_test_t *test)
}
else
{
log_text(LOG_NOTICE, "Random number generator seed is 0 and will be ignored\n");
log_text(LOG_NOTICE,
"Initializing random number generator from current time\n");
sb_srnd(time(NULL));
}
if (sb_globals.force_shutdown)
@ -578,7 +573,7 @@ static void *eventgen_thread_proc(void *arg)
curr_ns = sb_timer_value(&sb_globals.exec_timer);
/* emulate exponential distribution with Lambda = tx_rate */
intr_ns = (long) (log(1 - (double) sb_rnd() / (double) SB_MAX_RND) /
intr_ns = (long) (log(1 - sb_rnd_double()) /
(-(double) sb_globals.tx_rate)*1000000);
next_ns = curr_ns + intr_ns*1000;
@ -587,7 +582,7 @@ static void *eventgen_thread_proc(void *arg)
curr_ns = sb_timer_value(&sb_globals.exec_timer);
/* emulate exponential distribution with Lambda = tx_rate */
intr_ns = (long) (log(1 - (double)sb_rnd() / (double)SB_MAX_RND) /
intr_ns = (long) (log(1 - sb_rnd_double()) /
(-(double)sb_globals.tx_rate)*1000000);
next_ns = next_ns + intr_ns*1000;
@ -794,10 +789,11 @@ static int run_test(sb_test_t *test)
if (sb_globals.report_interval > 0)
{
/* Create a thread for intermediate statistic reports */
if ((err = pthread_create(&report_thread, &thread_attr, &report_thread_proc,
NULL)) != 0)
if ((err = sb_thread_create(&report_thread, &thread_attr, &report_thread_proc,
NULL)) != 0)
{
log_errno(LOG_FATAL, "pthread_create() for the reporting thread failed.");
log_errno(LOG_FATAL,
"sb_thread_create() for the reporting thread failed.");
return 1;
}
report_thread_created = 1;
@ -805,10 +801,11 @@ static int run_test(sb_test_t *test)
if (sb_globals.tx_rate > 0)
{
if ((err = pthread_create(&eventgen_thread, &thread_attr, &eventgen_thread_proc,
NULL)) != 0)
if ((err = sb_thread_create(&eventgen_thread, &thread_attr, &eventgen_thread_proc,
NULL)) != 0)
{
log_errno(LOG_FATAL, "pthread_create() for the reporting thread failed.");
log_errno(LOG_FATAL,
"sb_thread_create() for the reporting thread failed.");
return 1;
}
eventgen_thread_created = 1;
@ -817,11 +814,11 @@ static int run_test(sb_test_t *test)
if (sb_globals.n_checkpoints > 0)
{
/* Create a thread for checkpoint statistic reports */
if ((err = pthread_create(&checkpoints_thread, &thread_attr,
&checkpoints_thread_proc, NULL)) != 0)
if ((err = sb_thread_create(&checkpoints_thread, &thread_attr,
&checkpoints_thread_proc, NULL)) != 0)
{
log_errno(LOG_FATAL, "pthread_create() for the checkpoint thread "
"failed.");
log_errno(LOG_FATAL,
"sb_thread_create() for the checkpoint thread failed.");
return 1;
}
checkpoints_thread_created = 1;
@ -832,10 +829,10 @@ static int run_test(sb_test_t *test)
{
if (sb_globals.error)
return 1;
if ((err = pthread_create(&(threads[i].thread), &thread_attr,
&runner_thread, (void*)(threads + i))) != 0)
if ((err = sb_thread_create(&(threads[i].thread), &thread_attr,
&runner_thread, (void*)(threads + i))) != 0)
{
log_errno(LOG_FATAL, "pthread_create() for thread #%d failed.", i);
log_errno(LOG_FATAL, "sb_thread_create() for thread #%d failed.", i);
return 1;
}
}
@ -855,8 +852,8 @@ static int run_test(sb_test_t *test)
log_text(LOG_NOTICE, "Threads started!\n");
for(i = 0; i < sb_globals.num_threads; i++)
{
if((err = pthread_join(threads[i].thread, NULL)) != 0)
log_errno(LOG_FATAL, "pthread_join() for thread #%d failed.", i);
if((err = sb_thread_join(threads[i].thread, NULL)) != 0)
log_errno(LOG_FATAL, "sb_thread_join() for thread #%d failed.", i);
}
sb_timer_stop(&sb_globals.exec_timer);
@ -895,7 +892,7 @@ static int run_test(sb_test_t *test)
/* Delay killing the reporting threads to avoid mutex lock leaks */
if (report_thread_created)
{
if (pthread_cancel(report_thread) || pthread_join(report_thread, NULL))
if (sb_thread_cancel(report_thread) || sb_thread_join(report_thread, NULL))
log_errno(LOG_FATAL, "Terminating the reporting thread failed.");
}
@ -903,14 +900,15 @@ static int run_test(sb_test_t *test)
if (eventgen_thread_created)
{
if (pthread_cancel(eventgen_thread) || pthread_join(eventgen_thread, NULL))
if (sb_thread_cancel(eventgen_thread) ||
sb_thread_join(eventgen_thread, NULL))
log_text(LOG_FATAL, "Terminating the event generator thread failed.");
}
if (checkpoints_thread_created)
{
if (pthread_cancel(checkpoints_thread) ||
pthread_join(checkpoints_thread, NULL))
if (sb_thread_cancel(checkpoints_thread) ||
sb_thread_join(checkpoints_thread, NULL))
log_errno(LOG_FATAL, "Terminating the checkpoint thread failed.");
}
@ -1018,13 +1016,7 @@ static int init(void)
sb_globals.validate = sb_get_value_flag("validate");
rand_init = sb_get_value_flag("rand-init");
rand_seed = sb_get_value_int("rand-seed");
if (rand_init && rand_seed)
{
log_text(LOG_FATAL, "Cannot set both --rand-init and --rand-seed");
return 1;
}
rand_seed = sb_get_value_int("rand-seed");
s = sb_get_value_string("rand-type");
if (!strcmp(s, "uniform"))

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2004 MySQL AB
Copyright (C) 2004-2015 Alexey Kopytov <akopytov@gmail.com>
Copyright (C) 2004-2016 Alexey Kopytov <akopytov@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -51,25 +51,9 @@
#define SB_THREAD_MUTEX_LOCK() pthread_mutex_lock(&sb_globals.exec_mutex)
#define SB_THREAD_MUTEX_UNLOCK() pthread_mutex_unlock(&sb_globals.exec_mutex)
#define SB_MAX_RND 0x3fffffffu
/* Maximum number of elements in --report-checkpoints list */
#define MAX_CHECKPOINTS 256
/* random() is not thread-safe on most platforms, use lrand48() if available */
#ifdef HAVE_LRAND48
# define sb_rnd() (lrand48() % SB_MAX_RND)
# define sb_srnd(seed) srand48(seed)
#else
# define sb_rnd() (random() % SB_MAX_RND)
# define sb_srnd(seed) srandom((unsigned int)seed)
#endif
#ifdef HAVE_DRAND48
# define sb_rnd_double() drand48()
#else
# define sb_rnd_double() (((double) sb_rnd()) / SB_MAX_RND)
#endif
/* Sysbench commands */
typedef enum
{

View File

@ -60,6 +60,7 @@
#include "sysbench.h"
#include "crc32.h"
#include "sb_percentile.h"
#include "sb_rnd.h"
/* Lengths of the checksum and the offset fields in a block */
#define FILE_CHECKSUM_LENGTH sizeof(int)
@ -532,7 +533,6 @@ sb_request_t file_get_rnd_request(int thread_id)
{
sb_request_t sb_req;
sb_file_request_t *file_req = &sb_req.u.file_request;
unsigned int randnum;
unsigned long long tmppos;
int real_mode = test_mode;
int mode = test_mode;
@ -608,9 +608,7 @@ sb_request_t file_get_rnd_request(int thread_id)
file_req->operation = FILE_OP_TYPE_READ;
retry:
randnum = sb_rnd();
tmppos = (long long) ((double) randnum / SB_MAX_RND * total_size);
tmppos = (long long) (sb_rnd_double() * total_size);
tmppos = tmppos - (tmppos % (long long) file_block_size);
file_req->file_id = (int) (tmppos / (long long) file_size);
file_req->pos = (long long) (tmppos % (long long) file_size);

View File

@ -24,6 +24,7 @@
#endif
#include "sysbench.h"
#include "sb_rnd.h"
#ifdef HAVE_SYS_IPC_H
# include <sys/ipc.h>
@ -252,8 +253,7 @@ int memory_execute_request(sb_request_t *sb_req, int thread_id)
log_msg_t msg;
log_msg_oper_t op_msg;
long i;
unsigned int rand;
/* Prepare log message */
msg.type = LOG_MSG_TYPE_OPER;
msg.data = &op_msg;
@ -266,22 +266,19 @@ int memory_execute_request(sb_request_t *sb_req, int thread_id)
if (memory_access_rnd)
{
rand = sb_rnd();
LOG_EVENT_START(msg, thread_id);
switch (mem_req->type) {
case SB_MEM_OP_WRITE:
for (i = 0; i < memory_block_size; i++)
{
idx = (int)((double)rand / (double)SB_MAX_RND *
(double)(memory_block_size / sizeof(int)));
idx = (int)(sb_rnd_double() * (memory_block_size / sizeof(int)));
buf[idx] = tmp;
}
break;
case SB_MEM_OP_READ:
for (i = 0; i < memory_block_size; i++)
{
idx = (int)((double)rand / (double)SB_MAX_RND *
(double)(memory_block_size / sizeof(int)));
idx = (int)(sb_rnd_double() * (memory_block_size / sizeof(int)));
tmp = buf[idx];
}
break;