Zrównoleglanie pętli przy użyciu nowszych standardów

0

Takie pytanie, czy c++11/14/17 ma w sobie jakieś "for parallel" jak OpenMP czy muszę samemu zrównoleglać pętle?

Wiem, że jest std::par, ale jakoś nie działa zawsze tak jak bym chciał, bo wątki nie dzielą się po kilka części i każdy robi swoje jak np mam 100elementowego vectora, używam std::for_each i te wątki wykonują kod trochę chaotycznie, chciałbym zaś aby podzieliły się np w ten sposób:
0-24,
25-49,
50-74,
75-99. Jezeli się da to poratujcie a jak nie to pobawie się <thread> i <mutex> na spokojnie.

M.in tego typu pętlę chciałbym rozłożyć na kilka wątków, bo dla np siatki kwadratów 50x50 kod działa mi na ok 12fpsów.

sf::RenderWindow window(sf::VideoMode(400, 400), "Game");
std::vector<Cell> cells;
// główna pętla itd

		for (auto &x : cells) {
			window.draw(x);
}
6

Wielowątkowość to nie złoty młotek na wszystkie problemy z wydajnością!
Często ludzie wciskają ją na siłę i uzyskują efekt odwrotny (i nawet tego nie zauważają, bo nie robią obiektywnych pomiarów wydajności).
window.draw(x); oznacza akcję UI, króra operuje na zasobach, które są nie synchronizowane.
Wszystkie znane mi frameworki UI mają wyraźny zakaz używania wielowątkowości w operacjach związanych bezpośrednio z UI.

Ergo zapomnij o wielowątkowości.
Polecam użyć jakiegokolwiek pofilera (bardzo proste w użyciu narzędzie), zlokalizowanie wąskiego gardła aplikacji i poprawienie kodu właściwym miejscu.

1

Coś mi mówi, że wiem, na jaki przedmiot będzie ten projekt i co to za cells ( ͡° ͜ʖ ͡°)

Odpuść zrównoleglanie rysowania komórek w UI. Nie wiem jak w SFML, ale w innych frameworkach dało się co najwyżej co-nieco ugrać rysując piksele bezpośrednio na canvas, zamiast zarządzać nimi jako jakimiś obiektami Rectangle itp - omijasz narzuty, a rysowanie kwadracika raczej nie jest trudne :p

Jak już chcesz zrównoleglić, to przyjrzyj się wyznaczaniu nowego stanu - jest niemalże zawstydzająco równoległe, potrzebujesz tylko bariery między poszczególnymi krokami, a jak przyjdą bardziej skomplikowane reguły przejścia i sąsiedztwa przy rozroście ziaren to może zamulać. Z drugiej strony - jak przyjdzie Ci robić do tego Monte-Carlo i wjedzie aktualizacja stanu in-place to przestanie być takie łatwe do zrównoleglenia.

1

Jeżeli chcesz przyśpieszyć rysowanie/renderowanie prostokątów, to pomyśl o zastosowaniu bibliotek OpenGL lub DirectX, gdzie wszystkie operacje związane z rysowaniem optymalizowane są pod kątem przetwarzania równoległego za pomocą GPU. W praktyce oznacza to że rysowanie 1 miliona prostokątów może odbywać się
jednocześnie aż w 57 344 osobnych wątkach, oczywiście jeżeli dysponuje się kartą GeForce GTX 1080T :)

0

Trochę wracając do tematu po miesiącu czasu, dzięki za radę z profilerem. Okazało się, że głównym pożeraczem fps-ów było bezsensowne odwoływanie się do niektórych sfml-owskich metod oraz ogólną algorytmiką, trochę też dało przesyłanie i odbieranie obiektów jako "const &". Samo zrównoleglenie coś tam dało, ale nie jakoś szczególnie dużo, użyłem do tego execution::par z c++17, Teraz przy planszy 50x50 zamiast 12fps-ów mam 90-130 co jest jak dla mnie dużym skokiem. Oczywiście mówię tu cały czas o wersji Release z Visual Studio
Teraz kiedy chciałem wrzucić jakiś duży pattern z GameOfLife to na planszy 170x170 już zaczęło mocno zamulać i mam tak 10-20fpsów. Profiler wskazuje, że głównym pożeraczem jest "window.draw(white)/(black)", zajmuje on właśnie 99% wszystkiego:

void Board::display(sf::RectangleShape& white, sf::RectangleShape& black, sf::RenderWindow& window) {
	std::for_each(std::execution::seq, cells.begin(), cells.end(), [&](Cell& x) {
		switch (x.isNewAlive())
		{
		case DEATH: {
			white.setPosition(x.getPosition());
			window.draw(white);
			break;
		}
		case ALIVE: {
			black.setPosition(x.getPosition());
			window.draw(black);
			break;
		}
		}
		x.updateState();
	});

}

Da się coś z tym jeszcze zrobić? Czy może to już jest granica mojej sprzętu? Czy rysowanie np dwóch trójkątów zamiast jednego prostokąta będzie dobrym pomysłem? Czy może faktycznie rysować kształty z OpenGL zamiast SFML-a

1

Oczywiście że się da. Kilka możliwych pomysłów (nie widzę całego kodu, stąd spekuluję):

  1. Jeśli symulacja odbywa się na dużej planszy a tylko środkowa część jest zajęta, po co obliczać dla "martwych połaci komórek"?
  2. W jakim celu sięgać do całego kontenera z wierszem jeśli potrzebujesz jedynie 1 wiersza który po obliczeniu 3 wierszy jest "zwalniany"?
  3. Jak wygląda % trafień w cache?
  4. Można rozważyć inne ułożenie danych (nie ciągły vector-vectorów czy tablica-tablic a raczej "kafelki")
  5. ...inne ... po zerknięciu do kodu.

Poza tym zapewne znajdziesz jeszcze miejsca z małą optymalizacją w samym kodzie.

PS. Rozumiem że rozmiar planszy jest stały. W takim przypadku rozważ użycie std::array a nie std::vector (są oczywiście i wady ale bez kodu....).

1

IMHO najłatwiej będzie po prostu przerysowywać tylko to co się zmieniło, można to w miarę łatwo zrobić kolejką gdzie tylko wysyłasz informacje w postaci "pomaluj X na czarno" oraz "pomaluj Y na biało". W ten sposób dodatkowo rozdzielisz logikę aplikacji (samą symulację) od wyświetlania.

1

Jeśli to są prostokąty bez tekstur (tylko wypełnione kolorem), to zamiast bawić się z sf::RectangleShape użyj wstępnie wypełnionego sf::VertexArray (sf::Quads). Poniżej przykład z liniami, ale bez problemu sobie zmienisz. Zwróć uwagę na to, że całość (wszystkie linie) rysujesz jednym wywołaniem draw()

#include <SFML/Graphics.hpp>

int main()
{
    // Create the main window
    sf::RenderWindow window(sf::VideoMode(800, 600), "SFML window");

    sf::VertexArray piramida(sf::Lines);
    for (int x=0; x<800; x+=10)
    {
        sf::Vertex poczatek, koniec;
        poczatek.position = sf::Vector2f(400, 0);
        poczatek.color = sf::Color(200, 200, 200); // RGB

        koniec.position = sf::Vector2f(x, 599);
        koniec.color = sf::Color(rand() % 255, rand()%255, rand()%255);

        piramida.append(poczatek);
        piramida.append(koniec);
    };

    // Start the game loop
    while (window.isOpen())
    {
        // Process events
        sf::Event event;
        while (window.pollEvent(event))
        {
            // Close window : exit
            if (event.type == sf::Event::Closed)
                window.close();
        }

        // Clear screen
        window.clear();

        window.draw(piramida);

        // Update the window
        window.display();
    }

    return EXIT_SUCCESS;
}

1 użytkowników online, w tym zalogowanych: 0, gości: 1