From 183fc6b43d058291c122d74be121098a8cadd188 Mon Sep 17 00:00:00 2001 From: Yedidya Feldblum Date: Thu, 4 Jan 2018 23:02:09 -0800 Subject: [PATCH] Extract WaitOptions 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 | 37 +++------- folly/synchronization/WaitOptions.cpp | 23 ++++++ folly/synchronization/WaitOptions.h | 70 +++++++++++++++++++ .../test/SaturatingSemaphoreTest.cpp | 10 +-- 4 files changed, 108 insertions(+), 32 deletions(-) create mode 100644 folly/synchronization/WaitOptions.cpp create mode 100644 folly/synchronization/WaitOptions.h diff --git a/folly/synchronization/SaturatingSemaphore.h b/folly/synchronization/SaturatingSemaphore.h index d222dc4a..8d40b850 100644 --- a/folly/synchronization/SaturatingSemaphore.h +++ b/folly/synchronization/SaturatingSemaphore.h @@ -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 #include #include +#include #include @@ -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::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(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 index 00000000..711e9d31 --- /dev/null +++ b/folly/synchronization/WaitOptions.cpp @@ -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 + +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 index 00000000..a729b4e2 --- /dev/null +++ b/folly/synchronization/WaitOptions.h @@ -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 + +#include + +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 diff --git a/folly/synchronization/test/SaturatingSemaphoreTest.cpp b/folly/synchronization/test/SaturatingSemaphoreTest.cpp index 2db42266..141e0402 100644 --- a/folly/synchronization/test/SaturatingSemaphoreTest.cpp +++ b/folly/synchronization/test/SaturatingSemaphoreTest.cpp @@ -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(); }); } -- 2.34.1