Dostęp do zmiennych klasy - problem z szybkością

Dostęp do zmiennych klasy - problem z szybkością
CR
  • Rejestracja:ponad 16 lat
  • Ostatnio:11 miesięcy
0

Witam.

Mam funkcję, która kilkaset razy na sekundę przerysowuje bufor obrazka do bufora okna. Zanim obrazek zostanie przerysowany, potrzebne jest sprawdzenie jego położenia względem okna i ewentualne odcięcie tego co wystaje poza krawędzie (tzw. clipping). Początkowo robiłem to przy każdym rysowaniu, a potem uświadomiłem sobie, że przecież wystarczy to robić tylko wtedy, gdy zmienia się położenie obrazka względem okna, w innym wypadku wystarczy po prostu wykorzystywać dane z poprzedniego clippingu, co w teorii powinno dać o wiele lepszą wydajność (bo to jednak sporo mniej kalkulacji). Dorzuciłem więc do klasy obrazka dodatkowe zmienne określające ramy bufora, które należy przerysować (wcześniej były one zmiennymi lokalnymi, alokowanymi i aktualizowanymi przy każdym przerysowywaniu obrazu). Efekt? Szybkość rysowania spadła o połowę (z 600 klatek na sekundę do ok. 300)!

Ja wnioskuję, jest to różnica wynikająca z szybkości dostępu do zmiennych, bo pola klasy są przechowywanie na stercie (heap), a zmienne lokalne na stosie (stack), ale czy mogę coś w tym względzie zaradzić bez odwoływania się do pisania wstawki w ASM?

edytowany 2x, ostatnio: Crow
AK
  • Rejestracja:ponad 6 lat
  • Ostatnio:około 12 godzin
  • Postów:3561
0

Szczerze, to BARDZO wątpię w diagnozę - że winny heap itd.
Jak ci się wydaje ten heap to gdzie jest, na Hawajach?

nawiasem mówiąc od wczoraj jest wysyp obwiniania kompilatorów, providerów (dla aplikacji webowych), fabrycznych kontenerów, KLASY itd.

Nieoficjalnie by obstawiał, że coś w kodzie jest nieoptymalne, a tego skąpisz


Bo C to najlepszy język, każdy uczeń ci to powie
edytowany 1x, ostatnio: AnyKtokolwiek
CR
  • Rejestracja:ponad 16 lat
  • Ostatnio:11 miesięcy
1

@AnyKtokolwiek:

Jest i kod :).

Kopiuj
void Image::Draw(int iX, int iY)
	{
		if (iX + Data.Width < 0 || iY + Data.Height < 0 || iX >= Parent.Resolution.Render.Width || iY >= Parent.Resolution.Render.Height) return;
		else
		{
			int* In, * Out, * In_RowEnd, * In_ColumnEnd;
			int In_RowComp, Out_RowComp;

			if (iX >= 0)
			{
				if (iY >= 0)
				{
					In = Data.Bits;
					Out = &Parent.Buffer.Color[(iY * Parent.Resolution.Render.Width) + iX];
				}
				else
				{
					In = &Data.Bits[-iY * Data.Width];
					Out = &Parent.Buffer.Color[iX];
				}

				if (Data.Width + iX < Parent.Resolution.Render.Width)
				{
					In_RowEnd = In + Data.Width;
					In_RowComp = 0;
					Out_RowComp = Parent.Resolution.Render.Width - Data.Width;
				}
				else
				{
					In_RowEnd = In + (Parent.Resolution.Render.Width - iX);
					In_RowComp = Data.Width - (Parent.Resolution.Render.Width - iX);
					Out_RowComp = iX;
				}	
			}
			else
			{
				if (iY >= 0)
				{
					In = &Data.Bits[-iX];
					Out = &Parent.Buffer.Color[iY * Parent.Resolution.Render.Width];
				}
				else
				{
					In = &Data.Bits[(-iY * Data.Width) - iX];
					Out = Parent.Buffer.Color;
				}

				if (Data.Width + iX < Parent.Resolution.Render.Width)
				{
					In_RowEnd = In + (Data.Width + iX);
					In_RowComp = -iX;
					Out_RowComp = Parent.Resolution.Render.Width - (Data.Width + iX);
				}
				else
				{
					In_RowEnd = In + Parent.Resolution.Render.Width;
					In_RowComp = Data.Width - Parent.Resolution.Render.Width;
					Out_RowComp = 0;
				}
			}

			In_ColumnEnd = iY + Data.Height < Parent.Resolution.Render.Height ? &Data.Bits[Data.Size] : &Data.Bits[Data.Width * (Parent.Resolution.Render.Height - iY)];

			for (; In < In_ColumnEnd; In += In_RowComp, Out += Out_RowComp, In_RowEnd += Data.Width)
			{
				for (; In < In_RowEnd; In++, Out++) *Out = *In;
			}
		}
	}

