Odkąd zacząłem używać Darktable, mniej więcej w 2012 roku, zawsze zaskakiwało mnie, jak mało pamięci RAM zużywa. Ludzie sądzą, że to dobrze, gdy aplikacja oszczędnie gospodaruje pamięcią — i to z pewnością prawda, gdy mowa o Twoim środowisku graficznym. Ale gdy mówimy o oprogramowaniu produkcyjnym, które wykonuje ciężkie renderowanie pikseli na obrazach od 12 do 54 Mpikseli, oznacza to, że te same ciężkie obliczenia wykonywane są w kółko, zamiast być zapisane do ponownego wykorzystania później. Właśnie od tego jest pamięć podręczna (cache): od unikania kosztownych obliczeń. I powinna używać do tego całej dostępnej pamięci RAM, ponieważ obliczanie to marnowanie energii, a to ma konkretny wpływ, jeśli pracujesz na baterii. Poza tym zapłaciłeś za tę pamięć RAM, a jej używanie nie rozładowuje baterii. Z kolei CPU/GPU…

Tak, fotoreporterzy i wielu cyfrowych nomadów retuszuje zdjęcia na laptopach zasilanych z baterii, w terenie. Najwyższy czas, by świat open source uświadomił sobie, że nie wszyscy fotografowie siedzą przy biurku wieczorami i w weekendy, po swojej „prawdziwej" dziennej pracy. Zatem obliczanie i przeliczanie w kółko tych samych rzeczy to po prostu marnowanie energii, niezależnie od tego, czy jesteś podłączony do gniazdka, czy pracujesz na baterii.

Tymczasem Darktable zawsze miał to dziwne, małe ustawienie pamięci RAM, które pozwalało określić, ile ma jej używać. Tyle że nie miał żadnej możliwości faktycznego śledzenia, ile pamięci RAM zużywa, więc była to raczej wskazówka niż reguła. Zostało to później zastąpione zupełnie idiotyczną, jakościową preferencją „zasobów", która zarówno nic nie znaczy, jak i faktycznie nie mówi Ci, ile pamięci RAM przydzielasz oprogramowaniu.

Zatem pamięć podręczna RAM w Darktable nigdy tak naprawdę nie działała, była niewykorzystana i wymagała naprawy. Bo w rezultacie wszystko było ciągle przeliczane bez powodu.

Ale… Był powód, dla którego pamięć podręczna była niewykorzystana i tak naprawdę nie działała: to oprogramowanie miało zerową widoczność cyklu życia danych, co jest niejako warunkiem wstępnym, jeśli chcesz przechowywać dane do ponownego wykorzystania. Wynikało to z potoku pikseli, który był naprawdę zawiły (naprawdę zastanawiam się, czy ktokolwiek spośród „deweloperów" Darktable jest w stanie narysować schemat blokowy tego, co robimy z pikselami, od początku do końca), ale także z metadanych obrazu, które były obliczane w kilku miejscach, kopiowane i przechowywane (a następnie częściowo aktualizowane) w kilku miejscach…

Pamięć podręczna potoku miała jeden cel: alokowanie buforów wejściowych i wyjściowych modułów oraz ewentualne ich ponowne wykorzystanie, jeśli były już dostępne. Ale jej rozmiar był określony w liczbie wpisów, która była naprawdę ograniczona, a nie w ilości zaalokowanej pamięci. To czyniło ją średnio użyteczną i zmuszało nas do używania jej zbyt zachowawczo, ponieważ jej faktyczny rozmiar w pamięci był całkowicie nieprzewidywalny.

Nie trzeba dodawać, że „naprawa pamięci podręcznej" implikowała „naprawę potoku", co z kolei implikowało „uczynienie cyklu życia danych widocznym, śledzalnym i zarządzanym globalnie", co implikowało przeprojektowanie wysokopoziomowej architektury oprogramowania. Jak zobaczymy, przyniosło to nieoczekiwane korzyści w wydajności, które znacznie przewyższają wewnętrzne optymalizacje na poziomie modułów, które Darktable wykonał w tym samym czasie.

Co zmieniło się od 2022 roku

Ten artykuł jest bezpośrednią kontynuacją Naprawianie pamięci podręcznej potoku i 10-letnich błędów. Poprzedni rozdział dotyczył znajdowania zgnilizny i zatrzymywania najbardziej kompromitujących awarii. Ten dotyczy tego, co stało się później, gdy stało się oczywiste, że błędy pamięci podręcznej nie były wcale odosobnionymi błędami, lecz objawami renderera, którego odpowiedzialności były zmieszane ze sobą przez zdecydowanie zbyt długi czas.

Jak to się wpisuje historycznie

Pierwotny problem nie polegał jedynie na tym, że „pamięć podręczna nie cachuje wystarczająco dużo". Faktyczny problem polegał na tym, że cały stos potoku wyrósł wokół przypadkowego zachowania. Przerysowania GUI mogły w niekontrolowany sposób doprowadzić do rozpoczęcia przetwarzania obrazu. Podgląd maski przeciekał stan GUI do renderera. Logika histogramu i próbnika koloru zależała od specjalnych efektów ubocznych potoku. Eksport, miniatury, podgląd i ciemnia — wszystkie miały swoje własne małe warianty „na pewno możemy po prostu skopiować ten bufor jeszcze raz". A gdy tylko udowodnienie spójności stawało się trudne (podczas wyświetlania maski lub zmian kadrowania), strategią awaryjną było często opróżnianie linii pamięci podręcznej i przeliczanie wszystkiego, bo najwyraźniej „podpal to i zacznij od nowa" przez jakiś czas uchodziło za architekturę.

Niektóre komentarze w kodzie już to mówiły. Były ostrzeżenia o przeliczaniu napędzanym przerysowaniami. Była nawet stara notatka mówiąca, że pamięć podręczna powinna ostatecznie stać się globalna. Zatem ostatnich kilka lat nie polegało na wymyślaniu nowej teorii od zera. Polegało głównie na wreszcie wykonaniu porządków, o które kod błagał od lat 2010.

Kluczowa zmiana historyczna polega na tym, że potok przestał być traktowany jak jedna rekurencyjna kula, od której oczekiwano, że sama odkryje swoje potrzeby w trakcie działania. Był stopniowo dzielony na wyraźniejsze ścieżki wykonania dla renderowania interaktywnego, renderowania bezgłowego (headless), przetwarzania GPU, przetwarzania CPU oraz konsumentów po stronie GUI. Jednocześnie zachowanie GUI zostało odepchnięte z powrotem do GUI. Rysowanie znów stało się rysowaniem. Przetwarzanie znów stało się przetwarzaniem. Brzmi to banalnie, co zwykle jest oznaką, że poprzedni projekt stał się absurdalny.

Sumy kontrolne, czyli jak ważność przestała być zgadywanką

Gdy renderer nie był już splątany z każdym widżetem i każdym kanałem pobocznym, stało się możliwe jawne zdefiniowanie stanu obrazu. Każdy element historii niesie teraz sumę kontrolną modułu zbudowaną z parametrów modułu, parametrów mieszania i wszelkich istotnych form maski. To daje każdemu stanowi modułu kanoniczny odcisk palca, gdy tylko wchodzi do historii, zamiast pozostawiać ważność do wywnioskowania później na podstawie tego, co akurat się wykonało.

Samo to nie wystarczyło, ponieważ potok to nie tylko stos ustawień modułów. Zależy on także od kolejności, obszaru zainteresowania, kontraktów buforów i stanu związanego z wyświetlaniem. Zatem etap planowania akumuluje te odciski palców na poziomie modułów przez cały łańcuch i włącza resztę kontraktu wykonawczego, zanim jakiekolwiek piksele się poruszą. Wynikiem jest stabilna tożsamość na etap i ostateczna tożsamość potoku.

To tutaj wreszcie pojawia się widoczność. Potok może wiedzieć, czy jest nadal ważny, porównując swój skrót historii. Wyświetlany backbuffer może wiedzieć, czy nadal pasuje do bieżącego stanu potoku i historii. Linia pamięci podręcznej może wiedzieć, czy naprawdę należy do dokładnie tego etapu, ROI i parametrów aktualnie żądanych. Brzmi to niemal obraźliwie rozsądnie, ale wymagało najpierw wielu porządków: synchronizacja historii z potokiem musiała zostać uczyniona jawną, obejście pamięci podręcznej musiało być zadeklarowane z wyprzedzeniem (dla podglądu maski i edycji kadrowania), wyświetlanie maski nie mogło już zmieniać reguł w połowie wykonania, a stare efekty uboczne wyzwalane przerysowaniami musiały odejść. Nie uzyskasz niezawodnych skrótów na wierzchu zawodnego przepływu sterowania.

Praktyczna korzyść polega na tym, że spójność nie jest już utrzymywana przez zabobony. Nie musimy już pytać „czy GUI prawdopodobnie unieważniło właściwą rzecz we właściwym czasie?" Odpowiedź jest w sumach kontrolnych. Albo stan pasuje, albo nie. I możemy pobrać dowolne wyjście modułu z warstwy GUI, bez uruchamiania potoku, ponieważ suma kontrolna modułu jest przewidywalna i może być obliczona przed renderowaniem lub równolegle.

Architektura zorientowana na pamięć podręczną (cache-first)

Niezawodne skróty zmieniają architekturę niemal siłą rzeczy. Gdy każdy znaczący stan potoku ma stabilną tożsamość znaną z wyprzedzeniem, pamięć podręczna przestaje być optymalizacją na miarę możliwości i staje się centrum przepływu danych.

Dlatego potok ma teraz prawdziwą fazę planowania przed wykonaniem. Najpierw synchronizowana jest historia. Obszary zainteresowania są propagowane przez cały łańcuch. Każdy etap otrzymuje jawny kontrakt wejścia i wyjścia. Polityka pamięci podręcznej po stronie hosta jest zapieczętowana przed uruchomieniem. Globalne skróty są obliczane przed wykonaniem. Innymi słowy, silnik wie, co ma zamiar zrobić, zanim zacznie to robić, co jest orzeźwiającą odmianą od starszej metody odkrywania tego w locie i kompensowania hackami.

Odtąd wyszukiwania w pamięci podręcznej stają się deterministyczne. Etap może ponownie otworzyć dokładnie ten bufor, który pasuje do jego stanu, więc moduł może całkowicie pominąć obliczenia, jedynie zaglądając do pamięci podręcznej, jeśli linia pamięci podręcznej już pasuje do jego wewnętrznego stanu w ramach wyszukiwania po sumie kontrolnej. Konsumenci dalej w potoku mogą ponownie otwierać opublikowane wyniki po skrócie, zamiast prosić renderer o wykonanie jeszcze jednego specjalnego tańca tylko dla nich. Pamięć podręczna przestaje być stertą mgliście wielokrotnie używalnej pamięci i staje się autorytatywnym rejestrem ważnych wyników pośrednich i końcowych.

To także wyjaśnia, dlaczego można było usunąć lub uprościć tak wiele kodu. Gdy ważność jest scentralizowana, potrzebujesz mniej heurystyk, mniej specjalnych gałęzi, mniej klauzul „z wyjątkiem sytuacji, gdy GUI jest w tym trybie" i mniej awaryjnych opróżnień. Kod staje się krótszy nie dlatego, że problem stał się mniejszy, lecz dlatego, że przestał być rozwiązywany w sześciu sprzecznych miejscach naraz.

Linie pamięci podręcznej wiążą także pamięć GPU i CPU, co pozwala nam używać przypiętej pamięci OpenCL (pinned memory). Od zawsze istniała preferencja użytkownika OpenCL, która pozwalała włączyć lub wyłączyć przypiętą pamięć, ale odkryłem, że ta opcja była używana tylko do przetwarzania kafelkowego, gdy moduły nie mieściły całych swoich potrzeb pamięciowych na GPU. Przypięta pamięć została teraz rozszerzona także na normalne operacje, ponieważ pozwala mapować RAM i VRAM bez kopiowania. Użycie przypiętej pamięci na w pełni zbuforowanym potoku GPU redukuje czas spędzony na przenoszeniu buforów pamięci nawet o czynnik 7.

Ale wymuszone cachowanie wyjść modułów OpenCL, nawet z przypiętymi buforami zero-copy, wciąż ma pewien koszt i jest bardziej kosztowne niż zwykłe całkowite pominięcie pamięci podręcznej. Dla niektórych ciężkich modułów (odszumianie profilowane, rozmycie lub wyostrzenie, korektor kontrastu) narzut cachowania zdecydowanie się opłaca, ale dla szybkich (balans bieli, ekspozycja, wejściowy profil koloru) koszt cachowania buforów OpenCL sprawił, że całe moduły działały faktycznie wolniej na GPU niż na CPU.

Mamy więc teraz heurystyki na poziomie modułu, które decydują, czy moduł OpenCL opublikuje swój bufor wyjściowy w pamięci podręcznej RAM. Ciężkie moduły oraz wszystkie moduły przechwytujące próbnik koloru lub histogramy są publikowane domyślnie. W przypadku pozostałych użytkownicy mogą zdecydować sami:

image

Pozwoli to użytkownikom także wybiórczo wyłączyć OpenCL w niektórych modułach, gdy mają z nimi powracające problemy. Na przykład odszumianie profilowane i usuwanie mgły zużywają dużo pamięci GPU i mogą zawieść w trakcie działania z powodu braku dostępnej pamięci, mimo że początkowe planowanie pamięci uznało, że powinno jej wystarczyć. Gdy to się zdarza, moduł już przez pewien czas prowadził obliczenia i przełączy się awaryjnie na CPU. Jeśli zdarza się to często, lepiej stale omijać GPU w tym module, niż ponosić podwójną karę.

Ale to nie koniec. Bufory GPU są utrzymywane przy życiu także na GPU. Oznacza to, że wznowienie częściowego potoku od modułu OpenCL pobiera wejście bezpośrednio z GPU, gdy to możliwe, bez żadnego kopiowania z pamięci RAM. Oznacza to również, że lekkie moduły są w stanie ponownie wykorzystać swój poprzedni bufor GPU, jeśli się mieści, zamiast alokować nowy. Samo to otwiera możliwość posiadania potoku działającego w czasie rzeczywistym, potrzebnego dla nowego modułu Rysowanie, z opóźnieniem około 60–80 ms między pociągnięciami pędzla a aktualizacjami obrazu. Nigdy bym nie pomyślał, że będzie to w ogóle możliwe.

Ta sama realokacja pamięci jest wykonywana w pamięci RAM, ponieważ Ansel przechowuje teraz całą pamięć podręczną we własnej arenie pamięci . Ten schemat nie tylko pozwala nam wstępnie zarezerwować ciągły blok pamięci z systemu operacyjnego przy uruchamianiu aplikacji, co prowadzi do szybszych alokacji, ale także zapewnia, że nie przekroczymy zdefiniowanej przez użytkownika objętości pamięci. Zatem gdy zdecydujesz się przydzielić 8 GB pamięci RAM dla Ansela, jesteśmy teraz w stanie zapewnić, że tak faktycznie się stanie: będziemy wypełniać całą tę objętość, aż się zapełni, po czym zaczniemy poddawać recyklingowi najstarsze bufory. Wszystkie bufory pikseli, wejścia i wyjścia modułów, ale także tymczasowe wewnętrzne brudnopisy, są alokowane w naszej arenie pamięci.

I wreszcie, co nie mniej ważne, istnieje tylko jedna pamięć podręczna potoku dla całej aplikacji, co oznacza, że wszystkie typy potoku (podgląd ciemni, główny obraz ciemni, eksport miniatur i eksport plików) współdzielą tę samą pamięć podręczną. Choć wymagało to zarządzania współbieżnym dostępem wielowątkowym, oznacza to, że jeśli potok potrzebuje wyjścia modułu, które zostało już obliczone w tym samym rozmiarze przez inny potok, może bezpośrednio pobrać linię pamięci podręcznej i pominąć własne przeliczanie.

Aby to wykorzystać, wcześniej stałorozmiarowy „podgląd" ciemni (czyli pomniejszona wersja pełnego obrazu, używana w widżecie nawigacji, do próbkowania histogramów i próbników koloru oraz jako przejściowe „rozmyte" wypełnienie, gdy główny obraz jest przeliczany) został zmieniony tak, by być dynamicznie skalowany do poziomu powiększenia „dopasuj" głównego obrazu (tego wyświetlanego w centrum ciemni). W rezultacie tylko jeden obraz musi zostać obliczony, gdy wchodzisz do ciemni, a powrót do powiększenia „dopasuj" jest natychmiastowy.

