Skip to content
Snippets Groups Projects
Commit f9419bb4 authored by David Sehnal's avatar David Sehnal
Browse files

collections

parent a0ee98a7
Branches
Tags
No related merge requests found
import * as B from 'benchmark'
import ChunkedArray from '../utils/chunked-array'
function testNative(size: number) {
const xs = new Array(size);
for (let i = 0; i < size; i++) xs[i] = i * i;
return xs;
}
function testChunkedTyped(size: number, chunk: number, linear: boolean) {
const xs = ChunkedArray.create(s => new Int32Array(s), 1, chunk, linear);
for (let i = 0; i < size; i++) ChunkedArray.add(xs, i * i);
return ChunkedArray.compact(xs);
}
function testChunkedNative(size: number, chunk: number) {
const xs = ChunkedArray.create(s => [], 1, chunk, false);
for (let i = 0; i < size; i++) ChunkedArray.add(xs, i * i);
return ChunkedArray.compact(xs);
}
const suite = new B.Suite();
const N = 70000;
suite
.add('native', () => testNative(N))
.add('chunkedT 0.1k', () => testChunkedTyped(N, 100, false))
.add('chunkedT 4k', () => testChunkedTyped(N, 4096, false))
.add('chunkedT 4k lin', () => testChunkedTyped(N, 4096, true))
.add('chunkedT N / 2', () => testChunkedTyped(N, N / 2, false))
.add('chunkedT N', () => testChunkedTyped(N, N, false))
.add('chunkedT 2 * N', () => testChunkedTyped(N, 2 * N, false))
.add('chunkedN N', () => testChunkedNative(N, N))
.add('chunkedN 0.1k', () => testChunkedNative(N, 100))
.add('chunkedN N / 2', () => testChunkedNative(N, N / 2))
.add('chunkedN 2 * N', () => testChunkedNative(N, 2 * N))
.on('cycle', (e: any) => {
console.log(String(e.target));
})
.run();
//console.log(testChunkedTyped(10, 16));
...@@ -24,11 +24,6 @@ class RangeImpl implements Impl { ...@@ -24,11 +24,6 @@ class RangeImpl implements Impl {
size: number; size: number;
has(x: number) { return x >= this.min && x <= this.max; } has(x: number) { return x >= this.min && x <= this.max; }
indexOf(x: number) { return x >= this.min && x <= this.max ? x - this.min : -1; } indexOf(x: number) { return x >= this.min && x <= this.max ? x - this.min : -1; }
toArray() {
const ret = new Array(this.size);
for (let i = 0; i < this.size; i++) ret[i] = i + this.min;
return ret;
}
elementAt(i: number) { return this.min + i; } elementAt(i: number) { return this.min + i; }
elements() { return Iterator.Range(this.min, this.max); } elements() { return Iterator.Range(this.min, this.max); }
...@@ -54,6 +49,17 @@ class ArrayImpl implements Impl { ...@@ -54,6 +49,17 @@ class ArrayImpl implements Impl {
} }
namespace OrderedSet { namespace OrderedSet {
export function ofSingleton(value: number): OrderedSet { return new RangeImpl(value, value); }
export function ofRange(min: number, max: number): OrderedSet { return max < min ? Empty : new RangeImpl(min, max); }
/** It is the responsibility of the caller to ensure the array is sorted and contains unique values. */
export function ofSortedArray(xs: ArrayLike<number>): OrderedSet {
if (!xs.length) return Empty;
// check if the array is just a range
if (xs[xs.length - 1] - xs[0] + 1 === xs.length) return ofRange(xs[0], xs[xs.length - 1]);
return new ArrayImpl(xs);
}
export const Empty = new RangeImpl(0, -1);
export function isEmpty(a: OrderedSet) { return a.size === 0; } export function isEmpty(a: OrderedSet) { return a.size === 0; }
export function hashCode(a: OrderedSet) { export function hashCode(a: OrderedSet) {
...@@ -117,17 +123,6 @@ namespace OrderedSet { ...@@ -117,17 +123,6 @@ namespace OrderedSet {
return intersectAA((a as ArrayImpl).values, (b as ArrayImpl).values); return intersectAA((a as ArrayImpl).values, (b as ArrayImpl).values);
} }
} }
export function ofSingleton(value: number): OrderedSet { return new RangeImpl(value, value); }
export function ofRange(min: number, max: number): OrderedSet { return max < min ? Empty : new RangeImpl(min, max); }
/** It is the responsibility of the caller to ensure the array is sorted and contains unique values. */
export function ofSortedArray(xs: ArrayLike<number>): OrderedSet {
if (!xs.length) return Empty;
// check if the array is just a range
if (xs[xs.length - 1] - xs[0] + 1 === xs.length) return ofRange(xs[0], xs[xs.length - 1]);
return new ArrayImpl(xs);
}
export const Empty = new RangeImpl(0, -1);
} }
function min(a: OrderedSet) { return (a as Impl).min; } function min(a: OrderedSet) { return (a as Impl).min; }
......
...@@ -9,125 +9,129 @@ ...@@ -9,125 +9,129 @@
* A generic chunked array builder. * A generic chunked array builder.
* *
* When adding elements, the array growns by a specified number * When adding elements, the array growns by a specified number
* of elements and no copying is done until ChunkedArray.compact * of elements (either linear or exponential growth) and no copying
* is called. * is done until ChunkedArray.compact is called.
*/ */
export interface ChunkedArray<T> { interface ChunkedArray<T> {
creator: (size: number) => any; ctor: (size: number) => any,
elementSize: number; elementSize: number,
chunkSize: number;
current: any; linearGrowth: boolean,
currentIndex: number;
initialSize: number,
parts: any[]; allocatedSize: number,
elementCount: number; elementCount: number,
currentSize: number,
currentChunk: any,
currentIndex: number,
chunks: any[]
} }
export namespace ChunkedArray { // TODO: better api, write tests
namespace ChunkedArray {
export function is(x: any): x is ChunkedArray<any> { export function is(x: any): x is ChunkedArray<any> {
return x.creator && x.chunkSize; return x.creator && x.chunkSize;
} }
export function add4<T>(array: ChunkedArray<T>, x: T, y: T, z: T, w: T) { function allocateNext(array: ChunkedArray<any>) {
if (array.currentIndex >= array.chunkSize) { let nextSize = !array.allocatedSize || array.linearGrowth
array.currentIndex = 0; ? array.initialSize * array.elementSize
array.current = array.creator(array.chunkSize); : Math.max(Math.ceil(0.61 * array.allocatedSize), 1);
array.parts[array.parts.length] = array.current; if (nextSize % array.elementSize !== 0) nextSize += nextSize % array.elementSize;
} array.currentSize = nextSize;
array.currentIndex = 0;
array.currentChunk = array.ctor(nextSize);
array.allocatedSize += nextSize;
array.chunks[array.chunks.length] = array.currentChunk;
}
array.current[array.currentIndex++] = x; export function add4<T>(array: ChunkedArray<T>, x: T, y: T, z: T, w: T) {
array.current[array.currentIndex++] = y; if (array.currentIndex >= array.currentSize) allocateNext(array);
array.current[array.currentIndex++] = z; const c = array.currentChunk;
array.current[array.currentIndex++] = w; c[array.currentIndex++] = x;
c[array.currentIndex++] = y;
c[array.currentIndex++] = z;
c[array.currentIndex++] = w;
return array.elementCount++; return array.elementCount++;
} }
export function add3<T>(array: ChunkedArray<T>, x: T, y: T, z: T) { export function add3<T>(array: ChunkedArray<T>, x: T, y: T, z: T) {
if (array.currentIndex >= array.chunkSize) { if (array.currentIndex >= array.currentSize) allocateNext(array);
array.currentIndex = 0; const c = array.currentChunk;
array.current = array.creator(array.chunkSize); c[array.currentIndex++] = x;
array.parts[array.parts.length] = array.current; c[array.currentIndex++] = y;
} c[array.currentIndex++] = z;
array.current[array.currentIndex++] = x;
array.current[array.currentIndex++] = y;
array.current[array.currentIndex++] = z;
return array.elementCount++; return array.elementCount++;
} }
export function add2<T>(array: ChunkedArray<T>, x: T, y: T) { export function add2<T>(array: ChunkedArray<T>, x: T, y: T) {
if (array.currentIndex >= array.chunkSize) { if (array.currentIndex >= array.currentSize) allocateNext(array);
array.currentIndex = 0; const c = array.currentChunk;
array.current = array.creator(array.chunkSize); c[array.currentIndex++] = x;
array.parts[array.parts.length] = array.current; c[array.currentIndex++] = y;
}
array.current[array.currentIndex++] = x;
array.current[array.currentIndex++] = y;
return array.elementCount++; return array.elementCount++;
} }
export function add<T>(array: ChunkedArray<T>, x: T) { export function add<T>(array: ChunkedArray<T>, x: T) {
if (array.currentIndex >= array.chunkSize) { if (array.currentIndex >= array.currentSize) allocateNext(array);
array.currentIndex = 0; array.currentChunk[array.currentIndex++] = x;
array.current = array.creator(array.chunkSize);
array.parts[array.parts.length] = array.current;
}
array.current[array.currentIndex++] = x;
return array.elementCount++; return array.elementCount++;
} }
export function compact<T>(array: ChunkedArray<T>): T[] { export function compact<T>(array: ChunkedArray<T>): ArrayLike<T> {
const ret = array.creator(array.elementSize * array.elementCount) const { ctor, chunks, currentIndex } = array;
const offset = (array.parts.length - 1) * array.chunkSize
let offsetInner = 0
let part: any
if (array.parts.length > 1) { if (!chunks.length) return ctor(0);
if (array.parts[0].buffer) { if (chunks.length === 1 && currentIndex === array.allocatedSize) {
for (let i = 0; i < array.parts.length - 1; i++) { return chunks[0];
ret.set(array.parts[i], array.chunkSize * i); }
}
} else {
for (let i = 0; i < array.parts.length - 1; i++) { const ret = ctor(array.elementSize * array.elementCount);
offsetInner = array.chunkSize * i; let offset = 0;
part = array.parts[i];
for (let j = 0; j < array.chunkSize; j++) { if (ret.buffer) {
ret[offsetInner + j] = part[j]; for (let i = 0, _i = chunks.length - 1; i < _i; i++) {
} ret.set(chunks[i], offset);
} offset += chunks[i].length;
} }
}
if (array.current.buffer && array.currentIndex >= array.chunkSize) {
ret.set(array.current, array.chunkSize * (array.parts.length - 1));
} else { } else {
for (let i = 0; i < array.currentIndex; i++) { for (let i = 0, _i = chunks.length - 1; i < _i; i++) {
ret[offset + i] = array.current[i]; const chunk = chunks[i];
for (let j = 0, _j = chunk.length; j < _j; j++) ret[offset + j] = chunk[j];
offset += chunk.length;
} }
} }
return ret as any;
}
export function create<T>(creator: (size: number) => any, chunkElementCount: number, elementSize: number): ChunkedArray<T> { const lastChunk = chunks[chunks.length - 1];
chunkElementCount = chunkElementCount | 0; if (ret.buffer && currentIndex >= array.currentSize) {
if (chunkElementCount <= 0) chunkElementCount = 1; ret.set(lastChunk, offset);
} else {
for (let j = 0, _j = lastChunk.length; j < _j; j++) ret[offset + j] = lastChunk[j];
}
let chunkSize = chunkElementCount * elementSize; return ret;
let current = creator(chunkSize) }
export function create<T>(ctor: (size: number) => any, elementSize: number, initialSize: number, linearGrowth: boolean): ChunkedArray<T> {
return { return {
ctor,
elementSize, elementSize,
chunkSize, linearGrowth,
creator,
current, initialSize,
parts: [current], allocatedSize: 0,
elementCount: 0,
currentSize: 0,
currentChunk: void 0,
currentIndex: 0, currentIndex: 0,
elementCount: 0
} as ChunkedArray<T> chunks: []
} as ChunkedArray<T>;
} }
} }
export default ChunkedArray
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment