# zklisches dc.execReadRequest(multireadPDU01, rs) reserviert !zunehmends! Speicher



## bool (16 Mai 2010)

Hallo,

ich polle mit meinem VB.NET Programm (VB2008 Express) zyklisch Daten aus der SPS und da musste ich im Windows Task-Manager leidlicherweise feststellen, dass mein Programm zunehmends Speicher reserviert und nicht wieder freigibt.

Dem wollte ich auf die Spur gehen und habe zunächst meine zyklisch aufgerufene Pollroutine mit "Exit Function" in der ersten Zeile deaktiviert.
Danach blieb der reservierte Speicher in einem festen Bereich.

Als nächsten Schritt habe ich folgende Zeilen hinzugefügt:

Globale Variable:
Public rs AsNew libnodave.resultSet 

PrivateFunction PollData() AsBoolean
Dim res AsInteger
res = dc.execReadRequest(multireadPDU01, rs)
ExitFunction
...

...so dass in meinem Pollzyklus (zur Zeit alle 200ms) nur die Funktion dc.execReadRequest(multireadPDU01, rs) aufgerufen wird und keine anderen Aktionen wie Datenauswertung oder das Schreiben ins Logfile stattfinden.

Hier tritt nun bereits das Problem auf, dass das Programm etwa alle zwei bis drei Sekunden 4kB zusätzlichen Speicher reserviert und diesen erst mit Programmende wieder freigibt (so hoffe ich zumindest).

Das bedeutet, dass ein Loggen über mehrere Stunden/Tage zum Problem werden können, da dem Computer die Resourcen ausgehen und andere laufende Programme in Mitleidenschaft gezogen werden können.

Zur zusätzlichen Information:
- der Speicherzuwachs findet unabhängig vom Protokoll stat (ISO on TCP, S7Online, MPI)
- die multireadPDU01 besteht aus 19 Variablen welche zuvor einmalig (nicht im Pollzyklus) über die Funktion 
multireadPDU01.addVarToReadRequest(nArea, nDBnr, nByteAdr, nDataLength) hinzugefügt wurden:

...
multireadPDU01 = dc.prepareReadRequest()
...
Schleife 0...x (18)
multireadPDU01.addVarToReadRequest(nArea, nDBnr, nByteAdr, nDataLength)
... 



Ich hoffe es kann mir jemand bei diesem Problem helfen, da ich ansonsten das Programm nicht einsetzen werden kann.

Was mache ich hier falsch? Wie kann ich den reservierten Speicher nach dem Auslesen und Verarbeiten dieser Werte wieder freigeben? 

Ich wäre wirklich seeeehr dankbar wenn mir da jemand aus der Patsche helfen könnte.

Gruss,

bool


----------



## gravieren (16 Mai 2010)

Hi

Kenne mich überhaupt nicht aus.

Jedoch.
Teste/Vegleiche doch mal beide Codes.


http://www.sps-forum.de/showthread.php?t=35854







Oder lies das hier: http://spsforum.com/showthread.php?t=10790


----------



## bool (16 Mai 2010)

gravieren schrieb:


> Hi
> 
> Kenne mich überhaupt nicht aus.
> 
> ...


 
Bei diesem Beispiel handelt es sich um ein C# Programm und es werden die Variablen am Ende mit "null" zugewiesen.

rs = null;
myPDU = null;

Bei meinem VB.NET Programm habe ich bereits das selbig versucht nachzubilden. Da "null" dort nicht als solches nicht existiert sondern je nachdem "vbnull", "vbnullchar" oder "vbnullstring" verwendet werden müssen, habe ich solchiges versucht. Ich bekomme dann jedoch leider die Meldung, dass der Typ "Variant" nicht in "libnodave.resultset" konvertiert werden kann.
Ich vermute, dass die "null" Zuweisung jedoch nicht der Knackpunkt ist 

@Jochen:
Wie sieht es bei Dir im Taskmanager mit dem verwendeten Speicher Deiner Applikation aus wenn Du die Daten zyklisch pollst?
Testweise wäre es interessant, wenn Du mal die "null" Zuweisungen nach Deiner Datenauswertung ausklammern und dann den Speicherzuwachs nochmals beobachten könntest. 



gravieren schrieb:


