Poznáváme C# a Microsoft .NET – 5. díl

Diskuze čtenářů k článku

avatar
24. 11. 2007 13:11

Programovou oflline verzi seriálu naleznete ke stažení na http://poznavame-c-msnet.wz.cz/

Souhlasím  |  Nesouhlasím  |  Odpovědět
nik  |  28. 12. 2004 20:22

Tak se přiznám, že mne docela pobavila diskuze vyvolaná jednoduchým dotazem, kde se sníží čítač instancí. Faktem je, že dodnes musím pracovat s Borland Pascalem 7.02 pro DOS a v něm bych jednoduše měl globální proměnnou skrytou v implementation části unitu a inkrementovanou v konstruktoru a dekrementovanou v destruktoru (který musí být explicitně zavolán). Podobně v ObjectPascalu v Delphi, ale mohly by to být proměnné a metody třídy. Ale dozvěděl jsem se hodně nového - například že trpím syndromem "assembleristů" (který se zřejmě projevuje programátorovou nezkrotnou touhou, aby program dělal to, co dělat má), že potřebuji "nový způsob myšlení" (možná viz Michail Gorbačov: "Přestavba a nové myšlení pro naši zemi a pro celý svět"). Přitom je mi celkem fuk, jestli Pascal nebo C#, pokud to bude konzistentní a průhledné. A uvedený příklad na počet instancí zrovna takto nepůsobí ... (To jsem zvědav, jaký syndrom se u mne nyní tímto projevil.)

Souhlasím  |  Nesouhlasím  |  Odpovědět
Benjamin  |  28. 12. 2004 20:50

Dokud budes pracevat jenom v Pascalu, tak zadny novy zpusob mysleni nepotrebujes. Ale kazdy jazyk s sebou nese nejaky zpusob mysleni, kteremu se budto prispusobis, nebo jsi odsouzen k tomu, abys programy akorat bastlil, ale nikdy nebudes skutecne programovat.
Existuje na to pekna analogie: Rekneme, ze mame cloveka, ktery umi jezdit na motorce a jedna z veci, ktere umi, je zataceni - to se na motorce dela tak, ze vezme riditka, otoci je o urcity uhel kolem svisle osy a krome toho se v pozadovanem smeru nakloni. Jednoho dne se objevi auto, a jeho zajima, jestli je ta vec k necemu dobra, a jestli to umi aspon to, co motorka - napriklad zatacet. V tu chvili ma dve moznosti jak se zeptat:
1) kde ma auto riditka a jak se auto naklani. Pokud se zepta takhle, jiste bude sklaman , kdyz se dozvi, ze auto riditka nema - ma akorat volant, a ten se otaci podle vodorovne, a ne sbisle osy - a naklanet ho sice lze, ale je to velmi namahave, nehlede k tomu, ze to nenese ocekavany efekt. Nepochybne potom usoudi, ze auto je na nic a zustane u sve motorky.
2) Jak se zataci s autem - smiri se s tim, ze auto se pouziva jinak, nez motorka, a muze vecne uvazovat, co se k reseni jeho problemu hodi vic.

V C# se proste pise jinak, nez v Pascalu - ma jine vyrazove prostredky, jine moznosti, jine typicke vzory reseni typickych problemu, z cehoz plyne i nutnost jineho zpusobu reseni tech netypickych (napriklad pocitani instanci nebyva uplne typicke a C# se k tomu skutecne prilis nehodi). Ja osobne jsem presvedcen, ze C# je dobry jazyk, urcite lepsi, nez Delphi (a s Delphama nejake zkusenosti mam), napriklad i diky GC, ale je tam toho vic.

Souhlasím  |  Nesouhlasím  |  Odpovědět
nik  |  28. 12. 2004 23:03

Benjamine, Benjamine - to jsem tedy zbastlil v Pascalu program o 135 000 řádcích, který za 11 let používání na 8 pobočkách ve firmě nikdy neselhal (nikdy jsem neřešil situaci "program bouchá, okamžitě opravit" - možná někdy počítal trochu mimo /ale tak, že to šlo obejít/ nebo tiskl krapet rozhozenou sestavu).
A bez objektového událostmi řízeného programování bych vskutku něco takového nedokázal ...
A řidičák mám jak na auto, tak na motorku. A to auto jsem před 8 lety kupoval s ABS a automatickou CVT převodovkou - jojo, takový jsem já zpátečník.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Benjamin  |  28. 12. 2004 23:20

Ale ja jsem netvrdil, ze v Pascalu musis zarucene programy bastlit. Ja jsem psal, ze pokud budes v C# programovat, jako kdybys psal v Pascalu, tak to bude jedine bastleni.
Taky jsem netvrdil, ze v Pascalu se neda napsat dobry program, nebo, ze v nem nejde napsat dobry udalostmi rizeny program pouzivajici objekty (konec koncu - objektove jde psat i v assembleru, protoze objektove programovani je konec koncu zase jenom zpusob, jak pristupuju k problemum, s tim, ze pouzivany jazyk mi to muze vyrazne usnadnit).

Co jsem chtel rict byly dve veci:
1) Pokud se budes ptat "Muzu v C# resit ty same problemy, jako v Pascalu tim samym zpusobem, jako v Pascalu?" odpoved bude: Samozrejmne, ze nemuzes, ale nepochybne existuje zpusob, jak to vyresit v C#, jenomze s trochu (obcas i hodne) jinym pristupem.
2) Podle me, je C# lepsi nez Pascal, ale i nez C++, nebo VB (Javu znam prilis malo)

Souhlasím  |  Nesouhlasím  |  Odpovědět
Petrik  |  28. 12. 2004 23:47

C# je určitě výtečný a v řadě ohledů perspektivní jazyk, nicméně bych úplně nepřehlížel fakt, že naprostá většina (95%?, 99% ??) volně distribuovaných aplikací  je i nadále (tři roky po uvedení .NET) odladěna právě v těch druhořadých jazycích, jako je C++ a Delphi (Pascal?) - to je prostý fakt. O Javě lze jistě říct, že svým způsobem předstihla dobu a její rozvoj poznamenal spor MS vs. Sun - ale C#? Co vlastně brání šíření .NET aplikací na desktopy?

Souhlasím  |  Nesouhlasím  |  Odpovědět
Benjamin  |  29. 12. 2004 00:03

"druhorade jazyky" jste pouzil prvni vy, ne ja (to jenom, aby bylo jasno ). Jinak - 3 roky jsou kratka doba. Treba C++ vznikl na zacatku 80. let, ale jako dominantni nastroj pro vyvoj aplikaci (a to jeste zdaleka ne vsude) se prosadil az v 90. letech.
Ale zkuste si nekdy zalistovat nabidkami prace pro programatory - casto narazite na firmu hledajici programatory v C# nebo VB.NET, aby jim stavajici system prevedli na .NET platformu.

"Co vlastně brání šíření .NET aplikací na desktopy?"

Mimo jine urcite tuny kodu, napsane v jinych jazycich - malokomu se chce zacinat od nuly. Na druhou stranu Microsoft dela hodne pro to, aby tento prechod usnadnil (viz. MC++ a relativne jednoduche volani funkci z nativnich a COMovskych DLL).

Souhlasím  |  Nesouhlasím  |  Odpovědět
Petrik  |  29. 12. 2004 00:16

Ačkoliv naše diskuse o GC může působit docela akademicky, byl jsem svědkem projektu .NET který málem předčasně skončil na faktu, že GC uvolňuje paměť liknavě - WindowsForms aplikace s několika DataGridy po několika přepnutích Tabbed Dialogu postupně naalokovala klidně přes 80 - 120 MB RAM a volání GC.Collect nezabere, dokud se nezahýbe s WorkingSet procesu - např. při minimalizaci okna. Ale chtějte po zákazníkovi, aby si každou půlhodinu práce minimalizoval okno... Nakonec myslím ten problém částečně vyřešil nějaký oficiálně nepodporovaný hotfix - .NET 2.0 tímto problémem údajně už trpět nemá, ale zatím jsem nepozoroval, že by se vůči paměti choval decentněji.
 
Vlastnost GC je, že dobře funguje v relativně vyváženém ustáleném multithreadovém prostředí, třeba na serveru zatíženém mnoha požadavky v několika security kontextech současně. To je jeho parketa, na které si vede skvěle - ale u klientských aplikací se systémové nároky s časem mění podstatně výrazněji. Každá latence při práci s pamětí je tam zkrátka znát - i když je třeba v konečném důsledku uvolňována robustněji.
 
Dalším faktorem je čistý výkon managed platformy - zde má .NET do budoucnosti velkou rezervu s ohledem na možnost implementace JIT kompilace, ale současná situace je prostě postavena tak, že se v dohledné době neobávám implementace třeba Photoshopu v .NETu. Výhoda Delphi v tomto směru je, že má jeden z nejrychlejších překladačů vůbec.

Souhlasím  |  Nesouhlasím  |  Odpovědět
viman  |  29. 12. 2004 01:05

