Multithreading, Speicher ans OS retournieren.
-
Hallo!
Ich suche verlorene Bytes, und beim Debuggen bin ich auf ein eigenartiges
Verhalten beim Multithreading unter Linux gestoßen. Hier ist ein
Minimalbeispiel dazu:#include <stdio.h> #include <iostream> #include <boost/thread.hpp> using namespace std; void fun() { cout<<"Hi, I'm the thread"<<endl; }; int main(int ,char**) { #if 0 fun(); #else boost::thread *a=new boost::thread(fun); a->join(); delete a; #endif cout<<"Thread finished"<<endl; while(1); return 0; }Je nachdem, ob im Code oben #if 0 oder #if 1 steht, wird fun() normal oder
in einem Thread gestartet. Der Thread wird anschließend gelöscht. In jedem
Fall gehe ich danach in eine Endlosschleife, um mit "top" den Speicherbedarf
auslesen zu können.Ohne Thread: 12 MB
Mit Thread: 24.5 MBPID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 23839 geom 20 0 24520 1204 1008 R 99 0.0 7:09.44 justATestWas hat es da? Ist das ein Boost-Fehler oder holt sich das OS den Speicher
einfach nicht gleich zurück? Hier sind es nur ein paar Bytes, aber in meiner
Applikation sind es 150 MB, die auf die Art liegen bleiben.Danke
-
Nur weil der Speicher nicht sofort ans Betriebssystem zurückgegeben wird, heisst das nicht, dass er nicht korrekt freigegeben wird.
-
Ev. ist ja dein Bsp. einfach ein wenig unglücklich gewählt, denn boost::thread ist movable! D.h. das new ist in aller Regel unnötig!
-
theta schrieb:
Ev. ist ja dein Bsp. einfach ein wenig unglücklich gewählt, denn boost::thread ist movable! D.h. das new ist in aller Regel unnötig!
Bei dem Programm muss man aber gar nicht mooven. Da es würde auch reichen, direkt einen Funktor in den Ctor zu geben. Ich denke aber eher, dass der TO die Lebensdauer des Threadobjekts kontrollieren will. Aber da hätte es evtl. auch ein umschließender Scope getan.
-
1. Vlt zieht das Threading einen Rattenschwanz an Abhängigkeiten in das Projekt und bläht den Speicherverbrauch auf.
2. Möglicherweise musste auch einfach nur der Heap wachsen und macht das in großen Schritten, um seltener wachsen zu müssen.Mach mal Folgendes: Geh in die Endlosschleife, besorg dir die PID deines Prozesses und mach 'cat /proc/DIE PID/maps'. Bei beiden. Das kannst du hier posten oder selbst interpretieren.

-
Führ halt nen Scope ein, damit das thread-Objekt auch vor der Schleife aufgeräumt wird...
-
314159265358979 schrieb:
Führ halt nen Scope ein, damit das thread-Objekt auch vor der Schleife aufgeräumt wird...
Das sagte ich doch schon...

-
Ethon schrieb:
1. Vlt zieht das Threading einen Rattenschwanz an Abhängigkeiten in das Projekt und bläht den Speicherverbrauch auf.
2. Möglicherweise musste auch einfach nur der Heap wachsen und macht das in großen Schritten, um seltener wachsen zu müssen.Mach mal Folgendes: Geh in die Endlosschleife, besorg dir die PID deines Prozesses und mach 'cat /proc/DIE PID/maps'. Bei beiden. Das kannst du hier posten oder selbst interpretieren.

