Try-catch ja jõudlus
12.03.2008 | Gunnar
Vigade käsitlemisel tuleb alati arvestada sellega, et see pakub süsteemile lisategevust. Objektorienteeritud keeltes on tavaline try-catch blokkide kasutamine. Lähenemise mõttes on try-catch igati korrektne, kuid arvestama peab sellega, et jõudluse osas on ta suhteliselt ootamatu.
Halb stiilinäide on lisada igasse meetodisse igaks-juhuks-veakäsitlus. Näide koodis võiks olla midagi sellist.
{
try
{
AccountCollection accounts;
accounts = AccountManager.ListUnaccepted();
foreach(Account account in accounts)
RegisterAccount(account);
}
catch(Exception ex)
{
this.Errors.Add(ex.Message);
}
}
private void RegisterAccount(Account account)
{
try
{
if(account.Blocked)
return;
account.Accepted = true;
}
catch(Exception ex)
{
this.Errors.Add(ex.Message);
}
}
Selline lähenemine on suhteliselt mõttetu, sest paljud vead võib ära käsitleda hoopis kuskil kõrgemal. Kuskil eespoos, kus midagi tehakse. Samuti võib paljudel juhtudel minna odavamat teed ning kirjutada kontrollid. Eeltoodud juhul võiks vigade käsitlemise jätta vabalt CreateAccounts() meetodi hoolde, sest seal tehakse vigade käsitluses ära täpselt sama töö, mis RegisterAccount() meetodis.
Praktiline kogemus. Üks süsteem, kus vigade käsitlus oli igas meetodis, osutus mahukamate tegemiste juures päris aeglaseks. Peale veakontrollide vähendamist ja eeltoodud andmete kontrollide lisamist tõusis süsteemi jõudlus 50%
Selles, et viga "ronib" läbi mitme meetodi, pole mitte midagi halba. Oluline on see, et see mingis punktis siiski ära käsitletakse.

12.03.2008 kell 09:52
Väga palju oleneb, mis seal try-catchi sees toimub. Kunagi olin juures, kui kaks teoreetikut vaidlesid - ühe arvates peab kõik alati olema try-catch sees, teine arvas et ainult äärmuslikel juhtudel tuleb veahaldust kasutada, .NET’is tuleks lihtsalt pidevalt NULL jms kontrolle teha.
Tüüpiliste teoreetikutena nad ei teinud katseid. Mina aga tegin. Ning selgus, et n-ö “tavatehete”, so muutujate deklareerimine, stringitöötlus, aritmeetilised tehted, puhul oli veahalduse kulu praktiliselt olematu - ca 10s jooksvas tsüklis puhul oli veahaldusega versioon tuhandiksekundi jagu aeglasem.
Küll aga on oluliselt kulukam veahaldus “keerukate” asjade puhul - boxing/unboxing, suuremate klasside loomine jne. Sel puhul oli erinevus ca 20%.
Nõnda et ühest vastust ei ole. Alati taandub asi ka sellele - kas on kasutaja jaoks reaalset kiiruse vahet? Kas vastuse saamine võtab 0.01 või 0.05 sekundit - aga teisel korral ei saada koledat “Exception:…” teadet, siis veahaldus on kindlasti vajalik ja hea. Kui aga erinevus on 0.1 ja kolm sekundit…
12.03.2008 kell 10:09
Kontrollide tegemine vähendab try-catch plokkide mahtu oluliselt ja see on igati soovitatav lähenemine. Boxing/unboxing - C# 2.0 võimaldab generice abil luua ka value type’ide jaoks sobivad kollektsioonid ja listid.
Mahukamate klasside korral on üks variant see, et lood ühe instantsi ja teed sellest edaspidi deep copy. Alati ei pruugi see muidugi võimalik olla, kuid omamoodi tagasihoidliku puhverdamise võimaluse pakub see lähenemine välja küll.
Erinevused stiilist 0.1 ja 3 sec on enamasti põhjustatud mingitest peenematest nüansidest, mida algul tähele ei pane. Enamasti saab neist siiski üle, kuid aega võib selliste asjade peale kuluda päris palju.
12.03.2008 kell 11:43
Väike keelekommentaar — ajaliselt {peale} → pärast.
14.03.2008 kell 11:30
Ise väldin try / catchi nagu katku ehk niivähe kui on vaja. Kõige depressiivsem näide on ilmselt vanakoolimõistes skalaarsete tüüpide parsimine. int.Parse(), float.Parse() jms. Näiteks int.TryParse() kasutamisega ja try/catch blokkidest loobumisega õnnestus viimati vähendada ajakulu 3-4 sekundilt peaaegu momentaalseks. Põhiprobleem ei teki ju mitte try/catch olemasolu tõttu vaid siis kui see pagana Exception reaalselt ka visatakse.
14.03.2008 kell 11:37
See oleneb päris üksjagu sellest ka, et kas on debug mode või ei. Debugi korral koostatakse exceptioni trace program debug database’i põhjal ja selles megadesse ulatuvas kuhjas ringi tuhlamine võib tõesti ajakulukas olla.