515 lines
15 KiB
HTML
515 lines
15 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.34.0.min.js"></script>
|
|
<style>
|
|
body {
|
|
font-family: Tahoma, Serif;
|
|
font-size: 10pt;
|
|
background-color: white;
|
|
}
|
|
.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="round_trip_chart">
|
|
<!-- 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 = [];
|
|
let round_trip_plot_data = [];
|
|
|
|
function nextTestSuite(testIndex) {
|
|
const nextTestIndex = testIndex + 1;
|
|
setTimeout(execTestSuite, 0, nextTestIndex);
|
|
}
|
|
|
|
function generateString(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 generateArrayBuffer(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(request, testIndex) {
|
|
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);
|
|
round_trip_plot_data[testIndex].x.push(test.sample);
|
|
round_trip_plot_data[testIndex].y.push(roundTrip);
|
|
setTimeout(execTest, 0, testIndex);
|
|
};
|
|
|
|
window.cefQuery({
|
|
request: request,
|
|
onSuccess: onSuccess,
|
|
onFailure: reportError,
|
|
});
|
|
}
|
|
|
|
function sendArrayBuffer(request, testIndex) {
|
|
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);
|
|
round_trip_plot_data[testIndex].x.push(test.sample);
|
|
round_trip_plot_data[testIndex].y.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.request, 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 stringRoundTrip = {
|
|
x: [],
|
|
y: [],
|
|
type: "scatter",
|
|
name: "String",
|
|
};
|
|
|
|
let binaryRoundTrip = {
|
|
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,
|
|
});
|
|
|
|
stringRoundTrip.x.push(test.byteSize);
|
|
binaryRoundTrip.x.push(test.byteSize);
|
|
stringRoundTrip.y.push(avgRoundTrip);
|
|
binaryRoundTrip.y.push(avgRoundTripBin);
|
|
}
|
|
|
|
round_trip_avg_plot_data = [stringRoundTrip, binaryRoundTrip];
|
|
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 = [];
|
|
});
|
|
round_trip_plot_data.forEach((data) => {
|
|
data.x = [];
|
|
data.y = [];
|
|
});
|
|
}
|
|
|
|
function queueTest(name, byteSize, request, testFunc) {
|
|
const testIndex = tests.length;
|
|
test = {
|
|
name: name,
|
|
byteSize: byteSize,
|
|
index: testIndex,
|
|
sample: 0,
|
|
totalRoundTrip: 0,
|
|
request: request,
|
|
func: testFunc,
|
|
};
|
|
tests.push(test);
|
|
|
|
box_plot_test_data.push({
|
|
x: [],
|
|
type: "box",
|
|
boxpoints: "all",
|
|
name: name,
|
|
jitter: 0.3,
|
|
pointpos: -1.8,
|
|
});
|
|
|
|
round_trip_plot_data.push({
|
|
x: [],
|
|
y: [],
|
|
type: "scatter",
|
|
name: name,
|
|
});
|
|
}
|
|
|
|
function execTestSuite(testIndex) {
|
|
if (testIndex < tests.length) {
|
|
setTimeout(execTest, 0, 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));
|
|
|
|
Plotly.newPlot("round_trip_avg_chart", round_trip_avg_plot_data, {
|
|
title: "Average round trip, μs (Smaller Better)",
|
|
});
|
|
Plotly.newPlot("round_trip_chart", round_trip_plot_data, {
|
|
title: "Linear: Round Trip Time, μs",
|
|
});
|
|
Plotly.newPlot("box_plot_chart", box_plot_test_data, {
|
|
title: "Box plot: Round Trip Time, μs",
|
|
});
|
|
|
|
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");
|
|
Plotly.purge("round_trip_chart");
|
|
setSettingsState(true);
|
|
const totalSamples = parseInt(
|
|
document.getElementById("sSamples").value
|
|
);
|
|
execQueuedTests(totalSamples);
|
|
};
|
|
|
|
const totalSamples = parseInt(document.getElementById("sSamples").value);
|
|
|
|
queueTest("Empty String", 0, generateString(0), sendString);
|
|
queueTest("Empty Binary", 0, generateArrayBuffer(0), sendArrayBuffer);
|
|
for (let byteSize = 8; byteSize <= 512 * 1024; byteSize *= 4) {
|
|
// Byte size of a string is twice its length because of UTF-16 encoding
|
|
const stringLen = byteSize / 2;
|
|
queueTest(
|
|
humanFileSize(byteSize) + " String",
|
|
byteSize,
|
|
generateString(stringLen),
|
|
sendString
|
|
);
|
|
queueTest(
|
|
humanFileSize(byteSize) + " Binary",
|
|
byteSize,
|
|
generateArrayBuffer(byteSize),
|
|
sendArrayBuffer
|
|
);
|
|
}
|
|
resetTestsResults(totalSamples);
|
|
</script>
|
|
</body>
|
|
</html>
|