> Oder lies das hier: http://spsforum.com/showthread.php?t=10790


 
Bringt leider auch keine Lösung, da der dortige Fall noch offen ist.
Danke aber trotzdem für die Rückmeldung.


Mitlerweilen habe ich es mit "Using" probiert, hatte bislang aber noch keinen Erfolg, da ich folgende Meldung bekomme "Ein Using-Operand vom Typ libnodave.resultSet muss System.IDisposable implementieren." Wie dies zu bewerkstelligen ist versuche ich nun herauszufinden. -> scheint nicht zu gehen, da der Typ "libnodave.resultSet" selbst schon die Eigenschaft mitbringen muss, damit dieser per Using implementiert werden kann. Scheibenkleister!

Dim res AsInteger
Using rs AsNew libnodave.resultSet
res = dc.execReadRequest(multireadPDU01, rs)
EndUsing
ExitFunction
 

Ich kann mir aber nicht vorstellen, dass die Speicherfreigabe an sich ( auf irgend eine Art und Weise) nicht funktionieren soll, da ja wie ich den Eindruck habe doch mittlerweile einige Leute libnodave einsetzen und ein zunehmender Speicherzuwachs (kann im Task-Manager live überprüft werden) eigentlich im Forum kaum zur Sprache kommt. 

So hoffe ich auf wetere Ideen/Lösungen.

Gruss,

bool


----------



## Rainer Hönle (16 Mai 2010)

Früher wurde in VB nicht null sondern nothing verwendet. Vielleicht geht dies auch noch in vb.net.


----------



## bool (16 Mai 2010)

Rainer Hönle schrieb:


> Früher wurde in VB nicht null sondern nothing verwendet. Vielleicht geht dies auch noch in vb.net.


 
ok, "nothing" wird im vb.net erkannt und kann rs zugewiesen werden, doch leider gibt es dadurch in Bezug auf die zunehmende Speicherauslastung keine Besserung. Danke aber auf jeden Fall für die Info.

Inzwischen habe ich die Funktion "System.GC.Collect()" in meinem Poll vorangeführt. Da "rs" erst in der folgenden Zeile deklariert wird, vermute /hoofe ich dass dadurch die alte unbenutze rs Variabe vom GarbageCollector wieder freigegeben wird. Ein kurzer Test hat hier schon mal Besserung gezeigt, ich werde das ganze jetzt mal ein paar Stunden auf meinem anderen Rechner laufen lassen und die Resourcen beobachten.

Der Code des Polls sieht nun prinzipiell so aus:

PrivateFunction PollData() AsBoolean

System.GC.Collect()
Dim rs AsNew libnodave.resultSet 
Dim res AsInteger
res = dc.execReadRequest(multireadPDU01, rs)
...
Auswertung
...

Bitte um Feedback ob dies auf diese Art und Weise legitim ist oder ob dies zu anderen Problemen führen kann.

Auch über Alternativvorschläge bin ich offen und äusserst dankbar.

Gruss,

bool


----------



## argv_user (16 Mai 2010)

Ich gebe offen zu, dass ich VB nicht benutze.
Aber kann es sein, bool, dass der Aufruf in einem Objekt steckt, das dann regelmäßig Speicher alloziert? Von LibNoDave kann ich mir das eigentlich nicht vorstellen...


----------



## bool (16 Mai 2010)

argv_user schrieb:


> Ich gebe offen zu, dass ich VB nicht benutze.
> Aber kann es sein, bool, dass der Aufruf in einem Objekt steckt, das dann regelmäßig Speicher alloziert? Von LibNoDave kann ich mir das eigentlich nicht vorstellen...


Das scheint schon von der Funktion "res = dc.execReadRequest(multireadPDU01, rs)" bzw der Variablen "rs" auszugehen, da wenn ich diese Funktion nicht zyklisch aufrufe bliebt der reservierte Speicher auch ohne GarbageCollector konstant. Sobald ich diese Funktion in den Poll mit aufnehme wächst der reservierte Speicher alle 2-3 Sekunden um 4kB. Um den restlichen Code auszuschliessen habe ich die getriggerte (200ms) Pollfunktion direkt nach dem Aufruf von "dc.execReadRequest" terminiert.
Es kann natürlich damit zusammenhängen, dass ich die "multireadPDU01" einmalig vor dem Pollen deklariere und mit Variablen bestücke (siehe mein erstes Post) und dann den ReadRequest zyklisch ausführe. ???

