499 lines
14 KiB
HTML
499 lines
14 KiB
HTML
|
<!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>
|