Multithreaded: cat /proc/25105/maps 00400000-00406000 r-xp 00000000 08:23 5767236 /home/geom/repo/dev/test_all/test_mt2/justATest 00605000-00606000 r--p 00005000 08:23 5767236 /home/geom/repo/dev/test_all/test_mt2/justATest 00606000-00607000 rw-p 00006000 08:23 5767236 /home/geom/repo/dev/test_all/test_mt2/justATest 025e6000-02607000 rw-p 00000000 00:00 0 [heap] 7f3b129b4000-7f3b129b5000 ---p 00000000 00:00 0 7f3b129b5000-7f3b131b5000 rw-p 00000000 00:00 0 7f3b131b5000-7f3b13238000 r-xp 00000000 08:21 406195 /lib/x86_64-linux-gnu/libm-2.13.so 7f3b13238000-7f3b13437000 ---p 00083000 08:21 406195 /lib/x86_64-linux-gnu/libm-2.13.so 7f3b13437000-7f3b13438000 r--p 00082000 08:21 406195 /lib/x86_64-linux-gnu/libm-2.13.so 7f3b13438000-7f3b13439000 rw-p 00083000 08:21 406195 /lib/x86_64-linux-gnu/libm-2.13.so 7f3b13439000-7f3b135d0000 r-xp 00000000 08:21 406191 /lib/x86_64-linux-gnu/libc-2.13.so 7f3b135d0000-7f3b137cf000 ---p 00197000 08:21 406191 /lib/x86_64-linux-gnu/libc-2.13.so 7f3b137cf000-7f3b137d3000 r--p 00196000 08:21 406191 /lib/x86_64-linux-gnu/libc-2.13.so 7f3b137d3000-7f3b137d4000 rw-p 0019a000 08:21 406191 /lib/x86_64-linux-gnu/libc-2.13.so 7f3b137d4000-7f3b137da000 rw-p 00000000 00:00 0 7f3b137da000-7f3b137ef000 r-xp 00000000 08:21 395815 /lib/x86_64-linux-gnu/libgcc_s.so.1 7f3b137ef000-7f3b139ee000 ---p 00015000 08:21 395815 /lib/x86_64-linux-gnu/libgcc_s.so.1 7f3b139ee000-7f3b139ef000 r--p 00014000 08:21 395815 /lib/x86_64-linux-gnu/libgcc_s.so.1 7f3b139ef000-7f3b139f0000 rw-p 00015000 08:21 395815 /lib/x86_64-linux-gnu/libgcc_s.so.1 7f3b139f0000-7f3b13ad8000 r-xp 00000000 08:21 924268 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.16 7f3b13ad8000-7f3b13cd8000 ---p 000e8000 08:21 924268 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.16 7f3b13cd8000-7f3b13ce0000 r--p 000e8000 08:21 924268 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.16 7f3b13ce0000-7f3b13ce2000 rw-p 000f0000 08:21 924268 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.16 7f3b13ce2000-7f3b13cf7000 rw-p 00000000 00:00 0 7f3b13cf7000-7f3b13d0f000 r-xp 00000000 08:21 406205 /lib/x86_64-linux-gnu/libpthread-2.13.so 7f3b13d0f000-7f3b13f0e000 ---p 00018000 08:21 406205 /lib/x86_64-linux-gnu/libpthread-2.13.so 7f3b13f0e000-7f3b13f0f000 r--p 00017000 08:21 406205 /lib/x86_64-linux-gnu/libpthread-2.13.so 7f3b13f0f000-7f3b13f10000 rw-p 00018000 08:21 406205 /lib/x86_64-linux-gnu/libpthread-2.13.so 7f3b13f10000-7f3b13f14000 rw-p 00000000 00:00 0 7f3b13f14000-7f3b13f2b000 r-xp 00000000 08:21 941896 /usr/lib/libboost_thread.so.1.46.1 7f3b13f2b000-7f3b1412a000 ---p 00017000 08:21 941896 /usr/lib/libboost_thread.so.1.46.1 7f3b1412a000-7f3b1412c000 r--p 00016000 08:21 941896 /usr/lib/libboost_thread.so.1.46.1 7f3b1412c000-7f3b1412d000 rw-p 00018000 08:21 941896 /usr/lib/libboost_thread.so.1.46.1 7f3b1412d000-7f3b1414e000 r-xp 00000000 08:21 406188 /lib/x86_64-linux-gnu/ld-2.13.so 7f3b1431c000-7f3b14322000 rw-p 00000000 00:00 0 7f3b1434a000-7f3b1434d000 rw-p 00000000 00:00 0 7f3b1434d000-7f3b1434e000 r--p 00020000 08:21 406188 /lib/x86_64-linux-gnu/ld-2.13.so 7f3b1434e000-7f3b14350000 rw-p 00021000 08:21 406188 /lib/x86_64-linux-gnu/ld-2.13.so 7fff08a3a000-7fff08a5b000 rw-p 00000000 00:00 0 [stack] 7fff08bff000-7fff08c00000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]Ohne Multithreading: 00400000-00403000 r-xp 00000000 08:23 5767236 /home/geom/repo/dev/test_all/test_mt2/justATest 00602000-00603000 r--p 00002000 08:23 5767236 /home/geom/repo/dev/test_all/test_mt2/justATest 00603000-00604000 rw-p 00003000 08:23 5767236 /home/geom/repo/dev/test_all/test_mt2/justATest 01cce000-01cef000 rw-p 00000000 00:00 0 [heap] 7f3385dae000-7f3385e31000 r-xp 00000000 08:21 406195 /lib/x86_64-linux-gnu/libm-2.13.so 7f3385e31000-7f3386030000 ---p 00083000 08:21 406195 /lib/x86_64-linux-gnu/libm-2.13.so 7f3386030000-7f3386031000 r--p 00082000 08:21 406195 /lib/x86_64-linux-gnu/libm-2.13.so 7f3386031000-7f3386032000 rw-p 00083000 08:21 406195 /lib/x86_64-linux-gnu/libm-2.13.so 7f3386032000-7f33861c9000 r-xp 00000000 08:21 406191 /lib/x86_64-linux-gnu/libc-2.13.so 7f33861c9000-7f33863c8000 ---p 00197000 08:21 406191 /lib/x86_64-linux-gnu/libc-2.13.so 7f33863c8000-7f33863cc000 r--p 00196000 08:21 406191 /lib/x86_64-linux-gnu/libc-2.13.so 7f33863cc000-7f33863cd000 rw-p 0019a000 08:21 406191 /lib/x86_64-linux-gnu/libc-2.13.so 7f33863cd000-7f33863d3000 rw-p 00000000 00:00 0 7f33863d3000-7f33863e8000 r-xp 00000000 08:21 395815 /lib/x86_64-linux-gnu/libgcc_s.so.1 7f33863e8000-7f33865e7000 ---p 00015000 08:21 395815 /lib/x86_64-linux-gnu/libgcc_s.so.1 7f33865e7000-7f33865e8000 r--p 00014000 08:21 395815 /lib/x86_64-linux-gnu/libgcc_s.so.1 7f33865e8000-7f33865e9000 rw-p 00015000 08:21 395815 /lib/x86_64-linux-gnu/libgcc_s.so.1 7f33865e9000-7f33866d1000 r-xp 00000000 08:21 924268 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.16 7f33866d1000-7f33868d1000 ---p 000e8000 08:21 924268 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.16 7f33868d1000-7f33868d9000 r--p 000e8000 08:21 924268 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.16 7f33868d9000-7f33868db000 rw-p 000f0000 08:21 924268 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.16 7f33868db000-7f33868f0000 rw-p 00000000 00:00 0 7f33868f0000-7f3386911000 r-xp 00000000 08:21 406188 /lib/x86_64-linux-gnu/ld-2.13.so 7f3386ae0000-7f3386ae5000 rw-p 00000000 00:00 0 7f3386b0d000-7f3386b10000 rw-p 00000000 00:00 0 7f3386b10000-7f3386b11000 r--p 00020000 08:21 406188 /lib/x86_64-linux-gnu/ld-2.13.so 7f3386b11000-7f3386b13000 rw-p 00021000 08:21 406188 /lib/x86_64-linux-gnu/ld-2.13.so 7fff981ac000-7fff981cd000 rw-p 00000000 00:00 0 [stack] 7fff981ff000-7fff98200000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
-
Tachyon schrieb:
314159265358979 schrieb:
Führ halt nen Scope ein, damit das thread-Objekt auch vor der Schleife aufgeräumt wird...
Das sagte ich doch schon...

