Use shared empty-map sentinels with copy-on-write for scroll recycling, and an inlined _data flag scan for populated sparse maps (faster than per-cell function calls on master). Avoid slow Object.assign map copies. Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
114 lines
3.7 KiB
TypeScript
114 lines
3.7 KiB
TypeScript
/**
|
|
* Copyright (c) 2026 The xterm.js authors. All rights reserved.
|
|
* @license MIT
|
|
*/
|
|
|
|
import { perfContext, before, RuntimeCase } from 'xterm-benchmark';
|
|
import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
|
import { BufferLineStringCache } from 'common/buffer/BufferLineStringCache';
|
|
import { CellData } from 'common/buffer/CellData';
|
|
import { BgFlags, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, UnderlineStyle } from 'common/buffer/Constants';
|
|
|
|
const ITERATIONS = 500_000;
|
|
const stringCache = new BufferLineStringCache();
|
|
const nullCell = CellData.fromCharData([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
|
|
const yCell = CellData.fromCharData([0, 'y', 1, 'y'.charCodeAt(0)]);
|
|
|
|
function fillPlainY(line: BufferLine, cols: number): void {
|
|
for (let i = 0; i < cols; i++) {
|
|
line.setCellFromCodepoint(i, 'y'.charCodeAt(0), 1, DEFAULT_ATTR_DATA);
|
|
}
|
|
}
|
|
|
|
function setupScrollRecyclePair(cols: number): { recycled: BufferLine, blank: BufferLine } {
|
|
const blank = new BufferLine(stringCache, cols, nullCell, false);
|
|
const recycled = new BufferLine(stringCache, cols, yCell, false);
|
|
fillPlainY(recycled, cols);
|
|
return { recycled, blank };
|
|
}
|
|
|
|
/** Source line with combining chars and extended attrs spread across columns. */
|
|
function buildSparseSourceLine(cols: number): BufferLine {
|
|
const line = new BufferLine(stringCache, cols, nullCell, false);
|
|
for (let i = 0; i < cols; i++) {
|
|
if (i % 10 === 0) {
|
|
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
|
|
cell.extended.underlineStyle = UnderlineStyle.CURLY;
|
|
cell.bg |= BgFlags.HAS_EXTENDED;
|
|
line.setCell(i, cell);
|
|
} else if (i % 17 === 0) {
|
|
line.setCell(i, CellData.fromCharData([1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]));
|
|
} else {
|
|
line.setCellFromCodepoint(i, 'y'.charCodeAt(0), 1, DEFAULT_ATTR_DATA);
|
|
}
|
|
}
|
|
return line;
|
|
}
|
|
|
|
function setupCopyFromSparsePair(cols: number): { dest: BufferLine, src: BufferLine } {
|
|
const src = buildSparseSourceLine(cols);
|
|
const dest = new BufferLine(stringCache, cols, yCell, false);
|
|
fillPlainY(dest, cols);
|
|
return { dest, src };
|
|
}
|
|
|
|
perfContext('BufferLine.copyFrom (scroll recycle)', () => {
|
|
perfContext('cols=80', () => {
|
|
let recycled: BufferLine;
|
|
let blank: BufferLine;
|
|
before(() => {
|
|
({ recycled, blank } = setupScrollRecyclePair(80));
|
|
});
|
|
new RuntimeCase('', () => {
|
|
for (let i = 0; i < ITERATIONS; i++) {
|
|
recycled.copyFrom(blank);
|
|
}
|
|
return { payloadSize: ITERATIONS };
|
|
}, { fork: false }).showAverageRuntime();
|
|
});
|
|
|
|
perfContext('cols=279', () => {
|
|
let recycled: BufferLine;
|
|
let blank: BufferLine;
|
|
before(() => {
|
|
({ recycled, blank } = setupScrollRecyclePair(279));
|
|
});
|
|
new RuntimeCase('', () => {
|
|
for (let i = 0; i < ITERATIONS; i++) {
|
|
recycled.copyFrom(blank);
|
|
}
|
|
return { payloadSize: ITERATIONS };
|
|
}, { fork: false }).showAverageRuntime();
|
|
});
|
|
});
|
|
|
|
perfContext('BufferLine.copyFrom (sparse source: combined + extended attrs)', () => {
|
|
perfContext('cols=80', () => {
|
|
let dest: BufferLine;
|
|
let src: BufferLine;
|
|
before(() => {
|
|
({ dest, src } = setupCopyFromSparsePair(80));
|
|
});
|
|
new RuntimeCase('', () => {
|
|
for (let i = 0; i < ITERATIONS; i++) {
|
|
dest.copyFrom(src);
|
|
}
|
|
return { payloadSize: ITERATIONS };
|
|
}, { fork: false }).showAverageRuntime();
|
|
});
|
|
|
|
perfContext('cols=279', () => {
|
|
let dest: BufferLine;
|
|
let src: BufferLine;
|
|
before(() => {
|
|
({ dest, src } = setupCopyFromSparsePair(279));
|
|
});
|
|
new RuntimeCase('', () => {
|
|
for (let i = 0; i < ITERATIONS; i++) {
|
|
dest.copyFrom(src);
|
|
}
|
|
return { payloadSize: ITERATIONS };
|
|
}, { fork: false }).showAverageRuntime();
|
|
});
|
|
});
|