No to jsou docela zajímavé informace. Zkusím se po podobných zkušenostech trochu systematicky pídit. Nevíte náhodou, čím jsou ty problémy jsou způsobeny? Dle mých dosavadních teoretických znalostí by k tomu u dobře napsané aplikace nemělo docházet (fakt je to ale jen teorie . Prostě špatně napsaná formulářová aplikace v Delphi může být zhlediska paměti rovněž problematická (v tom mám narozdíl od .NET zkušenost).

Opravdu bych VELMI přivítal jakékoli logické vysvětlení, proč ve WinForms aplikacích dochází k tomu, co popisujete.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Petrik  |  29. 12. 2004 02:15

Např. nějakou vnitřní implementační chybou v nějaké nenápadné komponentě, jako je třeba ToolTips control, na které bývá nabindováno několik ovládacích prvků současně, a která správně nedealokuje zdroje a díky tomu na ní visí alokace paměti v Datagridu, jeho Datasetu, atd... 

Souhlasím  |  Nesouhlasím  |  Odpovědět
Petrik  |  29. 12. 2004 03:39

Typický případ, kdy má GC problémy je práce s větším počtem referenčních typů, např. při spojování řetězců - tahle krátká ukázka při spojení 5000 řetězcových polí vyžaduje alokaci přes 760 MB objektů na heapu (v tomto případě jde sice použít StringBuilder, ale to není obecné řešení pro všechny objekty).
  

using System;
public class ConcatenateStrings {
public static void Main() {
string[] aStr = new string[5] {"A","B","C","D", Environment.NewLine};
Console.WriteLine(JoinStr(aStr, 5000));
}
private static string JoinStr(string[] aStr, int n) {
string ss = String.Empty;
for (int i=0; i<n; i++) foreach(string s in aStr) ss += s;
return ss;
}
}

Souhlasím  |  Nesouhlasím  |  Odpovědět
Benjamin  |  29. 12. 2004 10:55

Ten priklad ale prece vubec nic nedokazuje, a troufl bych si rict, ze se GC vlastne ani netyka. Za prve - na tom grafu nikde nevidim naalokovanych 760 MB, a kdyz jsem to zkousel doma spustit, cely program zabiral maximalne 6,5 MB, ale to je stejne jenom mimochodem, protoze v tomhle pripade alokujete spoustu pameti, kterou ale vyuzivate a neco takoveho zadny system spravy pameti ani vyresit nemuze, protoze kdyz tu pamet proste pouzivate, tak ji ten program proste musi zabirat a s GC to nema nic spolecneho.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Petrik  |  29. 12. 2004 22:58

Jde o to, že si při každém spojení řetězce vytvoříte nový string object a starou instanci musí GC uklidit, což nestíhá, takže se nároky na haldu nekontrolovatelně zvětšují - ačkoliv by program v Delphi nebo C++ vystačil na analogickou úlohu s nějakými 200 - 400 kB paměti, kdyby reference počítal a likvidoval ihned po použití. Nemluvě o nutnosti zastavovování threadů, přepínání kontextu a tisících nedeterministických kolekcích zvětšujících režii celé operace. K tomu tématu viz např. postřeh z této diskuse:
 
http://ryangregg.com/archive/2004/01/15/168.aspx
 
There is also memory issues. I experiences that I had used string concatenation to build SQL and XML statements for a message board. The result were that the web site crashed with OutOfMemory problems when the site was heavily loaded.  After I switched to a StringBuilder, I not only rendered my web pages 1000 times faster, I also got rid of the OutOfMemory problems....
 
 
Pokud je systém zatížen, stává se použití GC nestabilní záležitost, protože se proces GC nemusí vůbec dostat ke slovu, k úklidu objektů nedojde a stress systému se tím napak ještě zvětší.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Benjamin  |  29. 12. 2004 23:52

1) Ten prispevek vubec nemluvi o GC - problem je v tom, ze pokud skladate velke mnozstvi stringu pomoci konkatenace, mate v pameti najednou velke mnozstvi ZIVYCH objektu a proto ma .NET StringBuilder - autor tim reaguje na clanek, ve kterem nejaky koumes pomoci jakehosi prapodivneho benchmarku nameril, ze StringBuilder je na nic.
GC nema sebemensi duvod mit problemy dostat se ke slovu, protoze, kdyz prijde na vec, tak proste sefuje on, vsechna vlakna zastavi, jak uz jste zminil, a v klidu si uklidi .

Souhlasím  |  Nesouhlasím  |  Odpovědět
viman  |  30. 12. 2004 00:36

Vážně běží GC v samostatném vlákně? To by se musel pořád zastavovat a obnovovat procesy, kontext, atp. Podle mě (a snad jsem to i někde četl) je GC spouštěn jako součást aplikačního vlákna. Ale opravte mě, jestli se mýlím.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Benjamin  |  30. 12. 2004 15:42

Ja nevim, jestli ma vlastni vlakno, to byste se musel zeptat nekoho, klo tomu rozumi vic, ale navenek je to vlastne jedno - tak jako tak, pokud bezi garbage collection, vsechna ostatni vlakna musi pockat, tim jsem si jist, protoze tohle vim, navic - pokud znate zpusob, jak by mohl bezet zbytek aplikace, behem toho, kdy GC uklizi, okamzite napiste nekomu s CLR tymu, budete slavny a bohaty . S finalizaci je to neco jineho - pokud finalizator splnuje pravidla, ktera tak jako tak splnovat musi, neni zadny problem, aby bezel spolu s ostatnimi vlakny.

Souhlasím  |  Nesouhlasím  |  Odpovědět
viman  |  30. 12. 2004 20:13

To je naprosto jasné, že GC nemůže běžet během práce aplikačních vláken (já to snad ani opačně nikde nenaznačil ) To, zda běží v synchronizovaném samostatném vlákně nebo přímo jako součást aplikačního vlákna však může mít drtivý dopad na výkon. Proto mě to zajímá (a momentálně se mi to nechce vyhledávat na netu). Vycházím však z toho, že GC testuje a případně spouští kolekci při alokacích paměti - a tato operace je přece součástí aplikačního threadu, ne?

Souhlasím  |  Nesouhlasím  |  Odpovědět
Petrik  |  31. 12. 2004 02:50

.NET ve skutečnosti podporuje oba druhy GC: jak  synchronní (používaný třeba v ASP.NET, kde běží mnoho vláken v různém kontextu) tak ten běžící v samostatném vlákně (finalizer thread) s IDLE prioritou, pokud je dostatek místa na spravované haldě (jinak přepne do REALTIME priority) který procesy a kontexty přepíná.

Souhlasím  |  Nesouhlasím  |  Odpovědět
viman  |  30. 12. 2004 00:30

To není možné, měl by to stíhat vždy. Důvod je prostý: GC spouští CLR ve stejném vlákně jako aplikace, takže se vždycky nejdřív ukončí úklid (to ovšem neplatí o objektech, které mají finalizátor) a pak se pokračuje. Pokud vím, thready zastavuje pouze manuální volání GC.Collect().

Jediné s čím souhlasím je větší režie těch nedeterministických kolekcí. Ale přesto si GC provádí za běhu statistické vyhodnocení, takže to zkonverguje k nějakému optimálnímu poměru mezi velikostí haldy a množstvím provedených kolekcí. Alespoň na mém počítači se ta křivka alokované paměti blíže logaritmě.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Petrik  |  31. 12. 2004 02:56

Viz třeba zde pro srovnání C++ a C#  - Memory footprint for a linked-list memory consumption test
 
Figure 4.
 
a co se srovnání výkonu C++ vs C# týče (http://www.computer.org/software/homepage/2003/s1lap_print.htm)
 
Figure 3.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Benjamin  |  31. 12. 2004 11:26

Ja nevim, kde porad berete tyhle testy. Ale na jedne strane jsou zde ruzne benchmarky, ktere navozuji situace, v nichz si .NET nevi uplne rady, na druhe strane jsou zde ale firmy, ktere zalozily svou existenci na dodavani reseni postavenych na .NET frameworku a firmy a instituce, ktere vyuzivaji reseni postavene na .NET frameworku a zatim nekrachujou, takze realne zkusenosti jsou spise pozitivni.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Petrik  |  31. 12. 2004 19:15

Aby bylo jasno, já jsem stejný fanda DotNetu, jako Vy (..jinak bych tu o něm nepsal seriál). Ale přesto (či spíš právě proto) nerad vidím, když se kolem něj šíří mýty ve stylu "je to rychlé jako ....". To prostě není pravda. Jsem přesvědčen, že kdyby systémové nároky .NETu byly nižší, byl by rozšířen mnohem více i na desktopech.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Benjamin  |  01. 01. 2005 19:44

Ja uznavam, ze .NET je nekdy pomalejsi, nez nativne zkompilovane programy, ale tam, kde je to potreba je rychly dost. Navic - vsechna tato ruzna srovnani jsou k .NETu nefer v tom, ze porovnavaji vykon jenom v tom, co umi i to druhe srovnavane prostredi, jenomze .NET toho umi daleko vic - co treba metadata a Reflection, Remoting, a napriklad bezpecnost kodu - to jsou veci,o kterych samozrejmne u nativne zkompilovanych programu nema vubec smysl mluvit.

Souhlasím  |  Nesouhlasím  |  Odpovědět
viman  |  01. 01. 2005 16:19

Za grafy děkuji. Ten druhý je však nekorektní, protože jeho autor ten spojový seznam v C vytvářel neobjektově. Chtělo by to porovnání s objektovým řešením v C++ nebo Delphi. Jak víme, již implementace samotné objektovosti jako takové něco stojí (prohledávání VMT).

Většina testů navíc provádí srovnává komplexnější algoritmy místo atomických operací, což poněkud zkresluje (resp. "zneúplňuje"). Já si potřebuji udělat představy na těchto úrovních:

1)
Atomické operace (aritmeticko-logické oprace, alokace paměti, dealokace, cykly, výjimky, atd.). Zde si .NET vede docela dobře.

2)
Kvalita resp. optimalizace knihoven. Ve WinForms mám obavy. I když objektově-programový model je vynikající, osobně nesnáším byť jen nepatrně zpomalené GUI (jako např. FireFox, ThunderBird, OpenOffice.org, MS Visual Studio .NET, atp. - všechny tyto aplikace však používám).

Souhlasím  |  Nesouhlasím  |  Odpovědět
viman  |  29. 12. 2004 19:56

Také tomu nerozumím. Performance monitor mi ukazuje max. množství 2 551 948 B na všech haldách během provedení algoritmu. Během provádění cyklu byl úklid 0-té generace proveden 1194x, první 108x a druhé 0x. To se mi jeví přijatelně.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Petrik  |  29. 12. 2004 22:19

Viz Allocation Graph CLR profileru...

Souhlasím  |  Nesouhlasím  |  Odpovědět
viman  |  29. 12. 2004 23:01

To je prakticky zaručeně chyba toho profileru. Tohle totiž ukáže po skončení programu (místo aby zobrazení zcela znemožnil). Dejte si tam ihned po provedení algoritmu System.ReadLine() a nechte si při tom čekání na stisk klávesy zobrazit ten graf. To pak vypadá úplně jinak.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Benjamin  |  29. 12. 2004 15:26

"Dalším faktorem je čistý výkon managed platformy - (...) Výhoda Delphi v tomto směru je, že má jeden z nejrychlejších překladačů vůbec."

