Sugerowałbym, że jeśli chce się wdrożyć kopiowanie przy zapisie skutecznie (na smyczki lub cokolwiek), należy zdefiniować typ wrapper który będzie zachowywał się jak zmienny łańcucha, i która będzie posiadać zarówno pustych odwołanie do zmiennego łańcucha (żadne inne odniesienie do tego elementu nigdy nie będzie istnieć) i zerowalne odniesienie do "niezmiennych" ciągów (odniesienia do których nigdy nie będzie istnieć poza rzeczami, które nie będą próbowały go zmutować). Opakowania będą zawsze tworzone z co najmniej jednym z tych odniesień, które nie są zerowe; gdy odniesienie do pozycji zmiennoprzecinkowej jest kiedykolwiek ustawione na wartość inną niż null (podczas lub po zakończeniu budowy), zawsze będzie odnosić się do tego samego celu. Za każdym razem, gdy oba odniesienia mają wartość inną niż null, odwołanie do niezmiennego elementu wskaże kopię elementu, który został wykonany jakiś czas po ostatniej zakończonej mutacji (podczas mutacji odwołanie do niezmiennego elementu może lub nie może zawierać odniesienia do wartości przed mutacją).
Aby odczytać obiekt, należy sprawdzić, czy odwołanie do "zmiennego elementu" ma wartość inną niż null. Jeśli tak, użyj go. W przeciwnym razie sprawdź, czy odwołanie do "niezmiennego elementu" ma wartość inną niż null. Jeśli tak, użyj go. W przeciwnym razie użyj odwołania "item zmienny" (które już nie będzie miało wartości NULL).
Aby zmutować obiekt, sprawdź, czy odniesienie do "pozycji zmiennej" ma wartość inną niż null. Jeśli nie, skopiuj cel odniesienia "niezmienny element" i porównajZmień odniesienie do nowego obiektu do pozycji odniesienia "pozycja zmienna". Następnie zmodyfikuj cel odniesienia odniesienia "zmienny przedmiot" i unieważnij odniesienie do "niezmiennych elementów".
Aby sklonować obiekt, jeśli klon ma zostać ponownie sklonowany przed zmutowaniem, należy pobrać wartość odwołania "niezmienny element". Jeśli ma wartość NULL, wykonaj kopię elementu docelowego "zmienny element" i porównaj wartość odniesienia do tego nowego obiektu w odwołanie do niezmiennego elementu. Następnie utwórz nowe opakowanie, którego odwołanie do "pozycji zmiennoprzecinkowych" jest puste i którego odwołanie do "niezmiennego elementu" jest wartością pobraną (jeśli nie była zerowa) lub nową pozycją (jeśli takowa była).
Aby sklonować obiekt, jeśli klon ma zostać zmutowany przed sklonowaniem, należy pobrać wartość odniesienia "niezmienny element". Jeśli null, pobierz odwołanie do "pozycji zmiennej". Skopiuj cel dowolnego odwołania, a następnie utwórz nowe opakowanie, którego odniesienie do "pozycji zmiennoprzecinkowych" wskazuje na nową kopię i którego odwołanie do "niezmiennego elementu" jest puste.
Obie metody klonowania będą semantycznie identyczne, ale wybór niewłaściwej dla danej sytuacji spowoduje dodatkową operację kopiowania. Jeśli konsekwentnie wybierzesz poprawną operację kopiowania, uzyskasz największą korzyść z "agresywnego" kopiowania przy zapisie, ale z dużo mniejszym obciążeniem. Każdy obiekt przechowujący dane (na przykład łańcuch) będzie albo nieudostępniony, albo zmienny, niezmienny i żaden obiekt nigdy nie będzie przełączał się między tymi stanami.W związku z tym można w razie potrzeby wyeliminować wszystkie "narzuty do gwintowania/synchronizacji" (zastępując operacje CompareExchange prostymi magazynami), pod warunkiem, że żaden obiekt opakowania nie jest używany w więcej niż jednym wątku jednocześnie. Dwa obiekty opakowania mogą zawierać odniesienia do tego samego niezmiennego właściciela danych, ale mogą być nieświadome istnienia innych.
Należy pamiętać, że podczas korzystania z tego podejścia może być wymagane kilka dodatkowych operacji kopiowania niż w przypadku stosowania metody "agresywnej". Na przykład, jeśli nowa etykieta zostanie utworzona z nowym ciągiem, a opakowanie zostanie zmutowane i skopiowane sześć razy, oryginalne opakowanie będzie zawierać odniesienia do oryginalnego posiadacza ciągów znaków i niezmiennego pliku przechowującego kopię danych. Sześć skopiowanych owijaczy wystarczyłoby odwołać się do niezmiennego ciągu (dwa ciągi łącznie, chociaż jeśli pierwotny ciąg nigdy nie został zmutowany po wykonaniu kopii, agresywna implementacja mogłaby zająć jeden). Jeśli oryginalne opakowanie zostało zmutowane, wraz z pięcioma z sześciu kopii, wszystkie z wyjątkiem jednego z odwołań do niezmiennego łańcucha zostałyby unieważnione. W tym momencie, jeśli szósta kopia opakowania została zmutowana, agresywna implementacja kopiowania przy zapisie może uświadomić sobie, że zawiera ona jedyne odniesienie do jej ciągu, a tym samym zdecydować, że kopia nie była potrzebna. Wdrożona przeze mnie implementacja utworzyłaby nową zmienną kopię i porzuciłaby niezmienny. Pomimo faktu, że istnieje kilka dodatkowych operacji kopiowania, jednak zmniejszenie narzutu na gwint powinno w większości przypadków zrekompensować koszty. Jeśli większość tworzonych kopii logicznych nigdy nie zostanie zmutowana, takie podejście może być skuteczniejsze niż wykonywanie kopii ciągów.
Jaka jest Twoja strategia alokacji pamięci? Aby uzyskać lepszą wydajność, możesz polegać na alokacji puli. –
Mam nadzieję, że to tylko nauka. Jest tak wiele pułapek, aby działało poprawnie we wszystkich sytuacjach. –
tylko dla celów edukacyjnych .. – fiveOthersWaiting