As we've noted, there are two disadvantages of wait sets vs. condition variables: With condition
variables it is clear from the code what you're waking up, whereas notifyAll() will potentially
wake up a lot of threads unnecessarily and waste a lot of time.
The first point is unambiguous, but the second has that word potentially in it. What about the
realities? We certainly have no problem in producing cases where performance is indeed abysmal,
but how common are those cases?
Let's take a typical client/server program that has been optimized for a specific platform. Our
primary concern is going to be obtaining maximum throughput on a dedicated machine. We'd like
lower loads to be efficient also, but that is strictly secondary.
Let's assume that we have one producer thread listening to all clients. It will take 1 ms of CPU
time to receive a request and enqueue it. Some number of consumer threads will dequeue those
requests and process them as usual. We'll assume that all processing requires 4 ms of CPU and
also requires one disk access averaging 15 ms latency. Further, we'll assume a sufficient number
of disks and distribution of data to allow any number of overlapping requests to run completely
simultaneously (i.e., 15 ms). Finally, we'll choose a 10-CPU machine.
We can conclude that the system will be 100% CPU-bound and that each request/reply will
require 5 ms of CPU, allowing 200 requests/s on each CPU. Total latency will be 20 ms/request;
thus each thread will be able to process 50 requests/s. To obtain maximum throughput, we'll need
4 threads per CPU--thus a total of 40 threads on our 10 CPUs, processing 2000 requests/s.
If we conveniently assume a very steady load with negative feedback from the buffer (i.e., a client
who is waiting for a reply will not issue any new requests), the buffer will remain partially full at
all times and no consumer threads will ever be waiting on an empty buffer, nor will the producer
thread ever be waiting on a full one. The potential problem with excessive wakeups due to
notifyAll() will be completely moot.
Now let's assume an overload. The buffer will remain full at all times and the producer will be
blocking regularly, while the consumers will never block (the list is never empty). Once again, no
excessive wakeup problem!
Finally, let's look at the underloaded case. Instead of the peak load of 2000 requests/s, let's look at
1000 requests/s. The buffer will be empty almost all the time and an average of 20 consumer
threads will be sleeping. Each time the producer adds a request to the queue, it will wake up all 20.
One consumer will get the request and the other 19 will have to go back to sleep. This is clearly a
waste, but how much of one, and do we care?
On an SS4, a spurious wakeup costs about 100 µs. With a rate of 100 requests/s, this will cost us
about 100 ms, roughly 1% of available CPU power (on our 10 CPUs). Do we care about a 1%
waste on a non-peak load? Not very much. The conclusion is that on any similar program, the
excessive wakeup problem is not a major performance problem at all!
By contrast, let's look at the extra CPU costs of using an explicit condition variable. A call to
condWait()/condSignal() costs about 9 µs on an SS4, whereas wait/notify costs 3 µs. In our
maximum throughput example we never block on the condition variable anyway, so there's no
cost. In our overflow example, we'd be making 2000 calls/s, wasting 100 ms, 1% of CPU. In our
underloaded example, we'd be saving 100 ms. None of these numbers is very large and the entire
performance issue is completely moot for this kind of program (and indeed, probably for any
"normal" program!).
The one perversely funny aspect of this entire issue is that wait/notify is implemented in terms on
condition variables in the underlying POSIX library! If condition variables were included as part
Search WWH :
Custom Search
Previous Page
Multithreaded Programming with JAVA - Topic Index
Next Page
Multithreaded Programming with JAVA - Bookmarks
Home