//Data.Bits to tablica intów zawierająca piksele (bajty) obrazka
//Parent.Buffer.Color to tablica intów zawierająca piksele okna
//Data.Width, Data.Height i Data.Size to odpowiednio szerokość, wysokość obrazka, oraz ich iloczyn
//Parent.Resolution.Render.Width i Parent.Resolution.Render.Height to wysokość i szerokość okna

W zasadzie wystarczy zmienne z linii 6 i 7 przenieść do pól klasy, by ilość klatek spadła o połowę (mimo, że reszta kodu zostaje bez zmian).

edytowany 1x, ostatnio: Crow
enedil
  • Rejestracja:ponad 11 lat
  • Ostatnio:4 dni
  • Postów:1027
0

pola klasy są przechowywanie na stercie (heap), a zmienne lokalne na stosie (stack)

Tylko jeśli sam obiekt klasy siedzi w heapie (albo tak zaprojektujesz klasę). Ogólnie przydałoby się, żebyś pokazał(a) cały kod, oraz takie rzeczy jak metodę benchmarkowania.

edytowany 1x, ostatnio: enedil
AK
  • Rejestracja:ponad 6 lat
  • Ostatnio:około 12 godzin
  • Postów:3561
0
Crow napisał(a):

@AnyKtokolwiek:

Jest i kod :).

W zasadzie wystarczy zmienne z linii 6 i 7 przenieść do pól klasy, by ilość klatek spadła o połowę (mimo, że reszta kodu zostaje bez zmian).

Oryginalne stwierdzenie.
Rozwinięcie maszynowe nie różni się więcej niż o nanosekundy (inny rejestr bazowy pracuje).


Bo C to najlepszy język, każdy uczeń ci to powie
CR
Oryginalne czy nie, tak właśnie się dzieje.
AK
Co to za framework / środowisko?
AK
ps. a kod nie jest CAŁY
CR
Kod już dodałem. A framework żaden, to mój własny silnik (game engine), napisany od zera, z użyciem jedynie bazowych bibliotek windowsowych.
_13th_Dragon
  • Rejestracja:ponad 19 lat
  • Ostatnio:2 miesiące
0
Crow napisał(a):

Mam funkcję, która kilkaset razy na sekundę przerysowuje bufor obrazka do bufora okna...

Po kiego tak często?
Telewizja 4k ma tylko 100Hz = 100 klatek na sekundę.


Wykonuję programy na zamówienie, pisać na Priv.
Asm/C/C++/Pascal/Delphi/Java/C#/PHP/JS oraz inne języki.
CR
Bo to silnik (game engine), który wypluwa tyle klatek na sekundę, ile zdoła. To daje mi dobre pojęcie o szybkości działania poszczególnych partii kodu.
mwl4
@Crow: użyj frame profilera żeby się takich rzeczy dowiedzieć ;) Na przykład: Tracy https://github.com/wolfpld/tracy
CR
  • Rejestracja:ponad 16 lat
  • Ostatnio:11 miesięcy
