/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file blender/blenlib/intern/array_store.c
* \ingroup bli
* \brief Array storage to minimize duplication.
*
* This is done by splitting arrays into chunks and using copy-on-write (COW),
* to de-duplicate chunks,
* from the users perspective this is an implementation detail.
*
*
* Overview
* ========
*
*
* Data Structure
* --------------
*
* This diagram is an overview of the structure of a single array-store.
*
* \note The only 2 structures here which are referenced externally are the.
*
* - BArrayStore: The whole array store.
* - BArrayState: Represents a single state (array) of data.
* These can be add using a reference state, while this could be considered the previous or parent state.
* no relationship is kept, so the caller is free to add any state from the same BArrayStore as a reference.
*
*
* <+> BArrayStore: root data-structure,
* | can store many 'states', which share memory.
* |
* | This can store many arrays, however they must share the same 'stride'.
* | Arrays of different types will need to use a new BArrayStore.
* |
* +- <+> states (Collection of BArrayState's):
* | | Each represents an array added by the user of this API.
* | | and references a chunk_list (each state is a chunk_list user).
* | | Note that the list order has no significance.
* | |
* | +- <+> chunk_list (BChunkList):
* | | The chunks that make up this state.
* | | Each state is a chunk_list user,
* | | avoids duplicating lists when there is no change between states.
* | |
* | +- chunk_refs (List of BChunkRef): Each chunk_ref links to a a BChunk.
* | Each reference is a chunk user,
* | avoids duplicating smaller chunks of memory found in multiple states.
* |
* +- info (BArrayInfo):
* | Sizes and offsets for this array-store.
* | Also caches some variables for reuse.
* |
* +- <+> memory (BArrayMemory):
* | Memory pools for storing BArrayStore data.
* |
* +- chunk_list (Pool of BChunkList):
* | All chunk_lists, (reference counted, used by BArrayState).
* |
* +- chunk_ref (Pool of BChunkRef):
* | All chunk_refs (link between BChunkList & BChunk).
* |
* +- chunks (Pool of BChunk):
* All chunks, (reference counted, used by BChunkList).
* These have their headers hashed for reuse so we can quickly check for duplicates.
*
*
*
* De-Duplication
* --------------
*
* When creating a new state, a previous state can be given as a reference,
* matching chunks from this state are re-used in the new state.
*
* First matches at either end of the array are detected.
* For identical arrays this is all thats needed.
*
* De-duplication is performed on any remaining chunks, by hashing the first few bytes of the chunk
* (see: BCHUNK_HASH_TABLE_ACCUMULATE_STEPS).
*
* \note This is cached for reuse since the referenced data never changes.
*
* An array is created to store hash values at every 'stride',
* then stepped over to search for matching chunks.
*
* Once a match is found, there is a high chance next chunks match too,
* so this is checked to avoid performing so many hash-lookups.
* Otherwise new chunks are created.
*/
#include
#include
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
#include "BLI_mempool.h"
#include "BLI_strict_flags.h"
#include "BLI_array_store.h" /* own include */
/* only for BLI_array_store_is_valid */
#include "BLI_ghash.h"
/** \name Defines
*
* Some of the logic for merging is quite involved,
* support disabling some parts of this.
* \{ */
/* Scan first chunks (happy path when beginning of the array matches).
* When the array is a perfect match, we can re-use the entire list.
*
* Note that disabling makes some tests fail that check for output-size.
*/
#define USE_FASTPATH_CHUNKS_FIRST
/* Scan last chunks (happy path when end of the array matches).
* When the end of the array matches, we can quickly add these chunks.
* note that we will add contiguous matching chunks
* so this isn't as useful as USE_FASTPATH_CHUNKS_FIRST,
* however it avoids adding matching chunks into the lookup table,
* so creating the lookup table won't be as expensive.
*/
#ifdef USE_FASTPATH_CHUNKS_FIRST
# define USE_FASTPATH_CHUNKS_LAST
#endif
/* For arrays of matching length, test that *enough* of the chunks are aligned,
* and simply step over both arrays, using matching chunks.
* This avoids overhead of using a lookup table for cases when we can assume they're mostly aligned.
*/
#define USE_ALIGN_CHUNKS_TEST
/* Accumulate hashes from right to left so we can create a hash for the chunk-start.
* This serves to increase uniqueness and will help when there is many values which are the same.
*/
#define USE_HASH_TABLE_ACCUMULATE
#ifdef USE_HASH_TABLE_ACCUMULATE
/* Number of times to propagate hashes back.
* Effectively a 'triangle-number'.
* so 4 -> 7, 5 -> 10, 6 -> 15... etc.
*/
# define BCHUNK_HASH_TABLE_ACCUMULATE_STEPS 4
#else
/* How many items to hash (multiplied by stride)
*/
# define BCHUNK_HASH_LEN 4
#endif
/* Calculate the key once and reuse it
*/
#define USE_HASH_TABLE_KEY_CACHE
#ifdef USE_HASH_TABLE_KEY_CACHE
# define HASH_TABLE_KEY_UNSET ((uint64_t)-1)
# define HASH_TABLE_KEY_FALLBACK ((uint64_t)-2)
#endif
/* How much larger the table is then the total number of chunks.
*/
#define BCHUNK_HASH_TABLE_MUL 3
/* Merge too small/large chunks:
*
* Using this means chunks below a threshold will be merged together.
* Even though short term this uses more memory,
* long term the overhead of maintaining many small chunks is reduced.
* This is defined by setting the minimum chunk size (as a fraction of the regular chunk size).
*
* Chunks may also become too large (when incrementally growing an array),
* this also enables chunk splitting.
*/
#define USE_MERGE_CHUNKS
#ifdef USE_MERGE_CHUNKS
/* Merge chunks smaller then: (chunk_size / BCHUNK_MIN_SIZE_DIV)
*/
# define BCHUNK_SIZE_MIN_DIV 8
/* Disallow chunks bigger then the regular chunk size scaled by this value
* note: must be at least 2!
* however, this code runs wont run in tests unless its ~1.1 ugh.
* so lower only to check splitting works.
*/
# define BCHUNK_SIZE_MAX_MUL 2
#endif /* USE_MERGE_CHUNKS */
/* slow (keep disabled), but handy for debugging */
// #define USE_VALIDATE_LIST_SIZE
// #define USE_VALIDATE_LIST_DATA_PARTIAL
// #define USE_PARANOID_CHECKS
/** \} */
/** \name Internal Structs
* \{ */
typedef uint64_t hash_key;
typedef struct BArrayInfo {
size_t chunk_stride;
// uint chunk_count; /* UNUSED (other values are derived from this) */
/* pre-calculated */
size_t chunk_byte_size;
/* min/max limits (inclusive) */
size_t chunk_byte_size_min;
size_t chunk_byte_size_max;
size_t accum_read_ahead_bytes;
#ifdef USE_HASH_TABLE_ACCUMULATE
size_t accum_steps;
size_t accum_read_ahead_len;
#endif
} BArrayInfo;
typedef struct BArrayMemory {
BLI_mempool *chunk_list; /* BChunkList */
BLI_mempool *chunk_ref; /* BChunkRef */
BLI_mempool *chunk; /* BChunk */
} BArrayMemory;
/**
* Main storage for all states
*/
struct BArrayStore {
/* static */
BArrayInfo info;
/* memory storage */
BArrayMemory memory;
/**
* #BArrayState may be in any order (logic should never depend on state order).
*/
ListBase states;
};
/**
* A single instance of an array.
*
* This is how external API's hold a reference to an in-memory state,
* although the struct is private.
*
* \note Currently each 'state' is allocated separately.
* While this could be moved to a memory pool,
* it makes it easier to trace invalid usage, so leave as-is for now.
*/
struct BArrayState {
/** linked list in #BArrayStore.states */
struct BArrayState *next, *prev;
struct BChunkList *chunk_list; /* BChunkList's */
};
typedef struct BChunkList {
ListBase chunk_refs; /* BChunkRef's */
uint chunk_refs_len; /* BLI_listbase_count(chunks), store for reuse. */
size_t total_size; /* size of all chunks */
/** number of #BArrayState using this. */
int users;
} BChunkList;
/* a chunk of an array */
typedef struct BChunk {
const uchar *data;
size_t data_len;
/** number of #BChunkList using this. */
int users;
#ifdef USE_HASH_TABLE_KEY_CACHE
hash_key key;
#endif
} BChunk;
/**
* Links to store #BChunk data in #BChunkList.chunk_refs.
*/
typedef struct BChunkRef {
struct BChunkRef *next, *prev;
BChunk *link;
} BChunkRef;
/**
* Single linked list used when putting chunks into a temporary table,
* used for lookups.
*
* Point to the #BChunkRef, not the #BChunk,
* to allow talking down the chunks in-order until a mis-match is found,
* this avoids having to do so many table lookups.
*/
typedef struct BTableRef {
struct BTableRef *next;
const BChunkRef *cref;
} BTableRef;
/** \} */
static size_t bchunk_list_size(const BChunkList *chunk_list);
/** \name Internal BChunk API
* \{ */
static BChunk *bchunk_new(
BArrayMemory *bs_mem, const uchar *data, const size_t data_len)
{
BChunk *chunk = BLI_mempool_alloc(bs_mem->chunk);
chunk->data = data;
chunk->data_len = data_len;
chunk->users = 0;
#ifdef USE_HASH_TABLE_KEY_CACHE
chunk->key = HASH_TABLE_KEY_UNSET;
#endif
return chunk;
}
static BChunk *bchunk_new_copydata(
BArrayMemory *bs_mem, const uchar *data, const size_t data_len)
{
uchar *data_copy = MEM_mallocN(data_len, __func__);
memcpy(data_copy, data, data_len);
return bchunk_new(bs_mem, data_copy, data_len);
}
static void bchunk_decref(
BArrayMemory *bs_mem, BChunk *chunk)
{
BLI_assert(chunk->users > 0);
if (chunk->users == 1) {
MEM_freeN((void *)chunk->data);
BLI_mempool_free(bs_mem->chunk, chunk);
}
else {
chunk->users -= 1;
}
}
static bool bchunk_data_compare(
const BChunk *chunk,
const uchar *data_base, const size_t data_base_len,
const size_t offset)
{
if (offset + (size_t)chunk->data_len <= data_base_len) {
return (memcmp(&data_base[offset], chunk->data, chunk->data_len) == 0);
}
else {
return false;
}
}
/** \} */
/** \name Internal BChunkList API
* \{ */
static BChunkList *bchunk_list_new(
BArrayMemory *bs_mem, size_t total_size)
{
BChunkList *chunk_list = BLI_mempool_alloc(bs_mem->chunk_list);
BLI_listbase_clear(&chunk_list->chunk_refs);
chunk_list->chunk_refs_len = 0;
chunk_list->total_size = total_size;
chunk_list->users = 0;
return chunk_list;
}
static void bchunk_list_decref(
BArrayMemory *bs_mem, BChunkList *chunk_list)
{
BLI_assert(chunk_list->users > 0);
if (chunk_list->users == 1) {
for (BChunkRef *cref = chunk_list->chunk_refs.first, *cref_next; cref; cref = cref_next) {
cref_next = cref->next;
bchunk_decref(bs_mem, cref->link);
BLI_mempool_free(bs_mem->chunk_ref, cref);
}
BLI_mempool_free(bs_mem->chunk_list, chunk_list);
}
else {
chunk_list->users -= 1;
}
}
#ifdef USE_VALIDATE_LIST_SIZE
# ifndef NDEBUG
# define ASSERT_CHUNKLIST_SIZE(chunk_list, n) BLI_assert(bchunk_list_size(chunk_list) == n)
# endif
#endif
#ifndef ASSERT_CHUNKLIST_SIZE
# define ASSERT_CHUNKLIST_SIZE(chunk_list, n) (EXPR_NOP(chunk_list), EXPR_NOP(n))
#endif
#ifdef USE_VALIDATE_LIST_DATA_PARTIAL
static size_t bchunk_list_data_check(
const BChunkList *chunk_list, const uchar *data)
{
size_t offset = 0;
for (BChunkRef *cref = chunk_list->chunk_refs.first; cref; cref = cref->next) {
if (memcmp(&data[offset], cref->link->data, cref->link->data_len) != 0) {
return false;
}
offset += cref->link->data_len;
}
return true;
}
# define ASSERT_CHUNKLIST_DATA(chunk_list, data) BLI_assert(bchunk_list_data_check(chunk_list, data))
#else
# define ASSERT_CHUNKLIST_DATA(chunk_list, data) (EXPR_NOP(chunk_list), EXPR_NOP(data))
#endif
#ifdef USE_MERGE_CHUNKS
static void bchunk_list_ensure_min_size_last(
const BArrayInfo *info, BArrayMemory *bs_mem,
BChunkList *chunk_list)
{
BChunkRef *cref = chunk_list->chunk_refs.last;
if (cref && cref->prev) {
/* both are decref'd after use (end of this block) */
BChunk *chunk_curr = cref->link;
BChunk *chunk_prev = cref->prev->link;
if (MIN2(chunk_prev->data_len, chunk_curr->data_len) < info->chunk_byte_size_min) {
const size_t data_merge_len = chunk_prev->data_len + chunk_curr->data_len;
/* we could pass, but no need */
if (data_merge_len <= info->chunk_byte_size_max) {
/* we have enough space to merge */
/* remove last from linklist */
BLI_assert(chunk_list->chunk_refs.last != chunk_list->chunk_refs.first);
cref->prev->next = NULL;
chunk_list->chunk_refs.last = cref->prev;
chunk_list->chunk_refs_len -= 1;
uchar *data_merge = MEM_mallocN(data_merge_len, __func__);
memcpy(data_merge, chunk_prev->data, chunk_prev->data_len);
memcpy(&data_merge[chunk_prev->data_len], chunk_curr->data, chunk_curr->data_len);
cref->prev->link = bchunk_new(bs_mem, data_merge, data_merge_len);
cref->prev->link->users += 1;
BLI_mempool_free(bs_mem->chunk_ref, cref);
}
else {
/* If we always merge small slices, we should _almost_ never end up having very large chunks.
* Gradual expanding on contracting will cause this.
*
* if we do, the code below works (test by setting 'BCHUNK_SIZE_MAX_MUL = 1.2') */
/* keep chunk on the left hand side a regular size */
const size_t split = info->chunk_byte_size;
/* merge and split */
const size_t data_prev_len = split;
const size_t data_curr_len = data_merge_len - split;
uchar *data_prev = MEM_mallocN(data_prev_len, __func__);
uchar *data_curr = MEM_mallocN(data_curr_len, __func__);
if (data_prev_len <= chunk_prev->data_len) {
const size_t data_curr_shrink_len = chunk_prev->data_len - data_prev_len;
/* setup 'data_prev' */
memcpy(data_prev, chunk_prev->data, data_prev_len);
/* setup 'data_curr' */
memcpy(data_curr, &chunk_prev->data[data_prev_len], data_curr_shrink_len);
memcpy(&data_curr[data_curr_shrink_len], chunk_curr->data, chunk_curr->data_len);
}
else {
BLI_assert(data_curr_len <= chunk_curr->data_len);
BLI_assert(data_prev_len >= chunk_prev->data_len);
const size_t data_prev_grow_len = data_prev_len - chunk_prev->data_len;
/* setup 'data_prev' */
memcpy(data_prev, chunk_prev->data, chunk_prev->data_len);
memcpy(&data_prev[chunk_prev->data_len], chunk_curr->data, data_prev_grow_len);
/* setup 'data_curr' */
memcpy(data_curr, &chunk_curr->data[data_prev_grow_len], data_curr_len);
}
cref->prev->link = bchunk_new(bs_mem, data_prev, data_prev_len);
cref->prev->link->users += 1;
cref->link = bchunk_new(bs_mem, data_curr, data_curr_len);
cref->link->users += 1;
}
/* free zero users */
bchunk_decref(bs_mem, chunk_curr);
bchunk_decref(bs_mem, chunk_prev);
}
}
}
#endif /* USE_MERGE_CHUNKS */
/**
* Split length into 2 values
* \param r_data_trim_len: Length which is aligned to the #BArrayInfo.chunk_byte_size
* \param r_data_last_chunk_len: The remaining bytes.
*
* \note This function ensures the size of \a r_data_last_chunk_len
* is larger than #BArrayInfo.chunk_byte_size_min.
*/
static void bchunk_list_calc_trim_len(
const BArrayInfo *info, const size_t data_len,
size_t *r_data_trim_len, size_t *r_data_last_chunk_len)
{
size_t data_last_chunk_len = 0;
size_t data_trim_len = data_len;
#ifdef USE_MERGE_CHUNKS
/* avoid creating too-small chunks
* more efficient then merging after */
if (data_len > info->chunk_byte_size) {
data_last_chunk_len = (data_trim_len % info->chunk_byte_size);
data_trim_len = data_trim_len - data_last_chunk_len;
if (data_last_chunk_len) {
if (data_last_chunk_len < info->chunk_byte_size_min) {
/* may be zero and thats OK */
data_trim_len -= info->chunk_byte_size;
data_last_chunk_len += info->chunk_byte_size;
}
}
}
else {
data_trim_len = 0;
data_last_chunk_len = data_len;
}
BLI_assert((data_trim_len == 0) || (data_trim_len >= info->chunk_byte_size));
#else
data_last_chunk_len = (data_trim_len % info->chunk_byte_size);
data_trim_len = data_trim_len - data_last_chunk_len;
#endif
BLI_assert(data_trim_len + data_last_chunk_len == data_len);
*r_data_trim_len = data_trim_len;
*r_data_last_chunk_len = data_last_chunk_len;
}
/**
* Append and don't manage merging small chunks.
*/
static void bchunk_list_append_only(
BArrayMemory *bs_mem,
BChunkList *chunk_list, BChunk *chunk)
{
BChunkRef *cref = BLI_mempool_alloc(bs_mem->chunk_ref);
BLI_addtail(&chunk_list->chunk_refs, cref);
cref->link = chunk;
chunk_list->chunk_refs_len += 1;
chunk->users += 1;
}
/**
* \note This is for writing single chunks,
* use #bchunk_list_append_data_n when writing large blocks of memory into many chunks.
*/
static void bchunk_list_append_data(
const BArrayInfo *info, BArrayMemory *bs_mem,
BChunkList *chunk_list,
const uchar *data, const size_t data_len)
{
BLI_assert(data_len != 0);
#ifdef USE_MERGE_CHUNKS
BLI_assert(data_len <= info->chunk_byte_size_max);
if (!BLI_listbase_is_empty(&chunk_list->chunk_refs)) {
BChunkRef *cref = chunk_list->chunk_refs.last;
BChunk *chunk_prev = cref->link;
if (MIN2(chunk_prev->data_len, data_len) < info->chunk_byte_size_min) {
const size_t data_merge_len = chunk_prev->data_len + data_len;
/* realloc for single user */
if (cref->link->users == 1) {
uchar *data_merge = MEM_reallocN((void *)cref->link->data, data_merge_len);
memcpy(&data_merge[chunk_prev->data_len], data, data_len);
cref->link->data = data_merge;
cref->link->data_len = data_merge_len;
}
else {
uchar *data_merge = MEM_mallocN(data_merge_len, __func__);
memcpy(data_merge, chunk_prev->data, chunk_prev->data_len);
memcpy(&data_merge[chunk_prev->data_len], data, data_len);
cref->link = bchunk_new(bs_mem, data_merge, data_merge_len);
cref->link->users += 1;
bchunk_decref(bs_mem, chunk_prev);
}
return;
}
}
#else
UNUSED_VARS(info);
#endif /* USE_MERGE_CHUNKS */
BChunk *chunk = bchunk_new_copydata(bs_mem, data, data_len);
bchunk_list_append_only(bs_mem, chunk_list, chunk);
/* don't run this, instead preemptively avoid creating a chunk only to merge it (above). */
#if 0
#ifdef USE_MERGE_CHUNKS
bchunk_list_ensure_min_size_last(info, bs_mem, chunk_list);
#endif
#endif
}
/**
* Similar to #bchunk_list_append_data, but handle multiple chunks.
* Use for adding arrays of arbitrary sized memory at once.
*
* \note This function takes care not to perform redundant chunk-merging checks,
* so we can write successive fixed size chunks quickly.
*/
static void bchunk_list_append_data_n(
const BArrayInfo *info, BArrayMemory *bs_mem,
BChunkList *chunk_list,
const uchar *data, size_t data_len)
{
size_t data_trim_len, data_last_chunk_len;
bchunk_list_calc_trim_len(info, data_len, &data_trim_len, &data_last_chunk_len);
if (data_trim_len != 0) {
size_t i_prev;
{
const size_t i = info->chunk_byte_size;
bchunk_list_append_data(info, bs_mem, chunk_list, data, i);
i_prev = i;
}
while (i_prev != data_trim_len) {
const size_t i = i_prev + info->chunk_byte_size;
BChunk *chunk = bchunk_new_copydata(bs_mem, &data[i_prev], i - i_prev);
bchunk_list_append_only(bs_mem, chunk_list, chunk);
i_prev = i;
}
if (data_last_chunk_len) {
BChunk *chunk = bchunk_new_copydata(bs_mem, &data[i_prev], data_last_chunk_len);
bchunk_list_append_only(bs_mem, chunk_list, chunk);
// i_prev = data_len; /* UNUSED */
}
}
else {
/* if we didn't write any chunks previously,
* we may need to merge with the last. */
if (data_last_chunk_len) {
bchunk_list_append_data(info, bs_mem, chunk_list, data, data_last_chunk_len);
// i_prev = data_len; /* UNUSED */
}
}
#ifdef USE_MERGE_CHUNKS
if (data_len > info->chunk_byte_size) {
BLI_assert(((BChunkRef *)chunk_list->chunk_refs.last)->link->data_len >= info->chunk_byte_size_min);
}
#endif
}
static void bchunk_list_append(
const BArrayInfo *info, BArrayMemory *bs_mem,
BChunkList *chunk_list,
BChunk *chunk)
{
bchunk_list_append_only(bs_mem, chunk_list, chunk);
#ifdef USE_MERGE_CHUNKS
bchunk_list_ensure_min_size_last(info, bs_mem, chunk_list);
#else
UNUSED_VARS(info);
#endif
}
static void bchunk_list_fill_from_array(
const BArrayInfo *info, BArrayMemory *bs_mem,
BChunkList *chunk_list,
const uchar *data,
const size_t data_len)
{
BLI_assert(BLI_listbase_is_empty(&chunk_list->chunk_refs));
size_t data_trim_len, data_last_chunk_len;
bchunk_list_calc_trim_len(info, data_len, &data_trim_len, &data_last_chunk_len);
size_t i_prev = 0;
while (i_prev != data_trim_len) {
const size_t i = i_prev + info->chunk_byte_size;
BChunk *chunk = bchunk_new_copydata(bs_mem, &data[i_prev], i - i_prev);
bchunk_list_append_only(bs_mem, chunk_list, chunk);
i_prev = i;
}
if (data_last_chunk_len) {
BChunk *chunk = bchunk_new_copydata(bs_mem, &data[i_prev], data_last_chunk_len);
bchunk_list_append_only(bs_mem, chunk_list, chunk);
// i_prev = data_len;
}
#ifdef USE_MERGE_CHUNKS
if (data_len > info->chunk_byte_size) {
BLI_assert(((BChunkRef *)chunk_list->chunk_refs.last)->link->data_len >= info->chunk_byte_size_min);
}
#endif
/* works but better avoid redundant re-alloc */
#if 0
#ifdef USE_MERGE_CHUNKS
bchunk_list_ensure_min_size_last(info, bs_mem, chunk_list);
#endif
#endif
ASSERT_CHUNKLIST_SIZE(chunk_list, data_len);
ASSERT_CHUNKLIST_DATA(chunk_list, data);
}
/** \} */
/* ---------------------------------------------------------------------------
* Internal Table Lookup Functions
*/
/** \name Internal Hashing/De-Duplication API
*
* Only used by #bchunk_list_from_data_merge
* \{ */
#define HASH_INIT (5381)
BLI_INLINE uint hash_data_single(const uchar p)
{
return ((HASH_INIT << 5) + HASH_INIT) + (unsigned int)(*((signed char *)&p));
}
/* hash bytes, from BLI_ghashutil_strhash_n */
static uint hash_data(const uchar *key, size_t n)
{
const signed char *p;
unsigned int h = HASH_INIT;
for (p = (const signed char *)key; n--; p++) {
h = ((h << 5) + h) + (unsigned int)*p;
}
return h;
}
#undef HASH_INIT
#ifdef USE_HASH_TABLE_ACCUMULATE
static void hash_array_from_data(
const BArrayInfo *info, const uchar *data_slice, const size_t data_slice_len,
hash_key *hash_array)
{
if (info->chunk_stride != 1) {
for (size_t i = 0, i_step = 0; i_step < data_slice_len; i++, i_step += info->chunk_stride) {
hash_array[i] = hash_data(&data_slice[i_step], info->chunk_stride);
}
}
else {
/* fast-path for bytes */
for (size_t i = 0; i < data_slice_len; i++) {
hash_array[i] = hash_data_single(data_slice[i]);
}
}
}
/*
* Similar to hash_array_from_data,
* but able to step into the next chunk if we run-out of data.
*/
static void hash_array_from_cref(
const BArrayInfo *info, const BChunkRef *cref, const size_t data_len,
hash_key *hash_array)
{
const size_t hash_array_len = data_len / info->chunk_stride;
size_t i = 0;
do {
size_t i_next = hash_array_len - i;
size_t data_trim_len = i_next * info->chunk_stride;
if (data_trim_len > cref->link->data_len) {
data_trim_len = cref->link->data_len;
i_next = data_trim_len / info->chunk_stride;
}
BLI_assert(data_trim_len <= cref->link->data_len);
hash_array_from_data(info, cref->link->data, data_trim_len, &hash_array[i]);
i += i_next;
cref = cref->next;
} while ((i < hash_array_len) && (cref != NULL));
/* If this isn't equal, the caller didn't properly check
* that there was enough data left in all chunks */
BLI_assert(i == hash_array_len);
}
static void hash_accum(hash_key *hash_array, const size_t hash_array_len, size_t iter_steps)
{
/* _very_ unlikely, can happen if you select a chunk-size of 1 for example. */
if (UNLIKELY((iter_steps > hash_array_len))) {
iter_steps = hash_array_len;
}
const size_t hash_array_search_len = hash_array_len - iter_steps;
while (iter_steps != 0) {
const size_t hash_offset = iter_steps;
for (uint i = 0; i < hash_array_search_len; i++) {
hash_array[i] += (hash_array[i + hash_offset]) * ((hash_array[i] & 0xff) + 1);
}
iter_steps -= 1;
}
}
/**
* When we only need a single value, can use a small optimization.
* we can avoid accumulating the tail of the array a little, each iteration.
*/
static void hash_accum_single(hash_key *hash_array, const size_t hash_array_len, size_t iter_steps)
{
BLI_assert(iter_steps <= hash_array_len);
if (UNLIKELY(!(iter_steps <= hash_array_len))) {
/* while this shouldn't happen, avoid crashing */
iter_steps = hash_array_len;
}
/* We can increase this value each step to avoid accumulating quite as much
* while getting the same results as hash_accum */
size_t iter_steps_sub = iter_steps;
while (iter_steps != 0) {
const size_t hash_array_search_len = hash_array_len - iter_steps_sub;
const size_t hash_offset = iter_steps;
for (uint i = 0; i < hash_array_search_len; i++) {
hash_array[i] += (hash_array[i + hash_offset]) * ((hash_array[i] & 0xff) + 1);
}
iter_steps -= 1;
iter_steps_sub += iter_steps;
}
}
static hash_key key_from_chunk_ref(
const BArrayInfo *info, const BChunkRef *cref,
/* avoid reallocating each time */
hash_key *hash_store, const size_t hash_store_len)
{
/* in C, will fill in a reusable array */
BChunk *chunk = cref->link;
BLI_assert((info->accum_read_ahead_bytes * info->chunk_stride) != 0);
if (info->accum_read_ahead_bytes <= chunk->data_len) {
hash_key key;
#ifdef USE_HASH_TABLE_KEY_CACHE
key = chunk->key;
if (key != HASH_TABLE_KEY_UNSET) {
/* Using key cache!
* avoids calculating every time */
}
else {
hash_array_from_cref(info, cref, info->accum_read_ahead_bytes, hash_store);
hash_accum_single(hash_store, hash_store_len, info->accum_steps);
key = hash_store[0];
/* cache the key */
if (UNLIKELY(key == HASH_TABLE_KEY_UNSET)) {
key = HASH_TABLE_KEY_FALLBACK;
}
chunk->key = key;
}
#else
hash_array_from_cref(info, cref, info->accum_read_ahead_bytes, hash_store);
hash_accum_single(hash_store, hash_store_len, info->accum_steps);
key = hash_store[0];
#endif
return key;
}
else {
/* corner case - we're too small, calculate the key each time. */
hash_array_from_cref(info, cref, info->accum_read_ahead_bytes, hash_store);
hash_accum_single(hash_store, hash_store_len, info->accum_steps);
hash_key key = hash_store[0];
#ifdef USE_HASH_TABLE_KEY_CACHE
if (UNLIKELY(key == HASH_TABLE_KEY_UNSET)) {
key = HASH_TABLE_KEY_FALLBACK;
}
#endif
return key;
}
}
static const BChunkRef *table_lookup(
const BArrayInfo *info, BTableRef **table, const size_t table_len, const size_t i_table_start,
const uchar *data, const size_t data_len, const size_t offset, const hash_key *table_hash_array)
{
size_t size_left = data_len - offset;
hash_key key = table_hash_array[((offset - i_table_start) / info->chunk_stride)];
size_t key_index = (size_t)(key % (hash_key)table_len);
for (const BTableRef *tref = table[key_index]; tref; tref = tref->next) {
const BChunkRef *cref = tref->cref;
#ifdef USE_HASH_TABLE_KEY_CACHE
if (cref->link->key == key)
#endif
{
BChunk *chunk_test = cref->link;
if (chunk_test->data_len <= size_left) {
if (bchunk_data_compare(chunk_test, data, data_len, offset)) {
/* we could remove the chunk from the table, to avoid multiple hits */
return cref;
}
}
}
}
return NULL;
}
#else /* USE_HASH_TABLE_ACCUMULATE */
/* NON USE_HASH_TABLE_ACCUMULATE code (simply hash each chunk) */
static hash_key key_from_chunk_ref(const BArrayInfo *info, const BChunkRef *cref)
{
const size_t data_hash_len = BCHUNK_HASH_LEN * info->chunk_stride;
hash_key key;
BChunk *chunk = cref->link;
#ifdef USE_HASH_TABLE_KEY_CACHE
key = chunk->key;
if (key != HASH_TABLE_KEY_UNSET) {
/* Using key cache!
* avoids calculating every time */
}
else {
/* cache the key */
key = hash_data(chunk->data, data_hash_len);
if (key == HASH_TABLE_KEY_UNSET) {
key = HASH_TABLE_KEY_FALLBACK;
}
chunk->key = key;
}
#else
key = hash_data(chunk->data, data_hash_len);
#endif
return key;
}
static const BChunkRef *table_lookup(
const BArrayInfo *info, BTableRef **table, const size_t table_len, const uint UNUSED(i_table_start),
const uchar *data, const size_t data_len, const size_t offset, const hash_key *UNUSED(table_hash_array))
{
const size_t data_hash_len = BCHUNK_HASH_LEN * info->chunk_stride; /* TODO, cache */
size_t size_left = data_len - offset;
hash_key key = hash_data(&data[offset], MIN2(data_hash_len, size_left));
size_t key_index = (size_t)(key % (hash_key)table_len);
for (BTableRef *tref = table[key_index]; tref; tref = tref->next) {
const BChunkRef *cref = tref->cref;
#ifdef USE_HASH_TABLE_KEY_CACHE
if (cref->link->key == key)
#endif
{
BChunk *chunk_test = cref->link;
if (chunk_test->data_len <= size_left) {
if (bchunk_data_compare(chunk_test, data, data_len, offset)) {
/* we could remove the chunk from the table, to avoid multiple hits */
return cref;
}
}
}
}
return NULL;
}
#endif /* USE_HASH_TABLE_ACCUMULATE */
/* End Table Lookup
* ---------------- */
/** \} */
/** \name Main Data De-Duplication Function
*
* \{ */
/**
* \param data: Data to store in the returned value.
* \param data_len_original: Length of data in bytes.
* \param chunk_list_reference: Reuse this list or chunks within it, don't modify its content.
* \note Caller is responsible for adding the user.
*/
static BChunkList *bchunk_list_from_data_merge(
const BArrayInfo *info, BArrayMemory *bs_mem,
const uchar *data, const size_t data_len_original,
const BChunkList *chunk_list_reference)
{
ASSERT_CHUNKLIST_SIZE(chunk_list_reference, chunk_list_reference->total_size);
/* -----------------------------------------------------------------------
* Fast-Path for exact match
* Check for exact match, if so, return the current list.
*/
const BChunkRef *cref_match_first = NULL;
uint chunk_list_reference_skip_len = 0;
size_t chunk_list_reference_skip_bytes = 0;
size_t i_prev = 0;
#ifdef USE_FASTPATH_CHUNKS_FIRST
{
bool full_match = true;
const BChunkRef *cref = chunk_list_reference->chunk_refs.first;
while (i_prev < data_len_original) {
if (cref != NULL && bchunk_data_compare(cref->link, data, data_len_original, i_prev)) {
cref_match_first = cref;
chunk_list_reference_skip_len += 1;
chunk_list_reference_skip_bytes += cref->link->data_len;
i_prev += cref->link->data_len;
cref = cref->next;
}
else {
full_match = false;
break;
}
}
if (full_match) {
if (chunk_list_reference->total_size == data_len_original) {
return (BChunkList *)chunk_list_reference;
}
}
}
/* End Fast-Path (first)
* --------------------- */
#endif /* USE_FASTPATH_CHUNKS_FIRST */
/* Copy until we have a mismatch */
BChunkList *chunk_list = bchunk_list_new(bs_mem, data_len_original);
if (cref_match_first != NULL) {
size_t chunk_size_step = 0;
const BChunkRef *cref = chunk_list_reference->chunk_refs.first;
while (true) {
BChunk *chunk = cref->link;
chunk_size_step += chunk->data_len;
bchunk_list_append_only(bs_mem, chunk_list, chunk);
ASSERT_CHUNKLIST_SIZE(chunk_list, chunk_size_step);
ASSERT_CHUNKLIST_DATA(chunk_list, data);
if (cref == cref_match_first) {
break;
}
else {
cref = cref->next;
}
}
/* happens when bytes are removed from the end of the array */
if (chunk_size_step == data_len_original) {
return chunk_list;
}
i_prev = chunk_size_step;
}
else {
i_prev = 0;
}
/* ------------------------------------------------------------------------
* Fast-Path for end chunks
*
* Check for trailing chunks
*/
/* In this case use 'chunk_list_reference_last' to define the last index
* index_match_last = -1 */
/* warning, from now on don't use len(data)
* since we want to ignore chunks already matched */
size_t data_len = data_len_original;
#define data_len_original invalid_usage
#ifdef data_len_original /* quiet warning */
#endif
const BChunkRef *chunk_list_reference_last = NULL;
#ifdef USE_FASTPATH_CHUNKS_LAST
if (!BLI_listbase_is_empty(&chunk_list_reference->chunk_refs)) {
const BChunkRef *cref = chunk_list_reference->chunk_refs.last;
while ((cref->prev != NULL) &&
(cref != cref_match_first) &&
(cref->link->data_len <= data_len - i_prev))
{
BChunk *chunk_test = cref->link;
size_t offset = data_len - chunk_test->data_len;
if (bchunk_data_compare(chunk_test, data, data_len, offset)) {
data_len = offset;
chunk_list_reference_last = cref;
chunk_list_reference_skip_len += 1;
chunk_list_reference_skip_bytes += cref->link->data_len;
cref = cref->prev;
}
else {
break;
}
}
}
/* End Fast-Path (last)
* -------------------- */
#endif /* USE_FASTPATH_CHUNKS_LAST */
/* -----------------------------------------------------------------------
* Check for aligned chunks
*
* This saves a lot of searching, so use simple heuristics to detect aligned arrays.
* (may need to tweak exact method).
*/
bool use_aligned = false;
#ifdef USE_ALIGN_CHUNKS_TEST
if (chunk_list->total_size == chunk_list_reference->total_size) {
/* if we're already a quarter aligned */
if (data_len - i_prev <= chunk_list->total_size / 4) {
use_aligned = true;
}
else {
/* TODO, walk over chunks and check if some arbitrary amount align */
}
}
#endif /* USE_ALIGN_CHUNKS_TEST */
/* End Aligned Chunk Case
* ----------------------- */
if (use_aligned) {
/* Copy matching chunks, creates using the same 'layout' as the reference */
const BChunkRef *cref = cref_match_first ? cref_match_first->next : chunk_list_reference->chunk_refs.first;
while (i_prev != data_len) {
const size_t i = i_prev + cref->link->data_len;
BLI_assert(i != i_prev);
if ((cref != chunk_list_reference_last) &&
bchunk_data_compare(cref->link, data, data_len, i_prev))
{
bchunk_list_append(info, bs_mem, chunk_list, cref->link);
ASSERT_CHUNKLIST_SIZE(chunk_list, i);
ASSERT_CHUNKLIST_DATA(chunk_list, data);
}
else {
bchunk_list_append_data(info, bs_mem, chunk_list, &data[i_prev], i - i_prev);
ASSERT_CHUNKLIST_SIZE(chunk_list, i);
ASSERT_CHUNKLIST_DATA(chunk_list, data);
}
cref = cref->next;
i_prev = i;
}
}
else if ((data_len - i_prev >= info->chunk_byte_size) &&
(chunk_list_reference->chunk_refs_len >= chunk_list_reference_skip_len) &&
(chunk_list_reference->chunk_refs.first != NULL))
{
/* --------------------------------------------------------------------
* Non-Aligned Chunk De-Duplication */
/* only create a table if we have at least one chunk to search
* otherwise just make a new one.
*
* Support re-arranged chunks */
#ifdef USE_HASH_TABLE_ACCUMULATE
size_t i_table_start = i_prev;
const size_t table_hash_array_len = (data_len - i_prev) / info->chunk_stride;
hash_key *table_hash_array = MEM_mallocN(sizeof(*table_hash_array) * table_hash_array_len, __func__);
hash_array_from_data(info, &data[i_prev], data_len - i_prev, table_hash_array);
hash_accum(table_hash_array, table_hash_array_len, info->accum_steps);
#else
/* dummy vars */
uint i_table_start = 0;
hash_key *table_hash_array = NULL;
#endif
const uint chunk_list_reference_remaining_len =
(chunk_list_reference->chunk_refs_len - chunk_list_reference_skip_len) + 1;
BTableRef *table_ref_stack = MEM_mallocN(chunk_list_reference_remaining_len * sizeof(BTableRef), __func__);
uint table_ref_stack_n = 0;
const size_t table_len = chunk_list_reference_remaining_len * BCHUNK_HASH_TABLE_MUL;
BTableRef **table = MEM_callocN(table_len * sizeof(*table), __func__);
/* table_make - inline
* include one matching chunk, to allow for repeating values */
{
#ifdef USE_HASH_TABLE_ACCUMULATE
const size_t hash_store_len = info->accum_read_ahead_len;
hash_key *hash_store = MEM_mallocN(sizeof(hash_key) * hash_store_len, __func__);
#endif
const BChunkRef *cref;
size_t chunk_list_reference_bytes_remaining =
chunk_list_reference->total_size - chunk_list_reference_skip_bytes;
if (cref_match_first) {
cref = cref_match_first;
chunk_list_reference_bytes_remaining += cref->link->data_len;
}
else {
cref = chunk_list_reference->chunk_refs.first;
}
#ifdef USE_PARANOID_CHECKS
{
size_t test_bytes_len = 0;
const BChunkRef *cr = cref;
while (cr != chunk_list_reference_last) {
test_bytes_len += cr->link->data_len;
cr = cr->next;
}
BLI_assert(test_bytes_len == chunk_list_reference_bytes_remaining);
}
#endif
while ((cref != chunk_list_reference_last) &&
(chunk_list_reference_bytes_remaining >= info->accum_read_ahead_bytes))
{
hash_key key = key_from_chunk_ref(info, cref
#ifdef USE_HASH_TABLE_ACCUMULATE
, hash_store, hash_store_len
#endif
);
size_t key_index = (size_t)(key % (hash_key)table_len);
BTableRef *tref_prev = table[key_index];
BLI_assert(table_ref_stack_n < chunk_list_reference_remaining_len);
BTableRef *tref = &table_ref_stack[table_ref_stack_n++];
tref->cref = cref;
tref->next = tref_prev;
table[key_index] = tref;
chunk_list_reference_bytes_remaining -= cref->link->data_len;
cref = cref->next;
}
BLI_assert(table_ref_stack_n <= chunk_list_reference_remaining_len);
#ifdef USE_HASH_TABLE_ACCUMULATE
MEM_freeN(hash_store);
#endif
}
/* done making the table */
BLI_assert(i_prev <= data_len);
for (size_t i = i_prev; i < data_len; ) {
/* Assumes exiting chunk isnt a match! */
const BChunkRef *cref_found = table_lookup(
info,
table, table_len, i_table_start,
data, data_len, i, table_hash_array);
if (cref_found != NULL) {
BLI_assert(i < data_len);
if (i != i_prev) {
bchunk_list_append_data_n(info, bs_mem, chunk_list, &data[i_prev], i - i_prev);
i_prev = i;
}
/* now add the reference chunk */
{
BChunk *chunk_found = cref_found->link;
i += chunk_found->data_len;
bchunk_list_append(info, bs_mem, chunk_list, chunk_found);
}
i_prev = i;
BLI_assert(i_prev <= data_len);
ASSERT_CHUNKLIST_SIZE(chunk_list, i_prev);
ASSERT_CHUNKLIST_DATA(chunk_list, data);
/* its likely that the next chunk in the list will be a match, so check it! */
while ((cref_found->next != NULL) &&
(cref_found->next != chunk_list_reference_last))
{
cref_found = cref_found->next;
BChunk *chunk_found = cref_found->link;
if (bchunk_data_compare(chunk_found, data, data_len, i_prev)) {
/* may be useful to remove table data, assuming we dont have repeating memory
* where it would be useful to re-use chunks. */
i += chunk_found->data_len;
bchunk_list_append(info, bs_mem, chunk_list, chunk_found);
/* chunk_found may be freed! */
i_prev = i;
BLI_assert(i_prev <= data_len);
ASSERT_CHUNKLIST_SIZE(chunk_list, i_prev);
ASSERT_CHUNKLIST_DATA(chunk_list, data);
}
else {
break;
}
}
}
else {
i = i + info->chunk_stride;
}
}
#ifdef USE_HASH_TABLE_ACCUMULATE
MEM_freeN(table_hash_array);
#endif
MEM_freeN(table);
MEM_freeN(table_ref_stack);
/* End Table Lookup
* ---------------- */
}
ASSERT_CHUNKLIST_SIZE(chunk_list, i_prev);
ASSERT_CHUNKLIST_DATA(chunk_list, data);
/* -----------------------------------------------------------------------
* No Duplicates to copy, write new chunks
*
* Trailing chunks, no matches found in table lookup above.
* Write all new data. */
if (i_prev != data_len) {
bchunk_list_append_data_n(info, bs_mem, chunk_list, &data[i_prev], data_len - i_prev);
i_prev = data_len;
}
BLI_assert(i_prev == data_len);
#ifdef USE_FASTPATH_CHUNKS_LAST
if (chunk_list_reference_last != NULL) {
/* write chunk_list_reference_last since it hasn't been written yet */
const BChunkRef *cref = chunk_list_reference_last;
while (cref != NULL) {
BChunk *chunk = cref->link;
// BLI_assert(bchunk_data_compare(chunk, data, data_len, i_prev));
i_prev += chunk->data_len;
/* use simple since we assume the references chunks have already been sized correctly. */
bchunk_list_append_only(bs_mem, chunk_list, chunk);
ASSERT_CHUNKLIST_DATA(chunk_list, data);
cref = cref->next;
}
}
#endif
#undef data_len_original
BLI_assert(i_prev == data_len_original);
/* check we're the correct size and that we didn't accidentally modify the reference */
ASSERT_CHUNKLIST_SIZE(chunk_list, data_len_original);
ASSERT_CHUNKLIST_SIZE(chunk_list_reference, chunk_list_reference->total_size);
ASSERT_CHUNKLIST_DATA(chunk_list, data);
return chunk_list;
}
/* end private API */
/** \} */
/** \name Main Array Storage API
* \{ */
/**
* Create a new array store, which can store any number of arrays
* as long as their stride matches.
*
* \param stride: ``sizeof()`` each element,
*
* \note while a stride of ``1`` will always work,
* its less efficient since duplicate chunks of memory will be searched
* at positions unaligned with the array data.
*
* \param chunk_count: Number of elements to split each chunk into.
* - A small value increases the ability to de-duplicate chunks,
* but adds overhead by increasing the number of chunks to look-up when searching for duplicates,
* as well as some overhead constructing the original array again, with more calls to ``memcpy``.
* - Larger values reduce the *book keeping* overhead,
* but increase the chance a small, isolated change will cause a larger amount of data to be duplicated.
*
* \return A new array store, to be freed with #BLI_array_store_destroy.
*/
BArrayStore *BLI_array_store_create(
uint stride,
uint chunk_count)
{
BArrayStore *bs = MEM_callocN(sizeof(BArrayStore), __func__);
bs->info.chunk_stride = stride;
// bs->info.chunk_count = chunk_count;
bs->info.chunk_byte_size = chunk_count * stride;
#ifdef USE_MERGE_CHUNKS
bs->info.chunk_byte_size_min = MAX2(1u, chunk_count / BCHUNK_SIZE_MIN_DIV) * stride;
bs->info.chunk_byte_size_max = (chunk_count * BCHUNK_SIZE_MAX_MUL) * stride;
#endif
#ifdef USE_HASH_TABLE_ACCUMULATE
bs->info.accum_steps = BCHUNK_HASH_TABLE_ACCUMULATE_STEPS - 1;
/* Triangle number, identifying now much read-ahead we need:
* https://en.wikipedia.org/wiki/Triangular_number (+ 1) */
bs->info.accum_read_ahead_len = (uint)((((bs->info.accum_steps * (bs->info.accum_steps + 1))) / 2) + 1);
bs->info.accum_read_ahead_bytes = bs->info.accum_read_ahead_len * stride;
#else
bs->info.accum_read_ahead_bytes = BCHUNK_HASH_LEN * stride;
#endif
bs->memory.chunk_list = BLI_mempool_create(sizeof(BChunkList), 0, 512, BLI_MEMPOOL_NOP);
bs->memory.chunk_ref = BLI_mempool_create(sizeof(BChunkRef), 0, 512, BLI_MEMPOOL_NOP);
/* allow iteration to simplify freeing, otherwise its not needed
* (we could loop over all states as an alternative). */
bs->memory.chunk = BLI_mempool_create(sizeof(BChunk), 0, 512, BLI_MEMPOOL_ALLOW_ITER);
return bs;
}
static void array_store_free_data(BArrayStore *bs)
{
/* free chunk data */
{
BLI_mempool_iter iter;
BChunk *chunk;
BLI_mempool_iternew(bs->memory.chunk, &iter);
while ((chunk = BLI_mempool_iterstep(&iter))) {
BLI_assert(chunk->users > 0);
MEM_freeN((void *)chunk->data);
}
}
/* free states */
for (BArrayState *state = bs->states.first, *state_next; state; state = state_next) {
state_next = state->next;
MEM_freeN(state);
}
}
/**
* Free the #BArrayStore, including all states and chunks.
*/
void BLI_array_store_destroy(
BArrayStore *bs)
{
array_store_free_data(bs);
BLI_mempool_destroy(bs->memory.chunk_list);
BLI_mempool_destroy(bs->memory.chunk_ref);
BLI_mempool_destroy(bs->memory.chunk);
MEM_freeN(bs);
}
/**
* Clear all contents, allowing reuse of \a bs.
*/
void BLI_array_store_clear(
BArrayStore *bs)
{
array_store_free_data(bs);
BLI_listbase_clear(&bs->states);
BLI_mempool_clear(bs->memory.chunk_list);
BLI_mempool_clear(bs->memory.chunk_ref);
BLI_mempool_clear(bs->memory.chunk);
}
/** \} */
/** \name BArrayStore Statistics
* \{ */
/**
* \return the total amount of memory that would be used by getting the arrays for all states.
*/
size_t BLI_array_store_calc_size_expanded_get(
const BArrayStore *bs)
{
size_t size_accum = 0;
for (const BArrayState *state = bs->states.first; state; state = state->next) {
size_accum += state->chunk_list->total_size;
}
return size_accum;
}
/**
* \return the amount of memory used by all #BChunk.data
* (duplicate chunks are only counted once).
*/
size_t BLI_array_store_calc_size_compacted_get(
const BArrayStore *bs)
{
size_t size_total = 0;
BLI_mempool_iter iter;
BChunk *chunk;
BLI_mempool_iternew(bs->memory.chunk, &iter);
while ((chunk = BLI_mempool_iterstep(&iter))) {
BLI_assert(chunk->users > 0);
size_total += (size_t)chunk->data_len;
}
return size_total;
}
/** \} */
/** \name BArrayState Access
* \{ */
/**
*
* \param data: Data used to create
* \param state_reference: The state to use as a reference when adding the new state,
* typically this is the previous state,
* however it can be any previously created state from this \a bs.
*
* \return The new state, which is used by the caller as a handle to get back the contents of \a data.
* This may be removed using #BLI_array_store_state_remove,
* otherwise it will be removed with #BLI_array_store_destroy.
*/
BArrayState *BLI_array_store_state_add(
BArrayStore *bs,
const void *data, const size_t data_len,
const BArrayState *state_reference)
{
/* ensure we're aligned to the stride */
BLI_assert((data_len % bs->info.chunk_stride) == 0);
#ifdef USE_PARANOID_CHECKS
if (state_reference) {
BLI_assert(BLI_findindex(&bs->states, state_reference) != -1);
}
#endif
BChunkList *chunk_list;
if (state_reference) {
chunk_list = bchunk_list_from_data_merge(
&bs->info, &bs->memory,
(const uchar *)data, data_len,
/* re-use reference chunks */
state_reference->chunk_list);
}
else {
chunk_list = bchunk_list_new(&bs->memory, data_len);
bchunk_list_fill_from_array(
&bs->info, &bs->memory,
chunk_list,
(const uchar *)data, data_len);
}
chunk_list->users += 1;
BArrayState *state = MEM_callocN(sizeof(BArrayState), __func__);
state->chunk_list = chunk_list;
BLI_addtail(&bs->states, state);
#ifdef USE_PARANOID_CHECKS
{
size_t data_test_len;
void *data_test = BLI_array_store_state_data_get_alloc(state, &data_test_len);
BLI_assert(data_test_len == data_len);
BLI_assert(memcmp(data_test, data, data_len) == 0);
MEM_freeN(data_test);
}
#endif
return state;
}
/**
* Remove a state and free any unused #BChunk data.
*
* The states can be freed in any order.
*/
void BLI_array_store_state_remove(
BArrayStore *bs,
BArrayState *state)
{
#ifdef USE_PARANOID_CHECKS
BLI_assert(BLI_findindex(&bs->states, state) != -1);
#endif
bchunk_list_decref(&bs->memory, state->chunk_list);
BLI_remlink(&bs->states, state);
MEM_freeN(state);
}
/**
* \return the expanded size of the array,
* use this to know how much memory to allocate #BLI_array_store_state_data_get's argument.
*/
size_t BLI_array_store_state_size_get(
BArrayState *state)
{
return state->chunk_list->total_size;
}
/**
* Fill in existing allocated memory with the contents of \a state.
*/
void BLI_array_store_state_data_get(
BArrayState *state,
void *data)
{
#ifdef USE_PARANOID_CHECKS
size_t data_test_len = 0;
for (BChunkRef *cref = state->chunk_list->chunk_refs.first; cref; cref = cref->next) {
data_test_len += cref->link->data_len;
}
BLI_assert(data_test_len == state->chunk_list->total_size);
#endif
uchar *data_step = (uchar *)data;
for (BChunkRef *cref = state->chunk_list->chunk_refs.first; cref; cref = cref->next) {
BLI_assert(cref->link->users > 0);
memcpy(data_step, cref->link->data, cref->link->data_len);
data_step += cref->link->data_len;
}
}
/**
* Allocate an array for \a state and return it.
*/
void *BLI_array_store_state_data_get_alloc(
BArrayState *state,
size_t *r_data_len)
{
void *data = MEM_mallocN(state->chunk_list->total_size, __func__);
BLI_array_store_state_data_get(state, data);
*r_data_len = state->chunk_list->total_size;
return data;
}
/** \} */
/** \name Debugging API (for testing).
* \{ */
/* only for test validation */
static size_t bchunk_list_size(const BChunkList *chunk_list)
{
size_t total_size = 0;
for (BChunkRef *cref = chunk_list->chunk_refs.first; cref; cref = cref->next) {
total_size += cref->link->data_len;
}
return total_size;
}
bool BLI_array_store_is_valid(
BArrayStore *bs)
{
bool ok = true;
/* Check Length
* ------------ */
for (BArrayState *state = bs->states.first; state; state = state->next) {
BChunkList *chunk_list = state->chunk_list;
if (!(bchunk_list_size(chunk_list) == chunk_list->total_size)) {
return false;
}
if (BLI_listbase_count(&chunk_list->chunk_refs) != (int)chunk_list->chunk_refs_len) {
return false;
}
#ifdef USE_MERGE_CHUNKS
/* ensure we merge all chunks that could be merged */
if (chunk_list->total_size > bs->info.chunk_byte_size_min) {
for (BChunkRef *cref = chunk_list->chunk_refs.first; cref; cref = cref->next) {
if (cref->link->data_len < bs->info.chunk_byte_size_min) {
return false;
}
}
}
#endif
}
{
BLI_mempool_iter iter;
BChunk *chunk;
BLI_mempool_iternew(bs->memory.chunk, &iter);
while ((chunk = BLI_mempool_iterstep(&iter))) {
if (!(MEM_allocN_len(chunk->data) >= chunk->data_len)) {
return false;
}
}
}
/* Check User Count & Lost References
* ---------------------------------- */
{
GHashIterator gh_iter;
#define GHASH_PTR_ADD_USER(gh, pt) \
{ \
void **val; \
if (BLI_ghash_ensure_p((gh), (pt), &val)) { \
*((int *)val) += 1; \
} \
else { \
*((int *)val) = 1; \
} \
} ((void)0)
/* count chunk_list's */
GHash *chunk_list_map = BLI_ghash_ptr_new(__func__);
GHash *chunk_map = BLI_ghash_ptr_new(__func__);
int totrefs = 0;
for (BArrayState *state = bs->states.first; state; state = state->next) {
GHASH_PTR_ADD_USER(chunk_list_map, state->chunk_list);
}
GHASH_ITER (gh_iter, chunk_list_map) {
const struct BChunkList *chunk_list = BLI_ghashIterator_getKey(&gh_iter);
const int users = GET_INT_FROM_POINTER(BLI_ghashIterator_getValue(&gh_iter));
if (!(chunk_list->users == users)) {
ok = false;
goto user_finally;
}
}
if (!(BLI_mempool_len(bs->memory.chunk_list) == (int)BLI_ghash_len(chunk_list_map))) {
ok = false;
goto user_finally;
}
/* count chunk's */
GHASH_ITER (gh_iter, chunk_list_map) {
const struct BChunkList *chunk_list = BLI_ghashIterator_getKey(&gh_iter);
for (const BChunkRef *cref = chunk_list->chunk_refs.first; cref; cref = cref->next) {
GHASH_PTR_ADD_USER(chunk_map, cref->link);
totrefs += 1;
}
}
if (!(BLI_mempool_len(bs->memory.chunk) == (int)BLI_ghash_len(chunk_map))) {
ok = false;
goto user_finally;
}
if (!(BLI_mempool_len(bs->memory.chunk_ref) == totrefs)) {
ok = false;
goto user_finally;
}
GHASH_ITER (gh_iter, chunk_map) {
const struct BChunk *chunk = BLI_ghashIterator_getKey(&gh_iter);
const int users = GET_INT_FROM_POINTER(BLI_ghashIterator_getValue(&gh_iter));
if (!(chunk->users == users)) {
ok = false;
goto user_finally;
}
}
#undef GHASH_PTR_ADD_USER
user_finally:
BLI_ghash_free(chunk_list_map, NULL, NULL);
BLI_ghash_free(chunk_map, NULL, NULL);
}
return ok;
/* TODO, dangling pointer checks */
}
/** \} */