diff --git a/src/mol-data/util/_spec/chunked-array.spec.ts b/src/mol-data/util/_spec/chunked-array.spec.ts index ded69465ddb438799afbfa50ba7ba3e175f65e9f..c611d7575164bf52d912455f2d9401a216c6e025 100644 --- a/src/mol-data/util/_spec/chunked-array.spec.ts +++ b/src/mol-data/util/_spec/chunked-array.spec.ts @@ -8,17 +8,23 @@ import { ChunkedArray } from '../chunked-array' describe('Chunked Array', () => { it('creation', () => { - const arr = ChunkedArray.create<number>(_ => [], 2, 2); + const arr = ChunkedArray.create<number>(Array, 2, 2); ChunkedArray.add2(arr, 1, 2); ChunkedArray.add2(arr, 3, 4); expect(ChunkedArray.compact(arr)).toEqual([1, 2, 3, 4]); }); it('initial', () => { - const arr = ChunkedArray.create<number>(s => new Int32Array(s), 2, 6, new Int32Array([1, 2, 3, 4])); + const arr = ChunkedArray.create<number>(Int32Array, 2, 6, new Int32Array([1, 2, 3, 4])); ChunkedArray.add2(arr, 4, 3); ChunkedArray.add2(arr, 2, 1); ChunkedArray.add2(arr, 5, 6); expect(ChunkedArray.compact(arr)).toEqual(new Int32Array([4, 3, 2, 1, 5, 6])); }); + + it('add many', () => { + const arr = ChunkedArray.create<number>(Array, 2, 2); + ChunkedArray.addMany(arr, [1, 2, 3, 4]); + expect(ChunkedArray.compact(arr)).toEqual([1, 2, 3, 4]); + }); }); \ No newline at end of file diff --git a/src/mol-data/util/chunked-array.ts b/src/mol-data/util/chunked-array.ts index 2cb03e02a9343a8616735481d14a562f57bd2559..fdad72464b6057f878cb218ea0ce27cb898c28ff 100644 --- a/src/mol-data/util/chunked-array.ts +++ b/src/mol-data/util/chunked-array.ts @@ -8,16 +8,17 @@ /** * A generic chunked array builder. * - * When adding elements, the array growns by a specified number - * of elements (either linear or exponential growth) and no copying - * is done until ChunkedArray.compact is called. + * When adding elements, the array grows by a specified number + * of elements and no copying is done until ChunkedArray.compact + * is called. */ interface ChunkedArray<T> { - ctor: (size: number) => any, + ctor: { new (size: number): ArrayLike<T> }, elementSize: number, growBy: number, allocatedSize: number, + /** current size of the array */ elementCount: number, currentSize: number, @@ -36,7 +37,7 @@ namespace ChunkedArray { let nextSize = array.growBy * array.elementSize; array.currentSize = nextSize; array.currentIndex = 0; - array.currentChunk = array.ctor(nextSize); + array.currentChunk = new array.ctor(nextSize); array.allocatedSize += nextSize; array.chunks[array.chunks.length] = array.currentChunk; } @@ -74,6 +75,20 @@ namespace ChunkedArray { return array.elementCount++; } + export function addMany<T>(array: ChunkedArray<T>, data: ArrayLike<T>) { + const { elementSize } = array; + for (let i = 0, _i = data.length; i < _i; i += elementSize) { + if (array.currentIndex >= array.currentSize) allocateNext(array); + const { currentChunk } = array; + for (let j = 0; j < elementSize; j++) { + currentChunk[array.currentIndex++] = data[i + j]; + } + array.elementCount++; + } + return array.elementCount; + } + + /** If doNotResizeSingleton = true and the data fit into a single chunk, do not resize it. */ export function compact<T>(array: ChunkedArray<T>, doNotResizeSingleton = false): ArrayLike<T> { return _compact(array, doNotResizeSingleton); } @@ -81,7 +96,7 @@ namespace ChunkedArray { export function _compact<T>(array: ChunkedArray<T>, doNotResizeSingleton: boolean): ArrayLike<T> { const { ctor, chunks, currentIndex } = array; - if (!chunks.length) return ctor(0); + if (!chunks.length) return new ctor(0); if (chunks.length === 1) { if (doNotResizeSingleton || currentIndex === array.allocatedSize) { return chunks[0]; @@ -92,7 +107,7 @@ namespace ChunkedArray { for (let i = 0, _i = chunks.length - 1; i < _i; i++) size += chunks[i].length; size += array.currentIndex; - const ret = ctor(size); + const ret = new ctor(size) as any; let offset = 0; if (ret.buffer) { @@ -118,12 +133,17 @@ namespace ChunkedArray { return ret; } - export function create<T>(ctor: (size: number) => any, elementSize: number, growBy: number, initialChunk?: ArrayLike<T>): ChunkedArray<T> { + export function create<T>(ctor: { new (size: number): ArrayLike<T> }, elementSize: number, chunkSize: number): ChunkedArray<T> + /** The size of the initial chunk is elementSize * initialCount */ + export function create<T>(ctor: { new (size: number): ArrayLike<T> }, elementSize: number, chunkSize: number, initialCount: number): ChunkedArray<T> + /** Use the provided array as the initial chunk. The size of the array must be divisible by the elementSize */ + export function create<T>(ctor: { new (size: number): ArrayLike<T> }, elementSize: number, chunkSize: number, initialChunk: ArrayLike<T>): ChunkedArray<T> + export function create<T>(ctor: { new (size: number): ArrayLike<T> }, elementSize: number, chunkSize: number, initialChunkOrCount?: number | ArrayLike<T>): ChunkedArray<T> { const ret: ChunkedArray<T> = { ctor, elementSize, - growBy, + growBy: chunkSize, allocatedSize: 0, elementCount: 0, @@ -134,8 +154,17 @@ namespace ChunkedArray { chunks: [] }; - if (!initialChunk) return ret; + if (typeof initialChunkOrCount === 'undefined') return ret; + + if (typeof initialChunkOrCount === 'number') { + ret.currentChunk = new ctor(initialChunkOrCount * elementSize); + ret.allocatedSize = initialChunkOrCount * elementSize; + ret.currentSize = ret.currentChunk.length; + ret.chunks[0] = ret.currentChunk; + return ret; + } + const initialChunk = initialChunkOrCount; if (initialChunk.length % elementSize !== 0) throw new Error('initialChunk length must be a multiple of the element size.'); ret.currentChunk = initialChunk; ret.allocatedSize = initialChunk.length; diff --git a/src/mol-io/common/binary-cif/array-encoder.ts b/src/mol-io/common/binary-cif/array-encoder.ts index e73e0a816807895b0f178b035bfae3437a9aa839..c3c119494e898cc186031961009eb8b7a4d20a79 100644 --- a/src/mol-io/common/binary-cif/array-encoder.ts +++ b/src/mol-io/common/binary-cif/array-encoder.ts @@ -357,7 +357,7 @@ export namespace ArrayEncoding { let map: any = Object.create(null); let strings: string[] = []; let accLength = 0; - let offsets = ChunkedArray.create<number>(s => new Int32Array(s), 1, + let offsets = ChunkedArray.create<number>(Int32Array, 1, Math.min(1024, data.length < 32 ? data.length + 1 : Math.round(data.length / 8) + 1)); let output = new Int32Array(data.length); diff --git a/src/perf-tests/chunked-array-vs-native.ts b/src/perf-tests/chunked-array-vs-native.ts index d4f13f2e138dfd799b36fabd60a47e8cbff24489..6e0bd57febb5e2576297675689e3fb1aff023fdb 100644 --- a/src/perf-tests/chunked-array-vs-native.ts +++ b/src/perf-tests/chunked-array-vs-native.ts @@ -8,13 +8,13 @@ function testNative(size: number) { } function testChunkedTyped(size: number, chunk: number) { - const xs = ChunkedArray.create(s => new Int32Array(s), 1, chunk); + const xs = ChunkedArray.create(Int32Array, 1, chunk); 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); + const xs = ChunkedArray.create(Array, 1, chunk); for (let i = 0; i < size; i++) ChunkedArray.add(xs, i * i); return ChunkedArray.compact(xs); }