Tak jsem zkusil porovnat vykon dvou programku - jednoho v Delphach(verze 7) a jednoho v C#:

const int velikost = 5000000;
[STAThread]
static void Main(string[] args)
{
int[] Prvni = new int[velikost];
int[] Druhe = new int[velikost];
int[] Vysl = new int[velikost];
DateTime cas1;
DateTime cas2;
for (int i=0; i< velikost;i++)
{
Prvni[i] = i;
Druhe[i] = 2*i;
}
cas1 = DateTime.Now;
for (int i=0;i {
Vysl[i] = Prvni[i] + Druhe[i];
}
cas2 = DateTime.Now;

long trvani =
(cas2.Millisecond + 1000 * cas2.Second + 1000 * 60 * cas2.Minute + 1000 * 60 * 60 * cas2.Hour) -
(cas1.Millisecond + 1000 * cas1.Second + 1000 * 60 * cas1.Minute + 1000 * 60 * 60 * cas1.Hour);


Console.WriteLine("Trvalo to: {0}", trvani);
Console.ReadLine();
}

Const Velikost = 5000000;
Type VelkePole = ARRAY[0..Velikost-1] Of Integer;
VAR Prvni, Druhe, Vysl: VelkePole;
i: Integer;
msec1, sec1, min1, hod1,msec2, sec2, min2, hod2: Word;
rozdil: Longint;
cas1, cas2: TDateTime;
begin
For i:= 0 To Velikost - 1 Do
Begin
Prvni[i]:= i;
Druhe[i]:= 2*i;
End;

cas1:= Now();
For i:= 0 To Velikost - 1 Do
Begin
Vysl[i]:= Prvni[i] + Druhe[i];
End;
cas2:= Now();

DecodeTime(cas1,hod1,min1,sec1,msec1);
DecodeTime(cas2,hod2,min2,sec2,msec2);

rozdil:= (msec2 + 1000 * sec2 + 1000 * 60 * min2 + 1000 * 60 * 60 * hod2)-
(msec1 + 1000 * sec1 + 1000 * 60 * min1 + 1000 * 60 * 60 * hod1);
Writeln('Delka trvani byla ',rozdil,' milisekund');
Readln;
end.

Oba tyto programy napred po prvcich poscitaji dve pole o velikosti 5000000 prvku, spocitaji, jak dlouho jim to trvalo a pak se tim pochlubi.

A ted vysledky: Na mem pocitaci to programu v C#(nepocitame-li prvni spusteni, kdy to bylo 63) trvalo mezi 31 a 47 milisekundami, zatimco programu v Delphach to trvalo mezi 31 a 47 milisekundami (to neni preklep, skutecne jde o stejna cisla).

Ja nevim, kde se bere presvedceni, ze programy na .NETu musi zarucene byt pomalejsi, nez ty nativne kompilovane. Takova predstava je do znacne miry prezitek, a tohle myslim je pomerne presvedcivy dukaz.

Souhlasím  |  Nesouhlasím  |  Odpovědět
viman  |  29. 12. 2004 17:49

Ty představy jsou asi způsobeny zafixovaným předsudkem, že kód není kompilovaný nebo může být kompilovaný jedině jako JIT (což zabírá procesorový čas i prostředky). Ale opravdu nevidím žádný rozdíl oproti nativním aplikacím, pokud provedu install-time kompilaci příslušných assemblies z MS IL do strojového kódu (utilitou ngen.exe). Pro klientské aplikace je tento způsob normálně doporučován.

Výkon aplikací podle mě mohou ovlivnit hlavně tři věci:

1) Maršalování typů při spolupráci s nativními DLL, které nemají odpovídající reprezentaci v "jednodušších" jazycích (např typ string, který nelze měnit, ale pouze vždy vytvořit nový). Kdybych teoreticky nebyl závislý na knihovnách FCL, tak mohu v návrhu software použít pouze přímočaře maršalovatelné typy (neříkám, že je to výhodné). Nicméně vše je zde predikovatelné.

2) Efektivita a nároky GC.
Tohle ovlivním jen minimálně a mohu jen doufat, že nenarazím na těžko řešitelný prolém

3) Kvalita návrhu knihovny FCL
Tohle je obecný problém všech knihoven. Chyby v knihovnách resp. neznalost vnitřní implementace mohou vést k nesprávnému chování resp. použití a tím výrazně degradovat výkon a predikovatelnost aplikace. Z tohoto mám alespoň já největší můru (stejně tak v ale i v Delphi), ale zatím na mně dělá více než dobrý dojem (ještě jsem nezkusil WinForms).

Souhlasím  |  Nesouhlasím  |  Odpovědět
Petrik  |  30. 12. 2004 04:34

Problém je, že se v praxi uplatňuje spíš rychlost vnímaná uživatelem, a ta je v případě -NETu citelně menší, než u klasických aplikací. Před časem jsem to demonstroval na jednoduchém benchmarku, který nastavuje v několika cyklech hodnoty RadioButtonu na WindowsForms panelu.
http://vbnet.aspweb.cz/guibench/

Imports System, System.Drawing, System.Windows.Forms
Public Class frmGuiBenchmark: Inherits Form
Private WithEvents L As New Label(), P As New Panel()
Private Const N = 15
Private RB(N, N) As RadioButton, i%, j%
Shared Sub Main()
Application.Run(New frmGuiBenchmark())
End Sub
Sub New()
L.Dock = Dock style.Top
L.Size = New Size(400, 16)
L.Font = New Font("Arial", 8)
L.Text = "Single click here to start the benchmark"
L.TextAlign = ContentAlignment.MiddleCenter
P.Dock = Dock style.Fill
P.Location = New System.Drawing.Point(0, 16)
Controls.Add(L): Controls.Add(P)
Size = New Size(400, 368)
For i = 0 To N
For j = 0 To N
RB(i, j) = New RadioButton()
RB(i, j).Left = 5 + i * 30 : RB(i, j).Top = 5 + j * 20
RB(i, j).Width = 10 : RB(i, j).Height = 10 : RB(i, j).Tag = " " & i & " " & j
AddHandler RB(i, j).Click, AddressOf RB_Click
P.Controls.Add(RB(i, j))
Next
Next
End Sub
Private Sub RB_Click(S As Object, EA As EventArgs)
L.Text = S.ToString & S.tag
End Sub
Private Sub L_Click(S As Object, EA As EventArgs) Handles L.Click
Dim tSta& = Date.Now.Ticks, Rnd = New Random(), it%
L.Text = "Start: " & tSta : Application.DoEvents()
For it = 0 To 1000
i = Rnd.next(N) : j = Rnd.next(N)
RB(i, j).Checked = True
Next
L.Text = "Stop: " & (Date.Now.Ticks - tSta)
L.Text = (Date.Now.Ticks - tSta) / 10000000.0 & " sec"
End Sub
End Class
Časy pro VB.NET, MS VJM a Visual Basic 6.0 jsou 1.17, 0.43 a 0.31 sekund (čili v GUI je VB.NET 4x pomalejší, než VB6) a kupodivu (v rozporu s vžitými představami o pomalosti Javy) i 3x pomalejší, než MS Java! Problém je, že v případě komplexnějších aplikací uživatel nepříznivě vnímá odezvu GUI (u DB aplikací je jinak rychlost málo závislá na FLOPS apod., spíš na odezvách databáze nebo webové služby). Odpovídající zdroják VB6 je zde, zdroják Javy je na linku.      
Const N% = 15
Private Sub Form_Load()
Dim i%, j%, ii%
For i = 1 To N
For j = 1 To N
ii = ii + 1
Load RB(ii): RB(ii).Visible = True
RB(ii).Move 5 + i * 30, 5 + j * 20
Next j
Next i
End Sub
Private Sub Form_DblClick()
Dim tSta!: tSta = Timer
For it = 0 To 1000
i = Fix(Rnd * N): j = Fix(Rnd * N)
RB(i + j * N).Value = True
Next
L = Timer - tSta
End Sub

Souhlasím  |  Nesouhlasím  |  Odpovědět
Benjamin  |  30. 12. 2004 16:23

Ja nebudu predstirat, ze chapu, co ten program vlastne dela... , ale to je vedlejsi. Ja souhlasim ze je mozne napsat .NET aplikaci, ktera bude pomala, a ktera bude mit pomlou odezvu uzivateli, ale to jde vzdycky. Princip je v tom, ze neni az takovy problem napsat aplikaci, ktera pobezi dobre.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Petrik  |  30. 12. 2004 19:08

No, to je o tom, že jakákoliv GUI aplikace, napsaná v .NETu bude pomalejší, než nativní, napsaná třeba v Delphi, ale i Visual Basicu.
Narážím na prinicipiální lenost.NET widgetů při zpracování klientských událostí. Co ukázka dělá je vidět na stránce s applety, ale těžko v .NETu napíšete jinou, která bude pracovat rychleji.

Souhlasím  |  Nesouhlasím  |  Odpovědět
viman  |  28. 12. 2004 21:18

Vždyť jste odpověď dostal, že se to v .NET udělá úplně stejně jako v Object Pascalu (stejně pracně a se stejným výsledkem)!!!!!!!!!!!!!!!!!!!! Ten syndrom jsem nepopsal jako touhu, aby program dělal to co má, ale jako psychologický blok přijímat kromě té své i jiné filozofie. Tohle je fakt a nikoli výmysl a už vůbec ne můj. Toto si někteří bývalí assembleristé, které znám sami sobě dokázali přiznat, když se jim to podařilo překonat.

Teď nevím jaký příklad máte na mysli, ale stačí toto, což podle mě není složitější, než ve vašem BP:


using System;

class Trida
{
static int pocetInstanci = 0;

public Trida()
{
pocetInstanci++;

}

public void Dispose()
{
pocetInstanci--;
}
}

class MainClass
{
[STAThread]
static void Main(string[] args)
{
// takhle s objektem začínám
Trida t = new Trida();


// a takto dám najevo, že ho už nepotřebuji
t.Dispose();
t = null;
}
}


Souhlasím  |  Nesouhlasím  |  Odpovědět
nik  |  28. 12. 2004 22:54

