
Hi Darryl, --- Darryl Green <darryl.green@myrealbox.com> 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.