HTTP/2 vs HTTP/3 Latency Modeler
function calcH2(rttMs, lossFrac, bwBps, streams, pageBytes) {
const mss = 1460; // bytes
const rttS = rttMs / 1000;
// Mathis throughput per stream
const mathisBps = lossFrac > 0
? Math.min((mss / rttS) * (1 / Math.sqrt(lossFrac)), bwBps / streams)
: bwBps / streams;
const totalBw = mathisBps * Math.min(streams, 6); // browser typically 6 TCP connections for HTTP/2
// Connection setup: 1-RTT TCP + 1-RTT TLS1.3
const setupRtts = 2;
const transferS = pageBytes / totalBw;
// HoL blocking penalty: estimated as avg retransmit timeout * loss probability per stream
const rtoMs = Math.max(rttMs * 1.5, 200); // RTO ~= 1.5 RTT, min 200ms
const holPenaltyS = (lossFrac * rtoMs / 1000) * streams;
const totalS = setupRtts * rttS + transferS + holPenaltyS;
return { totalMs: totalS * 1000, throughputMbps: (totalBw / 1e6), setupMs: setupRtts * rttMs, transferMs: transferS * 1000, holMs: holPenaltyS * 1000 };
}
function calcH3(rttMs, lossFrac, bwBps, streams, pageBytes) {
const mss = 1200; // QUIC uses smaller initial PMTU
const rttS = rttMs / 1000;
// QUIC per-stream Mathis, but no HoL blocking -- each stream recovers independently
const mathisPerStream = lossFrac > 0
? Math.min((mss / rttS) * (1 / Math.sqrt(lossFrac)), bwBps / streams)
: bwBps / streams;
const totalBw = mathisPerStream * streams; // all streams can progress independently
// 0-RTT resumption for repeat visitors, 1-RTT for new
const setupRtts = 1;
const transferS = pageBytes / totalBw;
// No HoL -- only the one stream with the lost packet stalls briefly
const rtoMs = Math.max(rttMs * 1.5, 200);
const holPenaltyS = (lossFrac * rtoMs / 1000) * 1; // only 1 stream stalls
const totalS = setupRtts * rttS + transferS + holPenaltyS;
return { totalMs: totalS * 1000, throughputMbps: (totalBw / 1e6), setupMs: setupRtts * rttMs, transferMs: transferS * 1000, holMs: holPenaltyS * 1000 };
}
function model() {
const rttMs = parseFloat(document.getElementById('rttMs').value);
const lossPct = parseFloat(document.getElementById('lossRate').value);
const bwMbps = parseFloat(document.getElementById('bwMbps').value);
const streams = parseInt(document.getElementById('streams').value);
const pageKb = parseFloat(document.getElementById('pageKb').value);
const div = document.getElementById('modelResult');
div.style.display = 'block';
if (isNaN(rttMs) || isNaN(lossPct) || isNaN(bwMbps) || rttMs <= 0 || bwMbps <= 0) {
div.innerHTML = 'Invalid input.';
return;
}
const lossFrac = lossPct / 100;
const bwBps = bwMbps * 1e6;
const pageBytes = pageKb * 1024;
const h2 = calcH2(rttMs, lossFrac, bwBps, streams, pageBytes);
const h3 = calcH3(rttMs, lossFrac, bwBps, streams, pageBytes);
const winner = h2.totalMs < h3.totalMs * 0.95 ? 'h2'
: h3.totalMs < h2.totalMs * 0.95 ? 'h3' : 'tie';
const fmt = n => n < 1000 ? `${Math.round(n)} ms` : `${(n/1000).toFixed(2)} s`;
div.innerHTML = `
`;
}
function modelCurve() {
const rttMs = parseFloat(document.getElementById('rttMs').value) || 80;
const bwMbps = parseFloat(document.getElementById('bwMbps').value) || 10;
const streams = parseInt(document.getElementById('streams').value) || 6;
const pageKb = parseFloat(document.getElementById('pageKb').value) || 500;
const bwBps = bwMbps * 1e6;
const pageBytes = pageKb * 1024;
const lossPoints = [0, 0.1, 0.5, 1, 2, 3, 5, 10];
const div = document.getElementById('lossCurve');
div.style.display = 'block';
const maxMs = Math.max(...lossPoints.map(l => {
const h2 = calcH2(rttMs, l/100, bwBps, streams, pageBytes);
const h3 = calcH3(rttMs, l/100, bwBps, streams, pageBytes);
return Math.max(h2.totalMs, h3.totalMs);
}));
const rows = lossPoints.map(l => {
const h2 = calcH2(rttMs, l/100, bwBps, streams, pageBytes);
const h3 = calcH3(rttMs, l/100, bwBps, streams, pageBytes);
const h2w = Math.round((h2.totalMs / maxMs) * 100);
const h3w = Math.round((h3.totalMs / maxMs) * 100);
const fmt = n => n < 1000 ? `${Math.round(n)}ms` : `${(n/1000).toFixed(1)}s`;
const winnerLbl = h2.totalMs < h3.totalMs * 0.95 ? '< H2' : h3.totalMs < h2.totalMs * 0.95 ? 'H3 >' : '~tie';
return `
Latency Model Results
HTTP/2 (TCP)
Total page load${fmt(h2.totalMs)}
Effective throughput${h2.throughputMbps.toFixed(2)} Mbps
Handshake overhead${fmt(h2.setupMs)}
Transfer time${fmt(h2.transferMs)}
HoL blocking penalty${fmt(h2.holMs)}
HTTP/3 (QUIC)
Total page load${fmt(h3.totalMs)}
Effective throughput${h3.throughputMbps.toFixed(2)} Mbps
Handshake overhead${fmt(h3.setupMs)}
Transfer time${fmt(h3.transferMs)}
HoL blocking penalty${fmt(h3.holMs)}
Model uses Mathis equation for TCP throughput. HoL blocking penalty: HTTP/2 estimates RTO × loss × streams; HTTP/3 only 1 stream stalls. Actual results vary by congestion algorithm, QUIC implementation, and network conditions.
${l}%
${fmt(h2.totalMs)}
${fmt(h3.totalMs)}
${winnerLbl}
`;
}).join('');
div.innerHTML = `
Loss %
HTTP/2
HTTP/3
${rows}`;
}