OK, gleicher Effekt.
#include <stdio.h> #include <iostream> #include <boost/thread.hpp> using namespace std; void fun() { cout<<"Hi, I'm the thread"<<endl; }; int main(int ,char**) { #if 0 fun(); #else { boost::thread a(fun); a.join(); } #endif cout<<"Thread finished"<<endl; while(1); return 0; }
-
Die zusätzlichen 12.5 MB gehen sicher nicht im Thread-Objekt drauf, d.h. ob new() oder nicht wird hier keine Rolle spielen.
Ich würde eher davon ausgehen dass die Heap-Implementierung den frei gewordenen Speicher nicht ans OS zurückgibt. Entweder weil sie es nicht kann (z.B. wegen Fragmentierung), oder weil sie den Speicher nur dann ans OS zurückgibt, wenn eine bestimmte Grenze überschritten wird. Was für die meisten Applikationen ja auch Sinn macht. Die Heap-Performance würde arg leiden, wenn jede 4K Seite sofort ans OS zurückgegeben würde - dann müsste ja dauernd neuer Speicher angefordert und wieder freigegeben werden.
Ob es ein echtes Memory-Leak gibt, kann man am einfachsten feststellen, indem man ein geeignetes Tool verwendet. Ich denke Valgrind sollte das können (hab's aber selbst noch nie verwendet, da ich für Windows programmiere und wir BoundsChecker dafür verwenden).
Nochwas: es ist denkbar, dass eine CRT/SCL Implementierung bestimmte Datenstrukturen die für thread-safety nötig sind erst erzeugt/initialisiert, wenn der erste "nicht-main" Thread bestimmte Funktionen aufruft. Und in Boost.Thread gibt es definitiv einige Datenstrukturen die "lazy" Initialisiert werden, d.h. erst bei Erzeugung des ersten Boost.Thread.
Ich denke folgendes Testprogramm wäre eher sinnvoll:
#include <stdio.h> #include <iostream> #include <boost/thread.hpp> using namespace std; void fun() { cout<<"Hi, I'm the thread"<<endl; }; int main(int ,char**) { cout<<"Hi, I'm main()"<<endl; { boost::thread a(fun); a.join(); } #if 0 fun(); #else { boost::thread b(fun); b.join(); } #endif cout<<"Thread finished"<<endl; while(1); return 0; }Hier dürfte mMn. kein grosser Unterschied mehr bestehen. Wenn doch, dann könnte man wirklich mal auf die Suche gehen ob hier nicht irgendwas verkehrt läuft.
-
um mit "top" den Speicherbedarf
Nutze
valgrind,topbitte nicht
-
hustbaer schrieb:
Die zusätzlichen 12.5 MB gehen sicher nicht im Thread-Objekt drauf, d.h. ob new() oder nicht wird hier keine Rolle spielen.
Ich würde eher davon ausgehen dass die Heap-Implementierung den frei gewordenen Speicher nicht ans OS zurückgibt.
Ich glaube auch, daß der Speicher aus Effizienzgründen nicht gleich retourniert
wird. Macht nur das Debuggen schwieriger. Valgrind kenne und verwende ich sehr
gerne. Nur ist meine Anwendung numerisch kritisch, und da nicht alle Rundungs-
Modi in Valgrind implementiert sind, läuft das nicht. Unser Testprogramm geht
aber. Endlosschleife entfernt, und dann:==25904== Memcheck, a memory error detector ==25904== Copyright (C) 2002-2010, and GNU GPL'd, by Julian Seward et al. ==25904== Using Valgrind-3.6.1-Debian and LibVEX; rerun with -h for copyright info ==25904== Command: ./justATest ==25904== Hi, I'm main() Hi, I'm the thread Hi, I'm the thread Thread finished ==25904== ==25904== HEAP SUMMARY: ==25904== in use at exit: 8 bytes in 1 blocks ==25904== total heap usage: 7 allocs, 6 frees, 808 bytes allocated ==25904== ==25904== 8 bytes in 1 blocks are still reachable in loss record 1 of 1 ==25904== at 0x4C28F9F: malloc (vg_replace_malloc.c:236) ==25904== by 0x4E424C9: boost::detail::get_once_per_thread_epoch() (in /usr/lib/libboost_thread.so.1.46.1) ==25904== by 0x4E3B3BF: ??? (in /usr/lib/libboost_thread.so.1.46.1) ==25904== by 0x4E3B688: boost::detail::get_current_thread_data() (in /usr/lib/libboost_thread.so.1.46.1) ==25904== by 0x4E3CDFA: boost::thread::join() (in /usr/lib/libboost_thread.so.1.46.1) ==25904== by 0x402D4E: main (in /home/geom/repo/dev/test_all/test_mt3/justATest) ==25904== ==25904== LEAK SUMMARY: ==25904== definitely lost: 0 bytes in 0 blocks ==25904== indirectly lost: 0 bytes in 0 blocks ==25904== possibly lost: 0 bytes in 0 blocks ==25904== still reachable: 8 bytes in 1 blocks ==25904== suppressed: 0 bytes in 0 blocks ==25904== ==25904== For counts of detected and suppressed errors, rerun with: -v ==25904== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 4 from 4)Interessant ist, woher die Still-Reachable kommen.
-
Ich glaube auch, daß der Speicher aus Effizienzgründen nicht gleich retourniert wird.
Ja, weil wenn es gleich danach wieder was vom Programm angefordert wird, fuehlt sich das Betriebssystem verarscht.
Nur ist meine Anwendung numerisch kritisch, und da nicht alle Rundungs-
Modi in Valgrind implementiert sind, läuft das nicht.Naja, Speicher hat normalerweise nichts mit Rundungen zu tun.
-
knivil schrieb:
Naja, Speicher hat normalerweise nichts mit Rundungen zu tun.
Bei Rundungsfehlern werden Prädikate falsch evauliert und das Programm haut
es in Valgrind auf.
-
-
memr schrieb:
Bei Rundungsfehlern werden Prädikate falsch evauliert
Valgrind fuehrt dein Programm so aus, wie du es kompiliert hast. Klar kann das Verhalten von Debug zu Release unterschiedlich sein, aber das hat mit Valgrind nichts zu tun. Du kannst Valgrind auch mit der Releaseversion benutzen. Du erhaelts immer noch eine Zusammenfassung.
und das Programm haut es in Valgrind auf.
Aeh, und das heisst? Bitte auf Deutsch.
-
knivil schrieb:
memr schrieb:
Bei Rundungsfehlern werden Prädikate falsch evauliert
Valgrind fuehrt dein Programm so aus, wie du es kompiliert hast. Klar kann das Verhalten von Debug zu Release unterschiedlich sein, aber das hat mit Valgrind nichts zu tun. Du kannst Valgrind auch mit der Releaseversion benutzen. Du erhaelts immer noch eine Zusammenfassung.
und das Programm haut es in Valgrind auf.
Aeh, und das heisst? Bitte auf Deutsch.
In der rechnerischen Geometrie ist es häufig erforderlich, die Vorzeichen von
Determinanten zu ermitteln. Beispielsweise, um festzustellen, ob ein Punkt
oberhalb, unterhalb oder genau in einer Ebene liegt. Ist die Determinante nahe
Null, so kann wegen der begrenzten Rechengenauigkeit eine falsche Antwort
berechnet werden, was den Algorithmus in einen falschen Zweig führt ->
Endlosschleife/Segfault/Assertion. Typischerweise sind viele tausend solcher
Tests erforderlich, so daß das Problem nicht nur theoretischer Natur ist,
sondern eigentlich immer schlagend wird. Es gibt Methoden, um die Problematik
zu handhaben, aber die setzen korrekte Rundung voraus, die in Valgrind nicht
vorliegt. Deshalb laufen dort solche Programme meist nicht.
-
Ich weiss um die Numerik dahinter Bescheid, nur kann ich den Zusammenhang mit Valgrind nicht herstellen. Valgrind installiert Hooks fuer free und malloc und fuehrt dein Programm aus und tracked deinen Speicher. Dabei kann ich keinen Einfluss auf numerische Berechnungen feststellen. Wo rundet Valgrind?
-
http://stackoverflow.com/questions/1656227/how-does-valgrind-work schrieb:
Your program is then run on a synthetic CPU provided by the Valgrind core. As new code is executed for the first time, the core hands the code to the selected tool. The tool adds its own instrumentation code to this and hands the result back to the core, which coordinates the continued execution of this instrumented code.
Das klingt nicht so, als würde valgrind nur einige Hooks einfügen. Deswegen ist valgrind auch so langsam.
-
Gut, ich habe wieder was gelernt. Und liest man weiter, dann kann auch Nulgrind benutzt werden. Sicher gibt es zwischen "simulate every instruction" und "nothing" viele Optionen.