Blender  V2.93
array_store.c
Go to the documentation of this file.
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  */
16 
100 #include <stdlib.h>
101 #include <string.h>
102 
103 #include "MEM_guardedalloc.h"
104 
105 #include "BLI_listbase.h"
106 #include "BLI_mempool.h"
107 
108 #include "BLI_strict_flags.h"
109 
110 #include "BLI_array_store.h" /* own include */
111 
112 /* only for BLI_array_store_is_valid */
113 #include "BLI_ghash.h"
114 
115 /* -------------------------------------------------------------------- */
122 /* Scan first chunks (happy path when beginning of the array matches).
123  * When the array is a perfect match, we can re-use the entire list.
124  *
125  * Note that disabling makes some tests fail that check for output-size.
126  */
127 #define USE_FASTPATH_CHUNKS_FIRST
128 
129 /* Scan last chunks (happy path when end of the array matches).
130  * When the end of the array matches, we can quickly add these chunks.
131  * note that we will add contiguous matching chunks
132  * so this isn't as useful as USE_FASTPATH_CHUNKS_FIRST,
133  * however it avoids adding matching chunks into the lookup table,
134  * so creating the lookup table won't be as expensive.
135  */
136 #ifdef USE_FASTPATH_CHUNKS_FIRST
137 # define USE_FASTPATH_CHUNKS_LAST
138 #endif
139 
140 /* For arrays of matching length, test that *enough* of the chunks are aligned,
141  * and simply step over both arrays, using matching chunks.
142  * This avoids overhead of using a lookup table for cases
143  * when we can assume they're mostly aligned.
144  */
145 #define USE_ALIGN_CHUNKS_TEST
146 
147 /* Accumulate hashes from right to left so we can create a hash for the chunk-start.
148  * This serves to increase uniqueness and will help when there is many values which are the same.
149  */
150 #define USE_HASH_TABLE_ACCUMULATE
151 
152 #ifdef USE_HASH_TABLE_ACCUMULATE
153 /* Number of times to propagate hashes back.
154  * Effectively a 'triangle-number'.
155  * so 4 -> 7, 5 -> 10, 6 -> 15... etc.
156  */
157 # define BCHUNK_HASH_TABLE_ACCUMULATE_STEPS 4
158 #else
159 /* How many items to hash (multiplied by stride)
160  */
161 # define BCHUNK_HASH_LEN 4
162 #endif
163 
164 /* Calculate the key once and reuse it
165  */
166 #define USE_HASH_TABLE_KEY_CACHE
167 #ifdef USE_HASH_TABLE_KEY_CACHE
168 # define HASH_TABLE_KEY_UNSET ((uint64_t)-1)
169 # define HASH_TABLE_KEY_FALLBACK ((uint64_t)-2)
170 #endif
171 
172 /* How much larger the table is then the total number of chunks.
173  */
174 #define BCHUNK_HASH_TABLE_MUL 3
175 
176 /* Merge too small/large chunks:
177  *
178  * Using this means chunks below a threshold will be merged together.
179  * Even though short term this uses more memory,
180  * long term the overhead of maintaining many small chunks is reduced.
181  * This is defined by setting the minimum chunk size (as a fraction of the regular chunk size).
182  *
183  * Chunks may also become too large (when incrementally growing an array),
184  * this also enables chunk splitting.
185  */
186 #define USE_MERGE_CHUNKS
187 
188 #ifdef USE_MERGE_CHUNKS
189 /* Merge chunks smaller then: (chunk_size / BCHUNK_MIN_SIZE_DIV)
190  */
191 # define BCHUNK_SIZE_MIN_DIV 8
192 
193 /* Disallow chunks bigger than the regular chunk size scaled by this value
194  * note: must be at least 2!
195  * however, this code runs wont run in tests unless its ~1.1 ugh.
196  * so lower only to check splitting works.
197  */
198 # define BCHUNK_SIZE_MAX_MUL 2
199 #endif /* USE_MERGE_CHUNKS */
200 
201 /* slow (keep disabled), but handy for debugging */
202 // #define USE_VALIDATE_LIST_SIZE
203 
204 // #define USE_VALIDATE_LIST_DATA_PARTIAL
205 
206 // #define USE_PARANOID_CHECKS
207 
210 /* -------------------------------------------------------------------- */
215 
216 typedef struct BArrayInfo {
217  size_t chunk_stride;
218  // uint chunk_count; /* UNUSED (other values are derived from this) */
219 
220  /* pre-calculated */
222  /* min/max limits (inclusive) */
225 
227 #ifdef USE_HASH_TABLE_ACCUMULATE
228  size_t accum_steps;
230 #endif
232 
233 typedef struct BArrayMemory {
234  BLI_mempool *chunk_list; /* BChunkList */
235  BLI_mempool *chunk_ref; /* BChunkRef */
236  BLI_mempool *chunk; /* BChunk */
238 
242 struct BArrayStore {
243  /* static */
245 
246  /* memory storage */
248 
253 };
254 
265 struct BArrayState {
267  struct BArrayState *next, *prev;
268 
269  struct BChunkList *chunk_list; /* BChunkList's */
270 };
271 
272 typedef struct BChunkList {
273  ListBase chunk_refs; /* BChunkRef's */
274  uint chunk_refs_len; /* BLI_listbase_count(chunks), store for reuse. */
275  size_t total_size; /* size of all chunks */
276 
278  int users;
280 
281 /* a chunk of an array */
282 typedef struct BChunk {
283  const uchar *data;
284  size_t data_len;
286  int users;
287 
288 #ifdef USE_HASH_TABLE_KEY_CACHE
290 #endif
292 
296 typedef struct BChunkRef {
297  struct BChunkRef *next, *prev;
300 
309 typedef struct BTableRef {
310  struct BTableRef *next;
311  const BChunkRef *cref;
313 
316 static size_t bchunk_list_size(const BChunkList *chunk_list);
317 
318 /* -------------------------------------------------------------------- */
322 static BChunk *bchunk_new(BArrayMemory *bs_mem, const uchar *data, const size_t data_len)
323 {
324  BChunk *chunk = BLI_mempool_alloc(bs_mem->chunk);
325  chunk->data = data;
326  chunk->data_len = data_len;
327  chunk->users = 0;
328 #ifdef USE_HASH_TABLE_KEY_CACHE
329  chunk->key = HASH_TABLE_KEY_UNSET;
330 #endif
331  return chunk;
332 }
333 
334 static BChunk *bchunk_new_copydata(BArrayMemory *bs_mem, const uchar *data, const size_t data_len)
335 {
336  uchar *data_copy = MEM_mallocN(data_len, __func__);
337  memcpy(data_copy, data, data_len);
338  return bchunk_new(bs_mem, data_copy, data_len);
339 }
340 
341 static void bchunk_decref(BArrayMemory *bs_mem, BChunk *chunk)
342 {
343  BLI_assert(chunk->users > 0);
344  if (chunk->users == 1) {
345  MEM_freeN((void *)chunk->data);
346  BLI_mempool_free(bs_mem->chunk, chunk);
347  }
348  else {
349  chunk->users -= 1;
350  }
351 }
352 
353 static bool bchunk_data_compare(const BChunk *chunk,
354  const uchar *data_base,
355  const size_t data_base_len,
356  const size_t offset)
357 {
358  if (offset + (size_t)chunk->data_len <= data_base_len) {
359  return (memcmp(&data_base[offset], chunk->data, chunk->data_len) == 0);
360  }
361  return false;
362 }
363 
366 /* -------------------------------------------------------------------- */
370 static BChunkList *bchunk_list_new(BArrayMemory *bs_mem, size_t total_size)
371 {
372  BChunkList *chunk_list = BLI_mempool_alloc(bs_mem->chunk_list);
373 
374  BLI_listbase_clear(&chunk_list->chunk_refs);
375  chunk_list->chunk_refs_len = 0;
376  chunk_list->total_size = total_size;
377  chunk_list->users = 0;
378  return chunk_list;
379 }
380 
381 static void bchunk_list_decref(BArrayMemory *bs_mem, BChunkList *chunk_list)
382 {
383  BLI_assert(chunk_list->users > 0);
384  if (chunk_list->users == 1) {
385  for (BChunkRef *cref = chunk_list->chunk_refs.first, *cref_next; cref; cref = cref_next) {
386  cref_next = cref->next;
387  bchunk_decref(bs_mem, cref->link);
388  BLI_mempool_free(bs_mem->chunk_ref, cref);
389  }
390 
391  BLI_mempool_free(bs_mem->chunk_list, chunk_list);
392  }
393  else {
394  chunk_list->users -= 1;
395  }
396 }
397 
398 #ifdef USE_VALIDATE_LIST_SIZE
399 # ifndef NDEBUG
400 # define ASSERT_CHUNKLIST_SIZE(chunk_list, n) BLI_assert(bchunk_list_size(chunk_list) == n)
401 # endif
402 #endif
403 #ifndef ASSERT_CHUNKLIST_SIZE
404 # define ASSERT_CHUNKLIST_SIZE(chunk_list, n) (EXPR_NOP(chunk_list), EXPR_NOP(n))
405 #endif
406 
407 #ifdef USE_VALIDATE_LIST_DATA_PARTIAL
408 static size_t bchunk_list_data_check(const BChunkList *chunk_list, const uchar *data)
409 {
410  size_t offset = 0;
411  LISTBASE_FOREACH (BChunkRef *, cref, &chunk_list->chunk_refs) {
412  if (memcmp(&data[offset], cref->link->data, cref->link->data_len) != 0) {
413  return false;
414  }
415  offset += cref->link->data_len;
416  }
417  return true;
418 }
419 # define ASSERT_CHUNKLIST_DATA(chunk_list, data) \
420  BLI_assert(bchunk_list_data_check(chunk_list, data))
421 #else
422 # define ASSERT_CHUNKLIST_DATA(chunk_list, data) (EXPR_NOP(chunk_list), EXPR_NOP(data))
423 #endif
424 
425 #ifdef USE_MERGE_CHUNKS
427  BArrayMemory *bs_mem,
428  BChunkList *chunk_list)
429 {
430  BChunkRef *cref = chunk_list->chunk_refs.last;
431  if (cref && cref->prev) {
432  /* both are decref'd after use (end of this block) */
433  BChunk *chunk_curr = cref->link;
434  BChunk *chunk_prev = cref->prev->link;
435 
436  if (MIN2(chunk_prev->data_len, chunk_curr->data_len) < info->chunk_byte_size_min) {
437  const size_t data_merge_len = chunk_prev->data_len + chunk_curr->data_len;
438  /* we could pass, but no need */
439  if (data_merge_len <= info->chunk_byte_size_max) {
440  /* we have enough space to merge */
441 
442  /* remove last from linklist */
443  BLI_assert(chunk_list->chunk_refs.last != chunk_list->chunk_refs.first);
444  cref->prev->next = NULL;
445  chunk_list->chunk_refs.last = cref->prev;
446  chunk_list->chunk_refs_len -= 1;
447 
448  uchar *data_merge = MEM_mallocN(data_merge_len, __func__);
449  memcpy(data_merge, chunk_prev->data, chunk_prev->data_len);
450  memcpy(&data_merge[chunk_prev->data_len], chunk_curr->data, chunk_curr->data_len);
451 
452  cref->prev->link = bchunk_new(bs_mem, data_merge, data_merge_len);
453  cref->prev->link->users += 1;
454 
455  BLI_mempool_free(bs_mem->chunk_ref, cref);
456  }
457  else {
458  /* If we always merge small slices, we should _almost_
459  * never end up having very large chunks.
460  * Gradual expanding on contracting will cause this.
461  *
462  * if we do, the code below works (test by setting 'BCHUNK_SIZE_MAX_MUL = 1.2') */
463 
464  /* keep chunk on the left hand side a regular size */
465  const size_t split = info->chunk_byte_size;
466 
467  /* merge and split */
468  const size_t data_prev_len = split;
469  const size_t data_curr_len = data_merge_len - split;
470  uchar *data_prev = MEM_mallocN(data_prev_len, __func__);
471  uchar *data_curr = MEM_mallocN(data_curr_len, __func__);
472 
473  if (data_prev_len <= chunk_prev->data_len) {
474  const size_t data_curr_shrink_len = chunk_prev->data_len - data_prev_len;
475 
476  /* setup 'data_prev' */
477  memcpy(data_prev, chunk_prev->data, data_prev_len);
478 
479  /* setup 'data_curr' */
480  memcpy(data_curr, &chunk_prev->data[data_prev_len], data_curr_shrink_len);
481  memcpy(&data_curr[data_curr_shrink_len], chunk_curr->data, chunk_curr->data_len);
482  }
483  else {
484  BLI_assert(data_curr_len <= chunk_curr->data_len);
485  BLI_assert(data_prev_len >= chunk_prev->data_len);
486 
487  const size_t data_prev_grow_len = data_prev_len - chunk_prev->data_len;
488 
489  /* setup 'data_prev' */
490  memcpy(data_prev, chunk_prev->data, chunk_prev->data_len);
491  memcpy(&data_prev[chunk_prev->data_len], chunk_curr->data, data_prev_grow_len);
492 
493  /* setup 'data_curr' */
494  memcpy(data_curr, &chunk_curr->data[data_prev_grow_len], data_curr_len);
495  }
496 
497  cref->prev->link = bchunk_new(bs_mem, data_prev, data_prev_len);
498  cref->prev->link->users += 1;
499 
500  cref->link = bchunk_new(bs_mem, data_curr, data_curr_len);
501  cref->link->users += 1;
502  }
503 
504  /* free zero users */
505  bchunk_decref(bs_mem, chunk_curr);
506  bchunk_decref(bs_mem, chunk_prev);
507  }
508  }
509 }
510 #endif /* USE_MERGE_CHUNKS */
511 
520 static void bchunk_list_calc_trim_len(const BArrayInfo *info,
521  const size_t data_len,
522  size_t *r_data_trim_len,
523  size_t *r_data_last_chunk_len)
524 {
525  size_t data_last_chunk_len = 0;
526  size_t data_trim_len = data_len;
527 
528 #ifdef USE_MERGE_CHUNKS
529  /* avoid creating too-small chunks
530  * more efficient than merging after */
531  if (data_len > info->chunk_byte_size) {
532  data_last_chunk_len = (data_trim_len % info->chunk_byte_size);
533  data_trim_len = data_trim_len - data_last_chunk_len;
534  if (data_last_chunk_len) {
535  if (data_last_chunk_len < info->chunk_byte_size_min) {
536  /* May be zero and that's OK. */
537  data_trim_len -= info->chunk_byte_size;
538  data_last_chunk_len += info->chunk_byte_size;
539  }
540  }
541  }
542  else {
543  data_trim_len = 0;
544  data_last_chunk_len = data_len;
545  }
546 
547  BLI_assert((data_trim_len == 0) || (data_trim_len >= info->chunk_byte_size));
548 #else
549  data_last_chunk_len = (data_trim_len % info->chunk_byte_size);
550  data_trim_len = data_trim_len - data_last_chunk_len;
551 #endif
552 
553  BLI_assert(data_trim_len + data_last_chunk_len == data_len);
554 
555  *r_data_trim_len = data_trim_len;
556  *r_data_last_chunk_len = data_last_chunk_len;
557 }
558 
562 static void bchunk_list_append_only(BArrayMemory *bs_mem, BChunkList *chunk_list, BChunk *chunk)
563 {
565  BLI_addtail(&chunk_list->chunk_refs, cref);
566  cref->link = chunk;
567  chunk_list->chunk_refs_len += 1;
568  chunk->users += 1;
569 }
570 
575 static void bchunk_list_append_data(const BArrayInfo *info,
576  BArrayMemory *bs_mem,
577  BChunkList *chunk_list,
578  const uchar *data,
579  const size_t data_len)
580 {
581  BLI_assert(data_len != 0);
582 
583 #ifdef USE_MERGE_CHUNKS
584  BLI_assert(data_len <= info->chunk_byte_size_max);
585 
586  if (!BLI_listbase_is_empty(&chunk_list->chunk_refs)) {
587  BChunkRef *cref = chunk_list->chunk_refs.last;
588  BChunk *chunk_prev = cref->link;
589 
590  if (MIN2(chunk_prev->data_len, data_len) < info->chunk_byte_size_min) {
591  const size_t data_merge_len = chunk_prev->data_len + data_len;
592  /* realloc for single user */
593  if (cref->link->users == 1) {
594  uchar *data_merge = MEM_reallocN((void *)cref->link->data, data_merge_len);
595  memcpy(&data_merge[chunk_prev->data_len], data, data_len);
596  cref->link->data = data_merge;
597  cref->link->data_len = data_merge_len;
598  }
599  else {
600  uchar *data_merge = MEM_mallocN(data_merge_len, __func__);
601  memcpy(data_merge, chunk_prev->data, chunk_prev->data_len);
602  memcpy(&data_merge[chunk_prev->data_len], data, data_len);
603  cref->link = bchunk_new(bs_mem, data_merge, data_merge_len);
604  cref->link->users += 1;
605  bchunk_decref(bs_mem, chunk_prev);
606  }
607  return;
608  }
609  }
610 #else
611  UNUSED_VARS(info);
612 #endif /* USE_MERGE_CHUNKS */
613 
614  BChunk *chunk = bchunk_new_copydata(bs_mem, data, data_len);
615  bchunk_list_append_only(bs_mem, chunk_list, chunk);
616 
617  /* don't run this, instead preemptively avoid creating a chunk only to merge it (above). */
618 #if 0
619 # ifdef USE_MERGE_CHUNKS
620  bchunk_list_ensure_min_size_last(info, bs_mem, chunk_list);
621 # endif
622 #endif
623 }
624 
632 static void bchunk_list_append_data_n(const BArrayInfo *info,
633  BArrayMemory *bs_mem,
634  BChunkList *chunk_list,
635  const uchar *data,
636  size_t data_len)
637 {
638  size_t data_trim_len, data_last_chunk_len;
639  bchunk_list_calc_trim_len(info, data_len, &data_trim_len, &data_last_chunk_len);
640 
641  if (data_trim_len != 0) {
642  size_t i_prev;
643 
644  {
645  const size_t i = info->chunk_byte_size;
646  bchunk_list_append_data(info, bs_mem, chunk_list, data, i);
647  i_prev = i;
648  }
649 
650  while (i_prev != data_trim_len) {
651  const size_t i = i_prev + info->chunk_byte_size;
652  BChunk *chunk = bchunk_new_copydata(bs_mem, &data[i_prev], i - i_prev);
653  bchunk_list_append_only(bs_mem, chunk_list, chunk);
654  i_prev = i;
655  }
656 
657  if (data_last_chunk_len) {
658  BChunk *chunk = bchunk_new_copydata(bs_mem, &data[i_prev], data_last_chunk_len);
659  bchunk_list_append_only(bs_mem, chunk_list, chunk);
660  // i_prev = data_len; /* UNUSED */
661  }
662  }
663  else {
664  /* if we didn't write any chunks previously,
665  * we may need to merge with the last. */
666  if (data_last_chunk_len) {
667  bchunk_list_append_data(info, bs_mem, chunk_list, data, data_last_chunk_len);
668  // i_prev = data_len; /* UNUSED */
669  }
670  }
671 
672 #ifdef USE_MERGE_CHUNKS
673  if (data_len > info->chunk_byte_size) {
674  BLI_assert(((BChunkRef *)chunk_list->chunk_refs.last)->link->data_len >=
675  info->chunk_byte_size_min);
676  }
677 #endif
678 }
679 
680 static void bchunk_list_append(const BArrayInfo *info,
681  BArrayMemory *bs_mem,
682  BChunkList *chunk_list,
683  BChunk *chunk)
684 {
685  bchunk_list_append_only(bs_mem, chunk_list, chunk);
686 
687 #ifdef USE_MERGE_CHUNKS
688  bchunk_list_ensure_min_size_last(info, bs_mem, chunk_list);
689 #else
690  UNUSED_VARS(info);
691 #endif
692 }
693 
694 static void bchunk_list_fill_from_array(const BArrayInfo *info,
695  BArrayMemory *bs_mem,
696  BChunkList *chunk_list,
697  const uchar *data,
698  const size_t data_len)
699 {
701 
702  size_t data_trim_len, data_last_chunk_len;
703  bchunk_list_calc_trim_len(info, data_len, &data_trim_len, &data_last_chunk_len);
704 
705  size_t i_prev = 0;
706  while (i_prev != data_trim_len) {
707  const size_t i = i_prev + info->chunk_byte_size;
708  BChunk *chunk = bchunk_new_copydata(bs_mem, &data[i_prev], i - i_prev);
709  bchunk_list_append_only(bs_mem, chunk_list, chunk);
710  i_prev = i;
711  }
712 
713  if (data_last_chunk_len) {
714  BChunk *chunk = bchunk_new_copydata(bs_mem, &data[i_prev], data_last_chunk_len);
715  bchunk_list_append_only(bs_mem, chunk_list, chunk);
716  // i_prev = data_len;
717  }
718 
719 #ifdef USE_MERGE_CHUNKS
720  if (data_len > info->chunk_byte_size) {
721  BLI_assert(((BChunkRef *)chunk_list->chunk_refs.last)->link->data_len >=
722  info->chunk_byte_size_min);
723  }
724 #endif
725 
726  /* works but better avoid redundant re-alloc */
727 #if 0
728 # ifdef USE_MERGE_CHUNKS
729  bchunk_list_ensure_min_size_last(info, bs_mem, chunk_list);
730 # endif
731 #endif
732 
733  ASSERT_CHUNKLIST_SIZE(chunk_list, data_len);
734  ASSERT_CHUNKLIST_DATA(chunk_list, data);
735 }
736 
739 /*
740  * Internal Table Lookup Functions
741  */
742 
743 /* -------------------------------------------------------------------- */
749 #define HASH_INIT (5381)
750 
752 {
753  return ((HASH_INIT << 5) + HASH_INIT) + (unsigned int)(*((signed char *)&p));
754 }
755 
756 /* hash bytes, from BLI_ghashutil_strhash_n */
757 static uint hash_data(const uchar *key, size_t n)
758 {
759  const signed char *p;
760  unsigned int h = HASH_INIT;
761 
762  for (p = (const signed char *)key; n--; p++) {
763  h = ((h << 5) + h) + (unsigned int)*p;
764  }
765 
766  return h;
767 }
768 
769 #undef HASH_INIT
770 
771 #ifdef USE_HASH_TABLE_ACCUMULATE
772 static void hash_array_from_data(const BArrayInfo *info,
773  const uchar *data_slice,
774  const size_t data_slice_len,
775  hash_key *hash_array)
776 {
777  if (info->chunk_stride != 1) {
778  for (size_t i = 0, i_step = 0; i_step < data_slice_len; i++, i_step += info->chunk_stride) {
779  hash_array[i] = hash_data(&data_slice[i_step], info->chunk_stride);
780  }
781  }
782  else {
783  /* fast-path for bytes */
784  for (size_t i = 0; i < data_slice_len; i++) {
785  hash_array[i] = hash_data_single(data_slice[i]);
786  }
787  }
788 }
789 
790 /*
791  * Similar to hash_array_from_data,
792  * but able to step into the next chunk if we run-out of data.
793  */
794 static void hash_array_from_cref(const BArrayInfo *info,
795  const BChunkRef *cref,
796  const size_t data_len,
797  hash_key *hash_array)
798 {
799  const size_t hash_array_len = data_len / info->chunk_stride;
800  size_t i = 0;
801  do {
802  size_t i_next = hash_array_len - i;
803  size_t data_trim_len = i_next * info->chunk_stride;
804  if (data_trim_len > cref->link->data_len) {
805  data_trim_len = cref->link->data_len;
806  i_next = data_trim_len / info->chunk_stride;
807  }
808  BLI_assert(data_trim_len <= cref->link->data_len);
809  hash_array_from_data(info, cref->link->data, data_trim_len, &hash_array[i]);
810  i += i_next;
811  cref = cref->next;
812  } while ((i < hash_array_len) && (cref != NULL));
813 
814  /* If this isn't equal, the caller didn't properly check
815  * that there was enough data left in all chunks */
816  BLI_assert(i == hash_array_len);
817 }
818 
819 static void hash_accum(hash_key *hash_array, const size_t hash_array_len, size_t iter_steps)
820 {
821  /* _very_ unlikely, can happen if you select a chunk-size of 1 for example. */
822  if (UNLIKELY((iter_steps > hash_array_len))) {
823  iter_steps = hash_array_len;
824  }
825 
826  const size_t hash_array_search_len = hash_array_len - iter_steps;
827  while (iter_steps != 0) {
828  const size_t hash_offset = iter_steps;
829  for (uint i = 0; i < hash_array_search_len; i++) {
830  hash_array[i] += (hash_array[i + hash_offset]) * ((hash_array[i] & 0xff) + 1);
831  }
832  iter_steps -= 1;
833  }
834 }
835 
840 static void hash_accum_single(hash_key *hash_array, const size_t hash_array_len, size_t iter_steps)
841 {
842  BLI_assert(iter_steps <= hash_array_len);
843  if (UNLIKELY(!(iter_steps <= hash_array_len))) {
844  /* while this shouldn't happen, avoid crashing */
845  iter_steps = hash_array_len;
846  }
847  /* We can increase this value each step to avoid accumulating quite as much
848  * while getting the same results as hash_accum */
849  size_t iter_steps_sub = iter_steps;
850 
851  while (iter_steps != 0) {
852  const size_t hash_array_search_len = hash_array_len - iter_steps_sub;
853  const size_t hash_offset = iter_steps;
854  for (uint i = 0; i < hash_array_search_len; i++) {
855  hash_array[i] += (hash_array[i + hash_offset]) * ((hash_array[i] & 0xff) + 1);
856  }
857  iter_steps -= 1;
858  iter_steps_sub += iter_steps;
859  }
860 }
861 
863  const BChunkRef *cref,
864  /* avoid reallocating each time */
865  hash_key *hash_store,
866  const size_t hash_store_len)
867 {
868  /* in C, will fill in a reusable array */
869  BChunk *chunk = cref->link;
870  BLI_assert((info->accum_read_ahead_bytes * info->chunk_stride) != 0);
871 
872  if (info->accum_read_ahead_bytes <= chunk->data_len) {
873  hash_key key;
874 
875 # ifdef USE_HASH_TABLE_KEY_CACHE
876  key = chunk->key;
877  if (key != HASH_TABLE_KEY_UNSET) {
878  /* Using key cache!
879  * avoids calculating every time */
880  }
881  else {
882  hash_array_from_cref(info, cref, info->accum_read_ahead_bytes, hash_store);
883  hash_accum_single(hash_store, hash_store_len, info->accum_steps);
884  key = hash_store[0];
885 
886  /* cache the key */
887  if (UNLIKELY(key == HASH_TABLE_KEY_UNSET)) {
889  }
890  chunk->key = key;
891  }
892 # else
893  hash_array_from_cref(info, cref, info->accum_read_ahead_bytes, hash_store);
894  hash_accum_single(hash_store, hash_store_len, info->accum_steps);
895  key = hash_store[0];
896 # endif
897  return key;
898  }
899  /* corner case - we're too small, calculate the key each time. */
900 
901  hash_array_from_cref(info, cref, info->accum_read_ahead_bytes, hash_store);
902  hash_accum_single(hash_store, hash_store_len, info->accum_steps);
903  hash_key key = hash_store[0];
904 
905 # ifdef USE_HASH_TABLE_KEY_CACHE
906  if (UNLIKELY(key == HASH_TABLE_KEY_UNSET)) {
908  }
909 # endif
910  return key;
911 }
912 
913 static const BChunkRef *table_lookup(const BArrayInfo *info,
914  BTableRef **table,
915  const size_t table_len,
916  const size_t i_table_start,
917  const uchar *data,
918  const size_t data_len,
919  const size_t offset,
920  const hash_key *table_hash_array)
921 {
922  size_t size_left = data_len - offset;
923  hash_key key = table_hash_array[((offset - i_table_start) / info->chunk_stride)];
924  size_t key_index = (size_t)(key % (hash_key)table_len);
925  for (const BTableRef *tref = table[key_index]; tref; tref = tref->next) {
926  const BChunkRef *cref = tref->cref;
927 # ifdef USE_HASH_TABLE_KEY_CACHE
928  if (cref->link->key == key)
929 # endif
930  {
931  BChunk *chunk_test = cref->link;
932  if (chunk_test->data_len <= size_left) {
933  if (bchunk_data_compare(chunk_test, data, data_len, offset)) {
934  /* we could remove the chunk from the table, to avoid multiple hits */
935  return cref;
936  }
937  }
938  }
939  }
940  return NULL;
941 }
942 
943 #else /* USE_HASH_TABLE_ACCUMULATE */
944 
945 /* NON USE_HASH_TABLE_ACCUMULATE code (simply hash each chunk) */
946 
947 static hash_key key_from_chunk_ref(const BArrayInfo *info, const BChunkRef *cref)
948 {
949  const size_t data_hash_len = BCHUNK_HASH_LEN * info->chunk_stride;
950  hash_key key;
951  BChunk *chunk = cref->link;
952 
953 # ifdef USE_HASH_TABLE_KEY_CACHE
954  key = chunk->key;
955  if (key != HASH_TABLE_KEY_UNSET) {
956  /* Using key cache!
957  * avoids calculating every time */
958  }
959  else {
960  /* cache the key */
961  key = hash_data(chunk->data, data_hash_len);
962  if (key == HASH_TABLE_KEY_UNSET) {
964  }
965  chunk->key = key;
966  }
967 # else
968  key = hash_data(chunk->data, data_hash_len);
969 # endif
970 
971  return key;
972 }
973 
974 static const BChunkRef *table_lookup(const BArrayInfo *info,
975  BTableRef **table,
976  const size_t table_len,
977  const uint UNUSED(i_table_start),
978  const uchar *data,
979  const size_t data_len,
980  const size_t offset,
981  const hash_key *UNUSED(table_hash_array))
982 {
983  const size_t data_hash_len = BCHUNK_HASH_LEN * info->chunk_stride; /* TODO, cache */
984 
985  size_t size_left = data_len - offset;
986  hash_key key = hash_data(&data[offset], MIN2(data_hash_len, size_left));
987  size_t key_index = (size_t)(key % (hash_key)table_len);
988  for (BTableRef *tref = table[key_index]; tref; tref = tref->next) {
989  const BChunkRef *cref = tref->cref;
990 # ifdef USE_HASH_TABLE_KEY_CACHE
991  if (cref->link->key == key)
992 # endif
993  {
994  BChunk *chunk_test = cref->link;
995  if (chunk_test->data_len <= size_left) {
996  if (bchunk_data_compare(chunk_test, data, data_len, offset)) {
997  /* we could remove the chunk from the table, to avoid multiple hits */
998  return cref;
999  }
1000  }
1001  }
1002  }
1003  return NULL;
1004 }
1005 
1006 #endif /* USE_HASH_TABLE_ACCUMULATE */
1007 
1008 /* End Table Lookup
1009  * ---------------- */
1010 
1013 /* -------------------------------------------------------------------- */
1024  BArrayMemory *bs_mem,
1025  const uchar *data,
1026  const size_t data_len_original,
1027  const BChunkList *chunk_list_reference)
1028 {
1029  ASSERT_CHUNKLIST_SIZE(chunk_list_reference, chunk_list_reference->total_size);
1030 
1031  /* -----------------------------------------------------------------------
1032  * Fast-Path for exact match
1033  * Check for exact match, if so, return the current list.
1034  */
1035 
1036  const BChunkRef *cref_match_first = NULL;
1037 
1038  uint chunk_list_reference_skip_len = 0;
1039  size_t chunk_list_reference_skip_bytes = 0;
1040  size_t i_prev = 0;
1041 
1042 #ifdef USE_FASTPATH_CHUNKS_FIRST
1043  {
1044  bool full_match = true;
1045 
1046  const BChunkRef *cref = chunk_list_reference->chunk_refs.first;
1047  while (i_prev < data_len_original) {
1048  if (cref != NULL && bchunk_data_compare(cref->link, data, data_len_original, i_prev)) {
1049  cref_match_first = cref;
1050  chunk_list_reference_skip_len += 1;
1051  chunk_list_reference_skip_bytes += cref->link->data_len;
1052  i_prev += cref->link->data_len;
1053  cref = cref->next;
1054  }
1055  else {
1056  full_match = false;
1057  break;
1058  }
1059  }
1060 
1061  if (full_match) {
1062  if (chunk_list_reference->total_size == data_len_original) {
1063  return (BChunkList *)chunk_list_reference;
1064  }
1065  }
1066  }
1067 
1068  /* End Fast-Path (first)
1069  * --------------------- */
1070 
1071 #endif /* USE_FASTPATH_CHUNKS_FIRST */
1072 
1073  /* Copy until we have a mismatch */
1074  BChunkList *chunk_list = bchunk_list_new(bs_mem, data_len_original);
1075  if (cref_match_first != NULL) {
1076  size_t chunk_size_step = 0;
1077  const BChunkRef *cref = chunk_list_reference->chunk_refs.first;
1078  while (true) {
1079  BChunk *chunk = cref->link;
1080  chunk_size_step += chunk->data_len;
1081  bchunk_list_append_only(bs_mem, chunk_list, chunk);
1082  ASSERT_CHUNKLIST_SIZE(chunk_list, chunk_size_step);
1083  ASSERT_CHUNKLIST_DATA(chunk_list, data);
1084  if (cref == cref_match_first) {
1085  break;
1086  }
1087  cref = cref->next;
1088  }
1089  /* happens when bytes are removed from the end of the array */
1090  if (chunk_size_step == data_len_original) {
1091  return chunk_list;
1092  }
1093 
1094  i_prev = chunk_size_step;
1095  }
1096  else {
1097  i_prev = 0;
1098  }
1099 
1100  /* ------------------------------------------------------------------------
1101  * Fast-Path for end chunks
1102  *
1103  * Check for trailing chunks
1104  */
1105 
1106  /* In this case use 'chunk_list_reference_last' to define the last index
1107  * index_match_last = -1 */
1108 
1109  /* warning, from now on don't use len(data)
1110  * since we want to ignore chunks already matched */
1111  size_t data_len = data_len_original;
1112 #define data_len_original invalid_usage
1113 #ifdef data_len_original /* quiet warning */
1114 #endif
1115 
1116  const BChunkRef *chunk_list_reference_last = NULL;
1117 
1118 #ifdef USE_FASTPATH_CHUNKS_LAST
1119  if (!BLI_listbase_is_empty(&chunk_list_reference->chunk_refs)) {
1120  const BChunkRef *cref = chunk_list_reference->chunk_refs.last;
1121  while ((cref->prev != NULL) && (cref != cref_match_first) &&
1122  (cref->link->data_len <= data_len - i_prev)) {
1123  BChunk *chunk_test = cref->link;
1124  size_t offset = data_len - chunk_test->data_len;
1125  if (bchunk_data_compare(chunk_test, data, data_len, offset)) {
1126  data_len = offset;
1127  chunk_list_reference_last = cref;
1128  chunk_list_reference_skip_len += 1;
1129  chunk_list_reference_skip_bytes += cref->link->data_len;
1130  cref = cref->prev;
1131  }
1132  else {
1133  break;
1134  }
1135  }
1136  }
1137 
1138  /* End Fast-Path (last)
1139  * -------------------- */
1140 #endif /* USE_FASTPATH_CHUNKS_LAST */
1141 
1142  /* -----------------------------------------------------------------------
1143  * Check for aligned chunks
1144  *
1145  * This saves a lot of searching, so use simple heuristics to detect aligned arrays.
1146  * (may need to tweak exact method).
1147  */
1148 
1149  bool use_aligned = false;
1150 
1151 #ifdef USE_ALIGN_CHUNKS_TEST
1152  if (chunk_list->total_size == chunk_list_reference->total_size) {
1153  /* if we're already a quarter aligned */
1154  if (data_len - i_prev <= chunk_list->total_size / 4) {
1155  use_aligned = true;
1156  }
1157  else {
1158  /* TODO, walk over chunks and check if some arbitrary amount align */
1159  }
1160  }
1161 #endif /* USE_ALIGN_CHUNKS_TEST */
1162 
1163  /* End Aligned Chunk Case
1164  * ----------------------- */
1165 
1166  if (use_aligned) {
1167  /* Copy matching chunks, creates using the same 'layout' as the reference */
1168  const BChunkRef *cref = cref_match_first ? cref_match_first->next :
1169  chunk_list_reference->chunk_refs.first;
1170  while (i_prev != data_len) {
1171  const size_t i = i_prev + cref->link->data_len;
1172  BLI_assert(i != i_prev);
1173 
1174  if ((cref != chunk_list_reference_last) &&
1175  bchunk_data_compare(cref->link, data, data_len, i_prev)) {
1176  bchunk_list_append(info, bs_mem, chunk_list, cref->link);
1177  ASSERT_CHUNKLIST_SIZE(chunk_list, i);
1178  ASSERT_CHUNKLIST_DATA(chunk_list, data);
1179  }
1180  else {
1181  bchunk_list_append_data(info, bs_mem, chunk_list, &data[i_prev], i - i_prev);
1182  ASSERT_CHUNKLIST_SIZE(chunk_list, i);
1183  ASSERT_CHUNKLIST_DATA(chunk_list, data);
1184  }
1185 
1186  cref = cref->next;
1187 
1188  i_prev = i;
1189  }
1190  }
1191  else if ((data_len - i_prev >= info->chunk_byte_size) &&
1192  (chunk_list_reference->chunk_refs_len >= chunk_list_reference_skip_len) &&
1193  (chunk_list_reference->chunk_refs.first != NULL)) {
1194 
1195  /* --------------------------------------------------------------------
1196  * Non-Aligned Chunk De-Duplication */
1197 
1198  /* only create a table if we have at least one chunk to search
1199  * otherwise just make a new one.
1200  *
1201  * Support re-arranged chunks */
1202 
1203 #ifdef USE_HASH_TABLE_ACCUMULATE
1204  size_t i_table_start = i_prev;
1205  const size_t table_hash_array_len = (data_len - i_prev) / info->chunk_stride;
1206  hash_key *table_hash_array = MEM_mallocN(sizeof(*table_hash_array) * table_hash_array_len,
1207  __func__);
1208  hash_array_from_data(info, &data[i_prev], data_len - i_prev, table_hash_array);
1209 
1210  hash_accum(table_hash_array, table_hash_array_len, info->accum_steps);
1211 #else
1212  /* dummy vars */
1213  uint i_table_start = 0;
1214  hash_key *table_hash_array = NULL;
1215 #endif
1216 
1217  const uint chunk_list_reference_remaining_len = (chunk_list_reference->chunk_refs_len -
1218  chunk_list_reference_skip_len) +
1219  1;
1220  BTableRef *table_ref_stack = MEM_mallocN(
1221  chunk_list_reference_remaining_len * sizeof(BTableRef), __func__);
1222  uint table_ref_stack_n = 0;
1223 
1224  const size_t table_len = chunk_list_reference_remaining_len * BCHUNK_HASH_TABLE_MUL;
1225  BTableRef **table = MEM_callocN(table_len * sizeof(*table), __func__);
1226 
1227  /* table_make - inline
1228  * include one matching chunk, to allow for repeating values */
1229  {
1230 #ifdef USE_HASH_TABLE_ACCUMULATE
1231  const size_t hash_store_len = info->accum_read_ahead_len;
1232  hash_key *hash_store = MEM_mallocN(sizeof(hash_key) * hash_store_len, __func__);
1233 #endif
1234 
1235  const BChunkRef *cref;
1236  size_t chunk_list_reference_bytes_remaining = chunk_list_reference->total_size -
1237  chunk_list_reference_skip_bytes;
1238 
1239  if (cref_match_first) {
1240  cref = cref_match_first;
1241  chunk_list_reference_bytes_remaining += cref->link->data_len;
1242  }
1243  else {
1244  cref = chunk_list_reference->chunk_refs.first;
1245  }
1246 
1247 #ifdef USE_PARANOID_CHECKS
1248  {
1249  size_t test_bytes_len = 0;
1250  const BChunkRef *cr = cref;
1251  while (cr != chunk_list_reference_last) {
1252  test_bytes_len += cr->link->data_len;
1253  cr = cr->next;
1254  }
1255  BLI_assert(test_bytes_len == chunk_list_reference_bytes_remaining);
1256  }
1257 #endif
1258 
1259  while ((cref != chunk_list_reference_last) &&
1260  (chunk_list_reference_bytes_remaining >= info->accum_read_ahead_bytes)) {
1261  hash_key key = key_from_chunk_ref(info,
1262  cref
1263 
1265  ,
1266  hash_store,
1267  hash_store_len
1268 #endif
1269  );
1270  size_t key_index = (size_t)(key % (hash_key)table_len);
1271  BTableRef *tref_prev = table[key_index];
1272  BLI_assert(table_ref_stack_n < chunk_list_reference_remaining_len);
1273  BTableRef *tref = &table_ref_stack[table_ref_stack_n++];
1274  tref->cref = cref;
1275  tref->next = tref_prev;
1276  table[key_index] = tref;
1277 
1278  chunk_list_reference_bytes_remaining -= cref->link->data_len;
1279  cref = cref->next;
1280  }
1281 
1282  BLI_assert(table_ref_stack_n <= chunk_list_reference_remaining_len);
1283 
1284 #ifdef USE_HASH_TABLE_ACCUMULATE
1285  MEM_freeN(hash_store);
1286 #endif
1287  }
1288  /* done making the table */
1289 
1290  BLI_assert(i_prev <= data_len);
1291  for (size_t i = i_prev; i < data_len;) {
1292  /* Assumes exiting chunk isn't a match! */
1293 
1294  const BChunkRef *cref_found = table_lookup(
1295  info, table, table_len, i_table_start, data, data_len, i, table_hash_array);
1296  if (cref_found != NULL) {
1297  BLI_assert(i < data_len);
1298  if (i != i_prev) {
1299  bchunk_list_append_data_n(info, bs_mem, chunk_list, &data[i_prev], i - i_prev);
1300  i_prev = i;
1301  }
1302 
1303  /* now add the reference chunk */
1304  {
1305  BChunk *chunk_found = cref_found->link;
1306  i += chunk_found->data_len;
1307  bchunk_list_append(info, bs_mem, chunk_list, chunk_found);
1308  }
1309  i_prev = i;
1310  BLI_assert(i_prev <= data_len);
1311  ASSERT_CHUNKLIST_SIZE(chunk_list, i_prev);
1312  ASSERT_CHUNKLIST_DATA(chunk_list, data);
1313 
1314  /* its likely that the next chunk in the list will be a match, so check it! */
1315  while (!ELEM(cref_found->next, NULL, chunk_list_reference_last)) {
1316  cref_found = cref_found->next;
1317  BChunk *chunk_found = cref_found->link;
1318 
1319  if (bchunk_data_compare(chunk_found, data, data_len, i_prev)) {
1320  /* May be useful to remove table data, assuming we don't have
1321  * repeating memory where it would be useful to re-use chunks. */
1322  i += chunk_found->data_len;
1323  bchunk_list_append(info, bs_mem, chunk_list, chunk_found);
1324  /* chunk_found may be freed! */
1325  i_prev = i;
1326  BLI_assert(i_prev <= data_len);
1327  ASSERT_CHUNKLIST_SIZE(chunk_list, i_prev);
1328  ASSERT_CHUNKLIST_DATA(chunk_list, data);
1329  }
1330  else {
1331  break;
1332  }
1333  }
1334  }
1335  else {
1336  i = i + info->chunk_stride;
1337  }
1338  }
1339 
1340 #ifdef USE_HASH_TABLE_ACCUMULATE
1341  MEM_freeN(table_hash_array);
1342 #endif
1343  MEM_freeN(table);
1344  MEM_freeN(table_ref_stack);
1345 
1346  /* End Table Lookup
1347  * ---------------- */
1348  }
1349 
1350  ASSERT_CHUNKLIST_SIZE(chunk_list, i_prev);
1351  ASSERT_CHUNKLIST_DATA(chunk_list, data);
1352 
1353  /* -----------------------------------------------------------------------
1354  * No Duplicates to copy, write new chunks
1355  *
1356  * Trailing chunks, no matches found in table lookup above.
1357  * Write all new data. */
1358  if (i_prev != data_len) {
1359  bchunk_list_append_data_n(info, bs_mem, chunk_list, &data[i_prev], data_len - i_prev);
1360  i_prev = data_len;
1361  }
1362 
1363  BLI_assert(i_prev == data_len);
1364 
1365 #ifdef USE_FASTPATH_CHUNKS_LAST
1366  if (chunk_list_reference_last != NULL) {
1367  /* write chunk_list_reference_last since it hasn't been written yet */
1368  const BChunkRef *cref = chunk_list_reference_last;
1369  while (cref != NULL) {
1370  BChunk *chunk = cref->link;
1371  // BLI_assert(bchunk_data_compare(chunk, data, data_len, i_prev));
1372  i_prev += chunk->data_len;
1373  /* use simple since we assume the references chunks
1374  * have already been sized correctly. */
1375  bchunk_list_append_only(bs_mem, chunk_list, chunk);
1376  ASSERT_CHUNKLIST_DATA(chunk_list, data);
1377  cref = cref->next;
1378  }
1379  }
1380 #endif
1381 
1382 #undef data_len_original
1383 
1384  BLI_assert(i_prev == data_len_original);
1385 
1386  /* check we're the correct size and that we didn't accidentally modify the reference */
1388  ASSERT_CHUNKLIST_SIZE(chunk_list_reference, chunk_list_reference->total_size);
1389 
1390  ASSERT_CHUNKLIST_DATA(chunk_list, data);
1391 
1392  return chunk_list;
1393 }
1394 /* end private API */
1395 
1398 /* -------------------------------------------------------------------- */
1423 {
1424  BArrayStore *bs = MEM_callocN(sizeof(BArrayStore), __func__);
1425 
1426  bs->info.chunk_stride = stride;
1427  // bs->info.chunk_count = chunk_count;
1428 
1429  bs->info.chunk_byte_size = chunk_count * stride;
1430 #ifdef USE_MERGE_CHUNKS
1431  bs->info.chunk_byte_size_min = MAX2(1u, chunk_count / BCHUNK_SIZE_MIN_DIV) * stride;
1432  bs->info.chunk_byte_size_max = (chunk_count * BCHUNK_SIZE_MAX_MUL) * stride;
1433 #endif
1434 
1435 #ifdef USE_HASH_TABLE_ACCUMULATE
1437  /* Triangle number, identifying now much read-ahead we need:
1438  * https://en.wikipedia.org/wiki/Triangular_number (+ 1) */
1439  bs->info.accum_read_ahead_len = (uint)(
1440  (((bs->info.accum_steps * (bs->info.accum_steps + 1))) / 2) + 1);
1442 #else
1443  bs->info.accum_read_ahead_bytes = BCHUNK_HASH_LEN * stride;
1444 #endif
1445 
1448  /* allow iteration to simplify freeing, otherwise its not needed
1449  * (we could loop over all states as an alternative). */
1451 
1452  return bs;
1453 }
1454 
1456 {
1457  /* free chunk data */
1458  {
1459  BLI_mempool_iter iter;
1460  BChunk *chunk;
1461  BLI_mempool_iternew(bs->memory.chunk, &iter);
1462  while ((chunk = BLI_mempool_iterstep(&iter))) {
1463  BLI_assert(chunk->users > 0);
1464  MEM_freeN((void *)chunk->data);
1465  }
1466  }
1467 
1468  /* free states */
1469  for (BArrayState *state = bs->states.first, *state_next; state; state = state_next) {
1470  state_next = state->next;
1471  MEM_freeN(state);
1472  }
1473 }
1474 
1479 {
1481 
1485 
1486  MEM_freeN(bs);
1487 }
1488 
1493 {
1495 
1496  BLI_listbase_clear(&bs->states);
1497 
1501 }
1502 
1505 /* -------------------------------------------------------------------- */
1513 {
1514  size_t size_accum = 0;
1515  LISTBASE_FOREACH (const BArrayState *, state, &bs->states) {
1516  size_accum += state->chunk_list->total_size;
1517  }
1518  return size_accum;
1519 }
1520 
1526 {
1527  size_t size_total = 0;
1528  BLI_mempool_iter iter;
1529  BChunk *chunk;
1530  BLI_mempool_iternew(bs->memory.chunk, &iter);
1531  while ((chunk = BLI_mempool_iterstep(&iter))) {
1532  BLI_assert(chunk->users > 0);
1533  size_total += (size_t)chunk->data_len;
1534  }
1535  return size_total;
1536 }
1537 
1540 /* -------------------------------------------------------------------- */
1557  const void *data,
1558  const size_t data_len,
1559  const BArrayState *state_reference)
1560 {
1561  /* ensure we're aligned to the stride */
1562  BLI_assert((data_len % bs->info.chunk_stride) == 0);
1563 
1564 #ifdef USE_PARANOID_CHECKS
1565  if (state_reference) {
1566  BLI_assert(BLI_findindex(&bs->states, state_reference) != -1);
1567  }
1568 #endif
1569 
1570  BChunkList *chunk_list;
1571  if (state_reference) {
1572  chunk_list = bchunk_list_from_data_merge(&bs->info,
1573  &bs->memory,
1574  (const uchar *)data,
1575  data_len,
1576  /* re-use reference chunks */
1577  state_reference->chunk_list);
1578  }
1579  else {
1580  chunk_list = bchunk_list_new(&bs->memory, data_len);
1581  bchunk_list_fill_from_array(&bs->info, &bs->memory, chunk_list, (const uchar *)data, data_len);
1582  }
1583 
1584  chunk_list->users += 1;
1585 
1586  BArrayState *state = MEM_callocN(sizeof(BArrayState), __func__);
1587  state->chunk_list = chunk_list;
1588 
1589  BLI_addtail(&bs->states, state);
1590 
1591 #ifdef USE_PARANOID_CHECKS
1592  {
1593  size_t data_test_len;
1594  void *data_test = BLI_array_store_state_data_get_alloc(state, &data_test_len);
1595  BLI_assert(data_test_len == data_len);
1596  BLI_assert(memcmp(data_test, data, data_len) == 0);
1597  MEM_freeN(data_test);
1598  }
1599 #endif
1600 
1601  return state;
1602 }
1603 
1610 {
1611 #ifdef USE_PARANOID_CHECKS
1612  BLI_assert(BLI_findindex(&bs->states, state) != -1);
1613 #endif
1614 
1615  bchunk_list_decref(&bs->memory, state->chunk_list);
1616  BLI_remlink(&bs->states, state);
1617 
1618  MEM_freeN(state);
1619 }
1620 
1626 {
1627  return state->chunk_list->total_size;
1628 }
1629 
1634 {
1635 #ifdef USE_PARANOID_CHECKS
1636  size_t data_test_len = 0;
1637  LISTBASE_FOREACH (BChunkRef *, cref, &state->chunk_list->chunk_refs) {
1638  data_test_len += cref->link->data_len;
1639  }
1640  BLI_assert(data_test_len == state->chunk_list->total_size);
1641 #endif
1642 
1643  uchar *data_step = (uchar *)data;
1644  LISTBASE_FOREACH (BChunkRef *, cref, &state->chunk_list->chunk_refs) {
1645  BLI_assert(cref->link->users > 0);
1646  memcpy(data_step, cref->link->data, cref->link->data_len);
1647  data_step += cref->link->data_len;
1648  }
1649 }
1650 
1655 {
1656  void *data = MEM_mallocN(state->chunk_list->total_size, __func__);
1658  *r_data_len = state->chunk_list->total_size;
1659  return data;
1660 }
1661 
1664 /* -------------------------------------------------------------------- */
1668 /* only for test validation */
1669 static size_t bchunk_list_size(const BChunkList *chunk_list)
1670 {
1671  size_t total_size = 0;
1672  LISTBASE_FOREACH (BChunkRef *, cref, &chunk_list->chunk_refs) {
1673  total_size += cref->link->data_len;
1674  }
1675  return total_size;
1676 }
1677 
1679 {
1680  bool ok = true;
1681 
1682  /* Check Length
1683  * ------------ */
1684 
1686  BChunkList *chunk_list = state->chunk_list;
1687  if (!(bchunk_list_size(chunk_list) == chunk_list->total_size)) {
1688  return false;
1689  }
1690 
1691  if (BLI_listbase_count(&chunk_list->chunk_refs) != (int)chunk_list->chunk_refs_len) {
1692  return false;
1693  }
1694 
1695 #ifdef USE_MERGE_CHUNKS
1696  /* ensure we merge all chunks that could be merged */
1697  if (chunk_list->total_size > bs->info.chunk_byte_size_min) {
1698  LISTBASE_FOREACH (BChunkRef *, cref, &chunk_list->chunk_refs) {
1699  if (cref->link->data_len < bs->info.chunk_byte_size_min) {
1700  return false;
1701  }
1702  }
1703  }
1704 #endif
1705  }
1706 
1707  {
1708  BLI_mempool_iter iter;
1709  BChunk *chunk;
1710  BLI_mempool_iternew(bs->memory.chunk, &iter);
1711  while ((chunk = BLI_mempool_iterstep(&iter))) {
1712  if (!(MEM_allocN_len(chunk->data) >= chunk->data_len)) {
1713  return false;
1714  }
1715  }
1716  }
1717 
1718  /* Check User Count & Lost References
1719  * ---------------------------------- */
1720  {
1721  GHashIterator gh_iter;
1722 
1723 #define GHASH_PTR_ADD_USER(gh, pt) \
1724  { \
1725  void **val; \
1726  if (BLI_ghash_ensure_p((gh), (pt), &val)) { \
1727  *((int *)val) += 1; \
1728  } \
1729  else { \
1730  *((int *)val) = 1; \
1731  } \
1732  } \
1733  ((void)0)
1734 
1735  /* count chunk_list's */
1736  GHash *chunk_list_map = BLI_ghash_ptr_new(__func__);
1737  GHash *chunk_map = BLI_ghash_ptr_new(__func__);
1738 
1739  int totrefs = 0;
1741  GHASH_PTR_ADD_USER(chunk_list_map, state->chunk_list);
1742  }
1743  GHASH_ITER (gh_iter, chunk_list_map) {
1744  const struct BChunkList *chunk_list = BLI_ghashIterator_getKey(&gh_iter);
1745  const int users = POINTER_AS_INT(BLI_ghashIterator_getValue(&gh_iter));
1746  if (!(chunk_list->users == users)) {
1747  ok = false;
1748  goto user_finally;
1749  }
1750  }
1751  if (!(BLI_mempool_len(bs->memory.chunk_list) == (int)BLI_ghash_len(chunk_list_map))) {
1752  ok = false;
1753  goto user_finally;
1754  }
1755 
1756  /* count chunk's */
1757  GHASH_ITER (gh_iter, chunk_list_map) {
1758  const struct BChunkList *chunk_list = BLI_ghashIterator_getKey(&gh_iter);
1759  LISTBASE_FOREACH (const BChunkRef *, cref, &chunk_list->chunk_refs) {
1760  GHASH_PTR_ADD_USER(chunk_map, cref->link);
1761  totrefs += 1;
1762  }
1763  }
1764  if (!(BLI_mempool_len(bs->memory.chunk) == (int)BLI_ghash_len(chunk_map))) {
1765  ok = false;
1766  goto user_finally;
1767  }
1768  if (!(BLI_mempool_len(bs->memory.chunk_ref) == totrefs)) {
1769  ok = false;
1770  goto user_finally;
1771  }
1772 
1773  GHASH_ITER (gh_iter, chunk_map) {
1774  const struct BChunk *chunk = BLI_ghashIterator_getKey(&gh_iter);
1775  const int users = POINTER_AS_INT(BLI_ghashIterator_getValue(&gh_iter));
1776  if (!(chunk->users == users)) {
1777  ok = false;
1778  goto user_finally;
1779  }
1780  }
1781 
1782 #undef GHASH_PTR_ADD_USER
1783 
1784  user_finally:
1785  BLI_ghash_free(chunk_list_map, NULL, NULL);
1786  BLI_ghash_free(chunk_map, NULL, NULL);
1787  }
1788 
1789  return ok;
1790  /* TODO, dangling pointer checks */
1791 }
1792 
Efficient in-memory storage of multiple similar arrays.
#define BLI_assert(a)
Definition: BLI_assert.h:58
#define BLI_INLINE
BLI_INLINE void * BLI_ghashIterator_getKey(GHashIterator *ghi) ATTR_WARN_UNUSED_RESULT
Definition: BLI_ghash.h:146
BLI_INLINE void * BLI_ghashIterator_getValue(GHashIterator *ghi) ATTR_WARN_UNUSED_RESULT
Definition: BLI_ghash.h:150
#define GHASH_ITER(gh_iter_, ghash_)
Definition: BLI_ghash.h:169
unsigned int BLI_ghash_len(GHash *gh) ATTR_WARN_UNUSED_RESULT
Definition: BLI_ghash.c:744
void BLI_ghash_free(GHash *gh, GHashKeyFreeFP keyfreefp, GHashValFreeFP valfreefp)
Definition: BLI_ghash.c:1008
GHash * BLI_ghash_ptr_new(const char *info) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT
BLI_INLINE bool BLI_listbase_is_empty(const struct ListBase *lb)
Definition: BLI_listbase.h:124
#define LISTBASE_FOREACH(type, var, list)
Definition: BLI_listbase.h:172
BLI_INLINE void BLI_listbase_clear(struct ListBase *lb)
Definition: BLI_listbase.h:128
void BLI_addtail(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition: listbase.c:110
void BLI_remlink(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition: listbase.c:133
int BLI_findindex(const struct ListBase *listbase, const void *vlink) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
int BLI_listbase_count(const struct ListBase *listbase) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
void BLI_mempool_iternew(BLI_mempool *pool, BLI_mempool_iter *iter) ATTR_NONNULL()
Definition: BLI_mempool.c:537
@ BLI_MEMPOOL_ALLOW_ITER
Definition: BLI_mempool.h:85
@ BLI_MEMPOOL_NOP
Definition: BLI_mempool.h:77
void * BLI_mempool_iterstep(BLI_mempool_iter *iter) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition: BLI_mempool.c:645
void BLI_mempool_free(BLI_mempool *pool, void *addr) ATTR_NONNULL(1
BLI_mempool * BLI_mempool_create(unsigned int esize, unsigned int totelem, unsigned int pchunk, unsigned int flag) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT
Definition: BLI_mempool.c:268
void * BLI_mempool_alloc(BLI_mempool *pool) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition: BLI_mempool.c:334
void BLI_mempool_destroy(BLI_mempool *pool) ATTR_NONNULL(1)
Definition: BLI_mempool.c:757
void BLI_mempool_clear(BLI_mempool *pool) ATTR_NONNULL(1)
Definition: BLI_mempool.c:749
int BLI_mempool_len(BLI_mempool *pool) ATTR_NONNULL(1)
Definition: BLI_mempool.c:454
Strict compiler flags for areas of code we want to ensure don't do conversions without us knowing abo...
unsigned char uchar
Definition: BLI_sys_types.h:86
unsigned int uint
Definition: BLI_sys_types.h:83
#define UNUSED_VARS(...)
#define UNUSED(x)
#define POINTER_AS_INT(i)
#define MAX2(a, b)
#define UNLIKELY(x)
#define ELEM(...)
#define MIN2(a, b)
_GL_VOID GLfloat value _GL_VOID_RET _GL_VOID const GLuint GLboolean *residences _GL_BOOL_RET _GL_VOID GLsizei GLfloat GLfloat GLfloat GLfloat const GLubyte *bitmap _GL_VOID_RET _GL_VOID GLenum const void *lists _GL_VOID_RET _GL_VOID const GLdouble *equation _GL_VOID_RET _GL_VOID GLdouble GLdouble blue _GL_VOID_RET _GL_VOID GLfloat GLfloat blue _GL_VOID_RET _GL_VOID GLint GLint blue _GL_VOID_RET _GL_VOID GLshort GLshort blue _GL_VOID_RET _GL_VOID GLubyte GLubyte blue _GL_VOID_RET _GL_VOID GLuint GLuint blue _GL_VOID_RET _GL_VOID GLushort GLushort blue _GL_VOID_RET _GL_VOID GLbyte GLbyte GLbyte alpha _GL_VOID_RET _GL_VOID GLdouble GLdouble GLdouble alpha _GL_VOID_RET _GL_VOID GLfloat GLfloat GLfloat alpha _GL_VOID_RET _GL_VOID GLint GLint GLint alpha _GL_VOID_RET _GL_VOID GLshort GLshort GLshort alpha _GL_VOID_RET _GL_VOID GLubyte GLubyte GLubyte alpha _GL_VOID_RET _GL_VOID GLuint GLuint GLuint alpha _GL_VOID_RET _GL_VOID GLushort GLushort GLushort alpha _GL_VOID_RET _GL_VOID GLenum mode _GL_VOID_RET _GL_VOID GLint GLsizei GLsizei GLenum type _GL_VOID_RET _GL_VOID GLsizei GLenum GLenum const void *pixels _GL_VOID_RET _GL_VOID const void *pointer _GL_VOID_RET _GL_VOID GLdouble v _GL_VOID_RET _GL_VOID GLfloat v _GL_VOID_RET _GL_VOID GLint GLint i2 _GL_VOID_RET _GL_VOID GLint j _GL_VOID_RET _GL_VOID GLfloat param _GL_VOID_RET _GL_VOID GLint param _GL_VOID_RET _GL_VOID GLdouble GLdouble GLdouble GLdouble GLdouble zFar _GL_VOID_RET _GL_UINT GLdouble *equation _GL_VOID_RET _GL_VOID GLenum GLint *params _GL_VOID_RET _GL_VOID GLenum GLfloat *v _GL_VOID_RET _GL_VOID GLenum GLfloat *params _GL_VOID_RET _GL_VOID GLfloat *values _GL_VOID_RET _GL_VOID GLushort *values _GL_VOID_RET _GL_VOID GLenum GLfloat *params _GL_VOID_RET _GL_VOID GLenum GLdouble *params _GL_VOID_RET _GL_VOID GLenum GLint *params _GL_VOID_RET _GL_VOID GLsizei stride
Read Guarded memory(de)allocation.
#define MEM_reallocN(vmemh, len)
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)
Definition: array_store.c:520
#define BCHUNK_HASH_TABLE_MUL
Definition: array_store.c:174
static BChunk * bchunk_new_copydata(BArrayMemory *bs_mem, const uchar *data, const size_t data_len)
Definition: array_store.c:334
static hash_key key_from_chunk_ref(const BArrayInfo *info, const BChunkRef *cref, hash_key *hash_store, const size_t hash_store_len)
Definition: array_store.c:862
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)
Definition: array_store.c:1023
static void array_store_free_data(BArrayStore *bs)
Definition: array_store.c:1455
#define USE_HASH_TABLE_ACCUMULATE
Definition: array_store.c:150
struct BArrayMemory BArrayMemory
struct BChunkRef BChunkRef
BArrayStore * BLI_array_store_create(uint stride, uint chunk_count)
Definition: array_store.c:1422
static BChunkList * bchunk_list_new(BArrayMemory *bs_mem, size_t total_size)
Definition: array_store.c:370
struct BArrayInfo BArrayInfo
static void hash_array_from_cref(const BArrayInfo *info, const BChunkRef *cref, const size_t data_len, hash_key *hash_array)
Definition: array_store.c:794
BLI_INLINE uint hash_data_single(const uchar p)
Definition: array_store.c:751
void * BLI_array_store_state_data_get_alloc(BArrayState *state, size_t *r_data_len)
Definition: array_store.c:1654
#define GHASH_PTR_ADD_USER(gh, pt)
struct BChunk BChunk
static bool bchunk_data_compare(const BChunk *chunk, const uchar *data_base, const size_t data_base_len, const size_t offset)
Definition: array_store.c:353
static void bchunk_list_fill_from_array(const BArrayInfo *info, BArrayMemory *bs_mem, BChunkList *chunk_list, const uchar *data, const size_t data_len)
Definition: array_store.c:694
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)
Definition: array_store.c:913
void BLI_array_store_state_remove(BArrayStore *bs, BArrayState *state)
Definition: array_store.c:1609
#define ASSERT_CHUNKLIST_DATA(chunk_list, data)
Definition: array_store.c:422
static void bchunk_decref(BArrayMemory *bs_mem, BChunk *chunk)
Definition: array_store.c:341
static void hash_accum_single(hash_key *hash_array, const size_t hash_array_len, size_t iter_steps)
Definition: array_store.c:840
static uint hash_data(const uchar *key, size_t n)
Definition: array_store.c:757
uint64_t hash_key
Definition: array_store.c:214
static void bchunk_list_append_data_n(const BArrayInfo *info, BArrayMemory *bs_mem, BChunkList *chunk_list, const uchar *data, size_t data_len)
Definition: array_store.c:632
size_t BLI_array_store_calc_size_expanded_get(const BArrayStore *bs)
Definition: array_store.c:1512
#define HASH_TABLE_KEY_UNSET
Definition: array_store.c:168
void BLI_array_store_state_data_get(BArrayState *state, void *data)
Definition: array_store.c:1633
void BLI_array_store_clear(BArrayStore *bs)
Definition: array_store.c:1492
static void bchunk_list_decref(BArrayMemory *bs_mem, BChunkList *chunk_list)
Definition: array_store.c:381
static size_t bchunk_list_size(const BChunkList *chunk_list)
Definition: array_store.c:1669
#define BCHUNK_SIZE_MAX_MUL
Definition: array_store.c:198
bool BLI_array_store_is_valid(BArrayStore *bs)
Definition: array_store.c:1678
BArrayState * BLI_array_store_state_add(BArrayStore *bs, const void *data, const size_t data_len, const BArrayState *state_reference)
Definition: array_store.c:1556
#define HASH_TABLE_KEY_FALLBACK
Definition: array_store.c:169
size_t BLI_array_store_state_size_get(BArrayState *state)
Definition: array_store.c:1625
#define data_len_original
struct BChunkList BChunkList
static void bchunk_list_ensure_min_size_last(const BArrayInfo *info, BArrayMemory *bs_mem, BChunkList *chunk_list)
Definition: array_store.c:426
static void hash_accum(hash_key *hash_array, const size_t hash_array_len, size_t iter_steps)
Definition: array_store.c:819
#define ASSERT_CHUNKLIST_SIZE(chunk_list, n)
Definition: array_store.c:404
static void hash_array_from_data(const BArrayInfo *info, const uchar *data_slice, const size_t data_slice_len, hash_key *hash_array)
Definition: array_store.c:772
static void bchunk_list_append_only(BArrayMemory *bs_mem, BChunkList *chunk_list, BChunk *chunk)
Definition: array_store.c:562
void BLI_array_store_destroy(BArrayStore *bs)
Definition: array_store.c:1478
static BChunk * bchunk_new(BArrayMemory *bs_mem, const uchar *data, const size_t data_len)
Definition: array_store.c:322
size_t BLI_array_store_calc_size_compacted_get(const BArrayStore *bs)
Definition: array_store.c:1525
#define BCHUNK_HASH_TABLE_ACCUMULATE_STEPS
Definition: array_store.c:157
static void bchunk_list_append(const BArrayInfo *info, BArrayMemory *bs_mem, BChunkList *chunk_list, BChunk *chunk)
Definition: array_store.c:680
#define HASH_INIT
Definition: array_store.c:749
static void bchunk_list_append_data(const BArrayInfo *info, BArrayMemory *bs_mem, BChunkList *chunk_list, const uchar *data, const size_t data_len)
Definition: array_store.c:575
#define BCHUNK_SIZE_MIN_DIV
Definition: array_store.c:191
struct BTableRef BTableRef
int users
Definition: editfont_undo.c:90
void(* MEM_freeN)(void *vmemh)
Definition: mallocn.c:41
size_t(* MEM_allocN_len)(const void *vmemh)
Definition: mallocn.c:40
void *(* MEM_callocN)(size_t len, const char *str)
Definition: mallocn.c:45
void *(* MEM_mallocN)(size_t len, const char *str)
Definition: mallocn.c:47
static ulong state[N]
void split(const std::string &s, const char delim, std::vector< std::string > &tokens)
Definition: abc_util.cc:115
unsigned __int64 uint64_t
Definition: stdint.h:93
size_t chunk_byte_size_max
Definition: array_store.c:224
size_t accum_steps
Definition: array_store.c:228
size_t chunk_byte_size_min
Definition: array_store.c:223
size_t chunk_stride
Definition: array_store.c:217
size_t accum_read_ahead_bytes
Definition: array_store.c:226
size_t chunk_byte_size
Definition: array_store.c:221
size_t accum_read_ahead_len
Definition: array_store.c:229
BLI_mempool * chunk_list
Definition: array_store.c:234
BLI_mempool * chunk
Definition: array_store.c:236
BLI_mempool * chunk_ref
Definition: array_store.c:235
struct BArrayState * next
Definition: array_store.c:267
struct BChunkList * chunk_list
Definition: array_store.c:269
struct BArrayState * prev
Definition: array_store.c:267
BArrayMemory memory
Definition: array_store.c:247
ListBase states
Definition: array_store.c:252
BArrayInfo info
Definition: array_store.c:244
uint chunk_refs_len
Definition: array_store.c:274
ListBase chunk_refs
Definition: array_store.c:273
size_t total_size
Definition: array_store.c:275
BChunk * link
Definition: array_store.c:298
struct BChunkRef * prev
Definition: array_store.c:297
struct BChunkRef * next
Definition: array_store.c:297
const uchar * data
Definition: array_store.c:283
size_t data_len
Definition: array_store.c:284
int users
Definition: array_store.c:286
hash_key key
Definition: array_store.c:289
struct BTableRef * next
Definition: array_store.c:310
const BChunkRef * cref
Definition: array_store.c:311
void * last
Definition: DNA_listBase.h:47
void * first
Definition: DNA_listBase.h:47