capnproto

FORK: Cap'n Proto serialization/RPC system - core tools and C++ library
git clone https://git.neptards.moe/neptards/capnproto.git
Log | Files | Refs | README | LICENSE

2013-12-13-promise-pipelining-capnproto-vs-ice.md (4122B)


      1 ---
      2 layout: post
      3 title: "Promise Pipelining and Dependent Calls: Cap'n Proto vs. Thrift vs. Ice"
      4 author: kentonv
      5 ---
      6 
      7 _UPDATED:  Added Thrift to the comparison._
      8 
      9 So, I totally botched the 0.4 release announcement yesterday.  I was excited about promise
     10 pipelining, but I wasn't sure how to describe it in headline form.  I decided to be a bit
     11 silly and call it "time travel", tongue-in-cheek.  My hope was that people would then be
     12 curious, read the docs, find out that this is actually a really cool feature, and start doing
     13 stuff with it.
     14 
     15 Unfortunately, [my post](2013-12-12-capnproto-0.4-time-travel.html) only contained a link to
     16 the full explanation and then confusingly followed the "time travel" section with a separate section
     17 describing the fact that I had implemented a promise API in C++.  Half the readers clicked through
     18 to the documentation and understood.  The other half thought I was claiming that promises alone
     19 constituted "time travel", and thought I was ridiculously over-hyping an already-well-known
     20 technique.  My HN post was subsequently flagged into oblivion.
     21 
     22 Let me be clear:
     23 
     24 **Promises alone are _not_ what I meant by "time travel"!**
     25 
     26 <img src='{{ site.baseurl }}images/capnp-vs-thrift-vs-ice.png' style='width:350px; height:275px; float: right;'>
     27 
     28 So what did I mean?  Perhaps [this benchmark](https://github.com/kentonv/capnp-vs-ice) will
     29 make things clearer.  Here, I've defined a server that exports a simple four-function calculator
     30 interface, with `add()`, `sub()`, `mult()`, and `div()` calls, each taking two integers and\
     31 returning a result.
     32 
     33 You are probably already thinking:  That's a ridiculously bad way to define an RPC interface!
     34 You want to have _one_ method `eval()` that takes an expression tree (or graph, even), otherwise
     35 you will have ridiculous latency.  But this is exactly the point.  **With promise pipelining, simple,
     36 composable methods work fine.**
     37 
     38 To prove the point, I've implemented servers in Cap'n Proto, [Apache Thrift](http://thrift.apache.org/),
     39 and [ZeroC Ice](http://www.zeroc.com/).  I then implemented clients against each one, where the
     40 client attempts to evaluate the expression:
     41 
     42     ((5 * 2) + ((7 - 3) * 10)) / (6 - 4)
     43 
     44 All three frameworks support asynchronous calls with a promise/future-like interface, and all of my
     45 clients use these interfaces to parallelize calls.  However, notice that even with parallelization,
     46 it takes four steps to compute the result:
     47 
     48     # Even with parallelization, this takes four steps!
     49     ((5 * 2) + ((7 - 3) * 10)) / (6 - 4)
     50       (10    + (   4    * 10)) /    2      # 1
     51       (10    +         40)     /    2      # 2
     52             50                 /    2      # 3
     53                               25           # 4
     54 
     55 As such, the Thrift and Ice clients take four network round trips.  Cap'n Proto, however, takes
     56 only one.
     57 
     58 Cap'n Proto, you see, sends all six calls from the client to the server at one time.  For the
     59 latter calls, it simply tells the server to substitute the former calls' results into the new
     60 requests, once those dependency calls finish.  Typical RPC systems can only send three calls to
     61 start, then must wait for some to finish before it can continue with the remaining calls.  Over
     62 a high-latency connection, this means they take 4x longer than Cap'n Proto to do their work in
     63 this test.
     64 
     65 So, does this matter outside of a contrived example case?  Yes, it does, because it allows you to
     66 write cleaner interfaces with simple, composable methods, rather than monster do-everything-at-once
     67 methods.  The four-method calculator interface is much simpler than one involving sending an
     68 expression graph to the server in one batch.  Moreover, pipelining allows you to define
     69 object-oriented interfaces where you might otherwise be tempted to settle for singletons.  See
     70 [my extended argument]({{ site.baseurl }}rpc.html#introduction) (this is what I was trying to get
     71 people to click on yesterday :) ).
     72 
     73 Hopefully now it is clearer what I was trying to illustrate with this diagram, and what I meant
     74 by "time travel"!
     75 
     76 <img src='{{ site.baseurl }}images/time-travel.png' style='max-width:639px'>