Gruss,

bool


----------



## Jochen Kühner (16 Mai 2010)

bool schrieb:


> ok, "nothing" wird im vb.net erkannt und kann rs zugewiesen werden, doch leider gibt es dadurch in Bezug auf die zunehmende Speicherauslastung keine Besserung. Danke aber auf jeden Fall für die Info.
> 
> Inzwischen habe ich die Funktion "System.GC.Collect()" in meinem Poll vorangeführt. Da "rs" erst in der folgenden Zeile deklariert wird, vermute /hoofe ich dass dadurch die alte unbenutze rs Variabe vom GarbageCollector wieder freigegeben wird. Ein kurzer Test hat hier schon mal Besserung gezeigt, ich werde das ganze jetzt mal ein paar Stunden auf meinem anderen Rechner laufen lassen und die Resourcen beobachten.
> 
> ...



Es ist bei .NET Programmen normal das die Speichernutzung wächst, da der GC nur aufgerufen wird wenn es nötig ist! Solange noch speicher verfügbar ist und nicht von anderen Programmen gebraucht wird, läuft der GC nicht an, da der GC auch Rechenleistung braucht und normalerweise nicht selbst angestossen werden soll!


----------



## Jochen Kühner (16 Mai 2010)

bool schrieb:


> Das scheint schon von der Funktion "res = dc.execReadRequest(multireadPDU01, rs)" bzw der Variablen "rs" auszugehen, da wenn ich diese Funktion nicht zyklisch aufrufe bliebt der reservierte Speicher auch ohne GarbageCollector konstant. Sobald ich diese Funktion in den Poll mit aufnehme wächst der reservierte Speicher alle 2-3 Sekunden um 4kB. Um den restlichen Code auszuschliessen habe ich die getriggerte (200ms) Pollfunktion direkt nach dem Aufruf von "dc.execReadRequest" terminiert.
> Es kann natürlich damit zusammenhängen, dass ich die "multireadPDU01" einmalig vor dem Pollen deklariere und mit Variablen bestücke (siehe mein erstes Post) und dann den ReadRequest zyklisch ausführe. ???
> 
> Gruss,
> ...




In der libnodave.net werden im Destruktor von rs der Speicher für die Funktion wieder freigegeben, d.h. nach einem GC.collect, wenn du keinen verweis mehr auf rs hast, sollte der Speicher wieder frei werden!


----------



## bool (16 Mai 2010)

Jochen Kühner schrieb:


> Es ist bei .NET Programmen normal das die Speichernutzung wächst, da der GC nur aufgerufen wird wenn es nötig ist! Solange noch speicher verfügbar ist und nicht von anderen Programmen gebraucht wird, läuft der GC nicht an, da der GC auch Rechenleistung braucht und normalerweise nicht selbst angestossen werden soll!


 
Hallo Jochen,
danke für das Feedback.

Dass der Speicher deratig starg anwächst (Innerhalb von keinen vier Stunden auf >50MB) möchte/darf ich jedoch nicht zulassen, vor allem nicht auf einem Produktivsystem in der Fertigung wo ettliche andere Programme parallel auf dem System laufen und nicht in Bedrängnis geraten dürfen.

Alternativen zum zyklischen Aufruf der GC.Collect() vor jedem readRequest()welche ich da so sehen würde:
a) Aufruf eines zweiten Timer im z.B. 10 oder 60 Minutentakt um den GC.Collect() dort kontrolliert aufzurufen um keine grössere Perfomanceeinbußen zu riskieren. Der "assynchrone" Aufruf in einem zweiten Timer könnte dann höchstens ein Problem darstellen wenn der Destruktor von rs vor der Auswertung aufgerufen wird. Weisst Du ggf. wann der Destruktor von rs bei libnodave.net ausgeführt wird?

b) Hochzählen eines Zählers innerhalb des ersten Timers und Ausführen des GC.Collect alle x Aufrufe (z.B 1000 oder mehr, je nachdem wieviel MB man +/- tollerieren kann/möchte/darf -> wäre doch ein schönes Parameter fürs Ini-File)

Ich denke (b)) wäre ein Kompromiss mit dem ich auch leben könnte.

Gruss,

bool


----------



