COW Możliwe że obiekt zostanie odłączony

COW Możliwe że obiekt zostanie odłączony
Marius.Maximus
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 2202
0

W kontekście programowania użycie słowa "możliwe" powoduje u mnie dziwny niepokój

Przykład:

Kopiuj
QList<int> list1 = {1, 2, 3, 4};
QList<int> list2 = list1;  // list1 i list2 współdzielą pamięć

qDebug() << list1.isSharedWith(list2);  // Wydrukuje: true

for (auto i : list1) { // c++11 range-loop might detach Qt container (QList) [clazy-range-loop-detach] 
    qDebug() << i;
}

qDebug() << list1.isSharedWith(list2);  // Możliwe, że teraz będzie false, bo mogło nastąpić odłączenie!

Jak to jest możliwe , ja wolałbym mieć pewność , a nawet jestem pewny ze zawsze chciałbym wiedzieć jak zachowa sie program.

Jaka jest główna zaleta COW Copy-on-Write ?
Bo nie dostrzegam piekna tej konstrukcji

lion137
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 5025
1

Zobacz w dokumentacji, może tutaj:
https://doc.qt.io/qt-6/implicit-sharing.html

loza_prowizoryczna
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 1628
1
Marius.Maximus napisał(a):

Jaka jest główna zaleta COW Copy-on-Write ?
Bo nie dostrzegam piekna tej konstrukcji

Żadna, kiedyś w teorii miało to oszczędzać pamięć i zmniejszać liczbę hit-miss na różnych poziomach cache Lx. Dziś w momencie gdy całe binarki mogą siedzieć w całości w L3 to legacy które dodatkowo utrudnia systemowi operacyjnemu/runtimeowi/CPU optymalizację rozłożenia obciążenia pomiędzy rdzeniami bo nigdy nie wiadomo która część pamięci jest współdzielona.

Marius.Maximus
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 2202
0

COW mi wygląda trochę na ślepą ścieżkę ewolucyjną
bo chatGPT mi podpowiada ze std::string w C++98 tez to chyba miał (ale do konca mu nie ufam i nie weryfikowałem)
w Delphi pamietam że String tez miał ten bajer i też sie zastanawiałem czemu to ma służyć

mwl4
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: Wrocław
  • Postów: 404
2

Klasy mają spełniać pewne założenia. Jeśli te założenia są spełnione to szczegóły implementacyjne nie są aż tak istotne. To skąd taka klasa QList bierze pamięć - czy jest ona współdzielona czy nie, jest szczegółem implementacyjnym.

Drugą sprawą jest słowo "możliwe". Jestem pewien, że w tym kontekście oznacza ono tyle, że warunków, kiedy pamięć przestaje być współdzielona jest tak wiele i są one tak skomplikowane, że nie warto tego opisywać. Zwłaszcza, że jest to szczegół implementacyjny i użytkownika klasy nie powinno interesować czy pamięć jest współdzielona czy też nie.
Innymi słowy, kod na pewno jest deterministyczny. Znając wszystkie warunki odłączenia, jesteś w stanie przewidzieć stan odłączenia.

mwl4
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: Wrocław
  • Postów: 404
2
Marius.Maximus napisał(a):

COW mi wygląda trochę na ślepą ścieżkę ewolucyjną
bo chatGPT mi podpowiada ze std::string w C++98 tez to chyba miał (ale do konca mu nie ufam i nie weryfikowałem)
w Delphi pamietam że String tez miał ten bajer i też sie zastanawiałem czemu to ma służyć

Może trudno w to uwierzyć, ale Qt powstało prawie 30 lat temu (1995). W tamtych czasach komputery miały najczęściej od 4 do 8 MB pamięci RAM. Robiąc optymalizację COW mogli na pewno oszczędzić sporo pamięci. Wydajność nie była aż tak pożądana.

HM
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 34
1

COW w Qt ma sens do tej pory. Mechanizm sygnałów i slotów opiera się dość mocno na kopiach, a move nie zawsze jest chciane. Stąd kontenery i qstring mają ców aby kopię w sygnałach i slotach były taniutkie. Pamiętajcie, że sygnały działają między wątkami.

loza_prowizoryczna
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 1628
1
Herr Mannelig napisał(a):

COW w Qt ma sens do tej pory. Mechanizm sygnałów i slotów opiera się dość mocno na kopiach, a move nie zawsze jest chciane. Stąd kontenery i qstring mają ców aby kopię w sygnałach i slotach były taniutkie.

