Autor Wątek: [Solved] Różna prędkość wykonywania się kodu  (Przeczytany 2819 razy)

Offline komorra

  • Użytkownik
    • Blog naszego teamu (o grze Voxelfield)

# Grudzień 26, 2014, 20:25:23
Mam taki oto kod: https://github.com/komorra/CraftSaga/blob/master/Mesher/Mesher/mesher_src.cpp oraz funkcję od której zaczyna się "meshowanie" wokseli (na samym dole):
extern "C" __declspec(dllexport) void __stdcall MeshVoxels(
int voxelCount,
uint64_t* coords,
uint32_t* types,
vector3* vertices,
vector3* normals,
vector2* uvs,
int* tris,
int* vertexCount,
int* indexCount,
int* texture,
int* texW,
int* texH,
bool liquid
)

Z poziomu Unity3D jest to wywoływane tak:
sygnatura:

[DllImport("Mesher")]
    public static extern void MeshVoxels(
        int voxelCount,
        long[] coords,
        int[] types,
        Vector3[] vertices,
        Vector3[] normals,
        Vector2[] uvs,
        int[] tris,
        ref int vertexCount,
        ref int indexCount,
        int[] texture,
        ref int texW,
        ref int texH,
        [MarshalAs(UnmanagedType.I1)] bool liquid
        );

wywołanie:

var keys = Voxels.Keys.ToArray();
        var vals = Voxels.Values.ToArray();

        Stopwatch watch = new Stopwatch();
        watch.Start();
        Mesher.MeshVoxels(Voxels.Count, keys, vals,
            vertices, normals, uvs, tris, ref vcount, ref icount, itex, ref texw, ref texh, tag=="Liquid");
        watch.Stop();
        UnityEngine.Debug.Log(watch.Elapsed);

I to własnie miejsce wywołania jest o-stopwatch-owane. Teren generuje identyczny (ten sam seed). I teraz leci mi w konsoli Unity szereg komunikatów z Debug.Log o czasie stopwatch-a. Jak raz uruchomie to czasy wywołania na funkcję są prawie wszystkie powyżej 1s, a jak innym razem (co się zdarza jednak rzadziej) to czasy oscylują wokół 80-100ms - co już jest powiedzmy akceptowalne. Ale nie akceptowalna jest ta jedna sekunda. To samo występuje też w buildzie.

Co tu może być nie tak? (Przy okazji jakby ktoś się pokusił o code review mały to z góry dzięki ;) )
« Ostatnia zmiana: Grudzień 27, 2014, 14:30:24 wysłana przez komorra »

Offline Mr. Spam

  • Miłośnik przetworów mięsnych

Offline aphity

  • Użytkownik

# Grudzień 26, 2014, 21:24:18
Nie znam się zbyt na Unity, ale przepuszczenie tego przez profiler tak, by wiedzieć które funkcje zjadają najwięcej czasu z pewnością ułatwiłoby dochodzenie.

Offline laggyluk

  • Użytkownik
    • http://laggyluk.com

  • +5
# Grudzień 26, 2014, 21:45:46
code review: nie widziałem 'goto' od paru ładnych lat :D

Offline .:NOXY:.

  • Użytkownik
    • Profil

  • +2
# Grudzień 26, 2014, 22:49:30
code review: nie widziałem 'goto' od paru ładnych lat :D

goto jest 100 razy lepsze niż 2boole i ify je obsługujące żeby zrobić break w owych paru loopach.

Offline revo

  • Użytkownik

  • +3
# Grudzień 26, 2014, 22:57:56
goto jest 100 razy lepsze niż 2boole i ify je obsługujące żeby zrobić break w owych paru loopach.

Zwykle ten sam kod można wydzielić w czytelną funkcję i zastąpić goto przez return ;)

Offline Krzysiek K.

  • Redaktor
    • DevKK.net

  • +1
# Grudzień 26, 2014, 23:30:21
Zwykle ten sam kod można wydzielić w czytelną funkcję i zastąpić goto przez return ;)
Tylko że po co, jeżeli kod już w danym miejscu jest czytelny.

Offline komorra

  • Użytkownik
    • Blog naszego teamu (o grze Voxelfield)

# Grudzień 26, 2014, 23:51:36
goto gotem ;) ale coś chyba czuje że te "różnice" w prędkości kodu to coś po stronie Unity jednak (chyba że się mylę) - bo np. benchmark.cpp zwykle daje stabilne rezultaty czasowe.

Offline laggyluk

  • Użytkownik
    • http://laggyluk.com

  • +1
# Grudzień 27, 2014, 00:03:17
obstawiałbym alokacje pamięci ale z tego co widzę to tylko w make_texture jest i bez pętli więc pewnie nie to :P

edit: na c# również się nie znam więc drugi strzał to że zamuła następuje w unity przy przekazaniu tych arrayów do parametrów funkcji
« Ostatnia zmiana: Grudzień 27, 2014, 00:08:28 wysłana przez laggyluk »

Offline komorra

  • Użytkownik
    • Blog naszego teamu (o grze Voxelfield)

# Grudzień 27, 2014, 00:18:39
@laggyluk: Być może to te właśnie to te arraye... przyjrzę się temu. Możliwe że GC w tle robi jakiś "pinning",a te arraye są dosyć spore (49152 elementów sama tablica pozycji werteksów).
« Ostatnia zmiana: Grudzień 27, 2014, 00:20:31 wysłana przez komorra »

Offline blejd123

  • Użytkownik

  • +1