0

@enedil:

Kopiuj
class Image
{
private:
	struct BitmapData //To jest przerobiona wersja windowsowego structa BITMAP, którego trzeba do GetObject() - dodałem 2 ostatnie wiersze
	{
		long Type;
		long Width;
		long Height;
		long BytesPerScanLine;
		long Planes;
		long BitsPerPixel;
		int* Bits = nullptr;
		int Size;
	};
	Display& Parent; //Display to referencja do klasy okna, z której uzyskuję wymiary, dostęp do bufora itd.
	BitmapData Data = { 0 };
	int* In, * Out, * In_RowEnd, * In_ColumnEnd; //to właśnie te testowane pola klasy
	int In_RowComp, Out_RowComp; //to właśnie te testowane pola klasy
public:
	Image(Display& cDisplay);
	void Draw(int iX, int iY);
	void LoadFromFile(LPCWSTR lpcwstrFilePath);
	virtual ~Image();
};

Image::Image(Display& cDisplay) : Parent(cDisplay){}

void Image::LoadFromFile(LPCWSTR lpcwstrFilePath)
{
	if (HBITMAP Source = static_cast<HBITMAP>(LoadImageW(NULL, lpcwstrFilePath, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE)))
	{
		GetObject(Source, sizeof(BITMAP), &Data);
		Data.Size = Data.Width * Data.Height;

		BITMAPINFO BitmapInfo;
		BitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
		BitmapInfo.bmiHeader.biWidth = Data.Width;
		BitmapInfo.bmiHeader.biHeight = -Data.Height;
		BitmapInfo.bmiHeader.biPlanes = 1;
		BitmapInfo.bmiHeader.biBitCount = 32;
		BitmapInfo.bmiHeader.biCompression = BI_RGB;

		Data.Bits = new int[Data.Size];
		if (!GetDIBits(Parent.WindowDC, Source, 0, Data.Height, Data.Bits, &BitmapInfo, DIB_RGB_COLORS))
		{
			delete[] Data.Bits;
			std::cout << "ERROR! Could not read bits" << std::endl;
		}

		DeleteObject(Source);
	}
	else std::cout << "ERROR! Could not load bitmap" << std::endl;
}

Draw() już było.
 
A metoda benchmarkowania wygląda tak:

int FPS;
std::chrono::steady_clock::time_point Second = std::chrono::steady_clock::now();
std::chrono::steady_clock::time_point Before = Second;
std::chrono::steady_clock::time_point After;
std::wstring Caption;

while (!Terminated) //to obsługuje osobny wątek
{
	After = std::chrono::steady_clock::now();
	Before = After;

	//Tutaj kod robiący różne rzeczy w ramach renderowania klatki, odświeżania logiki itd.

	if ((std::chrono::duration_cast<std::chrono::seconds> (After - Second)).count())
	{
		Second = After;
		Caption = Display.Caption + L" | FPS: " + std::to_wstring((FPS)); //Liczba FPS wyświetlana na belce okna
		SetWindowTextW(Display.Handle, Caption.c_str()); //Display.Handle to windowsowy uchwyt okna
		FPS = 1;
	}
	else FPS++;
}
edytowany 4x, ostatnio: Crow
Miang
a pokaż może ten Draw() w nowej wersji
_13th_Dragon
  • Rejestracja:ponad 19 lat
  • Ostatnio:2 miesiące
1

Z tego co widzę w strasznej pętli przepisujesz pixel po pixel'u.
Doprawdy myślisz że jesteś w stanie konkurować z producentami kart graficznych?
Rysowanie jednego obrazku na drugim pozostaw systemowi, ewentualnie użyj CUDA i zmuś do tego kartę graficzną (ale i tak nie osiągniesz prędkości karty graficznej).

Skoro i tak używasz windowsowych poleceń to użyj tego:
https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-bitblt


