Boost logo

Boost :

From: Christopher Kohlhoff (chris_at_[hidden])
Date: 2005-12-28 07:31:26


Hi Darryl,

--- Darryl Green <darryl.green_at_[hidden]> wrote:
> Christopher Kohlhoff wrote:
> > For stream-oriented I/O, this separation is presented in the
> > form of the Stream concept. This concept is implemented by
> > stream_socket, ssl::stream, buffered_read_stream and so on. It
> > would be implemented by a hypothetical pipe class.
>
> Precisely. Why write it/maintain it again?

Well... because although the interface might be the same the
implementation is different.

> >>A very simple active object template that supports read and
> >>write, but that relies on free functions or helper classes for
> >>less universal operations such as bind, setting options, etc,
> >>used as a base class, allows appropriate (and necessary in my
> >>opinion) use of runtime polymorphism.
> >
> > I have to disagree on runtime polymorphism: compile-time
> > polymorphism should always be the default.
>
> A tad dogmatic. Would you like to qualify "always" at all?

I was referring to operations such as read, write, etc. I
thought that was clear from the context. If not, my apologies.

> What do you mean by "default" - is the default selectable at
> design/implementation or compilation time?

By "default" I mean that the interface should be designed to use
compile time polymorphism with concepts. Runtime polymorphism
can be introduced, should the developer choose, through a
wrapper.

> > Runtime polymorphism can impose additional costs (such as
> > virtual function calls on a fine-grained interface), and
> > eliminates potential optimisations. This adds up
> > considerably if there are many layers of runtime
> > polymorphism involved.
>
> Can we cut the re-education program for java programmers :-)
> out of this and deal with the use case constructively, or
> would you prefer to ignore it?

My design choice is based on experience in developing real-world
high-performance networking applications that are often CPU
bound.

A previous system I worked on made extensive use of runtime
polymorphism via the ACE_Streams abstraction, where passing data
through each layer involves a virtual function call. Since
configuration of the individual layers did not need to occur at
runtime, this runtime polymorphism was unnecessary. Removing it
markedly improved performance.

> I qualified my concerns about performance by saying they
> lacked actual benchmark results, however I have now seen posts
> from others backing my concerns (between the time I spent
> responding and gmane being my only interface at the time I
> hadn't seen Caleb's results when I posted).

I do not believe that particular test is representative of
typical use, as it only involves a single socket on a demuxer
receiving data from another socket in the same process. Also as
I have indicated elsewhere I have since made some optimisations
that have significantly reduced costs.

> You are now expressing concern about virtual function overhead
> etc with no measurements to back it up and no qualification of
> when they are "bad". In the development of the library I
> referred to in my post I have measured negligible performance
> impact from runtime polymorphism. A double indirection is
> unlikely to be significant compared to the other operations
> being performed.

Double indirection is not the only cost of runtime polymorphism,
as you are no doubt aware. Virtual functions can also result in
a loss of locality-of-reference, causing increased cache misses
and possibly page faults in memory-constrained systems, and
they eliminate the possibility of inlining. Furthermore, the
custom allocation optimisation is possible because type
information has not been erased, which would have occurred if
the interface used virtual functions.

Do I have the benchmarks on hand to support this? No, but I have
done these tests in the past. Working on high performance
systems has taught me that runtime polymorphism has very real
costs, and so in general should not be introduced unless runtime
variation of behaviour is required. The design of asio reflects
this experience.

<snip>
> > What do you think? I'd be happy to add it if you think it
> > generally useful.
>
> I do think it is generally useful.
>
> >>Short of placing a wrapper around asio to make it more
> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Yes to the following - but there
> is an aspect of code re-use that it doesn't address.

Which aspect would this be?

> >>As far as I can see asio doesn't even allow interchangeable
> >>use of an ssl and a tcp stream.
> > As I mentioned above, asio addresses this using compile time
> > polymorphism, with the Stream concept being implemented by
> > stream_socket and ssl::stream. For example, both work
> > equally well with free
>
> template !

I guess this means that you like templates about as much as I
like virtual functions. :)

> I'm referring to the potential for bloat if eg a pipe service
> duplicates much of the socket interface. I know openssl is
> quite weird enough to be a special case all of its own, but I
> would expect many systems would be able to share a lot of
> implementation across different stream-like file/socket/device
> interfaces at least.

I don't think there would be as much sharing as you might think.
And even in cases where there is some sharing, the common
functionality can be extracted into a function or class to be
called from each place, so I don't see how this design
necessarily leads to bloat. Fundamentally, asio's public
interface cannot assume sharing, since that assumption is
already known to be incorrect.

Cheers,
Chris

P.S. Still reading your other replies. I hope to respond to them
soon.


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