G**no prawda. Mają COW bo w swej upośledzonej implementacji alokują obiekty na stercie (więc muszą trackować pamięć). Gdyby obiekty te były zwyczajowymi struclami z dowiązanymi funkcjami (jak w pierwszych iteracjach GTK) to problemu by nie było a kompilator statycznie by takie kopiowanie wycinał.

MarekR22
  • Rejestracja: dni
  • Ostatnio: dni
3
Marius.Maximus napisał(a):

W kontekście programowania użycie słowa "możliwe" powoduje u mnie dziwny niepokój

Przykład:

Kopiuj
QList<int> list1 = {1, 2, 3, 4};
QList<int> list2 = list1;  // list1 i list2 współdzielą pamięć

qDebug() << list1.isSharedWith(list2);  // Wydrukuje: true

for (auto i : list1) { // c++11 range-loop might detach Qt container (QList) [clazy-range-loop-detach] 
    qDebug() << i;
}

qDebug() << list1.isSharedWith(list2);  // Możliwe, że teraz będzie false, bo mogło nastąpić odłączenie!

Jak to jest możliwe , ja wolałbym mieć pewność , a nawet jestem pewny ze zawsze chciałbym wiedzieć jak zachowa sie program.

Jaka jest główna zaleta COW Copy-on-Write ?
Bo nie dostrzegam piekna tej konstrukcji

Głeboka kopia wykonuje się za każdym razem jak następuje użycie funkcji none const.

Ergo w twoim przykładzie: for (auto i : list1) niejawnie zostanie wywołane list1.begin() w wersji none const.
Efekt jest taki, że głeboka kopia zostanie wykonana.

Żeby temu zapobiec, trzeba przekonać kompilator, by użył przeładowań const dla list1.begin() i list1.end().
Samo dodanie const & ( for (const auto& i : list1)) raczej nie pomoże bo problemem jest to, że list1 nie jest typu const.

Demo:
https://godbolt.org/z/W5ser1xPr

HM
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 34
0
loza_prowizoryczna napisał(a):
Herr Mannelig napisał(a):

COW w Qt ma sens do tej pory. Mechanizm sygnałów i slotów opiera się dość mocno na kopiach, a move nie zawsze jest chciane. Stąd kontenery i qstring mają ców aby kopię w sygnałach i slotach były taniutkie.

G**no prawda. Mają COW bo w swej upośledzonej implementacji alokują obiekty na stercie (więc muszą trackować pamięć). Gdyby obiekty te były zwyczajowymi struclami z dowiązanymi funkcjami (jak w pierwszych iteracjach GTK) to problemu by nie było a kompilator statycznie by takie kopiowanie wycinał.

Szkoda tylko, że generacji w trakcie kompilacji podlega wyłącznie kod slotów, a nie dowiązania. Sygnały I sloty muszą mieć możliwość pełnego operowania w runtime, w tym dołączania, odłączania, współpracy z bibliotekami dynamicznymi oraz obiektami QML. Są to dość częste scenariusze w ramach Qt stąd nie można liczyć ani na optymalizację kompilatora dla każdego przypadku ani wyciąć pewnych rzeczy na etapie MOC.

loza_prowizoryczna
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 1628
0
Herr Mannelig napisał(a):

Szkoda tylko, że generacji w trakcie kompilacji podlega wyłącznie kod slotów, a nie dowiązania. Sygnały I sloty muszą mieć możliwość pełnego operowania w runtime, w tym dołączania, odłączania, współpracy z bibliotekami dynamicznymi oraz obiektami QML. Są to dość częste scenariusze w ramach Qt stąd nie można liczyć ani na optymalizację kompilatora dla każdego przypadku ani wyciąć pewnych rzeczy na etapie MOC.

Bo Qt powstało by łatać upośledzenie C++ (ówczesnego) gdzie kompilatory były prymitywne a GCC było katedralną kobyłą gdzie każda zmiana na lepsze mogła rozwalić jakiś kod wynikowy na przedpotopowym SPARCu. Czyli meta na upośledzonym języku na upośledzonych kompilatorach.

Dziś mamy LLVM (dzięki ci Apple!) a GCC w końcu ogarnęli by dało się go rozwijać. Więc po co bawić się w Qt na reliktowym C++ i jego myki na optymalizację które nijak mają się do dzisiejszych architektur i kompilatorów?

Marius.Maximus
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 2202
0
loza_prowizoryczna napisał(a):

Więc po co bawić się w Qt na reliktowym C++ i jego myki na optymalizację które nijak mają się do dzisiejszych architektur i kompilatorów?

