a40f0f6475
This commit adds a space and time efficient sparse array data structure. The structure's raw API is wrapped by inline functions which provide type safety. Reviewed-by: Richard Levitte <levitte@openssl.org> Reviewed-by: Nicola Tuveri <nic.tuv@gmail.com> (Merged from https://github.com/openssl/openssl/pull/8197)
155 lines
5.6 KiB
Text
155 lines
5.6 KiB
Text
The sparse_array.c file contains an implementation of a sparse array that
|
|
attempts to be both space and time efficient.
|
|
|
|
The sparse array is represented using a tree structure. Each node in the
|
|
tree contains a block of pointers to either the user supplied leaf values or
|
|
to another node.
|
|
|
|
There are a number of parameters used to define the block size:
|
|
|
|
OPENSSL_SA_BLOCK_BITS Specifies the number of bits covered by each block
|
|
SA_BLOCK_MAX Specifies the number of pointers in each block
|
|
SA_BLOCK_MASK Specifies a bit mask to perform modulo block size
|
|
SA_BLOCK_MAX_LEVELS Indicates the maximum possible height of the tree
|
|
|
|
These constants are inter-related:
|
|
SA_BLOCK_MAX = 2 ^ OPENSSL_SA_BLOCK_BITS
|
|
SA_BLOCK_MASK = SA_BLOCK_MAX - 1
|
|
SA_BLOCK_MAX_LEVELS = number of bits in size_t divided by
|
|
OPENSSL_SA_BLOCK_BITS rounded up to the next multiple
|
|
of OPENSSL_SA_BLOCK_BITS
|
|
|
|
OPENSSL_SA_BLOCK_BITS can be defined at compile time and this overrides the
|
|
built in setting.
|
|
|
|
As a space and performance optimisation, the height of the tree is usually
|
|
less than the maximum possible height. Only sufficient height is allocated to
|
|
accommodate the largest index added to the data structure.
|
|
|
|
The largest index used to add a value to the array determines the tree height:
|
|
|
|
+----------------------+---------------------+
|
|
| Largest Added Index | Height of Tree |
|
|
+----------------------+---------------------+
|
|
| SA_BLOCK_MAX - 1 | 1 |
|
|
| SA_BLOCK_MAX ^ 2 - 1 | 2 |
|
|
| SA_BLOCK_MAX ^ 3 - 1 | 3 |
|
|
| ... | ... |
|
|
| size_t max | SA_BLOCK_MAX_LEVELS |
|
|
+----------------------+---------------------+
|
|
|
|
The tree height is dynamically increased as needed based on additions.
|
|
|
|
An empty tree is represented by a NULL root pointer. Inserting a value at
|
|
index 0 results in the allocation of a top level node full of null pointers
|
|
except for the single pointer to the user's data (N = SA_BLOCK_MAX for
|
|
breviety):
|
|
|
|
+----+
|
|
|Root|
|
|
|Node|
|
|
+-+--+
|
|
|
|
|
|
|
|
|
|
|
v
|
|
+-+-+---+---+---+---+
|
|
| 0 | 1 | 2 |...|N-1|
|
|
| |nil|nil|...|nil|
|
|
+-+-+---+---+---+---+
|
|
|
|
|
|
|
|
|
|
|
v
|
|
+-+--+
|
|
|User|
|
|
|Data|
|
|
+----+
|
|
Index 0
|
|
|
|
|
|
Inserting at element 2N+1 creates a new root node and pushes down the old root
|
|
node. It then creates a second second level node to hold the pointer to the
|
|
user's new data:
|
|
|
|
+----+
|
|
|Root|
|
|
|Node|
|
|
+-+--+
|
|
|
|
|
|
|
|
|
|
|
v
|
|
+-+-+---+---+---+---+
|
|
| 0 | 1 | 2 |...|N-1|
|
|
| |nil| |...|nil|
|
|
+-+-+---+-+-+---+---+
|
|
| |
|
|
| +------------------+
|
|
| |
|
|
v v
|
|
+-+-+---+---+---+---+ +-+-+---+---+---+---+
|
|
| 0 | 1 | 2 |...|N-1| | 0 | 1 | 2 |...|N-1|
|
|
|nil| |nil|...|nil| |nil| |nil|...|nil|
|
|
+-+-+---+---+---+---+ +---+-+-+---+---+---+
|
|
| |
|
|
| |
|
|
| |
|
|
v v
|
|
+-+--+ +-+--+
|
|
|User| |User|
|
|
|Data| |Data|
|
|
+----+ +----+
|
|
Index 0 Index 2N+1
|
|
|
|
|
|
The nodes themselves are allocated in a sparse manner. Only nodes which exist
|
|
along a path from the root of the tree to an added leaf will be allocated.
|
|
The complexity is hidden and nodes are allocated on an as needed basis.
|
|
Because the data is expected to be sparse this doesn't result in a large waste
|
|
of space.
|
|
|
|
Values can be removed from the sparse array by setting their index position to
|
|
NULL. The data structure does not attempt to reclaim nodes or reduce the
|
|
height of the tree on removal. For example, now setting index 0 to NULL would
|
|
result in:
|
|
|
|
+----+
|
|
|Root|
|
|
|Node|
|
|
+-+--+
|
|
|
|
|
|
|
|
|
|
|
v
|
|
+-+-+---+---+---+---+
|
|
| 0 | 1 | 2 |...|N-1|
|
|
| |nil| |...|nil|
|
|
+-+-+---+-+-+---+---+
|
|
| |
|
|
| +------------------+
|
|
| |
|
|
v v
|
|
+-+-+---+---+---+---+ +-+-+---+---+---+---+
|
|
| 0 | 1 | 2 |...|N-1| | 0 | 1 | 2 |...|N-1|
|
|
|nil|nil|nil|...|nil| |nil| |nil|...|nil|
|
|
+---+---+---+---+---+ +---+-+-+---+---+---+
|
|
|
|
|
|
|
|
|
|
|
v
|
|
+-+--+
|
|
|User|
|
|
|Data|
|
|
+----+
|
|
Index 2N+1
|
|
|
|
|
|
Accesses to elements in the sparse array take O(log n) time where n is the
|
|
largest element. The base of the logarithm is SA_BLOCK_MAX, so for moderately
|
|
small indices (e.g. NIDs), single level (constant time) access is achievable.
|
|
Space usage is O(minimum(m, n log(n)) where m is the number of elements in the
|
|
array.
|
|
|
|
Note: sparse arrays only include pointers to types. Thus, SPARSE_ARRAY_OF(char)
|
|
can be used to store a string.
|