# Grudzień 27, 2014, 00:51:51
Z tego co pamiętam, to możliwe, że dla tych tablic tworzone są kopie. Pobierz do nich wskaźnik (UIntPtr) i taki typ przekazuj do funkcji (zmień deklaracje importu).

Offline komorra

  • Użytkownik
    • Blog naszego teamu (o grze Voxelfield)

# Grudzień 27, 2014, 09:29:17
No i teraz chodzi jak żyleta :). Zmieniłem wywołania z tablic na IntPtry, które są alokowane raz do zmiennych IntPtr tablicy statycznej (jeden element per wątek). Zmieniłem przy okazji sposób wywoływania wątków: teraz jest tworzone na starcie 5 wątków i każdy ma swój "item" nad którym aktualnie pracuje - a nie jak było wcześniej że na każdy "item" tworzony jest nowy wątek.

Czasy na wywołania mojej funkcji z C++ spadły poniżej 1ms :):):):)

Offline Xender

  • Użytkownik

  • +1
# Grudzień 27, 2014, 16:31:46
(Przy okazji jakby ktoś się pokusił o code review mały to z góry dzięki ;) )

(rewizja 45803b0240e77af034fa6d5ddfd5f4e5adb1ca54)

mesher_src.cpp: 19-25
mesher_src.cpp: 32-38
Brak komentarza (zwłaszcza w headerze!), czemu akurat taka wartość.
To nie jest oczywiste, -1 byłoby równie logiczne.

mesher_src.cpp: 19-25
mesher_src.cpp: 32-38
mesher_src.cpp: 45-52
Czemu tyle powtórzeć ifó(<foo>) return ...?
Kwestia w sumie osobistych preferencji, ja bym dał tam operator ||.

mesher_src.cpp: 78-94
#include <algorithm>
Albo jak już musisz reimplementować, to templatkowo i w osobym headerze, coby się nie wcinało pomiędzy funkcje specyficzne dla meshowania.

mesher_src.cpp: 215-234
Wygląda, jakby dało się to zapisać 2-4x krócej. Ale nie jestem pewien, jak.

mesher_src.cpp: 375-385
Wykomentowany martwy kod. Nie commituj takich rzeczy, najlepiej przy commitowaniu wywal w ogóle z pliku.
Gita masz m.in. po to, by do takich rzeczy w razie potrzeby wracać.

mesher_src.cpp: 392-412
Ło.
v %= 4;

uv->m[1] = (v & 1) ? y+tempPatchH : y;
uv->m[0] = (v & 2) ? x+tempPatchW : x;


Zmieniłem przy okazji sposób wywoływania wątków: teraz jest tworzone na starcie 5 wątków i każdy ma swój "item" nad którym aktualnie pracuje - a nie jak było wcześniej że na każdy "item" tworzony jest nowy wątek.
Czyli pula wątków?
Jak kolejkulesz zadania?

Teoria kolejek Elranga: najlepiej jedna globalna kolejka, z któraj wątek pobiera zadanie, gdy nie ma co robić.
Intel TBB (IIRC): kolejka per wątek z podkradaniem z innych wątków w razie wyczerpania zadań. Bo cache i/lub koszty synchronizacji (locki/operacje atomowe) na globalnej kolejce.

Więc pewnie najlepiej sprawdzić empirycznie, jeśli jest tu jakieś wąskie gardło.

Te 5 wątków to zahardkodowane?
Spotkałem się z doborem ilości procesów jako ilość_rdzeni+1.
To było przy równoległej kompilacji, więc też inna charakterystyka obciążenia (I/O vs. CPU).

Oczywiście pytanie, czy liczyć HT jako rdzeń. Pewnie to i optymalna ilość wątków w ogóle też do sprofilowania.

Offline komorra

  • Użytkownik
    • Blog naszego teamu (o grze Voxelfield)

# Grudzień 27, 2014, 17:19:13
Cytuj
mesher_src.cpp: 19-25
mesher_src.cpp: 32-38
Brak komentarza (zwłaszcza w headerze!), czemu akurat taka wartość.
To nie jest oczywiste, -1 byłoby równie logiczne.

W sumie racja, wartość CS*CS*CS wskazuje na ostatni element tablicy (bo deklaruje ją zawsze jako CS*CS*CS+1) i tam trzymam 0.

Cytuj
mesher_src.cpp: 375-385
Wykomentowany martwy kod. Nie commituj takich rzeczy, najlepiej przy commitowaniu wywal w ogóle z pliku.
Gita masz m.in. po to, by do takich rzeczy w razie potrzeby wracać.

Wiem, ale jakoś się wympsknęło ;) (nie zauważyłem)

Cytuj
mesher_src.cpp: 392-412
Ło.
Kod: [Zaznacz]
v %= 4;

uv->m[1] = (v & 1) ? y+tempPatchH : y;
uv->m[0] = (v & 2) ? x+tempPatchW : x;

Faktycznie... :)

Cytuj
Czyli pula wątków?
Jak kolejkulesz zadania?

Jest to robione tutaj: https://github.com/komorra/CraftSaga/blob/master/UnityProject/Assets/Scripts/Threader.cs

Generalnie kolejkowaniem zadań zajmuje się funkcja QueueCheck() (która jest wywoływana co ileś tam dziesiąt milisekund) w wątku głównym. I ona po prostu ustawia elementy tablicy Item[] z odpowiednimi flagami, dzięki którym wątek "wie, że powinien wziąć" dany Item.

Dzięki za review Xender :)