

Show HN: Serializable Math Expressions Using Cap'n Proto - niekb
https://github.com/niekbouman/commelec-api

======
paulasmuth
I have to admit I don't really understand what this is. Looking at the source
I can find and a small server programm that sends/receives hardcoded UDP+JSON
messages that contain some very specific data (a few numeric parameters, I
think you need to understand electrical generators to understand what they
mean) but doesn't seem to do anything with the data except to send and receive
it and doesn't seem to contain error handling/etc. Also I see a bunch of
cap'n'proto schemas to encode a number of mathematical expressions and a small
c++ class to execute those expressions.

Since OP is the author of the linked project: How do the serialized
expressions and the server program fit together and what would this software
be used for? Is it still in early development? Also, why not serialize the
expressions as simple strings/s-expressions -- what is the upside of using
protobuf for this? doesn't it just add unnecessary complexity and overhead?

~~~
niekb
Hi Paul/Paula,

The software is part of a smart (electricity) grid control platform. I agree
that on the github project there is also quite some code that is unrelated to
the title of this Show HN post.

The particular thing I wanted to highlight is explained concretely here:
[https://github.com/niekbouman/commelec-
api/blob/master/docs/...](https://github.com/niekbouman/commelec-
api/blob/master/docs/llapi.md)

The application of the code is a networked control system, where, roughly
speaking, many resources send messages to a central controller. In the light
of this "many-to-one" topology, the advantage of the approach taken in this
project (essentially, sending an abstract syntax tree encoded in capnproto)
over just sending a string, is that there is no parsing-step needed at the
receiver, which saves time. (It is a real-time system)

The server program (I assume that you're referring to daemon.cpp), was added
as a bridge between the more-powerful capnproto-based message format and a
less-flexible but easier-to-use-for-non-experts (JSON-based) interface.
However, what happens in this daemon should not be viewed as part of this Show
HN topic.

For more details about the project, you could have a look at the following
paper (open access)
[https://infoscience.epfl.ch/record/210555?ln=en](https://infoscience.epfl.ch/record/210555?ln=en)

~~~
paulasmuth

        >> the advantage of the approach taken in this project (essentially, sending an
        >> abstract syntax tree encoded in capnproto) over just sending a string, is that
        >> there is no parsing-step needed at the receiver, which saves time. (It is a
        >> real-time system)
    

even though cap'n'proto advertises itself as a "zero copy" serialization
scheme that doesn't necessarily need to copy the data when reading it, it
still needs to "parse" the incoming message. and the way your interpreter is
implemented I think you still need to switch()/recurse over everything in the
proto tree. So I think the main difference here would be switching() over an
enum vs throwing an extra strncmp/hashmap access in there when loading the
expression from the incoming network buffer.

I think cutting that one strncmp per symbol could be a perfomance upside, but
I reckon this would be _tiny_ compared to all the networking and evaluation
overhead (think low microseconds for a few compares on strings that all fit
within the SSO). But might be an interesting thing to benchmark nonetheless,
maybe even against another solution that goes all the way and implements a
proper micro vm so it can execute "bytecode" directly from the incoming
networking buffer without any intermediate at all.

EDIT / side note: just had another look and saw that you are actually storing
some symbols as strings in the capnprot messages and then do a lookup in the
eval loop, too --- and the symbol lookup is implemented in O(N) by doing a
linear scan over the symbol list and comparing the subject with each possible
candidate here -- if you care about speed/micro-optimizing I think you can
improve this to O(1) or at least logn using a hashmap/multiset ->
[https://github.com/niekbouman/commelec-
api/blob/master/comme...](https://github.com/niekbouman/commelec-
api/blob/master/commelec-interpreter/adv-interpreter-eval.cpp#L269)

another thing if you want to microoptmize: you should try to pass stuff by
reference instead of copy in your eval loop wherever possible -- removing as
many mallocs() as possible from the inner interpreter eval loop should be one
of the most low hanging fruits. what stuck out to me was esp. your use of the
auto keyword in the eval code -- some thoughts regarding that:

\- the "auto" keyword does not automatically take a reference. a plain "auto"
may create a copy. use "auto&" or "const auto&" to take a reference

\- esp. in the new range based for loops: in the places where you are
currently using (for auto item : collection) you are performing a copy of all
the items while iterating -- for (const auto& item : collection) will
eliminate those copies

EDIT2: Skimmed your paper and saw it included some benchmarks regarding
message size. However you are benchmarking your cap'n'proto solution ONLY
against MathML, an XML encoded representation (lol).

So I tried one example from your paper -- am I missing something here?

Expression: "P _-10+q^2 "

\- Your capnproto encoding with packing: 82bytes (value from your paper)

\- Your capnproto encoding without packing: 280 bytes (value from your paper,
actually larger than XML...)

\- mathml (xml) without packing: 196 bytes (value from your paper)

\- mathml (xml) without packing: 164 bytes (value from your paper)

\- string encoding of the same expression ("P_-10+q^2"): 9 bytes

[ Hope I am not coming across as too negative, but since you put it in a paper
and published it I think it is fair to do some peer review ]

~~~
niekb
> [ Hope I am not coming across as too negative, but since you put it in a
> paper and published it I think it is fair to do some peer review ]

No, not at all, your feedback is highly appreciated! BTW, the paper is a
preprint, it is currently under review.

\- I fully agree with the O(N) vs O(1) issue in the evalPartialDerivative.
Also, an optimization possibility I considered is to replace string-valued
variable names like "Y" by integers, but this adds some bookkeeping-burden for
a creator of a message that works in a programming language other than c++
(for which we do not provide convenience methods for building messages)

\- I agree with the auto vs (const) auto& point you raised. However, some
cap'n proto types (readers / builders) are copyable types ("handles"), hence
they should be using with auto. (And, sometimes, the compiler can do better
optimizations when you've passed objects (of small size) by value rather than
ref, but this might be different for each specific case; one would have to
benchmark.)

Regarding expression: "P-10+q^2": The string encoding is much shorter indeed,
and is an alternative way. However, \- double values potentially lose
precision in a string representation (btw, also this "JSON bridge" I mentioned
earlier suffers from this problem) and possibly take up more space than 8
bytes. \- the schema also serves as the grammar of the message format; is a
compact "manual", in that (almost) everything that can be expressed using the
schema will be "valid" and accepted by the interpreter. If one encodes a
string, one has to define this grammar separately (one has to define what is a
'valid' string and what not). Adopting this schema-based solution saved us
work. \- there are typically more objects in a message ("advertisement"), so
the string would have to be augmented with information about what it
represents in the message. \- also: having a schema-based solution helped us
in designing the message format (the types defined in the schema let you think
in a more abstract way about the message format), and provides evolvability.

Hence, there were certain motivations for choosing this approach that are
unrelated to space (bytesize) or runtime performance.

