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