Zapomeňte na Assembler - na Tesle 200 to bylo před 17 lety, na EC mi osvěžoval horor programování v Cobolu a na PC to jsou maličkosti pro DOS a inline Assembler v Pascalu.
Takhle to je opravdu prakticky stejné, jako kdybych musel explicitně volat destruktor, který by to odečítal.Ale ještě bych ten váš příklad doplnil (kdybych uměl psát z hlavy v C#, tak bych to sem rovnou napsal) o logickou proměnnou evidující, zda bylo u instance voláno Dispose, a pokud ne, tak bych Dispose zavolal z destruktoru (jinak zapomenu jednou dát Dispose a pocetInstanci je mimo).

Souhlasím  |  Nesouhlasím  |  Odpovědět
Petrik  |  28. 12. 2004 23:10

Ano, zkuste Váš návrh zkonfrontovat se standardním Dispose design pattern (sem ho linkovat nebudu, tento DP je uveden na mnoha místech webu, vč. MSDN).

Souhlasím  |  Nesouhlasím  |  Odpovědět
viman  |  28. 12. 2004 23:52

Ano, to doplnění je OK, chtěl jsem jen demostrovat konstrukci totožnou stejnému řešení v Object Pascalu a to jak v chování, tak i mírou zabezpečení proti chybám. Jak už kolegové vysvětlili, patrně lze stejné požadavky na funkcionalitu vyřešit jinak a bezpečně. A to je narozdíl od Object Pascalu benefit.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Petrik  |  28. 12. 2004 21:38

Pokud jste nucen destruktor volat explicitně, pak příklad s počítáním referencí v Dispose není až tak vzdálen Vaší realitě. Jinak jsem proti jakémukoliv škatulkování ve stylu "assembler je fuj, OOP je dobré" a řadu problémů .NET spojených s neprůhlednou správou zdrojů vnímám stejně negativně, jako Vy. Sledování referencí má proti počítání zase výhodu v třeba v tom, že se o řadu problémů typů křížové reference nebo threadová bezpečnost (locking) nemusí programátor příliš starat - neprůhlednost správy paměti vyvažuje průhlednost správy objektů. Osobně bych uvítal, kdyby .NET nabízel oba přístupy a v tom ohledu vítám, že tuto možnost Microsoft studuje a investuje do jejího výzkumu. Jeho cílem není GC nahradit, ale doplnit ho počítáním odkazů tak, aby bylo možné implementovat low-latency finalizaci. Má to význam tím spíš, že .NET se (na rozdíl od Javy) hodně opírá o unmanaged knihovny a zdroje, Kdyby jste byl jediný, kdo trpí takovým "syndromem", jistě by to Microsoft nedělal... 
http://www.sellsbrothers.com/spout/default.aspx?content=archive.htm#refCountRotor

Souhlasím  |  Nesouhlasím  |  Odpovědět
nik  |  25. 12. 2004 17:37

A kde se pocetInstanci sníží ? V Delphi bych to dělal v destruktoru, ale tady nevidím kde ...

Souhlasím  |  Nesouhlasím  |  Odpovědět
Petr  |  25. 12. 2004 20:46

Jednoduše, použijete destruktor - nevím, proč se o něm Petr nezmínil. Destruktor v C# používá notaci s tildou, bez dalších modifikátorů:
 

 class  NaseTrida&n bsp; {
  private static int  pocetInstanci;
  public int PocetInsta nci  {
    get{return p ocetInstanci;}
  }
  public Na seTrida()  {
    pocetInstan ci++;
  }
  ~NaseTrida() &n bsp;{
    pocetInstanci--;
 &n bsp;}
}
 


Z hlediska MSIL instrukcí CLR& nbsp;je zápis
 
   ~NaseTrida()  {
& nbsp;   pocetInstanci--;
  }


funkčně totožný s překrytím virtální metody Finalize, kterou disponuje každá třída nás ledovně (to je také jediný způsob,&n bsp;kterým lze destruktor implementovat v e VB.NET):

   protected override&nb sp;void Finalize()  {
   &nb sp;try  {
    pocetInstanci- -;
    }
    fi nally {
      base.Fina lize();
    }
  }


Pokud si budete chtít destruktor vyzkoušet (např. nastavením instance třídy na null, měl by jste současně zavolat metodu GC.Collect(), a tím umožnit procesu "sběrače smetí" (Garbage Collector, GC), aby se dostal ke slovu a instanci třídy skutečně uvolnil z paměti, nebo ve Vaší třídě implementovat IDisposable rozhraní, což Vám umožní volat jeho metodu Dispose() explicitně.
 
Rozdíl oproti chování destruktorů v Delphi nebo C++ je dán právě faktem, že proces GC běží na pozadí, takže volání destruktorů není tak deterministické, jako v Delphi nebo C++. Pokud Vaši třídu používá nějaký jiný objekt, nemusí ji GC vůbec uvolnit a destruktor se zavolá až před ukončením celé aplikace.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Pusak, Pusak  |  25. 12. 2004 20:55

Dobrý večer,
v C# existují destruktory, které umožňují definovat, co se má provést při rušení instance třídy. Pokud bychom chtěli příklad doplnit o snížení hodnoty proměnné pocetInstanci, příslušný destruktor bychom nadefinovali následovně :

~ NaseTrida()
{
pocetInstanci--;
}

Je ovšem potřeba mít na mysli, že čas rušení instance nemáme pod kontrolou. Je sice možné zavolat garbage collection pomocí metody GC.Collect, ale ani tak není jisté, že dojde k úklidu okamžitě.

Zdraví Petr Puš

Souhlasím  |  Nesouhlasím  |  Odpovědět
Pavel  |  27. 12. 2004 08:13

Proboha jenom neucte nikoho volat GC.Collect(), tato metoda se vola jenom ve zvlastnich pripadech! Jestli bude kazdy programator nutit GC k uklidu nebude delat nic jineho, coz jiste na vykonu neprida ;)

Souhlasím  |  Nesouhlasím  |  Odpovědět
nik  |  27. 12. 2004 08:38

Souhlas - ale ksakru kde tedy evidovat počet instancí ? V Delphi (nebo ještě BP 7.02, s kterým dodnes musím pracovat) se to provede v konstruktoru a destruktoru (nebo metodách vždy z nich volaných), ale pokud v C# není destruktor volán definovaně, tak jak na to? (Sice mne to dosti zajímá, ale divil bych se, kdyby naši manageři mne ještě nechali programovat - u nás nesmí programovat matematici, na takové činnosti jsou u nás určeni chemici, strojaři, absolventi gymnázií a hotelových škol, popřípadě vyučení kuchaři, ale jenom ne proboha matematik vzdělaný v oboru numerické zpracování informací ...).

Souhlasím  |  Nesouhlasím  |  Odpovědět
Petrik  |  27. 12. 2004 17:20

Je dobré rozlišovat počet instancí a počet referencí. Pokud ve třídě implementujete metodu Dispose() rozhraní IDisposable, můžete zdroje spojené se třídou spravovat preemptivně.

class NaseTrida: IDisposable {
  static private int pocetInstanci;
  public NaseTrida()  {
pocetInstanci++;
  }
public void Dispose() {
pocetInstanci--;
GC.SuppressFinalize(this);
}
  ~NaseTrida() {
Dispose();
}
public int PocetInstanci  {
    get{retur n pocetInstanci;}
  }
}
buďto explicitním voláním metody Dispose()
using System;
class CMain {
public static void Main() {
NaseTrida NT;
for (int i=0; i<10; i++) {
NT = new NaseTrida();
Console.WriteLine (NT.PocetInstanci);
NT.Dispose();
}
Console.ReadLine();
}
}
nebo vymezením působnosti třídy blokem using
using System;
class CMain {
public static void Main() {
for (int i=0; i<10; i++) {
using (NaseTrida NT = new NaseTrida()) {
Console.WriteLine (NT.PocetInstanci);
}
}
Console.ReadLine();
}
}

Souhlasím  |  Nesouhlasím  |  Odpovědět
Libb  |  31. 12. 2004 09:25

prosím

Souhlasím  |  Nesouhlasím  |  Odpovědět
Libb  |  31. 12. 2004 09:25

prosím vás,

Souhlasím  |  Nesouhlasím  |  Odpovědět
Libb  |  31. 12. 2004 09:25

prosím vás, asi

Souhlasím  |  Nesouhlasím  |  Odpovědět
Libb  |  31. 12. 2004 09:25

prosím vás, asi tomu trochu tozumíte.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Libb  |  31. 12. 2004 09:30

co to bylo? Ještě jednou: mám tenhle problém. Mám v nějakém list boxu seznam souborů .JPG v nějakém adresáři, mám definovánu Image obr a při změně označeného souboru v listboxu dělám zhruba tohle:
private nactiobr(string obrazek)
{
obr = null;
obr = Image.FromFile(obrazek);
}
jenže GC mně neuvolňuje staré obrázky z paměti, takže i když si obrázky procházím fakt pomalu, tak se dostanu velice rychle na víc jak 200 MB alokované paměti, což je už trošku moc. Když přidám GC.Collect(), paměť se v pohodě uvolní a výkon se dokonce zvýší (není třeba swapovat). Jak mám jinak tohle řešit? Předem díky

Souhlasím  |  Nesouhlasím  |  Odpovědět
Benjamin  |  01. 01. 2005 11:15

No, jestli volani GC.Collect opravdu pomaha, tak ho proste jednou za cas zavolej.

Souhlasím  |  Nesouhlasím  |  Odpovědět
viman  |  01. 01. 2005 16:00

Příklad jsem testoval na 1000 iteracích (a to rychlých v normálním cyklu) a dospěl jsem monitoringem paměti k těmto závěrům:

1)
Paměť se neuvolňuje tak rychle kvůli nedeterministicky spouštěným finalizacím. Přesně mi souhlasí ta velikost 200 MB. Více to ale nepřesáhne. Pak dojde díky spuštěné finalizaci k uvolnění a zase se to plní do těch 200 MB (a takhle to pořád iteruje). Ale je faktem, že na ta uvolnění nelze spoléhat.

2)
GC.Collect nepomáhá ani tak kvůli samotnému procesu uvolnění jako díky dalšímu efektu této funkce, a to přímého spuštění finalizátorů.