Ale ponieważ ten stałorozmiarowy podgląd był leniwie używany w wielu miejscach w GUI do przeliczania współrzędnych kursora w przestrzeni widżetu na współrzędne obrazu RAW w pełnej rozdzielczości, poprzez kopiowanie-wklejanie wszędzie tego samego brzydkiego bloku kodu (kopiując nawet komentarz mówiący „nie rozumiem, co to robi"), cały układ współrzędnych musiał zostać przerobiony, by uwzględnić tę zmianę. I znów, te współrzędne były obliczane tylko wewnątrz potoku, więc GUI musiało czekać, aż potok przeliczy nowe rozmiary, by wiedzieć, gdzie umieścić obiekty, co wyzwalało usterki GUI, zwłaszcza podczas zmiany kadrowania. Ale teraz wszystko to jest obliczane z wyprzedzeniem z ujednoliconego API, więc koniec z głupim kopiowaniem-wklejaniem mnożonym przez liczbę modułów.

Co to zmieniło poza rdzeniem

Pierwszą zewnętrzną konsekwencją jest to, że kopie przestały udawać funkcje. Ciemnia nie potrzebuje już dedykowanego backbuffera GUI, by wyświetlić to, co renderer już wyprodukował. Może pobrać opublikowany obraz bezpośrednio z pamięci podręcznej, powiązać z nim powierzchnię Cairo i wyświetlić go w ciągu kilku milisekund. Wcześniej ścieżka była wolniejsza i zajęta kopiowaniem bez żadnego dobrego powodu poza odziedziczonym zamętem projektowym. Ciemnia może także bezpośrednio sprawdzić, czy dany backbuffer potoku jest aktualny względem bieżącej historii wywoływania, za pomocą sum kontrolnych. Wcześniej opierało się to na fladze „dirty", która mogła zostać nadpisana przez kilka wątków i uniemożliwiała jakiekolwiek przerysowanie ciemni „na miarę możliwości" (czyli: wrzuć ostatni znany ważny backbuffer w oczekiwaniu na przeliczenie aktualnego). Było to ważne, gdy użytkownik zmieniał parametry modułu szybciej, niż potok mógł przeliczać: wciąż potrzebujemy czegoś, by wypełnić widok i uniknąć migotania.

Ta sama logika rozszerza się teraz na eksport i miniatury, które mogą ponownie otworzyć ostateczny opublikowany wynik zamiast budować własną prywatną rzeczywistość. Kod histogramu i próbnika koloru także odszedł od pasożytowania na wykonaniu potoku. Skopy podążają za jawnymi sygnałami ukończenia ze ścieżki podglądu, cachują własną powierzchnię Cairo względem stanu źródłowego i próbkują bezpośrednio z backbufferów opartych na pamięci podręcznej. Histogram RAW (próbkowany po module demozaikowania) został dodany do starego wyjścia potoku: przełączanie między obydwoma nie wymaga przeliczenia potoku, podobnie jak próbkowanie koloru w każdym z nich. To znacznie nudniejszy projekt, co jest dokładnie powodem, dla którego jest lepszy.

Kolejną poważną konsekwencją jest to, że odmiany potoku nie żyją już w odizolowanych silosach pamięci podręcznej. Podgląd, ciemnia, eksport, miniatury i konsumenci histogramu mogą teraz współdzielić stan pamięci podręcznej. Taka była już intencja zasugerowana dawno temu w kodzie pamięci podręcznej; wymagało to tylko pełnego uporządkowania śledzenia ważności, by stała się realna. Gdy wszyscy ci konsumenci zgadzają się co do tych samych skrótów, mogą wreszcie zgodzić się także co do tych samych buforów. Korzyść polega na tym, że jeden potok może ponownie wykorzystać już obliczoną linię pamięci podręcznej z innego potoku, jeśli się mieści (ten sam parametr, ten sam rozmiar wejścia i wyjścia). Zatem początek potoków ciemni został przełączony na pełną rozdzielczość: demozaikowanie i odszumianie będą wolniejsze przy wchodzeniu do ciemni, ale potem powiększanie i przesuwanie stają się natychmiastowe. A eksport obrazu również pobierze te wczesne etapy prosto z pamięci podręcznej. Nawiasem mówiąc, eksport tego samego obrazu dwukrotnie w dwóch różnych rozdzielczościach przelicza tylko od modułu skalowania, na końcu potoku.

Obsługa pamięci podążyła za tą samą logiką. Linie pamięci podręcznej stały się obiektami zliczanymi referencjami, blokowalnymi, wielokrotnego użytku, zamiast jednorazowymi kulami przekazywanymi w pobliżu przez doraźne magazyny poboczne. Ścieżki CPU i GPU współdzielą teraz znacznie więcej tego samego modelu własności. Po stronie OpenCL bufory hosta i urządzenia są zarządzane z jawnymi regułami czasu życia, przypięta pamięć jest ponownie wykorzystywana zamiast marnotrawiona, a RAM i VRAM pozostają zsynchronizowane poprzez transfery przypięte i zero-copy, gdzie to możliwe, zamiast być odzwierciedlane przez bezsensowne kopie pośrednie. To nie sprawia, że GPU staje się magicznie szybsze w matematyce. Głównie powstrzymuje otaczającą hydraulikę od marnowania zysków.

Korzyść to nie tylko szybkość w benchmarku. To także opóźnienie, odporność i zużycie energii. Uruchamianie ciemni staje się krótsze, ponieważ jest mniej kopii i mniej zduplikowanych przebiegów. Powiększanie, przesuwanie, podgląd maski i nawigacja po historii przestają unieważniać pół wszechświata tylko dlatego, że udowodnienie poprawności było kiedyś zbyt trudne. OpenCL staje się mniej losowy, ponieważ przełączanie awaryjne, kafelkowanie i własność pamięci są sformułowane bardziej jawnie. Oprogramowanie spędza mniej czasu na przenoszeniu pikseli w kółko tylko po to, by upewnić się, że wciąż gdzieś istnieją.

Jako efekt uboczny poprawiono obsługę błędów w potoku. Moduły, którym nie udało się zaalokować pamięci, natychmiast zgłaszają teraz błąd i przerywają potok. Wcześniej niepowodzenia alokacji nie były sprawdzane wszędzie i cały potok mógł nieść bufor NULL aż do końca… lub zawiesić się jak nic na błędach segmentation fault. Jest to teraz zapobiegane wcześniej w potoku w jednolity sposób w całym oprogramowaniu.

Próbkowanie próbnika koloru i histogramy mogą próbkować dane bez wyzwalania przeliczania potoku, bezpośrednio pobierając z pamięci podręcznej, jeśli żądana linia pamięci podręcznej istnieje. Globalny zestaw narzędzi skopy pozwala Ci wybrać spośród RAW RGB (próbkowanego po demozaikowaniu) lub finalnego RGB potoku (próbkowanego albo po wyjściowym profilu koloru w 32-bitowej zmiennoprzecinkowej, albo w backbufferze w 8-bitowych liczbach całkowitych): zmiana z jednego na drugi nie restartuje potoku, lecz po prostu ładuje inną linię pamięci podręcznej, a próbkowanie koloru odbywa się na tej linii pamięci podręcznej w sposób przezroczysty.

Co faktycznie zostało naprawione dzięki zrobieniu tego porządnie

Wiele widocznych irytacji wywodzi się z tego przeprojektowania bardziej niż z jakiejkolwiek pojedynczej mikrooptymalizacji. Obsługa ROI w wysokiej rozdzielczości (High-DPI) poprawiła się, ponieważ własność geometrii stała się wyraźniejsza, a mapowanie współrzędnych między GUI a obrazem RAW jest teraz obsługiwane w centralnym API. Resynchronizacja historii z potokiem stała się mniej krucha, ponieważ historia przestała być przypadkowym efektem ubocznym przepływu GUI i stała się pełnoprawnym wejściem do planowania. Podgląd maski w czasie rzeczywistym nie musi już walczyć z modelem pamięci podręcznej zaprojektowanym na inną epokę. Odświeżanie histogramu stało się przewidywalne, ponieważ konsumuje teraz opublikowane dane zamiast liczyć na to, że właściwy bufor pośredni akurat gdzieś żyje. Nieskończone pętle, przeterminowane linie pamięci podręcznej, nieważne backbuffery i klasyczne „podgląd przeliczył cały potok od modułu 0 bez żadnego oczywistego powodu" — wszystko to staje się łatwiejsze do zdiagnozowania, gdy cykl życia jest jawny.

Zatem interesujące jest tutaj nie to, że jeden wielki refaktor zdarzył się w izolacji. Chodzi o to, że długa seria porządków wreszcie ułożyła się wokół jednej idei: ważność powinna być obliczana, nazywana i współdzielona z wyprzedzeniem. Gdy to się stało, reszta podążyła niemal mechanicznie. Pamięć podręczna stała się centralna, ponieważ skróty były godne zaufania. Zewnętrzni konsumenci mogli przestać kopiować, ponieważ bufory publikowane w pamięci podręcznej stały się autorytatywne. Ścieżki CPU i GPU mogły współdzielić więcej stanu, ponieważ własność stała się jawna. A renderer przestał zachowywać się jak nawiedzony dom, w którym zmiana jednego suwaka mogła obudzić trzy niepowiązane podsystemy i callback przerysowania z 2013 roku.

Testy wydajności

Benchmarki są wykonywane na obu aplikacjach skompilowanych lokalnie w wersji Release i z GCC14, przy użyciu ich odpowiednich skryptów build.sh --build-type Release.

Konfiguracja 1
Sprzętem jest Thinkpad P51, z 8 × Intel Xeon CPU E3-1505M v6 @ 3.00GHz i GPU Nvidia Quadro M2200, działający na Fedora 41 Plasma z Waylandem. Wersja sterownika Nvidia to 580.105.08. Intel Xeon to wysokiej klasy CPU wydany w 2015 roku. Quadro M2200 to profesjonalny CPU średniej klasy wydany w 2017 roku.
Konfiguracja 2
Sprzętem jest komputer stacjonarny z 4 × Intel Core i3-4130 i GPU AMD Radeon RX 580 Polaris 10, działający na Ubuntu 24.04 LTS z Waylandem. Wersja sterownika AMD to RustiCL 25.2.8. Core i3 to CPU średniej klasy wydany w 2013 roku. AMD Polaris 10 to GPU średnio-wysokiej klasy wydany w 2017 roku.

Pokazujemy tutaj wyjście ansel -d perf i darktable -d perf. Pliki konfiguracyjne zarówno Ansela, jak i Darktable są ustawione za pomocą tego skryptu , aby zapewnić tę samą bazową konfigurację. Testowe zdjęcie 45 Mpx pochodzi stąd , a plik XMP z edycją można pobrać tutaj.

Wstępne ładowanie w ciemni

Uwaga: w Anselu ważne jest, by przeprowadzać ten test bez żadnego modułu w fokusie w GUI (czyli rozwiniętego). W tej sytuacji Ansel zawsze cachuje wyjście poprzedniego modułu, dzięki czemu zapewniamy, że gdy w tym module w fokusie dokonywane są modyfikacje, zawsze restartujemy obliczenia od bezpośredniego wejścia. Ale wymuszanie pamięci podręcznej w ten sposób wyzwala narzut pamięci, który może być widoczny dla lekkich modułów.

Konfiguracja 1

 13.690316 [dev_pixelpipe] took 0.000 secs (0.000 CPU) to load the image.
 24.201079 [dev_pixelpipe] pipeline resync with history took 0.029 secs (0.026 CPU) for pipe virtual-preview
 34.260916 [dev_pixelpipe] pipeline resync with history took 0.000 secs (0.000 CPU) for pipe virtual-preview
 44.260963 [dev_pixelpipe] pipeline resync with history took 0.000 secs (0.000 CPU) for pipe virtual-preview
 54.327523 [dev_pixelpipe] pipeline resync with history took 0.019 secs (0.025 CPU) for pipe preview
 64.384013 [dev_pixelpipe] took 0.056 secs (0.072 CPU) initing base buffer [preview]
 74.402520 [dev_pixelpipe] took 0.018 secs (0.031 CPU) processed `Raw settings' on GPU, blended on  [preview]
 84.421489 [dev_pixelpipe] took 0.016 secs (0.012 CPU) processed `white balance' on GPU, blended on  [preview]
 96.912680 [dev_pixelpipe] took 2.491 secs (2.470 CPU) processed `highlight reconstruction' on GPU, blended on GPU [preview]
107.167233 [dev_pixelpipe] took 0.254 secs (0.144 CPU) processed `demosaic' on GPU, blended on  [preview]
117.470521 [dev_pixelpipe] took 0.303 secs (0.173 CPU) processed `lens correction' on GPU, blended on  [preview]
127.629764 [dev_pixelpipe] took 0.159 secs (0.172 CPU) processed `Initial resampling' on GPU, blended on  [preview]
137.635593 [dev_pixelpipe] took 0.006 secs (0.006 CPU) processed `exposure' on GPU, blended on GPU [preview]
147.638110 [dev_pixelpipe] took 0.002 secs (0.001 CPU) processed `input color profile' on GPU, blended on  [preview]
157.639667 [dev_pixelpipe] took 0.002 secs (0.002 CPU) processed `color calibration' on GPU, blended on GPU [preview]
168.726634 [dev_pixelpipe] took 1.087 secs (1.090 CPU) processed `diffuse or sharpen' on GPU, blended on GPU [preview]
178.735900 [dev_pixelpipe] took 0.009 secs (0.009 CPU) processed `color balance rgb' on GPU, blended on GPU [preview]
188.742309 [dev_pixelpipe] took 0.006 secs (0.007 CPU) processed `filmic rgb' on GPU, blended on GPU [preview]
198.765175 [dev_pixelpipe] took 0.023 secs (0.029 CPU) processed `output color profile' on GPU, blended on  [preview]
208.808546 [dev_pixelpipe] took 0.043 secs (0.049 CPU) processed `dithering' on CPU, blended on  [preview]
218.820376 [dev_pixelpipe] took 0.012 secs (0.061 CPU) processed `display encoding' on CPU, blended on  [preview]
228.820439 [pixelpipe] preview internal pixel pipeline processing took 4.493 secs (4.332 CPU)
238.820483 [dev_process_preview] pipeline processing thread took 4.493 secs (4.333 CPU)
248.858716 [dev_pixelpipe] pipeline resync with history took 0.026 secs (0.032 CPU) for pipe full
258.858805 [dev_process_full] pipeline processing thread took 0.000 secs (0.000 CPU)
 110,5815 [dt_dev_process_image_job] loading image. took 0,000 secs (0,000 CPU)
 210,6799 [dev_pixelpipe] took 0,018 secs (0,029 CPU) initing base buffer [full]
 310,6968 [dev_pixelpipe] took 0,017 secs (0,020 CPU) [full] processed `rawprepare' on GPU, blended on GPU
 410,7094 [dev_pixelpipe] took 0,013 secs (0,011 CPU) [full] processed `temperature' on GPU, blended on GPU
 514,4091 [dev_pixelpipe] took 3,700 secs (3,719 CPU) [full] processed `highlights' on GPU with tiling, blended on CPU
 614,8078 [resample_cl] plan 0,000 secs (0,000 CPU) resample 0,010 secs (0,002 CPU)
 714,8988 [dev_pixelpipe] took 0,490 secs (0,349 CPU) [full] processed `demosaic' on GPU, blended on GPU
 814,9094 [dev_pixelpipe] took 0,010 secs (0,001 CPU) [full] processed `lens' on GPU, blended on GPU
 914,9200 [dev_pixelpipe] took 0,010 secs (0,002 CPU) [full] processed `exposure' on GPU, blended on GPU
1014,9516 [dev_pixelpipe] took 0,032 secs (0,010 CPU) [full] processed `colorin' on GPU, blended on GPU
1114,9654 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_LAB-->IOP_CS_RGB took 0,013 secs (0,000 GPU) [channelmixerrgb]
1214,9902 [dev_pixelpipe] took 0,039 secs (0,006 CPU) [full] processed `channelmixerrgb' on GPU, blended on GPU
1316,6800 [dev_pixelpipe] took 1,690 secs (1,745 CPU) [full] processed `diffuse' on GPU, blended on GPU
1416,6923 [dev_pixelpipe] took 0,012 secs (0,007 CPU) [full] processed `colorbalancergb' on GPU, blended on GPU
1516,7037 [dev_pixelpipe] took 0,011 secs (0,005 CPU) [full] processed `filmicrgb' on GPU, blended on GPU
1616,7100 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_RGB-->IOP_CS_LAB took 0,006 secs (0,000 GPU) [colorout]
1716,7555 [dev_pixelpipe] took 0,052 secs (0,020 CPU) [full] processed `colorout' on GPU, blended on GPU
1816,8435 [dev_pixelpipe] took 0,088 secs (0,052 CPU) [full] processed `dither' on CPU, blended on CPU
1916,8525 [dev_pixelpipe] took 0,009 secs (0,066 CPU) [full] processed `gamma' on CPU, blended on CPU
2016,8594 [dev_process_image] pixel pipeline took 6,197 secs (6,048 CPU) processing `2022-06-17__DSC00078.arw'
21
2216,8918 [dt_dev_process_image_job] loading image. took 0,000 secs (0,000 CPU)
2316,9470 [dt_dev_process_image_job] loading image. took 0,000 secs (0,000 CPU)
2416,9968 [dev_pixelpipe] took 0,000 secs (0,000 CPU) initing base buffer [preview]
2516,9986 [dev_pixelpipe] took 0,002 secs (0,002 CPU) [preview] processed `rawprepare' on GPU, blended on GPU
2616,9995 [dev_pixelpipe] took 0,001 secs (0,001 CPU) [preview] processed `temperature' on GPU, blended on GPU
2717,0698 [dev_pixelpipe] took 0,070 secs (0,081 CPU) [preview] processed `highlights' on GPU, blended on GPU
2817,0811 [dev_pixelpipe] took 0,011 secs (0,005 CPU) [preview] processed `demosaic' on GPU, blended on GPU
2917,0829 [dev_pixelpipe] took 0,002 secs (0,000 CPU) [preview] processed `lens' on GPU, blended on GPU
3017,0853 [dev_pixelpipe] took 0,002 secs (0,001 CPU) [preview] processed `exposure' on GPU, blended on GPU
3117,0886 [dev_pixelpipe] took 0,003 secs (0,001 CPU) [preview] processed `colorin' on GPU, blended on GPU
3217,0915 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_LAB-->IOP_CS_RGB took 0,003 secs (0,000 GPU) [channelmixerrgb]
3317,0952 [dev_pixelpipe] took 0,007 secs (0,002 CPU) [preview] processed `channelmixerrgb' on GPU, blended on GPU
3417,4667 [dev_pixelpipe] took 0,371 secs (0,354 CPU) [preview] processed `diffuse' on GPU, blended on GPU
3517,4702 [dev_pixelpipe] took 0,003 secs (0,001 CPU) [preview] processed `colorbalancergb' on GPU, blended on GPU
3617,4748 [dev_pixelpipe] took 0,004 secs (0,001 CPU) [preview] processed `filmicrgb' on GPU, blended on GPU
3717,4776 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_RGB-->IOP_CS_LAB took 0,002 secs (0,000 GPU) [colorout]
3817,4939 [dev_pixelpipe] took 0,019 secs (0,009 CPU) [preview] processed `colorout' on GPU, blended on GPU
3917,5141 [dev_pixelpipe] took 0,020 secs (0,007 CPU) [preview] processed `dither' on CPU, blended on CPU
4017,5195 [dev_pixelpipe] took 0,005 secs (0,021 CPU) [preview] processed `gamma' on CPU, blended on CPU
4117,5355 [dt_ioppr_transform_image_colorspace_rgb] `Système' -> `sRVB' took 0,016 secs (0,067 CPU) [final histogram]
4217,6218 [dev_process_image] pixel pipeline took 0,625 secs (0,657 CPU) processing `2022-06-17__DSC00078.arw'
 14.146713 [dev_pixelpipe] took 0.000 secs (0.000 CPU) to load the image.
 24.642451 [dev_pixelpipe] pipeline resync with history took 0.029 secs (0.026 CPU) for pipe virtual-preview
 34.703889 [dev_pixelpipe] pipeline resync with history took 0.000 secs (0.000 CPU) for pipe virtual-preview
 44.703915 [dev_pixelpipe] pipeline resync with history took 0.000 secs (0.000 CPU) for pipe virtual-preview
 54.782635 [dev_pixelpipe] pipeline resync with history took 0.033 secs (0.041 CPU) for pipe preview
 64.796521 [dev_pixelpipe] took 0.014 secs (0.018 CPU) initing base buffer [preview]
 74.831601 [dev_pixelpipe] took 0.035 secs (0.061 CPU) processed `Raw settings' on CPU, blended on  [preview]
 84.862626 [dev_pixelpipe] took 0.031 secs (0.077 CPU) processed `white balance' on CPU, blended on  [preview]
 910.978874 [dev_pixelpipe] took 6.116 secs (43.505 CPU) processed `highlight reconstruction' on CPU, blended on CPU [preview]
1011.369357 [dev_pixelpipe] took 0.390 secs (2.623 CPU) processed `demosaic' on CPU, blended on  [preview]
1111.449953 [dev_pixelpipe] took 0.081 secs (0.474 CPU) processed `lens correction' on CPU, blended on  [preview]
1211.797645 [dev_pixelpipe] took 0.348 secs (2.088 CPU) processed `Initial resampling' on CPU, blended on  [preview]
1311.802788 [dev_pixelpipe] took 0.005 secs (0.025 CPU) processed `exposure' on CPU, blended on CPU [preview]
1411.807751 [dev_pixelpipe] took 0.005 secs (0.024 CPU) processed `input color profile' on CPU, blended on  [preview]
1511.877682 [dev_pixelpipe] took 0.070 secs (0.361 CPU) processed `color calibration' on CPU, blended on CPU [preview]
1615.025427 [dev_pixelpipe] took 3.148 secs (23.610 CPU) processed `diffuse or sharpen' on CPU, blended on CPU [preview]
1715.290600 [dev_pixelpipe] took 0.262 secs (1.469 CPU) processed `color balance rgb' on CPU, blended on CPU [preview]
1815.425400 [dev_pixelpipe] took 0.135 secs (0.761 CPU) processed `filmic rgb' on CPU, blended on CPU [preview]
1915.438259 [dev_pixelpipe] took 0.013 secs (0.072 CPU) processed `output color profile' on CPU, blended on  [preview]
2015.463031 [dev_pixelpipe] took 0.025 secs (0.039 CPU) processed `dithering' on CPU, blended on  [preview]
2115.474803 [dev_pixelpipe] took 0.012 secs (0.058 CPU) processed `display encoding' on CPU, blended on  [preview]
2215.474885 [pixelpipe] preview internal pixel pipeline processing took 10.692 secs (75.269 CPU)
2315.474906 [dev_process_preview] pipeline processing thread took 10.692 secs (75.269 CPU)
2415.522324 [dev_pixelpipe] pipeline resync with history took 0.037 secs (0.047 CPU) for pipe full
2515.522431 [dev_process_full] pipeline processing thread took 0.000 secs (0.000 CPU)
 14,5577 [dt_dev_load_raw] loading the image. took 0,131 secs (0,040 CPU)
 25,4913 [dt_dev_process_image_job] loading image. took 0,000 secs (0,000 CPU)
 35,6087 [dev_pixelpipe] took 0,017 secs (0,029 CPU) initing base buffer [full]
 45,6491 [dev_pixelpipe] took 0,040 secs (0,120 CPU) [full] processed `rawprepare' on CPU, blended on CPU
 55,6782 [dev_pixelpipe] took 0,029 secs (0,061 CPU) [full] processed `temperature' on CPU, blended on CPU
 612,0060 [dev_pixelpipe] took 6,328 secs (45,005 CPU) [full] processed `highlights' on CPU, blended on CPU
 712,6762 [resample_plain] plan 0,001 secs (0,001 CPU) resample 0,249 secs (1,829 CPU)
 812,7230 [dev_pixelpipe] took 0,717 secs (4,499 CPU) [full] processed `demosaic' on CPU, blended on CPU
 912,7370 [dev_pixelpipe] took 0,013 secs (0,029 CPU) [full] processed `lens' on CPU, blended on CPU
1012,7487 [dev_pixelpipe] took 0,012 secs (0,029 CPU) [full] processed `exposure' on CPU, blended on CPU
1112,7637 [dev_pixelpipe] took 0,015 secs (0,045 CPU) [full] processed `colorin' on CPU, blended on CPU
1212,7693 [dt_ioppr_transform_image_colorspace] IOP_CS_LAB-->IOP_CS_RGB took 0,006 secs (0,028 CPU) [channelmixerrgb]
1312,8473 [dev_pixelpipe] took 0,084 secs (0,438 CPU) [full] processed `channelmixerrgb' on CPU, blended on CPU
1417,0570 [dev_pixelpipe] took 4,210 secs (32,406 CPU) [full] processed `diffuse' on CPU, blended on CPU
1517,3181 [dev_pixelpipe] took 0,261 secs (1,872 CPU) [full] processed `colorbalancergb' on CPU, blended on CPU
1617,4583 [dev_pixelpipe] took 0,140 secs (0,898 CPU) [full] processed `filmicrgb' on CPU, blended on CPU
1717,4634 [dt_ioppr_transform_image_colorspace] IOP_CS_RGB-->IOP_CS_LAB took 0,005 secs (0,033 CPU) [colorout]
1817,4792 [dev_pixelpipe] took 0,021 secs (0,116 CPU) [full] processed `colorout' on CPU, blended on CPU
1917,5381 [dev_pixelpipe] took 0,059 secs (0,037 CPU) [full] processed `dither' on CPU, blended on CPU
2017,5472 [dev_pixelpipe] took 0,009 secs (0,061 CPU) [full] processed `gamma' on CPU, blended on CPU
2117,5543 [dev_process_image] pixel pipeline took 11,962 secs (85,649 CPU) processing `2022-06-17__DSC00078.arw'
22
2317,5861 [dt_dev_process_image_job] loading image. took 0,000 secs (0,000 CPU)
2417,6428 [dt_dev_process_image_job] loading image. took 0,000 secs (0,000 CPU)
2517,6993 [dev_pixelpipe] took 0,000 secs (0,000 CPU) initing base buffer [preview]
2617,7004 [dev_pixelpipe] took 0,001 secs (0,002 CPU) [preview] processed `rawprepare' on CPU, blended on CPU
2717,7015 [dev_pixelpipe] took 0,001 secs (0,004 CPU) [preview] processed `temperature' on CPU, blended on CPU
2817,8101 [dev_pixelpipe] took 0,109 secs (0,570 CPU) [preview] processed `highlights' on CPU, blended on CPU
2917,8261 [dev_pixelpipe] took 0,016 secs (0,071 CPU) [preview] processed `demosaic' on CPU, blended on CPU
3017,8308 [dev_pixelpipe] took 0,005 secs (0,006 CPU) [preview] processed `lens' on CPU, blended on CPU
3117,8345 [dev_pixelpipe] took 0,004 secs (0,006 CPU) [preview] processed `exposure' on CPU, blended on CPU
3217,8393 [dev_pixelpipe] took 0,005 secs (0,016 CPU) [preview] processed `colorin' on CPU, blended on CPU
3317,8411 [dt_ioppr_transform_image_colorspace] IOP_CS_LAB-->IOP_CS_RGB took 0,002 secs (0,008 CPU) [channelmixerrgb]
3417,8670 [dev_pixelpipe] took 0,028 secs (0,144 CPU) [preview] processed `channelmixerrgb' on CPU, blended on CPU
3519,0763 [dev_pixelpipe] took 1,209 secs (8,091 CPU) [preview] processed `diffuse' on CPU, blended on CPU
3619,1926 [dev_pixelpipe] took 0,116 secs (0,708 CPU) [preview] processed `colorbalancergb' on CPU, blended on CPU
3719,2628 [dev_pixelpipe] took 0,070 secs (0,342 CPU) [preview] processed `filmicrgb' on CPU, blended on CPU
3819,2667 [dt_ioppr_transform_image_colorspace] IOP_CS_RGB-->IOP_CS_LAB took 0,003 secs (0,012 CPU) [colorout]
3919,2758 [dev_pixelpipe] took 0,012 secs (0,045 CPU) [preview] processed `colorout' on CPU, blended on CPU
4019,2860 [dev_pixelpipe] took 0,010 secs (0,003 CPU) [preview] processed `dither' on CPU, blended on CPU
4119,2907 [dev_pixelpipe] took 0,005 secs (0,020 CPU) [preview] processed `gamma' on CPU, blended on CPU
4219,3052 [dt_ioppr_transform_image_colorspace_rgb] `Système' -> `sRVB' took 0,014 secs (0,071 CPU) [final histogram]
4319,3928 [dev_process_image] pixel pipeline took 1,694 secs (10,211 CPU) processing `2022-06-17__DSC00078.arw'

Konfiguracja 2

 136.764111 [dev_pixelpipe] took 0.313 secs (0.056 CPU) to load the image.
 237.528673 [dev_pixelpipe] pipeline resync with history took 0.044 secs (0.040 CPU) for pipe virtual-preview
 337.691646 [dev_pixelpipe] pipeline resync with history took 0.000 secs (0.000 CPU) for pipe virtual-preview
 437.691699 [dev_pixelpipe] pipeline resync with history took 0.000 secs (0.000 CPU) for pipe virtual-preview
 537.745145 [dev_pixelpipe] pipeline resync with history took 0.036 secs (0.043 CPU) for pipe preview
 637.916228 [dev_pixelpipe] took 0.171 secs (0.115 CPU) initing base buffer [preview]
 737.953157 [dev_pixelpipe] took 0.037 secs (0.024 CPU) processed `Raw settings' on GPU, blended on  [preview]
 837.959823 [dev_pixelpipe] took 0.007 secs (0.002 CPU) processed `white balance' on GPU, blended on  [preview]
 939.377680 [dev_pixelpipe] took 1.418 secs (0.196 CPU) processed `highlight reconstruction' on GPU, blended on GPU [preview]
1039.526422 [dev_pixelpipe] took 0.149 secs (0.039 CPU) processed `demosaic' on GPU, blended on  [preview]
1141.012847 [dev_pixelpipe] took 1.486 secs (0.271 CPU) processed `lens correction' on GPU, blended on  [preview]
1241.266624 [dev_pixelpipe] took 0.254 secs (0.110 CPU) processed `Initial resampling' on GPU, blended on  [preview]
1341.299983 [dev_pixelpipe] took 0.033 secs (0.025 CPU) processed `exposure' on GPU, blended on GPU [preview]
1441.311230 [dev_pixelpipe] took 0.011 secs (0.009 CPU) processed `input color profile' on GPU, blended on  [preview]
1541.313526 [dev_pixelpipe] took 0.002 secs (0.002 CPU) processed `color calibration' on GPU, blended on GPU [preview]
1641.471857 [dev_pixelpipe] took 0.158 secs (0.049 CPU) processed `diffuse or sharpen' on GPU, blended on GPU [preview]
1741.482377 [dev_pixelpipe] took 0.010 secs (0.008 CPU) processed `color balance rgb' on GPU, blended on GPU [preview]
1841.486457 [dev_pixelpipe] took 0.004 secs (0.007 CPU) processed `filmic rgb' on GPU, blended on GPU [preview]
1941.508325 [dev_pixelpipe] took 0.022 secs (0.018 CPU) processed `output color profile' on GPU, blended on  [preview]
2041.521171 [dev_pixelpipe] took 0.013 secs (0.016 CPU) processed `dithering' on CPU, blended on  [preview]
2141.529057 [dev_pixelpipe] took 0.008 secs (0.014 CPU) processed `display encoding' on CPU, blended on  [preview]
2241.529178 [pixelpipe] preview internal pixel pipeline processing took 3.784 secs (0.906 CPU)
2341.529291 [dev_process_preview] pipeline processing thread took 3.784 secs (0.906 CPU)
2441.576629 [dev_pixelpipe] pipeline resync with history took 0.036 secs (0.049 CPU) for pipe full
2541.579279 [dev_process_full] pipeline processing thread took 0.000 secs (0.000 CPU)
 123.6180 [dt_dev_load_raw] loading the image. took 0.290 secs (0.055 CPU)
 224.6549 [dt_dev_process_image_job] loading image. took 0.000 secs (0.000 CPU)
 324.7621 [dev_pixelpipe] took 0.023 secs (0.034 CPU) initing base buffer [full]
 424.8212 [dev_pixelpipe] took 0.059 secs (0.022 CPU) [full] processed `rawprepare' on GPU, blended on GPU
 524.8286 [dev_pixelpipe] took 0.007 secs (0.000 CPU) [full] processed `temperature' on GPU, blended on GPU
 626.6983 [dev_pixelpipe] took 1.870 secs (0.433 CPU) [full] processed `highlights' on GPU, blended on GPU
 726.7416 [resample_cl] plan 0.002 secs (0.001 CPU) resample 0.032 secs (0.000 CPU)
 826.8215 [dev_pixelpipe] took 0.123 secs (0.009 CPU) [full] processed `demosaic' on GPU, blended on GPU
 926.8260 [dev_pixelpipe] took 0.004 secs (0.001 CPU) [full] processed `lens' on GPU, blended on GPU
1026.8282 [dev_pixelpipe] took 0.002 secs (0.001 CPU) [full] processed `ashift' on GPU, blended on GPU
1126.8306 [dev_pixelpipe] took 0.002 secs (0.001 CPU) [full] processed `exposure' on GPU, blended on GPU
1226.8347 [dev_pixelpipe] took 0.004 secs (0.001 CPU) [full] processed `colorin' on GPU, blended on GPU
1326.8370 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_LAB-->IOP_CS_RGB took 0.002 secs (0.001 GPU) [channelmixerrgb]
1426.8384 [dev_pixelpipe] took 0.004 secs (0.001 CPU) [full] processed `channelmixerrgb' on GPU, blended on GPU
1527.3567 [dev_pixelpipe] took 0.518 secs (0.022 CPU) [full] processed `diffuse' on GPU, blended on GPU
1627.3613 [dev_pixelpipe] took 0.004 secs (0.001 CPU) [full] processed `colorbalancergb' on GPU, blended on GPU
1727.4019 [dev_pixelpipe] took 0.041 secs (0.011 CPU) [full] processed `filmicrgb' on GPU, blended on GPU
1827.4053 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_RGB-->IOP_CS_LAB took 0.003 secs (0.001 GPU) [colorout]
1927.4345 [dev_pixelpipe] took 0.032 secs (0.009 CPU) [full] processed `colorout' on GPU, blended on GPU
2027.4814 [dev_pixelpipe] took 0.047 secs (0.025 CPU) [full] processed `dither' on CPU, blended on CPU
2127.4962 [dev_pixelpipe] took 0.015 secs (0.045 CPU) [full] processed `gamma' on CPU, blended on CPU
2227.4990 [dev_process_image] pixel pipeline took 2.760 secs (0.618 CPU) processing `2022-06-17__DSC00078.arw'
23
2427.5249 [dt_dev_process_image_job] loading image. took 0.005 secs (0.013 CPU)
2527.5866 [dt_dev_process_image_job] loading image. took 0.000 secs (0.000 CPU)
2627.6513 [dev_pixelpipe] took 0.000 secs (0.000 CPU) initing base buffer [preview]
2727.6561 [dev_pixelpipe] took 0.005 secs (0.006 CPU) [preview] processed `rawprepare' on GPU, blended on GPU
2827.6580 [dev_pixelpipe] took 0.002 secs (0.001 CPU) [preview] processed `temperature' on GPU, blended on GPU
2927.7797 [dev_pixelpipe] took 0.122 secs (0.062 CPU) [preview] processed `highlights' on GPU, blended on GPU
3027.7845 [dev_pixelpipe] took 0.005 secs (0.002 CPU) [preview] processed `demosaic' on GPU, blended on GPU
3127.7854 [dev_pixelpipe] took 0.001 secs (0.000 CPU) [preview] processed `lens' on GPU, blended on GPU
3227.8023 [dev_pixelpipe] took 0.017 secs (0.002 CPU) [preview] processed `ashift' on GPU, blended on GPU
3327.8034 [dev_pixelpipe] took 0.001 secs (0.000 CPU) [preview] processed `exposure' on GPU, blended on GPU
3427.8058 [dev_pixelpipe] took 0.002 secs (0.000 CPU) [preview] processed `colorin' on GPU, blended on GPU
3527.8077 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_LAB-->IOP_CS_RGB took 0.001 secs (0.000 GPU) [channelmixerrgb]
3627.8089 [dev_pixelpipe] took 0.003 secs (0.000 CPU) [preview] processed `channelmixerrgb' on GPU, blended on GPU
3728.5157 [dev_pixelpipe] took 0.707 secs (0.076 CPU) [preview] processed `diffuse' on GPU, blended on GPU
3828.5240 [dev_pixelpipe] took 0.008 secs (0.004 CPU) [preview] processed `colorbalancergb' on GPU, blended on GPU
3928.6037 [dev_pixelpipe] took 0.079 secs (0.031 CPU) [preview] processed `filmicrgb' on GPU, blended on GPU
4028.6092 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_RGB-->IOP_CS_LAB took 0.005 secs (0.000 GPU) [colorout]
4128.6323 [dev_pixelpipe] took 0.028 secs (0.007 CPU) [preview] processed `colorout' on GPU, blended on GPU
4228.6675 [dev_pixelpipe] took 0.035 secs (0.010 CPU) [preview] processed `dither' on CPU, blended on CPU
4328.6881 [dev_pixelpipe] took 0.021 secs (0.066 CPU) [preview] processed `gamma' on CPU, blended on CPU
4428.8183 [dev_process_image] pixel pipeline took 1.167 secs (0.442 CPU) processing `2022-06-17__DSC00078.arw'
 113.969925 [dev_pixelpipe] took 0.271 secs (0.044 CPU) to load the image.
 214.519352 [dev_pixelpipe] pipeline resync with history took 0.039 secs (0.037 CPU) for pipe virtual-preview
 314.609258 [dev_pixelpipe] pipeline resync with history took 0.000 secs (0.000 CPU) for pipe virtual-preview
 414.609291 [dev_pixelpipe] pipeline resync with history took 0.000 secs (0.000 CPU) for pipe virtual-preview
 514.685917 [dev_pixelpipe] pipeline resync with history took 0.036 secs (0.048 CPU) for pipe preview
 614.724434 [dev_pixelpipe] took 0.038 secs (0.024 CPU) initing base buffer [preview]
 714.787570 [dev_pixelpipe] took 0.063 secs (0.064 CPU) processed `Raw settings' on CPU, blended on  [preview]
 814.857062 [dev_pixelpipe] took 0.069 secs (0.080 CPU) processed `white balance' on CPU, blended on  [preview]
 927.961302 [dev_pixelpipe] took 13.104 secs (32.983 CPU) processed `highlight reconstruction' on CPU, blended on CPU [preview]
1028.949286 [dev_pixelpipe] took 0.988 secs (3.057 CPU) processed `demosaic' on CPU, blended on  [preview]
1129.075925 [dev_pixelpipe] took 0.127 secs (0.326 CPU) processed `lens correction' on CPU, blended on  [preview]
1230.176998 [dev_pixelpipe] took 1.101 secs (3.061 CPU) processed `Initial resampling' on CPU, blended on  [preview]
1330.190589 [dev_pixelpipe] took 0.013 secs (0.024 CPU) processed `exposure' on CPU, blended on CPU [preview]
1430.192681 [dev_pixelpipe] took 0.002 secs (0.005 CPU) processed `input color profile' on CPU, blended on  [preview]
1530.239877 [dev_pixelpipe] took 0.047 secs (0.103 CPU) processed `color calibration' on CPU, blended on CPU [preview]
1631.944712 [dev_pixelpipe] took 1.705 secs (4.286 CPU) processed `diffuse or sharpen' on CPU, blended on CPU [preview]
1732.096771 [dev_pixelpipe] took 0.152 secs (0.407 CPU) processed `color balance rgb' on CPU, blended on CPU [preview]
1832.190015 [dev_pixelpipe] took 0.093 secs (0.228 CPU) processed `filmic rgb' on CPU, blended on CPU [preview]
1932.205505 [dev_pixelpipe] took 0.015 secs (0.028 CPU) processed `output color profile' on CPU, blended on  [preview]
2032.214424 [dev_pixelpipe] took 0.009 secs (0.011 CPU) processed `dithering' on CPU, blended on  [preview]
2132.223982 [dev_pixelpipe] took 0.009 secs (0.021 CPU) processed `display encoding' on CPU, blended on  [preview]
2232.224049 [pixelpipe] preview internal pixel pipeline processing took 17.538 secs (44.709 CPU)
2332.224090 [dev_process_preview] pipeline processing thread took 17.538 secs (44.710 CPU)
2432.276027 [dev_pixelpipe] pipeline resync with history took 0.042 secs (0.054 CPU) for pipe full
2532.276152 [dev_process_full] pipeline processing thread took 0.000 secs (0.000 CPU)
 112.5778 [dt_dev_process_image_job] loading image. took 0.000 secs (0.000 CPU)
 212.6692 [dt_dev_process_image_job] loading image. took 0.000 secs (0.000 CPU)
 312.7607 [dev_pixelpipe] took 0.018 secs (0.022 CPU) initing base buffer [full]
 412.8098 [dev_pixelpipe] took 0.049 secs (0.105 CPU) [full] processed `rawprepare' on CPU
 512.8448 [dev_pixelpipe] took 0.035 secs (0.042 CPU) [full] processed `temperature' on CPU
 625.7535 [dev_pixelpipe] took 12.909 secs (44.992 CPU) [full] processed `highlights' on CPU
 727.5129 [resample_plain] plan 0.001 secs (0.001 CPU) resample 0.732 secs (2.749 CPU)
 827.5515 [dev_pixelpipe] took 1.798 secs (6.012 CPU) [full] processed `demosaic' on CPU
 927.5571 [dev_pixelpipe] took 0.006 secs (0.005 CPU) [full] processed `lens' on CPU
1027.5603 [dev_pixelpipe] took 0.003 secs (0.003 CPU) [full] processed `ashift' on CPU
1127.5634 [dev_pixelpipe] took 0.003 secs (0.005 CPU) [full] processed `exposure' on CPU
1227.5963 [dev_pixelpipe] took 0.033 secs (0.100 CPU) [full] processed `colorin' on CPU
1327.6038 [dt_ioppr_transform_image_colorspace] IOP_CS_LAB-->IOP_CS_RGB took 0.007 secs (0.018 CPU) [channelmixerrgb]
1427.6464 [dev_pixelpipe] took 0.050 secs (0.163 CPU) [full] processed `channelmixerrgb' on CPU
1531.0132 [dev_pixelpipe] took 3.367 secs (12.611 CPU) [full] processed `diffuse' on CPU
1631.1921 [dev_pixelpipe] took 0.179 secs (0.647 CPU) [full] processed `colorbalancergb' on CPU
1731.3065 [dev_pixelpipe] took 0.114 secs (0.406 CPU) [full] processed `filmicrgb' on CPU
1831.3321 [dt_ioppr_transform_image_colorspace] IOP_CS_RGB-->IOP_CS_LAB took 0.025 secs (0.091 CPU) [colorout]
1931.3475 [dev_pixelpipe] took 0.041 secs (0.124 CPU) [full] processed `colorout' on CPU
2031.3754 [dev_pixelpipe] took 0.028 secs (0.020 CPU) [full] processed `dither' on CPU
2131.3849 [dev_pixelpipe] took 0.009 secs (0.034 CPU) [full] processed `gamma' on CPU
2231.3870 [dev_process_image] pixel pipeline took 18.644 secs (65.292 CPU) processing `2022-06-17__DSC00078.arw'
23
2431.4040 [dt_dev_process_image_job] loading image. took 0.000 secs (0.000 CPU)
2531.5018 [dt_dev_process_image_job] loading image. took 0.000 secs (0.000 CPU)
2631.5722 [dev_pixelpipe] took 0.000 secs (0.000 CPU) initing base buffer [preview]
2731.5824 [dev_pixelpipe] took 0.010 secs (0.014 CPU) [preview] processed `rawprepare' on CPU
2831.5861 [dev_pixelpipe] took 0.004 secs (0.008 CPU) [preview] processed `temperature' on CPU
2932.7796 [dev_pixelpipe] took 1.193 secs (4.129 CPU) [preview] processed `highlights' on CPU
3032.8917 [dev_pixelpipe] took 0.112 secs (0.387 CPU) [preview] processed `demosaic' on CPU
3132.9099 [dev_pixelpipe] took 0.018 secs (0.031 CPU) [preview] processed `lens' on CPU
3232.9433 [dev_pixelpipe] took 0.033 secs (0.050 CPU) [preview] processed `ashift' on CPU
3332.9610 [dev_pixelpipe] took 0.018 secs (0.023 CPU) [preview] processed `exposure' on CPU
3433.1087 [dev_pixelpipe] took 0.148 secs (0.532 CPU) [preview] processed `colorin' on CPU
3533.1362 [dt_ioppr_transform_image_colorspace] IOP_CS_LAB-->IOP_CS_RGB took 0.027 secs (0.096 CPU) [channelmixerrgb]
3633.3353 [dev_pixelpipe] took 0.227 secs (0.825 CPU) [preview] processed `channelmixerrgb' on CPU
3755.6386 [dev_pixelpipe] took 22.303 secs (82.427 CPU) [preview] processed `diffuse' on CPU
3856.4763 [dev_pixelpipe] took 0.837 secs (3.180 CPU) [preview] processed `colorbalancergb' on CPU
3957.0002 [dev_pixelpipe] took 0.523 secs (1.966 CPU) [preview] processed `filmicrgb' on CPU
4057.1225 [dt_ioppr_transform_image_colorspace] IOP_CS_RGB-->IOP_CS_LAB took 0.122 secs (0.437 CPU) [colorout]
4157.1852 [dev_pixelpipe] took 0.184 secs (0.637 CPU) [preview] processed `colorout' on CPU
4257.2043 [dev_pixelpipe] took 0.019 secs (0.018 CPU) [preview] processed `dither' on CPU
4357.2471 [dev_pixelpipe] took 0.043 secs (0.155 CPU) [preview] processed `gamma' on CPU
4457.4090 [dev_process_image] pixel pipeline took 25.837 secs (94.647 CPU) processing `2022-06-17__DSC00078.arw'

Raport

Po pierwsze, przy wchodzeniu do ciemni Darktable oblicza 2 potoki dla tego samego wyniku, który Ansel oblicza tylko jeden. Zatem niezależnie od faktycznych czasów działania, już połowa obliczeń jest bezużyteczna.

Następnie Ansel oblicza obraz w pełnej rozdzielczości aż do modułu wstępnego ponownego próbkowania, podczas gdy Darktable robi to tylko do momentu przed demozaikowaniem. To ważne dla porównania.

Konfiguracja 1 (Intel Xeon, Nvidia Quadro)

OprogramowanieGPUCPU
Ansel4.493 s10.692 s
Darktable 5.4.16.822 s13.66 s

Konfiguracja 2 (Intel i3, AMD Radeon)

OprogramowanieGPUCPU
Ansel3.784 s17.538 s
Darktable 5.4.13.927 s44.481 s

Na konfiguracji 1 Darktable zdołał być wolniejszy niż Ansel na GPU, nawet patrząc tylko na renderowanie pojedynczego głównego potoku i nawet pomniejszając zdjęcie do rozdzielczości wyświetlacza wcześniej w potoku. Ale faktycznie widzimy ten sam efekt na CPU, mimo wszystkich rzekomych „optymalizacji" wykonanych na modułach tylko-CPU około 2023–2024 roku w Darktable. Ogólnie Darktable zamrozi Twój komputer o 2.3 do 3.0 s dłużej.

Na konfiguracji 2 ścieżka CPU pokazuje 0.8 s kary dla Ansela na samym głównym potoku. GPU AMD pokazuje silne uderzenie na module korekcji obiektywu, który na Anselu przetwarza w pełnej rozdzielczości, podczas gdy w Darktable przetwarza w rozdzielczości ekranu. Biorąc pod uwagę drugi potok, który uruchamia Darktable, Ansel jest szybszy o 0.14 s na GPU. Na CPU natomiast Darktable zamraża teraz maszynę o 27.07 s dłużej z powodu tej zduplikowanej pracy potoku. Tak czy inaczej, wolne moduły znajdują się na początku potoku i nie będą przeliczane przez całą sesję.

To daje nam następujące porównania modułów obok siebie. Porównujemy tylko moduły działające w tej samej rozdzielczości, wyłącznie dla potoku głównego obrazu. W zakładce ze średnimi czynniki są uśredniane średnią geometryczną, a różnice względne średnią harmoniczną:

Moduł / układAnsel MasterDarktable 5.4.1Przyspieszenie Ansela
Raw settings* / CPU35 ms40 ms×1.14 / +13 %
Raw settings* / GPU18 ms17 ms×0.96 / -5.9 %
White balance* / CPU31 ms29 ms×0.94 / -6.9 %
White balance* / GPU16 ms13 ms×0.81 / -23 %
Highlights reconstruction* / CPU6.12 s6.33 s×1.03 / +3.4 %
Highlights reconstruction* / GPU2.49 s3.70 s×1.49 / +33 %
Demosaic* / CPU348 ms717 ms×2.06 / +52 %
Demosaic* / GPU254 ms490 ms×1.93 / +48 %
Exposure / CPU5 ms12 ms×2.4 / +58 %
Exposure / GPU6 ms10 ms×1.67 / +40 %
Input color profile / CPU3 ms15 ms×3 / +67 %
Input color profile / GPU2 ms32 ms×16 / +94 %
Color calibration / CPU70 ms84 ms×1.20 / +25 %
Color calibration / GPU1 ms39 ms×39 / +97 %
Diffuse or sharpen / CPU3.15 s4.21 s×1.34 / +25.2 %
Diffuse or sharpen / GPU1.09 s1.69 s×1.55 / +35 %
Color balance RGB / CPU262 ms261 ms×1.00 / -0.4 %
Color balance RGB / GPU9 ms12 ms×1.33 / +25 %
Filmic RGB / CPU137 ms140 ms×1.02 / +2.1 %
Filmic RGB / GPU6 ms11 ms×1.83 / +45 %
Output color profile / CPU13 ms21 ms×1.62 / +38 %
Output color profile / GPU23 ms52 ms×2.26 / +56 %
Dithering (CPU only) / CPU pipeline24 ms59 ms×2.46 / +59 %
Dithering (CPU only) / GPU pipeline43 ms88 ms×2.05 / +51 %
Gamma / display encoding (CPU only) / GPU pipeline12 ms9 ms×0.75 / -33 %
Gamma / display encoding (CPU only) / CPU pipeline12 ms9 ms×0.75 / -33 %

*: moduły działające w pełnej rozdzielczości w obu aplikacjach.

Moduł / układAnsel MasterDarktable 5.4.1Przyspieszenie Ansela
Raw settings* / CPU63 ms49 ms×0.78 / -29 %
Raw settings* / GPU37 ms59 ms×1.59 / +37 %
White balance* / CPU69 ms35 ms×0.5 / -97 %
White balance* / GPU7 ms7 ms×1 / 0 %
Highlights reconstruction* / CPU13.10 s12.91 s×0.99 / -1.5 %
Highlights reconstruction* / GPU1.42 s1.87 s×1.32 / +24 %
Demosaic* / CPU0.99 s1.80 s×1.82 / +45 %
Demosaic* / GPU149 ms123 ms×0.83 / -21 %
Exposure / CPU13 ms3 ms×0.23 / -333 %
Exposure / GPU33 ms2 ms×0.06 / -1550 %
Input color profile / CPU2 ms33 ms×16.5 / +94 %
Input color profile / GPU11 ms4 ms×0.36 / -175 %
Color calibration / CPU47 ms50 ms×1.06 / 6 %
Color calibration / GPU2 ms4 ms×2 / +50 %
Diffuse or sharpen / CPU1.71 s3.37 s×1.97 / +49.4 %
Diffuse or sharpen / GPU158 ms518 ms×3.28 / +70 %
Color balance RGB / CPU152 ms179 ms×1.18 / +15 %
Color balance RGB / GPU10 ms4 ms×0.40 / -150 %
Filmic RGB / CPU93 ms114 ms×1.23 / +18 %
Filmic RGB / GPU4 ms41 ms×10.26 / +90 %
Output color profile / CPU15 ms41 ms×2.73 / +63 %
Output color profile / GPU22 ms32 ms×1.45 / +31 %
Dithering (CPU only) / CPU pipeline9 ms28 ms×3.62 / +72 %
Dithering (CPU only) / GPU pipeline13 ms47 ms×2.76 / +64 %
Gamma / display encoding (CPU only) / CPU pipeline9 ms9 ms×1.0 / 0 %
Gamma / display encoding (CPU only) / GPU pipeline8 ms15 ms×1.88 / +47 %

*: moduły działające w pełnej rozdzielczości w obu aplikacjach.

Średnia dla konfiguracji 1 i 2. Czynniki są uśredniane średnią geometryczną, a różnice względne średnią harmoniczną.

Moduł / układPrzyspieszenie AnselaKod Ansela
Raw settings* / CPU×0.94Własny (ręcznie wektoryzowany)
Raw settings* / GPU×1.23Taki sam jak DT 5.4.1
White balance* / CPU×0.69Własny (ręcznie wektoryzowany)
White balance* / GPU×0.97Taki sam jak DT 5.4.1
Highlights reconstruction* / CPU×1.1Własny (ręcznie wektoryzowany)
Highlights reconstruction* / GPU×1.4Własny
Demosaic* / CPU×1.94Taki sam jak DT 4.0
Demosaic* / GPU×1.26Własny
Exposure / CPU×0.74Własny (ręcznie wektoryzowany)
Exposure / GPU×0.32Taki sam jak DT 4.0
Input color profile / CPU×7.04Własny (ręcznie wektoryzowany)
Input color profile / GPU×2.41Własny
Color calibration / CPU×1.13Własny (ręcznie wektoryzowany)
Color calibration / GPU×8.83Taki sam jak DT 4.0
Diffuse or sharpen / CPU×1.08Własny
Diffuse or sharpen / GPU×2.26Własny
Color balance RGB / CPU×1.08Własny (ręcznie wektoryzowany)
Color balance RGB / GPU×0.73Własny
Filmic RGB / CPU×1.13Własny (ręcznie wektoryzowany)
Filmic RGB / GPU×4.33Własny
Output color profile / CPU×2.10Własny (ręcznie wektoryzowany)
Output color profile / GPU×1.81Własny
Dithering (CPU only) / CPU pipeline×2.71Taki sam jak DT 4.0
Dithering (CPU only) / GPU pipeline×2.72Taki sam jak DT 4.0
Gamma / display encoding (CPU only) / CPU pipeline×0.87Taki sam jak DT 5.4.1
Gamma / display encoding (CPU only) / GPU pipeline×1.63Taki sam jak DT 5.4.1

*: moduły działające w pełnej rozdzielczości w obu aplikacjach.

Względna szybkość na moduł (GPU). Więcej znaczy lepiej.

Ansel pokazuje poprawę wydajności GPU nawet na kernelach, które nie zmieniły się od Darktable 4.0. Przyspieszenia, które widzimy, są wyraźnie korzyścią z wysokopoziomowego zarządzania pamięcią, przy użyciu przypiętej pamięci OpenCL lub zwykłych buforów pamięci wyłącznie na urządzeniu, tam gdzie to istotne.

We wszystkich modułach Ansel jest 1,6 raza szybszy niż Darktable na GPU (średnia geometryczna).

Względna szybkość na moduł (CPU). Więcej znaczy lepiej.

Na CPU sprawy są bardziej niuansowane.

Darktable 5.x rzekomo otrzymał optymalizacje CPU dla ditheringu i demozaikowania (RCD), a jednak oba te moduły wypadają gorzej niż w Anselu na obu konfiguracjach.

Ansel ma ręcznie wektoryzowany kod pikseli CPU dla color balance RGB, color calibration, input/output color profile, raw settings, white balance, exposure i filmic, podczas gdy Darktable używa podpowiedzi kompilatora, aby osiągnąć wektoryzację. Wszystkie one wypadają lepiej niż w Darktable na Intel Xeon. Na Intel i3, przy podstawowej pracy z macierzami koloru, Ansel wypada (znacznie) lepiej, ale nieco szkodzi to podstawowym modułom jak raw settings, exposure czy white balance.

Diffuse or sharpen powinien wciąż używać tego samego kodu Darktable 4.0 na Anselu i na Darktable 5.4, ale Ansel jest szybszy na obu konfiguracjach, co pokazuje korzyść z użycia naszej własnej wstępnie zaalokowanej tablicy pamięci dla pamięci podręcznej oraz alokowania buforów pamięci poza obwodami systemu operacyjnego. To pokazuje, że optymalizowanie na poziomie modułu to zły zakres i że globalny potok wymagał najpierw przebudowy.

Ale oczywiście łatwiej jest zająć się optymalizacjami na poziomie modułów, ponieważ moduły są… modularne, czyli samozamknięte i samowystarczalne. Jest niewielka szansa na zepsucie oprogramowania jako całości podczas pracy nad modułami, ponieważ są one odizolowane od reszty. Z drugiej strony zmiany w globalnej architekturze aplikacji są niebezpieczne, skomplikowane i wymagające. I wymuszają zatrzymanie rozwoju jakichkolwiek nowych modułów, podczas gdy architektura jest stabilizowana. Czego Darktable nigdy nie będzie miał dyscypliny, by zrobić, ponieważ projekt jest niezarządzany, a losowe pull requesty pojawiają się na Githubie w losowych momentach od losowych współtwórców.

We wszystkich modułach Ansel jest 1,8 raza szybszy niż Darktable na CPU (średnia geometryczna).

Łącząc ścieżki GPU i CPU na obu konfiguracjach, otwieranie ciemni jest 1,5 raza szybsze na Anselu (średnia geometryczna).

Powiększanie do 100%

W ciemni, z rozmiaru „dopasuj powiększenie", powiększ do 100% jednym kliknięciem środkowym przyciskiem myszy

 112.083270 [dev_pixelpipe] pipeline resync with history took 0.000 secs (0.000 CPU) for pipe virtual-preview
 212.111607 [dev_pixelpipe] pipeline resync with history took 0.000 secs (0.000 CPU) for pipe full
 312.177976 [dev_pixelpipe] took 0.066 secs (0.095 CPU) processed `Initial resampling' on GPU, blended on  [full]
 412.179976 [dev_pixelpipe] took 0.002 secs (0.002 CPU) processed `exposure' on GPU, blended on GPU [full]
 512.183194 [dev_pixelpipe] took 0.003 secs (0.005 CPU) processed `input color profile' on GPU, blended on  [full]
 612.185327 [dev_pixelpipe] took 0.002 secs (0.002 CPU) processed `color calibration' on GPU, blended on GPU [full]
 714.887725 [dev_pixelpipe] took 2.702 secs (2.705 CPU) processed `diffuse or sharpen' on GPU, blended on GPU [full]
 814.899213 [dev_pixelpipe] took 0.011 secs (0.013 CPU) processed `color balance rgb' on GPU, blended on GPU [full]
 914.907159 [dev_pixelpipe] took 0.008 secs (0.008 CPU) processed `filmic rgb' on GPU, blended on GPU [full]
1014.940617 [dev_pixelpipe] took 0.033 secs (0.039 CPU) processed `output color profile' on GPU, blended on  [full]
1115.005967 [dev_pixelpipe] took 0.065 secs (0.057 CPU) processed `dithering' on CPU, blended on  [full]
1215.030966 [dev_pixelpipe] took 0.025 secs (0.036 CPU) processed `display encoding' on GPU, blended on  [full]
1315.031021 [pixelpipe] full internal pixel pipeline processing took 2.919 secs (2.963 CPU)
1415.031080 [dev_process_full] pipeline processing thread took 2.919 secs (2.963 CPU)
 189,3415 [dt_dev_process_image_job] loading image. took 0,000 secs (0,000 CPU)
 289,3440 [dev_pixelpipe] took 0,002 secs (0,001 CPU) initing base buffer [full]
 389,3544 [dev_pixelpipe] took 0,010 secs (0,008 CPU) [full] processed `rawprepare' on GPU, blended on GPU
 489,3573 [dev_pixelpipe] took 0,003 secs (0,001 CPU) [full] processed `temperature' on GPU, blended on GPU
 589,8611 [dev_pixelpipe] took 0,504 secs (0,511 CPU) [full] processed `highlights' on GPU, blended on GPU
 689,9200 [dev_pixelpipe] took 0,059 secs (0,023 CPU) [full] processed `demosaic' on GPU, blended on GPU
 789,9251 [dev_pixelpipe] took 0,005 secs (0,003 CPU) [full] processed `lens' on GPU, blended on GPU
 889,9346 [dev_pixelpipe] took 0,009 secs (0,003 CPU) [full] processed `exposure' on GPU, blended on GPU
 989,9480 [dev_pixelpipe] took 0,013 secs (0,003 CPU) [full] processed `colorin' on GPU, blended on GPU
1089,9576 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_LAB-->IOP_CS_RGB took 0,009 secs (0,002 GPU) [channelmixerrgb]
1189,9736 [dev_pixelpipe] took 0,026 secs (0,008 CPU) [full] processed `channelmixerrgb' on GPU, blended on GPU
1294,9286 [dev_pixelpipe] took 4,955 secs (5,096 CPU) [full] processed `diffuse' on GPU, blended on GPU
1394,9528 [dev_pixelpipe] took 0,024 secs (0,007 CPU) [full] processed `colorbalancergb' on GPU, blended on GPU
1494,9836 [dev_pixelpipe] took 0,031 secs (0,009 CPU) [full] processed `filmicrgb' on GPU, blended on GPU
1594,9925 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_RGB-->IOP_CS_LAB took 0,009 secs (0,000 GPU) [colorout]
1695,0700 [dev_pixelpipe] took 0,086 secs (0,028 CPU) [full] processed `colorout' on GPU, blended on GPU
1795,2184 [dev_pixelpipe] took 0,148 secs (0,093 CPU) [full] processed `dither' on CPU, blended on CPU
1895,2404 [dev_pixelpipe] took 0,022 secs (0,104 CPU) [full] processed `gamma' on CPU, blended on CPU
1995,2537 [dev_process_image] pixel pipeline took 5,912 secs (5,904 CPU) processing `2022-06-17__DSC00078.arw'
2095,2634 [dev_process_image] pixel pipeline took 0,010 secs (0,004 CPU) processing `2022-06-17__DSC00078.arw'

Ponieważ Ansel przetwarza wczesne etapy potoku w pełnej rozdzielczości, są one także cachowane w pełnej rozdzielczości, co oznacza, że przesuwanie i powiększanie pobierają pamięć podręczną wcześniej w potoku niż moduł wstępnego ponownego próbkowania, a przeliczają tylko dalej w potoku. Zajmuje to więc 2.9 s renderowania. Darktable z kolei ma niedziałającą pamięć podręczną, więc przelicza wszystko od początku, zajmując 5.9 s na wyrenderowanie tego samego. To kara wydajnościowa ×2 dla Darktable.

Przesuwanie przy 100%

 155.456510 [dev_pixelpipe] pipeline resync with history took 0.000 secs (0.000 CPU) for pipe full
 255.536149 [dev_pixelpipe] took 0.080 secs (0.121 CPU) processed `Initial resampling' on GPU, blended on  [full]
 355.541720 [dev_pixelpipe] took 0.005 secs (0.003 CPU) processed `exposure' on GPU, recycled cacheline, blended on GPU [full]
 455.551346 [dev_pixelpipe] took 0.009 secs (0.006 CPU) processed `input color profile' on GPU, recycled cacheline, blended on  [full]
 555.559669 [dev_pixelpipe] took 0.008 secs (0.005 CPU) processed `color calibration' on GPU, recycled cacheline, blended on GPU [full]
 658.290943 [dev_pixelpipe] took 2.731 secs (2.819 CPU) processed `diffuse or sharpen' on GPU, blended on GPU [full]
 758.310400 [dev_pixelpipe] took 0.016 secs (0.017 CPU) processed `color balance rgb' on GPU, recycled cacheline, blended on GPU [full]
 858.322514 [dev_pixelpipe] took 0.012 secs (0.014 CPU) processed `filmic rgb' on GPU, recycled cacheline, blended on GPU [full]
 958.364803 [dev_pixelpipe] took 0.042 secs (0.054 CPU) processed `output color profile' on GPU, blended on  [full]
1058.411730 [dev_pixelpipe] took 0.045 secs (0.052 CPU) processed `dithering' on CPU, recycled cacheline, blended on  [full]
1158.440432 [dev_pixelpipe] took 0.029 secs (0.042 CPU) processed `display encoding' on GPU, blended on  [full]
1258.440478 [pixelpipe] full internal pixel pipeline processing took 2.984 secs (3.133 CPU)
1358.440516 [dev_process_full] pipeline processing thread took 2.984 secs (3.133 CPU)
 1253,1082 [dev_pixelpipe] took 0,003 secs (0,004 CPU) initing base buffer [full]
 2253,1132 [dev_pixelpipe] took 0,005 secs (0,004 CPU) [full] processed `rawprepare' on GPU, blended on GPU
 3253,1152 [dev_pixelpipe] took 0,002 secs (0,001 CPU) [full] processed `temperature' on GPU, blended on GPU
 4253,5743 [dev_pixelpipe] took 0,459 secs (0,441 CPU) [full] processed `highlights' on GPU, blended on GPU
 5253,6257 [dev_pixelpipe] took 0,051 secs (0,022 CPU) [full] processed `demosaic' on GPU, blended on GPU
 6253,6307 [dev_pixelpipe] took 0,005 secs (0,002 CPU) [full] processed `lens' on GPU, blended on GPU
 7253,6394 [dev_pixelpipe] took 0,009 secs (0,003 CPU) [full] processed `exposure' on GPU, blended on GPU
 8253,6521 [dev_pixelpipe] took 0,013 secs (0,003 CPU) [full] processed `colorin' on GPU, blended on GPU
 9253,6610 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_LAB-->IOP_CS_RGB took 0,008 secs (0,001 GPU) [channelmixerrgb]
10253,6760 [dev_pixelpipe] took 0,024 secs (0,005 CPU) [full] processed `channelmixerrgb' on GPU, blended on GPU
11258,6306 [dev_pixelpipe] took 4,954 secs (4,882 CPU) [full] processed `diffuse' on GPU, blended on GPU
12258,6548 [dev_pixelpipe] took 0,024 secs (0,005 CPU) [full] processed `colorbalancergb' on GPU, blended on GPU
13258,6857 [dev_pixelpipe] took 0,031 secs (0,009 CPU) [full] processed `filmicrgb' on GPU, blended on GPU
14258,6945 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_RGB-->IOP_CS_LAB took 0,009 secs (0,000 GPU) [colorout]
15258,7725 [dev_pixelpipe] took 0,087 secs (0,034 CPU) [full] processed `colorout' on GPU, blended on GPU
16258,9198 [dev_pixelpipe] took 0,147 secs (0,086 CPU) [full] processed `dither' on CPU, blended on CPU
17258,9373 [dev_pixelpipe] took 0,017 secs (0,110 CPU) [full] processed `gamma' on CPU, blended on CPU
18258,9407 [dev_process_image] pixel pipeline took 5,859 secs (5,614 CPU) processing `2022-06-17__DSC00078.arw'
19258,9735 [dev_process_image] pixel pipeline took 0,033 secs (0,014 CPU) processing `2022-06-17__DSC00078.arw'

Przesuwanie prowadzi do tej samej sytuacji co powiększanie: Ansel ma obraz w pełnej rozdzielczości w pamięci podręcznej, więc przeliczamy od wstępnego przepróbkowania, co zajmuje 3 s. Darktable nie potrafi właściwie zarządzać pamięcią podręczną, więc czeka Cię pełne przeliczenie i 5,9 s zamrożonego komputera.

Przechodzenie tam i z powrotem do stołu podświetlanego

1184.536988 [dev_pixelpipe] took 0.000 secs (0.000 CPU) to load the image.
2184.574776 [dev_pixelpipe] pipeline resync with history took 0.019 secs (0.016 CPU) for pipe virtual-preview
3185.012395 [dev_pixelpipe] pipeline resync with history took 0.020 secs (0.017 CPU) for pipe virtual-preview
4185.046379 [dev_pixelpipe] pipeline resync with history took 0.000 secs (0.000 CPU) for pipe virtual-preview
5185.046424 [dev_pixelpipe] pipeline resync with history took 0.000 secs (0.000 CPU) for pipe virtual-preview
6185.049883 [dev_pixelpipe] pipeline resync with history took 0.026 secs (0.049 CPU) for pipe preview
7185.049931 [dev_pixelpipe] pipeline resync with history took 0.000 secs (0.000 CPU) for pipe preview
8185.068516 [dev_pixelpipe] pipeline resync with history took 0.019 secs (0.020 CPU) for pipe full
9185.068568 [dev_process_full] pipeline processing thread took 0.000 secs (0.000 CPU)
 13324,5295 [histogram] took 0,006 secs (0,005 CPU) scope draw
 23324,5837 [dt_dev_process_image_job] loading image. took 0,000 secs (0,000 CPU)
 33324,6371 [dev_process_image] pixel pipeline took 0,002 secs (0,001 CPU) processing `2022-06-17__DSC00078.arw'
 4
 53324,7352 [dt_dev_process_image_job] loading image. took 0,000 secs (0,000 CPU)
 63324,8074 [dev_pixelpipe] took 0,000 secs (0,000 CPU) initing base buffer [preview]
 73324,8155 [dev_pixelpipe] took 0,008 secs (0,005 CPU) [preview] processed `rawprepare' on GPU, blended on GPU
 83324,8165 [dev_pixelpipe] took 0,001 secs (0,001 CPU) [preview] processed `temperature' on GPU, blended on GPU
 93324,8178 [dev_pixelpipe] took 0,001 secs (0,003 CPU) [preview] processed `highlights' on GPU, blended on GPU
103324,8337 [dev_pixelpipe] took 0,016 secs (0,021 CPU) [preview] processed `demosaic' on GPU, blended on GPU
113324,8351 [dev_pixelpipe] took 0,001 secs (0,002 CPU) [preview] processed `lens' on GPU, blended on GPU
123324,8387 [dev_pixelpipe] took 0,003 secs (0,013 CPU) [preview] processed `exposure' on GPU, blended on GPU
133324,8440 [dev_pixelpipe] took 0,005 secs (0,015 CPU) [preview] processed `colorin' on GPU, blended on GPU
143324,8491 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_LAB-->IOP_CS_RGB took 0,005 secs (0,012 GPU) [channelmixerrgb]
153324,8545 [dev_pixelpipe] took 0,010 secs (0,019 CPU) [preview] processed `channelmixerrgb' on GPU, blended on GPU
163325,6440 [dev_pixelpipe] took 0,789 secs (0,921 CPU) [preview] processed `diffuse' on GPU, blended on GPU
173325,6482 [dev_pixelpipe] took 0,004 secs (0,003 CPU) [preview] processed `colorbalancergb' on GPU, blended on GPU
183325,6534 [dev_pixelpipe] took 0,005 secs (0,003 CPU) [preview] processed `filmicrgb' on GPU, blended on GPU
193325,6569 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_RGB-->IOP_CS_LAB took 0,003 secs (0,002 GPU) [colorout]
203325,6747 [dev_pixelpipe] took 0,021 secs (0,008 CPU) [preview] processed `colorout' on GPU, blended on GPU
213325,6979 [dev_pixelpipe] took 0,023 secs (0,009 CPU) [preview] processed `dither' on CPU, blended on CPU
223325,7061 [dev_pixelpipe] took 0,008 secs (0,029 CPU) [preview] processed `gamma' on CPU, blended on CPU
233325,7263 [dt_ioppr_transform_image_colorspace_rgb] `Système' -> `sRVB' took 0,020 secs (0,068 CPU) [final histogram]
243325,7357 [dev_process_image] pixel pipeline took 0,935 secs (1,152 CPU) processing `2022-06-17__DSC00078.arw'

Pamięć podręczna potoku Ansela przeżywa same potoki, co oznacza, że opuszczenie ciemni i ponowne wejście do niej jest niemal natychmiastowe: zajmuje jedynie 60 ms ponowne zsynchronizowanie wszystkich potoków z historią wywoływania i stwierdzenie, że wynik potoku mamy już w pamięci podręcznej dzięki jego sumie kontrolnej. Zauważ, że nie ma tu żadnej specjalnej obsługi, to zwykłe zachowanie pamięci podręcznej.

W przypadku Darktable działa to niemal dla głównego obrazu, ale nie dla podglądu, więc czeka Cię 1 s bezużytecznego przeliczania. Biorąc pod uwagę, że działa to na jednym potoku, ale nie na drugim, obstawiam, że jest to obsługiwane za pomocą przypadków szczególnych. W porównaniu z Anselem to kara wydajnościowa ×1,75.

Wyłączanie modułu w trakcie potoku

Wyłączyłem rozmycie lub wyostrzenie przy powiększeniu w rozmiarze „wypełnij".

 1124.206298 [dev_pixelpipe] pipeline resync with history took 0.011 secs (0.010 CPU) for pipe virtual-preview
 2124.237640 [dev_pixelpipe] pipeline resync with history took 0.011 secs (0.028 CPU) for pipe preview
 3124.290940 [dev_pixelpipe] took 0.023 secs (0.028 CPU) processed `color balance rgb' on GPU, recycled cacheline, blended on GPU [preview]
 4124.303176 [dev_pixelpipe] took 0.012 secs (0.012 CPU) processed `filmic rgb' on GPU, recycled cacheline, blended on GPU [preview]
 5124.331334 [dev_pixelpipe] took 0.028 secs (0.030 CPU) processed `output color profile' on GPU, blended on  [preview]
 6124.370193 [darkroom] took 0.007 secs (0.011 CPU) redraw
 7124.382425 [dev_pixelpipe] took 0.051 secs (0.055 CPU) processed `dithering' on CPU, blended on  [preview]
 8124.410053 [dev_pixelpipe] took 0.028 secs (0.028 CPU) processed `display encoding' on GPU, blended on  [preview]
 9124.410088 [pixelpipe] preview internal pixel pipeline processing took 0.172 secs (0.197 CPU)
10124.410115 [dev_process_preview] pipeline processing thread took 0.172 secs (0.197 CPU)
11124.433186 [dev_pixelpipe] pipeline resync with history took 0.013 secs (0.017 CPU) for pipe full
12124.433286 [dev_process_full] pipeline processing thread took 0.000 secs (0.000 CPU)
 1300,9771 [dt_dev_process_image_job] loading image. took 0,000 secs (0,000 CPU)
 2301,0564 [dev_pixelpipe] took 0,016 secs (0,028 CPU) initing base buffer [full]
 3301,0936 [dev_pixelpipe] took 0,037 secs (0,037 CPU) [full] processed `rawprepare' on GPU, blended on GPU
 4301,1102 [dev_pixelpipe] took 0,017 secs (0,017 CPU) [full] processed `temperature' on GPU, blended on GPU
 5304,8700 [dev_pixelpipe] took 3,760 secs (3,786 CPU) [full] processed `highlights' on GPU with tiling, blended on CPU
 6305,2630 [resample_cl] plan 0,001 secs (0,000 CPU) resample 0,010 secs (0,001 CPU)
 7305,3543 [dev_pixelpipe] took 0,484 secs (0,284 CPU) [full] processed `demosaic' on GPU, blended on GPU
 8305,3648 [dev_pixelpipe] took 0,010 secs (0,002 CPU) [full] processed `lens' on GPU, blended on GPU
 9305,3764 [dev_pixelpipe] took 0,012 secs (0,001 CPU) [full] processed `exposure' on GPU, blended on GPU
10305,4083 [dev_pixelpipe] took 0,032 secs (0,007 CPU) [full] processed `colorin' on GPU, blended on GPU
11305,4220 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_LAB-->IOP_CS_RGB took 0,013 secs (0,001 GPU) [channelmixerrgb]
12305,4460 [dev_pixelpipe] took 0,038 secs (0,008 CPU) [full] processed `channelmixerrgb' on GPU, blended on GPU
13307,1388 [dev_pixelpipe] took 1,693 secs (1,645 CPU) [full] processed `diffuse' on GPU, blended on GPU
14307,1500 [dev_pixelpipe] took 0,011 secs (0,003 CPU) [full] processed `colorbalancergb' on GPU, blended on GPU
15307,1612 [dev_pixelpipe] took 0,011 secs (0,003 CPU) [full] processed `filmicrgb' on GPU, blended on GPU
16307,1675 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_RGB-->IOP_CS_LAB took 0,006 secs (0,001 GPU) [colorout]
17307,2185 [dev_pixelpipe] took 0,057 secs (0,020 CPU) [full] processed `colorout' on GPU, blended on GPU
18307,3095 [dev_pixelpipe] took 0,091 secs (0,056 CPU) [full] processed `dither' on CPU, blended on CPU
19307,3235 [dev_pixelpipe] took 0,014 secs (0,065 CPU) [full] processed `gamma' on CPU, blended on CPU
20307,3333 [dev_process_image] pixel pipeline took 6,293 secs (5,969 CPU) processing `2022-06-17__DSC00078.arw'
21307,3843 [dev_process_image] pixel pipeline took 0,051 secs (0,018 CPU) processing `2022-06-17__DSC00078.arw'
22
23307,4258 [dt_dev_process_image_job] loading image. took 0,000 secs (0,000 CPU)
24307,4875 [dev_pixelpipe] took 0,000 secs (0,000 CPU) initing base buffer [preview]
25307,4895 [dev_pixelpipe] took 0,002 secs (0,002 CPU) [preview] processed `rawprepare' on GPU, blended on GPU
26307,4906 [dev_pixelpipe] took 0,001 secs (0,001 CPU) [preview] processed `temperature' on GPU, blended on GPU
27307,5609 [dev_pixelpipe] took 0,070 secs (0,072 CPU) [preview] processed `highlights' on GPU, blended on GPU
28307,5722 [dev_pixelpipe] took 0,011 secs (0,005 CPU) [preview] processed `demosaic' on GPU, blended on GPU
29307,5740 [dev_pixelpipe] took 0,002 secs (0,000 CPU) [preview] processed `lens' on GPU, blended on GPU
30307,5765 [dev_pixelpipe] took 0,002 secs (0,001 CPU) [preview] processed `exposure' on GPU, blended on GPU
31307,5798 [dev_pixelpipe] took 0,003 secs (0,002 CPU) [preview] processed `colorin' on GPU, blended on GPU
32307,5827 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_LAB-->IOP_CS_RGB took 0,002 secs (0,002 GPU) [channelmixerrgb]
33307,5864 [dev_pixelpipe] took 0,007 secs (0,003 CPU) [preview] processed `channelmixerrgb' on GPU, blended on GPU
34307,9576 [dev_pixelpipe] took 0,371 secs (0,356 CPU) [preview] processed `diffuse' on GPU, blended on GPU
35307,9613 [dev_pixelpipe] took 0,003 secs (0,002 CPU) [preview] processed `colorbalancergb' on GPU, blended on GPU
36307,9659 [dev_pixelpipe] took 0,005 secs (0,000 CPU) [preview] processed `filmicrgb' on GPU, blended on GPU
37307,9686 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_RGB-->IOP_CS_LAB took 0,002 secs (0,000 GPU) [colorout]
38307,9847 [dev_pixelpipe] took 0,019 secs (0,008 CPU) [preview] processed `colorout' on GPU, blended on GPU
39308,0043 [dev_pixelpipe] took 0,020 secs (0,011 CPU) [preview] processed `dither' on CPU, blended on CPU
40308,0103 [dev_pixelpipe] took 0,006 secs (0,023 CPU) [preview] processed `gamma' on CPU, blended on CPU
41308,0244 [dt_ioppr_transform_image_colorspace_rgb] `Système' -> `sRVB' took 0,014 secs (0,081 CPU) [final histogram]
42308,0353 [dev_process_image] pixel pipeline took 0,553 secs (0,604 CPU) processing `2022-06-17__DSC00078.arw'

Znowu pamięć podręczna Ansela po prostu działa: wyłączenie rozmycia lub wyostrzenia prowadzi do przeliczenia jedynie modułów znajdujących się dalej w potoku, co zajmuje 172 ms. Znowu pamięć podręczna Darktable nie działa, co prowadzi do przeliczenia całych 2 potoków (podglądu i głównego), przy łącznym koszcie 6,9 s. To kara wydajnościowa ×40, ludzie! Sprawia to, że wszystkie fikcyjne przyspieszenia o +15% od Ralfa Browna są całkowicie nieistotne! Ale hej! Szukaj dalej zgubionych kluczy tam, gdzie widzisz swoje stopy, a nie tam, gdzie je zgubiłeś! .

Pobieranie koloru w module

Ustawiamy balans bieli w kalibracji koloru przy użyciu próbnika koloru, na poziomie powiększenia „dopasuj do rozmiaru".

 1colorpicker stats reading took 0.020 secs (0.088 CPU)
 2colorpicker stats reading took 0.018 secs (0.087 CPU)
 312.128030 [dev_pixelpipe] pipeline resync with history took 0.024 secs (0.037 CPU) for pipe virtual-preview
 412.137829 [dev_pixelpipe] pipeline resync with history took 0.024 secs (0.056 CPU) for pipe preview
 512.157665 [dev_pixelpipe] took 0.020 secs (0.048 CPU) processed `color calibration' on GPU, blended on GPU [preview]
 613.327326 [dev_pixelpipe] took 1.170 secs (1.231 CPU) processed `diffuse or sharpen' on GPU, blended on GPU [preview]
 713.336618 [dev_pixelpipe] took 0.009 secs (0.008 CPU) processed `color balance rgb' on GPU, recycled cacheline, blended on GPU [preview]
 813.345393 [dev_pixelpipe] took 0.009 secs (0.007 CPU) processed `filmic rgb' on GPU, recycled cacheline, blended on GPU [preview]
 913.368053 [dev_pixelpipe] took 0.023 secs (0.030 CPU) processed `output color profile' on GPU, blended on  [preview]
1013.418643 [dev_pixelpipe] took 0.050 secs (0.061 CPU) processed `dithering' on CPU, blended on  [preview]
1113.435846 [dev_pixelpipe] took 0.017 secs (0.024 CPU) processed `display encoding' on GPU, blended on  [preview]
1213.435919 [pixelpipe] preview internal pixel pipeline processing took 1.298 secs (1.408 CPU)
1313.435949 [dev_process_preview] pipeline processing thread took 1.298 secs (1.408 CPU)
1413.526316 [dev_pixelpipe] pipeline resync with history took 0.080 secs (0.376 CPU) for pipe full
1513.526443 [dev_process_full] pipeline processing thread took 0.000 secs (0.000 CPU)
16colorpicker stats reading took 0.185 secs (0.741 CPU)
17image colorspace transform RGB-->RGB took 0.000 secs (0.000 CPU) [[histogram] sample swatch]
18image colorspace transform RGB-->RGB took 0.000 secs (0.000 CPU) [[histogram] sample swatch]
19image colorspace transform RGB-->RGB took 0.000 secs (0.000 CPU) [[histogram] sample swatch]
 116,1190 [dt_dev_process_image_job] loading image. took 0,000 secs (0,000 CPU)
 216,1364 dt_color_picker_helper stats reading 4 channels (filters 2492765332) cst 2 -> 2 size 1118880 denoised 0 took 0,001 secs (0,005 CPU)
 316,1396 [dev_pixelpipe] took 0,020 secs (0,025 CPU) [preview] processed `channelmixerrgb' on GPU, blended on GPU
 416,2071 [dt_dev_process_image_job] loading image. took 0,000 secs (0,000 CPU)
 516,6064 [dev_pixelpipe] took 0,467 secs (0,567 CPU) [preview] processed `diffuse' on GPU, blended on GPU
 616,6106 [dev_pixelpipe] took 0,004 secs (0,004 CPU) [preview] processed `colorbalancergb' on GPU, blended on GPU
 716,6160 [dev_pixelpipe] took 0,005 secs (0,001 CPU) [preview] processed `filmicrgb' on GPU, blended on GPU
 816,6192 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_RGB-->IOP_CS_LAB took 0,003 secs (0,002 GPU) [colorout]
 916,6364 [dev_pixelpipe] took 0,020 secs (0,010 CPU) [preview] processed `colorout' on GPU, blended on GPU
1016,6513 [dev_pixelpipe] took 0,015 secs (0,005 CPU) [preview] processed `dither' on CPU, blended on CPU
1116,6572 [dev_pixelpipe] took 0,006 secs (0,023 CPU) [preview] processed `gamma' on CPU, blended on CPU
1216,6584 dt_color_picker_helper stats reading 4 channels (filters 2492765332) cst 2 -> 2 size 1118880 denoised 0 took 0,001 secs (0,005 CPU)
1316,6586 [dt_ioppr_transform_image_colorspace] IOP_CS_RGB-->IOP_CS_LAB took 0,000 secs (0,000 CPU) [gamma]
1416,6587 [dt_ioppr_transform_image_colorspace_rgb] `Système' -> `sRVB' took 0,000 secs (0,000 CPU) [primary picker]
1516,6754 [dt_ioppr_transform_image_colorspace_rgb] `Système' -> `sRVB' took 0,017 secs (0,063 CPU) [final histogram]
1616,6843 [histogram] took 0,026 secs (0,093 CPU) final waveform
1716,6847 [dev_process_image] pixel pipeline took 0,566 secs (0,733 CPU) processing `2022-06-17__DSC00078.arw'
18
1916,6854 [dt_dev_process_image_job] loading image. took 0,000 secs (0,000 CPU)
2020,3395 [dev_pixelpipe] took 3,606 secs (3,778 CPU) [full] processed `highlights' on GPU with tiling, blended on CPU
2120,7350 [resample_cl] plan 0,001 secs (0,000 CPU) resample 0,010 secs (0,001 CPU)
2220,8263 [dev_pixelpipe] took 0,487 secs (0,285 CPU) [full] processed `demosaic' on GPU, blended on GPU
2320,8389 [dev_pixelpipe] took 0,012 secs (0,002 CPU) [full] processed `lens' on GPU, blended on GPU
2420,8505 [dev_pixelpipe] took 0,012 secs (0,002 CPU) [full] processed `exposure' on GPU, blended on GPU
2520,8812 [dev_pixelpipe] took 0,031 secs (0,008 CPU) [full] processed `colorin' on GPU, blended on GPU
2620,8972 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_LAB-->IOP_CS_RGB took 0,016 secs (0,004 GPU) [channelmixerrgb]
2720,9551 [dev_pixelpipe] took 0,074 secs (0,023 CPU) [full] processed `channelmixerrgb' on GPU, blended on GPU
2821,8137 [dev_pixelpipe] took 0,000 secs (0,000 CPU) initing base buffer [preview]
2921,8160 [dev_pixelpipe] took 0,001 secs (0,005 CPU) [preview] processed `rawprepare' on CPU, blended on CPU
3021,8173 [dev_pixelpipe] took 0,001 secs (0,001 CPU) [preview] processed `temperature' on CPU, blended on CPU
3121,9879 [dev_pixelpipe] took 0,171 secs (0,758 CPU) [preview] processed `highlights' on CPU, blended on CPU
3222,0020 [dev_pixelpipe] took 0,014 secs (0,080 CPU) [preview] processed `demosaic' on CPU, blended on CPU
3322,0103 [dev_pixelpipe] took 0,008 secs (0,016 CPU) [preview] processed `lens' on CPU, blended on CPU
3422,0127 [dev_pixelpipe] took 0,002 secs (0,014 CPU) [preview] processed `exposure' on CPU, blended on CPU
3522,0155 [dev_pixelpipe] took 0,003 secs (0,015 CPU) [preview] processed `colorin' on CPU, blended on CPU
3622,0172 [dt_ioppr_transform_image_colorspace] IOP_CS_LAB-->IOP_CS_RGB took 0,002 secs (0,009 CPU) [channelmixerrgb]
3722,0457 dt_color_picker_helper stats reading 4 channels (filters 2492765332) cst 2 -> 2 size 1118880 denoised 0 took 0,002 secs (0,013 CPU)
3822,0458 [dev_pixelpipe] took 0,030 secs (0,180 CPU) [preview] processed `channelmixerrgb' on CPU, blended on CPU
3922,6461 [dev_pixelpipe] took 1,691 secs (5,549 CPU) [full] processed `diffuse' on GPU, blended on GPU
4022,6595 [dev_pixelpipe] took 0,013 secs (0,079 CPU) [full] processed `colorbalancergb' on GPU, blended on GPU
4122,6725 [dev_pixelpipe] took 0,013 secs (0,073 CPU) [full] processed `filmicrgb' on GPU, blended on GPU
4222,6824 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_RGB-->IOP_CS_LAB took 0,007 secs (0,041 GPU) [colorout]
4322,7443 [dev_pixelpipe] took 0,072 secs (0,398 CPU) [full] processed `colorout' on GPU, blended on GPU
4422,8624 [dev_pixelpipe] took 0,118 secs (0,664 CPU) [full] processed `dither' on CPU, blended on CPU
4522,8878 [dev_pixelpipe] took 0,025 secs (0,172 CPU) [full] processed `gamma' on CPU, blended on CPU
4622,8894 [dev_process_image] pixel pipeline took 6,637 secs (11,596 CPU) processing `2022-06-17__DSC00078.arw'
4723,4204 [dev_pixelpipe] took 1,374 secs (8,556 CPU) [preview] processed `diffuse' on CPU, blended on CPU
4823,5271 [dev_pixelpipe] took 0,106 secs (0,617 CPU) [preview] processed `colorbalancergb' on CPU, blended on CPU
4923,5788 [dev_pixelpipe] took 0,051 secs (0,291 CPU) [preview] processed `filmicrgb' on CPU, blended on CPU
5023,5817 [dt_ioppr_transform_image_colorspace] IOP_CS_RGB-->IOP_CS_LAB took 0,003 secs (0,011 CPU) [colorout]
5123,5870 [dev_pixelpipe] took 0,008 secs (0,037 CPU) [preview] processed `colorout' on CPU, blended on CPU
5223,5913 [dev_pixelpipe] took 0,004 secs (0,004 CPU) [preview] processed `dither' on CPU, blended on CPU
5323,5964 [dev_pixelpipe] took 0,005 secs (0,021 CPU) [preview] processed `gamma' on CPU, blended on CPU
5423,5973 dt_color_picker_helper stats reading 4 channels (filters 2492765332) cst 2 -> 2 size 1118880 denoised 0 took 0,001 secs (0,006 CPU)
5523,5976 [dt_ioppr_transform_image_colorspace] IOP_CS_RGB-->IOP_CS_LAB took 0,000 secs (0,000 CPU) [gamma]
5623,5977 [dt_ioppr_transform_image_colorspace_rgb] `Système' -> `sRVB' took 0,000 secs (0,000 CPU) [primary picker]
5723,6099 [dt_ioppr_transform_image_colorspace_rgb] `Système' -> `sRVB' took 0,012 secs (0,067 CPU) [final histogram]
5823,6215 [histogram] took 0,024 secs (0,097 CPU) final waveform
5923,6219 [dev_process_image] pixel pipeline took 6,889 secs (15,651 CPU) processing `2022-06-17__DSC00078.arw'

Ansel nie musi uruchamiać nowego renderowania potoku, aby pobrać próbkę próbnikiem koloru, ponieważ wejście i wyjście modułu wciąż znajdują się w pamięci podręcznej, więc pobranie próbki zajmuje łącznie 40 ms. Następnie nowe parametry są zatwierdzane w module, co zmienia historię i wyzwala przeliczenie modułu kalibracja koloru oraz wszystkich modułów dalej w potoku. Łączny czas: 1,4 s.

Darktable musi ponownie uruchomić nowe renderowanie potoku, aby pobrać próbkę próbnikiem koloru, bo czemu nie? Zajmuje to 0,6 s. Ucieszy Cię wiadomość, że Darktable pobiera próbkę koloru na GPU, co zajmuje tylko 1 ms (w środku tych 0,6 s czasu działania potoku), podczas gdy Ansel pobiera próbkę na CPU w 20 ms. Następnie parametry próbnika koloru są zatwierdzane w historii, co ponownie uruchamia pełne przeliczenie 2 potoków. Łączny czas: 7,5 s.

To kara ×5,4 dla Darktable, wszystko dlatego, że nie ulepszają swoich potoków, lecz gonią za każdym możliwym wiatrem w polu.

Eksportowanie w pełnej rozdzielczości

Eksportujemy po otwarciu obrazu w ciemni. To ważne. W Darktable używane jest próbkowanie „wysokiej jakości", w Anselu jest to jedyny tryb.

 118.917740 [dev_pixelpipe] took 0.000 secs (0.000 CPU) to load the image.
 218.970877 [export] creating pixelpipe took 0.054 secs (0.074 CPU)
 319.215438 [dev_pixelpipe] took 0.244 secs (0.222 CPU) processed `exposure' on GPU, blended on GPU [export]
 419.241544 [dev_pixelpipe] took 0.026 secs (0.025 CPU) processed `input color profile' on GPU, blended on  [export]
 519.267887 [dev_pixelpipe] took 0.026 secs (0.026 CPU) processed `color calibration' on GPU, blended on GPU [export]
 656.171662 [dev_pixelpipe] took 36.904 secs (36.592 CPU) processed `diffuse or sharpen' on GPU with tiling, blended on CPU [export]
 756.364588 [dev_pixelpipe] took 0.193 secs (0.156 CPU) processed `color balance rgb' on GPU, blended on GPU [export]
 857.293819 [dev_pixelpipe] took 0.929 secs (0.468 CPU) processed `filmic rgb' on GPU with tiling, blended on CPU [export]
 957.516252 [dev_pixelpipe] took 0.222 secs (0.176 CPU) processed `output color profile' on GPU, blended on  [export]
1057.534628 [dev_pixelpipe] took 0.018 secs (0.017 CPU) processed `Final resampling' on GPU, blended on  [export]
1158.462789 [dev_pixelpipe] took 0.928 secs (0.544 CPU) processed `dithering' on CPU, blended on  [export]
1258.462829 [pixelpipe] export internal pixel pipeline processing took 39.492 secs (38.227 CPU)
1358.462898 [dev_process_export] pixel pipeline processing thread took 39.492 secs (38.227 CPU)
14[export_job] exported to `/home/aurelienpierre/Téléchargements/2022-06-17__DSC00078_08.jpg'
 1526,9861 [export] creating pixelpipe took 0,063 secs (0,091 CPU)
 2526,9862 [dev_pixelpipe] took 0,000 secs (0,000 CPU) initing base buffer [export]
 3527,0072 [dev_pixelpipe] took 0,021 secs (0,020 CPU) [export] processed `rawprepare' on GPU, blended on GPU
 4527,0237 [dev_pixelpipe] took 0,016 secs (0,007 CPU) [export] processed `temperature' on GPU, blended on GPU
 5530,7560 [dev_pixelpipe] took 3,732 secs (3,868 CPU) [export] processed `highlights' on GPU with tiling, blended on CPU
 6531,1444 [dev_pixelpipe] took 0,388 secs (0,201 CPU) [export] processed `demosaic' on GPU, blended on GPU
 7531,9899 [dev_pixelpipe] took 0,845 secs (0,493 CPU) [export] processed `lens' on GPU with tiling, blended on CPU
 8532,1378 [dev_pixelpipe] took 0,148 secs (0,139 CPU) [export] processed `exposure' on GPU, blended on GPU
 9532,1968 [dev_pixelpipe] took 0,059 secs (0,047 CPU) [export] processed `colorin' on GPU, blended on GPU
10532,2287 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_LAB-->IOP_CS_RGB took 0,031 secs (0,019 GPU) [channelmixerrgb]
11532,3206 [dev_pixelpipe] took 0,124 secs (0,089 CPU) [export] processed `channelmixerrgb' on GPU, blended on GPU
12575,5645 [dev_pixelpipe] took 43,244 secs (43,220 CPU) [export] processed `diffuse' on GPU with tiling, blended on CPU
13575,7422 [dev_pixelpipe] took 0,178 secs (0,167 CPU) [export] processed `colorbalancergb' on GPU, blended on GPU
14576,2975 [dev_pixelpipe] took 0,555 secs (0,474 CPU) [export] processed `filmicrgb' on GPU with tiling, blended on CPU
15576,3897 [resample_cl] took 0,000 secs (0,000 CPU) 1:1 copy/crop of 7968x5320 pixels
16576,4081 [dev_pixelpipe] took 0,111 secs (0,101 CPU) [export] processed `finalscale' on GPU, blended on GPU
17576,4403 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_RGB-->IOP_CS_LAB took 0,032 secs (0,020 GPU) [colorout]
18576,5618 [dev_pixelpipe] took 0,154 secs (0,120 CPU) [export] processed `colorout' on GPU, blended on GPU
19577,0202 [dev_pixelpipe] took 0,458 secs (0,456 CPU) [export] processed `dither' on CPU, blended on CPU
20577,0204 [dev_process_export] pixel pipeline processing took 50,034 secs (49,403 CPU)
21578,2407 [export_job] exported to `/home/aurelienpierre/Téléchargements/2022-06-17__DSC00078_06.jpg'

W Anselu, ponieważ wcześniej otworzyliśmy obraz w ciemni, wczesne etapy potoku wciąż znajdują się w pamięci podręcznej, więc eksport zaczyna się od modułu ekspozycja i zajmuje 39,5 s. W Darktable, cóż… jak zwykle: pełne przeliczenie potoku, które zajmuje 50 s.

Ponowne eksportowanie w połowie rozdzielczości

Eksportujemy w połowie rozdzielczości bezpośrednio po wyeksportowaniu w pełnej rozdzielczości.

165.601240 [dev_pixelpipe] took 0.000 secs (0.000 CPU) to load the image.
265.658874 [export] creating pixelpipe took 0.060 secs (0.079 CPU)
365.912677 [dev_pixelpipe] took 0.254 secs (0.233 CPU) processed `output color profile' on GPU, blended on  [export]
465.972780 [dev_pixelpipe] took 0.060 secs (0.073 CPU) processed `Final resampling' on GPU, blended on  [export]
566.160165 [dev_pixelpipe] took 0.187 secs (0.238 CPU) processed `dithering' on CPU, blended on  [export]
666.160184 [pixelpipe] export internal pixel pipeline processing took 0.501 secs (0.544 CPU)
766.160202 [dev_process_export] pixel pipeline processing thread took 0.501 secs (0.545 CPU)
8[export_job] exported to `/home/aurelienpierre/Téléchargements/2022-06-17__DSC00078_09.jpg'
 1607,7584 [export] creating pixelpipe took 0,064 secs (0,088 CPU)
 2607,7586 [dev_pixelpipe] took 0,000 secs (0,000 CPU) initing base buffer [export]
 3607,7918 [dev_pixelpipe] took 0,033 secs (0,028 CPU) [export] processed `rawprepare' on GPU, blended on GPU
 4607,8083 [dev_pixelpipe] took 0,016 secs (0,007 CPU) [export] processed `temperature' on GPU, blended on GPU
 5611,5555 [dev_pixelpipe] took 3,747 secs (3,865 CPU) [export] processed `highlights' on GPU with tiling, blended on CPU
 6611,9454 [dev_pixelpipe] took 0,390 secs (0,201 CPU) [export] processed `demosaic' on GPU, blended on GPU
 7612,7083 [dev_pixelpipe] took 0,763 secs (0,462 CPU) [export] processed `lens' on GPU with tiling, blended on CPU
 8612,8559 [dev_pixelpipe] took 0,148 secs (0,137 CPU) [export] processed `exposure' on GPU, blended on GPU
 9612,9149 [dev_pixelpipe] took 0,059 secs (0,045 CPU) [export] processed `colorin' on GPU, blended on GPU
10612,9468 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_LAB-->IOP_CS_RGB took 0,032 secs (0,021 GPU) [channelmixerrgb]
11613,0390 [dev_pixelpipe] took 0,124 secs (0,090 CPU) [export] processed `channelmixerrgb' on GPU, blended on GPU
12656,1897 [dev_pixelpipe] took 43,151 secs (42,852 CPU) [export] processed `diffuse' on GPU with tiling, blended on CPU
13656,3673 [dev_pixelpipe] took 0,178 secs (0,167 CPU) [export] processed `colorbalancergb' on GPU, blended on GPU
14656,9163 [dev_pixelpipe] took 0,549 secs (0,467 CPU) [export] processed `filmicrgb' on GPU with tiling, blended on CPU
15657,0078 [resample_cl] plan 0,001 secs (0,000 CPU) resample 0,001 secs (0,000 CPU)
16657,1070 [dev_pixelpipe] took 0,191 secs (0,181 CPU) [export] processed `finalscale' on GPU, blended on GPU
17657,1233 [dt_ioppr_transform_image_colorspace_cl] IOP_CS_RGB-->IOP_CS_LAB took 0,016 secs (0,004 GPU) [colorout]
18657,1792 [dev_pixelpipe] took 0,072 secs (0,030 CPU) [export] processed `colorout' on GPU, blended on GPU
19657,3186 [dev_pixelpipe] took 0,139 secs (0,136 CPU) [export] processed `dither' on CPU, blended on CPU
20657,3187 [dev_process_export] pixel pipeline processing took 49,560 secs (48,669 CPU)
21657,8107 [export_job] exported to `/home/aurelienpierre/Téléchargements/2022-06-17__DSC00078_07.jpg'

Ponieważ Ansel zawsze eksportuje w pełnej rozdzielczości aż do modułu Końcowe przepróbkowanie (tryb zwany „wysoką jakością" w Darktable), potok w większości pozostaje ten sam niezależnie od ostatecznego rozmiaru. Ponieważ pamięć podręczna Ansela działa, wcześniej wyeksportowany obraz w pełnej rozdzielczości wciąż znajduje się w pamięci podręcznej, więc przeliczamy jedynie ostatnie etapy: wyjściowy profil koloru, zmniejszanie rozdzielczości, ditbering. Zajmuje to 0,5 s.

Darktable, jak zwykle, nie zarządza tym, co wcześniej obliczył, więc czeka Cię kolejny ~50 s pełny eksport. Czyli kara wydajnościowa ×100.

Zauważ, że pamięć podręczna potoku Ansela ma mechanizm odśmiecania, który opróżnia wszystkie nieużywane linie pamięci podręcznej starsze niż 5 minut, więc nie zadziałałoby to, gdybyś czekał zbyt długo.

Podsumowanie

Zabawnie jest czytać na forach tech-brosów, takich jak discuss.pixls.us , że ludzie bardziej ufają Darktable niż Anselowi, ponieważ ten pierwszy ma o wiele więcej małp machających rękami w powietrzu i udających, że pracują, co najwyraźniej wystarczy, by dać całkowicie fałszywe poczucie niezawodności i zaufania. To po prostu ostateczny dowód na to, że ludzie nie mają pojęcia, co dzieje się w kodzie. Ale to kapitalistyczna choroba — mylić krzątaninę z pracą, a pracę, która nie tworzy wartości, wciąż można sprzedać jako postęp przy odpowiednim marketingu.

Nie zajmuję się opiniami, zajmuję się testami wydajności. Liczby, które tu opublikowałem, można w całości odtworzyć:

Darktable performance penalty per benchmark case
  • pojedyncze moduły są średnio 1,4 razy szybsze na Ansel CPU w porównaniu z Darktable CPU,
  • pojedyncze moduły są średnio 1,6 razy szybsze na Ansel GPU w porównaniu z Darktable GPU,
  • pełne potoki są od 1,5 raza do 100 razy szybsze na Anselu w porównaniu z Darktable GPU, ponieważ Ansel ma działającą pamięć podręczną,
  • Ansel jest nie tylko szybszy, ale też bardziej energooszczędny, ponieważ mniej rzeczy jest (prze)liczanych.

Wszystko to osiągnięto poprzez usunięcie kodu GUI z kodu renderowania potoku, poprzez uczynienie cyklu życia danych klarowniejszym, prostszym i bardziej odpornym, a nie poprzez dodawanie kolejnej obsługi przypadków szczególnych i obejść. Ansel jest w stanie wykorzystać znacznie więcej pamięci RAM niż Darktable, prowadząc jednocześnie ewidencję niezbędną do tego, by wiedzieć, ile faktycznie jej używa i czy powinien zacząć odzyskiwać pamięć.

Wszystko to jest dowodem na to, że „zespół" Darktable nie ulepsza oprogramowania, że jego (brak) zarządzania aktywnie szkodzi oprogramowaniu. W notatkach do wersji od 2022 roku ogłoszono mnóstwo „optymalizacji". A mimo to ich oprogramowanie wciąż jest boleśnie powolną bestią nieefektywności.

Zauważ, że optymalizacje na poziomie modułów wykonane w Darktable mogą zostać przeniesione do Ansela, choć nie wszystkie z nich wydają się faktycznymi przyspieszeniami na podstawie tego, co tu pokazano. Niektóre już zostały. Ale Darktable nie może przenieść architektury potoku Ansela, z której pochodzi większość naszych przyspieszeń.

W trakcie tej pracy straciłem też wiarę w Vkdt . Vkdt to ponowna próba, podjęta przez założyciela Darktable, Johannesa Hannikę, zbudowania od podstaw oprogramowania do zdjęć RAW, działającego w całości na GPU poprzez Vulkan. Ale przez 4 lata sprzątania cudzego gówna nauczyłem się kilku rzeczy:

  1. Spowolnień Darktable nie należy zrzucać na stos GTK ani na stos OpenCL. To prawda, nie są one świetne, ale da się nimi zarządzać. To błędy programistów i zły projekt, który nie został ponownie oceniony w miarę zmiany potrzeb i wymagań oprogramowania.
  2. Projekt stawiający pamięć podręczną na pierwszym miejscu pozwala ukryć wiele opóźnień poprzez unikanie przeliczeń, a przepływ pracy w całości na GPU wkrótce napotka ograniczenia pamięci GPU.
  3. GPU stają się coraz droższe, po gorączce kryptowalut, a teraz gorączce AI.
  4. To, co zabiło Darktable, to brak zarządzania i brak wizji, kultura „laissez faire" i wesołej przypadkowości, która działa tylko przy garstce programistów, którzy wszyscy się znają. Vkdt zmierza w tym samym kierunku. Jeśli kodowi i funkcjom pozwoli się rosnąć bez priorytetów i zacieśniania API, to pociąg rozbijający się w zwolnionym tempie. „Nowsze = lepsze" to tylko stan przejściowy. Prawdziwym wyzwaniem jest zarządzanie wzrostem.

Z perspektywy programisty Darktable ma 3 główne choroby:

  1. Jego programistom całkowicie brakuje (i od dawna brakowało) zdolności abstrakcji, by poznawczo oddzielić to, co należy do warstwy GUI, od tego, co należy do warstwy zaplecza. W efekcie stany GUI/GTK znajdziesz wtopione głęboko w warstwy zaplecza, co jest tym bardziej irytujące, że darktable-cli działa bez interfejsu graficznego i wymusza obsługę obu trybów. Ale wielokrotnie znajdziesz też problemy GUI rozwiązywane w warstwie zaplecza i na odwrót. Wyzwala to realne problemy z niezawodnością, a na dodatek czyni całość skomplikowaną, nieczytelną i splątaną.
  2. Jego programiści nie rozumieją koncepcji prywatnych API i hermetyzacji: wszystkie struktury danych są publiczne, wszystkie pliki nagłówkowe są importowane w całym oprogramowaniu, więc całe oprogramowanie wie o całym oprogramowaniu i może modyfikować struktury danych w miejscu z dowolnego miejsca w kodzie.
  3. Złożoność jest „rozwiązywana" większą złożonością i nikt najwyraźniej nie widzi w tym problemu zwanego długiem technicznym.

Zdrowe bazy kodu chronią dane, czyniąc je prywatnymi i wymuszając interakcje poprzez funkcje getterów/setterów zdefiniowane jako publiczne API. Pozwala to na łatwe zarządzanie bezpieczeństwem wątków, gdy nadejdzie na to czas, ale pozwala też śledzić, co zapisuje gdzie i kiedy, z centralnego miejsca. A następnie w każdym pliku dołączasz minimalny zestaw API, by jak najbardziej odizolować kod.

Zamiast tego kod źródłowy Darktable zapisuje wszędzie skądkolwiek, bez właściwych interfejsów. Więc jakakolwiek zmiana w bazie kodu Darktable systematycznie wyzwala efekty uboczne i psuje coś nieoczekiwanego na drugim końcu oprogramowania, w przygnębiających sesjach walenia w krety zrodzonych z lenistwa i głupoty.

Tak więc w Anselu wykonano mnóstwo żmudnej pracy, by zacieśnić API, uprywatnić jak najwięcej struktur danych i odizolować biblioteki od siebie nawzajem, aby nieco rozplątać graf zależności.

Aneks: plany

Cała architektura

Oto wysokopoziomowy schemat blokowy całej architektury, od bazy danych po bufory tylne. Teraz możemy wreszcie narysować schemat blokowy tego bałaganu.

flowchart TD
  subgraph Module
    M["Module instance
dt_iop_module_t"] end subgraph Develop H["dev->history
history items"] D["dt_develop_t
history_end
history_hash"] end subgraph Persist["DB + image cache"] DB["SQLite history tables"] IC["image cache
dt_image_t"] end subgraph Pipeline F["Pipe dirty flags"] P["Pipe nodes / pieces"] G["global_hash"] B["Backbuf"] end subgraph Cache C["Pixelpipe cache"] end subgraph Consumers["Backbuffer consumers"] DR["darkroom.c"] NAV["navigation.c"] EXP["imageio.c"] end M -->|"write snapshot
dt_dev_add_history_item_real()"| H H -->|"update history_end / history_hash
dt_dev_add_history_item_real()"| D D -->|"write history
dt_dev_write_history_ext()"| DB D -->|"write image-side hash
dt_dev_write_history_ext()"| IC DB -.->|"read history
dt_dev_read_history_ext()"| H IC -.->|"read image metadata
dt_dev_read_history_ext()"| H H -->|"apply to modules
dt_dev_pop_history_items_ext()"| M D -->|"mark pipes dirty
dt_dev_add_history_item_real()"| F D -->|"check history mismatch
dt_dev_darkroom_pipeline()"| F H -->|"sync history into pipe
dt_dev_pixelpipe_change()"| P F -->|"consume dirty flags
dt_dev_pixelpipe_change()"| P P -->|"compute cache keys
dt_pixelpipe_get_global_hash()"| G G -->|"key cache entries
dt_dev_pixelpipe_process_rec()"| C C -.->|"read exact-hit / input
dt_dev_pixelpipe_process_rec()"| P P -->|"publish stage output
dt_dev_pixelpipe_process_rec()"| C C -->|"promote final output
dt_dev_pixelpipe_process()"| B B -->|"borrow main/preview backbuf
_darkroom_expose()"| DR B -->|"copy preview backbuf
_lib_navigation_draw_callback()"| NAV B -->|"borrow export backbuf
dt_imageio_export_with_flags()"| EXP EXP -->|"update mipmap hash
dt_imageio_export_with_flags()"| IC

Zarządzanie pamięcią RAM/vRAM

Zarządzanie buforami pamięci podręcznej:

flowchart TD
  H["CPU RAM buffer"]
  P["Pinned vRAM buffer
CL_MEM_USE_HOST_PTR"] V["Device-only vRAM buffer"] G["pixelpipe_process_on_GPU()"] C["pixelpipe_process_on_CPU()"] H -->|"prepare GPU input
dt_dev_pixelpipe_cache_prepare_cl_input()"| P P -->|"sync host/device if needed
dt_dev_pixelpipe_cache_sync_cl_buffer()"| G V -->|"reuse existing vRAM input
dt_dev_pixelpipe_cache_prepare_cl_input()"| G G -->|"publish pinned output
dt_dev_pixelpipe_cache_release_cl_buffer()"| P G -->|"publish device-only output
dt_dev_pixelpipe_cache_release_cl_buffer()"| V V -->|"GPU to CPU fallback copy
dt_dev_pixelpipe_cache_restore_cl_buffer()"| H H -->|"resume CPU processing
pixelpipe_process_on_CPU()"| C

Jeśli wyjście modułu jest przewidziane do zapisania w pamięci podręcznej (z powodu preferencji użytkownika, potrzeby tego bufora dla histogramu/próbnika koloru lub gdy następny moduł jest tym aktywnym w GUI), OpenCL używa przypiętego bufora pamięci (przypiętego do pamięci podręcznej RAM). Przypinamy również wejściowe bufory pamięci, gdy GPU pobiera wejście z pamięci podręcznej RAM. Jest to nieco droższe niż użycie czystego bufora wyłącznie po stronie urządzenia, ale tańsze niż skopiowanie tego bufora wyłącznie po stronie urządzenia do hosta. Jeśli wyjście OpenCL nie jest przewidziane do zapisania w pamięci podręcznej, używamy bufora wyłącznie po stronie urządzenia, który może zostać później poddany recyklingowi, ale nigdy nie zostanie skopiowany do RAM.

W przypadku błędu OpenCL dla konkretnego modułu (najbardziej oczywisty: brak pamięci) wejściowy bufor GPU jest w sposób przezroczysty kopiowany z powrotem do pamięci podręcznej RAM przed przejściem awaryjnym do ścieżki czysto CPU.

Wszystkie te bufory są śledzone przez wpisy pamięci podręcznej, które wiążą bufor RAM z buforem vRAM (jeśli używamy obu w sytuacji przypięcia) lub po prostu śledzą, co jest zaalokowane. Jeśli alokacja pamięci GPU się nie powiedzie, najpierw przeglądamy pamięć podręczną, aby zwolnić wszystkie wcześniej zaalokowane bufory. Jeśli alokacja pamięci GPU nadal się nie powiedzie, przechodzimy awaryjnie na CPU.

Wpisy pamięci podręcznej

Pamięć podręczna to tablica mieszająca wpisów pamięci podręcznej. Ich kluczem jest hasz/suma kontrolna. Mamy 2 rodzaje wpisów pamięci podręcznej:

  • wewnętrzne, używane jako bufory wielokrotnego użytku dla wejść/wyjść modułów,
  • zewnętrzne, używane jako jednorazowe bufory dla pośrednich obliczeń modułów.

Wewnętrzne wpisy pamięci podręcznej są kluczowane za pomocą global_hash ich modułu nadrzędnego i można je odnaleźć później (nawet z poziomu GUI), ponieważ ten hasz jest stabilny między uruchomieniami i potokami. Można je przekluczować, jeśli ponownie użyjemy ich bufora pamięci, tak by nowy klucz odpowiadał nowemu stanowi ich modułu nadrzędnego. Te wpisy pamięci podręcznej można zablokować w trybie odczytu lub zapisu w celu bezpiecznego jednoczesnego dostępu między równoległymi wątkami. Blokadę zapisu może przechwycić tylko jeden wątek, jeśli żaden inny wątek nie czyta; blokadę odczytu może przechwycić kilka wątków, jeśli żaden inny wątek nie zapisuje. Ponieważ blokada zapisu jest zwalniana przez moduł zapisujący swoje wyjście wcześniej, niż blokada odczytu jest przejmowana przez moduł używający tego samego wpisu pamięci podręcznej jako swojego wejścia, a pomiędzy nimi istnieje ryzyko usunięcia, mają one również mechanizm refcount: producent wpisu pamięci podręcznej go inkrementuje, konsument dekrementuje. Tego samego mechanizmu używa ciemnia, aby pobrać bufor tylny bez kopiowania.

Zewnętrzne wpisy pamięci podręcznej są kluczowane za pomocą adresu pamięci ich bufora danych. Mają być krótkotrwałe.

Dodawanie/usuwanie wpisów pamięci podręcznej automatycznie inkrementuje/dekrementuje globalny rozmiar pamięci podręcznej. Dodajemy nowe wpisy tak długo, jak globalny rozmiar pamięci podręcznej jest poniżej dozwolonego progu, po czym zaczynamy niszczyć najstarsze wpisy, jeśli ich refcount wynosi 0 i nie są zablokowane.

flowchart TD
  A["absent"]
  B["reserved for write
refcount + 1
write-locked"] C["published
exact-hit eligible"] D["borrowed by consumer
refcount > 0"] E["backbuf-held
one display ref kept"] F["auto-destroy flagged"] G["removed / freed"] A -->|"create new entry
dt_dev_pixelpipe_cache_get()
dt_dev_pixelpipe_cache_get_writable()"| B C -->|"rekey reusable entry
dt_dev_pixelpipe_cache_get_writable()
dt_dev_pixelpipe_cache_rekey()"| B B -->|"backend finishes and unlocks
dt_dev_pixelpipe_process_rec()"| C C -->|"exact-hit / reopen / GUI read-only
dt_dev_pixelpipe_cache_peek()
dt_dev_pixelpipe_cache_get_read_only()"| D D -->|"release consumer ref
dt_dev_pixelpipe_cache_unref_hash()
dt_dev_pixelpipe_cache_close_read_only()"| C C -->|"keep final output alive for display
_update_backbuf_cache_reference()"| E E -->|"backbuf changes or invalidates
_update_backbuf_cache_reference()"| C C -->|"mark transient output
dt_dev_pixelpipe_cache_flag_auto_destroy()"| F F -->|"reap when no ref and no lock remain
dt_dev_pixelpipe_cache_auto_destroy_apply()"| G C -->|"explicit remove / GC / LRU
dt_dev_pixelpipe_cache_remove()
dt_dev_pixelpipe_cache_flush_old()
dt_dev_pixel_pipe_cache_remove_lru()"| G C -->|"invalid payload detected
dt_dev_pixelpipe_cache_peek()"| G

Wpis pamięci podręcznej nie zna rodzaju danych, które przechowuje. Zna jedynie adresy buforów, ich rozmiar, ich refcount oraz ich stan blokady. Jest więc ogólną abstrakcją rzeczy zawartych w pamięci.


Translated from English by : Claude. In case of conflict, inconsistency or error, the English version shall prevail.