## bool (16 Mai 2010)

Habe eben mal den GC Aufruf vor den zyklischen Poll gesetzt. Es scheint als würde ein einmaliger GC Aufruf schon ausreichen, dass die "abgemeldeten Daten" sauber entsorgt werden. Kann das jemand bestätigen, dass es so richtig ist, dass der GC.Collect() ab dessen ersten und eizigen Aufruf aktiv ist und nach einer gewissen Zeit NICHT erneut aufgerufen werden muss?

Danke und Gruss,

bool


----------



## Jochen Kühner (16 Mai 2010)

bool schrieb:


> Hallo Jochen,
> danke für das Feedback.
> 
> Dass der Speicher deratig starg anwächst (Innerhalb von keinen vier Stunden auf >50MB) möchte/darf ich jedoch nicht zulassen, vor allem nicht auf einem Produktivsystem in der Fertigung wo ettliche andere Programme parallel auf dem System laufen und nicht in Bedrängnis geraten dürfen.
> ...



Also Ich denke immer noch nicht das du das tun musst, da wenn vom System Speicher gebraucht wird der Garbage Collector aufgerufen wird!

siehe auch http://dotnetbase.de/faq-was-garbage-collectorb-t583.html


----------



## bool (16 Mai 2010)

Jochen Kühner schrieb:


> Also Ich denke immer noch nicht das du das tun musst, da wenn vom System Speicher gebraucht wird der Garbage Collector aufgerufen wird!
> 
> siehe auch http://dotnetbase.de/faq-was-garbage-collectorb-t583.html


Danke für den Link.
Da wir in der Vergangenheit auf Produktivsystemen bereits Probleme mit Speicherfressern (im speziellen ein simpler Druckertreiber eines renomierten Herstellers) hatten welche zunehmends Speicher alloziiert aber nicht wieder freigegeben haben.
Auf diese Weise war bereits einmal das gesamte Produktivsystem zum Absturz gekommen. Seitdem werden die Resourcen von Programmen und Diensten stets beäugt und ist es nicht gern gesehen, wenn die Resourcen eines Programmes stetig zunehmen.
Wenn man nun Programme einsetzt bei denen es sozusagen in Ordnung ist, wenn diese zunehmend Speicher beanspruchen, da dieser erst freigegeben wird wenn dieser benötigt wird, geht eine einfach und doch wichtige Kontrollfunktion per einfachen Blick in den Task-Manager verloren.

Gruss,

bool


----------



## Jochen Kühner (16 Mai 2010)

bool schrieb:


> Danke für den Link.
> Da wir in der Vergangenheit auf Produktivsystemen bereits Probleme mit Speicherfressern (im speziellen ein simpler Druckertreiber eines renomierten Herstellers) hatten welche zunehmends Speicher alloziiert aber nicht wieder freigegeben haben.
> Auf diese Weise war bereits einmal das gesamte Produktivsystem zum Absturz gekommen. Seitdem werden die Resourcen von Programmen und Diensten stets beäugt und ist es nicht gern gesehen, wenn die Resourcen eines Programmes stetig zunehmen.
> Wenn man nun Programme einsetzt bei denen es sozusagen in Ordnung ist, wenn diese zunehmend Speicher beanspruchen, da dieser erst freigegeben wird wenn dieser benötigt wird, geht eine einfach und doch wichtige Kontrollfunktion per einfachen Blick in den Task-Manager verloren.
> ...



Man kann ja das GC.collect automatisch aufrufen, Ich wollte nur erläutern das man es normal nicht macht und nicht nötig ist. Und wenn dein programm mal ein paar Wochen läuft ohne das die Speicherauslastung weiter steigt, dann kannst du ja das GC.collect entfernen.

Also wenn du den GC ständig aufrufst sollte auf jeden Fall der Speicherbedarf nicht immer steigen! Es gilt ja rauszufinden  ob es ein Problem in LibNoDave, oder eines deines CSharp programmes ist!


----------



## argv_user (16 Mai 2010)

Gerade bei Produktivsystemen achte ich auch darauf, dass meine Anwendung keinen Speicher frisst. Ob das jetzt die Anwendung selber ist oder das tolle Framework drumrum ist mir dabei völlig egal. Kann man das ständige Allozieren von Speicher nicht tolerieren, so bleibt leider nichts anderes übrig, als etwas anderes zu verwenden. LibNoDave zB funktioniert auch sehr gut mit ANSI-C.

