Blender V5.0
array_store.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
89
90#include <algorithm>
91#include <cstdlib>
92#include <cstring>
93
94#include "MEM_guardedalloc.h"
95
96#include "BLI_assert.h"
97#include "BLI_listbase.h"
98#include "BLI_mempool.h"
99#include "BLI_utildefines.h"
100
101#include "BLI_array_store.h" /* Own include. */
102#include "BLI_ghash.h" /* Only for #BLI_array_store_is_valid. */
103
104#include "BLI_strict_flags.h" /* IWYU pragma: keep. Keep last. */
105
106struct BChunkList;
107
108/* -------------------------------------------------------------------- */
114
121#define USE_FASTPATH_CHUNKS_FIRST
122
131#ifdef USE_FASTPATH_CHUNKS_FIRST
132# define USE_FASTPATH_CHUNKS_LAST
133#endif
134
141#define USE_ALIGN_CHUNKS_TEST
142
147#define USE_HASH_TABLE_ACCUMULATE
148
149#ifdef USE_HASH_TABLE_ACCUMULATE
150/* Number of times to propagate hashes back.
151 * Effectively a 'triangle-number'.
152 * so 3 -> 7, 4 -> 11, 5 -> 16, 6 -> 22, 7 -> 29, ... etc.
153 *
154 * \note additional steps are expensive, so avoid high values unless necessary
155 * (with low strides, between 1-4) where a low value would cause the hashes to
156 * be un-evenly distributed.
157 */
158# define BCHUNK_HASH_TABLE_ACCUMULATE_STEPS_DEFAULT 3
159# define BCHUNK_HASH_TABLE_ACCUMULATE_STEPS_32BITS 4
160# define BCHUNK_HASH_TABLE_ACCUMULATE_STEPS_16BITS 5
169# define BCHUNK_HASH_TABLE_ACCUMULATE_STEPS_8BITS 6
170#else
175# define BCHUNK_HASH_LEN 16
176#endif
177
181#define USE_HASH_TABLE_KEY_CACHE
182#ifdef USE_HASH_TABLE_KEY_CACHE
183# define HASH_TABLE_KEY_UNSET ((hash_key) - 1)
184# define HASH_TABLE_KEY_FALLBACK ((hash_key) - 2)
185#endif
186
195#define USE_HASH_TABLE_DEDUPLICATE
196
200#define BCHUNK_HASH_TABLE_MUL 3
201
213#define USE_MERGE_CHUNKS
214
215#ifdef USE_MERGE_CHUNKS
217# define BCHUNK_SIZE_MIN_DIV 8
218
226# define BCHUNK_SIZE_MAX_MUL 2
227#endif /* USE_MERGE_CHUNKS */
228
230// #define USE_VALIDATE_LIST_SIZE
231
232// #define USE_VALIDATE_LIST_DATA_PARTIAL
233
234// #define USE_PARANOID_CHECKS
235
237
238/* -------------------------------------------------------------------- */
241
242using hash_key = uint32_t;
243
246 // uint chunk_count; /* UNUSED (other values are derived from this) */
247
248 /* Pre-calculated. */
250 /* Min/max limits (inclusive) */
258#ifdef USE_HASH_TABLE_ACCUMULATE
261#endif
262};
263
265 BLI_mempool *chunk_list; /* #BChunkList. */
266 BLI_mempool *chunk_ref; /* #BChunkRef. */
267 BLI_mempool *chunk; /* #BChunk. */
268};
269
274 /* Static. */
276
279
284};
285
302
314
316struct BChunk {
317 const uchar *data;
318 size_t data_len;
320 int users;
321
322#ifdef USE_HASH_TABLE_KEY_CACHE
324#endif
325};
326
334
347
349
350static size_t bchunk_list_size(const BChunkList *chunk_list);
351
352/* -------------------------------------------------------------------- */
355
356static BChunk *bchunk_new(BArrayMemory *bs_mem, const uchar *data, const size_t data_len)
357{
358 BChunk *chunk = static_cast<BChunk *>(BLI_mempool_alloc(bs_mem->chunk));
359 chunk->data = data;
360 chunk->data_len = data_len;
361 chunk->users = 0;
362#ifdef USE_HASH_TABLE_KEY_CACHE
363 chunk->key = HASH_TABLE_KEY_UNSET;
364#endif
365 return chunk;
366}
367
368static BChunk *bchunk_new_copydata(BArrayMemory *bs_mem, const uchar *data, const size_t data_len)
369{
370 uchar *data_copy = MEM_malloc_arrayN<uchar>(data_len, __func__);
371 memcpy(data_copy, data, data_len);
372 return bchunk_new(bs_mem, data_copy, data_len);
373}
374
375static void bchunk_decref(BArrayMemory *bs_mem, BChunk *chunk)
376{
377 BLI_assert(chunk->users > 0);
378 if (chunk->users == 1) {
379 MEM_freeN(chunk->data);
380 BLI_mempool_free(bs_mem->chunk, chunk);
381 }
382 else {
383 chunk->users -= 1;
384 }
385}
386
388 const uchar *data_base,
389 const size_t data_base_len,
390 const size_t offset)
391{
392 BLI_assert(offset + size_t(chunk->data_len) <= data_base_len);
393 UNUSED_VARS_NDEBUG(data_base_len);
394 return (memcmp(&data_base[offset], chunk->data, chunk->data_len) == 0);
395}
396
397static bool bchunk_data_compare(const BChunk *chunk,
398 const uchar *data_base,
399 const size_t data_base_len,
400 const size_t offset)
401{
402 if (offset + size_t(chunk->data_len) <= data_base_len) {
403 return bchunk_data_compare_unchecked(chunk, data_base, data_base_len, offset);
404 }
405 return false;
406}
407
409
410/* -------------------------------------------------------------------- */
413
414static BChunkList *bchunk_list_new(BArrayMemory *bs_mem, size_t total_expanded_size)
415{
416 BChunkList *chunk_list = static_cast<BChunkList *>(BLI_mempool_alloc(bs_mem->chunk_list));
417
418 BLI_listbase_clear(&chunk_list->chunk_refs);
419 chunk_list->chunk_refs_len = 0;
420 chunk_list->total_expanded_size = total_expanded_size;
421 chunk_list->users = 0;
422 return chunk_list;
423}
424
425static void bchunk_list_decref(BArrayMemory *bs_mem, BChunkList *chunk_list)
426{
427 BLI_assert(chunk_list->users > 0);
428 if (chunk_list->users == 1) {
429 for (BChunkRef *cref = static_cast<BChunkRef *>(chunk_list->chunk_refs.first), *cref_next;
430 cref;
431 cref = cref_next)
432 {
433 cref_next = cref->next;
434 bchunk_decref(bs_mem, cref->link);
435 BLI_mempool_free(bs_mem->chunk_ref, cref);
436 }
437
438 BLI_mempool_free(bs_mem->chunk_list, chunk_list);
439 }
440 else {
441 chunk_list->users -= 1;
442 }
443}
444
445#ifdef USE_VALIDATE_LIST_SIZE
446# ifndef NDEBUG
447# define ASSERT_CHUNKLIST_SIZE(chunk_list, n) BLI_assert(bchunk_list_size(chunk_list) == n)
448# endif
449#endif
450#ifndef ASSERT_CHUNKLIST_SIZE
451# define ASSERT_CHUNKLIST_SIZE(chunk_list, n) (EXPR_NOP(chunk_list), EXPR_NOP(n))
452#endif
453
454#ifdef USE_VALIDATE_LIST_DATA_PARTIAL
455static size_t bchunk_list_data_check(const BChunkList *chunk_list, const uchar *data)
456{
457 size_t offset = 0;
458 LISTBASE_FOREACH (BChunkRef *, cref, &chunk_list->chunk_refs) {
459 if (memcmp(&data[offset], cref->link->data, cref->link->data_len) != 0) {
460 return false;
461 }
462 offset += cref->link->data_len;
463 }
464 return true;
465}
466# define ASSERT_CHUNKLIST_DATA(chunk_list, data) \
467 BLI_assert(bchunk_list_data_check(chunk_list, data))
468#else
469# define ASSERT_CHUNKLIST_DATA(chunk_list, data) (EXPR_NOP(chunk_list), EXPR_NOP(data))
470#endif
471
472#ifdef USE_MERGE_CHUNKS
474 BArrayMemory *bs_mem,
475 BChunkList *chunk_list)
476{
477 BChunkRef *cref = static_cast<BChunkRef *>(chunk_list->chunk_refs.last);
478 if (cref && cref->prev) {
479 /* Both are decrefed after use (end of this block). */
480 BChunk *chunk_curr = cref->link;
481 BChunk *chunk_prev = cref->prev->link;
482
483 if (std::min(chunk_prev->data_len, chunk_curr->data_len) < info->chunk_byte_size_min) {
484 const size_t data_merge_len = chunk_prev->data_len + chunk_curr->data_len;
485 /* We could pass, but no need. */
486 if (data_merge_len <= info->chunk_byte_size_max) {
487 /* We have enough space to merge. */
488
489 /* Remove last from the linked-list. */
490 BLI_assert(chunk_list->chunk_refs.last != chunk_list->chunk_refs.first);
491 cref->prev->next = nullptr;
492 chunk_list->chunk_refs.last = cref->prev;
493 chunk_list->chunk_refs_len -= 1;
494
495 uchar *data_merge = MEM_malloc_arrayN<uchar>(data_merge_len, __func__);
496 memcpy(data_merge, chunk_prev->data, chunk_prev->data_len);
497 memcpy(&data_merge[chunk_prev->data_len], chunk_curr->data, chunk_curr->data_len);
498
499 cref->prev->link = bchunk_new(bs_mem, data_merge, data_merge_len);
500 cref->prev->link->users += 1;
501
502 BLI_mempool_free(bs_mem->chunk_ref, cref);
503 }
504 else {
505 /* If we always merge small slices, we should _almost_
506 * never end up having very large chunks.
507 * Gradual expanding on contracting will cause this.
508 *
509 * if we do, the code below works (test by setting 'BCHUNK_SIZE_MAX_MUL = 1.2') */
510
511 /* Keep chunk on the left hand side a regular size. */
512 const size_t split = info->chunk_byte_size;
513
514 /* Merge and split. */
515 const size_t data_prev_len = split;
516 const size_t data_curr_len = data_merge_len - split;
517 uchar *data_prev = MEM_malloc_arrayN<uchar>(data_prev_len, __func__);
518 uchar *data_curr = MEM_malloc_arrayN<uchar>(data_curr_len, __func__);
519
520 if (data_prev_len <= chunk_prev->data_len) {
521 const size_t data_curr_shrink_len = chunk_prev->data_len - data_prev_len;
522
523 /* Setup 'data_prev'. */
524 memcpy(data_prev, chunk_prev->data, data_prev_len);
525
526 /* Setup 'data_curr'. */
527 memcpy(data_curr, &chunk_prev->data[data_prev_len], data_curr_shrink_len);
528 memcpy(&data_curr[data_curr_shrink_len], chunk_curr->data, chunk_curr->data_len);
529 }
530 else {
531 BLI_assert(data_curr_len <= chunk_curr->data_len);
532 BLI_assert(data_prev_len >= chunk_prev->data_len);
533
534 const size_t data_prev_grow_len = data_prev_len - chunk_prev->data_len;
535
536 /* Setup 'data_prev'. */
537 memcpy(data_prev, chunk_prev->data, chunk_prev->data_len);
538 memcpy(&data_prev[chunk_prev->data_len], chunk_curr->data, data_prev_grow_len);
539
540 /* Setup 'data_curr'. */
541 memcpy(data_curr, &chunk_curr->data[data_prev_grow_len], data_curr_len);
542 }
543
544 cref->prev->link = bchunk_new(bs_mem, data_prev, data_prev_len);
545 cref->prev->link->users += 1;
546
547 cref->link = bchunk_new(bs_mem, data_curr, data_curr_len);
548 cref->link->users += 1;
549 }
550
551 /* Free zero users. */
552 bchunk_decref(bs_mem, chunk_curr);
553 bchunk_decref(bs_mem, chunk_prev);
554 }
555 }
556}
557#endif /* USE_MERGE_CHUNKS */
558
567static void bchunk_list_calc_trim_len(const BArrayInfo *info,
568 const size_t data_len,
569 size_t *r_data_trim_len,
570 size_t *r_data_last_chunk_len)
571{
572 size_t data_last_chunk_len = 0;
573 size_t data_trim_len = data_len;
574
575#ifdef USE_MERGE_CHUNKS
576 /* Avoid creating too-small chunks more efficient than merging after. */
577 if (data_len > info->chunk_byte_size) {
578 data_last_chunk_len = (data_trim_len % info->chunk_byte_size);
579 data_trim_len = data_trim_len - data_last_chunk_len;
580 if (data_last_chunk_len) {
581 if (data_last_chunk_len < info->chunk_byte_size_min) {
582 /* May be zero and that's OK. */
583 data_trim_len -= info->chunk_byte_size;
584 data_last_chunk_len += info->chunk_byte_size;
585 }
586 }
587 }
588 else {
589 data_trim_len = 0;
590 data_last_chunk_len = data_len;
591 }
592
593 BLI_assert((data_trim_len == 0) || (data_trim_len >= info->chunk_byte_size));
594#else
595 data_last_chunk_len = (data_trim_len % info->chunk_byte_size);
596 data_trim_len = data_trim_len - data_last_chunk_len;
597#endif
598
599 BLI_assert(data_trim_len + data_last_chunk_len == data_len);
600
601 *r_data_trim_len = data_trim_len;
602 *r_data_last_chunk_len = data_last_chunk_len;
603}
604
608static void bchunk_list_append_only(BArrayMemory *bs_mem, BChunkList *chunk_list, BChunk *chunk)
609{
610 BChunkRef *cref = static_cast<BChunkRef *>(BLI_mempool_alloc(bs_mem->chunk_ref));
611 BLI_addtail(&chunk_list->chunk_refs, cref);
612 cref->link = chunk;
613 chunk_list->chunk_refs_len += 1;
614 chunk->users += 1;
615}
616
621static void bchunk_list_append_data(const BArrayInfo *info,
622 BArrayMemory *bs_mem,
623 BChunkList *chunk_list,
624 const uchar *data,
625 const size_t data_len)
626{
627 BLI_assert(data_len != 0);
628
629#ifdef USE_MERGE_CHUNKS
630 BLI_assert(data_len <= info->chunk_byte_size_max);
631
632 if (!BLI_listbase_is_empty(&chunk_list->chunk_refs)) {
633 BChunkRef *cref = static_cast<BChunkRef *>(chunk_list->chunk_refs.last);
634 BChunk *chunk_prev = cref->link;
635
636 if (std::min(chunk_prev->data_len, data_len) < info->chunk_byte_size_min) {
637 const size_t data_merge_len = chunk_prev->data_len + data_len;
638 /* Re-allocate for single user. */
639 if (cref->link->users == 1) {
640 uchar *data_merge = static_cast<uchar *>(
641 MEM_reallocN((void *)cref->link->data, data_merge_len));
642 memcpy(&data_merge[chunk_prev->data_len], data, data_len);
643 cref->link->data = data_merge;
644 cref->link->data_len = data_merge_len;
645 }
646 else {
647 uchar *data_merge = MEM_malloc_arrayN<uchar>(data_merge_len, __func__);
648 memcpy(data_merge, chunk_prev->data, chunk_prev->data_len);
649 memcpy(&data_merge[chunk_prev->data_len], data, data_len);
650 cref->link = bchunk_new(bs_mem, data_merge, data_merge_len);
651 cref->link->users += 1;
652 bchunk_decref(bs_mem, chunk_prev);
653 }
654 return;
655 }
656 }
657#else
658 UNUSED_VARS(info);
659#endif /* USE_MERGE_CHUNKS */
660
661 BChunk *chunk = bchunk_new_copydata(bs_mem, data, data_len);
662 bchunk_list_append_only(bs_mem, chunk_list, chunk);
663
664 /* Don't run this, instead preemptively avoid creating a chunk only to merge it (above). */
665#if 0
666# ifdef USE_MERGE_CHUNKS
667 bchunk_list_ensure_min_size_last(info, bs_mem, chunk_list);
668# endif
669#endif
670}
671
679static void bchunk_list_append_data_n(const BArrayInfo *info,
680 BArrayMemory *bs_mem,
681 BChunkList *chunk_list,
682 const uchar *data,
683 size_t data_len)
684{
685 size_t data_trim_len, data_last_chunk_len;
686 bchunk_list_calc_trim_len(info, data_len, &data_trim_len, &data_last_chunk_len);
687
688 if (data_trim_len != 0) {
689 size_t i_prev;
690
691 {
692 const size_t i = info->chunk_byte_size;
693 bchunk_list_append_data(info, bs_mem, chunk_list, data, i);
694 i_prev = i;
695 }
696
697 while (i_prev != data_trim_len) {
698 const size_t i = i_prev + info->chunk_byte_size;
699 BChunk *chunk = bchunk_new_copydata(bs_mem, &data[i_prev], i - i_prev);
700 bchunk_list_append_only(bs_mem, chunk_list, chunk);
701 i_prev = i;
702 }
703
704 if (data_last_chunk_len) {
705 BChunk *chunk = bchunk_new_copydata(bs_mem, &data[i_prev], data_last_chunk_len);
706 bchunk_list_append_only(bs_mem, chunk_list, chunk);
707 // i_prev = data_len; /* UNUSED */
708 }
709 }
710 else {
711 /* If we didn't write any chunks previously, we may need to merge with the last. */
712 if (data_last_chunk_len) {
713 bchunk_list_append_data(info, bs_mem, chunk_list, data, data_last_chunk_len);
714 // i_prev = data_len; /* UNUSED */
715 }
716 }
717
718#ifdef USE_MERGE_CHUNKS
719 if (data_len > info->chunk_byte_size) {
720 BLI_assert(((BChunkRef *)chunk_list->chunk_refs.last)->link->data_len >=
721 info->chunk_byte_size_min);
722 }
723#endif
724}
725
726static void bchunk_list_append(const BArrayInfo *info,
727 BArrayMemory *bs_mem,
728 BChunkList *chunk_list,
729 BChunk *chunk)
730{
731 bchunk_list_append_only(bs_mem, chunk_list, chunk);
732
733#ifdef USE_MERGE_CHUNKS
734 bchunk_list_ensure_min_size_last(info, bs_mem, chunk_list);
735#else
736 UNUSED_VARS(info);
737#endif
738}
739
741 BArrayMemory *bs_mem,
742 BChunkList *chunk_list,
743 const uchar *data,
744 const size_t data_len)
745{
747
748 size_t data_trim_len, data_last_chunk_len;
749 bchunk_list_calc_trim_len(info, data_len, &data_trim_len, &data_last_chunk_len);
750
751 size_t i_prev = 0;
752 while (i_prev != data_trim_len) {
753 const size_t i = i_prev + info->chunk_byte_size;
754 BChunk *chunk = bchunk_new_copydata(bs_mem, &data[i_prev], i - i_prev);
755 bchunk_list_append_only(bs_mem, chunk_list, chunk);
756 i_prev = i;
757 }
758
759 if (data_last_chunk_len) {
760 BChunk *chunk = bchunk_new_copydata(bs_mem, &data[i_prev], data_last_chunk_len);
761 bchunk_list_append_only(bs_mem, chunk_list, chunk);
762 // i_prev = data_len;
763 }
764
765#ifdef USE_MERGE_CHUNKS
766 if (data_len > info->chunk_byte_size) {
767 BLI_assert(((BChunkRef *)chunk_list->chunk_refs.last)->link->data_len >=
768 info->chunk_byte_size_min);
769 }
770#endif
771
772 /* Works but better avoid redundant re-allocation. */
773#if 0
774# ifdef USE_MERGE_CHUNKS
775 bchunk_list_ensure_min_size_last(info, bs_mem, chunk_list);
776# endif
777#endif
778
779 ASSERT_CHUNKLIST_SIZE(chunk_list, data_len);
780 ASSERT_CHUNKLIST_DATA(chunk_list, data);
781}
782
784
785/*
786 * Internal Table Lookup Functions.
787 */
788
789/* -------------------------------------------------------------------- */
794
795#define HASH_INIT (5381)
796
798{
799 return ((HASH_INIT << 5) + HASH_INIT) + (hash_key) * ((signed char *)&p);
800}
801
802/* Hash bytes, from #BLI_ghashutil_strhash_n. */
803static hash_key hash_data(const uchar *key, size_t n)
804{
805 const signed char *p;
807
808 for (p = (const signed char *)key; n--; p++) {
809 h = (hash_key)((h << 5) + h) + (hash_key)*p;
810 }
811
812 return h;
813}
814
815#undef HASH_INIT
816
817#ifdef USE_HASH_TABLE_ACCUMULATE
818static void hash_array_from_data(const BArrayInfo *info,
819 const uchar *data_slice,
820 const size_t data_slice_len,
821 hash_key *hash_array)
822{
823 if (info->chunk_stride != 1) {
824 for (size_t i = 0, i_step = 0; i_step < data_slice_len; i++, i_step += info->chunk_stride) {
825 hash_array[i] = hash_data(&data_slice[i_step], info->chunk_stride);
826 }
827 }
828 else {
829 /* Fast-path for bytes. */
830 for (size_t i = 0; i < data_slice_len; i++) {
831 hash_array[i] = hash_data_single(data_slice[i]);
832 }
833 }
834}
835
840static void hash_array_from_cref(const BArrayInfo *info,
841 const BChunkRef *cref,
842 const size_t data_len,
843 hash_key *hash_array)
844{
845 const size_t hash_array_len = data_len / info->chunk_stride;
846 size_t i = 0;
847 do {
848 size_t i_next = hash_array_len - i;
849 size_t data_trim_len = i_next * info->chunk_stride;
850 if (data_trim_len > cref->link->data_len) {
851 data_trim_len = cref->link->data_len;
852 i_next = data_trim_len / info->chunk_stride;
853 }
854 BLI_assert(data_trim_len <= cref->link->data_len);
855 hash_array_from_data(info, cref->link->data, data_trim_len, &hash_array[i]);
856 i += i_next;
857 cref = cref->next;
858 } while ((i < hash_array_len) && (cref != nullptr));
859
860 /* If this isn't equal, the caller didn't properly check
861 * that there was enough data left in all chunks. */
862 BLI_assert(i == hash_array_len);
863}
864
865BLI_INLINE void hash_accum_impl(hash_key *hash_array, const size_t i_dst, const size_t i_ahead)
866{
867 /* Tested to give good results when accumulating unique values from an array of booleans.
868 * (least unused cells in the `BTableRef **table`). */
869 BLI_assert(i_dst < i_ahead);
870 hash_array[i_dst] += ((hash_array[i_ahead] << 3) ^ (hash_array[i_dst] >> 1));
871}
872
873static void hash_accum(hash_key *hash_array, const size_t hash_array_len, size_t iter_steps)
874{
875 /* _very_ unlikely, can happen if you select a chunk-size of 1 for example. */
876 if (UNLIKELY(iter_steps > hash_array_len)) {
877 iter_steps = hash_array_len;
878 }
879
880 const size_t hash_array_search_len = hash_array_len - iter_steps;
881 while (iter_steps != 0) {
882 const size_t hash_offset = iter_steps;
883 for (size_t i = 0; i < hash_array_search_len; i++) {
884 hash_accum_impl(hash_array, i, i + hash_offset);
885 }
886 iter_steps -= 1;
887 }
888}
889
894static void hash_accum_single(hash_key *hash_array, const size_t hash_array_len, size_t iter_steps)
895{
896 BLI_assert(iter_steps <= hash_array_len);
897 if (UNLIKELY(!(iter_steps <= hash_array_len))) {
898 /* While this shouldn't happen, avoid crashing. */
899 iter_steps = hash_array_len;
900 }
901 /* We can increase this value each step to avoid accumulating quite as much
902 * while getting the same results as hash_accum. */
903 size_t iter_steps_sub = iter_steps;
904
905 while (iter_steps != 0) {
906 const size_t hash_array_search_len = hash_array_len - iter_steps_sub;
907 const size_t hash_offset = iter_steps;
908 for (uint i = 0; i < hash_array_search_len; i++) {
909 hash_accum_impl(hash_array, i, i + hash_offset);
910 }
911 iter_steps -= 1;
912 iter_steps_sub += iter_steps;
913 }
914}
915
917 const BChunkRef *cref,
918 /* Avoid reallocating each time. */
919 hash_key *hash_store,
920 const size_t hash_store_len)
921{
922 /* In C, will fill in a reusable array. */
923 BChunk *chunk = cref->link;
924 BLI_assert((info->accum_read_ahead_bytes * info->chunk_stride) != 0);
925
926 if (info->accum_read_ahead_bytes <= chunk->data_len) {
927 hash_key key;
928
929# ifdef USE_HASH_TABLE_KEY_CACHE
930 key = chunk->key;
931 if (key != HASH_TABLE_KEY_UNSET) {
932 /* Using key cache!
933 * avoids calculating every time. */
934 }
935 else {
936 hash_array_from_cref(info, cref, info->accum_read_ahead_bytes, hash_store);
937 hash_accum_single(hash_store, hash_store_len, info->accum_steps);
938 key = hash_store[0];
939
940 /* Cache the key. */
941 if (UNLIKELY(key == HASH_TABLE_KEY_UNSET)) {
943 }
944 chunk->key = key;
945 }
946# else
947 hash_array_from_cref(info, cref, info->accum_read_ahead_bytes, hash_store);
948 hash_accum_single(hash_store, hash_store_len, info->accum_steps);
949 key = hash_store[0];
950# endif
951 return key;
952 }
953 /* Corner case - we're too small, calculate the key each time. */
954
955 hash_array_from_cref(info, cref, info->accum_read_ahead_bytes, hash_store);
956 hash_accum_single(hash_store, hash_store_len, info->accum_steps);
957 hash_key key = hash_store[0];
958
959# ifdef USE_HASH_TABLE_KEY_CACHE
960 if (UNLIKELY(key == HASH_TABLE_KEY_UNSET)) {
962 }
963# endif
964 return key;
965}
966
967static const BChunkRef *table_lookup(const BArrayInfo *info,
968 BTableRef **table,
969 const size_t table_len,
970 const size_t i_table_start,
971 const uchar *data,
972 const size_t data_len,
973 const size_t offset,
974 const hash_key *table_hash_array)
975{
976 const hash_key key = table_hash_array[((offset - i_table_start) / info->chunk_stride)];
977 const uint key_index = uint(key % (hash_key)table_len);
978 const BTableRef *tref = table[key_index];
979 if (tref != nullptr) {
980 const size_t size_left = data_len - offset;
981 do {
982 const BChunkRef *cref = tref->cref;
983# ifdef USE_HASH_TABLE_KEY_CACHE
984 if (cref->link->key == key)
985# endif
986 {
987 const BChunk *chunk_test = cref->link;
988 if (chunk_test->data_len <= size_left) {
989 if (bchunk_data_compare_unchecked(chunk_test, data, data_len, offset)) {
990 /* We could remove the chunk from the table, to avoid multiple hits. */
991 return cref;
992 }
993 }
994 }
995 } while ((tref = tref->next));
996 }
997 return nullptr;
998}
999
1000#else /* USE_HASH_TABLE_ACCUMULATE */
1001
1002/* NON USE_HASH_TABLE_ACCUMULATE code (simply hash each chunk). */
1003
1004static hash_key key_from_chunk_ref(const BArrayInfo *info, const BChunkRef *cref)
1005{
1006 hash_key key;
1007 BChunk *chunk = cref->link;
1008 const size_t data_hash_len = std::min(chunk->data_len, BCHUNK_HASH_LEN * info->chunk_stride);
1009
1010# ifdef USE_HASH_TABLE_KEY_CACHE
1011 key = chunk->key;
1012 if (key != HASH_TABLE_KEY_UNSET) {
1013 /* Using key cache!
1014 * avoids calculating every time. */
1015 }
1016 else {
1017 /* Cache the key. */
1018 key = hash_data(chunk->data, data_hash_len);
1019 if (key == HASH_TABLE_KEY_UNSET) {
1021 }
1022 chunk->key = key;
1023 }
1024# else
1025 key = hash_data(chunk->data, data_hash_len);
1026# endif
1027
1028 return key;
1029}
1030
1031static const BChunkRef *table_lookup(const BArrayInfo *info,
1032 BTableRef **table,
1033 const size_t table_len,
1034 const uint UNUSED(i_table_start),
1035 const uchar *data,
1036 const size_t data_len,
1037 const size_t offset,
1038 const hash_key *UNUSED(table_hash_array))
1039{
1040 const size_t data_hash_len = BCHUNK_HASH_LEN * info->chunk_stride; /* TODO: cache. */
1041
1042 const size_t size_left = data_len - offset;
1043 const hash_key key = hash_data(&data[offset], std::min(data_hash_len, size_left));
1044 const uint key_index = uint(key % (hash_key)table_len);
1045 for (BTableRef *tref = table[key_index]; tref; tref = tref->next) {
1046 const BChunkRef *cref = tref->cref;
1047# ifdef USE_HASH_TABLE_KEY_CACHE
1048 if (cref->link->key == key)
1049# endif
1050 {
1051 BChunk *chunk_test = cref->link;
1052 if (chunk_test->data_len <= size_left) {
1053 if (bchunk_data_compare_unchecked(chunk_test, data, data_len, offset)) {
1054 /* We could remove the chunk from the table, to avoid multiple hits. */
1055 return cref;
1056 }
1057 }
1058 }
1059 }
1060 return nullptr;
1061}
1062
1063#endif /* USE_HASH_TABLE_ACCUMULATE */
1064
1065/* End Table Lookup
1066 * ---------------- */
1067
1069
1070/* -------------------------------------------------------------------- */
1073
1081 BArrayMemory *bs_mem,
1082 const uchar *data,
1083 const size_t data_len_original,
1084 const BChunkList *chunk_list_reference)
1085{
1086 ASSERT_CHUNKLIST_SIZE(chunk_list_reference, chunk_list_reference->total_expanded_size);
1087
1088 /* -----------------------------------------------------------------------
1089 * Fast-Path for exact match
1090 * Check for exact match, if so, return the current list.
1091 */
1092
1093 const BChunkRef *cref_match_first = nullptr;
1094
1095 uint chunk_list_reference_skip_len = 0;
1096 size_t chunk_list_reference_skip_bytes = 0;
1097 size_t i_prev = 0;
1098
1099#ifdef USE_FASTPATH_CHUNKS_FIRST
1100 {
1101 bool full_match = true;
1102
1103 const BChunkRef *cref = static_cast<const BChunkRef *>(chunk_list_reference->chunk_refs.first);
1104 while (i_prev < data_len_original) {
1105 if (cref != nullptr && bchunk_data_compare(cref->link, data, data_len_original, i_prev)) {
1106 cref_match_first = cref;
1107 chunk_list_reference_skip_len += 1;
1108 chunk_list_reference_skip_bytes += cref->link->data_len;
1109 i_prev += cref->link->data_len;
1110 cref = cref->next;
1111 }
1112 else {
1113 full_match = false;
1114 break;
1115 }
1116 }
1117
1118 if (full_match) {
1119 if (chunk_list_reference->total_expanded_size == data_len_original) {
1120 return (BChunkList *)chunk_list_reference;
1121 }
1122 }
1123 }
1124
1125 /* End Fast-Path (first)
1126 * --------------------- */
1127
1128#endif /* USE_FASTPATH_CHUNKS_FIRST */
1129
1130 /* Copy until we have a mismatch. */
1131 BChunkList *chunk_list = bchunk_list_new(bs_mem, data_len_original);
1132 if (cref_match_first != nullptr) {
1133 size_t chunk_size_step = 0;
1134 const BChunkRef *cref = static_cast<const BChunkRef *>(chunk_list_reference->chunk_refs.first);
1135 while (true) {
1136 BChunk *chunk = cref->link;
1137 chunk_size_step += chunk->data_len;
1138 bchunk_list_append_only(bs_mem, chunk_list, chunk);
1139 ASSERT_CHUNKLIST_SIZE(chunk_list, chunk_size_step);
1140 ASSERT_CHUNKLIST_DATA(chunk_list, data);
1141 if (cref == cref_match_first) {
1142 break;
1143 }
1144 cref = cref->next;
1145 }
1146 /* Happens when bytes are removed from the end of the array. */
1147 if (chunk_size_step == data_len_original) {
1148 return chunk_list;
1149 }
1150
1151 i_prev = chunk_size_step;
1152 }
1153 else {
1154 i_prev = 0;
1155 }
1156
1157 /* ------------------------------------------------------------------------
1158 * Fast-Path for end chunks
1159 *
1160 * Check for trailing chunks.
1161 */
1162
1163 /* In this case use 'chunk_list_reference_last' to define the last index
1164 * `index_match_last = -1`. */
1165
1166 /* Warning, from now on don't use len(data) since we want to ignore chunks already matched. */
1167 size_t data_len = data_len_original;
1168#define data_len_original invalid_usage
1169#ifdef data_len_original
1170 /* Quiet warning. */
1171#endif
1172
1173 const BChunkRef *chunk_list_reference_last = nullptr;
1174
1175#ifdef USE_FASTPATH_CHUNKS_LAST
1176 if (!BLI_listbase_is_empty(&chunk_list_reference->chunk_refs)) {
1177 const BChunkRef *cref = static_cast<const BChunkRef *>(chunk_list_reference->chunk_refs.last);
1178 while ((cref->prev != nullptr) && (cref != cref_match_first) &&
1179 (cref->link->data_len <= data_len - i_prev))
1180 {
1181 const BChunk *chunk_test = cref->link;
1182 size_t offset = data_len - chunk_test->data_len;
1183 if (bchunk_data_compare(chunk_test, data, data_len, offset)) {
1184 data_len = offset;
1185 chunk_list_reference_last = cref;
1186 chunk_list_reference_skip_len += 1;
1187 chunk_list_reference_skip_bytes += cref->link->data_len;
1188 cref = cref->prev;
1189 }
1190 else {
1191 break;
1192 }
1193 }
1194 }
1195
1196 /* End Fast-Path (last)
1197 * -------------------- */
1198#endif /* USE_FASTPATH_CHUNKS_LAST */
1199
1200 /* -----------------------------------------------------------------------
1201 * Check for aligned chunks
1202 *
1203 * This saves a lot of searching, so use simple heuristics to detect aligned arrays.
1204 * (may need to tweak exact method).
1205 */
1206
1207 bool use_aligned = false;
1208
1209#ifdef USE_ALIGN_CHUNKS_TEST
1210 if (chunk_list->total_expanded_size == chunk_list_reference->total_expanded_size) {
1211 /* If we're already a quarter aligned. */
1212 if (data_len - i_prev <= chunk_list->total_expanded_size / 4) {
1213 use_aligned = true;
1214 }
1215 else {
1216 /* TODO: walk over chunks and check if some arbitrary amount align. */
1217 }
1218 }
1219#endif /* USE_ALIGN_CHUNKS_TEST */
1220
1221 /* End Aligned Chunk Case
1222 * ----------------------- */
1223
1224 if (use_aligned) {
1225 /* Copy matching chunks, creates using the same 'layout' as the reference. */
1226 const BChunkRef *cref = cref_match_first ? cref_match_first->next :
1227 static_cast<const BChunkRef *>(
1228 chunk_list_reference->chunk_refs.first);
1229 while (i_prev != data_len) {
1230 const size_t i = i_prev + cref->link->data_len;
1231 BLI_assert(i != i_prev);
1232
1233 if ((cref != chunk_list_reference_last) &&
1234 bchunk_data_compare(cref->link, data, data_len, i_prev))
1235 {
1236 bchunk_list_append(info, bs_mem, chunk_list, cref->link);
1237 ASSERT_CHUNKLIST_SIZE(chunk_list, i);
1238 ASSERT_CHUNKLIST_DATA(chunk_list, data);
1239 }
1240 else {
1241 bchunk_list_append_data(info, bs_mem, chunk_list, &data[i_prev], i - i_prev);
1242 ASSERT_CHUNKLIST_SIZE(chunk_list, i);
1243 ASSERT_CHUNKLIST_DATA(chunk_list, data);
1244 }
1245
1246 cref = cref->next;
1247
1248 i_prev = i;
1249 }
1250 }
1251 else if ((data_len - i_prev >= info->chunk_byte_size) &&
1252 (chunk_list_reference->chunk_refs_len >= chunk_list_reference_skip_len) &&
1253 (chunk_list_reference->chunk_refs.first != nullptr))
1254 {
1255
1256 /* --------------------------------------------------------------------
1257 * Non-Aligned Chunk De-Duplication. */
1258
1259 /* Only create a table if we have at least one chunk to search
1260 * otherwise just make a new one.
1261 *
1262 * Support re-arranged chunks. */
1263
1264#ifdef USE_HASH_TABLE_ACCUMULATE
1265 size_t i_table_start = i_prev;
1266 const size_t table_hash_array_len = (data_len - i_prev) / info->chunk_stride;
1267 hash_key *table_hash_array = MEM_malloc_arrayN<hash_key>(table_hash_array_len, __func__);
1268 hash_array_from_data(info, &data[i_prev], data_len - i_prev, table_hash_array);
1269
1270 hash_accum(table_hash_array, table_hash_array_len, info->accum_steps);
1271#else
1272 /* Dummy vars. */
1273 uint i_table_start = 0;
1274 hash_key *table_hash_array = nullptr;
1275#endif
1276
1277 const uint chunk_list_reference_remaining_len = (chunk_list_reference->chunk_refs_len -
1278 chunk_list_reference_skip_len) +
1279 1;
1280 BTableRef *table_ref_stack = MEM_malloc_arrayN<BTableRef>(chunk_list_reference_remaining_len,
1281 __func__);
1282 uint table_ref_stack_n = 0;
1283
1284 const size_t table_len = chunk_list_reference_remaining_len * BCHUNK_HASH_TABLE_MUL;
1285 BTableRef **table = MEM_calloc_arrayN<BTableRef *>(table_len, __func__);
1286
1287 /* Table_make - inline
1288 * include one matching chunk, to allow for repeating values. */
1289 {
1290#ifdef USE_HASH_TABLE_ACCUMULATE
1291 const size_t hash_store_len = info->accum_read_ahead_len;
1292 hash_key *hash_store = MEM_malloc_arrayN<hash_key>(hash_store_len, __func__);
1293#endif
1294
1295 const BChunkRef *cref;
1296 size_t chunk_list_reference_bytes_remaining = chunk_list_reference->total_expanded_size -
1297 chunk_list_reference_skip_bytes;
1298
1299 if (cref_match_first) {
1300 cref = cref_match_first;
1301 chunk_list_reference_bytes_remaining += cref->link->data_len;
1302 }
1303 else {
1304 cref = static_cast<const BChunkRef *>(chunk_list_reference->chunk_refs.first);
1305 }
1306
1307#ifdef USE_PARANOID_CHECKS
1308 {
1309 size_t test_bytes_len = 0;
1310 const BChunkRef *cr = cref;
1311 while (cr != chunk_list_reference_last) {
1312 test_bytes_len += cr->link->data_len;
1313 cr = cr->next;
1314 }
1315 BLI_assert(test_bytes_len == chunk_list_reference_bytes_remaining);
1316 }
1317#endif
1318
1319 while ((cref != chunk_list_reference_last) &&
1320 (chunk_list_reference_bytes_remaining >= info->accum_read_ahead_bytes))
1321 {
1322 hash_key key = key_from_chunk_ref(info,
1323 cref
1324
1326 ,
1327 hash_store,
1328 hash_store_len
1329#endif
1330 );
1331 const uint key_index = uint(key % (hash_key)table_len);
1332 BTableRef *tref_prev = table[key_index];
1333 BLI_assert(table_ref_stack_n < chunk_list_reference_remaining_len);
1334#ifdef USE_HASH_TABLE_DEDUPLICATE
1335 bool is_duplicate = false;
1336 if (tref_prev) {
1337 const BChunk *chunk_a = cref->link;
1338 const BTableRef *tref = tref_prev;
1339 do {
1340 /* Not an error, it just isn't expected the links are ever shared. */
1341 BLI_assert(tref->cref != cref);
1342 const BChunk *chunk_b = tref->cref->link;
1343# ifdef USE_HASH_TABLE_KEY_CACHE
1344 if (key == chunk_b->key)
1345# endif
1346 {
1347 if (chunk_a != chunk_b) {
1348 if (chunk_a->data_len == chunk_b->data_len) {
1349 if (memcmp(chunk_a->data, chunk_b->data, chunk_a->data_len) == 0) {
1350 is_duplicate = true;
1351 break;
1352 }
1353 }
1354 }
1355 }
1356 } while ((tref = tref->next));
1357 }
1358
1359 if (!is_duplicate)
1360#endif /* USE_HASH_TABLE_DEDUPLICATE */
1361 {
1362 BTableRef *tref = &table_ref_stack[table_ref_stack_n++];
1363 tref->cref = cref;
1364 tref->next = tref_prev;
1365 table[key_index] = tref;
1366 }
1367
1368 chunk_list_reference_bytes_remaining -= cref->link->data_len;
1369 cref = cref->next;
1370 }
1371
1372 BLI_assert(table_ref_stack_n <= chunk_list_reference_remaining_len);
1373
1374#ifdef USE_HASH_TABLE_ACCUMULATE
1375 MEM_freeN(hash_store);
1376#endif
1377 }
1378 /* Done making the table. */
1379
1380 BLI_assert(i_prev <= data_len);
1381 for (size_t i = i_prev; i < data_len;) {
1382 /* Assumes exiting chunk isn't a match! */
1383
1384 const BChunkRef *cref_found = table_lookup(
1385 info, table, table_len, i_table_start, data, data_len, i, table_hash_array);
1386 if (cref_found != nullptr) {
1387 BLI_assert(i < data_len);
1388 if (i != i_prev) {
1389 bchunk_list_append_data_n(info, bs_mem, chunk_list, &data[i_prev], i - i_prev);
1390 i_prev = i;
1391 }
1392
1393 /* Now add the reference chunk. */
1394 {
1395 BChunk *chunk_found = cref_found->link;
1396 i += chunk_found->data_len;
1397 bchunk_list_append(info, bs_mem, chunk_list, chunk_found);
1398 }
1399 i_prev = i;
1400 BLI_assert(i_prev <= data_len);
1401 ASSERT_CHUNKLIST_SIZE(chunk_list, i_prev);
1402 ASSERT_CHUNKLIST_DATA(chunk_list, data);
1403
1404 /* Its likely that the next chunk in the list will be a match, so check it! */
1405 while (!ELEM(cref_found->next, nullptr, chunk_list_reference_last)) {
1406 cref_found = cref_found->next;
1407 BChunk *chunk_found = cref_found->link;
1408
1409 if (bchunk_data_compare(chunk_found, data, data_len, i_prev)) {
1410 /* May be useful to remove table data, assuming we don't have
1411 * repeating memory where it would be useful to re-use chunks. */
1412 i += chunk_found->data_len;
1413 bchunk_list_append(info, bs_mem, chunk_list, chunk_found);
1414 /* Chunk_found may be freed! */
1415 i_prev = i;
1416 BLI_assert(i_prev <= data_len);
1417 ASSERT_CHUNKLIST_SIZE(chunk_list, i_prev);
1418 ASSERT_CHUNKLIST_DATA(chunk_list, data);
1419 }
1420 else {
1421 break;
1422 }
1423 }
1424 }
1425 else {
1426 i = i + info->chunk_stride;
1427 }
1428 }
1429
1430#ifdef USE_HASH_TABLE_ACCUMULATE
1431 MEM_freeN(table_hash_array);
1432#endif
1433 MEM_freeN(table);
1434 MEM_freeN(table_ref_stack);
1435
1436 /* End Table Lookup
1437 * ---------------- */
1438 }
1439
1440 ASSERT_CHUNKLIST_SIZE(chunk_list, i_prev);
1441 ASSERT_CHUNKLIST_DATA(chunk_list, data);
1442
1443 /* -----------------------------------------------------------------------
1444 * No Duplicates to copy, write new chunks
1445 *
1446 * Trailing chunks, no matches found in table lookup above.
1447 * Write all new data. */
1448 if (i_prev != data_len) {
1449 bchunk_list_append_data_n(info, bs_mem, chunk_list, &data[i_prev], data_len - i_prev);
1450 i_prev = data_len;
1451 }
1452
1453 BLI_assert(i_prev == data_len);
1454 UNUSED_VARS_NDEBUG(i_prev);
1455
1456#ifdef USE_FASTPATH_CHUNKS_LAST
1457 if (chunk_list_reference_last != nullptr) {
1458 /* Write chunk_list_reference_last since it hasn't been written yet. */
1459 const BChunkRef *cref = chunk_list_reference_last;
1460 while (cref != nullptr) {
1461 BChunk *chunk = cref->link;
1462 // BLI_assert(bchunk_data_compare(chunk, data, data_len, i_prev));
1463 i_prev += chunk->data_len;
1464 /* Use simple since we assume the references chunks have already been sized correctly. */
1465 bchunk_list_append_only(bs_mem, chunk_list, chunk);
1466 ASSERT_CHUNKLIST_DATA(chunk_list, data);
1467 cref = cref->next;
1468 }
1469 }
1470#endif
1471
1472#undef data_len_original
1473
1474 BLI_assert(i_prev == data_len_original);
1475 UNUSED_VARS_NDEBUG(i_prev);
1476
1477 /* Check we're the correct size and that we didn't accidentally modify the reference. */
1479 ASSERT_CHUNKLIST_SIZE(chunk_list_reference, chunk_list_reference->total_expanded_size);
1480
1481 ASSERT_CHUNKLIST_DATA(chunk_list, data);
1482
1483 return chunk_list;
1484}
1485/* End private API. */
1486
1488
1489/* -------------------------------------------------------------------- */
1492
1494{
1495 BLI_assert(stride > 0 && chunk_count > 0);
1496
1497 BArrayStore *bs = MEM_callocN<BArrayStore>(__func__);
1498
1499 bs->info.chunk_stride = stride;
1500 // bs->info.chunk_count = chunk_count;
1501
1502 bs->info.chunk_byte_size = chunk_count * stride;
1503#ifdef USE_MERGE_CHUNKS
1504 bs->info.chunk_byte_size_min = std::max(1u, chunk_count / BCHUNK_SIZE_MIN_DIV) * stride;
1505 bs->info.chunk_byte_size_max = (chunk_count * BCHUNK_SIZE_MAX_MUL) * stride;
1506#endif
1507
1508#ifdef USE_HASH_TABLE_ACCUMULATE
1509 /* One is always subtracted from this `accum_steps`, this is intentional
1510 * as it results in reading ahead the expected amount. */
1511 if (stride <= sizeof(int8_t)) {
1513 }
1514 else if (stride <= sizeof(int16_t)) {
1516 }
1517 else if (stride <= sizeof(int32_t)) {
1519 }
1520 else {
1522 }
1523
1524 do {
1525 bs->info.accum_steps -= 1;
1526 /* Triangle number, identifying now much read-ahead we need:
1527 * https://en.wikipedia.org/wiki/Triangular_number (+ 1) */
1528 bs->info.accum_read_ahead_len = ((bs->info.accum_steps * (bs->info.accum_steps + 1)) / 2) + 1;
1529 /* Only small chunk counts are likely to exceed the read-ahead length. */
1530 } while (UNLIKELY(chunk_count < bs->info.accum_read_ahead_len));
1531
1533#else
1534 bs->info.accum_read_ahead_bytes = std::min(size_t(BCHUNK_HASH_LEN), chunk_count) * stride;
1535#endif
1536
1539 /* Allow iteration to simplify freeing, otherwise its not needed
1540 * (we could loop over all states as an alternative). */
1542
1544
1545 return bs;
1546}
1547
1549{
1550 /* Free chunk data. */
1551 {
1552 BLI_mempool_iter iter;
1553 BChunk *chunk;
1554 BLI_mempool_iternew(bs->memory.chunk, &iter);
1555 while ((chunk = static_cast<BChunk *>(BLI_mempool_iterstep(&iter)))) {
1556 BLI_assert(chunk->users > 0);
1557 MEM_freeN(chunk->data);
1558 }
1559 }
1560
1561 /* Free states. */
1562 for (BArrayState *state = static_cast<BArrayState *>(bs->states.first), *state_next; state;
1563 state = state_next)
1564 {
1565 state_next = state->next;
1567 }
1568}
1569
1580
1591
1593
1594/* -------------------------------------------------------------------- */
1597
1599{
1600 size_t size_accum = 0;
1601 LISTBASE_FOREACH (const BArrayState *, state, &bs->states) {
1602 size_accum += state->chunk_list->total_expanded_size;
1603 }
1604 return size_accum;
1605}
1606
1608{
1609 size_t size_total = 0;
1610 BLI_mempool_iter iter;
1611 const BChunk *chunk;
1612 BLI_mempool_iternew(bs->memory.chunk, &iter);
1613 while ((chunk = static_cast<BChunk *>(BLI_mempool_iterstep(&iter)))) {
1614 BLI_assert(chunk->users > 0);
1615 size_total += size_t(chunk->data_len);
1616 }
1617 return size_total;
1618}
1619
1621
1622/* -------------------------------------------------------------------- */
1625
1627 const void *data,
1628 const size_t data_len,
1629 const BArrayState *state_reference)
1630{
1631 /* Ensure we're aligned to the stride. */
1632 BLI_assert((data_len % bs->info.chunk_stride) == 0);
1633
1634#ifdef USE_PARANOID_CHECKS
1635 if (state_reference) {
1636 BLI_assert(BLI_findindex(&bs->states, state_reference) != -1);
1637 }
1638#endif
1639
1640 BChunkList *chunk_list;
1641 if (state_reference) {
1642 chunk_list = bchunk_list_from_data_merge(&bs->info,
1643 &bs->memory,
1644 (const uchar *)data,
1645 data_len,
1646 /* Re-use reference chunks. */
1647 state_reference->chunk_list);
1648 }
1649 else {
1650 chunk_list = bchunk_list_new(&bs->memory, data_len);
1651 bchunk_list_fill_from_array(&bs->info, &bs->memory, chunk_list, (const uchar *)data, data_len);
1652 }
1653
1654 chunk_list->users += 1;
1655
1657 state->chunk_list = chunk_list;
1658
1659 BLI_addtail(&bs->states, state);
1660
1661#ifdef USE_PARANOID_CHECKS
1662 {
1663 size_t data_test_len;
1664 void *data_test = BLI_array_store_state_data_get_alloc(state, &data_test_len);
1665 BLI_assert(data_test_len == data_len);
1666 BLI_assert(memcmp(data_test, data, data_len) == 0);
1667 MEM_freeN(data_test);
1668 }
1669#endif
1670
1671 return state;
1672}
1673
1675{
1676#ifdef USE_PARANOID_CHECKS
1677 BLI_assert(BLI_findindex(&bs->states, state) != -1);
1678#endif
1679
1680 bchunk_list_decref(&bs->memory, state->chunk_list);
1681 BLI_remlink(&bs->states, state);
1682
1684}
1685
1687{
1688 return state->chunk_list->total_expanded_size;
1689}
1690
1692{
1693#ifdef USE_PARANOID_CHECKS
1694 size_t data_test_len = 0;
1695 LISTBASE_FOREACH (BChunkRef *, cref, &state->chunk_list->chunk_refs) {
1696 data_test_len += cref->link->data_len;
1697 }
1698 BLI_assert(data_test_len == state->chunk_list->total_expanded_size);
1699#endif
1700
1701 uchar *data_step = (uchar *)data;
1702 LISTBASE_FOREACH (BChunkRef *, cref, &state->chunk_list->chunk_refs) {
1703 BLI_assert(cref->link->users > 0);
1704 memcpy(data_step, cref->link->data, cref->link->data_len);
1705 data_step += cref->link->data_len;
1706 }
1707}
1708
1710{
1711 void *data = MEM_mallocN(state->chunk_list->total_expanded_size, __func__);
1713 *r_data_len = state->chunk_list->total_expanded_size;
1714 return data;
1715}
1716
1718
1719/* -------------------------------------------------------------------- */
1722
1723/* Only for test validation. */
1724static size_t bchunk_list_size(const BChunkList *chunk_list)
1725{
1726 size_t total_expanded_size = 0;
1727 LISTBASE_FOREACH (BChunkRef *, cref, &chunk_list->chunk_refs) {
1728 total_expanded_size += cref->link->data_len;
1729 }
1730 return total_expanded_size;
1731}
1732
1734{
1735 bool ok = true;
1736
1737 /* Check Length
1738 * ------------ */
1739
1741 BChunkList *chunk_list = state->chunk_list;
1742 if (!(bchunk_list_size(chunk_list) == chunk_list->total_expanded_size)) {
1743 return false;
1744 }
1745
1746 if (BLI_listbase_count(&chunk_list->chunk_refs) != int(chunk_list->chunk_refs_len)) {
1747 return false;
1748 }
1749
1750#ifdef USE_MERGE_CHUNKS
1751 /* Ensure we merge all chunks that could be merged. */
1752 if (chunk_list->total_expanded_size > bs->info.chunk_byte_size_min) {
1753 LISTBASE_FOREACH (BChunkRef *, cref, &chunk_list->chunk_refs) {
1754 if (cref->link->data_len < bs->info.chunk_byte_size_min) {
1755 return false;
1756 }
1757 }
1758 }
1759#endif
1760 }
1761
1762 {
1763 BLI_mempool_iter iter;
1764 BChunk *chunk;
1765 BLI_mempool_iternew(bs->memory.chunk, &iter);
1766 while ((chunk = static_cast<BChunk *>(BLI_mempool_iterstep(&iter)))) {
1767 if (!(MEM_allocN_len(chunk->data) >= chunk->data_len)) {
1768 return false;
1769 }
1770 }
1771 }
1772
1773 /* Check User Count & Lost References
1774 * ---------------------------------- */
1775 {
1776 GHashIterator gh_iter;
1777
1778#define GHASH_PTR_ADD_USER(gh, pt) \
1779 { \
1780 void **val; \
1781 if (BLI_ghash_ensure_p((gh), (pt), &val)) { \
1782 *((int *)val) += 1; \
1783 } \
1784 else { \
1785 *((int *)val) = 1; \
1786 } \
1787 } \
1788 ((void)0)
1789
1790 /* Count chunk_list's. */
1791 GHash *chunk_list_map = BLI_ghash_ptr_new(__func__);
1792 GHash *chunk_map = BLI_ghash_ptr_new(__func__);
1793
1794 int totrefs = 0;
1796 GHASH_PTR_ADD_USER(chunk_list_map, state->chunk_list);
1797 }
1798 GHASH_ITER (gh_iter, chunk_list_map) {
1799 const BChunkList *chunk_list = static_cast<const BChunkList *>(
1800 BLI_ghashIterator_getKey(&gh_iter));
1801 const int users = POINTER_AS_INT(BLI_ghashIterator_getValue(&gh_iter));
1802 if (!(chunk_list->users == users)) {
1803 ok = false;
1804 goto user_finally;
1805 }
1806 }
1807 if (!(BLI_mempool_len(bs->memory.chunk_list) == int(BLI_ghash_len(chunk_list_map)))) {
1808 ok = false;
1809 goto user_finally;
1810 }
1811
1812 /* Count chunk's. */
1813 GHASH_ITER (gh_iter, chunk_list_map) {
1814 const BChunkList *chunk_list = static_cast<const BChunkList *>(
1815 BLI_ghashIterator_getKey(&gh_iter));
1816 LISTBASE_FOREACH (const BChunkRef *, cref, &chunk_list->chunk_refs) {
1817 GHASH_PTR_ADD_USER(chunk_map, cref->link);
1818 totrefs += 1;
1819 }
1820 }
1821 if (!(BLI_mempool_len(bs->memory.chunk) == int(BLI_ghash_len(chunk_map)))) {
1822 ok = false;
1823 goto user_finally;
1824 }
1825 if (!(BLI_mempool_len(bs->memory.chunk_ref) == totrefs)) {
1826 ok = false;
1827 goto user_finally;
1828 }
1829
1830 GHASH_ITER (gh_iter, chunk_map) {
1831 const BChunk *chunk = static_cast<const BChunk *>(BLI_ghashIterator_getKey(&gh_iter));
1832 const int users = POINTER_AS_INT(BLI_ghashIterator_getValue(&gh_iter));
1833 if (!(chunk->users == users)) {
1834 ok = false;
1835 goto user_finally;
1836 }
1837 }
1838
1839#undef GHASH_PTR_ADD_USER
1840
1841 user_finally:
1842 BLI_ghash_free(chunk_list_map, nullptr, nullptr);
1843 BLI_ghash_free(chunk_map, nullptr, nullptr);
1844 }
1845
1846 return ok;
1847 /* TODO: dangling pointer checks. */
1848}
1849
Efficient in-memory storage of multiple similar arrays.
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_INLINE
BLI_INLINE void * BLI_ghashIterator_getKey(GHashIterator *ghi) ATTR_WARN_UNUSED_RESULT
Definition BLI_ghash.h:295
BLI_INLINE void * BLI_ghashIterator_getValue(GHashIterator *ghi) ATTR_WARN_UNUSED_RESULT
Definition BLI_ghash.h:299
#define GHASH_ITER(gh_iter_, ghash_)
Definition BLI_ghash.h:318
GHash * BLI_ghash_ptr_new(const char *info) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT
unsigned int BLI_ghash_len(const GHash *gh) ATTR_WARN_UNUSED_RESULT
Definition BLI_ghash.cc:702
void BLI_ghash_free(GHash *gh, GHashKeyFreeFP keyfreefp, GHashValFreeFP valfreefp)
Definition BLI_ghash.cc:860
int BLI_findindex(const ListBase *listbase, const void *vlink) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:586
#define LISTBASE_FOREACH(type, var, list)
BLI_INLINE void BLI_listbase_clear(ListBase *lb)
BLI_INLINE bool BLI_listbase_is_empty(const ListBase *lb)
void BLI_addtail(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:111
void BLI_remlink(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:131
int BLI_listbase_count(const ListBase *listbase) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:524
void * BLI_mempool_alloc(BLI_mempool *pool) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_RETURNS_NONNULL ATTR_NONNULL(1)
void BLI_mempool_iternew(BLI_mempool *pool, BLI_mempool_iter *iter) ATTR_NONNULL()
void * BLI_mempool_iterstep(BLI_mempool_iter *iter) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
@ BLI_MEMPOOL_ALLOW_ITER
@ BLI_MEMPOOL_NOP
void BLI_mempool_free(BLI_mempool *pool, void *addr) ATTR_NONNULL(1
BLI_mempool * BLI_mempool_create(unsigned int esize, unsigned int elem_num, unsigned int pchunk, unsigned int flag) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_RETURNS_NONNULL
int BLI_mempool_len(const BLI_mempool *pool) ATTR_NONNULL(1)
void BLI_mempool_destroy(BLI_mempool *pool) ATTR_NONNULL(1)
void BLI_mempool_clear(BLI_mempool *pool) ATTR_NONNULL(1)
unsigned char uchar
unsigned int uint
#define UNUSED_VARS(...)
#define UNUSED_VARS_NDEBUG(...)
#define UNUSED(x)
#define POINTER_AS_INT(i)
#define UNLIKELY(x)
#define ELEM(...)
static void split(const char *text, const char *seps, char ***str, int *count)
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)
#define BCHUNK_HASH_TABLE_MUL
static hash_key key_from_chunk_ref(const BArrayInfo *info, const BChunkRef *cref, hash_key *hash_store, const size_t hash_store_len)
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)
static void array_store_free_data(BArrayStore *bs)
#define USE_HASH_TABLE_ACCUMULATE
static void hash_array_from_cref(const BArrayInfo *info, const BChunkRef *cref, const size_t data_len, hash_key *hash_array)
#define GHASH_PTR_ADD_USER(gh, pt)
BLI_INLINE void hash_accum_impl(hash_key *hash_array, const size_t i_dst, const size_t i_ahead)
#define BCHUNK_HASH_TABLE_ACCUMULATE_STEPS_DEFAULT
static bool bchunk_data_compare(const BChunk *chunk, const uchar *data_base, const size_t data_base_len, const size_t offset)
static hash_key hash_data(const uchar *key, size_t n)
static void bchunk_list_fill_from_array(const BArrayInfo *info, BArrayMemory *bs_mem, BChunkList *chunk_list, const uchar *data, const size_t data_len)
void BLI_array_store_state_remove(BArrayStore *bs, BArrayState *state)
#define ASSERT_CHUNKLIST_DATA(chunk_list, data)
static void bchunk_decref(BArrayMemory *bs_mem, BChunk *chunk)
static void hash_accum_single(hash_key *hash_array, const size_t hash_array_len, size_t iter_steps)
#define BCHUNK_HASH_TABLE_ACCUMULATE_STEPS_8BITS
BLI_INLINE hash_key hash_data_single(const uchar p)
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)
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 BLI_array_store_calc_size_expanded_get(const BArrayStore *bs)
#define HASH_TABLE_KEY_UNSET
void * BLI_array_store_state_data_get_alloc(const BArrayState *state, size_t *r_data_len)
BArrayStore * BLI_array_store_create(uint stride, uint chunk_count)
void BLI_array_store_clear(BArrayStore *bs)
static void bchunk_list_decref(BArrayMemory *bs_mem, BChunkList *chunk_list)
static size_t bchunk_list_size(const BChunkList *chunk_list)
#define BCHUNK_SIZE_MAX_MUL
bool BLI_array_store_is_valid(BArrayStore *bs)
#define HASH_TABLE_KEY_FALLBACK
#define data_len_original
static void bchunk_list_ensure_min_size_last(const BArrayInfo *info, BArrayMemory *bs_mem, BChunkList *chunk_list)
static void hash_accum(hash_key *hash_array, const size_t hash_array_len, size_t iter_steps)
uint32_t hash_key
#define ASSERT_CHUNKLIST_SIZE(chunk_list, n)
#define BCHUNK_HASH_TABLE_ACCUMULATE_STEPS_32BITS
static BChunk * bchunk_new(BArrayMemory *bs_mem, const uchar *data, const size_t data_len)
static void hash_array_from_data(const BArrayInfo *info, const uchar *data_slice, const size_t data_slice_len, hash_key *hash_array)
static void bchunk_list_append_only(BArrayMemory *bs_mem, BChunkList *chunk_list, BChunk *chunk)
size_t BLI_array_store_state_size_get(const BArrayState *state)
void BLI_array_store_state_data_get(const BArrayState *state, void *data)
void BLI_array_store_destroy(BArrayStore *bs)
BArrayState * BLI_array_store_state_add(BArrayStore *bs, const void *data, const size_t data_len, const BArrayState *state_reference)
size_t BLI_array_store_calc_size_compacted_get(const BArrayStore *bs)
static void bchunk_list_append(const BArrayInfo *info, BArrayMemory *bs_mem, BChunkList *chunk_list, BChunk *chunk)
#define HASH_INIT
static BChunk * bchunk_new_copydata(BArrayMemory *bs_mem, const uchar *data, const size_t data_len)
static void bchunk_list_append_data(const BArrayInfo *info, BArrayMemory *bs_mem, BChunkList *chunk_list, const uchar *data, const size_t data_len)
BLI_INLINE bool bchunk_data_compare_unchecked(const BChunk *chunk, const uchar *data_base, const size_t data_base_len, const size_t offset)
#define BCHUNK_SIZE_MIN_DIV
#define BCHUNK_HASH_TABLE_ACCUMULATE_STEPS_16BITS
static BChunkList * bchunk_list_new(BArrayMemory *bs_mem, size_t total_expanded_size)
BMesh const char void * data
int users
void * MEM_mallocN(size_t len, const char *str)
Definition mallocn.cc:128
void * MEM_calloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:123
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void * MEM_malloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:133
size_t(* MEM_allocN_len)(const void *vmemh)
Definition mallocn.cc:36
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
static ulong state[N]
size_t chunk_byte_size_max
size_t accum_steps
size_t chunk_byte_size_min
size_t chunk_stride
size_t accum_read_ahead_bytes
size_t chunk_byte_size
size_t accum_read_ahead_len
BLI_mempool * chunk_list
BLI_mempool * chunk
BLI_mempool * chunk_ref
BChunkList * chunk_list
BArrayState * next
BArrayState * prev
BArrayMemory memory
ListBase states
BArrayInfo info
uint chunk_refs_len
ListBase chunk_refs
size_t total_expanded_size
BChunk * link
BChunkRef * prev
BChunkRef * next
const uchar * data
size_t data_len
hash_key key
BTableRef * next
const BChunkRef * cref
void * last
void * first
i
Definition text_draw.cc:230