]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
common: implement random number generator (following N3551) 15341/head
authorJesse Williamson <jwilliamson@suse.de>
Thu, 18 May 2017 13:11:13 +0000 (06:11 -0700)
committerJesse Williamson <jwilliamson@suse.de>
Tue, 22 Aug 2017 16:14:34 +0000 (09:14 -0700)
Adds new mini-library for random number generation using C++11's
<random> header.

Refer-to:
https://isocpp.org/files/papers/n3551.pdf

Signed-off-by: Jesse Williamson <jwilliamson@suse.de>
src/include/random.h [new file with mode: 0644]
src/test/common/CMakeLists.txt
src/test/common/test_random.cc [new file with mode: 0644]

diff --git a/src/include/random.h b/src/include/random.h
new file mode 100644 (file)
index 0000000..2a42a51
--- /dev/null
@@ -0,0 +1,288 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2017 SUSE LINUX GmbH
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation.  See file COPYING.
+ *
+*/
+
+#ifndef CEPH_RANDOM_H
+#define CEPH_RANDOM_H 1
+
+#include <mutex>
+#include <random>
+
+#include "boost/optional.hpp"
+
+#include "common/backport14.h"
+
+// Basic random number facility, adapted from N3551:
+namespace ceph {
+namespace util {
+
+inline namespace version_1_0 {
+
+namespace detail {
+
+template <typename EngineT>
+EngineT& engine();
+
+template <typename MutexT, typename EngineT>
+void randomize_rng(const int seed, MutexT& m, EngineT& e)
+{
+  std::lock_guard<MutexT> lg(m);
+  e.seed(seed);
+}
+
+template <typename MutexT, typename EngineT>
+void randomize_rng(MutexT& m, EngineT& e)
+{
+  thread_local std::random_device rd;
+  std::lock_guard<MutexT> lg(m);
+  e.seed(rd());
+}
+
+template <typename EngineT = std::default_random_engine>
+void randomize_rng(const int n)
+{
+  detail::engine<EngineT>().seed(n);
+}
+
+template <typename EngineT = std::default_random_engine>
+void randomize_rng()
+{
+  thread_local std::random_device rd;
+  detail::engine<EngineT>().seed(rd());
+}
+
+template <typename EngineT>
+EngineT& engine()
+{
+  thread_local boost::optional<EngineT> rng_engine;
+
+  if (!rng_engine) {
+    rng_engine.emplace(EngineT());
+    randomize_rng<EngineT>();
+  }
+
+  return *rng_engine;
+}
+
+} // namespace detail
+
+namespace detail {
+
+template <typename NumberT,
+          typename DistributionT,
+          typename EngineT>
+NumberT generate_random_number(const NumberT min, const NumberT max,
+                               EngineT& e)
+{
+  thread_local DistributionT d { min, max };
+
+  using param_type = typename DistributionT::param_type;
+  return d(e, param_type { min, max });
+}
+
+template <typename NumberT,
+          typename MutexT,
+          typename DistributionT,
+          typename EngineT>
+NumberT generate_random_number(const NumberT min, const NumberT max,
+                               MutexT& m, EngineT& e)
+{
+  thread_local DistributionT d { min, max };
+  using param_type = typename DistributionT::param_type;
+  std::lock_guard<MutexT> lg(m);
+  return d(e, param_type { min, max });
+}
+
+template <typename NumberT,
+          typename DistributionT,
+          typename EngineT>
+NumberT generate_random_number(const NumberT min, const NumberT max)
+{
+  return detail::generate_random_number<NumberT, DistributionT, EngineT>
+          (min, max, detail::engine<EngineT>());
+}
+
+template <typename MutexT, typename EngineT,
+          int min = 0,
+          int max = std::numeric_limits<int>::max(), 
+          typename DistributionT = std::uniform_int_distribution<int>>
+int generate_random_number(MutexT& m, EngineT& e)
+{
+  return detail::generate_random_number<int, MutexT, DistributionT, EngineT>
+          (min, max, m, e);
+}
+
+} // namespace detail
+
+template <typename EngineT = std::default_random_engine>
+void randomize_rng()
+{
+  detail::randomize_rng<EngineT>();
+}
+
+template <int min = 0,
+          int max = std::numeric_limits<int>::max(), 
+          typename DistributionT = std::uniform_int_distribution<int>,
+          typename EngineT = std::default_random_engine>
+int generate_random_number()
+{
+  return detail::generate_random_number<int, DistributionT, EngineT>
+          (min, max);
+}
+
+template <typename IntegerT>
+IntegerT generate_random_number(const IntegerT min, const IntegerT max,
+                                ceph::enable_if_t<std::is_integral<IntegerT>::value>* = nullptr)
+{
+  return detail::generate_random_number<IntegerT,
+                                        std::uniform_int_distribution<IntegerT>,
+                                        std::default_random_engine>
+                                       (min, max); 
+}
+
+namespace detail {
+
+template <typename IntegerT, typename MutexT, typename EngineT>
+int generate_random_number(const IntegerT min, const IntegerT max,
+                           MutexT& m, EngineT& e,
+                           ceph::enable_if_t<std::is_integral<IntegerT>::value>* = nullptr)
+{
+  return detail::generate_random_number<IntegerT, MutexT,
+                                        std::uniform_int_distribution<IntegerT>,
+                                        EngineT>
+                                       (min, max, m, e);
+}
+
+template <typename IntegerT, typename MutexT, typename EngineT>
+int generate_random_number(const IntegerT max,
+                           MutexT& m, EngineT& e,
+                           ceph::enable_if_t<std::is_integral<IntegerT>::value>* = nullptr)
+{
+  constexpr IntegerT zero = 0;
+  return generate_random_number(zero, max, m, e);
+}
+
+} // namespace detail
+
+template <typename IntegerT>
+int generate_random_number(const IntegerT max,
+                           ceph::enable_if_t<std::is_integral<IntegerT>::value>* = nullptr)
+{
+  constexpr IntegerT zero = 0;   
+  return generate_random_number(zero, max);
+}
+
+template <typename RealT>
+RealT generate_random_number(const RealT min, const RealT max, 
+                             ceph::enable_if_t<std::is_floating_point<RealT>::value>* = nullptr)
+{
+  return detail::generate_random_number<RealT,
+                                        std::uniform_real_distribution<RealT>, 
+                                        std::default_random_engine>
+                                       (min, max);
+} 
+
+namespace detail {
+
+template <typename RealT, typename MutexT>
+RealT generate_random_number(const RealT max, MutexT& m,
+                             ceph::enable_if_t<std::is_floating_point<RealT>::value>* = nullptr)
+{
+  constexpr RealT zero = 0.0;
+  return generate_random_number(zero, max, m);
+}
+
+template <typename RealT, typename MutexT, typename EngineT>
+RealT generate_random_number(const RealT min, const RealT max, MutexT& m, EngineT& e,
+                             ceph::enable_if_t<std::is_floating_point<RealT>::value>* = nullptr)
+{
+  return detail::generate_random_number<RealT, MutexT, 
+                                        std::uniform_real_distribution<RealT>,
+                                        EngineT>
+                                       (min, max, m, e);
+}
+
+
+template <typename RealT, typename MutexT, typename EngineT>
+RealT generate_random_number(const RealT max, MutexT& m, EngineT& e,
+                             ceph::enable_if_t<std::is_floating_point<RealT>::value>* = nullptr)
+{
+  constexpr RealT zero = 0.0;
+  return generate_random_number(zero, max, m, e);
+}
+
+} // namespace detail
+
+template <typename RealT>
+RealT generate_random_number(const RealT max,
+                             ceph::enable_if_t<std::is_floating_point<RealT>::value>* = nullptr)
+{
+  constexpr RealT zero = 0.0;
+  return generate_random_number(zero, max);
+}
+
+// Function object:
+template <typename NumberT>
+class random_number_generator final
+{
+  std::mutex l;
+  std::random_device rd;
+  std::default_random_engine e;
+  public:
+  using number_type = NumberT;
+  public:
+  random_number_generator() {
+    detail::randomize_rng(l, e);
+  }
+  explicit random_number_generator(const int seed) {
+    detail::randomize_rng(seed, l, e);
+  }
+
+  random_number_generator(random_number_generator&& rhs)
+   : e(std::move(rhs.e))
+  {}
+  public:
+  random_number_generator(const random_number_generator&)            = delete;
+  random_number_generator& operator=(const random_number_generator&) = delete;
+  public:
+  NumberT operator()() { 
+    return detail::generate_random_number(l, e); 
+  }
+  NumberT operator()(const NumberT max) { 
+    return detail::generate_random_number(max, l, e); 
+  }
+  NumberT operator()(const NumberT min, const NumberT max) { 
+    return detail::generate_random_number(min, max, l, e); 
+  }
+  public:
+  void seed(const int n) { 
+    detail::randomize_rng(n, l, e); 
+  }
+};
+
+} // inline namespace version_1_0
+
+}} // namespace ceph::util
+
+#endif
index 9a2be3df293cc96ca29e8f10b53085086b7b51e9..14303886c75df9174a4a0ae0df2c6f5fe251e2c2 100644 (file)
@@ -95,6 +95,13 @@ add_executable(unittest_util
 add_ceph_unittest(unittest_util ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/unittest_util)
 target_link_libraries(unittest_util global ${BLKID_LIBRARIES})
 
+# unittest_random
+add_executable(unittest_random
+  test_random.cc
+  )
+add_ceph_unittest(unittest_random ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/unittest_random)
+target_link_libraries(unittest_random global ${BLKID_LIBRARIES})
+
 # unittest_throttle
 add_executable(unittest_throttle
   Throttle.cc
diff --git a/src/test/common/test_random.cc b/src/test/common/test_random.cc
new file mode 100644 (file)
index 0000000..d117447
--- /dev/null
@@ -0,0 +1,183 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2017 SUSE LINUX GmbH
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation.  See file COPYING.
+ *
+*/
+
+#include <sstream>
+
+#include "include/random.h"
+
+#include "gtest/gtest.h"
+
+// Helper to see if calls compile with various types:
+template <typename T>
+T type_check_ok(const T min, const T max)
+{
+  return ceph::util::generate_random_number(min, max);
+}
+
+/* Help wrangle "unused variable" warnings: */
+template <typename X>
+void swallow_values(const X x)
+{
+  static_cast<void>(x);
+}
+
+template <typename X, typename ...XS>
+void swallow_values(const X x, const XS... xs)
+{
+  swallow_values(x), swallow_values(xs...);
+}
+
+// Mini-examples showing canonical usage:
+TEST(util, test_random_canonical)
+{
+  // Seed random number generation:
+  ceph::util::randomize_rng();
+  // Get a random int between 0 and max int:
+  auto a = ceph::util::generate_random_number();
+  // Get a random int between 0 and 20:
+  auto b = ceph::util::generate_random_number(20);
+  // Get a random int between 1 and 20:
+  auto c = ceph::util::generate_random_number(1, 20);
+  // Get a random float between 0.0 and 20.0:
+  auto d = ceph::util::generate_random_number(20.0);
+  // Get a random float between 0.001 and 0.991:
+  auto e = ceph::util::generate_random_number(0.001, 0.991);
+  // Make a function object RNG suitable for putting on its own thread:
+  auto gen_fn = ceph::util::random_number_generator<int>();
+  auto z = gen_fn();
+  gen_fn.seed(42);   // re-seed
+
+  // Placate the compiler: 
+  swallow_values(a, b, c, d, e, z);
+}
+
+TEST(util, test_random)
+{
+  /* The intent of this test is not to formally test random number generation, 
+  but rather to casually check that "it works" and catch regressions: */
+  // The default overload should compile:
+  ceph::util::randomize_rng();
+  {
+    int a = ceph::util::generate_random_number();
+    int b = ceph::util::generate_random_number();
+    /* Technically, this can still collide and cause a false negative, but let's 
+    be optimistic: */
+    if (std::numeric_limits<int>::max() > 32767) {
+       ASSERT_GT(a, -1);
+       ASSERT_GT(b, -1);
+       ASSERT_NE(a, b);
+     }
+  }
+  {
+  auto a = ceph::util::generate_random_number(1, std::numeric_limits<int>::max());
+  auto b = ceph::util::generate_random_number(1, std::numeric_limits<int>::max());
+  if (std::numeric_limits<int>::max() > 32767) {
+     ASSERT_GT(a, 0);
+     ASSERT_GT(b, 0);
+     ASSERT_NE(a, b);
+   }
+  }
+  for (auto n = 100000; n; --n) {
+     constexpr int min = 0, max = 6;
+     int a = ceph::util::generate_random_number<min, max>();
+     ASSERT_GT(a, -1);
+     ASSERT_LT(a, 7);
+   }
+  // Multiple types (integral):
+  {
+    int min = 0, max = 1;
+    type_check_ok(min, max);
+  }
+  {
+    long min = 0, max = 1l;
+    type_check_ok(min, max);
+  }
+  // Multiple types (floating point):
+  {
+    double min = 0.0, max = 1.0;
+    type_check_ok(min, max);
+  }
+  {
+    float min = 0.0, max = 1.0;
+    type_check_ok(min, max);
+  }
+  // min > max should not explode:
+  {
+    float min = 1.0, max = 0.0;
+    type_check_ok(min, max);
+  }
+}
+
+TEST(util, test_random_class_interface)
+{
+  ceph::util::random_number_generator<int> rng_i;
+  ceph::util::random_number_generator<float> rng_f;
+  // Other ctors:
+  {
+    ceph::util::random_number_generator<int> rng(1234);   // seed
+  }
+  {
+    int a = rng_i();
+    int b = rng_i();
+    // Technically can fail, but should "almost never" happen:
+    ASSERT_NE(a, b);
+  }
+  {
+    int a = rng_i(10);
+    ASSERT_LE(a, 10);
+    ASSERT_GE(a, 0);
+  }
+  {
+    float a = rng_f(10.0);
+    ASSERT_LE(a, 10.0);
+    ASSERT_GE(a, 0.0);
+  }
+  {
+    int a = rng_i(10, 20);
+    ASSERT_LE(a, 20);
+    ASSERT_GE(a, 10);
+  }
+  {
+    float a = rng_f(10.0, 20.0);
+    ASSERT_LE(a, 20.0);
+    ASSERT_GE(a, 10.0);
+  }
+}
+