[PATCH v5 5/8] libstdc++ futex: Loop when waiting against arbitrary clock
Jonathan Wakely
jwakely@redhat.com
Fri Sep 11 09:47:30 GMT 2020
I'm finally getting round to merging this series!
On 29/05/20 07:17 +0100, Mike Crowe via Libstdc++ wrote:
>If std::future::wait_until is passed a time point measured against a clock
>that is neither std::chrono::steady_clock nor std::chrono::system_clock
>then the generic implementation of
>__atomic_futex_unsigned::_M_load_when_equal_until is called which
>calculates the timeout based on __clock_t and calls the
>_M_load_when_equal_until method for that clock to perform the actual wait.
>
>There's no guarantee that __clock_t is running at the same speed as the
>caller's clock, so if the underlying wait times out timeout we need to
>check the timeout against the caller's clock again before potentially
>looping.
>
>Also add two extra tests to the testsuite's async.cc:
>
>* run test03 with steady_clock_copy, which behaves identically to
> std::chrono::steady_clock, but isn't std::chrono::steady_clock. This
> causes the overload of __atomic_futex_unsigned::_M_load_when_equal_until
> that takes an arbitrary clock to be called.
>
>* invent test04 which uses a deliberately slow running clock in order to
> exercise the looping behaviour o
> __atomic_futex_unsigned::_M_load_when_equal_until described above.
>
> * libstdc++-v3/include/bits/atomic_futex.h:
> (__atomic_futex_unsigned) Add loop to _M_load_when_equal_until on
> generic _Clock to check the timeout against _Clock again after
> _M_load_when_equal_until returns indicating a timeout.
>
> * libstdc++-v3/testsuite/30_threads/async/async.cc: Invent
> slow_clock that runs at an eleventh of steady_clock's speed. Use it
> to test the user-supplied-clock variant of
> __atomic_futex_unsigned::_M_load_when_equal_until works generally
> with test03 and loops correctly when the timeout time hasn't been
> reached in test04.
>---
> libstdc++-v3/include/bits/atomic_futex.h | 15 ++--
> libstdc++-v3/testsuite/30_threads/async/async.cc | 70 +++++++++++++++++-
> 2 files changed, 80 insertions(+), 5 deletions(-)
>
>diff --git a/libstdc++-v3/include/bits/atomic_futex.h b/libstdc++-v3/include/bits/atomic_futex.h
>index 4375129..5f95ade 100644
>--- a/libstdc++-v3/include/bits/atomic_futex.h
>+++ b/libstdc++-v3/include/bits/atomic_futex.h
>@@ -229,11 +229,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> _M_load_when_equal_until(unsigned __val, memory_order __mo,
> const chrono::time_point<_Clock, _Duration>& __atime)
> {
>- const typename _Clock::time_point __c_entry = _Clock::now();
>- const __clock_t::time_point __s_entry = __clock_t::now();
>- const auto __delta = __atime - __c_entry;
>- const auto __s_atime = __s_entry + __delta;
>- return _M_load_when_equal_until(__val, __mo, __s_atime);
>+ typename _Clock::time_point __c_entry = _Clock::now();
>+ do {
>+ const __clock_t::time_point __s_entry = __clock_t::now();
>+ const auto __delta = __atime - __c_entry;
>+ const auto __s_atime = __s_entry + __delta;
>+ if (_M_load_when_equal_until(__val, __mo, __s_atime))
>+ return true;
I wonder if we can avoid looping if _Clock::is_steady is true i.e.
if _GLIBCXX_CONSTEXPR (_Clock::is_steady)
return false
If _Clock is steady then it can't be adjusted to move backwards. I am
not 100% sure, but I don't think a steady clock can run "slow" either.
But I'm checking with LWG about that. We should keep the loop for now.
The reason for wanting to return early is that _Clock::now() can be
quite expensive, so avoiding the second call if it's not needed would
be significant.
Related to this, I've been experimenting with a change to wait_for
that checks __rtime <= 0 and if so, uses __clock_t::time_point::min()
for the wait_until call, so that we don't bother to call
__clock_t::now(). For a non-positive duration we don't need to
determine the precise time_point in the past, because it's in the past
anyway. Using time_point::min() is as good as __clock_t::now() - rtime
(as long as we don't overflow anything when converting it to a struct
timespec, or course!)
Using future.wait_for(0s) to poll for a future becoming ready takes a
long time currently: https://wandbox.org/permlink/Z1arsDE7eHW9JrqJ
Using wait_until(C::time_point::min()) is faster, because it doesn't
call C::now().
This probably isn't important for most timed waiting functions in the
library, because I don't see any reason to use wait_for(0s) to poll a
mutex, condition_variable or atomic. But it's reasonable to do for
futures.
I already added (__rtime <= __rtime.zero()) to this_thread::sleep_for
in d1a74a287ee1a84b90e5675904dac7f945cffca1.
The extra branch to check rtime <= rtime.zero() or atime < C::now() is
probably insignificant compared to the cost of unnecessary calls to
now() on one or more clocks.
>+ __c_entry = _Clock::now();
>+ } while (__c_entry < __atime);
>+ return false;
> }
>
More information about the Gcc-patches
mailing list