PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : .Net: Verfügbaren Speicher auslesen


][immy
2009-08-18, 10:22:53
Ich habe ein kleines Problem beim Upload von Dateien via Webservice. Undzwar benötigt der .net base64-encoder und decoder massig Speicher.
Ich habe leider nur die wahl den Webservice per Soap anzusprechen und ihm so die Datei bereit zu stellen. Daher würde ich vorher gerne Checken ob genug Speicher für den samten Prozess zur verfügung steht.

Ich habe es jetzt erstmal damit probiert den noch verfügbaren Speicher auszulesen:

private PerformanceCounter _memoryCounter = null;


/// <summary>
/// Gets the free memory in bytes.
/// </summary>
/// <returns></returns>
public long GetFreeMemory()
{
if (_memoryCounter == null)
{
_memoryCounter = new PerformanceCounter("Memory", "Available Bytes");
}
return Convert.ToInt64(_memoryCounter.NextValue());

}


Allerdings dürfte dieser Code-Schnipsel spätestens ab Vista Probleme verursachen. Denn der PerformanceCounter zeigt zwar den derzeit verfügbaren Speicher an, jedoch ist im Hintergrund z.B. SuperFetch oder ähnliches aktiv die den verfügbaren Speicher befüllen, aber auch wieder freimachen, wenn dieser benötigt wird. Wie bekomme ich jetzt den Speicher der mir wirklich noch zur Verfügung steht?

Monger
2009-08-18, 12:32:02
[immy;7478759']
Allerdings dürfte dieser Code-Schnipsel spätestens ab Vista Probleme verursachen.
... und zwar schon deshalb, weil Diagnoseinformationen mit Benutzerrechten gar nicht zur Verfügung stehen. Unter Vista gibt es extra eine Benutzergruppe der "Diagnoseberechtigten User", oder so ähnlich. Oder halt der Admin.

Aber schau dir mal die System.GC Klasse genauer an. Hab da z.B. eine Methode namens "getTotalMemory" gesehen... vielleicht hilft dir die weiter. Der GC ist auf jeden Fall am ehesten derjenige, der wissen sollte ob genügend Speicher zur Verfügung steht.

Gnafoo
2009-08-18, 13:53:20
Um mal auf das eigentliche Problem einzugehen:

Wie genau machst du die Umwandlung? In kleinen Blöcken, die du direkt in den Ausgabestream schreibst? Oder nimmst du einfach einen riesigen Datenblock aus dem Speicher und schickst ihn an Convert.ToBase64...(...)? Im zweiten Fall würde mich der Speicherverbrauch nicht wundern (man braucht ja mindestens nochmal so viel Speicher für die Rückgabe, plus Overhead von Base64) und die Sache ließe sich durch eine blockweise Umwandlung deutlich verbessern. Ich kann mir eigentlich nicht so recht vorstellen, dass Convert da so ineffizient ist, wenn man es richtig verwendet.

Übrigens hilft es häufig auch, vorhandene Arrays wiederzuverwenden. Dann hat der GC weniger zu tun.

][immy
2009-08-18, 14:34:52
Um mal auf das eigentliche Problem einzugehen:

Wie genau machst du die Umwandlung? In kleinen Blöcken, die du direkt in den Ausgabestream schreibst? Oder nimmst du einfach einen riesigen Datenblock aus dem Speicher und schickst ihn an Convert.ToBase64...(...)? Im zweiten Fall würde mich der Speicherverbrauch nicht wundern (man braucht ja mindestens nochmal so viel Speicher für die Rückgabe, plus Overhead von Base64) und die Sache ließe sich durch eine blockweise Umwandlung deutlich verbessern. Ich kann mir eigentlich nicht so recht vorstellen, dass Convert da so ineffizient ist, wenn man es richtig verwendet.

Übrigens hilft es häufig auch, vorhandene Arrays wiederzuverwenden. Dann hat der GC weniger zu tun.

Das eigentlich Problem ist die serialisierung den .net Standardmäßig einsetzt um objekte in XML umzuwandeln. enthält ein Objekt z.B. ein byte[] müssen diese daten erst base64 kodiert werden.
bei meinen Tests ist es dann immer so gewesen, das immer wenn z.B. eine 100 MB große Datei zum Webservice geschickt werden sollte, das ganze erstmal xml-serialisiert wird (sprich byte[] wird zu base64 kodiert) und dann im Speicher bis zu 700 MB aufgebläht wurde. Sprich 600 MB an overhead im Speicher.
das das ganze vielleicht doppelt im Speicher verhanden wäre, würde ich ja noch akzeptieren können aber von 100 auf 600 mb ist einfach zu viel des guten (besonders wenn zumindest bei den Tests der Server auch der Client ist und der Webservice ebenfalls nochmal so viel speicher benötigt).

leider stößt man so auch noch sehr schnell auf die 32-bit Grenze. Jetzt könnte ich natürlich einen komplett eigenen serializer schreiben und den Webservice-kommunikation komplett selbst übernehmen, aber das würde wohl erstmal wieder zeit kosten und das wäre auch nicht der sinn hinter .net alles dann doch wieder selbst zu implementieren (fernab vom quasi-standard).

Protokolle wie mtom scheiden leider aus, weil das system dahinter leider keine WCF-Services zulässt.

Monger
2009-08-18, 14:44:18
Könntest du ein paar Zeilen Code von der entsprechenden Stelle posten?

Kann natürlich sein dass SOAP nicht auf so große Datenpakete ausgelegt ist, aber so rein gefühlsmäßig müsste sich das doch durch entsprechendes Streaming machen lassen. Gerade wenn das Objekt in XML gewandelt wird, gibt es doch keinen Grund, mit der Übertragung erst dann anzufangen wenn das Objekt komplett serialisiert ist.

][immy
2009-08-18, 15:02:59
... und zwar schon deshalb, weil Diagnoseinformationen mit Benutzerrechten gar nicht zur Verfügung stehen. Unter Vista gibt es extra eine Benutzergruppe der "Diagnoseberechtigten User", oder so ähnlich. Oder halt der Admin.

Aber schau dir mal die System.GC Klasse genauer an. Hab da z.B. eine Methode namens "getTotalMemory" gesehen... vielleicht hilft dir die weiter. Der GC ist auf jeden Fall am ehesten derjenige, der wissen sollte ob genügend Speicher zur Verfügung steht.

wenn ich mich recht ensinne ist die Methode GetTotalMemory nur dazu gut herauszufinden wie viel speicher gerade im aktuellen Process genutzt wird. Ist aber leider nicht dazu gut, herauszufinden wie viel speicher noch zur verfügung steht.

und nach möglichkeit will ich nicht sowas machen wie eine schleife die immer wieder etwas mehr speicher anfordert um dann mitzuzählen wie lange ich den speicher aufblähen kann. das wäre doch sehr ineffektiv.

Könntest du ein paar Zeilen Code von der entsprechenden Stelle posten?

Kann natürlich sein dass SOAP nicht auf so große Datenpakete ausgelegt ist, aber so rein gefühlsmäßig müsste sich das doch durch entsprechendes Streaming machen lassen. Gerade wenn das Objekt in XML gewandelt wird, gibt es doch keinen Grund, mit der Übertragung erst dann anzufangen wenn das Objekt komplett serialisiert ist.
also code von der stelle zu posten dürfte schwierig werden, weil das .net Framework ja die eigentlich arbeit macht.

an sich musst du nichts weiter tun, als an einen webservice (den man vorher ins projekt eingebunden hat) ein byte[] zu schicken.
auf 32-bit maschienen wirst du dann merken das bei ~100 MB schluss ist (nicht vergessen die bytes zu füllen)

Beispiel-Webservice-Methode:

[WebMethod]
public byte[] SendDataAsBytes(byte[] bytesReceived)
{
bytesReceived = null;
GC.Collect(2);
return new byte[5];
}


Beispiel Client methode (Webservice als .net 2.0 WebService einbinden):

private void RunTransfer()
{
DataService proxy = new DataService();
int mbToSend = 50;
int sendsize = mbToSend * 1024 * 1024;
Random rand = new Random();
byte[] bytesToSend = new byte[sendsize];
for (int i = 0; i < sendsize; i++)
{
bytesToSend[i] = Convert.ToByte(rand.Next(0, 255));
}
byte[] bytesReceived = null;

try
{
bytesReceived = proxy.SendDataAsBytes(bytesToSend);
}
catch (Exception ex)
{
}
bytesReceived = null;
bytesToSend = null;
GC.Collect(2);
}



nochmal, bevor das jemand falsch versteht. es geht mir um den overhead im speicher, nicht um die 33% overhead die durch die Soap-serialisierung (bzw. base64) für die gesendeten Daten zustande kommen.

The_Invisible
2009-08-18, 16:55:56
wie wärs mit einem native base64 codec?

in c++ gibts zb haufenweise im netz.

mfg

Monger
2009-08-18, 22:42:32
wie wärs mit einem native base64 codec?

in c++ gibts zb haufenweise im netz.

mfg
Mann mann Mann...

Lies dir bitte gefälligst den Thread durch! Nicht nur muss es eine .NET Implementierung sein, sondern auch der interne Serialisierungsmechanismus für SOAP. Ist ja nicht so, dass man das jenseits von SOAP nicht auch in .NET hinkriegen könnte.


@topic:
Ich muss zugeben, ich hab von Web Technologien nicht viel Ahnung, und leider auch nicht von SOAP. Aber von dem was ich so gelesen habe, ist das in der Form schlicht nicht möglich. SOAP basiert darauf, Nachrichten als ganzes per POST zu verschicken. Ein Request gibt halt eine Antwort. Ich bin auch nicht ganz sicher, ob ein so großes Datenpaket vernünftig ist - was passiert denn in der Zwischenzeit mit der Weboberfläche? Gar nichts, oder? Der Anwender kriegt erst eine Antwort wenn alles hochgeladen wurde. Wenn das 100MB oder mehr sind, kann das je nach Bandbreite dazu führen dass der Anwender minutenlang vor einem nicht mehr reagierenden Formular sitzt. Das kann ganz schön frustrierend sein - zumindest sowas wie ein Fortschrittsbalken wäre nett.

Deshalb meine Frage: kannst du die Pakete eventuell splitten? Sprich: jede Abfrage von SendDataAsBytes gibt dir halt die nächsten paar hundert, bis nichts mehr kommt. Sobald eine Anfrage ein leeres Array zurückgibt, weißt du dass du serverseitig fertig bist. Das ließe sich dann auch mit entsprechenden Stream Readern / Writern kombinieren, und das sollte dann hier wie da die Speicherbelastung sehr gering halten.

][immy
2009-08-18, 23:05:36
@topic:
Ich muss zugeben, ich hab von Web Technologien nicht viel Ahnung, und leider auch nicht von SOAP. Aber von dem was ich so gelesen habe, ist das in der Form schlicht nicht möglich. SOAP basiert darauf, Nachrichten als ganzes per POST zu verschicken. Ein Request gibt halt eine Antwort. Ich bin auch nicht ganz sicher, ob ein so großes Datenpaket vernünftig ist - was passiert denn in der Zwischenzeit mit der Weboberfläche? Gar nichts, oder? Der Anwender kriegt erst eine Antwort wenn alles hochgeladen wurde. Wenn das 100MB oder mehr sind, kann das je nach Bandbreite dazu führen dass der Anwender minutenlang vor einem nicht mehr reagierenden Formular sitzt. Das kann ganz schön frustrierend sein - zumindest sowas wie ein Fortschrittsbalken wäre nett.

Deshalb meine Frage: kannst du die Pakete eventuell splitten? Sprich: jede Abfrage von SendDataAsBytes gibt dir halt die nächsten paar hundert, bis nichts mehr kommt. Sobald eine Anfrage ein leeres Array zurückgibt, weißt du dass du serverseitig fertig bist. Das ließe sich dann auch mit entsprechenden Stream Readern / Writern kombinieren, und das sollte dann hier wie da die Speicherbelastung sehr gering halten.

es gibt keine weboberfläche und dementsprechend wartezeiten. der webservice ist "nur" dafür gut das der server irgendwo stehen kann, wird aber trotzdem nur von einer client-software angesprochen (eine art von automatischer import-dienst)

meine bisherige lösung sieht es auch vor die dateien chunk-basiert (oder auch fragment-basiert) hochzuladen wobei es dies das übel nur abmildert, aber nicht gänzlich "beseitigt".

was ich natürlich noch machen könnte ist das byte[] wirklich selbst base64 zu kodieren und dann das ganze als string hochzuladen. dadurch müsste die serialisierung zumindest nichts mehr base64-codieren.

allerdings ist mir nicht wirklich wohl bei der sache den webservice die rohdaten als string zu übergeben. aber dürfte wohl die einfachste möglichkeit sein.


das basis-problem die noch verfügbare speichergröße auslesen zu können, würde aber trotzdem bei dem ganzen ein wenig hilfreich sein.

Monger
2009-08-18, 23:19:03
[immy;7480371']
allerdings ist mir nicht wirklich wohl bei der sache den webservice die rohdaten als string zu übergeben.
Mir auch nicht. Bei Strings fängst du dir dann plötzlich so skurile Probleme wie kulturabhängige Zeichensätze o.ä. ein.

Aber wenn du die Pakete ohnehin schon fragmentierst, verstehe ich ehrlich gesagt nicht wieso du Serverseitig immer noch so einige hundert MB an RAM verbrätst. Irgendwas passt da nicht.

Gast
2009-08-18, 23:22:46
Ich würde jetzt so auf die Schnelle behaupten, dass du einen Stream zur Datei öffnen musst und dann häppchenweise den Stream auslesen und ihn in den WebRequest Stream reinschreibst.

Gnafoo
2009-08-19, 02:42:02
Hm ich habe mal nachgesehen, wie die Leute bei Amazon S3 das in ihrer SOAP-Schnittstelle gelöst haben [1]. Dort gibt es eine Methode PutObjectInline, welche die Daten im SOAP-Paket als base64 hochlädt (mit der Einschränkung < 1MB) und PutObject, welches offensichtlich ein DIME-Attachment benutzt, um die Daten zu übertragen.

Googlen nach DIME [2] bringt zum Vorschein, dass man das mit den Web Service Enhancements 2.0 für .NET nutzen kann und damit binäre Daten ohne Overhead verschicken kann. Scheint genau das zu sein, was du suchst [3].


The Web Services Enhancements for Microsoft .NET (WSE) supports attaching files to SOAP messages outside the SOAP envelope; these files are not serialized as XML. This can be beneficial when sending large text or binary files because XML serialization is a very costly process and can result in files much larger than the originals. Direct Internet Message Encapsulation (DIME) is a lightweight, binary message format that WSE uses to encapsulate SOAP messages and their attachments in a DIME message.


[1] http://docs.amazonwebservices.com/AmazonS3/latest/SOAPPutObject.html
[2] http://www.google.de/search?hl=de&q=.net+dime+attachment&btnG=Suche&meta=
[3] http://msdn.microsoft.com/en-us/library/ms824597.aspx

Gast
2009-08-19, 17:41:06
1.) Ich verwende immer fixe Limits, die sich bisher ziemlich gut bewährt haben:

32KB Cache für Festplatten und Netzwerkzugriffe, da das gut in den TCP- Buffer passt. Da ist die Performance höher, auch wenn 1MB Cache da wären, weil da Lesen und Schreiben parallel laufen können
1MB Cache für andere Operationen die im Speicher ablaufen, wo ich Cache benötige. Das sollte jeder Rechner auch mehrmals locker zur Verfügung stellen können, ohne dass es auffällt.
Bis zu 10MB Cache für Operationen, wenn durch mehr Cache deutlich Performance gespart wird außer ein bisschen weniger Overhead z.B. wenn ich eine Datei nicht doppelt lesen muss.

Bei der Verarbeitung von Dateien MUSS auf jeden Fall eine Variante implementiert werden, die das ganze nicht im RAM macht, wenn möglich ohne temporäre Datei. Man sollte immer damit rechnen, dass Dateien größer als 4GB sein können, auch wenn es in der Situation unüblich ist (z.B. Webserver), also auch alle Größenangaben als 64 Bit Variablen speichern und übertragen. Zusätzlich solltest du davon ausgehen, dass bis zu 10 Instanzen deines Programms laufen (grobe Faustregel), also einfach so 1GB RAM zu reservieren ist auch schlecht.

Ich würde alles unter 10MB mit der schnellen internen Variante bearbeiten und alles darüber mit der langsamen Variante in 1MB Stücken.

Zusätzlich solltest du nicht davon ausgehen, dass eine Datei >4GB als temporäre Datei auf dem System Platz findet.

2.) Es ist eine Unart sämtlichen verfügbaren Speicher zu belegen, nur weil dieser da ist. Das ist auf einem XP Desktop Rechner kein Problem. Bei Vista fährt schon Superfetch zurück, bei einem normalen Server wird der SQL Server ziemlich langsam sein, wenn dieser nachher gestartet wird, weil dieser keinen Cache mehr bekommt und bei einem virtualisierten Server killst du sowieso damit alles, weil die Kapazitäten überbucht sind d.h. z.B. 20 virtuelle Maschinen zu je 4GB RAM auf einer physischen Maschine mit 16GB. Wenn dann auf 4 Rechnern dein schönes Tool läuft, werden die anderen 12 nur mehr auslagern und das schaut dann nicht so aus, wie wenn der RAM auf einem physischen System zu knapp wird, sondern das Betriebssystem mein trotzdem noch, dass es quer über alles zugreifen muss und dann wird alles stehen.

][immy
2009-08-20, 18:05:56
Hm ich habe mal nachgesehen, wie die Leute bei Amazon S3 das in ihrer SOAP-Schnittstelle gelöst haben [1]. Dort gibt es eine Methode PutObjectInline, welche die Daten im SOAP-Paket als base64 hochlädt (mit der Einschränkung < 1MB) und PutObject, welches offensichtlich ein DIME-Attachment benutzt, um die Daten zu übertragen.

Googlen nach DIME [2] bringt zum Vorschein, dass man das mit den Web Service Enhancements 2.0 für .NET nutzen kann und damit binäre Daten ohne Overhead verschicken kann. Scheint genau das zu sein, was du suchst [3].



du meinst WCF (ehemals web service enhancements) das mit .net 3 reingekommen ist. Allerdings kann ich leider (aufgrund des grundsystems) WCF nicht nutzen. In WCF wäre das datenprotokoll mtom verfügbar, was rohdaten qasi hinter den soap-request hängt und die daten dadurch nicht base64 kodiert werden müssen.

aber wie schon erwähnt, kann ich das nicht nutzen. aber der overhead beim übertragen interessiert mich nicht wirklich. ob es sich um 100 MB oder 133 MB handelt, die ich zum server übertrage ist mir mehr oder minder egal. In den bereichen kommt es auf ein paar sekunden auch nicht mehr an.

das problem ist einfach, das der soap-serializer einfacher zu viel speicher schluckt. habe das Problem jetzt mit chunks gelöst (oder dimes, oder wie die technik auch immer heißt). ich übertrage jetzt nur noch in 5 mb paketen. dadurch bleibt der speicherverbrauch auf beiden seiten überschaubar. allerdings muss ich leider auf serverseite das dokument trotzdem komplett im speicher verwahren (hab da keine andere chance das temporär irgendwo hinzuschreiben).

daher suche ich nach wie vor nach einer lösung für das problem, denn ich will nicht, das eine methode eine "outofmemory" exception wirft und diese dann immer wieder neu durchgestartet wird, obwohl kein speicher freigeschaufelt werden konnte. zudem wäre es schön schon vor der übertragung feststellen zu können, ob ich es schaffe die komplette auf serverseite in den speicher zu laden. denn sonst kann ich mir die übertragung direkt sparen.

@gast
das mit der 4 GB grenze ist mehr oder minder kein problem. das dahinter stehende MS system ist schon mit 100MB Dateien sau lahm. daher würden wir unsere kunden schon davon abbringen können so große dateien in dem system zu speichern.

Gnafoo
2009-08-20, 19:10:13
Hm ich kenne mich da jetzt auch nicht so gut aus. Du hast Recht insofern, als dass WCF jetzt diese Funktionalitäten übernommen hat. Aber WSE 2.0 ist als Add-On bereits für .NET >= 1.1 verfügbar [1]. Vielleicht reicht das ja schon.

Wenn das nicht möglich ist, ist die einzige Alternative vermutlich wirklich das Übertragen mittels kleinerer Pakete. Nicht ganz klar ist mir allerdings, warum du die Datei im Speicher halten musst. Was passiert denn mit den Daten? Werden die nur verarbeitet und dann über das Netz woanders hingeschickt? Werden die nach der Verarbeitung verworfen, oder anderweilig gespeichert? (Wenn ja, warum dann nicht direkt dort hinschreiben?!) Oder musst du mit einer Schnittstelle zusammenarbeiten, die kein Streaming unterstützt, sondern ein byte[] will?

Evtl. lässt sich das ganze ja auch streambasiert verarbeiten auf dem Server, oder brauchst du wahlfreien Zugriff auf der gesamten Datenmenge?

[1] http://en.wikipedia.org/wiki/Web_Services_Enhancements