Skip to content
This repository has been archived by the owner on Aug 5, 2022. It is now read-only.

Performance Experiments

Geoff Gustafson edited this page Mar 8, 2017 · 5 revisions

I wanted to capture data from some experiments I ran to understand the overhead of our JS environment better. These tests were all on Arduino 101 which runs at 32MHz.

Overhead of calling a native C API

Calling a native C API bound as a JS external function, which simply increments a counter in C, and returning 0 (which happens to be the jerry_value_t for the number 0), averaged 2036 clock cycles, or about 64 microseconds.

That means our theoretical maximum number of calls to C per second is about 15,700.

I measured this by calling one API to record a start time in C, followed by 100 calls to the minimal counter API. I repeated the same line of code 100x in the source. On the 100th call, it records the time, calculates the elapsed time, and prints it out. So dividing that by 100 I got the average call time of 2036 cycles.

Returning ZJS_UNDEFINED (which creates an undefined value) instead added about 4 clock cycles, FWIW (2040 cycle average). Returning jerry_create_number(counter) instead added about 2 more cycles.

Overhead of looping

Next, instead of directly calling the function 100x in source, I created a for loop:

for (var i=0; i<100; i++)
    minimalAPI();

This averaged 2897 cycles. So the overhead of the loop and incrementing i was about 857 cycles per loop iteration.

Overhead of a simple JS statement

Next, I returned to the 100x repetition of the function call, but in between each I added two to a variable:

minimalAPI();
i += 2;
minimalAPI();
i += 2;
...

This bumped me up to about 3604 cycles average. So the addition operation seemed to add about 1564 cycles.

Overhead of calling a JS callback

A slightly different case is calling a JS callback from C. I recorded the time just before calling the callback. Then the callback calls into a minimal C API which records the time again. I print the elapsed time, and that came out to about 2400 cycles. I didn't do this as rigorously, averaging over 100 calls or anything, but checked a number of them and it seems pretty consistently around 2400 cycles.

Overhead of serial console print

Then I measured the time to print to the serial console from C with ZJS_PRINT.

  • Printing an empty string: ~117 cycles
  • Printing just a newline: ~9600 cycles
  • Printing 20 characters (including a newline): ~69,000 cycles
  • Printing 40 characters (including a newline): ~132,000 cycles
  • Printing 60 characters (including a newline): ~194,000 cycles
  • Printing 80 characters (including a newline): ~258,000 cycles

So, printing takes about 3130 clocks per character plus 9600 cycles for a newline.

Summary of Findings

The simplest line of JavaScript apparently takes about 1600 clock cycles to run. Getting out to a custom C API takes only slightly more, about 2000 clock cycles. So this confirms our intuition that almost anything we can handle in C is worth doing there and saving the user effort in JavaScript. Of course, this has to be balanced with code size.

On the other hand, printing to the serial console is very slow - taking over 3000 cycles per character. So we need to be very careful about when we do that. We need to tie our output to multiple debug levels. Production/release mode should have basically none except some startup messages. Then we should have multiple levels or options to not just enable everything. We currently always enable ERR_PRINT but only enable DBG_PRINT in debug mode. Instead, even ERR_PRINT should be disabled except when specifically debugging I think.

We also need to be especially careful not to put prints in performance-critical places, even in debugging. One technique to avoid it is to record an error flag and then display it later when we have time. I've already done this once with zjs_ringbuf_error_count in zjs_callbacks.c.

Future Experiments

Some more experiments we could run might give us an idea of the overhead of marshalling data between JS and C, such as passing arguments of different types to a JS callback, or to a C API.

If you think of other experiments that could be run, or data you'd like to have along these lines, feel free to edit this section or email me.

Suggested future work:

  • Overhead of passing data of different types between JS and C

Geoff Gustafson
March 8, 2017