Extract WaitOptions
authorYedidya Feldblum <yfeldblum@fb.com>
Fri, 5 Jan 2018 07:02:09 +0000 (23:02 -0800)
committerFacebook Github Bot <facebook-github-bot@users.noreply.github.com>
Fri, 5 Jan 2018 07:05:33 +0000 (23:05 -0800)
Summary:
[Folly] Extract `WaitOptions` from `SaturatingSemaphore`.

This type may prove useful in the future for a variety of similar cases, and so does not need to be locked up inside `SaturatingSemaphore`.

Additionally:
* Extract and redraft a comment from `Baton`.
* Rename `pre_block` to `spin_max`.

Reviewed By: djwatson, aary

Differential Revision: D6632875

fbshipit-source-id: 6b7faeeb6e1ac2011a037c2b560def0ee2e9f3d4

folly/synchronization/SaturatingSemaphore.h
folly/synchronization/WaitOptions.cpp [new file with mode: 0644]
folly/synchronization/WaitOptions.h [new file with mode: 0644]
folly/synchronization/test/SaturatingSemaphoreTest.cpp

index d222dc4aa4ee10577492c4efdc094726dea92c7c..8d40b85067719171c1f70f29ebdc0b678a705c5a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017 Facebook, Inc.
+ * Copyright 2017-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.
@@ -19,6 +19,7 @@
 #include <folly/Likely.h>
 #include <folly/detail/Futex.h>
 #include <folly/portability/Asm.h>
+#include <folly/synchronization/WaitOptions.h>
 
 #include <glog/logging.h>
 