Wykonuję programy na zamówienie, pisać na Priv.
Asm/C/C++/Pascal/Delphi/Java/C#/PHP/JS oraz inne języki.
edytowany 1x, ostatnio: _13th_Dragon
CR
Nie próbuję. To nie jest kod komercyjny, tylko hobbystyczny i edukacyjny, a moim celem jest sklecenie prostego silnika 2D i 3D opartego o renderowanie software'owe.
_13th_Dragon
Skoro celem jest masturbacja, to rób to w zaciszu domowym, nie publicznie.
Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około godziny
4
Crow napisał(a):

Ja wnioskuję, jest to różnica wynikająca z szybkości dostępu do zmiennych, bo pola klasy są przechowywanie na stercie (heap), a zmienne lokalne na stosie (stack), ale czy mogę coś w tym względzie zaradzić bez odwoływania się do pisania wstawki w ASM?

(...)

Crow napisał(a):

W zasadzie wystarczy zmienne z linii 6 i 7 przenieść do pól klasy, by ilość klatek spadła o połowę (mimo, że reszta kodu zostaje bez zmian).

Moim zdaniem różnica nie wynika z położenia sterta vs stos, a raczej zmienne lokalne vs obiekt. Możesz to zweryfikować alokując obiekt na stosie gdzieś na początku programu i posługując się cały czas wskaźnikiem do niego.

Kompilator C/ C++ musi przestrzegać reguł aliasowania wskaźników. Reguły te można wyłączyć słowem kluczowym restrict:
https://en.wikipedia.org/wiki/Restrict
https://en.cppreference.com/w/c/language/restrict

Moje rozumowanie jest takie:

  • zmienne lokalne są tworzone w ramce stosu przeznaczonej dla obecnego wywołania obecnie wykonywanej funkcji i kończą żywot wraz z wyjściem z funkcji
    • z tego powodu nikt nie mógł przed aktualnym wywołaniem funkcji pobrać w legalny sposób adresu tych zmiennych
    • jeżeli w samej funkcji też nie ma adresowania tych zmiennych lokalnych to można założyć, że nie ma do nich wskaźników w ogóle
    • skoro wiadomo, że nie ma do nich legalnych wskaźników to można je traktować tak jak pamięć adresowaną wskaźnikami z restrict
  • w przypadku wrzucenia zmiennych do obiektu możemy mieć taką sytuację, że któryś ze wskaźników int* In, * Out, * In_RowEnd, * In_ColumnEnd; może wskazywać na któreś pole obiektu int In_RowComp, Out_RowComp;
    • kompilator nie może zastosować optymalizacji, które wpłynęłyby na semantykę takiego przypadku i dlatego ma związane ręce
    • ma związane ręce to znaczy, że musi ciągle zapisywać zmiany w polach obiektu i nie może zmieniać kolejności ładowania, modyfikacji i zapisu danych (patrz: przykłady np w wikipedii dotyczące słówka restrict)
  • prawdopodobnie wskaźnik this nie jest restrict, więc spróbuj zamienić metodę void Image::Draw(int iX, int iY) na statyczną, a jako pierwszy parametr dać wskaźnik (z modyfikatorem restrict) do obiektu typu Image
    • ponadto wskaźniki w obiekcie Image też mają mieć modyfikator restrict