A to z ciekawości zapytam co proponujesz zamiast aby tworzyć UI w C++ ?

HM
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 34
0
loza_prowizoryczna napisał(a):
Herr Mannelig napisał(a):

Szkoda tylko, że generacji w trakcie kompilacji podlega wyłącznie kod slotów, a nie dowiązania. Sygnały I sloty muszą mieć możliwość pełnego operowania w runtime, w tym dołączania, odłączania, współpracy z bibliotekami dynamicznymi oraz obiektami QML. Są to dość częste scenariusze w ramach Qt stąd nie można liczyć ani na optymalizację kompilatora dla każdego przypadku ani wyciąć pewnych rzeczy na etapie MOC.

Bo Qt powstało by łatać upośledzenie C++ (ówczesnego) gdzie kompilatory były prymitywne a GCC było katedralną kobyłą gdzie każda zmiana na lepsze mogła rozwalić jakiś kod wynikowy na przedpotopowym SPARCu. Czyli meta na upośledzonym języku na upośledzonych kompilatorach.

Dziś mamy LLVM (dzięki ci Apple!) a GCC w końcu ogarnęli by dało się go rozwijać. Więc po co bawić się w Qt na reliktowym C++ i jego myki na optymalizację które nijak mają się do dzisiejszych architektur i kompilatorów?

Nie, nie z tego powodu. Wybacz jeśli zabrzmi to arogancko, ale wytłumaczę to ostatni raz. Mechanizm slotów musi mieć zapewnione całkowite działanie w czasie wykonania. Oznacza to, iż twoja aplikacja wystawia pewien interface do którego może podłączyć się w trakcie wykonania dowolny, nie znany wcześniej komponent - na przykład kod ładowany w trakcie wykonania z biblioteki dzielonej. Żaden kompilator nie może tutaj nic poradzić, gdyż oddzielne aplikacje, a de facto interakcja twoich źródeł z błotem binarnym o którym jedyne co wiemy to kilka symboli stanowi kompletną barierę optymalizacyjną.
Jedyne co ma prawo działać to move semantic, ale to nie jest złoty środek.
Zresztą akurat tego typu optymalizację ogólnie bywają i tak trudne dla kompilatorów prawie każdego języka gdyż przechodzą na granicy między plikami obiektowymi. Programistą lub projektant języka musi wtedy jak najwięcej implementacji pchać do źródeł pliku, w Rust temu masz tyle template. Po tym jedynym który może coś zmienić między plikami obiektowymi jest linker, to właśnie on potrafi oczy lto wykonać np. Dewirtualizację wywołań. A i tak nie robi tego dobrze, nawet clangowy.

Proponowałbym trochę więcej powściągliwości w materii tego czy faktycznie postęp jest tak duży jak się wydaje.

loza_prowizoryczna
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 1628
0
Herr Mannelig napisał(a):

No ok, DLLki i inne COMy - masz rację. Pytanie brzmi - po co odpalać w uwspólnionej pamięci procesu jakieś binarne g**no (które stanowi dodatkowy wektor ataku w aplikacjach krytycznych) skoro można je odpalić jako standardowy proces w izolowanym kontekście wykonania i mieć wywalone na to czy przepełnia bufor, cieknie czy zajmuje 100% zasobów (bo wtedy OS go ukatrupi) i komunikować się z nim przez jakieś IPC?

To o czym piszesz to kolejne legacy z czasów gdzie pamięć była droga, a oddzielne procesy + IPC kosztowne. Unixy miały od początku sensowną architekturę - proces per zadanie, shell pipeline jako IPC i to działało bardzo dobrze (chociaż to mainframe'y). COMy i DLLe przyszły z desktopami (konkretnie Windowsem) bo tam były zasoby ograniczone. Ale już dawno nie są. Po co się w to pchać?

Popatrz jakie założenia przyjęli twórcy Swift Package Managera - oni wyraźnie starają się ograniczać użycie binary frameworków a koncentrują się na zaciąganiu czystych źródeł + ew. statykach bo dzięki temu problemy o których wspominasz są minimalizowane. Ale to są nowe systemy dla nowych potrzeb działające w nowoczesnym środowisku.

Marius.Maximus napisał(a):

A to z ciekawości zapytam co proponujesz zamiast aby tworzyć UI w C++ ?

Proponuję nie tworzyć UI w C++ a logikę biznesową. Jak już potrzebujesz UI to napisz sobie w jakimś Elektronie (dla wszystkich platform) ew. warstwa per OS.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.