3)
Vše vyřešíte nejelegantněji a nejvýkoněji takto:

obr.Dispose();
obr = null;

Poté už GC.Collect() volat nemusíte.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Benjamin  |  01. 01. 2005 17:31

Jasne, celou dobu o tom tady tak chytre mluvim, a pak me to nenapadne... .No, ale stejne mam jeste poznamku - samozrejmne neni nutne ten odkaz nulovat, pokud je hned nastavovan na jiny objekt(to se vztahuje k te puvodni ukazce).
Ale abych ukazal, ze taky neco vim , tak reknu, ze pokud by nevadilo, ze program zabira 200 MB v pameti, dal by se misto toho zoptimalizovat vzhledem ke ctenim z disku, pokud treba uzivatel casto listuje mezi nekolika stejnymi obrazky. Bylo by totiz mozne pouzit tzv. weak reference - to jsou reference, ktere nebrani GC ve zlikvidovani objektu, ale PRED jeho zlikvidovanim umoznuji ziskani normalni refernce, a tehle priklad je temer ucebnicova zalezitost, jak je uplatnit - nekde bokem si drzet hash tabulku s weak referencema na vsechny obrazky, ktere nekdy byly nahravany.

Souhlasím  |  Nesouhlasím  |  Odpovědět
viman  |  01. 01. 2005 18:44

...Ale abych ukazal, ze taky neco vim

O tom tom tu snad nikdo nepochybuje . Hezký nový rok !!!

PS: Možná bychom se měli přesunout k novému dílu seriálu. Tady už je to nějaký těsný.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Benjamin  |  27. 12. 2004 17:05

Ono je to cele trochu slozitejsi. Ty destruktory totiz nejsou destruktory, ale tzv. finalizery. Neni to jenom slovickareni, jde skutecne o neco jineho. Docela zajimave nepriklad je, ze nejenom ze volani GC.Collect() neni uplne beznou a doporucovanou zalezitosti, ale v tomhle pripade by to ani nestacilo, musel by se totiz zavolat dvakrat po sobe (objekty s finalizerem totiz jednu Garbage collection preziji), z cehoz plyne dalsi ponauceni: nedeklarovat finalizer tam, kde to neni nezbytne, a nezbytne je to skutecne malokdy. Ve skutecnosti by se mel pouzivat jenom v pripade, ze trida pouziva nejaky externi zdroj (otevreny soubor, db spojeni...) a chceme mit jistotu, ze ho nezapomeneme uzavrit, nebo, pokud alokuje nejakou unmanaged pamet.
Rozhodne stoji za to, prostudovat si zalezitosti kolem rozhrani IDisposable a fce. dispose(), coz by mela byt i odpoved na ten problem s poctem instanci, ackoliv si myslim, ze pocitani instanci je akorat skolometsky priklad, ktery nema zadne prakticke pouziti.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Petrik  |  27. 12. 2004 17:28

by se mel pouzivat jenom v pripade, ze trida pouziva nejaky externi zdroj (otevreny soubor, db spojeni...) a chceme mit jistotu, ze ho nezapomeneme uzavrit...
 To je ve skutečnosti docela často....
 
objekty s finalizerem totiz jednu Garbage collection preziji
 
Toho jsem si tedy nikdy nevšiml - dalo by se to předvést? 
 

using System;
class CMain {
public static void Main() {
for (int i=0; i<10; i++) {
NaseTrida NT = new NaseTrida();
Console.WriteLine (NT.PocetInstanci);
NT = null; GC.Collect();
}
Console.ReadLine();
}
}
class NaseTrida {
  static private int pocetInstanci;
  public NaseTrida()  {
pocetInstanci++;
  }
  ~NaseTrida() {
pocetInstanci--;
  }
public int PocetInstanci  {
    get{retur n pocetInstanci;}
  }
}

Souhlasím  |  Nesouhlasím  |  Odpovědět
Benjamin  |  27. 12. 2004 18:07

Ukazat se to da a pomerne snadno:

using System;

namespace Final_ukazka
{
public class SFinalizerem
{
public SFinalizerem()
{
Console.WriteLine("Vyrobli me");
}

~SFinalizerem()
{
Console.Wr iteLine("Zabijeji me!!!");

}
}
class Class1
{
[STAThread]
static void Main(string[] args)
{
VyrobAZahod();
Console.WriteLine("Po prvni Collection:");
GC.Collect();
Console.WriteLine("P o druhe Collection:");
GC.Collect();
Console.ReadLine();< br>
}

static void VyrobAZahod()
{
SFinalizerem obj = new SFinalizerem();
Console.WriteLine("Vyrobeno a hned ho zahodim");
}
}
}


Vsimnout si toho neni snadne, u rozsahle aplikace se to projevi treba snizenim vykonu a zaryty Ceckar, ktery je zvykly psat vsude destruktor pak bude tvrdit, jak je to .NET na nic... .

Souhlasím  |  Nesouhlasím  |  Odpovědět
Petrik  |  27. 12. 2004 18:28

Mám-li  být  upřímný,  ničeho  zvlášního  jsem  si  na  Vaší  ukázce  nevšiml.  Po  prvním pokusu o finalizaci  je  třída SFinalizerem zkrátka "mrtvá"  a slovy klasika "už  nevstane"... Po  spuštění  Váš příklad vypíše toto (.NET 1.1):
 

Vyrobili  me
Vyrobeno a hned ho zahodim
Po prvni Collection:
Zabijeji me!!!
Po druhe Collection:

Souhlasím  |  Nesouhlasím  |  Odpovědět
Benjamin  |  27. 12. 2004 19:14

"Po spuštění Váš příklad vypíše toto (.NET 1.1)"

Nevim, v cem je problem, u me je ten objekt "zavrazden" az pri druhe kolekci.

"Po prvním pokusu o finalizaci je třída SFinalizerem zkrátka "mrtvá" a slovy klasika "už nevstane""

Mate samozrejmne na mysli objekt a ne tridu, ale to detail, zajimavejsi je totiz neco jineho:
Zkuste spustit tohle:

using System;

namespace Final_ukazka
{
public class SFinalizerem
{
public SFinalizerem()
{
Console.WriteLine("Vyrobli me");
}

~SFinalizerem()
{
GC.ReRegis terForFinalize(this);
Console.WriteLine("Ja jsem nesmrtelny!!!");
if (AppDomain.CurrentDomain.IsFinalizingForUnload() || Environment.HasShutdownStarted)
{Console.WriteLine("Ale stejne me zabijou... (");}

}
}
class Class1
{
[STAThread]
static void Main(string[] args)
{
VyrobAZahod();
Console.WriteLine("Po prvni Collection:");
GC.Collect();
Console.WriteLine("P o druhe Collection:");
GC.Collect();
for(int i=3;i<=50;i++)
{
Console.WriteLine("Po {0}. Collection:",i);
GC.Collect();
}
Console.R eadLine();

}

static void VyrobAZahod()
{
SFinalizerem obj = new SFinalizerem();
Console.WriteLine("Vyrobeno a hned ho zahodim");
}
}
}

Souhlasím  |  Nesouhlasím  |  Odpovědět
Benjamin  |  27. 12. 2004 19:47

Tenhle priklad ma jeste jednu vyhodu: Vzhledem k tomu, kolikrat po sobe je ten Collect volan, by prinejmensim aspon vetsinou melo platit, ze se finalizer zavola skutecne pri kazdem druhem sberu odpadku

Souhlasím  |  Nesouhlasím  |  Odpovědět
Petrik  |  27. 12. 2004 20:08

Nejspíš jsem ten příklad nepochopil, ale co konkrétně  je na předchozí ukázce zajímavého, nebo paradoxního? Demonstruje, že metoda GC.ReRegisterForFinalize dělá to, co má?
 
Pro mě je spíše zajímavé to, že se mi nedaří reprodukovat Vaši první ukázku, ani teorii o nutnosti dvou volání GC.Collect() na třídy (resp. objekty) s finalizérem (viz nejjednodušší možný případ níže)... Je to někde zdokumentováno?
 

using System;
class CMain{
static void Main() {
SFinalizerem C = new CSFinalizerem();
C = null; GC.Collect();
Console.ReadLine();
}
}
class SFinalizerem {
~ SFinalizerem() {
Console.WriteLine("Umírám...");
}
}

Souhlasím  |  Nesouhlasím  |  Odpovědět
Benjamin  |  27. 12. 2004 22:01

Vy nezmar, ale nemate pravdu. Ja bohuzel taky ne, trochu jsem to odflakl, a doufal, ze mi to projde..., takze poradne:
GC ve chvili, kdy nastartuje Garbage colection, zastavi vsechna ostatni vlakna (to musi, aby mohl potom v klidu updateovat vsechny reference). Samotny sber se sklada ze dvou kroku - oznaceni zijicich objektu a odstraneni tech mrtvych. Objekty s finalizerem ovsem pred odstranovaci fazi "ozivi", stejne, jako vsechny objekty, na ktere odkazuji a vlozi je do fronty objektu urcenych k finalizaci. Nekdy po skonceni sberu se GC rozhodne, ze u vsech tehto objektu ten finalizer zavola a ve chvili, kdy dobehne, tak jsou ty objekty skutecne definitivne "mrtve" a pri nejblizsim pristim sberu jsou zlikvidovany. To muze byt pricina ruzneho chovani na mem a Vasem pocitaci - u Vas to proste zavolalo finalizer hned, a me daleko pozdeji.

Co se tyka toho prikladu s GC.ReRegisterForFinalize - chtel jsem ukazat, ze objekt, urceny ke sberu, neni docista mrtvy, a jeste muze byt "vskrisen". A ma to i mozne prakticke uplatneni. C# (nevim jak VB.NET) obsahuje jeste tzv. Weak references, ktere nejsou brany v potaz pri sberu a maji specialni funkci, ktera umoznuje vytvorit silnou (to je normalni) referenci na objekt, ktery uz nema existovat, ale jeste nebyl znicen.

Souhlasím  |  Nesouhlasím  |  Odpovědět
viman  |  27. 12. 2004 22:30

Vždyť už jsem to popsal (trochu "dolejc") Ještě doplňuji:

Jeden cyklus GC je něco jiného než GC.Collect(). GC.Collect() může obecně provádět více cyklů, dokud není uvolněno vše, co uvolněno být má.

Finalizaci provádí samostatné vlákno a není možné zaručit, kdy a zda vůbec tak bude provedeno.

Pokud je zavoláno:

GC.Collect();
GC.WaitForPendingFinalizers ();

...běh je pozastaven, dokud nejsou všechny potřebné finalizátory provedeny. Může se stát, že to nebude pokračovat nikdy (ovšem lze provést přerušení aktuálního vlákna jiným vláknem).

Teď mě jen zajímá, zda je ještě jednou třeba provést GC.Collect(), aby se uvolnily všechny finalizované objekty. Domnívám se, že ano.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Petrik  |  28. 12. 2004 01:25

Tomu rozumím a za vysvětlení děkuji - ale popravdě řečeno - ptal jsem se, kde je zdokumentováno, že objekty s destruktorem (finalizerem) potřebují právě dvojí (?)  volání GC.Collect pro zavolání destruktoru? Žádná z ukázek, na které jsem narazil, se o této vlastnosti GC nezmiňuje.
...a ve chvili, kdy dobehne, tak jsou ty objekty skutecne definitivne "mrtve" a pri nejblizsim pristim sberu jsou zlikvidovany..
Nechápu, proč by jediné volání GC.Collect (bez ohledu na to, z kolika etap se samo o sobě skládá) nemělo a nemohlo vést k jednorázovému uvolnění všech momentálně disponibilních objektů z paměti (viz ukázka níže, doporučuji současně sledovat obsazení heapu ve Správci úloh Windows). Dokud neuvidím nějaký příklad, který toto chování reprodukovatelně vyvrací, nemám důvod tomu uvěřit. Příklad s ReRegisterForFinalize s tím imho nijak nesouvisí - zde je přece finalizace objektu výslovně odložena. 
  

using System;
using System.Runtime.InteropServices;
class CMain{
static void Main() {
Console.WriteLine("Alokuji objekt..."); Console.ReadLine();
CTest C = new CTest();
Console.WriteLine("Ruším objekt..."); Console.ReadLine();
C = null;
Console.WriteLine("Uvolňuji objekt.."); Console.ReadLine();
GC.Collect();
Console.WriteLine("Hotovo, konec..."); Console.ReadLine();
}
}
class CTest {
private IntPtr ip = Marshal.AllocHGlobal(99999999);
~ CTest() {
Marshal.FreeHGlobal(ip);
Console.WriteLine("Umírám...");
}
}

Souhlasím  |  Nesouhlasím  |  Odpovědět
viman  |  28. 12. 2004 03:50

1) Benjamin nepsal nic o tom, že je potřeba provést dvakrát GC pro zavolání destruktoru. Psal, že je třeba dvakrát provést GC (nebo-li dva cykly) pro UVOLNĚNÍ objektu z paměti. Uvolnění objektu z paměti NENÁSLEDUJE ihned po zavolání finalizátoru, ale provádí se v při nejbližší GC (pokud byla dokončena finalizace v samostatném vláknu).

2) Váš příklad nic neznamená, protože alokujete nespravovaný prostředek. I když provedete destruktor a paměť na haldě se uvolní, neuvolní se samotná paměť managed objektu třídy CTest. To se právě stane až při dalším volání GC.

Tady snad máte to, co chcete (nezapomeňte sledovat haldu):


using System;
using System.Runtime.InteropServices;
class CMain
{
static void Main()
{
Console.WriteLine("Alokuji objekt..."); Console.ReadLine();
CTest C = new CTest();
Console.WriteLine("Ruším objekt..."); Console.ReadLine();
C = null;
Console.WriteLine("Finalizuji objekt.."); Console.ReadLine();
GC.Collect();
GC.WaitForPending Finalizers();
Console.WriteLine("Uvolňuji objekt.."); Console.ReadLine();
GC.Collect();
Console.WriteLine ("Hotovo, konec..."); Console.ReadLine();
}
}
class CTest
{
byte[] i = new byte[100000000];
~ CTest()
{
Console.WriteLine("Umírám...");
}
}

Souhlasím  |  Nesouhlasím  |  Odpovědět
Petrik  |  28. 12. 2004 05:34

OK - zdá se, že toto funguje přesně tak, jak oba uvádíte  - v případě, že objekt nemá destruktor, stačí k uvolnění paměti jeden úklid GC, v případě, že ho má potřebuje dva. Děkuji za ukázku, toto mě vcelku přesvědčilo a máte bod...
 
I když provedete destruktor a paměť na haldě se uvolní ...
 
Můj příklad, dealokující paměť v destruktoru je v tomto ohledu zmatečný, ale ukazuje, že v případě unmanaged resources destruktor pro jejich dealokaci určitý význam má. Pokud se ovšem  z konstruktoru ta dealokace vypustí a ponechá se na rozhodnutí GC, probíhá dealokace haldy stejně jako spravovaného pole bajtů - a to přesně tak, jak jste předpověděli. Oba se ostatně alokují na heapu, protože pole je referenční typ.

Souhlasím  |  Nesouhlasím  |  Odpovědět
viman  |  27. 12. 2004 18:32

GC pracuje v jakýchsi cyklech. Ačkoli nyní nevím, kolik cyklů je provedeno při jednom provedení GC, zcela určitě platí fakt, že pro uvolnění paměti objektů s finalizátorem je třeba dvou cyklů. O tom se nemá smyslu přít, protože to lze najít v dokumentaci o MS CLR.

Teoreticky demonstrace na příkladu není možná právě z důvodu nondeterminence provedení GC.

Pominu-li tuto nondeterminenci, tak vím, že GC se standardně provádí při alokaci paměti - tudíž váš příklad v jednom cyklu provede GC.Collect() ve skutečnosti minimálně dvakrát (poprvé explicitně a podruhé implicitně při volání new NaseTrida()).

K těm externím zdrojům:
Externími zdroji zde rozumíme zdroje "nespravované" (unmanaged). Tyto zdroje jsou v .NET obvykle zapouzdřeny do spravovaných tříd. Takže i databázové spojení (např. SqlConnection) je třídou spravovanou a v objektu, která ho používá není třeba definovat finalizátor.

Souhlasím  |  Nesouhlasím  |  Odpovědět
viman  |  27. 12. 2004 18:51

Opravuji svůj výrok, že GC.Collect() se provádí po každé alokaci paměti. Provádí se pouze kontrola, zda je ještě dostatek paměti. Sběr se samozřejmě provede pouze v negativním případě. Takže ten příklad by v některých případech snad fungovat mohl (ale zaručit to nelze).

Souhlasím  |  Nesouhlasím  |  Odpovědět
Petrik  |  27. 12. 2004 19:43

To určitě, protože když ve svém příkladu vypustím volání GC.Collect(), počet instancí monotonně narůstá, dokud aplikace neskončí, konsekutivní alokace paměti na to nemají vliv. Od dvou cyklech GC mi není nic známo, je ale jasné, že nejprve se z paměti uvolňují objekty bez finalizeru. Moje zkušenost s GC.Collect je právě opačná, uvolňuje z paměti nepoužité objekty dost agresivně, i tehdy, když je instance deklarovaná, ale v dalším kódu už použitá/referencovaná není (resp. je jen nastavena na null). S tím, že by po prvním volání GC.Collect() objekt nezaniknul a po dalším ano jsem se nikdy nesetkal, ani k tomu nevidím důvod.

Souhlasím  |  Nesouhlasím  |  Odpovědět
viman  |  27. 12. 2004 21:23

Mě zase není úplně známo, co všechno dělá GC.Collect(). Vím akorát to, že ruší objekty všech generací (i když to lze overloadovat) a zastaví všechna dosavadní vlákna do té doby než se kompletně provede uvolnění (včetně finalizací).

Mě ovšem šlo spíše o implicitní GC. To zdaleka tak agresivní není. Při jednom cyklu se objekty bez finalizátoru (a příslušné generace) odstraní okamžitě. Objekty s finalizátory se pouze označí pro finalizaci. Finalizaci takto označených objektů provádí nezávislé vlákno. Poté, co jsou objekty finalizovány jsou připraveny pro uvolnění z paměti. To ale lze provést až v dalším cyklu GC (není totéž co GC.Collect). Také neexistuje kontrola nad tím, kdy bude finalizátor zavolán (a zda vůbec!!!). To má ten nepříjemný důsledek, že to může řetězově pozdržovat uvolňování paměti objektů, které odkazují na objekt s finalizátorem.

Takže shrnuto - bez finalizátoru se obejít, kdy to jen lze. Vhodnou metodou je samozřejmě implementovat rozhraní IDisposable, kde v metodě Dispose se zavolá GC.SuppressFinalize(this), čímž se objekt označí k uvolnění a finalizaci není třeba provádět. Uživatel třídy by může volat Dispose, když chce uvolnit prostředky.

Souhlasím  |  Nesouhlasím  |  Odpovědět
cicobasket, cicobasket  |  11. 05. 2006 09:01

test...

Souhlasím  |  Nesouhlasím  |  Odpovědět
nik  |  27. 12. 2004 20:25

Sorry, možná (ba dosti pravděpodobně) trochu mimo, ale počet instancí je pro mne důležitý - například základní dialog pro výběr záznamu z databáze je mimo provoz, pokud mám zobrazen prohlížeč vybraných záznamů (instance) - pak vybírám k detailnímu zobrazení záznamy uvedené v prohlížeči vybraných záznamů. A po odstranění posledního prohlížeče (instance) vybraných záznamů obživne základní dialog výběru. Size jde o BP 7.02, ale princip je stejný ...

Souhlasím  |  Nesouhlasím  |  Odpovědět
viman  |  27. 12. 2004 21:35

No, právě, že ten princip není stejný. Vy potřebujete počítat "použitelné" (odněkud referované) instance. V prostředí bez GC to můžete řešit přímo v konstruktoru a destruktoru. V prostředí s GC musíte dekrementaci provést v nějaké své metodě, kterou poctivě zavoláte ručně, pokud považujete objekt (dialog) za nepotřebný. Takže budete muset zavolat ručně např:


dialogObject = null;
dialogClass.PocetInstanci--;


Pak to samozřejmě nesmíte dekrementovat ve finalizaci. Ano, je to méně pohodlné, ale výhody GC podle mého skromného názoru převažují

Souhlasím  |  Nesouhlasím  |  Odpovědět
Benjamin  |  27. 12. 2004 22:06

Naucit se novy jazyk neznamena jenom naucit se novou syntaxi, ale i trochu jiny zpusob mysleni. V C# se proste programuje jinak, nez v Pascalu/Delphach. Az se s tim jazykem trochu szijes, temer urcite zjistis, ze ten problem se da vyresit nejak jinak.

Souhlasím  |  Nesouhlasím  |  Odpovědět
nik  |  27. 12. 2004 22:53

Programoval jsem v Tesla Assembleru, EC Assembleru, EC Cobolu, Pascalu, PC Assembleru, poprvé vlastně ve strojovém kódu východoněmeckého počítače Celatron s s gigantickou bubnovou pamětí čítající (asi) 1000 míst pro čísla a instrukce (to nebyly bajty, ale v počtu se možná pletu). Ta instance, která něco cosi provádí, samozřejmě musí být referována ( v Delphi se provádím Instance.Metoda(xxx)), ale když je zde příklad na statickou metodu třídy udávající počet instancí, tak bych byl rád, aby ten počet instancí souhlasil VŽDYCKY.

Souhlasím  |  Nesouhlasím  |  Odpovědět
nik  |  27. 12. 2004 23:03

A abych doplnil taky Fortran, Algol 60, různé dosovské verze Basicu, a jestli chcete tak taky HTML+CSS+JavaScript. A samozřejmě je možné lecjaký jazyk znásilnit a dělat v něm leccos z toho, pro co nebyl určen. Ale bacha, abychom se neustále nedrbali levou rukou za pravým uchem. S pozdravem BXLE prapůvodní assemblerista ...

Souhlasím  |  Nesouhlasím  |  Odpovědět
viman  |  27. 12. 2004 23:35

tak bych byl rád, aby ten počet instancí souhlasil VŽDYCKY

Tak toho dosáhnout nelze (může existovat nedeterministicky dlouhá prodleva mezi finalizací a fyzickým uvolněním paměti).

Trpíte typickým "syndromem assembleristů". Ti totiž těžko snášejí situaci, kdy nemůžou něco dělat přesně tak jak chtěj. Někteří co znám se s tím smířili třeba po 6 letech, někteří ještě vůbec. Stále je v nich ale skepticismus ke všemu, co má k assembleru daleko. Ale jinak veškerá úcta k Vám.

Souhlasím  |  Nesouhlasím  |  Odpovědět
nik  |  28. 12. 2004 09:01

Ale kdepak assembler, mojí "mateřštinou" je Algol 60 a z něj plynoucí Pascal, ve kterém programuji uplynulých  15 let, takže mne nemrzí, že nemůžu "assemblerovsky" dělat cokoliv. Ale vadí mi ta neustále se zvětšující "inteligentní" černá skříňka, která se snaží dělat spoustu věcí za mne, ovšem poněkud nevyzpytatelně ...

Souhlasím  |  Nesouhlasím  |  Odpovědět
viman  |  28. 12. 2004 14:11

No, to je ale přesně ten syndrom. Zkuste mi vysvětlit, proč Vám to vadí? Je to tím, že sama o sobě ta skříňka funguje špatně (v rozporu s dokumentací)? Nebo tím, že se chová jinak než intuitivně očekáváte? Nebo myslíte, že s ní nelze efektivněji řešit problémy, pro jejichž řešení především byla navržena?

Ten Váš příklad rozhodně lze řešit stejně složitě jako v Object Pascalu. Dekrement si dáte do metody Dispose a tu zavoláte ve chvíli, kdy Vás objekt přestane zajímat - stejně jako voláte metodu Free v Object Pascalu. Potom odstraníte všechny případné reference na objekt (přiřazením null). Stejně tak v Object Pascalu musíte reference odstranit přiřazením nil do příslušných pointerů na objekt. Je to úplně stejné. Pokud navíc nutně chcete objekt z paměti uvolnit hned (důvody jsou opravdu výjimečné), můžete v nejhorším zavolat GC.Collect(). Dokonce vůbec nepotřebujete implementovat finalizátor (a ani to ve vašem případě nedoporučuji). Stačí abyste si ohlídal zavolání Dispose před zánikem poslední reference. To samé ale přece musíte udělat v Object Pascalu - nedopustíte přece, aby jste zrušil všechny reference na objekt před zavoláním Free - to byste si uříznul větev sám pod sebou. V managed kódu se vám o případnou uniklou paměť postará GC. Navíc je obrovský benefit, že "nechat uniknout paměť" je zároveň standardním nástrojem na její uvolnění - nemusíte se o ni starat.

Mimo jiné, v .NET je i alokace paměti obvykle rychlejší než v unmanaged kódu, protože spravovaná halda se trvale snaží udržovat v kompaktním stavu.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Petrik  |  28. 12. 2004 00:37

Podle mě ani počítání referencí a ani deterministické finalizace koncepci GC neodporuje, jenom prostě zatím není implementována  - dtto Whidbey feature list pro Managed C++, kde je  mj. podpora deterministické finalizace zmíněna.
ht tp://discuss.develop.com/archives/wa.exe?A2=ind0010a&L=dotnet&F=&S=&P=39459
http://www.sellsbrothers.com/tools/RotorRefCounting.doc
 
Zatím se tedy musíte spokojit s tím, že si v odůvodněných případech musíte implementovat vlastní counter pro počítání odkazů a IDisposable wrapper pro uvolnění zdrojů, souvisejících s objektem. Jak z diskuse vyplynulo, objekt samotný tím z paměti sice stejně neuvolníte, ale aspoň předejdete nejhorším důsledkům, vyplývajícím z nedeterministické finalizace.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Benjamin  |  28. 12. 2004 17:47

Jenomze jeden z tech odkazu je 4 a druhy 2 roky stary. Navic .NET v soucasnosti deterministickou finalizaci prece podporuje, a je to ono zminovane rozhrani IDisposable.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Petrik  |  28. 12. 2004 23:36

Sám jste přece celkem komplexně vysvětlil, že to s tou deterministickou finalizací není zas tak žhavé - vzhledem k tomu, že v některých případech vyžaduje dvě kolekce GC...

Souhlasím  |  Nesouhlasím  |  Odpovědět
viman  |  29. 12. 2004 01:44

Ne tak úplně - finalizace není přímo spojena s uvolněním paměti, je jen podmínkou nutnou pro uvolnění, nikoli však postačující.

Finalizace se provádějí v samostatném vlákně (s vysokou prioritou), které je aktivní, dokud tzv. "freachable queue" (fronta objektů, na nichž je třeba zavolat metodu Finalize()) není prázdná. Ve chvíli, kdy se tato fronta vyprázdní, vlákno přejde do stavu "sleep". GC tedy finalizace nespouští, ale pouze přidává reference do té fronty (a tím se vlákno zase probudí). Vtip je v tom, že existence reference v té frontě má stejný charakter jako existence reference v našem programu. Tím je objekt uchráněn před uvolněním z paměti. Po finalizaci následuje vyřazení objektu z té fronty a pak už nic nebrání objekt definitivně uvolnit - a to se provede při nejbližším cyklu GC po finalizaci (zaručeně jen tehdy, pokud jde o úplný cyklus, který odstraní všechny generace). Takže mi jistě dáte za pravdu, že mezi finalizací a uvolněním může být ještě velká prodleva, a to zejména tehdy, je-li objekt ve druhé generaci.

Souhlasím  |  Nesouhlasím  |  Odpovědět
viman  |  27. 12. 2004 21:46

...ale ani tak není jisté, že dojde k úklidu okamžitě

Pravdě v tomto jediném případě to jisté je. Proto se manuální provedení GC.Collect nedoporučuje (neblahý dopad na výkon).

Souhlasím  |  Nesouhlasím  |  Odpovědět
Petrik  |  28. 12. 2004 01:51

Řekl bych, že k (pokusu o) úklidu sice dojde okamžitě, ale nikoliv k samotnému uklizení objektů, pokud nejsou skutečně momentálně disponibilní (nejsou např. referencovány dalšími objekty, vybaveny finalizerem(??)). A to ještě předpokládám, že neplatí tak docela tvrzení, podle kterého je vlastní uvolnění objektů provedeno v několika krocích - voláních(??) threadu GC.Collect.
 
Obávám se, že ten, kdo tuto diskusi čte, musí být nyní dokonale zmaten... 

Souhlasím  |  Nesouhlasím  |  Odpovědět
viman  |  28. 12. 2004 02:58

Za tím prvním si stojím, protože mohu docela dobře předpokládat, které objekty jsou v danou chvíli disponibilní (mohu přece vědět, že na objekt neexistuje reference a nemá finalizátor). Default GC se od násilně vyvolané GC.Collect() liší tím, že uklízí pouze nějakou generaci. Proto při vyvolání GC.Collect() lze předpokládat, že všechny objekty, které odstranitelné jsou, tak budou odstraněny, což při implicitní GC nenastává.

Za tím druhým tvrzením teda už raději nestojím. Zmátl mě jeden zdroj (vlastně vícero) a přemýšlet jsem o tom začal až poté, co jsem to napsal . Takže jsem tím mohl docela slušně zmást - omlouvám se.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Petrik  |  28. 12. 2004 04:53

...mohu přece vědět, že na objekt neexistuje reference a nemá finalizátor...
 
Ta otázka úzce souvisí s počítáním referencí. A obávám se, že její zodpovězení nemusí být ani v poměrně jednoduchých případech nijak triviální - dtto vzájemné reference, reference na hluboké kopie objektu a odkazy na instance v threadech a vnořených blocích kódu apod. - jinak by počítač referencí byl běžně k dispozici jako součást rozhraní objektu. Zkuste si třebas tipnout výstup programu níže  (btw myslím, že na základě jeho chování vznikla teorie o nutnosti dvojího volání GC)....
 

