|
| 1 | +# SciJava Ops Benchmarks |
| 2 | + |
| 3 | +This page describes a quantitative analysis of the SciJava Ops framework, and is heavily inspired by a similar comparison of [ImgLib2](https://imagej.net/libs/imglib2/benchmarks). |
| 4 | + |
| 5 | +For this analysis, we compare SciJava Ops against: |
| 6 | +1) Raw code execution |
| 7 | +2) [ImageJ Ops](https://imagej.net/libs/imagej-ops/index) |
| 8 | + |
| 9 | +Some of the charts shown plot execution time as a function of the number of executions - this allows us to show the impact of both the just-in-time (JIT) compiler and the framework's ability to improve performance on repeated function calls. |
| 10 | + |
| 11 | +Other charts plot execution time as a function of input size, which allows analysis of framework overhead. |
| 12 | + |
| 13 | +## Hardware and Software |
| 14 | + |
| 15 | +This analysis was performed with the following hardware: |
| 16 | +* 2017 Dell Inspiron 15 7000 Gaming |
| 17 | +* Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz |
| 18 | +* 16 GB 2400 MHz DDR4 RAM |
| 19 | +The following software components were used: |
| 20 | +* Ubuntu 22.04.3 LTS |
| 21 | +* OpenJDK Runtime Environment (build 11.0.21) with OpenJDK 64-Bit Server VM (build 11.0.21, mixed mode, sharing) |
| 22 | +* SciJava Ops Engine version `0.0-SNAPSHOT` |
| 23 | +* ImageJ Ops version `2.0.0` |
| 24 | + |
| 25 | +## "Cheap" Operations |
| 26 | + |
| 27 | +Following the precedent of ImgLib2, we first analyze the performance of each execution method in performing a simple byte inversion, which can be performed very quickly. This operation lends itself to the analysis of framework overhead. |
| 28 | +* The `Raw` execution method simply calls a static method |
| 29 | +* The `SciJava Ops` execution method discovers the static method through the `@implNote` Op declaration and calls the functionality through an `OpEnvironment` |
| 30 | +* The `ImageJ Ops` execution method discovers the static method through an `@Plugin` wrapper class and calls the functionality through an `OpService` |
| 31 | + |
| 32 | +```java |
| 33 | +/** |
| 34 | + * @param data the data to invert |
| 35 | + * @implNote op name="invert",type=Inplace1 |
| 36 | + */ |
| 37 | +public static void invertRaw(final byte[] data) { |
| 38 | + for (int i = 0; i < data.length; i++) { |
| 39 | + final int value = data[i] & 0xff; |
| 40 | + final int result = 255 - value; |
| 41 | + data[i] = (byte) result; |
| 42 | + } |
| 43 | +} |
| 44 | +``` |
| 45 | + |
| 46 | +### Repetition Results |
| 47 | + |
| 48 | +We first note that the JIT improves performance benefits starting from the second iteration on all tested methods. |
| 49 | + |
| 50 | +For the very first execution, both SciJava Ops and ImageJ Ops incur decreased performance due to the overhead of discovering and matching the required functionality. The overhead for SciJava Ops is approximately half as much as the overhead of ImageJ Ops. |
| 51 | + |
| 52 | +Starting from the second execution, SciJava Ops rivals the performance of Raw execution, as it caches the results of prior matching calls. ImageJ Ops does not provide similar functionality. |
| 53 | + |
| 54 | +<div> |
| 55 | +<div class="dygraph" id="cheapIterationVsTime1" style="width: 50%; float:left"></div> |
| 56 | +<div class="dygraph" id="cheapIterationVsTime25" style="width: 50%"></div> |
| 57 | +</div> |
| 58 | + |
| 59 | +### Scaling Results |
| 60 | + |
| 61 | +For the first iteration, we see a consistent hierarchy, where Raw execution outperforms SciJava Ops, and SciJava Ops outperforms ImageJ Ops. This hierarchy stems from matching overhead. |
| 62 | + |
| 63 | +For the final iteration, we see that SciJava Ops consistently performs equivalently to Raw execution, while ImageJ Ops maintains worse performance from matching overhead. |
| 64 | + |
| 65 | +<div> |
| 66 | +<div class="dygraph" id="cheapResolutionVsTime1" style="width: 50%; float:left"></div> |
| 67 | +<div class="dygraph" id="cheapResolutionVsTime10" style="width: 50%"></div> |
| 68 | +</div> |
| 69 | + |
| 70 | +## "Expensive" Operations |
| 71 | + |
| 72 | +We now analyze the performance of each execution method in performing randomization. This operation is much more intensive, and allows us to ensure that the computation dominates any framework overhead. |
| 73 | +* The `Raw` execution method simply calls a static method |
| 74 | +* The `SciJava Ops` execution method discovers the static method through the `@implNote` Op declaration and calls the functionality through an `OpEnvironment` |
| 75 | +* The `ImageJ Ops` execution method discovers the static method through an `@Plugin` wrapper class and calls the functionality through an `OpService` |
| 76 | + |
| 77 | +```java |
| 78 | +/** |
| 79 | + * @param data the data to invert |
| 80 | + * @implNote op name="randomize",type=Inplace1 |
| 81 | + */ |
| 82 | +public static void randomizeRaw(final byte[] data) { |
| 83 | + for (int i = 0; i < data.length; i++) { |
| 84 | + final int value = data[i] & 0xff; |
| 85 | + final double result = expensiveOperation(value); |
| 86 | + data[i] = (byte) result; |
| 87 | + } |
| 88 | +} |
| 89 | +``` |
| 90 | + |
| 91 | +### Repetition Results |
| 92 | + |
| 93 | +For this more expensive operation, we see that computation does indeed dominate any framework overhead for both SciJava Ops and ImageJ Ops. At the smallest input size, we see that ImageJ Ops still shows noticeable matching overhead at every iteration, and SciJava Ops shows noticeable overhead for only the first few iterations. |
| 94 | + |
| 95 | +<div> |
| 96 | +<div class="dygraph" id="expensiveIterationVsTime1" style="width: 50%; float:left"></div> |
| 97 | +<div class="dygraph" id="expensiveIterationVsTime25" style="width: 50%"></div> |
| 98 | +</div> |
| 99 | + |
| 100 | +### Scaling Results |
| 101 | + |
| 102 | +As computation dominates overhead, we see no noticeable difference between the three execution methods. |
| 103 | + |
| 104 | +<div> |
| 105 | +<div class="dygraph" id="expensiveResolutionVsTime1" style="width: 50%; float:left"></div> |
| 106 | +<div class="dygraph" id="expensiveResolutionVsTime10" style="width: 50%"></div> |
| 107 | +</div> |
| 108 | + |
| 109 | +## Reproducing these results |
| 110 | + |
| 111 | +To reproduce these results, take the following steps: |
| 112 | +1) Assuming you have a supported Python 3.x installed, you can run `src/main/scripts.benchmark.sh` within the SciJava Ops Benchmarks module. This will generate a file `copyme.txt` in your current directory |
| 113 | +2) Copy the contents of `copyme.txt` into the HTML at the bottom of this page, starting at the tag `<!--Begin Pasting Benchmark Data-->` |
| 114 | +3) Rebuild the document and view the graphs in a browser |
| 115 | + |
| 116 | +<!-- Populate graphs --> |
| 117 | + |
| 118 | +<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/dygraphs@2.1.0/dist/dygraph.min.js"></script> |
| 119 | +<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/dygraphs@2.1.0/dist/dygraph.min.css" /> |
| 120 | +<style type="text/css"> |
| 121 | + .dygraph { |
| 122 | + display: inline-block; |
| 123 | + max-width: 100%; |
| 124 | + width: 435px; |
| 125 | + height: 250px; |
| 126 | + } |
| 127 | + .dygraph-legend { |
| 128 | + background-color: rgba(200, 200, 255, 0.75) !important; |
| 129 | + padding: 4px; |
| 130 | + border: 1px solid #000; |
| 131 | + border-radius: 10px; |
| 132 | + box-shadow: 4px 4px 4px #888; |
| 133 | + pointer-events: none; |
| 134 | + width: 12em; |
| 135 | + } |
| 136 | + .dygraph-legend > span.highlight { background-color: rgba(255, 255, 200, 0.75) !important; } |
| 137 | + .dygraph-legend > span.highlight { display: inline; } |
| 138 | +</style> |
| 139 | + |
| 140 | + |
| 141 | +<script type="text/javascript"> |
| 142 | + function plot(id, title, xlabel, data) { |
| 143 | + new Dygraph(document.getElementById(id), data, { |
| 144 | + title: title, |
| 145 | + titleHeight: 24, |
| 146 | + xlabel: xlabel, |
| 147 | + ylabel: "Time", |
| 148 | + includeZero: true, |
| 149 | + labelsSeparateLines: true, |
| 150 | + drawPoints: true, |
| 151 | + pointSize: 3, |
| 152 | + highlightCircleSize: 2, |
| 153 | + strokeWidth: 1, |
| 154 | + strokeBorderWidth: 1, |
| 155 | + highlightSeriesOpts: { |
| 156 | + strokeWidth: 3, |
| 157 | + strokeBorderWidth: 1, |
| 158 | + highlightCircleSize: 5 |
| 159 | + } |
| 160 | + }); |
| 161 | + } |
| 162 | + |
| 163 | +<!--Begin Pasting Benchmark Data--> |
| 164 | +plot("cheapIterationVsTime1", "Iteration x Time (ms) at 1 Mpx", "Iteration", |
| 165 | + "Iteration,ImageJ Ops,Raw,SciJava Ops\n" + |
| 166 | + "0,119,11,79\n" + |
| 167 | + "1,15,1,0\n" + |
| 168 | + "2,11,0,0\n" + |
| 169 | + "3,10,1,0\n" + |
| 170 | + "4,10,0,1\n" + |
| 171 | + "5,13,1,0\n" + |
| 172 | + "6,10,0,2\n" + |
| 173 | + "7,7,0,1\n" + |
| 174 | + "8,7,0,0\n" + |
| 175 | + "9,7,0,0"); |
| 176 | + |
| 177 | +plot("cheapIterationVsTime25", "Iteration x Time (ms) at 25 Mpx", "Iteration", |
| 178 | + "Iteration,ImageJ Ops,Raw,SciJava Ops\n" + |
| 179 | + "0,129,76,84\n" + |
| 180 | + "1,21,4,4\n" + |
| 181 | + "2,22,4,4\n" + |
| 182 | + "3,25,7,6\n" + |
| 183 | + "4,12,9,4\n" + |
| 184 | + "5,10,4,4\n" + |
| 185 | + "6,14,4,3\n" + |
| 186 | + "7,12,4,6\n" + |
| 187 | + "8,20,5,6\n" + |
| 188 | + "9,13,5,6"); |
| 189 | + |
| 190 | +plot("cheapResolutionVsTime1", "Resolution x Time (ms) at iteration #1", "Mpx", |
| 191 | + "Mpx,ImageJ Ops,Raw,SciJava Ops\n" + |
| 192 | + "0,119,11,79\n" + |
| 193 | + "1,118,20,67\n" + |
| 194 | + "2,122,34,100\n" + |
| 195 | + "3,122,14,67\n" + |
| 196 | + "4,118,60,58\n" + |
| 197 | + "5,118,26,85\n" + |
| 198 | + "6,116,100,59\n" + |
| 199 | + "7,113,23,66\n" + |
| 200 | + "8,129,76,84"); |
| 201 | + |
| 202 | +plot("cheapResolutionVsTime10", "Resolution x Time (ms) at iteration #10", "Mpx", |
| 203 | + "Mpx,ImageJ Ops,Raw,SciJava Ops\n" + |
| 204 | + "0,7,0,0\n" + |
| 205 | + "1,7,1,1\n" + |
| 206 | + "2,11,1,2\n" + |
| 207 | + "3,8,3,3\n" + |
| 208 | + "4,11,3,2\n" + |
| 209 | + "5,10,3,3\n" + |
| 210 | + "6,8,3,4\n" + |
| 211 | + "7,11,5,7\n" + |
| 212 | + "8,13,5,6"); |
| 213 | + |
| 214 | +plot("expensiveIterationVsTime1", "Iteration x Time (ms) at 1 Mpx", "Iteration", |
| 215 | + "Iteration,ImageJ Ops,Raw,SciJava Ops\n" + |
| 216 | + "0,61,60,50\n" + |
| 217 | + "1,40,41,33\n" + |
| 218 | + "2,39,33,34\n" + |
| 219 | + "3,37,33,32\n" + |
| 220 | + "4,34,30,29\n" + |
| 221 | + "5,33,29,27\n" + |
| 222 | + "6,30,26,26\n" + |
| 223 | + "7,29,25,23\n" + |
| 224 | + "8,27,23,23\n" + |
| 225 | + "9,27,23,22"); |
| 226 | + |
| 227 | +plot("expensiveIterationVsTime25", "Iteration x Time (ms) at 25 Mpx", "Iteration", |
| 228 | + "Iteration,ImageJ Ops,Raw,SciJava Ops\n" + |
| 229 | + "0,798,837,783\n" + |
| 230 | + "1,790,784,781\n" + |
| 231 | + "2,785,780,774\n" + |
| 232 | + "3,761,756,757\n" + |
| 233 | + "4,731,730,731\n" + |
| 234 | + "5,699,694,693\n" + |
| 235 | + "6,649,645,640\n" + |
| 236 | + "7,599,590,594\n" + |
| 237 | + "8,570,566,566\n" + |
| 238 | + "9,557,551,550"); |
| 239 | + |
| 240 | +plot("expensiveResolutionVsTime1", "Resolution x Time (ms) at iteration #1", "Mpx", |
| 241 | + "Mpx,ImageJ Ops,Raw,SciJava Ops\n" + |
| 242 | + "0,61,60,50\n" + |
| 243 | + "1,139,205,124\n" + |
| 244 | + "2,235,304,221\n" + |
| 245 | + "3,331,368,307\n" + |
| 246 | + "4,424,466,407\n" + |
| 247 | + "5,513,557,488\n" + |
| 248 | + "6,596,601,592\n" + |
| 249 | + "7,700,743,674\n" + |
| 250 | + "8,798,837,783"); |
| 251 | + |
| 252 | +plot("expensiveResolutionVsTime10", "Resolution x Time (ms) at iteration #10", "Mpx", |
| 253 | + "Mpx,ImageJ Ops,Raw,SciJava Ops\n" + |
| 254 | + "0,27,23,22\n" + |
| 255 | + "1,92,87,87\n" + |
| 256 | + "2,160,156,154\n" + |
| 257 | + "3,222,217,218\n" + |
| 258 | + "4,293,287,286\n" + |
| 259 | + "5,358,352,346\n" + |
| 260 | + "6,424,420,416\n" + |
| 261 | + "7,480,478,478\n" + |
| 262 | + "8,557,551,550"); |
| 263 | +<!--End Pasting Benchmark Data--> |
| 264 | +</script> |
0 commit comments