@@ -593,6 +593,51 @@ struct ConcurrentReadableHashMap {
593593 }
594594 }
595595
596+ // Common implementation for `getOrInsert` and `GetOrInsertManyScope`
597+ template <class KeyTy , typename Call>
598+ void getOrInsertExternallyLocked (KeyTy key, const Call &call) {
599+ auto indices = IndexStorage{Indices.load (std::memory_order_relaxed)};
600+ auto indicesCapacityLog2 = indices.getCapacityLog2 ();
601+ auto elementCount = ElementCount.load (std::memory_order_relaxed);
602+ auto *elements = Elements.load (std::memory_order_relaxed);
603+ auto *elementsPtr = elements ? elements->data () : nullptr ;
604+
605+
606+ auto found = this ->find (key, indices, elementCount, elementsPtr);
607+ if (found.first ) {
608+ call (found.first , false );
609+ return ;
610+ }
611+
612+ auto indicesCapacity = 1UL << indicesCapacityLog2;
613+
614+ // The number of slots in use is elementCount + 1, since the capacity also
615+ // takes a slot.
616+ auto emptyCount = indicesCapacity - (elementCount + 1 );
617+ auto proportion = indicesCapacity / emptyCount;
618+ if (proportion >= ResizeProportion) {
619+ indices = resize (indices, indicesCapacityLog2, elementsPtr);
620+ found = find (key, indices, elementCount, elementsPtr);
621+ assert (!found.first && " Shouldn't suddenly find the key after rehashing" );
622+ }
623+
624+ if (!elements || elementCount >= elements->Capacity ) {
625+ elements = resize (elements, elementCount);
626+ }
627+ auto *element = &elements->data ()[elementCount];
628+
629+ // Order matters: fill out the element, then update the count,
630+ // then update the index.
631+ bool keep = call (element, true );
632+ if (keep) {
633+ assert (hash_value (key) == hash_value (*element) &&
634+ " Element must have the same hash code as its key." );
635+ ElementCount.store (elementCount + 1 , std::memory_order_release);
636+ indices.storeIndexAt (&Indices, elementCount + 1 , found.second ,
637+ std::memory_order_release);
638+ }
639+ }
640+
596641public:
597642 // Implicitly trivial constructor/destructor.
598643 ConcurrentReadableHashMap () = default ;
@@ -684,6 +729,48 @@ struct ConcurrentReadableHashMap {
684729 return Snapshot (this , indices, elementsPtr, elementCount);
685730 }
686731
732+ // / A wrapper that allows performing several `getOrInsert` operations under
733+ // / the same lock.
734+ class GetOrInsertManyScope {
735+ GetOrInsertManyScope () = delete ;
736+ GetOrInsertManyScope (const GetOrInsertManyScope &) = delete ;
737+ GetOrInsertManyScope &operator =(const GetOrInsertManyScope &) = delete ;
738+ GetOrInsertManyScope (GetOrInsertManyScope &&) = delete ;
739+ GetOrInsertManyScope &operator =(GetOrInsertManyScope &&) = delete ;
740+
741+ ConcurrentReadableHashMap ⤅
742+
743+ public:
744+ GetOrInsertManyScope (ConcurrentReadableHashMap &map) : Map(map) {
745+ Map.WriterLock .lock ();
746+ }
747+
748+ ~GetOrInsertManyScope () {
749+ Map.deallocateFreeListIfSafe ();
750+ Map.WriterLock .unlock ();
751+ }
752+
753+ // / Get an element by key, or insert a new element for that key if one is
754+ // / not already present. Invoke `call` with the pointer to the element.
755+ // /
756+ // / `call` is passed the following parameters:
757+ // / - `element`: the pointer to the element corresponding to `key`
758+ // / - `created`: true if the element is newly created, false if it already
759+ // / exists
760+ // / `call` returns a `bool`. When `created` is `true`, the return values
761+ // / mean:
762+ // / - `true` the new entry is to be kept
763+ // / - `false` indicates that the new entry is discarded
764+ // / If the new entry is kept, then the new element MUST be initialized, and
765+ // / have a hash value that matches the hash value of `key`.
766+ // /
767+ // / The return value is ignored when `created` is `false`.
768+ template <class KeyTy , typename Call>
769+ void getOrInsert (KeyTy key, const Call &call) {
770+ Map.getOrInsertExternallyLocked (key, call);
771+ }
772+ };
773+
687774 // / Get an element by key, or insert a new element for that key if one is not
688775 // / already present. Invoke `call` with the pointer to the element. BEWARE:
689776 // / `call` is invoked with the internal writer lock held, keep work to a
@@ -703,48 +790,7 @@ struct ConcurrentReadableHashMap {
703790 template <class KeyTy , typename Call>
704791 void getOrInsert (KeyTy key, const Call &call) {
705792 typename MutexTy::ScopedLock guard (WriterLock);
706-
707- auto indices = IndexStorage{Indices.load (std::memory_order_relaxed)};
708- auto indicesCapacityLog2 = indices.getCapacityLog2 ();
709- auto elementCount = ElementCount.load (std::memory_order_relaxed);
710- auto *elements = Elements.load (std::memory_order_relaxed);
711- auto *elementsPtr = elements ? elements->data () : nullptr ;
712-
713- auto found = this ->find (key, indices, elementCount, elementsPtr);
714- if (found.first ) {
715- call (found.first , false );
716- deallocateFreeListIfSafe ();
717- return ;
718- }
719-
720- auto indicesCapacity = 1UL << indicesCapacityLog2;
721-
722- // The number of slots in use is elementCount + 1, since the capacity also
723- // takes a slot.
724- auto emptyCount = indicesCapacity - (elementCount + 1 );
725- auto proportion = indicesCapacity / emptyCount;
726- if (proportion >= ResizeProportion) {
727- indices = resize (indices, indicesCapacityLog2, elementsPtr);
728- found = find (key, indices, elementCount, elementsPtr);
729- assert (!found.first && " Shouldn't suddenly find the key after rehashing" );
730- }
731-
732- if (!elements || elementCount >= elements->Capacity ) {
733- elements = resize (elements, elementCount);
734- }
735- auto *element = &elements->data ()[elementCount];
736-
737- // Order matters: fill out the element, then update the count,
738- // then update the index.
739- bool keep = call (element, true );
740- if (keep) {
741- assert (hash_value (key) == hash_value (*element) &&
742- " Element must have the same hash code as its key." );
743- ElementCount.store (elementCount + 1 , std::memory_order_release);
744- indices.storeIndexAt (&Indices, elementCount + 1 , found.second ,
745- std::memory_order_release);
746- }
747-
793+ getOrInsertExternallyLocked (key, call);
748794 deallocateFreeListIfSafe ();
749795 }
750796
0 commit comments