提交 567bf6ed 编写于 作者: P Peng Lyu

Refactor benchmark utilies.

上级 d0e03d1f
......@@ -2,7 +2,10 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ITextBufferBuilder, ITextBufferFactory, ITextBuffer, DefaultEndOfLine } from 'vs/editor/common/model';
import { LinesTextBufferBuilder } from 'vs/editor/common/model/linesTextBuffer/linesTextBufferBuilder';
import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder';
export function doBenchmark<T>(id: string, ts: T[], fn: (t: T) => void) {
let columns: string[] = [id];
......@@ -13,4 +16,55 @@ export function doBenchmark<T>(id: string, ts: T[], fn: (t: T) => void) {
columns.push(`${(diff[0] * 1000 + diff[1] / 1000000).toFixed(3)} ms`);
}
console.log('|' + columns.join('\t|') + '|');
}
\ No newline at end of file
}
export interface IBenchmark {
name: string;
/**
* Before each cycle, this function will be called to create TextBufferFactory
*/
buildBuffer: (textBufferBuilder: ITextBufferBuilder) => ITextBufferFactory;
/**
* Before each cycle, this function will be called to do pre-work for text buffer.
* This will be called onece `buildBuffer` is finished.
*/
preCycle: (textBuffer: ITextBuffer) => void;
/**
* The function we are benchmarking
*/
fn: (textBuffer: ITextBuffer) => void;
}
export class BenchmarkSuite {
name: string;
benchmarks: IBenchmark[];
constructor(suiteOptions: { name: string }) {
this.name = suiteOptions.name;
this.benchmarks = [];
}
add(benchmark: IBenchmark) {
this.benchmarks.push(benchmark);
}
run() {
console.log(`|${this.name}\t|line buffer\t|piece table\t|`);
console.log('|---|---|---|');
for (let i = 0; i < this.benchmarks.length; i++) {
let benchmark = this.benchmarks[i];
let columns: string[] = [benchmark.name];
[new LinesTextBufferBuilder(), new PieceTreeTextBufferBuilder()].forEach((builder: ITextBufferBuilder) => {
let factory = benchmark.buildBuffer(builder);
let buffer = factory.create(DefaultEndOfLine.LF);
benchmark.preCycle(buffer);
var start = process.hrtime();
benchmark.fn(buffer);
var diff = process.hrtime(start);
columns.push(`${(diff[0] * 1000 + diff[1] / 1000000).toFixed(3)} ms`);
});
console.log('|' + columns.join('\t|') + '|');
}
console.log('\n');
}
}
......@@ -2,45 +2,14 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { LinesTextBufferBuilder } from 'vs/editor/common/model/linesTextBuffer/linesTextBufferBuilder';
import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder';
import { ITextBuffer, IIdentifiedSingleEditOperation, EndOfLinePreference } from 'vs/editor/common/model';
import { generateRandomEdits, createMockBuffer, createMockText, generateSequentialInserts } from 'vs/editor/test/common/model/linesTextBuffer/textBufferAutoTestUtils';
import { ITextBufferBuilder, EndOfLinePreference } from 'vs/editor/common/model';
import { BenchmarkSuite } from 'vs/editor/test/common/model/benchmark/benchmarkUtils';
import { generateRandomChunkWithLF, generateRandomEdits, generateSequentialInserts, getRandomInt } from 'vs/editor/test/common/model/linesTextBuffer/textBufferAutoTestUtils';
import { Range } from 'vs/editor/common/core/range';
import { doBenchmark } from 'vs/editor/test/common/model/benchmark/benchmarkUtils';
let readLinesBenchmark = function (id: string, buffers: ITextBuffer[]) {
doBenchmark(id, buffers, (buffer) => {
for (let j = 0, len = buffer.getLineCount(); j < len; j++) {
var str = buffer.getLineContent(j + 1);
let firstChar = str.charCodeAt(0);
let lastChar = str.charCodeAt(str.length - 1);
firstChar = firstChar - lastChar;
lastChar = firstChar + lastChar;
firstChar = lastChar - firstChar;
}
});
};
let appyEditsBenchmark = function (id: string, buffers: ITextBuffer[], edits: IIdentifiedSingleEditOperation[]) {
doBenchmark(id, buffers, (buffer) => {
for (let j = 0; j < edits.length; j++) {
buffer.applyEdits([edits[j]], false);
}
});
};
let getValueBenchmark = function (id: string, buffers: ITextBuffer[], eol: EndOfLinePreference = EndOfLinePreference.LF): void {
doBenchmark(id, buffers, (buffer) => {
const lineCount = buffer.getLineCount();
const fullModelRange = new Range(1, 1, lineCount, buffer.getLineLength(lineCount) + 1);
buffer.getValueInRange(fullModelRange, eol);
});
};
let suites = [
let fileSizes = [1, 1000, 64 * 1000, 32 * 1000 * 1000];
let editTypes = [
{
id: 'random edits',
generateEdits: generateRandomEdits
......@@ -51,18 +20,119 @@ let suites = [
}
];
let text = createMockText(1000, 0, 50);
for (let fileSize of fileSizes) {
let chunks = [];
let chunkCnt = Math.floor(fileSize / (64 * 1000));
if (chunkCnt === 0) {
chunks.push(generateRandomChunkWithLF(fileSize, fileSize));
} else {
let chunk = generateRandomChunkWithLF(64 * 1000, 64 * 1000);
// try to avoid OOM
for (let j = 0; j < chunkCnt; j++) {
chunks.push(Buffer.from(chunk + j).toString());
}
}
for (let editType of editTypes) {
const edits = editType.generateEdits(chunks, 1000);
let editsSuite = new BenchmarkSuite({
name: `File Size: ${fileSize}Byte, ${editType.id}`,
});
for (let i of [10, 100, 1000]) {
editsSuite.add({
name: `apply ${i} edits`,
buildBuffer: (textBufferBuilder: ITextBufferBuilder) => {
chunks.forEach(ck => textBufferBuilder.acceptChunk(ck));
return textBufferBuilder.finish();
},
preCycle: (textBuffer) => {
return textBuffer;
},
fn: (textBuffer) => {
// for line model, this loop doesn't reflect the real situation.
for (let k = 0; k < edits.length && k < i; k++) {
textBuffer.applyEdits([edits[k]], false);
}
}
});
for (let i = 0, len = suites.length; i < len; i++) {
console.log(`\n|${suites[i].id}\t|line buffer\t|piece table\t|`);
console.log('|---|---|---|');
for (let j of [10, 100, 1000]) {
let linesTextBuffer = createMockBuffer(text, new LinesTextBufferBuilder());
let pieceTreeTextBuffer = createMockBuffer(text, new PieceTreeTextBufferBuilder());
let edits = suites[i].generateEdits(text, j);
editsSuite.add({
name: `Read all lines after ${i} edits`,
buildBuffer: (textBufferBuilder: ITextBufferBuilder) => {
chunks.forEach(ck => textBufferBuilder.acceptChunk(ck));
return textBufferBuilder.finish();
},
preCycle: (textBuffer) => {
for (let k = 0; k < edits.length && k < i; k++) {
textBuffer.applyEdits([edits[k]], false);
}
return textBuffer;
},
fn: (textBuffer) => {
for (let j = 0, len = textBuffer.getLineCount(); j < len; j++) {
var str = textBuffer.getLineContent(j + 1);
let firstChar = str.charCodeAt(0);
let lastChar = str.charCodeAt(str.length - 1);
firstChar = firstChar - lastChar;
lastChar = firstChar + lastChar;
firstChar = lastChar - firstChar;
}
}
});
editsSuite.add({
name: `Read 10 random windows after ${i} edits`,
buildBuffer: (textBufferBuilder: ITextBufferBuilder) => {
chunks.forEach(ck => textBufferBuilder.acceptChunk(ck));
return textBufferBuilder.finish();
},
preCycle: (textBuffer) => {
for (let k = 0; k < edits.length && k < i; k++) {
textBuffer.applyEdits([edits[k]], false);
}
return textBuffer;
},
fn: (textBuffer) => {
for (let i = 0; i < 10; i++) {
let minLine = 1;
let maxLine = textBuffer.getLineCount();
let startLine = getRandomInt(minLine, Math.max(minLine, maxLine - 100));
let endLine = Math.min(maxLine, startLine + 100);
for (let j = startLine; j < endLine; j++) {
var str = textBuffer.getLineContent(j + 1);
let firstChar = str.charCodeAt(0);
let lastChar = str.charCodeAt(str.length - 1);
firstChar = firstChar - lastChar;
lastChar = firstChar + lastChar;
firstChar = lastChar - firstChar;
}
}
}
});
editsSuite.add({
name: `save file after ${i} edits`,
buildBuffer: (textBufferBuilder: ITextBufferBuilder) => {
chunks.forEach(ck => textBufferBuilder.acceptChunk(ck));
return textBufferBuilder.finish();
},
preCycle: (textBuffer) => {
for (let k = 0; k < edits.length && k < i; k++) {
textBuffer.applyEdits([edits[k]], false);
}
return textBuffer;
},
fn: (textBuffer) => {
const lineCount = textBuffer.getLineCount();
const fullModelRange = new Range(1, 1, lineCount, textBuffer.getLineLength(lineCount) + 1);
textBuffer.getValueInRange(fullModelRange, EndOfLinePreference.LF);
}
});
}
appyEditsBenchmark(`apply ${j} edits`, [linesTextBuffer, pieceTreeTextBuffer], edits);
readLinesBenchmark(`getLineContent after ${j} edits`, [linesTextBuffer, pieceTreeTextBuffer]);
getValueBenchmark(`save after ${j} edits`, [linesTextBuffer, pieceTreeTextBuffer]);
editsSuite.run();
}
}
\ No newline at end of file
......@@ -4,25 +4,47 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { LinesTextBufferBuilder } from 'vs/editor/common/model/linesTextBuffer/linesTextBufferBuilder';
import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder';
import { IIdentifiedSingleEditOperation, ITextBuffer } from 'vs/editor/common/model';
import { createMockText, createMockBuffer, generateRandomReplaces } from 'vs/editor/test/common/model/linesTextBuffer/textBufferAutoTestUtils';
import { doBenchmark } from 'vs/editor/test/common/model/benchmark/benchmarkUtils';
let appyEditsBenchmark = function (id: string, buffers: ITextBuffer[], edits: IIdentifiedSingleEditOperation[]) {
doBenchmark(id, buffers, buffer => {
buffer.applyEdits(edits, false);
import { ITextBufferBuilder } from 'vs/editor/common/model';
import { generateRandomReplaces, generateRandomChunkWithLF } from 'vs/editor/test/common/model/linesTextBuffer/textBufferAutoTestUtils';
import { BenchmarkSuite } from 'vs/editor/test/common/model/benchmark/benchmarkUtils';
let fileSizes = [1, 1000, 64 * 1000, 32 * 1000 * 1000];
for (let fileSize of fileSizes) {
let chunks = [];
let chunkCnt = Math.floor(fileSize / (64 * 1000));
if (chunkCnt === 0) {
chunks.push(generateRandomChunkWithLF(fileSize, fileSize));
} else {
let chunk = generateRandomChunkWithLF(64 * 1000, 64 * 1000);
// try to avoid OOM
for (let j = 0; j < chunkCnt; j++) {
chunks.push(Buffer.from(chunk + j).toString());
}
}
let replaceSuite = new BenchmarkSuite({
name: `File Size: ${fileSize}Byte`,
});
};
let text = createMockText(1000, 50, 100);
let edits = generateRandomReplaces(chunks, 500, 5, 10);
for (let i of [10, 100, 500]) {
replaceSuite.add({
name: `replace ${i} occurrences`,
buildBuffer: (textBufferBuilder: ITextBufferBuilder) => {
chunks.forEach(ck => textBufferBuilder.acceptChunk(ck));
return textBufferBuilder.finish();
},
preCycle: (textBuffer) => {
return textBuffer;
},
fn: (textBuffer) => {
textBuffer.applyEdits(edits.slice(0, i), false);
}
});
}
console.log(`\n|replace all\t|line buffer\t|piece table\t|`);
console.log('|---|---|---|');
for (let i of [10, 100, 500, 1000]) {
let linesTextBuffer = createMockBuffer(text, new LinesTextBufferBuilder());
let pieceTreeTextBuffer = createMockBuffer(text, new PieceTreeTextBufferBuilder());
let edits = generateRandomReplaces(text, i, 5, 10);
appyEditsBenchmark(`replace ${i} occurrences`, [linesTextBuffer, pieceTreeTextBuffer], edits);
replaceSuite.run();
}
\ No newline at end of file
......@@ -32,14 +32,24 @@ export function getRandomString(minLength: number, maxLength: number): string {
return r;
}
export function generateRandomEdits(str: string, editCnt: number): IIdentifiedSingleEditOperation[] {
let lines = str.split(/\r\n|\r|\n/);
export function generateRandomEdits(chunks: string[], editCnt: number): IIdentifiedSingleEditOperation[] {
let lines = [];
for (let i = 0; i < chunks.length; i++) {
let newLines = chunks[i].split(/\r\n|\r|\n/);
if (lines.length === 0) {
lines.push(...newLines);
} else {
newLines[0] = lines[lines.length - 1] + newLines[0];
lines.splice(lines.length - 1, 1, ...newLines);
}
}
let ops: IIdentifiedSingleEditOperation[] = [];
for (let i = 0; i < editCnt; i++) {
let line = getRandomInt(1, lines.length);
let startColumn = getRandomInt(1, lines[line - 1].length + 1);
let endColumn = getRandomInt(startColumn, lines[line - 1].length + 1);
let startColumn = getRandomInt(1, Math.max(lines[line - 1].length, 1));
let endColumn = getRandomInt(startColumn, Math.max(lines[line - 1].length, startColumn));
let text: string = '';
if (Math.random() < .5) {
text = getRandomString(5, 10);
......@@ -55,8 +65,18 @@ export function generateRandomEdits(str: string, editCnt: number): IIdentifiedSi
return ops;
}
export function generateSequentialInserts(str: string, editCnt: number): IIdentifiedSingleEditOperation[] {
let lines = str.split(/\r\n|\r|\n/);
export function generateSequentialInserts(chunks: string[], editCnt: number): IIdentifiedSingleEditOperation[] {
let lines = [];
for (let i = 0; i < chunks.length; i++) {
let newLines = chunks[i].split(/\r\n|\r|\n/);
if (lines.length === 0) {
lines.push(...newLines);
} else {
newLines[0] = lines[lines.length - 1] + newLines[0];
lines.splice(lines.length - 1, 1, ...newLines);
}
}
let ops: IIdentifiedSingleEditOperation[] = [];
for (let i = 0; i < editCnt; i++) {
......@@ -67,7 +87,7 @@ export function generateSequentialInserts(str: string, editCnt: number): IIdenti
text = '\n';
lines.push('');
} else {
text = getRandomString(5, 10);
text = getRandomString(1, 2);
lines[line - 1] += text;
}
......@@ -80,8 +100,18 @@ export function generateSequentialInserts(str: string, editCnt: number): IIdenti
return ops;
}
export function generateRandomReplaces(str: string, editCnt: number, searchStringLen: number, replaceStringLen: number): IIdentifiedSingleEditOperation[] {
let lines = str.split(/\r\n|\r|\n/);
export function generateRandomReplaces(chunks: string[], editCnt: number, searchStringLen: number, replaceStringLen: number): IIdentifiedSingleEditOperation[] {
let lines = [];
for (let i = 0; i < chunks.length; i++) {
let newLines = chunks[i].split(/\r\n|\r|\n/);
if (lines.length === 0) {
lines.push(...newLines);
} else {
newLines[0] = lines[lines.length - 1] + newLines[0];
lines.splice(lines.length - 1, 1, ...newLines);
}
}
let ops: IIdentifiedSingleEditOperation[] = [];
let chunkSize = Math.max(1, Math.floor(lines.length / editCnt));
let chunkCnt = Math.floor(lines.length / chunkSize);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册