Napisałem miękką aplikację czasu rzeczywistego w Haskell, która zajmuje się symulowaną fizyką, wykrywaniem kolizji, wszystkimi dobrymi rzeczami. Robiąc wszystko, przeznaczam dużo pamięci i prawdopodobnie mógłbym zoptymalizować wykorzystanie pamięci, gdybym chciał, ale skoro siedzę wygodnie na 40% procesorze i tylko 1% pamięci RAM i tak było używane, to nie wydaje się to konieczne. Widzę jednak, że dużo czasu, gdy rzuca śmieci, klatki są pomijane. Sprawdziłem, że to jest przyczyna problemu poprzez profilowanie z threadscope
: nie ma użytecznych obliczeń, czasami nawet do 0,05 sekundy, gdy śmieciarz wykonuje swoją działalność, co skutkuje trzema pominiętymi ramkami, co jest bardzo zauważalne i bardzo irytujące .Jak zoptymalizować usuwanie pamięci dla miękkiej aplikacji czasu rzeczywistego w Haskell?
Próbowałem teraz rozwiązać ten problem, ręcznie wywołując performMinorGC
każdą klatkę, i wydaje się to łagodzić problem, czyniąc go znacznie bardziej płynnym, z wyjątkiem faktu, że całkowite użycie procesora idzie drastycznie do około 70%. Najwyraźniej wolałbym tego uniknąć.
Kolejną rzeczą, której próbowałem, było zmniejszenie przestrzeni alokacji GC do 64k z 512k przy pomocy -H64k, a także próbowałem ustawić -I0.03, aby spróbować go częściej zbierać. Obie te opcje zmieniły schemat usuwania śmieci, który widziałem w threadscope
, ale nadal powodowały pomijanie klatek.
Czy ktoś, kto ma trochę doświadczenia z optymalizacją GC, może mi tutaj pomóc? Czy jestem skazany na ręczne wywoływanie performMinorGC
i rezygnację z ogromnej utraty wydajności?
EDIT
Próbowałem uruchomić go za podobną ilość czasu w tych testach, ale ponieważ jest to w czasie rzeczywistym, nie ma sensu, w którym to „Gotowe”.
statystyki środowiska z performMinorGC
co 4 klatek:
9,776,109,768 bytes allocated in the heap
349,349,800 bytes copied during GC
53,547,152 bytes maximum residency (14 sample(s))
12,123,104 bytes maximum slop
105 MB total memory in use (0 MB lost due to fragmentation)
Tot time (elapsed) Avg pause Max pause
Gen 0 15536 colls, 15536 par 3.033s 0.997s 0.0001s 0.0192s
Gen 1 14 colls, 13 par 0.207s 0.128s 0.0092s 0.0305s
Parallel GC work balance: 6.15% (serial 0%, perfect 100%)
TASKS: 20 (2 bound, 13 peak workers (18 total), using -N4)
SPARKS: 74772 (20785 converted, 0 overflowed, 0 dud, 38422 GC'd, 15565 fizzled)
INIT time 0.000s ( 0.001s elapsed)
MUT time 9.773s ( 7.368s elapsed)
GC time 3.240s ( 1.126s elapsed)
EXIT time 0.003s ( 0.004s elapsed)
Total time 13.040s ( 8.499s elapsed)
Alloc rate 1,000,283,400 bytes per MUT second
Productivity 75.2% of total user, 115.3% of total elapsed
gc_alloc_block_sync: 29843
whitehole_spin: 0
gen[0].sync: 11
gen[1].sync: 71
Bez performMinorGC
12,316,488,144 bytes allocated in the heap
447,495,936 bytes copied during GC
63,556,272 bytes maximum residency (15 sample(s))
15,418,296 bytes maximum slop
146 MB total memory in use (0 MB lost due to fragmentation)
Tot time (elapsed) Avg pause Max pause
Gen 0 19292 colls, 19292 par 2.613s 0.950s 0.0000s 0.0161s
Gen 1 15 colls, 14 par 0.237s 0.165s 0.0110s 0.0499s
Parallel GC work balance: 2.67% (serial 0%, perfect 100%)
TASKS: 17 (2 bound, 13 peak workers (15 total), using -N4)
SPARKS: 100714 (29688 converted, 0 overflowed, 0 dud, 47577 GC'd, 23449 fizzled)
INIT time 0.000s ( 0.001s elapsed)
MUT time 13.377s ( 9.917s elapsed)
GC time 2.850s ( 1.115s elapsed)
EXIT time 0.000s ( 0.006s elapsed)
Total time 16.247s (11.039s elapsed)
Alloc rate 920,744,995 bytes per MUT second
Productivity 82.5% of total user, 121.4% of total elapsed
gc_alloc_block_sync: 68533
whitehole_spin: 0
gen[0].sync: 9
gen[1].sync: 147
Całkowita wydajność wydaje się być niższa bez performMinorGC
teraz, niż gdy testowałem go wczoraj z jakiegoś powodu - - zanim było zawsze> 90%.
Proszę wkleić statystyki środowiska wykonawczego ('+ RTS -s') – Yuras
naiwna sugestia, ale co jeśli po prostu wywołasz' performMinorGC' co, powiedzmy, 10 klatek? –
Co przydzielasz? Jeśli możesz uniknąć przydziałów, GC staje się nie problem. – MathematicalOrchid