@@ -61,11 +62,10 @@ namespace folly {
 ///   instruction to the critical path of posters.
 ///
 /// Wait options:
-///   The subclass WaitOptions contains optional per call setting for
-///   pre-block spin duration: Calls to wait(), try_wait_until(), and
-///   try_wait_for() block only after the passage of the pre-block
-///   period. The default pre-block duration is 10 microseconds. The
-///   pre block option is applicable only if MayBlock is true.
+///   WaitOptions contains optional per call setting for spin-max duration:
+///   Calls to wait(), try_wait_until(), and try_wait_for() block only after the
+///   passage of the spin-max period. The default spin-max duration is 10 usec.
+///   The spin-max option is applicable only if MayBlock is true.
 ///
 /// Functions:
 ///   bool ready():
@@ -101,11 +101,11 @@ namespace folly {
 ///     std::chrono::steady_clock::now() + std::chrono::microseconds(1)));
 /// ASSERT_FALSE(f.try_wait_until(
 ///     std::chrono::steady_clock::now() + std::chrono::microseconds(1),
-///     f.wait_options().pre_block(std::chrono::microseconds(1))));
+///     f.wait_options().spin_max(std::chrono::microseconds(1))));
 /// f.post();
 /// f.post();
 /// f.wait();
-/// f.wait(f.wait_options().pre_block(std::chrono::nanoseconds(100)));
+/// f.wait(f.wait_options().spin_max(std::chrono::nanoseconds(100)));
 /// ASSERT_TRUE(f.try_wait());
 /// ASSERT_TRUE(f.try_wait_until(
 ///     std::chrono::steady_clock::now() + std::chrono::microseconds(1)));
@@ -125,23 +125,6 @@ class SaturatingSemaphore {
   };
 
  public:
-  /** WaitOptions */
-
-  class WaitOptions {
-    std::chrono::nanoseconds dur_{std::chrono::microseconds(10)};
-
-   public:
-    FOLLY_ALWAYS_INLINE
-    std::chrono::nanoseconds pre_block() const {
-      return dur_;
-    }
-    FOLLY_ALWAYS_INLINE
-    WaitOptions& pre_block(std::chrono::nanoseconds dur) {
-      dur_ = dur;
-      return *this;
-    }
-  };
-
   FOLLY_ALWAYS_INLINE static WaitOptions wait_options() {
     return {};
   }
@@ -307,11 +290,11 @@ FOLLY_NOINLINE bool SaturatingSemaphore<MayBlock, Atom>::tryWaitSlow(
     if (MayBlock) {
       auto tnow = Clock::now();
       if (tnow < tbegin) {
-        // backward time discontinuity in Clock, revise pre_block starting point
+        // backward time discontinuity in Clock, revise spin_max starting point
         tbegin = tnow;
       }
       auto dur = std::chrono::duration_cast<Duration>(tnow - tbegin);
-      if (dur >= opt.pre_block()) {
+      if (dur >= opt.spin_max()) {
         if (before == NOTREADY) {
           if (!state_.compare_exchange_strong(
                   before,
diff --git a/folly/synchronization/WaitOptions.cpp b/folly/synchronization/WaitOptions.cpp
new file mode 100644 (file)
index 0000000..711e9d3
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2018-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.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/synchronization/WaitOptions.h>
+
+namespace folly {
+
+constexpr std::chrono::nanoseconds WaitOptions::Defaults::spin_max;
+
+} // namespace folly
diff --git a/folly/synchronization/WaitOptions.h b/folly/synchronization/WaitOptions.h
new file mode 100644 (file)
index 0000000..a729b4e
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2018-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.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <chrono>
+
+#include <folly/CPortability.h>
+
+namespace folly {
+
+/// WaitOptions
+///
+/// Various synchronization primitives as well as various concurrent data
+/// structures built using them have operations which might wait. This type
+/// represents a set of options for controlling such waiting.
+class WaitOptions {
+ public:
+  struct Defaults {
+    /// spin_max
+    ///
+    /// If multiple threads are actively using a synchronization primitive,
+    /// whether indirectly via a higher-level concurrent data structure or
+    /// directly, where the synchronization primitive has an operation which
+    /// waits and another operation which wakes the waiter, it is common for
+    /// wait and wake events to happen almost at the same time. In this state,
+    /// we lose big 50% of the time if the wait blocks immediately.
+    ///
+    /// We can improve our chances of being waked immediately, before blocking,
+    /// by spinning for a short duration, although we have to balance this
+    /// against the extra cpu utilization, latency reduction, power consumption,
+    /// and priority inversion effect if we end up blocking anyway.
+    ///
+    /// We use a default maximum of 10 usec of spinning. As partial consolation,
+    /// since spinning as implemented in folly uses the pause instruction where
+    /// available, we give a small speed boost to the colocated hyperthread.
+    ///
+    /// On circa-2013 devbox hardware, it costs about 7 usec to FUTEX_WAIT and
+    /// then be awoken. Spins on this hw take about 7 nsec, where all but 0.5
+    /// nsec is the pause instruction.
+    static constexpr std::chrono::nanoseconds spin_max =
+        std::chrono::microseconds(10);
+  };
+
+  std::chrono::nanoseconds spin_max() const {
+    return spin_max_;
+  }
+  WaitOptions& spin_max(std::chrono::nanoseconds dur) {
+    spin_max_ = dur;
+    return *this;
+  }
+
+ private:
+  std::chrono::nanoseconds spin_max_ = Defaults::spin_max;
+};
+
+} // namespace folly
index 2db422667576cb8d29d66aa8811ad58487148fc9..141e04029a33ac26c64d5dc5eb492c71f7c9c52a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017 Facebook, Inc.
+ * Copyright 2017-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.
@@ -32,11 +32,11 @@ void run_basic_test() {
       std::chrono::steady_clock::now() + std::chrono::microseconds(1)));
   ASSERT_FALSE(f.try_wait_until(
       std::chrono::steady_clock::now() + std::chrono::microseconds(1),
-      f.wait_options().pre_block(std::chrono::microseconds(1))));
+      f.wait_options().spin_max(std::chrono::microseconds(1))));
   f.post();
   f.post();
   f.wait();
-  f.wait(f.wait_options().pre_block(std::chrono::nanoseconds(100)));
+  f.wait(f.wait_options().spin_max(std::chrono::nanoseconds(100)));
   ASSERT_TRUE(f.ready());
   ASSERT_TRUE(f.try_wait());
   ASSERT_TRUE(f.try_wait_until(
@@ -98,7 +98,7 @@ void run_multi_poster_multi_waiter_test(int np, int nw) {
           std::chrono::steady_clock::now() + std::chrono::microseconds(1)));
       ASSERT_FALSE(f.try_wait_until(
           std::chrono::steady_clock::now() + std::chrono::microseconds(1),
-          f.wait_options().pre_block(std::chrono::microseconds(0))));
+          f.wait_options().spin_max(std::chrono::microseconds(0))));
       waited.fetch_add(1);
       while (!go_wait.load()) {
         /* spin */;
@@ -110,7 +110,7 @@ void run_multi_poster_multi_waiter_test(int np, int nw) {
           std::chrono::steady_clock::now() + std::chrono::microseconds(1)));
       ASSERT_TRUE(f.try_wait_until(
           std::chrono::steady_clock::now() + std::chrono::microseconds(1),
-          f.wait_options().pre_block(std::chrono::microseconds(0))));
+          f.wait_options().spin_max(std::chrono::microseconds(0))));
       f.wait();
     });
   }