UNPKG

@jawis/shared-page-heap

Version:
176 lines (175 loc) 6.51 kB
import { TypedArray, TypedArrayContructor } from "^jab"; import { FixedSizeHeap } from "^shared-algs"; export type SharedTreeHeapDeps = { maxSize: number; dataSize: number; sharedArray: Int32Array; useChunkVersion: boolean; initialized?: boolean; }; /** * A tree is laid out sequentially in memory. * * - A left Left-compact tree. * - With a fixed left endpoint, and a dynamic right endpoint for adding/removing pages. * - Pages can't be inserted/deleted in the middle of the tree. * - Each page contains valid bits, to indicate which data chunks are currently in use. * - The user can interpret the data in any way, the only restrictions are: * - Allocate finds an invalid chunk, makes i valid, and returns it. * - Deallocated throws if given chunk isn't valid. * - Must initialize all memory itself, because this is the most basic data structure and the sharedArray could be reused. * * Complexity * - Get is contant time, and requires no memory access. * - maybe we could accept a single memory access, in order to support relocation and garbage collection. * Maybe a better solution would be a way to signal to users, that a re-allocation would be desirable. * - Allocation and deallocation is log(n) * * notes * - Maintains a tree with a full subtree on the left hand-side. * - More precisely: All subtrees reachable by at least one left edge are perfect. * - Pages are zero-indexed. * - Pages are are always added/removed at the right end. * - Edges are virtual. They can be determined from the bit-representation of page-index. * - Similar implementation/purpose as SharedChunkHeap * - maybe that implementation is better, because the linked list is constant time in (de)allocation. * The only difference is, that this implementation must keep a free list instead of deallocation * in the parent heap. * * memory layout * - meta data * - pages (sequentially in memory) * * page layout * 2 uint32 free-bit-boolean. If left/right subtree has any valid bits it's 1, otherwise 0. * 64 bits valid-bits * n bytes space for chunks. For external use. * * structure of reference (heighest bits first) * 27 bits page index * 1 bit chunk index * 4 bits chunk integrity version * * todo * - What is the purpose of having chunks? Maybe so the validity bytes can be fully used. They have to be 8 bytes. * So alignment isn't problem. It would also be better to have free-bit-booleans co-located in memory rows, so the * the search for pages makes the most use for memory fetches. * - pagecount isn't decreased. But is the heighest unused pages could be removed, if user's want to reclaim place. * - Shrink pagecount isn't implemented. Could remove pages from right, when they becomes empty. */ export declare class SharedTreeHeap implements FixedSizeHeap { deps: SharedTreeHeapDeps; private maxPages; private pageByteSize; private static FREE_BIT_BYTE_SIZE; private static VALIDITY_VECTOR_BYTE_SIZE; private static VALIDITY_VECTOR_BYTE_OFFSET; private static PAGE_HEADER_BYTE_SIZE; private static DATA_OVERHEAD; private static DATA_COUNT_PER_PAGE; /** * */ constructor(deps: SharedTreeHeapDeps); /** * */ pack: () => SharedTreeHeapDeps; /** * */ static getExpectedByteSize: (n: number, dataSize: number) => number; /** * */ get dataSize(): number; /** * */ get count(): number; /** * */ private getChuckByteOffset; /** * Get previously allocated memory. * * - This is used in other threads, to get the shared memory, dynamically. */ get: <T extends TypedArray>(ref: number, TypedArray: TypedArrayContructor<T>) => T; /** * Allocate the given size of shared memory. * * - The memory isn't locked, so it can be retrieved in other threads. * - The memory is only protected against reuse for other allocations. * - All threads with the returned reference can get/deallocate the memory. * */ allocate: <T extends TypedArray>(TypedArray: TypedArrayContructor<T>, zeroFill?: boolean) => { ref: number; array: T; }; /** * udgår */ private allocate2; /** * * - Increases the data structure if it's needed. And if the allotted space allows it. * * impl * - The left subpage of the added page will always be full. Either it's a leaf page. Or it's * a inner page and the left sub page is filled, because we only add pages, when we run out of space. * - Note: The inserted page is the right most in the tree, because it's the largest page. Hence it's reachable * via only right edges. Otherwise a ancestor would be to the right it. * - The pages that will have free-bits below them after insertion, are the pages from the root to the * inserted page. I.e. the pages reachable from the root by following right edges. * - Note the inserted page shouldn't have a free-bits set true for right subtree, but the `_tryGet` will have same * semantics. It will just try to recurse and set free-bits to false. */ tryAllocate: () => number | undefined; /** * in-order dfs traversal. * - Finds the left-most page with free space. */ private _tryAllocate; /** * */ deallocate: (ref: number) => void; /** * in-order dfs traversal. * - Finds the page and invalidates the chunk. (it could be done in constant time) * - Sets free-bit-booleans. (which require a log(n) traversal) * * impl * - sets the page 'statistics' on the way up. Because there are user input, a throw * could corrupt the tree. * - Might actually not be a semantic problem, because `tryGet` will just perform the search, * and then set the page 'statistics' correctly again. * * todo * - extract, so this only does searchPage. */ private _deallocate; /** * SharedChunkHeap.encode seems better. And can we extract to ValidityVector? */ private encode; /** * SharedChunkHeap.decode seems better. And can we extract to ValidityVector? */ private decode; /** * */ private getFreeBitArray; /** * */ private getValidityVector; /** * */ toString: () => string; }