Tweaks to folly::call_once and folly::once_flag
authorYedidya Feldblum <yfeldblum@fb.com>
Sat, 30 Dec 2017 03:55:06 +0000 (19:55 -0800)
committerFacebook Github Bot <facebook-github-bot@users.noreply.github.com>
Sat, 30 Dec 2017 04:05:00 +0000 (20:05 -0800)
Summary:
[Folly] Tweaks to `folly::call_once` and `folly::once_flag`.

In particular:
* Move the template class out of `detail`.
* Add parameterization by the atomic type.
* Expand the comments.

Reviewed By: Orvid

Differential Revision: D6637250

fbshipit-source-id: 3806580ca0badf8464f637750c4873b2aba9f916

folly/fibers/CallOnce.h
folly/synchronization/CallOnce.h
folly/synchronization/test/CallOnceBenchmark.cpp
folly/synchronization/test/CallOnceTest.cpp

index 7b72020844827e71b9e04635a14ce1c8d5b4c5ea..d607855c2d1728e825e6df8150be2abbd8f2d00f 100644 (file)
@@ -20,6 +20,6 @@
 namespace folly {
 namespace fibers {
 
-using once_flag = ::folly::detail::once_flag<folly::fibers::TimedMutex>;
+using once_flag = basic_once_flag<TimedMutex>;
 }
 } // namespace folly
index 51543ea22b23e08a1d66c8bbec5f01c59cf3ac21..7147b9d9e9b3eab1b63321d21ee96c0f37340326 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017 Facebook, Inc.
+ * Copyright 2016-present Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * limitations under the License.
  */
 
-/*
- * Drop-in replacement for std::call_once() with a fast path, which the GCC
- * implementation lacks.  The tradeoff is a slightly larger `once_flag' struct
- * (8 bytes vs 4 bytes with GCC on Linux/x64).
- *
- * $ call_once_test --benchmark --bm_min_iters=100000000 --threads=16
- * ============================================================================
- * folly/test/CallOnceTest.cpp                     relative  time/iter  iters/s
- * ============================================================================
- * StdCallOnceBench                                             3.54ns  282.82M
- * FollyCallOnceBench                                         698.48ps    1.43G
- * ============================================================================
- */
-
 #pragma once
 
 #include <atomic>
 #include <folly/Likely.h>
 #include <folly/Portability.h>
 #include <folly/SharedMutex.h>
