Boost logo

Boost :

From: Howard Hinnant (howard.hinnant_at_[hidden])
Date: 2007-08-26 16:30:14


On Aug 25, 2007, at 6:24 PM, Peter Dimov wrote:

> Howard Hinnant:
>
>> They are people, just like you
>> and me. And when the standard gives them choices, they must make
>> them. They try to make them in such a way as to please most of their
>> customers. Sometimes they choose correctly, and sometimes they do
>> not
>> (as a vendor I admit to having chosen incorrectly sometimes). No
>> vendor that I'm aware of is immune to making incorrect choices.
>
> I accept that. So what's the problem? Vendor A ignores the mutex
> argument,
> his users complain, so he finds a way to add checking that doesn't
> increase
> sizeof(condition) and doesn't compromise performance for the rest of
> the
> user base. Vendor B stores the mutex pointer in std::condition, his
> users
> complain, he waits for the next ABI breakage and finds a way to
> achieve
> comparable checking quality and efficiency without increasing
> sizeof(condition). Everyone is happy.
>
> ...
>
>> I don't know any more than you do, so my "forward looking" is no more
>> or less valid than yours. But I strongly believe that it would be
>> careless of us to be careless with sizeof(std::condition).
>
> I'm not being careless with sizeof(condition). I already
> demonstrated two
> ways to achieve checking without storing the pointer into the
> condition
> itself, and hinted at another possibility (exploiting an unused
> void* in
> pthread_cond_t). I also questioned your assertion that increasing
> sizeof(condition) from 28 to 32 is of practical importance.

I've been using "sizeof(condition)" as shorthand for "reducing L1
cache misses". Your technique of moving the checking to a static map
(or unordered_map)<void*, void*> does indeed reduce the
sizeof(condition), but does not reduce L1 cache misses. It is simply
moving the checking data from one place to another. The checking data
still has to be written and read, even when a non-checking condition
is desired. My estimation is that your "external checking data
storage" plan is less efficient, not more efficient, than just storing
the checking data internal to the condition. Though admittedly that
is just an estimate. I haven't measured.

At this point I'm really leaning towards getting back to basics:

class condition
{
public:
     condition();
     ~condition();

     condition(const condition&) = delete;
     condition& operator=(const condition&) = delete;

     void notify_one();
     void notify_all();
     template <class Lock>
         void wait(Lock& lock); // Lock::mutex_type
must be mutex
     template <class Lock, class Predicate>
         void wait(Lock& lock, Predicate pred); // Lock::mutex_type
must be mutex
     template <class Lock> // Lock::mutex_type
must be mutex
         bool timed_wait(Lock& lock, const utc_time& abs_time);
     template <class Lock, class Predicate> // Lock::mutex_type
must be mutex
         bool timed_wait(Lock& lock, const utc_time& abs_time,
Predicate pred);
};

Or perhaps even:

class condition
{
public:
     condition();
     ~condition();

     condition(const condition&) = delete;
     condition& operator=(const condition&) = delete;

     void notify_one();
     void notify_all();
     void wait(unique_lock<mutex>& lock);
     template <class Predicate>
         void wait(unique_lock<mutex>& lock, Predicate pred);
     bool timed_wait(unique_lock<mutex>& lock, const utc_time&
abs_time);
     template <class Predicate>
         bool timed_wait(unique_lock<mutex>& lock, const utc_time&
abs_time, Predicate pred);
};

Or perhaps:

class condition
{
public:
     condition();
     ~condition();

     condition(const condition&) = delete;
     condition& operator=(const condition&) = delete;

     void notify_one();
     void notify_all();
     void wait(mutex& mut);
     template <class Predicate>
         void wait(mutex& mut, Predicate pred);
     bool timed_wait(mutex& mut, const utc_time& abs_time);
     template <class Predicate>
         bool timed_wait(mutex& mut, const utc_time& abs_time,
Predicate pred);
};

The above is small, relatively simple, and everything else we've
discussed can be built on top of it (some parts more easily than
others). The above is the one part of the entire original proposal
that is indispensable (goal #1). The above is a one-to-one mapping to
pthread_cond_t/pthread_mutex_t. We might use the diagnostic library
to return error codes or throw system_error for any non-zero returns
from pthread_cond_*, perhaps with overloads to specify which error
reporting style the user wants to have.

class condition
{
public:
     condition();
     ~condition();

     condition(const condition&) = delete;
     condition& operator=(const condition&) = delete;

     void notify_one();
     void notify_one(error_code&);

     void notify_all();
     void notify_all(error_code&);

     void wait(mutex& mut);
     void wait(mutex& mut, error_code&);

     template <class Predicate>
         void wait(mutex& mut, Predicate pred);
     template <class Predicate>
         void wait(mutex& mut, Predicate pred, error_code&);

     bool timed_wait(mutex& mut, const utc_time& abs_time);
     bool timed_wait(mutex& mut, const utc_time& abs_time, error_code&);

     template <class Predicate>
         bool timed_wait(mutex& mut, const utc_time& abs_time,
Predicate pred);
     template <class Predicate>
         bool timed_wait(mutex& mut, const utc_time& abs_time,
Predicate pred, error_code&);
};

-Howard


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk