Diskuze: XmlSerializer při serializování nebo deserializování 200Mb vyskočí využití paměti na >3GB
V předchozím kvízu, Test znalostí C# .NET online, jsme si ověřili nabyté zkušenosti z kurzu.
Marian Benčat:30.9.2017 17:56
To že xml serializer žere více než xml i několikanásobně je zcela normální... Určitě bych pooremyslel po tom, to parsovat po částech... navíc mi nedává absolutně smysl, parsovat 500mb XML a dávat to.jako observable kolekci.. To ti fakt neprojde.. Budes muset udělat nějaký load on demand.. V ui ku toho wpfka tedy budeš.mít nějaký list s podporou lazy loadingu..
Jan Vargovský:30.9.2017 19:40
XML jde krásně parsovat i sekvenčně. Zkus tam místo MemoryStreamu použít nějaký FileStream, který by to nemusel načítat celé do paměti ale spíše po částech. BinaryFormatter jsi zvažoval? Nebo je nutné zachovat XML?
U 200MB souboru už bych se zamejšlel nad tím, jestli je XML dobrej formát pro ukládání, binárně s kompresí by ses dostal na zlomek z těch 200 MB a nemusel bys řešit paměť
Jirka Jr:1.10.2017 16:06
ahoj,
pokud by slo o pouhe cteni megavelkeho xml souboru, doporucil bych pouzit xmlreader, kteryzto umoznuje pracovat pouze s casti souboru v pameti.
ale tady jde o serializaci/deserializaci, kterezto primarni ucel je pohodli programatora, aby nemusel resit nizkourovnove srajdy.
tudiz nebudu doporucovat prejit na nizsi uroven a pracovat primo s xml formatem rucne
tedy jedine me napada neserializovat celou kolekci najednou do jednoho velkeho souboru, ale v cyklu pekne co prvek kolekce, to jeden xml soubor
a pri deserializaci zase peknev cyklu soubor po souboru deserializovat a pridavat do kolekce.
a hlavne pri serializaci i deserializaci uvolnovat prubezne pamet, aby se
nezaplnila
tj.
- rusit reference na objekty, pokud to zabere
- nebo volat Dispose, pokud to zabere
- nebo serializaci i deserializaci jednoho souboru hodit do funkce ... to by snad melo donutit garbage collector k akci na konci kazdeho zavolani funkce
- nebo si tam dat citac a po kazdych X objektech (X si podle pozorovani zvol sam) zavolat GC.Collect()
btw pokud ten soubor jen deserializujes a nemas moznost ovlivnit serializaci (uzavreny kod, data od zakaznika apod.) ... tak tam by potom bylo asi potreba vyuzit xmlreader, vytahovat z xml jeden prvek kolekce za druhym, obalit kazdy prvek prislusnou deklaraci xml a pak deserializovat a rucne pridat do kolekce
Jirka Jr:1.10.2017 17:23
jeste me ted napadlo ... proc tam pouzivas ten MemoryStream?
jestli to xml nacitas odnekud ze site ci z databaze a ne ze souboru a nemas vliv na jeho tvorbu (tedy serializace se deje nekde, kam nemuzes zasahovat), tak to pak je jina
to bude asi chtit neco jako nasledujici:
- pomoci xmlreaderu postupne nacitat xml reprezentace jednotlivych prvku kolekce prvek po prvku
- z reprezentace kazdeho prvku ziskat zpet xml text a obalit prislusnou xml hlavickou
- vysledne xml deserializovat do objektu prvku a ten pak pridat do kolekce
mozna by to slo jeste o neco jednoduseji, ale je to jen nastrel zatim
pokud je tohle tvuj pripad, dej sem vedet a zkusime se s tim poprat
Jako první bych chtěl poděkovat všem za jejich tipy.
Pár poznámek...
- Chci aby byla data velmi těžko čitelná, používal jsem XML -> Base64 + nějáké posouvání znaků,
- XML serializer je na pamět náročnější než binaryformatter, jak jsem dnes zjistil, v binary je soubor 150MB místo 220MB u XML, XML serializer bere 3GB, a binary serializer 2GB, což dává vzhledem k výslednému souboru logiku
- Když výsledný soubor zkomprimuji , tak dostanu 85MB, což je dobré, ale problém je, že se o mnoho zvýší loading, to by ale nebyl ten hlavní problém, můžu do loadovat v jiném threadu, ale problém je samotná serializace nebo deserializace a následné zvýšené pamětové nároky. Kdyby byl soubor např. 500MB, což není nemožné, tak by měla spousta počítačů problémy se spuštěním a správnou funkčností
- Nemohu si dovolit výsledek, nebo průběžný výpočet ukládat někam do PC, viz výše (nečitelnost)
Takže se nejlépe jeví načítat jak tu už zaznělo prvek po prvku a přidávat do kolekce, co vy na to? S tím, že bych vytvořil v folderu nějákou složku data, kam bych ukládal ty prvky? Nebo existuje nějáké lepší řešení? Leda tak nějáká databáze, ale musela by být kódovaná a tím pádem by tak celkově ztrácely význam?
Jirka Jr:2.10.2017 2:02
takže ... berouce inspiraci v následujícím odkazu a maje náladu se sám pocvičit jsem spatlal malé konzolové demo
https://stackoverflow.com/…rom-a-stream
v podstatě jde o to, že při serializaci do souboru i deserializaci ze souboru se mezi filestream a xmlserializer vloží šifrovací vrstva v podobě CryptoStream.
doufám, že bloky using{} v šifrovací i dešifrovací funkci by mohly zajistit uvolnění nepotřebných objektů z paměti na konci bloku, čímž by se vyřešil problém s pamětí ... jinak se to ještě bude muset potunit.
demoprogram funguje následovně
- pokud není v programu nastaven klíč a inicializační vektor pro šifrováíní, vygeneruje je a vypíše
- buď deserializuje kolekci ze souborů, pokud existují ... jinak kolekci vygeneruje
- poté kolekci vypíše
- kolekci zmodifikuje
- kolekci znovu vypíše
- vymaže stávající soubory se serializovanými daty
- kolekci serializuje do nových souborů
je tu problém, kam uložit klíč, pokud ne do programu, jako to mám já ... ale určitě je lepší klíč v programu, než base64
jinak se dá použít asymetrická kryptografie a využít windowsí úložiště certifikátů, je-li to extrémně nutné, ale do toho se mi už nechtělo
tak a tady je důkaz místo slibů:
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Xml.Serialization;
using System.Security.Cryptography;
namespace HugeCollectionSerialize
{
public class MyClass
{
public int a, b;
public MyClass() { }
public MyClass(int _a, int _b)
{
this.a = _a;
this.b = _b;
}
public override string ToString()
{
return "[a: " + this.a + ", b: " + this.b + "]";
}
}
class Program
{
private static byte[] desIV = { 0x0, 0xb, 0xe1, 0xac, 0xfe, 0xe4, 0x44, 0x9f };
private static byte[] desKey = { 0xF4, 0x36, 0xAF, 0xC7, 0xC6, 0xAE, 0x24, 0x68 };
static void Main(string[] args)
{
if (desKey == null)
{
Console.Out.WriteLine("There is no key and initialization vector ... Generating ...");
generateIVAndKey();
Console.Out.WriteLine("\nPut the key into your code.");
Console.Out.WriteLine("Press <Enter> to quit ...");
Console.In.ReadLine();
Environment.Exit(0);
}
ObservableCollection<MyClass> col;
if (System.IO.File.Exists("c:\\Users\\doma\\hugecol0.dat"))
{
Console.Out.WriteLine("Deserializing collection items ...");
col = deserializeCollection("c:\\Users\\doma\\hugecol");
}
else
{
Console.Out.WriteLine("Creating collection ...");
col = createCollection();
}
Console.Out.WriteLine("Printing collection ...");
printCollection(col);
Console.Out.WriteLine("Modifying collection ...");
modifyCollection(col);
Console.Out.WriteLine("Printing collection ...");
printCollection(col);
Console.Out.WriteLine("Serializing collection ...");
serializeCollection("c:\\Users\\doma\\hugecol", col);
Console.Out.WriteLine("Press <Enter> to quit ...");
Console.In.ReadLine();
}
private static ObservableCollection<MyClass> deserializeCollection(string fileTemplate)
{
ObservableCollection<MyClass> col = new ObservableCollection<MyClass>();
DES des = new DESCryptoServiceProvider();
for (int i = 0; File.Exists(fileTemplate + i + ".dat"); i++)
{
MyClass item = DecryptAndDeserialize<MyClass>(fileTemplate + i + ".dat", des);
col.Add(item);
}
return col;
}
private static ObservableCollection<MyClass> createCollection()
{
ObservableCollection<MyClass> col = new ObservableCollection<MyClass>();
for(int i = 0; i < 5; i++)
{
MyClass mc = new MyClass(i, i + 1);
col.Add(mc);
}
return col;
}
private static void printCollection(ObservableCollection<MyClass> col)
{
foreach(MyClass item in col)
{
Console.Out.WriteLine(item.ToString());
}
}
private static void modifyCollection(ObservableCollection<MyClass> col)
{
foreach (MyClass item in col)
{
item.a += item.b;
}
}
private static void serializeCollection(string fileTemplate, ObservableCollection<MyClass> col)
{
string file = null;
int i = 0;
while(true)
{
file = fileTemplate + i + ".dat";
if(! File.Exists(file))
{
break;
}
File.Delete(file);
i++;
}
DES des = new DESCryptoServiceProvider();
for (i=0;i<col.Count();i++)
{
file = fileTemplate + i + ".dat";
EncryptAndSerialize<MyClass>(file, col[i], des);
}
}
private static void generateIVAndKey()
{
DES des = new DESCryptoServiceProvider();
bool first;
des.GenerateIV();
des.GenerateKey();
Console.Out.Write("IV: {");
first = true;
foreach (byte b in des.IV)
{
Console.Out.Write((first ? "" : ", ") + "0x" + b.ToString("x"));
first = false;
}
Console.Out.WriteLine("}");
Console.Out.Write("Key: {");
first = true;
foreach (byte b in des.Key)
{
Console.Out.Write((first ? "" : ", ") + "0x" + b.ToString("X"));
first = false;
}
Console.Out.WriteLine("}");
}
private static void EncryptAndSerialize<T>(string filename, T obj, SymmetricAlgorithm sa)
{
using (FileStream fs = File.Open(filename, FileMode.Create))
{
using (CryptoStream cs = new CryptoStream(fs, sa.CreateEncryptor(desKey, desIV), CryptoStreamMode.Write))
{
XmlSerializer xmlser = new XmlSerializer(typeof(T));
xmlser.Serialize(cs, obj);
}
}
}
private static T DecryptAndDeserialize<T>(string filename, SymmetricAlgorithm sa)
{
using (FileStream fs = File.Open(filename, FileMode.Open))
{
using (CryptoStream cs = new CryptoStream(fs, sa.CreateDecryptor(desKey, desIV), CryptoStreamMode.Read))
{
XmlSerializer xmlser = new XmlSerializer(typeof(T));
return (T)xmlser.Deserialize(cs);
}
}
}
}
}
+20 Zkušeností
+2,50 Kč
Jirka Jr:2.10.2017 2:05
teď koukám, že jsem tam nechal natvrdo absolutní cestu k těm souborům
takže to už ponechám na úpravu čtenáři ...
Řešení původního dotazu: použij FileStream místo MemoryStream. Toto bych považoval za jednoznačně funkční řešení popsaného problému. Ale protože mě má drahá polovička učí čtení mezi řádky, uvažuji dále.
Předpokládám, že nejdříve vše serializuješ do MemoryStream, pak to celé přeženeš do BASE64 abys mohl posouvat lidem čitelné znaky. Moc nechápu, k čemu ten Base64, ale budiž.
Osobně bych si udělal "kaskádu" streamů: deflate->crypto->file; a tomu serializeru podstrčit deflate. Toto by mělo vše zapisovat po částech (tak, jak to bude zapisovat ten serializer). Výsledný soubor by měl obsahovat zazipovaná a zašifrovaná data. A žádný kus kódu nebude pracovat s celým balíkem dat; ty streamy pracují jen s malými buffery. Pro deserializaci se použije kaskáda opačná.
Jirka Jr:3.10.2017 20:24
pomohli jsme ti nějak? je to na akceptaci řešení? nebo už jsi to vyřešil jinak?
Jirka Jr:6.10.2017 18:38
v klidu
vubec nespecham, jen si zjistuju, jestli me usili bylo k necemu ... ale vypada to, ze asi jo
Funguje to samozřejmě perfektně, ještě jsem to protáhl ještě deflate a výsledný soubor je 5x menší při o trošku zvýšeném nahrávacím času... Neznám pozadí těchto streamů a proto jsem netušil, že to udělá takový rozdíl v náročnosti. Nechal jsem tam tedy jen serialize a deserialize s tím, že tam jsou tři usingy ty dva + deflate
Díky moc za pomoci a vážím si toho
Jirka Jr:8.10.2017 23:20
tak to jsem rad, ze to vali a nehlti pamet i bez rozsekani na vic souboru
mam-li byt uprimny, netusil, jsem, ze ty streamy jsou takhle efektivni ... to potvrzuje to, co pak psal zelvicek
spis jsem doufal, ze zabere to rozdeleni kolekce na vic souboru
plus ty usingy, ktere se postaraji o uklid pameti za kazdym jednotlivym
souborem
kdyztak mas to rozsekani v zaloze, kdyby to do budoucna narostlo jeste vic ...
Marian Benčat:9.10.2017 9:10
Libi se mi odpoved, jen k par vecem co si napsal:
"a hlavne pri serializaci i deserializaci uvolnovat prubezne pamet, aby se
nezaplnila
tj.
rusit reference na objekty, pokud to zabere
nebo volat Dispose, pokud to zabere
nebo serializaci i deserializaci jednoho souboru hodit do funkce ... to by snad
melo donutit garbage collector k akci na konci kazdeho zavolani funkce
nebo si tam dat citac a po kazdych X objektech (X si podle pozorovani zvol sam)
zavolat GC.Collect()"
Ani jedno z toho, co jsi napsal neuvolňuje nutně pamět, ale to možná víš a kdyby si nevěděl tak:
- rusit reference na objekty, pokud to zabere
Toto neznamená, že se objekt nedostane do další generace, pokud nemá dokončený finalizer, tak velmi pravděpodobně bude v paměti i po GC, protože při tom prvním se zavolá finalizer a objekt přežije do dalši generace.
- nebo volat Dispose, pokud to zabere
Neuvolní paměť a ani k tomu neslouží, je to k uvolnění unmanaged zdrojů - to může pomoci třeba k zavření souborů, connectionu atp, zároveň to však může napomoci i přeci jen k uvolnění paměti o něco dříve, protože se může začít uvolňovat paměť po dokončení už v současné generaci
- nebo serializaci i deserializaci jednoho souboru hodit do funkce ... to by
snad melo donutit garbage collector k akci na konci kazdeho zavolani funkce
Ne, garbage collector se urcite nevolá po každém ukončení funkce, volá se většinou, když dojde paměť, nebo když se překročí určitý treshold (třeba veliksot nastaveného memory heapu), nebo když se zavolá manuálně GC collect.
- nebo si tam dat citac a po kazdych X objektech (X si podle pozorovani zvol
sam) zavolat GC.Collect()"
Samotne mu to moc nepomuze, musel by nejdrive zavolat dispose nad věcma a pak pravděpodobně 2x zavolat GC.Collect() což je perfektní způsob jak si zabít výkon.
Jinak abych byl přesný.. při každém GC se nemusí uvolňovat všechny generace, takže to že zavoláš GC.Collect() rozhodně neznamená, že se ti uvolní objecty, které se při předchozím GC finalizovaly.
Jirka Jr:9.10.2017 10:37
S tím dispose vím, že uvolňuje prostředky a že bez toho se nedá uvolnit pamět po objektu
jinak tak hluboko do GC jsem jeste nepronikl, takže moje rady byly spíše intuitivní a výsledkem mého pozorování z velké výšky ...
takže každopádně velké díky za opravy a poučné upřesnění ...
teď už aspoň vím, proč musím GC volat dvakrát a že to není kvůli debilitě Microsoftu, jak jsem si dlouho myslel
Marian Benčat:9.10.2017 11:30
Není to debilita microsoftu, je to tak vymyšlené kvůli efektivitě Proto jsou i různé generace a proto je třeba něco jako large object heap;-) Obecně, pokud potřebuješ volat GC ručně, tak je něco někde špatně. Ideální moment totiž je, když paměť pravidelně nepřiděluješ a neuvolňuješ.. proto se dělají věci jako object pooly a ta paměť se reusuje... -> pouze se nějakým bitem dá vědět, jestli je objekt obsazený či ne.. Pokud tedy chceš mít ještě více efektivní sw i v tom C#, tak si tam ten object pool taky uděláš, protože kromě toho, že ušetříš za alokaci a dealokaci, tak máš data co zpracováváš v jeden moment více v paměti u sebe, což má taky extrémní vliv na performance.
I v C++, C jsi toto dělal naprosto běžně a v C++ jsi k tomu měl i syntactic sugar v podobě placement new.. Vždy se snažíš resourcy uvolňovat a alokovat co nejméně - to je důvod, proč se třeba i Connectiony pooli na základě connection stringu...
Dokonce i u jazyka, který nemá garbage collector, se používá výraz garbage collecting, .. procesu, kdy se v memory managementu (několik linked listu), spojují chunky paměti do větších atp. - mimochodem to je důvud existence large object poolu...
Takže to, že při každém GC nečistí všechny generace není debilita microsoftu, naopak hodně velká inteligence Žádný rozumný GC neuvolňuje všechnu nevyužitou paměť najednou.. Doporučuji se kouknout na rozumné jazyky a zjistíš, že to tak dělají všude, protože ví, jak funguje memory management.
Pak existují jazyky, které jsou tak debilní, že se tam debilní GC ztratí.. třeba PHP, které (pokud to nezměnili) generace v GC nepoužívá a taky to podle toho vypadá .
Zobrazeno 20 zpráv z 20.