Der Grund ist natürlich, dass das Verhalten der Müllsammlung (Garbage Collection) nicht sicher vorhersagbar ist. 
Bei PCs, die täglich neu hochfahren oder einen riesigen SWAP haben, ist das kein wesentliches Problem. Aber das ist auch eine andere Liga.


----------



## Question_mark (16 Mai 2010)

*Keine Panik auf der Titanic*

Hallo,



			
				argv_user schrieb:
			
		

> Der Grund ist natürlich, dass das Verhalten der Müllsammlung (Garbage Collection) nicht sicher vorhersagbar ist.



Völlig richtig. Aber grundsätzlich gilt : Keine Panik, wenn nach ein paar Stunden Laufzeit die Speicherauslastung ansteigt, ein vernünftiger Speichermanager räumt den Garbage nicht alle paar Sekunden auf. Sondern nur, wenn weiterer Bedarf an Speicher anfällt. 
Insofern macht es absolut keinen Sinn, den Garbage Collector immer wieder aus dem Programm aufzurufen, das regelt der schon alleine. Unser User "bool" ist da wohl angesichts des steigenden Speicherbedarfs in Panik geraten, aber keine Angst. Ein vernünftiger Speichermanager regelt das schon im Hintergrund. 

@bool : Nimm einfach den Rat von Jochen Kühner an, es besteht kein Grund zur Panik ...
Um das zu verifizieren, starte einfach Deine Applikation und dann noch einige andere Programme, die intensiv Arbeitsspeicher verbrauchen. Und lasse das nicht nur eine Stunde, sondern auch mal einen Tag laufen. Du wirst sehen, alles regelt sich von selbst. Das Verhalten kann man auch in gewissen Grenzen über die Größe der Auslagerungsdatei von Windows steuern.
Anderer Vorschlag : Beobachte die Speicherauslastung und starte Deine Applikation (und nichts anderes). Lass das ganze einen Tag laufen und schließe das Programm. Danach sollte die Speicherauslastung wieder auf dem gleichen Stand wie vor dem Start der Applikation sein. Wenn nicht, hast Du wirklich einen Speicherfresser in Deinem Programm. Aber zum Aufspüren von Speicherleaks gibt es gute :TOOL:s. Bei Delphi gibt es da zum Beispiel den FastMM oder EurekaLog, die zeigen Dir direkt im Quelltext die Zeile des Ursprungs von Speicherleaks an. Ich selber verwende kein VB (und ich weiss auch genau warum), aber grundsätzlich lässt sich das auch auf VB übertragen. 

Also keine Panik, Jochen Kühner liegt da schon richtig mit seiner Vermutung.
Jedenfalls ist es seit Windows XP möglich, Programme zu schreiben, die jahrelang im 24/7 Betrieb ohne Probleme stabil durchlaufen 
Aber um das zu erreichen, muss man schon etwas Erfahrung haben, um Fehlersituationen ohne sichtbare MessageBoxen wie General Protection Fault abzufangen. 

Gruß

Question_mark


----------



## Question_mark (16 Mai 2010)

*???*

Hallo,



			
				bool schrieb:
			
		

> doch wichtige Kontrollfunktion per einfachen Blick in den Task-Manager verloren.



Ähemm, da möchte ich doch gerne mal anmerken : Wer zum Teufel kann denn die Angaben der Speicherauslastung des Systems im TaskManager qualifiziert und sinnvoll auswerten ? 

Ich kann das nicht ! Du etwa, dann herzlichen Glückwunsch, ich ernenne Dich damit zum Experten  

Gruß

Question_mark


----------



## Question_mark (16 Mai 2010)

*???*

Hallo,



			
				bool schrieb:
			
		

> etwa alle zwei bis drei Sekunden 4kB zusätzlichen Speicher reserviert und diesen erst mit Programmende wieder freigibt



Und selbst wenn Du nur ein popeliges Byte in Deinem Programm allozierst, wird der Speichermanager immer 4kB beim System (also Windows) anfordern und belegen. 

Wie das ganze dann verwaltet wird, also überlasse das ganz einfach Deinem Compiler und dem OS. 

Gruß

Question_mark


----------