Nie polecam zabawy w restrict bo nadużywając można strzelić sobie w stopę jeszcze łatwiej niż bez niego. Zamiast tego polecam skopiować pola obiektu do zmiennych lokalnych na wejściu do metody void Image::Draw(int iX, int iY), a przed wyjściem skopiować je z powrotem ze zmiennych lokalnych do obiektu.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 1x, ostatnio: Wibowit
Zobacz pozostałe 10 komentarzy
CR
Dokładnie int* In, * Out, * In_RowEnd, * In_ColumnEnd; int In_RowComp, Out_RowComp jest wszystkim, czego funkcji rysującej potrzeba do przepisania bufora obrazu do bufora okna i to właśnie one są przechowywanie w polach klasy (co zresztą widać w kodzie u góry). Metoda jest wywoływana tyle razy, ile zdoła wycisnąć procesor (powiedzmy od 200 do 1000 razy na sekundę). Z memcpy mogę próbować, ale to i tak co najwyżej wiersze będę mógł przenosić, bo całego bloku pamięci za jednym razem to nie da rady - bufor okna jest większy od bufora obrazu, a poza tym clipping.
enedil
@Crow a jeszcze lepiej std::copy, które to umie np. użyć informację o tym, że dane które kopiujesz mają już jakiś alignment
Wibowit
Jeżeli void Image::Draw(int iX, int iY) jest wywoływana do tysiąca razy na sekundę to w ogóle nie opłaca się optymalizować tych ifów do liczenia wspomnianych pól int* In, * Out, * In_RowEnd, * In_ColumnEnd; int In_RowComp, Out_RowComp. Na moje oko te ify w pojedynczym wywołaniu metody wykonują się w ułamek mikrosekundy. Pomnożone razy tysiąc daje to ułamek mlilsekundy (przeliczania tych ifów) na sekundę działania programu. Inaczej mówiąc ma to prawdopodobnie znikomy wpływ na szybkość programu i stąd brak różnic w wydajności przy spamiętywaniu ostatnich wyników.
Wibowit
Skup się na zastąpieniu for (; In < In_RowEnd; In++, Out++) *Out = *In; czymś szybszym (wbudowanym w język czy bibliotekę, wypróbuj to std::copy wspomniane przez @enedil), bo prawdopodobnie tutaj jest zużywane zdecydowanie najwięcej czasu procesora.
CR
Tak zrobię, potestuję i dam znać, czy cokolwiek się zmieniło. Póki co, dzięki za pomoc :).
AT
  • Rejestracja:ponad 9 lat
  • Ostatnio:17 dni
  • Postów:24
1

Generalnie do analizy takich problemów używa się narzędzi do profilowania, np. intelowego VTune'a, ktory z tego co wiem jest darmowy. Bez tego to jest zgadywanie.

MarekR22
Moderator C/C++
  • Rejestracja:około 17 lat
  • Ostatnio:3 minuty
0
Crow napisał(a):

Witam.

Efekt? Szybkość rysowania spadła o połowę (z 600 klatek na sekundę do ok. 300)!

Literówka, czy masz super szybki monitor?


Jeśli chcesz pomocy, NIE pisz na priva, ale zadaj dobre pytanie na forum.
edytowany 1x, ostatnio: MarekR22
Wibowit
zapewne brak synchronizacji i tyle
MarekR22
bardziej chodzi mi o to, że to wskazuje, że nie ma problemu.
Wibowit
Tu nie chodzi o to, czy program jest wystarczająco szybki tylko dlaczego zwolnił. Patrz: https://4programmers.net/Forum/C_i_C++/349457-dostep_do_zmiennych_klasy_problem_z_szybkoscia?p=1746735#comment-677396 (sry, zły link wkleiłem wcześniej)
CR
Może wyraziłem się nieprecyzyjnie, chodziło mi o to, że przed zmianą osiągałem 600 FPS, po zmianie 300, czyli rysowanie zajmowało 2x dłużej. I nie chodzi o szybkość wyświetlania, tylko renderowania.
Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
3

@Crow moja rada to zacząć od profilera a nie od prób optymalizacji na ślepo. Bo pomijając kwestie optymalizacji kompilatora które twoje zmiany mogą popsuć, albo np. problemów z obiektami które są większe niż linia cache, to może być tak że optymalizujesz kawałek kodu który zabiera 1% czasu zamiast skupić się na tym co zabiera go 99%.


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
edytowany 1x, ostatnio: Shalom
AK
  • Rejestracja:ponad 6 lat
  • Ostatnio:około 12 godzin
  • Postów:3561
