| <!DOCTYPE html> |
| <html> |
| <head> |
| <meta charset="UTF-8" /> |
| <title>Binary vs String Transfer Benchmark</title> |
| <script src="https://cdn.plot.ly/plotly-2.26.0.min.js"></script> |
| <style> |
| body { |
| font-family: Tahoma, Serif; |
| font-size: 10pt; |
| } |
| .info { |
| font-size: 12pt; |
| } |
| .left { |
| text-align: left; |
| } |
| .right { |
| text-align: right; |
| } |
| .positive { |
| color: green; |
| font-weight: bold; |
| } |
| .negative { |
| color: red; |
| font-weight: bold; |
| } |
| .center { |
| text-align: center; |
| } |
| table.resultTable { |
| border: 1px solid black; |
| border-collapse: collapse; |
| empty-cells: show; |
| width: 100%; |
| } |
| table.resultTable td { |
| padding: 2px 4px; |
| border: 1px solid black; |
| } |
| table.resultTable > thead > tr { |
| font-weight: bold; |
| background: lightblue; |
| } |
| table.resultTable > tbody > tr:nth-child(odd) { |
| background: white; |
| } |
| table.resultTable > tbody > tr:nth-child(even) { |
| background: lightgray; |
| } |
| .hide { |
| display: none; |
| } |
| </style> |
| </head> |
| |
| <body background-color="white"> |
| <h1>Binary vs String Transfer Benchmark</h1> |
| |
| <table> |
| <tr> |
| <td> |
| <p class="info"> |
| This benchmark evaluates the message transfer speed between the |
| renderer process and the browser process. <br />It compares the |
| performance of binary and string message transfer. |
| </p> |
| <p class="info"> |
| <b>Note:</b> There is no progress indication of the tests because it |
| significantly influences measurements. <br />It usually takes 30 |
| seconds (for 300 samples) to complete the tests. |
| </p> |
| </td> |
| </tr> |
| <tr> |
| <td> |
| Samples: |
| <input |
| id="sSamples" |
| type="text" |
| value="300" |
| required |
| pattern="[0-9]+" |
| /> |
| <button id="sRun" autofocus onclick="runTestSuite()">Run</button> |
| </td> |
| </tr> |
| </table> |
| |
| <div style="padding-top: 10px; padding-bottom: 10px"> |
| <table id="resultTable" class="resultTable"> |
| <thead> |
| <tr> |
| <td class="center" style="width: 8%">Message Size</td> |
| <td class="center" style="width: 8%"> |
| String Round Trip Avg, ms |
| </td> |
| <td class="center" style="width: 8%"> |
| Binary Round Trip Avg, ms |
| </td> |
| <td class="center" style="width: 10%">Relative Trip Difference</td> |
| <td class="center" style="width: 8%">String Speed, MB/s</td> |
| <td class="center" style="width: 8%">Binary Speed, MB/s</td> |
| <td class="center" style="width: 10%">Relative Speed Difference</td> |
| <td class="center" style="width: 8%">String Standard Deviation</td> |
| <td class="center" style="width: 8%">Binary Standard Deviation</td> |
| </tr> |
| </thead> |
| <tbody> |
| <!-- result rows here --> |
| </tbody> |
| </table> |
| </div> |
| |
| <div id="round_trip_avg_chart"> |
| <!-- Average round trip linear chart will be drawn inside this DIV --> |
| </div> |
| |
| <div id="box_plot_chart"> |
| <!-- Box plot of round trip time will be drawn inside this DIV --> |
| </div> |
| |
| <script type="text/javascript"> |
| let tests = []; |
| let box_plot_test_data = []; |
| let round_trip_avg_plot_data = []; |
| |
| function nextTestSuite(testIndex) { |
| const nextTestIndex = testIndex + 1; |
| setTimeout(execTestSuite, 0, nextTestIndex); |
| } |
| |
| function generateRandomString(size) { |
| // Symbols that will be encoded as two bytes in UTF-8 |
| // so we compare transfer of the same amount of bytes |
| const characters = |
| "АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя"; |
| let randomString = ""; |
| for (let i = 0; i < size; i++) { |
| const randomIndex = Math.floor(Math.random() * characters.length); |
| randomString += characters.charAt(randomIndex); |
| } |
| return randomString; |
| } |
| |
| function generateRandomArrayBuffer(size) { |
| const buffer = new ArrayBuffer(size); |
| const uint8Array = new Uint8Array(buffer); |
| for (let i = 0; i < uint8Array.length; i++) { |
| uint8Array[i] = Math.floor(Math.random() * 256); |
| } |
| return buffer; |
| } |
| |
| function reportError(errorCode, errorMessage) { |
| console.error(`ErrorCode:${errorCode} Message:${errorMessage}`); |
| } |
| |
| function sendString(size, testIndex) { |
| const request = generateRandomString(size); |
| const startTime = performance.now(); |
| const onSuccess = (response) => { |
| const roundTrip = performance.now() - startTime; |
| const test = tests[testIndex]; |
| test.totalRoundTrip += roundTrip; |
| test.sample++; |
| box_plot_test_data[testIndex].x.push(roundTrip); |
| setTimeout(execTest, 0, testIndex); |
| }; |
| |
| window.cefQuery({ |
| request: request, |
| onSuccess: onSuccess, |
| onFailure: reportError, |
| }); |
| } |
| |
| function sendArrayBuffer(size, testIndex) { |
| const request = generateRandomArrayBuffer(size); |
| const startTime = performance.now(); |
| const onSuccess = (response) => { |
| const roundTrip = performance.now() - startTime; |
| const test = tests[testIndex]; |
| test.totalRoundTrip += roundTrip; |
| test.sample++; |
| box_plot_test_data[testIndex].x.push(roundTrip); |
| setTimeout(execTest, 0, testIndex); |
| }; |
| |
| window.cefQuery({ |
| request: request, |
| onSuccess: onSuccess, |
| onFailure: reportError, |
| }); |
| } |
| |
| function getStandardDeviation(array, mean) { |
| const n = array.length; |
| if (n < 5) return null; |
| return Math.sqrt( |
| array.map((x) => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / |
| (n - 1) |
| ); |
| } |
| |
| function execTest(testIndex) { |
| const test = tests[testIndex]; |
| if (test.sample >= test.totalSamples) { |
| return nextTestSuite(testIndex); |
| } |
| test.func(test.length, test.index); |
| } |
| |
| function column(prepared, value) { |
| return ( |
| "<td class='right'>" + (!prepared ? "-" : value.toFixed(3)) + "</td>" |
| ); |
| } |
| |
| function relativeDiffColumn(prepared, value, isBiggerBetter) { |
| if (!prepared) return "<td class='right'>-</td>"; |
| |
| const isPositive = value >= 0 == isBiggerBetter; |
| return [ |
| "<td class='right ", |
| isPositive ? "positive" : "negative", |
| "'>", |
| value > 0 ? "+" : "", |
| value.toFixed(2), |
| "%</td>", |
| ].join(""); |
| } |
| |
| function displayResult(test) { |
| const id = "testResultRow_" + test.index; |
| |
| const markup = [ |
| "<tr id='", |
| id, |
| "'>", |
| "<td class='left'>", |
| test.name, |
| "</td>", |
| column(test.prepared, test.avgRoundTrip), |
| column(test.prepared, test.avgRoundTripBin), |
| relativeDiffColumn(test.prepared, test.relativeTripDiff, false), |
| column(test.prepared, test.speed), |
| column(test.prepared, test.speedBinary), |
| relativeDiffColumn(test.prepared, test.relativeSpeedDiff, true), |
| "<td class='right'>", |
| !test.prepared || test.stdDeviation == null |
| ? "-" |
| : test.stdDeviation.toFixed(3), |
| "</td>", |
| "<td class='right'>", |
| !test.prepared || test.stdDeviationBinary == null |
| ? "-" |
| : test.stdDeviationBinary.toFixed(3), |
| "</td>", |
| "</tr>", |
| ].join(""); |
| |
| const row = document.getElementById(id); |
| if (row) { |
| row.outerHTML = markup; |
| } else { |
| const tbody = document.getElementById("resultTable").tBodies[0]; |
| tbody.insertAdjacentHTML("beforeEnd", markup); |
| } |
| } |
| function relativeDiff(left, right) { |
| if (right != 0) { |
| return ((left - right) / right) * 100; |
| } |
| return 0; |
| } |
| |
| function buildTestResults(tests) { |
| testResults = []; |
| |
| let oldRoundTrip = { |
| x: [], |
| y: [], |
| type: "scatter", |
| name: "String", |
| }; |
| |
| let newRoundTrip = { |
| x: [], |
| y: [], |
| type: "scatter", |
| name: "Binary", |
| }; |
| |
| for (let i = 0; i < tests.length / 2; i++) { |
| const index = testResults.length; |
| |
| // Tests are in pairs - String and Binary |
| const test = tests[i * 2]; |
| const testBin = tests[i * 2 + 1]; |
| |
| const avgRoundTrip = test.totalRoundTrip / test.totalSamples; |
| const avgRoundTripBin = testBin.totalRoundTrip / testBin.totalSamples; |
| const relativeTripDiff = relativeDiff(avgRoundTripBin, avgRoundTrip); |
| |
| // In MB/s |
| const speed = test.byteSize / (avgRoundTrip * 1000); |
| const speedBinary = testBin.byteSize / (avgRoundTripBin * 1000); |
| const relativeSpeedDiff = relativeDiff(speedBinary, speed); |
| |
| const stdDeviation = getStandardDeviation( |
| box_plot_test_data[test.index].x, |
| avgRoundTrip |
| ); |
| const stdDeviationBinary = getStandardDeviation( |
| box_plot_test_data[testBin.index].x, |
| avgRoundTripBin |
| ); |
| |
| testResults.push({ |
| name: humanFileSize(test.byteSize), |
| index: index, |
| prepared: true, |
| avgRoundTrip: avgRoundTrip, |
| avgRoundTripBin: avgRoundTripBin, |
| relativeTripDiff: relativeTripDiff, |
| speed: speed, |
| speedBinary: speedBinary, |
| relativeSpeedDiff: relativeSpeedDiff, |
| stdDeviation: stdDeviation, |
| stdDeviationBinary: stdDeviationBinary, |
| }); |
| |
| oldRoundTrip.x.push(test.byteSize); |
| newRoundTrip.x.push(test.byteSize); |
| oldRoundTrip.y.push(avgRoundTrip); |
| newRoundTrip.y.push(avgRoundTripBin); |
| } |
| |
| round_trip_avg_plot_data = [oldRoundTrip, newRoundTrip]; |
| return testResults; |
| } |
| |
| function buildEmptyTestResults(tests) { |
| testResults = []; |
| for (let i = 0; i < tests.length / 2; i++) { |
| const index = testResults.length; |
| const test = tests[i * 2]; |
| |
| testResults.push({ |
| name: humanFileSize(test.byteSize), |
| index: index, |
| prepared: false, |
| }); |
| } |
| return testResults; |
| } |
| |
| function resetTestsResults(totalSamples) { |
| if (totalSamples <= 0) totalSamples = 1; |
| |
| // Reset tests results |
| tests.forEach((test) => { |
| test.sample = 0; |
| test.totalRoundTrip = 0; |
| test.totalSamples = totalSamples; |
| }); |
| |
| testResults = buildEmptyTestResults(tests); |
| testResults.forEach((result) => displayResult(result)); |
| |
| round_trip_avg_plot_data = []; |
| box_plot_test_data.forEach((data) => { |
| data.x = []; |
| }); |
| } |
| |
| function queueTest(name, byteSize, length, testFunc) { |
| const testIndex = tests.length; |
| test = { |
| name: name, |
| byteSize: byteSize, |
| length: length, |
| index: testIndex, |
| sample: 0, |
| totalRoundTrip: 0, |
| func: testFunc, |
| }; |
| tests.push(test); |
| |
| box_plot_test_data.push({ |
| x: [], |
| type: "box", |
| boxpoints: "all", |
| name: name, |
| jitter: 0.3, |
| pointpos: -1.8, |
| }); |
| } |
| |
| function execTestSuite(testIndex) { |
| if (testIndex < tests.length) { |
| execTest(testIndex); |
| } else { |
| testsRunFinished(); |
| } |
| } |
| |
| function startTests() { |
| // Let the updated table render before starting the tests |
| setTimeout(execTestSuite, 200, 0); |
| } |
| |
| function execQueuedTests(totalSamples) { |
| resetTestsResults(totalSamples); |
| startTests(); |
| } |
| |
| function setSettingsState(disabled) { |
| document.getElementById("sSamples").disabled = disabled; |
| document.getElementById("sRun").disabled = disabled; |
| } |
| |
| function testsRunFinished() { |
| testResults = buildTestResults(tests); |
| testResults.forEach((result) => displayResult(result)); |
| |
| const round_trip_layout = { |
| title: "Average round trip, μs (Smaller Better)", |
| }; |
| Plotly.newPlot( |
| "round_trip_avg_chart", |
| round_trip_avg_plot_data, |
| round_trip_layout |
| ); |
| |
| const box_plot_layout = { |
| title: "Round Trip Time, μs", |
| }; |
| Plotly.newPlot("box_plot_chart", box_plot_test_data, box_plot_layout); |
| setSettingsState(false); |
| } |
| |
| function humanFileSize(bytes) { |
| const step = 1024; |
| const originalBytes = bytes; |
| |
| if (Math.abs(bytes) < step) { |
| return bytes + " B"; |
| } |
| |
| const units = [" KB", " MB", " GB"]; |
| let u = -1; |
| let count = 0; |
| |
| do { |
| bytes /= step; |
| u += 1; |
| count += 1; |
| } while (Math.abs(bytes) >= step && u < units.length - 1); |
| |
| return bytes.toString() + units[u]; |
| } |
| |
| window.runTestSuite = () => { |
| Plotly.purge("round_trip_avg_chart"); |
| Plotly.purge("box_plot_chart"); |
| setSettingsState(true); |
| const totalSamples = parseInt( |
| document.getElementById("sSamples").value |
| ); |
| execQueuedTests(totalSamples); |
| }; |
| |
| const totalSamples = parseInt(document.getElementById("sSamples").value); |
| queueTest("Empty String", 0, 0, sendString); |
| queueTest("Empty Binary", 0, 0, sendArrayBuffer); |
| for (let byteSize = 8; byteSize <= 512 * 1024; byteSize *= 2) { |
| // Byte size of a string is twice its length because of UTF-16 encoding |
| const stringLen = byteSize / 2; |
| queueTest( |
| humanFileSize(byteSize) + " String", |
| byteSize, |
| stringLen, |
| sendString |
| ); |
| queueTest( |
| humanFileSize(byteSize) + " Binary", |
| byteSize, |
| byteSize, |
| sendArrayBuffer |
| ); |
| } |
| resetTestsResults(totalSamples); |
| </script> |
| </body> |
| </html> |