@jawis/shared-page-heap
Version:
Heap for concurrent programs.
176 lines (175 loc) • 6.51 kB
TypeScript
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;
}