2

Widzę, ze dużo szczegółów padło

  • co może i prawdopodobnie będzie zoptymalizowane
  • żeby nie używać "ręcznego" kodu
  • profilowanie, a nie domysły

Dodam, do gorącej herbaty, że nie ma zamienności zmiennych w klasie i zmiennych lokalnych w metodzie. Powiem jeszcze bardziej, nie ma zmiennych w klasie, a są pola.

To trzeba sobie mocno w głowie oddzielić. Pola są informacją o wiele bardziej trwałą, niż doraźne zmienne.
Po rozdzieleniu w głowie j/w drugi temat: nazwy. O ile ix jest dla mnie 'spoko' nazwą zmiennej lokalnej o zakresie 3 linii, to fatalną nazwą pola (czy zmiennej o dużym zakresie).
Źle użyta nazwa ma razić, nie dać spać itd i nakłonić do refaktoringu.

Tak się składa, że w 95% przypadków dążenie do czytelnego, stylowego kodu polepsza optymalizację (pozostałe 5% to dodanie poziomu abstrakcji, może nieco osłabić szybkosć).
Na gruncie tego wątku: najbardziej lokalny charakter zmiennych, jaki jest możliwy, jest czynnością ku dobremu stylowi, i daje kompilatorowi okazję optymalizacji. Współczesny CPU ma więcej rejestrów niż dawniej, ale dalej to skończona ilość, niech tam pracują zmienne najbardziej lokalne.

Dodam, że arytmetyka wskaźnikowa to przypuszczalnie była szybsza od indeksowej (tablicowej), ale to było trzydzieści lat temu.

Kopiuj
for (; In < In_RowEnd; In++, Out++) *Out = *In;

to nie tylko można zastąpić funkcją z karty graficznej, ale memcpy czy czymś, co się zinlinuje.
Po (drugie??? chyba już siedemnasta myśl), wątpię, by porównanie końca zakresu na wskaźnikach było szybsze niż na integerach integerze (jednym), chętnie o mniejszym zakresie (i zmienne silnie lokalne).
Dwa długie inkrementy, zamiast jednego (krótkiego) itd Ten zapis ma jeden cel: czuć się jak hacker.
Gdybym miał pisac ręcznie, bym dał:

Kopiuj
for(short i=0; i< row_length; i++) a[i] = b[i];

Z drobiazgów, które są pro-stylowe, a dają kompilatorowi szanse do wykazania się, są const i constexpr

@Wibowit wolał bym, zebyś wycofał się z porady

prawdopodobnie wskaźnik this nie jest restrict, więc spróbuj zamienić metodę void Image::Draw(int iX, int iY) na statyczną,

Ta klasa ma przed sobą DUŻY zakres analizy, co jest polem, jakim jest polem itd, niech to najpierw się stanie. W sumie, to nie ma pewności czy klasa to naprawdę Image, czy coś innego (ImageBuffer ?) a metoda to Draw. Na pewno w obecnym kształcie nie jest to klasa, która da się użyć w grze, poza testem wydajnościowym - tu jeszcze wiele będzie się działo


Bo C to najlepszy język, każdy uczeń ci to powie
edytowany 7x, ostatnio: AnyKtokolwiek
Wibowit
@Wibowit wolał bym, zebyś wycofał się z porady - w ostatnim akapicie odradzam i polecam alternatywne rozwiązanie. Na końcu z obliczeń i tak wychodzi na to, że spamiętywanie wyników tej drabinki ifów i tak nie ma sensu, bo narzut, który tworzy jest rzędu pojedynczego promila, a więc to zdecydowanie nie jest ten kawałek kodu, który jest sens optymalizować najpierw.
CR
A co jest złego w nazwie iX? :). Ja akurat też nie lubię "przypadkowych" nazw zmiennych typu a, b, v, cb itd (a np. na Rosetta Code widzę to nagminnie). Lubię nazywać swoje zmienne tak, bym nawet miesiąc później wiedział co robią, bez analizowania kodu linia po linii. Dlatego właśnie mam In_RowComp (odnoszące się do kompensacji tej części wiersza, która nie była rysowana, ale musiała zostać "przeskoczona", by adresy nowych wierszy się zgadzały), a nie np. IRC. A iX to po prostu int oraz współrzędna X, powszechnie używana przy określaniu położenia w przestrzeni :).
MarekR22
Moderator C/C++
  • Rejestracja:około 17 lat
  • Ostatnio:3 minuty