+#include <folly/functional/Invoke.h>
 
 namespace folly {
-namespace detail {
-template <typename Mutex>
-class once_flag;
 
-// Implementation detail: out-of-line slow path
-template <class Mutex, class Callable, class... Args>
-void FOLLY_NOINLINE call_once_impl_no_inline(
-    detail::once_flag<Mutex>& flag,
-    Callable&& f,
-    Args&&... args) {
-  std::lock_guard<Mutex> lg(flag.mutex_);
-  if (flag.called_) {
-    return;
-  }
-
-  std::forward<Callable>(f)(std::forward<Args>(args)...);
+template <typename Mutex, template <typename> class Atom = std::atomic>
+class basic_once_flag;
 
-  flag.called_.store(true, std::memory_order_release);
+//  call_once
+//
+//  Drop-in replacement for std::call_once.
+//
+//  The libstdc++ implementation has two flaws:
+//  * it lacks a fast path, and
+//  * it deadlocks (in explicit violation of the standard) when invoked twice
+//    with a given flag, and the callable passed to the first invocation throws.
+//
+//  This implementation corrects both flaws.
+//
+//  The tradeoff is a slightly larger once_flag struct at 8 bytes, vs 4 bytes
+//  with libstdc++ on Linux/x64.
+//
+//  Does not work with std::once_flag.
+//
+//  mimic: std::call_once
+template <
+    typename Mutex,
+    template <typename> class Atom,
+    typename F,
+    typename... Args>
+FOLLY_ALWAYS_INLINE void
+call_once(basic_once_flag<Mutex, Atom>& flag, F&& f, Args&&... args) {
+  flag.call_once(std::forward<F>(f), std::forward<Args>(args)...);
 }
-} // namespace detail
 
-using once_flag = detail::once_flag<folly::SharedMutex>;
-
-template <class Mutex, class Callable, class... Args>
-void FOLLY_ALWAYS_INLINE
-call_once(detail::once_flag<Mutex>& flag, Callable&& f, Args&&... args) {
-  if (LIKELY(flag.called_.load(std::memory_order_acquire))) {
-    return;
-  }
-  call_once_impl_no_inline(
-      flag, std::forward<Callable>(f), std::forward<Args>(args)...);
-}
+//  basic_once_flag
+//
+//  The flag template to be used with call_once. Parameterizable by the mutex
+//  type and atomic template. The mutex type is required to mimic std::mutex and
+//  the atomic type is required to mimic std::atomic.
+template <typename Mutex, template <typename> class Atom>
+class basic_once_flag {
+ public:
+  constexpr basic_once_flag() noexcept = default;
+  basic_once_flag(const basic_once_flag&) = delete;
+  basic_once_flag& operator=(const basic_once_flag&) = delete;
 
-namespace detail {
+ private:
+  template <
+      typename Mutex_,
+      template <typename> class Atom_,
+      typename F,
+      typename... Args>
+  friend void call_once(basic_once_flag<Mutex_, Atom_>&, F&&, Args&&...);
 
-template <typename Mutex>
-class once_flag {
- public:
-  constexpr once_flag() noexcept = default;
-  once_flag(const once_flag&) = delete;
-  once_flag& operator=(const once_flag&) = delete;
+  template <typename F, typename... Args>
+  FOLLY_ALWAYS_INLINE void call_once(F&& f, Args&&... args) {
+    if (LIKELY(called_.load(std::memory_order_acquire))) {
+      return;
+    }
+    call_once_slow(std::forward<F>(f), std::forward<Args>(args)...);
+  }
 
-  template <typename Mutex_, typename Callable, class... Args>
-  friend void ::folly::call_once(
-      detail::once_flag<Mutex_>& flag,
-      Callable&& f,
-      Args&&... args);
-  template <typename Mutex_, typename Callable, class... Args>
-  friend void call_once_impl_no_inline(
-      detail::once_flag<Mutex_>& flag,
-      Callable&& f,
-      Args&&... args);
+  template <typename F, typename... Args>
+  FOLLY_NOINLINE void call_once_slow(F&& f, Args&&... args) {
+    std::lock_guard<Mutex> lock(mutex_);
+    if (called_.load(std::memory_order_relaxed)) {
+      return;
+    }
+    invoke(std::forward<F>(f), std::forward<Args>(args)...);
+    called_.store(true, std::memory_order_release);
+  }
 
- private:
-  std::atomic<bool> called_{false};
+  Atom<bool> called_{false};
   Mutex mutex_;
 };
-} // namespace detail
+
+//  once_flag
+//
+//  The flag type to be used with call_once. An instance of basic_once_flag.
+//
+//  Does not work with sd::call_once.
+//
+//  mimic: std::once_flag
+using once_flag = basic_once_flag<SharedMutex>;
 
 } // namespace folly
index f85b23b57424fae663ff6a7403f91fc3c837782b..598d00a0daffaad843bf75601ec9ea3157113e6e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017 Facebook, Inc.
+ * Copyright 2016-present Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 DEFINE_int32(threads, 16, "benchmark concurrency");
 
 template <typename CallOnceFunc>
-void bm_impl(CallOnceFunc&& fn, int64_t iters) {
+void bm_impl(CallOnceFunc&& fn, size_t iters) {
   std::deque<std::thread> threads;
-  for (int i = 0; i < FLAGS_threads; ++i) {
+  for (size_t i = 0u; i < size_t(FLAGS_threads); ++i) {
     threads.emplace_back([&fn, iters] {
-      for (int64_t j = 0; j < iters; ++j) {
+      for (size_t j = 0u; j < iters; ++j) {
         fn();
       }
     });
@@ -55,6 +55,16 @@ BENCHMARK(FollyCallOnceBench, iters) {
   CHECK_EQ(1, out);
 }
 
+/*
+$ call_once_benchmark --bm_min_iters=100000000 --threads=16
+============================================================================
+folly/synchronization/test/CallOnceBenchmark.cpprelative  time/iter  iters/s
+============================================================================
+StdCallOnceBench                                             2.40ns  416.78M
+FollyCallOnceBench                                         651.94ps    1.53G
+============================================================================
+*/
+
 int main(int argc, char** argv) {
   gflags::ParseCommandLineFlags(&argc, &argv, true);
   folly::runBenchmarks();
index 81bcbe55ea00dc4e023823373e18ad3c7544002c..0b5817398a1b52d01c218344d46a8d0143a626a8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017 Facebook, Inc.
+ * Copyright 2016-present Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include <mutex>
 #include <thread>
 
-#include <folly/portability/GFlags.h>
 #include <folly/portability/GTest.h>
 #include <folly/synchronization/CallOnce.h>
 
-#include <glog/logging.h>
-
-DEFINE_int32(threads, 16, "benchmark concurrency");
+static size_t const kNumThreads = 16;
 
 template <typename CallOnceFunc>
-void bm_impl(CallOnceFunc&& fn, int64_t iters) {
+void bm_impl(CallOnceFunc&& fn, size_t iters) {
   std::deque<std::thread> threads;
-  for (int i = 0; i < FLAGS_threads; ++i) {
+  for (size_t i = 0u; i < kNumThreads; ++i) {
     threads.emplace_back([&fn, iters] {
-      for (int64_t j = 0; j < iters; ++j) {
+      for (size_t j = 0u; j < iters; ++j) {
         fn();
       }
     });