提交 6c357604 编写于 作者: A Alex Dima

Adopt some ES6 datastructures to improve memory consumption

上级 4056cfc2
......@@ -45,12 +45,3 @@ export function countToArray(fromOrTo: number, to?: number): number[] {
return result;
}
export const enum Constants {
/**
* MAX SMI (SMall Integer) as defined in v8.
* one bit is lost for boxing/unboxing flag.
* one bit is lost for sign flag.
*/
MAX_SAFE_SMALL_INTEGER = 1 << 30,
}
......@@ -4,49 +4,56 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { createMap, Map } from 'vs/editor/common/core/map';
import { toUint8 } from 'vs/editor/common/core/uint';
/**
* A fast character classifier that uses a compact array for ASCII values.
*/
export class CharacterClassifier<T> {
export class CharacterClassifier<T extends number> {
/**
* Maintain a compact (fully initialized ASCII map for quickly classifying ASCII characters - used more often in code).
*/
private _asciiMap: T[];
private _asciiMap: Uint8Array;
/**
* The entire map (sparse array).
*/
private _map: T[];
private _map: Map<number, number>;
private _defaultValue: number;
private _defaultValue: T;
constructor(_defaultValue: T) {
let defaultValue = toUint8(_defaultValue);
constructor(defaultValue: T) {
this._defaultValue = defaultValue;
this._asciiMap = CharacterClassifier._createAsciiMap(defaultValue);
this._map = [];
this._map = createMap<number, number>();
}
private static _createAsciiMap<T>(defaultValue: T): T[] {
let asciiMap: T[] = [];
private static _createAsciiMap(defaultValue: number): Uint8Array {
let asciiMap: Uint8Array = new Uint8Array(256);
for (let i = 0; i < 256; i++) {
asciiMap[i] = defaultValue;
}
return asciiMap;
}
public set(charCode: number, value: T): void {
public set(charCode: number, _value: T): void {
let value = toUint8(_value);
if (charCode >= 0 && charCode < 256) {
this._asciiMap[charCode] = value;
} else {
this._map[charCode] = value;
this._map.set(charCode, value);
}
}
public get(charCode: number): T {
if (charCode >= 0 && charCode < 256) {
return this._asciiMap[charCode];
return <T>this._asciiMap[charCode];
} else {
return this._map[charCode] || this._defaultValue;
return <T>(this._map.get(charCode) || this._defaultValue);
}
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
export interface Map<K, V> {
clear(): void;
delete(key: K): boolean;
forEach(callbackfn: (value: V, index: K, map: Map<K, V>) => void, thisArg?: any): void;
get(key: K): V;
has(key: K): boolean;
set(key: K, value?: V): Map<K, V>;
size: number;
// not supported on IE11:
// entries(): IterableIterator<[K, V]>;
// keys(): IterableIterator<K>;
// values(): IterableIterator<V>;
// [Symbol.iterator]():IterableIterator<[K,V]>;
// [Symbol.toStringTag]: string;
}
interface MapConstructor {
new <K, V>(): Map<K, V>;
prototype: Map<any, any>;
// not supported on IE11:
// new <K, V>(iterable: Iterable<[K, V]>): Map<K, V>;
}
declare var Map: MapConstructor;
export function createMap<K, V>(): Map<K, V> {
return new Map<K, V>();
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
export interface Set<T> {
add(value: T): Set<T>;
clear(): void;
delete(value: T): boolean;
forEach(callbackfn: (value: T, index: T, set: Set<T>) => void, thisArg?: any): void;
has(value: T): boolean;
size: number;
// not supported on IE11:
// entries(): IterableIterator<[T, T]>;
// keys(): IterableIterator<T>;
// values(): IterableIterator<T>;
// [Symbol.iterator]():IterableIterator<T>;
// [Symbol.toStringTag]: string;
}
interface SetConstructor {
new <T>(): Set<T>;
prototype: Set<any>;
// not supported on IE11:
// new <T>(iterable: Iterable<T>): Set<T>;
}
declare var Set: SetConstructor;
export function createSet<T>(): Set<T> {
return new Set<T>();
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
export class Uint8Matrix {
private _data: Uint8Array;
private _rows: number;
private _cols: number;
constructor(rows: number, cols: number, defaultValue: number) {
let data = new Uint8Array(rows * cols);
for (let i = 0, len = rows * cols; i < len; i++) {
data[i] = defaultValue;
}
this._data = data;
this._rows = rows;
this._cols = cols;
}
public get(row: number, col: number): number {
return this._data[row * this._cols + col];
}
public set(row: number, col: number, value: number): void {
this._data[row * this._cols + col] = value;
}
}
export const enum Constants {
/**
* MAX SMI (SMall Integer) as defined in v8.
* one bit is lost for boxing/unboxing flag.
* one bit is lost for sign flag.
*/
MAX_SAFE_SMALL_INTEGER = 1 << 30,
/**
* Max unsigned integer that fits on 8 bits.
*/
MAX_UINT_8 = 255, // 2^8 - 1
/**
* Max unsigned integer that fits on 16 bits.
*/
MAX_UINT_16 = 65535, // 2^16 - 1
/**
* Max unsigned integer that fits on 32 bits.
*/
MAX_UINT_32 = 4294967295, // 2^32 - 1
}
export function toUint8(v: number): number {
if (v < 0) {
return 0;
}
if (v > Constants.MAX_UINT_8) {
return Constants.MAX_UINT_8;
}
return v | 0;
}
export function toUint32(v: number): number {
if (v < 0) {
return 0;
}
if (v > Constants.MAX_UINT_32) {
return Constants.MAX_UINT_32;
}
return v | 0;
}
export function toUint32Array(arr: number[]): Uint32Array {
let len = arr.length;
let r = new Uint32Array(len);
for (let i = 0; i < len; i++) {
r[i] = toUint32(arr[i]);
}
return r;
}
......@@ -618,12 +618,12 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
// Lines in the middle
let newLinesContent: string[] = [];
let newLinesLengths: number[] = [];
let newLinesLengths = new Uint32Array(insertingLinesCnt - editingLinesCnt);
for (let j = editingLinesCnt + 1; j <= insertingLinesCnt; j++) {
let newLineNumber = startLineNumber + j;
this._lines.splice(newLineNumber - 1, 0, new ModelLine(newLineNumber, op.lines[j], tabSize));
newLinesContent.push(op.lines[j]);
newLinesLengths.push(op.lines[j].length + this._EOL.length);
newLinesLengths[j - editingLinesCnt - 1] = op.lines[j].length + this._EOL.length;
}
newLinesContent[newLinesContent.length - 1] += leftoverLine.text;
if (this._lineStarts) {
......
......@@ -67,10 +67,11 @@ export class MirrorModel2 {
protected _ensureLineStarts(): void {
if (!this._lineStarts) {
const lineStartValues: number[] = [];
const eolLength = this._eol.length;
for (let i = 0, len = this._lines.length; i < len; i++) {
lineStartValues.push(this._lines[i].length + eolLength);
const linesLength = this._lines.length;
const lineStartValues = new Uint32Array(linesLength);
for (let i = 0; i < linesLength; i++) {
lineStartValues[i] = this._lines[i].length + eolLength;
}
this._lineStarts = new PrefixSumComputer(lineStartValues);
}
......@@ -142,7 +143,7 @@ export class MirrorModel2 {
);
// Insert new lines & store lengths
let newLengths: number[] = new Array<number>(insertLines.length - 1);
let newLengths = new Uint32Array(insertLines.length - 1);
for (let i = 1; i < insertLines.length; i++) {
this._lines.splice(position.lineNumber + i - 1, 0, insertLines[i]);
newLengths[i - 1] = insertLines[i].length + this._eol.length;
......
......@@ -196,10 +196,11 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo
private _ensureLineStarts(): void {
if (!this._lineStarts) {
const lineStartValues: number[] = [];
const eolLength = this._EOL.length;
for (let i = 0, len = this._lines.length; i < len; i++) {
lineStartValues.push(this._lines[i].text.length + eolLength);
const linesLength = this._lines.length;
const lineStartValues = new Uint32Array(linesLength);
for (let i = 0; i < linesLength; i++) {
lineStartValues[i] = this._lines[i].text.length + eolLength;
}
this._lineStarts = new PrefixSumComputer(lineStartValues);
}
......
......@@ -7,6 +7,7 @@
import { ILink } from 'vs/editor/common/modes';
import { CharCode } from 'vs/base/common/charCode';
import { CharacterClassifier } from 'vs/editor/common/core/characterClassifier';
import { Uint8Matrix } from 'vs/editor/common/core/uint';
export interface ILinkComputerTarget {
getLineCount(): number;
......@@ -34,7 +35,7 @@ type Edge = [State, number, State];
class StateMachine {
private _states: State[][];
private _states: Uint8Matrix;
private _maxCharCode: number;
constructor(edges: Edge[]) {
......@@ -53,18 +54,13 @@ class StateMachine {
}
}
let states: number[][] = [];
for (let i = 0; i <= maxState; i++) {
let tmp: number[] = [];
for (let j = 0; j <= maxCharCode; j++) {
tmp[j] = State.Invalid;
}
states[i] = tmp;
}
maxCharCode++;
maxState++;
let states = new Uint8Matrix(maxState, maxCharCode, State.Invalid);
for (let i = 0, len = edges.length; i < len; i++) {
let [from, chCode, to] = edges[i];
states[from][chCode] = to;
states.set(from, chCode, to);
}
this._states = states;
......@@ -72,48 +68,55 @@ class StateMachine {
}
public nextState(currentState: State, chCode: number): State {
if (chCode < 0 || chCode > this._maxCharCode) {
if (chCode < 0 || chCode >= this._maxCharCode) {
return State.Invalid;
}
return this._states[currentState][chCode];
return this._states.get(currentState, chCode);
}
}
// State machine for http:// or https:// or file://
let stateMachine = new StateMachine([
[State.Start, CharCode.h, State.H],
[State.Start, CharCode.H, State.H],
[State.Start, CharCode.f, State.F],
[State.Start, CharCode.F, State.F],
let _stateMachine: StateMachine = null;
function getStateMachine(): StateMachine {
if (_stateMachine === null) {
_stateMachine = new StateMachine([
[State.Start, CharCode.h, State.H],
[State.Start, CharCode.H, State.H],
[State.Start, CharCode.f, State.F],
[State.Start, CharCode.F, State.F],
[State.H, CharCode.t, State.HT],
[State.H, CharCode.T, State.HT],
[State.H, CharCode.t, State.HT],
[State.H, CharCode.T, State.HT],
[State.HT, CharCode.t, State.HTT],
[State.HT, CharCode.T, State.HTT],
[State.HT, CharCode.t, State.HTT],
[State.HT, CharCode.T, State.HTT],
[State.HTT, CharCode.p, State.HTTP],
[State.HTT, CharCode.P, State.HTTP],
[State.HTT, CharCode.p, State.HTTP],
[State.HTT, CharCode.P, State.HTTP],
[State.HTTP, CharCode.s, State.BeforeColon],
[State.HTTP, CharCode.S, State.BeforeColon],
[State.HTTP, CharCode.Colon, State.AfterColon],
[State.HTTP, CharCode.s, State.BeforeColon],
[State.HTTP, CharCode.S, State.BeforeColon],
[State.HTTP, CharCode.Colon, State.AfterColon],
[State.F, CharCode.i, State.FI],
[State.F, CharCode.I, State.FI],
[State.F, CharCode.i, State.FI],
[State.F, CharCode.I, State.FI],
[State.FI, CharCode.l, State.FIL],
[State.FI, CharCode.L, State.FIL],
[State.FI, CharCode.l, State.FIL],
[State.FI, CharCode.L, State.FIL],
[State.FIL, CharCode.e, State.BeforeColon],
[State.FIL, CharCode.E, State.BeforeColon],
[State.FIL, CharCode.e, State.BeforeColon],
[State.FIL, CharCode.E, State.BeforeColon],
[State.BeforeColon, CharCode.Colon, State.AfterColon],
[State.BeforeColon, CharCode.Colon, State.AfterColon],
[State.AfterColon, CharCode.Slash, State.AlmostThere],
[State.AfterColon, CharCode.Slash, State.AlmostThere],
[State.AlmostThere, CharCode.Slash, State.End],
]);
}
return _stateMachine;
}
[State.AlmostThere, CharCode.Slash, State.End],
]);
const enum CharacterClass {
None = 0,
......@@ -163,6 +166,8 @@ class LinkComputer {
}
public static computeLinks(model: ILinkComputerTarget): ILink[] {
const stateMachine = getStateMachine();
let result: ILink[] = [];
for (let i = 1, lineCount = model.getLineCount(); i <= lineCount; i++) {
const line = model.getLineContent(i);
......
......@@ -10,6 +10,7 @@ import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer'
import { ILineMapperFactory, ILineMapping, OutputPosition } from 'vs/editor/common/viewModel/splitLinesCollection';
import { CharCode } from 'vs/base/common/charCode';
import { CharacterClassifier } from 'vs/editor/common/core/characterClassifier';
import { toUint32Array } from 'vs/editor/common/core/uint';
const enum CharacterClass {
NONE = 0,
......@@ -236,7 +237,10 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor
// Add last segment
breakingLengths[breakingLengthsIndex++] = len - lastBreakingOffset;
return new CharacterHardWrappingLineMapping(new PrefixSumComputer(breakingLengths), wrappedTextIndent);
return new CharacterHardWrappingLineMapping(
new PrefixSumComputer(toUint32Array(breakingLengths)),
wrappedTextIndent
);
}
}
......
......@@ -4,6 +4,8 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { toUint32 } from 'vs/editor/common/core/uint';
export class PrefixSumIndexOfResult {
_prefixSumIndexOfResultBrand: void;
......@@ -21,24 +23,21 @@ export class PrefixSumComputer {
/**
* values[i] is the value at index i
*/
private values: number[];
private values: Uint32Array;
/**
* prefixSum[i] = SUM(heights[j]), 0 <= j <= i
*/
private prefixSum: number[];
private prefixSum: Uint32Array;
/**
* prefixSum[i], 0 <= i <= prefixSumValidIndex can be trusted
*/
private prefixSumValidIndex: number;
constructor(values: number[]) {
constructor(values: Uint32Array) {
this.values = values;
this.prefixSum = [];
for (let i = 0, len = this.values.length; i < len; i++) {
this.prefixSum[i] = 0;
}
this.prefixSum = new Uint32Array(values.length);
this.prefixSumValidIndex = -1;
}
......@@ -46,51 +45,34 @@ export class PrefixSumComputer {
return this.values.length;
}
public insertValue(insertIndex: number, value: number): void {
insertIndex = Math.floor(insertIndex); //@perf
value = Math.floor(value); //@perf
this.values.splice(insertIndex, 0, value);
this.prefixSum.splice(insertIndex, 0, 0);
if (insertIndex - 1 < this.prefixSumValidIndex) {
this.prefixSumValidIndex = insertIndex - 1;
}
}
public insertValues(insertIndex: number, insertValues: Uint32Array): void {
insertIndex = toUint32(insertIndex);
const oldValues = this.values;
const oldPrefixSum = this.prefixSum;
const insertValuesLen = insertValues.length;
public insertValues(insertIndex: number, values: number[]): void {
insertIndex = Math.floor(insertIndex); //@perf
if (values.length === 0) {
if (insertValuesLen === 0) {
return;
}
if (values.length === 1) {
// Fast path for one element
this.values.splice(insertIndex, 0, values[0]);
this.prefixSum.splice(insertIndex, 0, values[0]);
} else {
this.values = this.values.slice(0, insertIndex).concat(values).concat(this.values.slice(insertIndex));
this.prefixSum = this.prefixSum.slice(0, insertIndex).concat(PrefixSumComputer._zeroArray(values.length)).concat(this.prefixSum.slice(insertIndex));
}
this.values = new Uint32Array(oldValues.length + insertValuesLen);
this.values.set(oldValues.subarray(0, insertIndex), 0);
this.values.set(oldValues.subarray(insertIndex), insertIndex + insertValuesLen);
this.values.set(insertValues, insertIndex);
if (insertIndex - 1 < this.prefixSumValidIndex) {
this.prefixSumValidIndex = insertIndex - 1;
}
}
private static _zeroArray(count: number): number[] {
count = Math.floor(count); //@perf
let r: number[] = [];
for (let i = 0; i < count; i++) {
r[i] = 0;
this.prefixSum = new Uint32Array(this.values.length);
if (this.prefixSumValidIndex >= 0) {
this.prefixSum.set(oldPrefixSum.subarray(0, this.prefixSumValidIndex + 1));
}
return r;
}
public changeValue(index: number, value: number): void {
index = Math.floor(index); //@perf
value = Math.floor(value); //@perf
index = toUint32(index);
value = toUint32(value);
if (this.values[index] === value) {
return;
......@@ -102,14 +84,36 @@ export class PrefixSumComputer {
}
public removeValues(startIndex: number, cnt: number): void {
startIndex = Math.floor(startIndex); //@perf
cnt = Math.floor(cnt); //@perf
startIndex = toUint32(startIndex);
cnt = toUint32(cnt);
const oldValues = this.values;
const oldPrefixSum = this.prefixSum;
this.values.splice(startIndex, cnt);
this.prefixSum.splice(startIndex, cnt);
if (startIndex >= oldValues.length) {
return;
}
let maxCnt = oldValues.length - startIndex;
if (cnt >= maxCnt) {
cnt = maxCnt;
}
if (cnt === 0) {
return;
}
this.values = new Uint32Array(oldValues.length - cnt);
this.values.set(oldValues.subarray(0, startIndex), 0);
this.values.set(oldValues.subarray(startIndex + cnt), startIndex);
this.prefixSum = new Uint32Array(this.values.length);
if (startIndex - 1 < this.prefixSumValidIndex) {
this.prefixSumValidIndex = startIndex - 1;
}
if (this.prefixSumValidIndex >= 0) {
this.prefixSum.set(oldPrefixSum.subarray(0, this.prefixSumValidIndex + 1));
}
}
public getTotalValue(): number {
......@@ -120,11 +124,12 @@ export class PrefixSumComputer {
}
public getAccumulatedValue(index: number): number {
index = Math.floor(index); //@perf
if (index < 0) {
return 0;
}
index = toUint32(index);
if (index <= this.prefixSumValidIndex) {
return this.prefixSum[index];
}
......
......@@ -326,9 +326,9 @@ export class SplitLinesCollection implements ILinesCollection {
this.hiddenAreasIds = [];
}
let values: number[] = [];
let linesContent = this.model.getLinesContent();
let lineCount = linesContent.length;
let values = new Uint32Array(lineCount);
let hiddenAreas = this.hiddenAreasIds.map((areaId) => this.model.getDecorationRange(areaId)).sort(Range.compareRangesUsingStarts);
let hiddenAreaStart = 1, hiddenAreaEnd = 0;
......@@ -546,7 +546,7 @@ export class SplitLinesCollection implements ILinesCollection {
let totalOutputLineCount = 0;
let insertLines: ISplitLine[] = [];
let insertPrefixSumValues: number[] = [];
let insertPrefixSumValues = new Uint32Array(text.length);
for (let i = 0, len = text.length; i < len; i++) {
let line = createSplitLine(this.linePositionMapperFactory, text[i], this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent, !isInHiddenArea);
......@@ -554,7 +554,7 @@ export class SplitLinesCollection implements ILinesCollection {
let outputLineCount = line.getOutputLineCount();
totalOutputLineCount += outputLineCount;
insertPrefixSumValues.push(outputLineCount);
insertPrefixSumValues[i] = outputLineCount;
}
this.lines = this.lines.slice(0, fromLineNumber - 1).concat(insertLines).concat(this.lines.slice(fromLineNumber - 1));
......
......@@ -11,7 +11,7 @@ import { CharCode } from 'vs/base/common/charCode';
suite('CharacterClassifier', () => {
test('works', () => {
let classifier = new CharacterClassifier(0);
let classifier = new CharacterClassifier<number>(0);
assert.equal(classifier.get(-1), 0);
assert.equal(classifier.get(0), 0);
......
......@@ -6,13 +6,14 @@
import * as assert from 'assert';
import { PrefixSumComputer, PrefixSumIndexOfResult } from 'vs/editor/common/viewModel/prefixSumComputer';
import { toUint32Array } from 'vs/editor/common/core/uint';
suite('Editor ViewModel - PrefixSumComputer', () => {
test('PrefixSumComputer', () => {
let indexOfResult: PrefixSumIndexOfResult;
var psc = new PrefixSumComputer([1, 1, 2, 1, 3]);
var psc = new PrefixSumComputer(toUint32Array([1, 1, 2, 1, 3]));
assert.equal(psc.getTotalValue(), 8);
assert.equal(psc.getAccumulatedValue(-1), 0);
assert.equal(psc.getAccumulatedValue(0), 1);
......
......@@ -11,6 +11,7 @@ import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer'
import { ILineMapping, IModel, SplitLine, SplitLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection';
import { MockConfiguration } from 'vs/editor/test/common/mocks/mockConfiguration';
import { Model } from 'vs/editor/common/model/model';
import { toUint32Array } from 'vs/editor/common/core/uint';
suite('Editor ViewModel - SplitLinesCollection', () => {
test('SplitLine', () => {
......@@ -177,7 +178,10 @@ function createSplitLine(splitLengths: number[], wrappedLinesPrefix: string, isV
}
function createLineMapping(breakingLengths: number[], wrappedLinesPrefix: string): ILineMapping {
return new CharacterHardWrappingLineMapping(new PrefixSumComputer(breakingLengths), wrappedLinesPrefix);
return new CharacterHardWrappingLineMapping(
new PrefixSumComputer(toUint32Array(breakingLengths)),
wrappedLinesPrefix
);
}
function createModel(text: string): IModel {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册