2

A ja powiem tak:
Analizowanie przyczyn spadku wydajności jedynie na podstawie kodu (gdy złożoność się nie zmieniła), jest trudne i wymaga ogromnej ilości doświadczenia. Uzyskanie informacji na forum internetowym lub SO graniczy z cudem (z powodu trudności przekazania wszystkich ważnych informacji i trudności ich interpretowania).
Rozważanie wstawek assemblerowych nie ma sensu, bo kompilator potrafi cuda (polecam dowolne filmami z Matt Godbolt, np to).

Fajnie, że robisz jakieś pomiary, ale początkujący często robią błędy, które są trudne do zauważenia, a zakłócające wyniki:

  • uruchamiania testów na build debug (brak optymalizacji kompilatora). Jak pisałem wcześniej, kompilator potrafi cuda, brak właściwych ustawień kompilatora będzie dawał dramatycznie inne wyniki. Przypuszczam, że to jest twój przypadek, bo: nie wspominałeś słowem (jakie masz ustawiania), a opisywany przez ciebie objaw (dwukrotny spadek wydajności, bo zmienne zostały przeniesione, z lokalnych do klasy), jest typowy dla braku optymalizacji i to jest najlepsze wyjaśnienie tego co obserwujesz
  • pomiary wykonywane zegarem o słabej rozdzielczości (raczej to nie jest problem)
  • kod testowy mierzący wydajność jest źle zbudowany i optymalizator usunął to co najistotniejsze.

Jeśli chcesz pomocy, NIE pisz na priva, ale zadaj dobre pytanie na forum.
edytowany 3x, ostatnio: MarekR22
CR
  • Rejestracja:ponad 16 lat
  • Ostatnio:11 miesięcy
0

Dziękuję wszystkim za bardzo sensowne porady!
Troszkę przerobiłem kod i obecnie wygląda tak (póki co trzymam się clippingu przy każdym rysowaniu bo nie wydaje się mieć specjalnego wpływu na wydajność, przynajmniej w porównaniu do kopiowania danych na wejściu):

Kopiuj
void Image::Draw(int iX, int iY)
{
	if (iX + Data.Width < 0 || iY + Data.Height < 0 || iX >= Parent.Resolution.Render.Width || iY >= Parent.Resolution.Render.Height) return;
	else
	{
		int* In, * Out, * In_ColumnEnd;
		int Width;

		if (iX >= 0)
		{
			if (iY >= 0)
			{
				In = Data.Bits;
				Out = &Parent.Buffer.Color[(iY * Parent.Resolution.Render.Width) + iX];
			}
			else
			{
				In = &Data.Bits[-iY * Data.Width];
				Out = &Parent.Buffer.Color[iX];
			}

			Width = (Data.Width + iX < Parent.Resolution.Render.Width) ? Data.Width * sizeof(*In) : (Parent.Resolution.Render.Width - iX) * sizeof(*In);
		}
		else
		{
			if (iY >= 0)
			{
				In = &Data.Bits[-iX];
				Out = &Parent.Buffer.Color[iY * Parent.Resolution.Render.Width];
			}
			else
			{
				In = &Data.Bits[(-iY * Data.Width) - iX];
				Out = Parent.Buffer.Color;
			}

			Width = (Data.Width + iX < Parent.Resolution.Render.Width) ? (Data.Width + iX) * sizeof(*In) : Parent.Resolution.Render.Width * sizeof(*In);
		}

		In_ColumnEnd = iY + Data.Height < Parent.Resolution.Render.Height ? &Data.Bits[Data.Size] : &Data.Bits[Data.Width * (Parent.Resolution.Render.Height - iY)];

		for (; In < In_ColumnEnd; In += Data.Width, Out += Parent.Resolution.Render.Width) std::memcpy(Out, In, Width);
	}
}

