|
Boost : |
From: williamkempf_at_[hidden]
Date: 2001-07-02 10:00:08
Let me take one more stab at convincing people that a thread ref
design is not the optimal design before I bow to popular opinion and
implement the design as a thread ref concept. I'm going to try and
persuade people by giving usage patterns using both concepts and
discussing the pros and cons I see for each.
1) Simple creation.
Thread concept:
void foo()
{
thread thrd(&bar);
}
No ref-counting is done and no dynamic memory is allocated (at least
directly). This is probably going to be the absolute optimal usage
of either design as far as speed and memory use is concerned.
Unfortunately, it's also the least used pattern so this may not be
compelling enough.
Thread ref concept:
void foo()
{
thread::ref thrd = thread::create(&bar);
}
Creation of the thread is going to be the same as the thread concept,
plus the overhead for maintaining a ref-count (note that I use this
term loosely, since other means such as linked lists can be employed,
but these other designs just try to optimize the overhead, they don't
eliminate it). This overhead in terms of speed may be significant,
since the ref-counting must be made thread safe. Even if the native
type has it's own form of ref-counting, it's still going to have this
overhead. If the native type doesn't support ref-counting there's
likely to also be overhead in terms of memory usage, and even if the
native type handles ref-counting we may have to roll our own if
additional thread state needs to be maintained. The only argument
that makes sense here in terms of performance is that the ref-
counting mechanisms are likely to be dwarfed by the overhead of
thread creation itself.
2) Simple creation with a join.
Thread concept:
void foo()
{
thread thrd(&bar);
thrd.join();
}
Thread ref concept:
void foo()
{
thread::ref thrd = thread::create(&bar);
thrd->join();
}
As far as performance is concerned, this pattern is identical to (1)
and so doesn't deserve a lot of comment. I should mention, however,
that this pattern is a frequently used one and so is more compelling
than (1) when comparing the overhead between the two designs.
3) Creation within a loop.
Thread concept:
void foo()
{
for (int i=0; i<10; ++i)
thread thrd(&bar);
}
Thread ref concept:
void foo()
{
for (int i=0; i<10; ++i)
thread::ref thrd = thread::create(&bar);
}
Since we aren't going to do a join we can code this with the exact
same methodology as with patterns (1) and (2). So the overhead is
the same and this pattern doesn't deserve a lot of comment either.
Again, though, like (1) this pattern isn't used frequently and so is
not very compelling when comparing the two designs.
4) Creation within a loop and join all.
Thread concept:
void foo()
{
std::auto_ptr<thread> threads[10];
for (int i=0; i<10; ++i)
threads[i].reset(new thread(&bar));
for (int i=0; i<10; ++i)
threads[i]->join();
}
* Or when coded with a thread_group concept. *
void foo()
{
thread_group threads;
for (int i=0; i<10; ++i)
threads.create(&bar);
threads.join_all();
}
This usage pattern requires dynamic memory allocation, which is
definately some significant overhead during construction and
destruction. However, in all likelyhood the thread ref concept is
going to do the same thing under the covers. So in theory the thread
ref concept can be more efficient, but in practice it's likely that
the thread concept will be more efficient. Why? Because the thread
ref concept has the overhead of maintaining the ref-count while the
thread concept with this usage pattern makes use of explicit
management techniques that are much faster and use less memory.
Thread ref concept:
void foo()
{
thread::ref threads[10];
for (int i=0; i<10; ++i)
threads[i] = thread::create(&bar);
for (int i=0; i<10; ++i)
threads[i]->join();
}
This usage pattern is nearly identical to the thread concept usage
pattern. In theory it can be more efficient, in practice it's likely
to be less efficient. If the fact that the overhead exists only in
construction and destruction and so will be dwarfed by the overhead
of these operations on threads is a valid argument for you, then this
usage pattern is a wash for either design.
5) Creation and then passage to an object that will manage the
thread's lifetime.
Thread concept:
void foo()
{
thread_manager manager;
manager.add(new thread(&bar));
}
Requires dynamic allocation, but this was discussed in (4).
Thread ref concept:
void foo()
{
thread_manager manager;
manager.add(thread::create(&bar));
}
Above and beyond the issues discussed in (4) this usage pattern
requires at least one additional ref-count manipulation. Since the
pattern is using explicit ownership this extra overhead is
significant and compelling, making the thread ref design a clear
loser here.
6) Creation with complex lifetime management needs:
Thread concept:
void foo()
{
shared_ptr<thread> thrd(new thread(&bar));
...
}
Thread ref concept:
void foo()
{
thread::ref ref = thread::create(&bar);
...
}
Again, theoretically the thread concept loses here because of the
need for dynamic allocation, but in practice this usage pattern is
probably going to be the only identical usage pattern as far as
efficiency is concerned.
It appears to me that the thread ref concept is more efficient in
only a couple of usage patterns, and then only theoretically, with
most implementations probably producing identical results, or
actually favoring the thread concept. There's also at least two
usage patterns in which the thread concept is going to be the clear
winner no matter the implementation. Usage is also nearly identical
between the two concepts, with neither design being a clear winner
because of ease of use. However, C++ programmers are likely to find
the thread concept more natural. My analysis thus indicates that the
thread concept is the better design.
Have I persuaded anyone?
Bill Kempf
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk