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

runner.c++ (21363B)


      1 // Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
      2 // Licensed under the MIT License:
      3 //
      4 // Permission is hereby granted, free of charge, to any person obtaining a copy
      5 // of this software and associated documentation files (the "Software"), to deal
      6 // in the Software without restriction, including without limitation the rights
      7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
      8 // copies of the Software, and to permit persons to whom the Software is
      9 // furnished to do so, subject to the following conditions:
     10 //
     11 // The above copyright notice and this permission notice shall be included in
     12 // all copies or substantial portions of the Software.
     13 //
     14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
     20 // THE SOFTWARE.
     21 
     22 #include <sys/types.h>
     23 #include <sys/time.h>
     24 #include <sys/resource.h>
     25 #include <sys/wait.h>
     26 #include <sys/stat.h>
     27 #include <inttypes.h>
     28 #include <string>
     29 #include <stdio.h>
     30 #include <unistd.h>
     31 #include <string.h>
     32 #include <iostream>
     33 #include <iomanip>
     34 
     35 using namespace std;
     36 
     37 namespace capnp {
     38 namespace benchmark {
     39 namespace runner {
     40 
     41 struct Times {
     42   uint64_t real;
     43   uint64_t user;
     44   uint64_t sys;
     45 
     46   uint64_t cpu() { return user + sys; }
     47 
     48   Times operator-(const Times& other) {
     49     Times result;
     50     result.real = real - other.real;
     51     result.user = user - other.user;
     52     result.sys = sys - other.sys;
     53     return result;
     54   }
     55 };
     56 
     57 uint64_t asNanosecs(const struct timeval& tv) {
     58   return (uint64_t)tv.tv_sec * 1000000000 + (uint64_t)tv.tv_usec * 1000;
     59 }
     60 
     61 Times currentTimes() {
     62   Times result;
     63 
     64   struct rusage self, children;
     65   getrusage(RUSAGE_SELF, &self);
     66   getrusage(RUSAGE_CHILDREN, &children);
     67 
     68   struct timeval real;
     69   gettimeofday(&real, nullptr);
     70 
     71   result.real = asNanosecs(real);
     72   result.user = asNanosecs(self.ru_utime) + asNanosecs(children.ru_utime);
     73   result.sys = asNanosecs(self.ru_stime) + asNanosecs(children.ru_stime);
     74 
     75   return result;
     76 }
     77 
     78 struct TestResult {
     79   uint64_t objectSize;
     80   uint64_t messageSize;
     81   Times time;
     82 };
     83 
     84 enum class Product {
     85   CAPNPROTO,
     86   PROTOBUF,
     87   NULLCASE
     88 };
     89 
     90 enum class TestCase {
     91   EVAL,
     92   CATRANK,
     93   CARSALES
     94 };
     95 
     96 const char* testCaseName(TestCase testCase) {
     97   switch (testCase) {
     98     case TestCase::EVAL:
     99       return "eval";
    100     case TestCase::CATRANK:
    101       return "catrank";
    102     case TestCase::CARSALES:
    103       return "carsales";
    104   }
    105   // Can't get here.
    106   return nullptr;
    107 }
    108 
    109 enum class Mode {
    110   OBJECTS,
    111   OBJECT_SIZE,
    112   BYTES,
    113   PIPE_SYNC,
    114   PIPE_ASYNC
    115 };
    116 
    117 enum class Reuse {
    118   YES,
    119   NO
    120 };
    121 
    122 enum class Compression {
    123   NONE,
    124   PACKED,
    125   SNAPPY
    126 };
    127 
    128 TestResult runTest(Product product, TestCase testCase, Mode mode, Reuse reuse,
    129                    Compression compression, uint64_t iters) {
    130   char* argv[6];
    131 
    132   string progName;
    133 
    134   switch (product) {
    135     case Product::CAPNPROTO:
    136       progName = "capnproto-";
    137       break;
    138     case Product::PROTOBUF:
    139       progName = "protobuf-";
    140       break;
    141     case Product::NULLCASE:
    142       progName = "null-";
    143       break;
    144   }
    145 
    146   progName += testCaseName(testCase);
    147   argv[0] = strdup(progName.c_str());
    148 
    149   switch (mode) {
    150     case Mode::OBJECTS:
    151       argv[1] = strdup("object");
    152       break;
    153     case Mode::OBJECT_SIZE:
    154       argv[1] = strdup("object-size");
    155       break;
    156     case Mode::BYTES:
    157       argv[1] = strdup("bytes");
    158       break;
    159     case Mode::PIPE_SYNC:
    160       argv[1] = strdup("pipe");
    161       break;
    162     case Mode::PIPE_ASYNC:
    163       argv[1] = strdup("pipe-async");
    164       break;
    165   }
    166 
    167   switch (reuse) {
    168     case Reuse::YES:
    169       argv[2] = strdup("reuse");
    170       break;
    171     case Reuse::NO:
    172       argv[2] = strdup("no-reuse");
    173       break;
    174   }
    175 
    176   switch (compression) {
    177     case Compression::NONE:
    178       argv[3] = strdup("none");
    179       break;
    180     case Compression::PACKED:
    181       argv[3] = strdup("packed");
    182       break;
    183     case Compression::SNAPPY:
    184       argv[3] = strdup("snappy");
    185       break;
    186   }
    187 
    188   char itersStr[64];
    189   sprintf(itersStr, "%llu", (long long unsigned int)iters);
    190   argv[4] = itersStr;
    191 
    192   argv[5] = nullptr;
    193 
    194   // Make pipe for child to write throughput.
    195   int childPipe[2];
    196   if (pipe(childPipe) < 0) {
    197     perror("pipe");
    198     exit(1);
    199   }
    200 
    201   // Spawn the child process.
    202   struct timeval start, end;
    203   gettimeofday(&start, nullptr);
    204   pid_t child = fork();
    205   if (child == 0) {
    206     close(childPipe[0]);
    207     dup2(childPipe[1], STDOUT_FILENO);
    208     close(childPipe[1]);
    209     execv(argv[0], argv);
    210     exit(1);
    211   }
    212 
    213   close(childPipe[1]);
    214   for (int i = 0; i < 4; i++) {
    215     free(argv[i]);
    216   }
    217 
    218   // Read throughput number written to child's stdout.
    219   FILE* input = fdopen(childPipe[0], "r");
    220   long long unsigned int throughput;
    221   if (fscanf(input, "%lld", &throughput) != 1) {
    222     fprintf(stderr, "Child didn't write throughput to stdout.");
    223   }
    224   char buffer[1024];
    225   while (fgets(buffer, sizeof(buffer), input) != nullptr) {
    226     // Loop until EOF.
    227   }
    228   fclose(input);
    229 
    230   // Wait for child exit.
    231   int status;
    232   struct rusage usage;
    233   wait4(child, &status, 0, &usage);
    234   gettimeofday(&end, nullptr);
    235 
    236   // Calculate results.
    237 
    238   TestResult result;
    239   result.objectSize = mode == Mode::OBJECT_SIZE ? throughput : 0;
    240   result.messageSize = mode == Mode::OBJECT_SIZE ? 0 : throughput;
    241   result.time.real = asNanosecs(end) - asNanosecs(start);
    242   result.time.user = asNanosecs(usage.ru_utime);
    243   result.time.sys = asNanosecs(usage.ru_stime);
    244 
    245   return result;
    246 }
    247 
    248 void reportTableHeader() {
    249   cout << setw(40) << left << "Test"
    250        << setw(10) << right << "obj size"
    251        << setw(10) << right << "I/O bytes"
    252        << setw(10) << right << "wall ns"
    253        << setw(10) << right << "user ns"
    254        << setw(10) << right << "sys ns"
    255        << endl;
    256   cout << setfill('=') << setw(90) << "" << setfill(' ') << endl;
    257 }
    258 
    259 void reportResults(const char* name, uint64_t iters, TestResult results) {
    260   cout << setw(40) << left << name
    261        << setw(10) << right << (results.objectSize / iters)
    262        << setw(10) << right << (results.messageSize / iters)
    263        << setw(10) << right << (results.time.real / iters)
    264        << setw(10) << right << (results.time.user / iters)
    265        << setw(10) << right << (results.time.sys / iters)
    266        << endl;
    267 }
    268 
    269 void reportComparisonHeader() {
    270   cout << setw(40) << left << "Measure"
    271        << setw(15) << right << "Protobuf"
    272        << setw(15) << right << "Cap'n Proto"
    273        << setw(15) << right << "Improvement"
    274        << endl;
    275   cout << setfill('=') << setw(85) << "" << setfill(' ') << endl;
    276 }
    277 
    278 void reportOldNewComparisonHeader() {
    279   cout << setw(40) << left << "Measure"
    280        << setw(15) << right << "Old"
    281        << setw(15) << right << "New"
    282        << setw(15) << right << "Improvement"
    283        << endl;
    284   cout << setfill('=') << setw(85) << "" << setfill(' ') << endl;
    285 }
    286 
    287 class Gain {
    288 public:
    289   Gain(double oldValue, double newValue)
    290       : amount(newValue / oldValue) {}
    291 
    292   void writeTo(std::ostream& os) {
    293     if (amount < 2) {
    294       double percent = (amount - 1) * 100;
    295       os << (int)(percent + 0.5) << "%";
    296     } else {
    297       os << fixed << setprecision(2) << amount << "x";
    298     }
    299   }
    300 
    301 private:
    302   double amount;
    303 };
    304 
    305 ostream& operator<<(ostream& os, Gain gain) {
    306   gain.writeTo(os);
    307   return os;
    308 }
    309 
    310 void reportComparison(const char* name, double base, double protobuf, double capnproto,
    311                       uint64_t iters) {
    312   cout << setw(40) << left << name
    313        << setw(14) << right << Gain(base, protobuf)
    314        << setw(14) << right << Gain(base, capnproto);
    315 
    316   // Since smaller is better, the "improvement" is the "gain" from capnproto to protobuf.
    317   cout << setw(14) << right << Gain(capnproto - base, protobuf - base) << endl;
    318 }
    319 
    320 void reportComparison(const char* name, const char* unit, double protobuf, double capnproto,
    321                       uint64_t iters) {
    322   cout << setw(40) << left << name
    323        << setw(15-strlen(unit)) << fixed << right << setprecision(2) << (protobuf / iters) << unit
    324        << setw(15-strlen(unit)) << fixed << right << setprecision(2) << (capnproto / iters) << unit;
    325 
    326   // Since smaller is better, the "improvement" is the "gain" from capnproto to protobuf.
    327   cout << setw(14) << right << Gain(capnproto, protobuf) << endl;
    328 }
    329 
    330 void reportIntComparison(const char* name, const char* unit, uint64_t protobuf, uint64_t capnproto,
    331                          uint64_t iters) {
    332   cout << setw(40) << left << name
    333        << setw(15-strlen(unit)) << right << (protobuf / iters) << unit
    334        << setw(15-strlen(unit)) << right << (capnproto / iters) << unit;
    335 
    336   // Since smaller is better, the "improvement" is the "gain" from capnproto to protobuf.
    337   cout << setw(14) << right << Gain(capnproto, protobuf) << endl;
    338 }
    339 
    340 size_t fileSize(const std::string& name) {
    341   struct stat stats;
    342   if (stat(name.c_str(), &stats) < 0) {
    343     perror(name.c_str());
    344     exit(1);
    345   }
    346 
    347   return stats.st_size;
    348 }
    349 
    350 int main(int argc, char* argv[]) {
    351   char* path = argv[0];
    352   char* slashpos = strrchr(path, '/');
    353   char origDir[1024];
    354   if (getcwd(origDir, sizeof(origDir)) == nullptr) {
    355     perror("getcwd");
    356     return 1;
    357   }
    358   if (slashpos != nullptr) {
    359     *slashpos = '\0';
    360     if (chdir(path) < 0) {
    361       perror("chdir");
    362       return 1;
    363     }
    364     *slashpos = '/';
    365   }
    366 
    367   TestCase testCase = TestCase::CATRANK;
    368   Mode mode = Mode::PIPE_SYNC;
    369   Compression compression = Compression::NONE;
    370   uint64_t iters = 1;
    371   const char* oldDir = nullptr;
    372 
    373   for (int i = 1; i < argc; i++) {
    374     string arg = argv[i];
    375     if (isdigit(argv[i][0])) {
    376       iters = strtoul(argv[i], nullptr, 0);
    377     } else if (arg == "async") {
    378       mode = Mode::PIPE_ASYNC;
    379     } else if (arg == "inmem") {
    380       mode = Mode::BYTES;
    381     } else if (arg == "eval") {
    382       testCase = TestCase::EVAL;
    383     } else if (arg == "carsales") {
    384       testCase = TestCase::CARSALES;
    385     } else if (arg == "snappy") {
    386       compression = Compression::SNAPPY;
    387     } else if (arg == "-c") {
    388       ++i;
    389       if (i == argc) {
    390         fprintf(stderr, "-c requires argument.\n");
    391         return 1;
    392       }
    393       oldDir = argv[i];
    394     } else {
    395       fprintf(stderr, "Unknown option: %s\n", argv[i]);
    396       return 1;
    397     }
    398   }
    399 
    400   // Scale iterations to something reasonable for each case.
    401   switch (testCase) {
    402     case TestCase::EVAL:
    403       iters *= 100000;
    404       break;
    405     case TestCase::CATRANK:
    406       iters *= 1000;
    407       break;
    408     case TestCase::CARSALES:
    409       iters *= 20000;
    410       break;
    411   }
    412 
    413   cout << "Running " << iters << " iterations of ";
    414   switch (testCase) {
    415     case TestCase::EVAL:
    416       cout << "calculator";
    417       break;
    418     case TestCase::CATRANK:
    419       cout << "CatRank";
    420       break;
    421     case TestCase::CARSALES:
    422       cout << "car sales";
    423       break;
    424   }
    425 
    426   cout << " example case with:" << endl;
    427 
    428   switch (mode) {
    429     case Mode::OBJECTS:
    430     case Mode::OBJECT_SIZE:
    431       // Can't happen.
    432       break;
    433     case Mode::BYTES:
    434       cout << "* in-memory I/O" << endl;
    435       cout << "  * with client and server in the same thread" << endl;
    436       break;
    437     case Mode::PIPE_SYNC:
    438       cout << "* pipe I/O" << endl;
    439       cout << "  * with client and server in separate processes" << endl;
    440       cout << "  * client waits for each response before sending next request" << endl;
    441       break;
    442     case Mode::PIPE_ASYNC:
    443       cout << "* pipe I/O" << endl;
    444       cout << "  * with client and server in separate processes" << endl;
    445       cout << "  * client sends as many simultaneous requests as it can" << endl;
    446       break;
    447   }
    448   switch (compression) {
    449     case Compression::NONE:
    450       cout << "* no compression" << endl;
    451       break;
    452     case Compression::PACKED:
    453       cout << "* de-zero packing for Cap'n Proto" << endl;
    454       cout << "* standard packing for Protobuf" << endl;
    455       break;
    456     case Compression::SNAPPY:
    457       cout << "* Snappy compression" << endl;
    458       break;
    459   }
    460 
    461   cout << endl;
    462 
    463   reportTableHeader();
    464 
    465   TestResult nullCase = runTest(
    466       Product::NULLCASE, testCase, Mode::OBJECT_SIZE, Reuse::YES, compression, iters);
    467   reportResults("Theoretical best pass-by-object", iters, nullCase);
    468 
    469   TestResult protobufBase = runTest(
    470       Product::PROTOBUF, testCase, Mode::OBJECTS, Reuse::YES, compression, iters);
    471   protobufBase.objectSize = runTest(
    472       Product::PROTOBUF, testCase, Mode::OBJECT_SIZE, Reuse::YES, compression, iters).objectSize;
    473   reportResults("Protobuf pass-by-object", iters, protobufBase);
    474 
    475   TestResult capnpBase = runTest(
    476       Product::CAPNPROTO, testCase, Mode::OBJECTS, Reuse::YES, compression, iters);
    477   capnpBase.objectSize = runTest(
    478       Product::CAPNPROTO, testCase, Mode::OBJECT_SIZE, Reuse::YES, compression, iters).objectSize;
    479   reportResults("Cap'n Proto pass-by-object", iters, capnpBase);
    480 
    481   TestResult nullCaseNoReuse = runTest(
    482       Product::NULLCASE, testCase, Mode::OBJECT_SIZE, Reuse::NO, compression, iters);
    483   reportResults("Theoretical best w/o object reuse", iters, nullCaseNoReuse);
    484 
    485   TestResult protobufNoReuse = runTest(
    486       Product::PROTOBUF, testCase, Mode::OBJECTS, Reuse::NO, compression, iters);
    487   protobufNoReuse.objectSize = runTest(
    488       Product::PROTOBUF, testCase, Mode::OBJECT_SIZE, Reuse::NO, compression, iters).objectSize;
    489   reportResults("Protobuf w/o object reuse", iters, protobufNoReuse);
    490 
    491   TestResult capnpNoReuse = runTest(
    492       Product::CAPNPROTO, testCase, Mode::OBJECTS, Reuse::NO, compression, iters);
    493   capnpNoReuse.objectSize = runTest(
    494       Product::CAPNPROTO, testCase, Mode::OBJECT_SIZE, Reuse::NO, compression, iters).objectSize;
    495   reportResults("Cap'n Proto w/o object reuse", iters, capnpNoReuse);
    496 
    497   TestResult protobuf = runTest(
    498       Product::PROTOBUF, testCase, mode, Reuse::YES, compression, iters);
    499   protobuf.objectSize = protobufBase.objectSize;
    500   reportResults("Protobuf I/O", iters, protobuf);
    501 
    502   TestResult capnp = runTest(
    503       Product::CAPNPROTO, testCase, mode, Reuse::YES, compression, iters);
    504   capnp.objectSize = capnpBase.objectSize;
    505   reportResults("Cap'n Proto I/O", iters, capnp);
    506   TestResult capnpPacked = runTest(
    507       Product::CAPNPROTO, testCase, mode, Reuse::YES, Compression::PACKED, iters);
    508   capnpPacked.objectSize = capnpBase.objectSize;
    509   reportResults("Cap'n Proto packed I/O", iters, capnpPacked);
    510 
    511   size_t protobufBinarySize = fileSize("protobuf-" + std::string(testCaseName(testCase)));
    512   size_t capnpBinarySize = fileSize("capnproto-" + std::string(testCaseName(testCase)));
    513   size_t protobufCodeSize = fileSize(std::string(testCaseName(testCase)) + ".pb.cc")
    514                           + fileSize(std::string(testCaseName(testCase)) + ".pb.h");
    515   size_t capnpCodeSize = fileSize(std::string(testCaseName(testCase)) + ".capnp.c++")
    516                        + fileSize(std::string(testCaseName(testCase)) + ".capnp.h");
    517   size_t protobufObjSize = fileSize(std::string(testCaseName(testCase)) + ".pb.o");
    518   size_t capnpObjSize = fileSize(std::string(testCaseName(testCase)) + ".capnp.o");
    519 
    520   TestResult oldNullCase;
    521   TestResult oldNullCaseNoReuse;
    522   TestResult oldCapnpBase;
    523   TestResult oldCapnpNoReuse;
    524   TestResult oldCapnp;
    525   TestResult oldCapnpPacked;
    526   size_t oldCapnpBinarySize = 0;
    527   size_t oldCapnpCodeSize = 0;
    528   size_t oldCapnpObjSize = 0;
    529   if (oldDir != nullptr) {
    530     if (chdir(origDir) < 0) {
    531       perror("chdir");
    532       return 1;
    533     }
    534     if (chdir(oldDir) < 0) {
    535       perror(oldDir);
    536       return 1;
    537     }
    538 
    539     oldNullCase = runTest(
    540         Product::NULLCASE, testCase, Mode::OBJECT_SIZE, Reuse::YES, compression, iters);
    541     reportResults("Old theoretical best pass-by-object", iters, nullCase);
    542 
    543     oldCapnpBase = runTest(
    544         Product::CAPNPROTO, testCase, Mode::OBJECTS, Reuse::YES, compression, iters);
    545     oldCapnpBase.objectSize = runTest(
    546         Product::CAPNPROTO, testCase, Mode::OBJECT_SIZE, Reuse::YES, compression, iters)
    547         .objectSize;
    548     reportResults("Old Cap'n Proto pass-by-object", iters, oldCapnpBase);
    549 
    550     oldNullCaseNoReuse = runTest(
    551         Product::NULLCASE, testCase, Mode::OBJECT_SIZE, Reuse::NO, compression, iters);
    552     reportResults("Old theoretical best w/o object reuse", iters, oldNullCaseNoReuse);
    553 
    554     oldCapnpNoReuse = runTest(
    555         Product::CAPNPROTO, testCase, Mode::OBJECTS, Reuse::NO, compression, iters);
    556     oldCapnpNoReuse.objectSize = runTest(
    557         Product::CAPNPROTO, testCase, Mode::OBJECT_SIZE, Reuse::NO, compression, iters).objectSize;
    558     reportResults("Old Cap'n Proto w/o object reuse", iters, oldCapnpNoReuse);
    559 
    560     oldCapnp = runTest(
    561         Product::CAPNPROTO, testCase, mode, Reuse::YES, compression, iters);
    562     oldCapnp.objectSize = oldCapnpBase.objectSize;
    563     reportResults("Old Cap'n Proto I/O", iters, oldCapnp);
    564     oldCapnpPacked = runTest(
    565         Product::CAPNPROTO, testCase, mode, Reuse::YES, Compression::PACKED, iters);
    566     oldCapnpPacked.objectSize = oldCapnpBase.objectSize;
    567     reportResults("Old Cap'n Proto packed I/O", iters, oldCapnpPacked);
    568 
    569     oldCapnpBinarySize = fileSize("capnproto-" + std::string(testCaseName(testCase)));
    570     oldCapnpCodeSize = fileSize(std::string(testCaseName(testCase)) + ".capnp.c++")
    571                      + fileSize(std::string(testCaseName(testCase)) + ".capnp.h");
    572     oldCapnpObjSize = fileSize(std::string(testCaseName(testCase)) + ".capnp.o");
    573   }
    574 
    575   cout << endl;
    576 
    577   reportComparisonHeader();
    578   reportComparison("memory overhead (vs ideal)",
    579       nullCase.objectSize, protobufBase.objectSize, capnpBase.objectSize, iters);
    580   reportComparison("memory overhead w/o object reuse",
    581       nullCaseNoReuse.objectSize, protobufNoReuse.objectSize, capnpNoReuse.objectSize, iters);
    582   reportComparison("object manipulation time (us)", "",
    583       ((int64_t)protobufBase.time.user - (int64_t)nullCase.time.user) / 1000.0,
    584       ((int64_t)capnpBase.time.user - (int64_t)nullCase.time.user) / 1000.0, iters);
    585   reportComparison("object manipulation time w/o reuse (us)", "",
    586       ((int64_t)protobufNoReuse.time.user - (int64_t)nullCaseNoReuse.time.user) / 1000.0,
    587       ((int64_t)capnpNoReuse.time.user - (int64_t)nullCaseNoReuse.time.user) / 1000.0, iters);
    588   reportComparison("I/O time (us)", "",
    589       ((int64_t)protobuf.time.user - (int64_t)protobufBase.time.user) / 1000.0,
    590       ((int64_t)capnp.time.user - (int64_t)capnpBase.time.user) / 1000.0, iters);
    591   reportComparison("packed I/O time (us)", "",
    592       ((int64_t)protobuf.time.user - (int64_t)protobufBase.time.user) / 1000.0,
    593       ((int64_t)capnpPacked.time.user - (int64_t)capnpBase.time.user) / 1000.0, iters);
    594 
    595   reportIntComparison("message size (bytes)", "", protobuf.messageSize, capnp.messageSize, iters);
    596   reportIntComparison("packed message size (bytes)", "",
    597                       protobuf.messageSize, capnpPacked.messageSize, iters);
    598 
    599   reportComparison("binary size (KiB)", "",
    600       protobufBinarySize / 1024.0, capnpBinarySize / 1024.0, 1);
    601   reportComparison("generated code size (KiB)", "",
    602       protobufCodeSize / 1024.0, capnpCodeSize / 1024.0, 1);
    603   reportComparison("generated obj size (KiB)", "",
    604       protobufObjSize / 1024.0, capnpObjSize / 1024.0, 1);
    605 
    606   if (oldDir != nullptr) {
    607     cout << endl;
    608     reportOldNewComparisonHeader();
    609 
    610     reportComparison("memory overhead",
    611         oldNullCase.objectSize, oldCapnpBase.objectSize, capnpBase.objectSize, iters);
    612     reportComparison("memory overhead w/o object reuse",
    613         oldNullCaseNoReuse.objectSize, oldCapnpNoReuse.objectSize, capnpNoReuse.objectSize, iters);
    614     reportComparison("object manipulation time (us)", "",
    615         ((int64_t)oldCapnpBase.time.user - (int64_t)oldNullCase.time.user) / 1000.0,
    616         ((int64_t)capnpBase.time.user - (int64_t)oldNullCase.time.user) / 1000.0, iters);
    617     reportComparison("object manipulation time w/o reuse (us)", "",
    618         ((int64_t)oldCapnpNoReuse.time.user - (int64_t)oldNullCaseNoReuse.time.user) / 1000.0,
    619         ((int64_t)capnpNoReuse.time.user - (int64_t)oldNullCaseNoReuse.time.user) / 1000.0, iters);
    620     reportComparison("I/O time (us)", "",
    621         ((int64_t)oldCapnp.time.user - (int64_t)oldCapnpBase.time.user) / 1000.0,
    622         ((int64_t)capnp.time.user - (int64_t)capnpBase.time.user) / 1000.0, iters);
    623     reportComparison("packed I/O time (us)", "",
    624         ((int64_t)oldCapnpPacked.time.user - (int64_t)oldCapnpBase.time.user) / 1000.0,
    625         ((int64_t)capnpPacked.time.user - (int64_t)capnpBase.time.user) / 1000.0, iters);
    626 
    627     reportIntComparison("message size (bytes)", "", oldCapnp.messageSize, capnp.messageSize, iters);
    628     reportIntComparison("packed message size (bytes)", "",
    629                         oldCapnpPacked.messageSize, capnpPacked.messageSize, iters);
    630 
    631     reportComparison("binary size (KiB)", "",
    632         oldCapnpBinarySize / 1024.0, capnpBinarySize / 1024.0, 1);
    633     reportComparison("generated code size (KiB)", "",
    634         oldCapnpCodeSize / 1024.0, capnpCodeSize / 1024.0, 1);
    635     reportComparison("generated obj size (KiB)", "",
    636         oldCapnpObjSize / 1024.0, capnpObjSize / 1024.0, 1);
    637   }
    638 
    639   return 0;
    640 }
    641 
    642 }  // namespace runner
    643 }  // namespace benchmark
    644 }  // namespace capnp
    645 
    646 int main(int argc, char* argv[]) {
    647   return capnp::benchmark::runner::main(argc, argv);
    648 }