NOVINKA! E-learningové kurzy umělé inteligence. Nyní AI za nejlepší ceny. Zjisti více:
NOVINKA – Víkendový online kurz Software tester, který tě posune dál. Zjisti, jak na to!

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.

Aktivity
Avatar
Dog
Člen
Avatar
Dog:30.9.2017 17:11

Ahoj, problém jsem popsal v titulku, jde o to, že při serializaci nebo deserializaci třídy (XmlSerializer) vyskáče využití RAM do takových výšin (11-12x původní xml), že jsem musel sestavovat jen pro 64x.

ObservableCollection<T> observableCollection;

//Deserializer
using (MemoryStream stream = new MemoryStream(temp2))
{
    XmlSerializer xmls =
        new XmlSerializer(typeof(ObservableCollection<T>));
    observableCollection =
        xmls.Deserialize(stream) as ObservableCollection<T>;
}
return observableCollection;

Musím po každém volání volat GC a hlavně ten načítanej xml se bude stále zvětšovat. Takže je možné, že by se to za chvíli nanačetlo ani na 8GB RAM.

Budu rád, za jakékoli řešení.
Díky

 
Odpovědět
30.9.2017 17:11
Avatar
Odpovídá na Dog
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..

Editováno 30.9.2017 17:56
Nahoru Odpovědět
30.9.2017 17:56
Totalitní admini..
Avatar
Jan Vargovský
Tvůrce
Avatar
Odpovídá na Dog
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?

 
Nahoru Odpovědět
30.9.2017 19:40
Avatar
Luboš Běhounek Satik:30.9.2017 20:39

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ěť

Nahoru Odpovědět
30.9.2017 20:39
https://www.facebook.com/peasantsandcastles/
Avatar
Jirka Jr
Člen
Avatar
Odpovídá na Dog
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/de­serializaci, 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

 
Nahoru Odpovědět
1.10.2017 16:06
Avatar
Jirka Jr
Člen
Avatar
Odpovídá na Dog
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:

  1. pomoci xmlreaderu postupne nacitat xml reprezentace jednotlivych prvku kolekce prvek po prvku
  2. z reprezentace kazdeho prvku ziskat zpet xml text a obalit prislusnou xml hlavickou
  3. 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

Editováno 1.10.2017 17:24
 
Nahoru Odpovědět
1.10.2017 17:23
Avatar
Dog
Člen
Avatar
Dog:1.10.2017 20:22

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?

 
Nahoru Odpovědět
1.10.2017 20:22
Avatar
Jirka Jr
Člen
Avatar
Odpovídá na Dog
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ě

  1. pokud není v programu nastaven klíč a inicializační vektor pro šifrováíní, vygeneruje je a vypíše
  2. buď deserializuje kolekci ze souborů, pokud existují ... jinak kolekci vygeneruje
  3. poté kolekci vypíše
  4. kolekci zmodifikuje
  5. kolekci znovu vypíše
  6. vymaže stávající soubory se serializovanými daty
  7. 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);
                }
            }
        }
    }
}
Editováno 2.10.2017 2:03
Akceptované řešení
+20 Zkušeností
+2,50 Kč
Řešení problému
 
Nahoru Odpovědět
2.10.2017 2:02
Avatar
Jirka Jr
Člen
Avatar
Odpovídá na Dog
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 ... :-)

 
Nahoru Odpovědět
2.10.2017 2:05
Avatar
zelvicek
Člen
Avatar
zelvicek:2.10.2017 11:44

Ř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á.

 
Nahoru Odpovědět
2.10.2017 11:44
Avatar
Jirka Jr
Člen
Avatar
Odpovídá na Dog
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?

 
Nahoru Odpovědět
3.10.2017 20:24
Avatar
Dog
Člen
Avatar
Dog:3.10.2017 23:31

Teď jsem se dostal sem na fórum, určitě ano, dám vědět do zítřka. Mám strašně moc práce momentálně.

Díky moc a zítra napíšu :-)

 
Nahoru Odpovědět
3.10.2017 23:31
Avatar
Dog
Člen
Avatar
Dog:6.10.2017 14:03

Dnes se na to mrknu, za zpoždění se omlouvám :)

 
Nahoru Odpovědět
6.10.2017 14:03
Avatar
Jirka Jr
Člen
Avatar
Odpovídá na Dog
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 :-)

 
Nahoru Odpovědět
6.10.2017 18:38
Avatar
Dog
Člen
Avatar
Dog:8.10.2017 22:10

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 :-)

Editováno 8.10.2017 22:12
 
Nahoru Odpovědět
8.10.2017 22:10
Avatar
Jirka Jr
Člen
Avatar
Odpovídá na Dog
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 ...

Editováno 8.10.2017 23:20
 
Nahoru Odpovědět
8.10.2017 23:20
Avatar
Odpovídá na Jirka Jr
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.

Editováno 9.10.2017 9:10
Nahoru Odpovědět
9.10.2017 9:10
Totalitní admini..
Avatar
Marian Benčat:9.10.2017 9:14

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.

Nahoru Odpovědět
9.10.2017 9:14
Totalitní admini..
Avatar
Jirka Jr
Člen
Avatar
Odpovídá na Marian Benčat
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 :-)

 
Nahoru Odpovědět
9.10.2017 10:37
Avatar
Odpovídá na Jirka Jr
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á .

Editováno 9.10.2017 11:32
Nahoru Odpovědět
9.10.2017 11:30
Totalitní admini..
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 20 zpráv z 20.