diff --git a/maxutils/maxbase/include/maxbase/random.hh b/maxutils/maxbase/include/maxbase/random.hh new file mode 100644 index 000000000..85d40c72f --- /dev/null +++ b/maxutils/maxbase/include/maxbase/random.hh @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2019 MariaDB Corporation Ab + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file and at www.mariadb.com/bsl11. + * + * Change Date: 2022-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2 or later of the General + * Public License. + */ +#pragma once + +#include + +#include +#include + +namespace maxbase +{ + +/** + * XorShiftRandom is an extremely fast all purpose random number generator. + * + * Uses xoshiro256** http://xoshiro.di.unimi.it written in 2018 by David Blackman + * and Sebastiano Vigna. Comment from the source code: + * This is xoshiro256** 1.0, our all-purpose, rock-solid generator. It has + * excellent (sub-ns) speed, a state (256 bits) that is large enough for + * any parallel application, and it passes all tests we are aware of. + */ +class XorShiftRandom +{ +public: + // Non-deterministic if seed == 0 + explicit XorShiftRandom(uint64_t seed = 0); + uint64_t rand(); + uint32_t rand32(); + bool rand_bool(); + int64_t b_to_e_exclusive(int64_t b, int64_t e); + double zero_to_one_exclusive(); +private: + uint64_t rotl(const uint64_t x, int k); + std::array m_state; +}; + +/** + * StdTwisterRandom is a class for random number generation using the C++ standard library. + * Uses the Mersenne Twister algorithms (mt19937 and mt19937_64). + */ +class StdTwisterRandom +{ +public: + // Non-deterministic if seed == 0 + explicit StdTwisterRandom(uint64_t seed = 0); + + uint64_t rand(); + uint32_t rand32(); + bool rand_bool(); + int64_t b_to_e_exclusive(int64_t b, int64_t e); + double zero_to_one_exclusive(); + + // Borrow the mt19937_64 engine for other distributions. + std::mt19937_64& rnd_engine(); +private: + std::mt19937 m_twister_engine_32; + std::mt19937_64 m_twister_engine_64; +}; + +// ********************************* +// inlined functions below this line + +inline uint64_t XorShiftRandom::rotl(const uint64_t x, int k) +{ + return (x << k) | (x >> (64 - k)); +} + +inline uint64_t XorShiftRandom::rand() +{ + // xoshiro256** + const uint64_t result_starstar = rotl(m_state[1] * 5, 7) * 9; + + const uint64_t t = m_state[1] << 17; + + m_state[2] ^= m_state[0]; + m_state[3] ^= m_state[1]; + m_state[1] ^= m_state[2]; + m_state[0] ^= m_state[3]; + + m_state[2] ^= t; + + m_state[3] = rotl(m_state[3], 45); + + return result_starstar; +} + +inline uint32_t XorShiftRandom::rand32() +{ + return rand() >> 32; // shifting, although low bits are good +} + +inline bool XorShiftRandom::rand_bool() +{ + return std::signbit(int64_t(rand())); +} + +inline double XorShiftRandom::zero_to_one_exclusive() +{ + uint64_t x = rand(); + + // Turn uint64_t to a [0,1[ double (yes, it's standard and portable) + const union + { + uint64_t i; + double d; + } u = {.i = UINT64_C(0x3FF) << 52 | x >> 12}; + + return u.d - 1.0; +} + +inline int64_t XorShiftRandom::b_to_e_exclusive(int64_t b, int64_t e) +{ + // With 64 bits mod bias does not happen in practise (a very, very large e-b would be needed). + // alternative: return b + int64_t(zero_to_one_exclusive()*(e-b)); + return b + rand() % (e - b); +} + +inline uint64_t StdTwisterRandom::rand() +{ + return m_twister_engine_64(); +} + +inline uint32_t StdTwisterRandom::rand32() +{ + return m_twister_engine_32(); +} + +inline bool StdTwisterRandom::rand_bool() +{ + return std::signbit(int32_t(rand32())); +} + +inline double StdTwisterRandom::zero_to_one_exclusive() +{ + std::uniform_real_distribution zero_to_one {0, 1}; + return zero_to_one(m_twister_engine_64); +} + +inline int64_t StdTwisterRandom::b_to_e_exclusive(int64_t b, int64_t e) +{ + std::uniform_int_distribution dist {b, e - 1}; + return dist(m_twister_engine_64); +} +} diff --git a/maxutils/maxbase/include/maxbase/worker.hh b/maxutils/maxbase/include/maxbase/worker.hh index 7ad49a5bb..ebb0e1dbe 100644 --- a/maxutils/maxbase/include/maxbase/worker.hh +++ b/maxutils/maxbase/include/maxbase/worker.hh @@ -29,6 +29,7 @@ #include #include #include +#include namespace maxbase { @@ -248,11 +249,12 @@ class Worker : public MXB_WORKER Worker& operator=(const Worker&) = delete; public: - typedef WORKER_STATISTICS STATISTICS; - typedef WorkerTask Task; - typedef WorkerDisposableTask DisposableTask; - typedef WorkerLoad Load; - typedef WorkerTimer Timer; + using STATISTICS = WORKER_STATISTICS; + using Task = WorkerTask; + using DisposableTask = WorkerDisposableTask; + using Load = WorkerLoad; + using Timer = WorkerTimer; + using RandomEngine = maxbase::XorShiftRandom; /** * A delegating timer that delegates the timer tick handling @@ -369,6 +371,11 @@ public: */ void get_descriptor_counts(uint32_t* pnCurrent, uint64_t* pnTotal); + /** + * Return the random engine of this worker. + */ + RandomEngine& random_engine(); + /** * Add a file descriptor to the epoll instance of the worker. * @@ -958,6 +965,7 @@ private: PrivateTimer* m_pTimer; /*< The worker's own timer. */ DelayedCallsByTime m_sorted_calls; /*< Current delayed calls sorted by time. */ DelayedCallsById m_calls; /*< Current delayed calls indexed by id. */ + RandomEngine m_random_engine; /*< Random engine for this worker (this thread). */ int32_t m_next_delayed_call_id; /*< The next delayed call id. */ }; diff --git a/maxutils/maxbase/src/CMakeLists.txt b/maxutils/maxbase/src/CMakeLists.txt index 49f54e00d..42303c82a 100644 --- a/maxutils/maxbase/src/CMakeLists.txt +++ b/maxutils/maxbase/src/CMakeLists.txt @@ -15,6 +15,7 @@ add_library(maxbase STATIC worker.cc workertask.cc average.cc + random.cc ) if(HAVE_SYSTEMD) diff --git a/maxutils/maxbase/src/random.cc b/maxutils/maxbase/src/random.cc new file mode 100644 index 000000000..ef550ad4f --- /dev/null +++ b/maxutils/maxbase/src/random.cc @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019 MariaDB Corporation Ab + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file and at www.mariadb.com/bsl11. + * + * Change Date: 2022-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2 or later of the General + * Public License. + */ + +#include + +namespace maxbase +{ + +static uint64_t splitmix(uint64_t& state) +{ + uint64_t z = (state += 0x9e3779b97f4a7c15); + z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; + z = (z ^ (z >> 27)) * 0x94d049bb133111eb; + return z ^ (z >> 31); +} + +XorShiftRandom::XorShiftRandom(uint64_t seed) +{ + if (!seed) + { + std::random_device rdev; + while (!(seed = rdev())) + { + } + } + for (auto& s : m_state) + { + s = splitmix(seed); + } +} + +StdTwisterRandom::StdTwisterRandom(uint64_t seed) +{ + std::random_device rdev; + m_twister_engine_32.seed(seed ? seed : rdev()); + m_twister_engine_64.seed(seed ? seed : rdev()); +} + +std::mt19937_64& StdTwisterRandom::rnd_engine() +{ + return m_twister_engine_64; +} +} diff --git a/maxutils/maxbase/src/worker.cc b/maxutils/maxbase/src/worker.cc index 8554842cd..c5b5e1e09 100644 --- a/maxutils/maxbase/src/worker.cc +++ b/maxutils/maxbase/src/worker.cc @@ -355,6 +355,11 @@ void Worker::get_descriptor_counts(uint32_t* pnCurrent, uint64_t* pnTotal) *pnTotal = atomic_load_uint64(&m_nTotal_descriptors); } +Worker::RandomEngine& Worker::random_engine() +{ + return m_random_engine; +} + bool Worker::add_fd(int fd, uint32_t events, MXB_POLL_DATA* pData) { bool rv = true; diff --git a/server/modules/routing/readwritesplit/rwsplit_select_backends.cc b/server/modules/routing/readwritesplit/rwsplit_select_backends.cc index cd36a1d1f..ffecdde48 100644 --- a/server/modules/routing/readwritesplit/rwsplit_select_backends.cc +++ b/server/modules/routing/readwritesplit/rwsplit_select_backends.cc @@ -28,15 +28,6 @@ using namespace maxscale; -// TODO, there should be a utility with the most common used random cases. -// FYI: rand() is about twice as fast as the below toss. -static std::mt19937 random_engine; -static std::uniform_real_distribution<> zero_to_one(0.0, 1.0); -double toss() -{ - return zero_to_one(random_engine); -} - /** * The functions that implement back end selection for the read write * split router. All of these functions are internal to that router and @@ -161,7 +152,8 @@ PRWBackends::iterator backend_cmp_response_time(PRWBackends& sBackends) } // Find the winner, role the ball: - double ball = toss(); + double ball = maxbase::Worker::get_current()->random_engine().zero_to_one_exclusive(); + double slot_walk {0}; int winner {0};