using System;
class CMain {
public static void Main() {
CTest[] aCT = new CTest[] {new CTest(), new CTest(), new CTest()};
aCT[0] = new CTest();
GC.Collect();
Console.WriteLine(aCT[0].pocetInstanci);
aCT = null;
GC.Collect();
Console.ReadLine();
}
}
class CTest {
  static private int i;
  public CTest()  {
i++; Console.WriteLine("Vznikla instance č.{0,00}", i);
  }
  ~CTest() {
i--; Console.WriteLine("Zanikla instance č.{0,00}", i);
}
public int pocetInstanci  {
    get{retur n i;}
}
}

Souhlasím  |  Nesouhlasím  |  Odpovědět
Petrik  |  28. 12. 2004 06:34

Tu zmínku o teorii taky raději odvolávám - tuším sice, jak asi mohl původně vzniknout rozpor ventilovaný v  [27.12.2004 18:29], ale Vaše příspěvky [27.12.2004 17:05] a [28.12.2004 03:50] jako celek popisují situaci docela přesně.
apropos, ten destruktor by správně měl vypadat následovně, aby vypisoval aktuální počet instancí:
 

  ~CTest() {
Console.WriteLine("Zanikla instance č.{0,00}", i); i--;
}

Souhlasím  |  Nesouhlasím  |  Odpovědět
Benjamin  |  28. 12. 2004 11:52

Jenomze GC nepocita reference (to, tusim, delal ve verzi 6.0 VB). GC postupuje jinak - on nehleda mrtve objekty, ale zive objekty. A vsechny objekty, ktere nejsou zive, povazuje za mrtve.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Petrik  |  28. 12. 2004 17:29

Nemyslím, že ve VB6 existuje nějaký dedikovaný proces pro úklid paměti v duchu GC, počítání referencí ve VB6 zajišťuje COM runtime, které si je ukládá v tabulce objektů hned za adresu objektu. Takže ve VB6 počítání instancí objektů nepředstavuje žádný problém.
 
Function GetRefCount&(o As IUnknown)
   RtlMoveMemory GetRefCount, ByVal ObjPtr(o) + 4, 4
   GetRefCount = GetRefCount - 2
End Function

 
btw Co je mrtvý objekt? Až dosud jsem se domníval, že mrtvý objekt je ten, na který není žádná reference.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Benjamin  |  28. 12. 2004 18:01

Vzdyt jsem to tak psal (pravda, mozna trochu nezretelne formulovano): VB 6.0 pocital reference. GC v .NETu to nedela.

ad mrtve objekty - Neni totiz docela pravda, ze na mrtvy objekt nevede zadna reference - Na mrtvy objekt muze vest treba stovka referenci, ale vsechny vedou z jinych mrtvych objektu. To je mimo jine duvod, proc pro normalniho cloveka neni treba finalizatoru - zadny objekt se pri svem zaniku nemusi starat o uvolnovani referenci na jine objekty spravovane GC.

Souhlasím  |  Nesouhlasím  |  Odpovědět
viman  |  28. 12. 2004 19:01

Toto je i má odpověď na příspěvěk z 28.12.2004 04:53. Tohle je přímý důsledek použití kořenového stromu dosažitelných objektů při GC. Jednoduše řečeno, když je nějaký podstrom nedosažitelný, na vazbách uvnitř takového podstromu vůbec nezáleží, všechno bude odstraněno.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Petrik  |  28. 12. 2004 19:26

neni treba finalizatoru - zadny objekt se pri svem zaniku nemusi starat o uvolnovani referenci ...
 
To ovšem ve VB, Delphi aspol. rovněž nemusíte - přesto jejich destruktory mohou výrazně zjednodušit aplikační logiku, např. soustředit procesy spojené s úklidem objektu do jedné automaticky volaného objektu. Nedeterministická finalizace v podobě, v jaké je praktikuje GC považuji nejen za funkční nedostatek, ale dokonce dosti závážný ústupek vůči praktikám OOP (pokud mě např. nutí separovat počítání instancí od destrukčního procesu objektu). Z podobného důvodu jsou přece v C# 2.0 zavedeny iterátory.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Benjamin  |  28. 12. 2004 20:21

Pokud mi pamet nespravuje GC, tak se musim sam starat budto primo o likvidaci objektu, nebo o uvolnovani referenci (pokud se nekdo jiny stara o uvolnovani objektu prostrednictvim pocitani refernci). destruktory u objektu slouzi predevsim k tomu, aby po sobe objekty "uklidily", coz ovsem v prostredi spravovanem GC neni treba - objekty si mohou po sobe nechat jakykoli svincik (s vyjimkou unmanaged zdroju) a GC ho uklidi za ne.
Nedeterministicka finalizace rozhodne neni nedostatek - nic vam nebrani pouzivat Dispose, kdyz si myslite, ze ho doopravdy potrebujete, a finalizator si nechat jenom jako zalohu, kdyz zapomenete Dispose zavolat, coz je take ucel, ke kteremu jsou finalizatory primarne urceny.

Souhlasím  |  Nesouhlasím  |  Odpovědět
viman  |  28. 12. 2004 18:57

Je rozdíl mezi počítáním referencí a zjišťováním, zda je objekt refrencován. Počítání referencí je jedna z metod správy paměti. Znamená to, že se u každé instance udržuje counter, což zvyšuje paměťové požadavky. Objekt v .NET CLR nic takového neudržuje. Při GC se vytvoří kořenový strom dostupných objektů a poté se halda lineárně prochází (ono je to ještě generačně optimalizováno, ale to je teď vedlejší). Není-li objekt ve stromu, označí se pro sběr (první etapa GC). V další etapě GC (ve stejném cyklu !!!) se provede uvolnění paměti uvolnitelných objektů, generační přesun a zkompaktnění haldy.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Petrik  |  28. 12. 2004 19:48

V zásadě reference počítat nemusíte, pro rozhodovací proces stačí počítat první instanci na každé úrovni stromu objektů. Takovou rekurzi musí implementovat i deterministický finalizer, ale počítáním instancí navíc získá možnost uvolnit objekt na vyžádání. Co se nutnosti udržovat u každé instance counter týče, právě z důvodů lepší a přesnější správy paměti očekávám, že deterministická finalizace se sledováním referencí bude časem implementováno v managed C++, později  možná i v ostatních jazycích .NET aspoň v STA režimu (obdoba smart pointerů C++) s cílem skloubit výhody obou typů finalizace (viz např. http://www.socalnetug.org/reference/determ_final.htm)

Souhlasím  |  Nesouhlasím  |  Odpovědět
Benjamin  |  28. 12. 2004 20:31

Jenomze ten odkaz je z roku 2000. Od te doby probehlo mnoho dalsich diskusi a vysledkem je, ze .NET nic, jako pocitani referenci nema, a ani mit nebude.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Petrik  |  28. 12. 2004 21:51

Nikdy bych neříkal nikdy...  Implementace správy odkazů není zas tak obtížná, pokud na ní není založena správa objektů - viz odkaz (z roku 2002), uvedený níže - jde o oficiální výzkumný grant financovaný Microsoftem.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Benjamin  |  28. 12. 2004 23:07

Ja bych nekdy nikdy klidne rekl, michani GC a ref-countingu by prineslo spoustu obtizneresitelnych problemu, jak se pise v jednom clanku, na ktery jste tady sam odkazoval:

There are two basic issues that we couldn't work around:

(1) composition - Any time you take an object that requires deterministic finalization and store it in an object that does not, you have lost the determinism transitively. The problem is that this strikes at the heart of the class hierarchy. For example, what about arrays? If you want to have arrays of deterministic objects, then arrays had better be deterministic. What about collections, hash tables, .... The list goes on and on and before you know it, the entire class library is ref counted. The other alternative is to bifurcate the class library - have deterministic arrays and non-deterministic arrays, etc. We thought about this, but soon concluded you'd have two copies of the whole framework and that would be confusing, perform horribly (you'd have 2 copies of every class loaded) and in the end wouldn't be practical. We could think of specific solutions to specific classes (like I think, although I can't remember, that we came up with some way to make arrays work, but the solution just didn't scale to the whole framework). The fundamental problem is that if a non-deterministic object contains a reference to a deterministic one, the system is not deterministic. We also considered outlawing it; simply having that be an error. Again we felt that you couldn't write real programs under those constraints.

(2) casting - A somewhat related issue is what about casting? Can I cast a deterministic object to System.Object? If so is it ref counted then? If the answer is "yes" than everything is ref counted. If the answer is "no" then the object loses determinism. If the answer is "it is an error" then it violates the fundamental premise that System.Object is the root of the object hierarchy. And what about interfaces? If a deterministic object implements interfaces, is the reference typed as an interface ref counted? If the answer is "yes" then you ref count all objects that implement interfaces (note System.Int32 implements interfaces). If the answer is "no" then you lose determinism. If the answer is "it is an error" then deterministic objects can't implement interfaces. If the answer is "it depends on whether the interface is marked deterministic or not" then you have a bifurcation problem of a different sort. Interfaces aren't supposed to dictate object lifetime semantics. What if some guy implemented an API that takes an ICollection interface (pick your favorite) and your object that implements it needs determinism but the interface wasn't defined that way? You are screwed. This leads to further bifurcation. Everybody defines 2 interfaces, one deterministic and one not and implements every method twice. Believe it or not, we actually looked at what it would take to do this automatically and to automatically generate the versions of the methods (deterministic or not) as they were used. That whole line of thought deteriorated into immense complexity. All of this led us to conclude that a really automatic and simple type modifier couldn't be done. That said, there are some ideas below on how we could relax some constraints and get something that might be helpful.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Petrik  |  28. 12. 2004 23:28

Budiž, to je do jisté míry věc názoru. Viz např. zde  https://lists.xcf.berkeley.edu/lists/advanced-java/2000-August/012002.html
a Recycler projekt realtime GC od IBM http://www.research.ibm.com/people/d/dfb/recycler.html. IBM se vůbec hodně angažuje ve výkonových optimalizacích a realtime aplikacích Javy.

Souhlasím  |  Nesouhlasím  |  Odpovědět
Zasílat názory e-mailem: Zasílat názory Můj názor