cef/tests/cefclient/resources/binary_transfer.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,&nbsp;ms
</td>
<td class="center" style="width: 8%">
Binary Round Trip Avg,&nbsp;ms
</td>
<td class="center" style="width: 10%">Relative Trip Difference</td>
<td class="center" style="width: 8%">String Speed,&nbsp;MB/s</td>
<td class="center" style="width: 8%">Binary Speed,&nbsp;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>