Czyli zamieniłem iterowanie po jednym int, na kopiowanie całych wierszy przy użyciu memcpy. Uzyskałem dzięki temu przyrost klatek z 600 do ok. 850 czyli w zasadzie sukces. Wczoraj jednak @enedil zasugerował użycie std::copy, które ponoć ma jakiś "wbudowany" sposób na odpowiedni aligment danych, przez co mógłbym kopiować jeszcze wydajniej niż wiersz po wierszu. Przepatrzyłem jednak dokumentację std::copy i niczego takiego nie znalazłem, ktoś coś może podpowiedzieć? :).

edytowany 3x, ostatnio: Crow
Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około godziny
1

Wczoraj jednak @enedil zasugerował użycie std::copy, które ponoć ma jakiś "wbudowany" sposób na odpowiedni aligment danych, przez co mógłbym kopiować jeszcze wydajniej niż wiersz po wierszu. Przepatrzyłem jednak dokumentację std::copy i niczego takiego nie znalazłem, ktoś coś może podpowiedzieć? :).

Nie chodzi o kopiowanie wydajniej niż wiersz po wierszu (bo do tego chyba nie ma gotowca w bibliotece standardowej), a o ten alignment, czyli wyrównanie wskaźników. Nie wiem na ile to wyrównanie wskaźników miałoby na coś wpłynąć (to i tak trzeba sprawdzić zarówno w memcpy jak i std::copy, nie widzę jakiegoś wymogu by wskaźniki przekazywane do std::copy miały być wyrównane). memcpy raczej ma wyifowane wszystko co trzeba, co najwyżej std::copy wymaga mniej ifów w implementacji. Popatrz jakie memcpy może być zakręcone: https://github.com/bminor/glibc/blob/master/sysdeps/x86_64/multiarch/memcpy-ssse3.S


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
enedil
chodzi o to, że std::copy jest szablonowe, więc jeśli operujemy na np. wskaźnikach na inta, i robimy std::copy, to mamy gwarancję, że wskaźnik na inta ma wyrównanie przynajmniej 4. Jako, że to jest szablon, to w wynikowej binarce pojawi się instancja std::copy specjalnie zrobiona dla intów.
enedil
aczkolwiek jak teraz patrzę, to chyba przynajmniej w takim glibc, nic takiego nie jest robione
Wibowit
to memcpy które podałem i tak kopiuje wektorami 128-bitowymi, więc wyrównanie do 32-bitów chyba nie wystarczy
several
  • Rejestracja:ponad 15 lat
  • Ostatnio:około 3 godziny
0

W zasadzie wystarczy zmienne z linii 6 i 7 przenieść do pól klasy, by ilość klatek spadła o połowę (mimo, że reszta kodu zostaje bez zmian).

@Crow No i co w tym dziwnego? Metody mają zewnętrzny storage (w sensie inny niż pola klasy/struktury), więc jeśli odwołujesz się do pól odwołujesz się do innego miejsca w pamięci, którego najprawdopodobniej nie ma w cache. To i tak nieźle, że spadło Ci tylko o połowę. Przekazanie wskaźników jako argumenty powinno być tak samo szybkie jak typowo lokalne, ale nie czytałem Twojego kodu więc gwarancji nie daje.

Jeśli chcesz się czegoś nauczyć w tym temacie to szukaj materiałów związanych z Data Oriented Design.


edytowany 5x, ostatnio: several
CR
Sprawdzę, dzięki!
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)