Rob Starling
Rob Starling

Reputation: 3908

Should std::future::wait be using so much CPU? Is there a more performant call?

EDIT: tl;dr -- this problem appears to be limited to a small set of OS/compiler/library combinations and is now tracked in the GCC Bugzilla as Bug 68921 thanks to @JonathanWakely.

I'm waiting on a future and I've noticed that top shows 100% CPU usage and strace shows a steady stream of futex calls:

...
[pid 15141] futex(0x9d19a24, FUTEX_WAIT, -2147483648, {4222429828, 3077922816}) = -1 EINVAL (Invalid argument)
...

This is on Linux 4.2.0 (32-bit i686), compiled with gcc version 5.2.1.

Here is my minimum-viable example program:

#include <future>
#include <iostream>
#include <thread>
#include <unistd.h>

int main() {
  std::promise<void> p;
  auto f = p.get_future();

  std::thread t([&p](){
    std::cout << "Biding my time in a thread.\n";
    sleep(10);
    p.set_value();
  });

  std::cout << "Waiting.\n";
  f.wait();
  std::cout << "Done.\n";

  t.join();
  return 0;
}

and here is the compiler invocation (same behavior without -g):

g++ --std=c++11 -Wall -g -o spin-wait spin-wait.cc -pthread

Is there a more-performant alternative?

Here is a logically-similar program using std::condition_variable that seems to perform much better:

#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>
#include <unistd.h>

int main() {
  bool done = 0;
  std::mutex m;
  std::condition_variable cv;

  std::thread t([&m, &cv, &done](){
    std::cout << "Biding my time in a thread.\n";
    sleep(10);
    {
      std::lock_guard<std::mutex> lock(m);
      done = 1;
    }
    cv.notify_all();
  });

  std::cout << "Waiting.\n";
  {
    std::unique_lock<std::mutex> lock(m);
    cv.wait(lock, [&done]{ return done; });
  }
  std::cout << "Done.\n";

  t.join();
  return 0;
}

Am I doing something wrong with my std::future-based code, or is the implementation in my libstdc++ just that bad?

Upvotes: 30

Views: 1636

Answers (2)

Jonathan Wakely
Jonathan Wakely

Reputation: 171413

No of course it shouldn't be doing that, it's a bug in the implementation, not a property of std::future.

This is now https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68921 - the loop that keeps calling futex(2) is in __atomic_futex_unsigned::_M_load_and_test_until

It looks like a simple missing argument to the syscall function, so a garbage pointer is passed to the kernel, which complains that it's not a valid timespec* argument. I'm testing the fix and will commit tomorrow, so it will be fixed in GCC 5.4

Upvotes: 12

Rob Starling
Rob Starling

Reputation: 3908

No, it should not. It usually works great.

(In the comments, we're trying to determine more about a specific broken configuration in which the resultant executable appears to spin-wait, but I believe that's the answer. It would still be nice to determine if this is still a spin-wait on a 32-bit target in the latest g++.)

The promise is the "push" end of the promise-future communication channel: the operation that stores a value in the shared state synchronizes-with (as defined in std::memory_order) the successful return from any function that is waiting on the shared state (such as std::future::get).

I assume this includes std::future::wait.

[std::promise::set_value] Atomically stores the value into the shared state and makes the state ready. The operation behaves as though set_value, set_exception, set_value_at_thread_exit, and set_exception_at_thread_exit acquire a single mutex associated with the promise object while updating the promise object.

While it's a little unsettling that they describe the synchronization in terms of the promise object and not the shared-state, the intent is pretty clear.

cppreference.com[*] goes on to use it in exactly the way that wasn't working in the question above. ("This example shows how promise can be used as signals between threads.")

Upvotes: 0

Related Questions