This is an performance analysis for a simple pubsub service written in node.js. It accepts connection on two ports; those on one port are expected to send lines of text which are the messages. All such messages are sent to all connections that are made to the other port. There is no method for subscribing to subsets of the messages.
All measurement are done with 100 'pub' clients and 20 'sub' clients, which means that every incoming message is sent twenty times.
The source code is in pubsub.js
, along with the code to break the
input into messages (lines, that it) in split.js
. I have done the
measurements with four different versions of the splitter (see the
history of this gist).
All graphs have 'message size' on the x axis.
The first one, marked raw
in the graphs, is without any actual
splitting, and thus isn't correct since there is no guarantee that
node.js will deliver the data in the same chunks as they are sent
on the other side.
The next one (1
) is the simples possible correct one; it has the
unfortunate effect that it always does an empty send when the
messages came in in one piese, and it shows in a clearly lower
achievable rate.
2
and 3
improve on that; the most significant improvement is
just not sending the empty buffer which yields an improvement of 30%.
Also apparent is that the processing is dominated by the per-message handling overhead; there is only a slight drop towards bigger messages (all of this is best seen in the 'messages per second' diagrams), and that is probably in the biggest part due to the loop looking for line ends.
The drop in message througput with message bigger than 1000 bytes is caused by saturation of the 1Gbit/s network interface; it can only deliver about 120MBytes/s, and 1k messages duplicated to 20 'sub' clients are already 20kBytes, so there is a limit of about 5000 messages. Below that, throughput is limited by the CPU consumption (single-core) of node.js.
For comparison, there is a measurement (marked C
) of a similar
setup implemented in C (with a more complex message format), which
still outperforms node.js by a factor of two to three. (This will
probably get worse when actually starting to look into the messages.)
The test tool used to generate input is in Plain.java
; it opens
the required number of connections to the node.js process, sends
messages to it, and counts the number of messages it receives on
the 'sub' connections. To avoid overrunning the server it limits
the number of outstanding messages.
node.js is actually remarkably fast.
It also greatly reduces coding overhead in comparison to a C library designed on very similar principles simply due to the use of garbage collection and functional programming; and makes possible very simple interfaces, as the one to the splitter (which is a function taking the next input block and a delivery function for outputting blocks).
👍