Soci (Soczó Zsolt) szakmai blogja

2015.03.12.

SQL teljesítményoptimalizálás – imádom

Az elmúlt 3 hétben egy fejlesztési projektből kicsípve 3 napot három cégnél is SQL Server teljesítményoptimalizáltam (és még vannak cégek a queue-ban, ilyen erős évindulásom még soha nem volt :). Azt kell mondjam, ez a legkedvesebb munkám mindenek felett. 1 nap alatt általában igen látványos eredményeket lehet elérni, eddig még soha nem csalódtak bennem ügyfelek.

Az áprilisom és a májusom teljesen tele van már, de ha valakinek hasonlóra van igénye, jelezze nekem, ha becsúszik valahol egy luk, egy napra lehet el tudok ugrani. Júniustól egyelőre még laza a naptáram, oda könnyebb tervezni.

A teljes üzleti transzparencia jegyében, és hogy könnyű legyen kalkulálni leírom az óradíjamat: 20000 Ft + ÁFA / óra. Azaz egy nap igen intenzív (lóg a nyelvem a nap végére tényleg, nagyon intenzív gondolkodást igényel a munka) optimalizálás 160e + ÁFA. Valaki erre biztos azt mondja, ez sok, megértem. Valaki viszont, aki már találkozott 30-40 ezres konzulenssel, annak ez olcsónak számít, tudva, hogy nem szoktam az ügyfeleknél húzni az időt, hanem nyomom a munkát nagy erővel. Egy nap alatt ki lehet végezni legalább 5-15 top lekérdezést, amitől nagyon meg szoktak könnyebbülni a szerverek. Azaz ennyi pénzből garantáltan jelentősen fel fog gyorsulni a szerver. Nyilván csodát nem tudok tenni, ha egy alkalmazás több ezer apró lekérdezéssel old megy egy feladatot, akkor a network roundtrip idején nem tudok segíteni, hiába gyorsítok fel egy lekérdezést pár msra. Vagy ha össze kell szummázni egymilliárd sort, az nehéz gyorsan megcsinálni. Azaz architekurális problémákon nem tud segíteni a db oldali hangolás.

A másik, amire fel kell készülni, hogy néha módosítani kell a hívó kódokon is, illetve időnként módosítani kell az adatbázis szerkezetét is. Én demonstrálom, mivel jár, ha ezt megteszi az ügyfél, aztán a döntés az övé, meglépi-e? Mivel ezeket nem lehet azonnal meglépni, amikor ott vagyok, gyakori, hogy az éles rendszerbe bevezetett módosításokat még egyszer át kell néznem. Ezt már legtöbbször TeamViewerrel vagy RDP-vel szoktam itthonról megtenni, mivel ez 1-2 óránál már nem visz el többet, nem éri meg ennél többet utazni miatta.

Hogy hatékony legyen a munka annyit szoktam kérni, hogy legyen jogom monitorozni az éles szervert, és legyen egy tesztszerver, ami egy restorolt adatbázist vagy adatbázisokat tartalmaz az éles rendszerről.
Ezen dokumentáltam be tudom mutatni, hogy ha az éles szerveren végrehajtják azokat a változtatásokat, amiket javaslok, akkor mennyivel lesz gyorsabb a rendszerük.

A munka része még, amikor átbeszéljük a fejlesztők vagy üzemeltetők fejében felmerült kérdéseket.

Az optimalizálási munkának egy hátránya van: mivel mindig az ügyféllel együtt végzem a munkát, és közben részletesen elmondom, mit és miért csinálok, általában már nem hívnak legközelebb, mivel kitanulják, mit kell tenni a lassulás esetén. :)

Ps. jövő héten lejár a 25%-os, 150e-es TDD tanfolyam akció, utána már csak ősszel fogok indítani tanfolyamot, mivel kilátásban van egy hosszabb projektem, így nem lesz rá időm. Aki akar, most szálljon fel a vonatra.

2011.03.05.

Érdekes .NET perf tapasztalat

Filed under: .NET,.NET 4,C#,CLR,Optimalizálás,Szakmai élet,Visual Studio,VS 2008 — Soczó Zsolt @ 12:53

Amikor profilerrel megnézünk egy .NET kódot sokszor megdöbbentő helyen lesz benne bottleneck.

Az alábbi kód 1% időt visz el egy nagyon processzorintenzív kódban:

if (bar.L == 0)

Ami ebben lassú, az a System.Decimal.op_Implicit(int32). A bar.L egy decimal. Érdekes, mi?
Mi a megoldás? A 0 legyen valóban decimal, de int, amit konvertálni kell:

if (bar.L == 0M)

1% kevés, de sok 1% már számít.

2010.05.09.

Walkthrough: Profiling With Automated Tests

Filed under: .NET,.NET 4,Optimalizálás,Szakmai élet,Visual Studio — Soczó Zsolt @ 23:11

Egyszerű, ha tudod hol kell keresni.

2009.09.22.

Windows Cache Extension for PHP

Filed under: Optimalizálás,Szakmai élet,Windows 2008 R2 — Soczó Zsolt @ 13:02

Hamarosan kipróbálom a jószágot. Folyamatban van egy munkám, amiben egy ismert websiteot migrálnak át Windows 2008R2 alá, valamilyen linuxról. Megnézzük, ugyanazon a vason mit lehet majd kihozni a php scriptekből Windows alatt. Ha már lúd, legyen kövér, megnézem ezt a Windows Cache Extension for PHP-t is, mennyit dob a teljesítményen.
A mérésből esettanulmányt írok, így majd meg lehet nézni a végeredményt.

2009.06.11.

A többszálú skálázás nehézségei

Filed under: .NET,Optimalizálás,Szakmai élet — Soczó Zsolt @ 11:58

(Egyszer megírtam ezt a postot hosszabban, de a wordpress a sorozatos Save-ek ellenére elvesztette…)
Az utóbbi időben az időm jelentős részét a tőzsdei kereskedő programom írásával töltöm, és eközben tanulom az Entity Frameworköt (nagyon sarkos, hogy finoman fogalmazzak), illetve próbálom kihasználni egy erős, 8 processzoros rendszer képességeit.
Nyilvánvalóan a szálak számának növelésével elvileg szépen meg lehet hajtani a processzorokat. Ám furcsa mód még a backtest programom, ami szoros ciklusokban pörög se tudta jobban kihajtani a procikat, mint 20-30%. A kérdést kicsit megvakargatva kiderült, hogy a Garbage Collector elviszi az idő 80%-át is egyes esetekben, azaz pazarlom a memóriát, ezt át kell írni, illetve meg kell nézni, hogy server vagy workstation GC megy-e a háttérben. A server GC párhuzamosított, a workstation nem, így az lehet torlódási pont.
Hogy az-e, azt döntse el mindenki a következő két kép alapján: :)

2009.04.27.

VS Profiler nem megy Hyper-V-s gépen

Filed under: .NET,Optimalizálás,Szakmai élet,Virtual PC,Visual Studio,VS 2008 — Soczó Zsolt @ 14:54

Felraktam a gépemre a Hyper-V-t. A VSTS profilert akartam használni, de azt mondta, No data collected.
Szerencsére megtaláltam ezt:
VS2008 does not support profiling in virtual environments (VMWare, VPC, Hyper-V).

Ez a jelek szerint nem csak virtuális guest-ekre vonatkozik, hanem a hostra is. Kikapcsoltam a BIOS-ban a virtualizálás támogatást, és egyből elindult a profiler.

Ezentúl rebootkor el kell döntenem, guest-eket akarok futtatni, vagy profiler-ezni.

2009.02.26.

.NET teljesítményhangolási tapasztalatok 7. – Hatékony UNC fájlelérés

Filed under: .NET,Optimalizálás,Szakmai élet — Soczó Zsolt @ 11:13

Hogyan közelítsük meg azt a problémát, hogy egy managed alkalmazásból naponta több milliószor el szeretnénk érni másik szerveren levő, viszonylag ritkán, pár percenként frissülő fájlokat, a lehető leghatékonyabb módon?

Először is érdemes tudni, hogy a workstation service, a windows fájok távoli elérésének kliense cache-el, az OS File Cache-ét használva, ami a helyi fájlokat is cache-eli. Ha azonban módosítják a megosztáson levő fájlt, kikapcsolja a cache-elése erre a fájlra, egészen addig, míg újra meg nem nyitjuk. Bővebben majd egy teljes cikkben is megemlékezek erről, amikor a Windows Cache részleteiről fogok írni (a technet site-on).

Szóval kapuk valamennyi teljesítménynövekedést az OS cache miatt, de mivel a fájl olvasása során a PInvoke-nak át kell mozgatni az adatokat a natív oldalról a managed oldalra, jelentős veszteségeket élünk meg azzal szemben, ha inprocess, managed memóriában tudnánk tartani a fájlok tartalmát, de kiütve őket onnan, ha megváltoznak.

Gondolom a legtöbben már hallottak a System.Web.Caching.CacheDependency típusról. Ezt alapvetően webalkalmazások használják, de 2.0 óta már egyéb appokban is használható. A segítségével bámulatosan egyszerűen lehet cache-elni a felolvasott fájlok tartalmát.

object o = HttpRuntime.Cache[cacheKey];
if (o != null)
{
string s = (string)o;
}
else
{
CacheDependency d = new CacheDependency(file);
string s = ProtectedRead(file);
HttpRuntime.Cache.Add(cacheKey, s, d,
Cache.NoAbsoluteExpiration,
Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
Console.WriteLine(“Content (re)cached”);
}

Az alábbi kód egy teljes, futtatható teszt, ami a demonstrálja a direkt fájlolvasás (implicit OS cache), és a kézi cache-elés közötti teljesítménykülönbséget:

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Web;
using System.Web.Caching;

class Program
{
const string file = @”c:\temp\a.log”;
const string cacheKey = @”alog”;
const int loopLen = 50000;
private static HttpRuntime httpRuntime;
const int HRSharingViolation = -2147024864;

static void Main(string[] args)
{
httpRuntime = new HttpRuntime();

Thread t = new Thread(FileChanger);
t.IsBackground = true;
t.Start();

int cc = GetSumGCCount();
Stopwatch w = Stopwatch.StartNew();
for (int i = 0; i < loopLen; i++) { TestWithoutCaching(); ShowProgress(i); } w.Stop(); Console.WriteLine("Elapsed time: {0}, collections during cached test: {1}", w.Elapsed, GetSumGCCount() - cc); cc = GetSumGCCount(); w = Stopwatch.StartNew(); for (int i = 0; i < loopLen; i++) { TestWithCaching(); ShowProgress(i); } w.Stop(); Console.WriteLine("Elapsed time: {0}, collections during noncached test: {1}", w.Elapsed, GetSumGCCount() - cc); } private static void ShowProgress(int i) { //Console.WriteLine(i); //if (i % (loopLen / 100) == 0) Console.WriteLine("{0}%", i * 100 / loopLen); } static void FileChanger(object state) { while (true) { string s = ProtectedRead(file); ProtectedWrite(file, s); Console.WriteLine("File has been modified sucessfully."); Thread.Sleep(100); } } private static int GetSumGCCount() { GC.Collect(); GC.WaitForPendingFinalizers(); int cc = 0; for (int gen = 0; gen < GC.MaxGeneration; ++gen) { cc += GC.CollectionCount(gen); } return cc; } private static void TestWithoutCaching() { string s = ProtectedRead(file); for (int i = 0; i < s.Length; i++) { char c = s[i]; } } private static void TestWithCaching() { object o = HttpRuntime.Cache[cacheKey]; if (o != null) { string s = (string)o; for (int i = 0; i < s.Length; i++) { char c = s[i]; } } else { CacheDependency d = new CacheDependency(file); string s = ProtectedRead(file); HttpRuntime.Cache.Add(cacheKey, s, d, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.Normal, null); Console.WriteLine("Content (re)cached"); } } static string ProtectedRead(string file) { string s = null; bool retry = false; do { try { s = File.ReadAllText(file); break; } catch (IOException ex) { int hr = Marshal.GetHRForException(ex); if (hr == HRSharingViolation) { retry = true; Console.WriteLine("File is locked while reading, retrying..."); RandomSleep(); } else { throw; } } } while (retry); return s; } static void ProtectedWrite(string file, string content) { bool retry = false; do { try { File.WriteAllText(file, content); break; } catch (IOException ex) { int hr = Marshal.GetHRForException(ex); if (hr == HRSharingViolation) { retry = true; Console.WriteLine("File is locked while writing, retrying..."); RandomSleep(); } else { throw; } } } while (retry); } private static void RandomSleep() { int r = new Random().Next(30, 200); Thread.Sleep(r); } } [/source] Hogy realisztikus legyen a teszt egy külön szál írogatja is a kérdéses fájt, ami az egyszerűség kedvéért most egy lokális, 256ks állomány. Az író-olvasó folyamatok persze időnként összeakadnak, ezért a hibakezelő és ismétlő logika a kódban. Bár elég sok mesterséges késleltetés van a példában, így is eléggé marakodik a két oldal. Nem csak az időket mérem a kódban, hanem a szemétgyűjtések számát is a két esetben. A direkt fájlolvasás esetén minden egyes olvasáskor újabb és újabb buffert allokálunk, ami persze jelentős memóriakényszert okoz. Habár lehetne mondjuk valami thread-local buffert előre allokálni és újrahasznosítani, a legtöbben úgyis ezt a kevésbé hatékony kódot használják, így realisztikus a teszt. A cache-elős példa esetén egyszerűen csak kapunk egy referenciát a memóriában már benn levő fájltartalomra, így nem történik nagyméretű memóriafoglalás sem. Érdemes tudni, hogy a nagyobb memóriatartalmakat a CLR a Large Object Heap-en tárolja, amely kevésbé hatékony mint a sima heap, szóval érdemes ésszel élni vele.

Lássuk a program kimenetét, azaz az eredményeket:


File is locked while reading, retrying…
File has been modified sucessfully.
File has been modified sucessfully.
File is locked while writing, retrying…
File is locked while writing, retrying…
File is locked while writing, retrying…
File is locked while writing, retrying…
File is locked while reading, retrying…
File has been modified sucessfully.
File is locked while reading, retrying…
File has been modified sucessfully.
File is locked while writing, retrying…
File is locked while writing, retrying…
File is locked while writing, retrying…
File is locked while writing, retrying…
File is locked while writing, retrying…
Elapsed time: 00:00:01.2796481, collections during noncached test: 252
Content (re)cached
Elapsed time: 00:00:00.0826961, collections during cached test: 2

Látható, hogy a saját cache nagyon sokat gyorsít, ráadásul a memóriát is sokkal jobban kíméli. Valójában annyira gyors a cache-elős eset, hogy az író szálnak nincs is elég ideje módosítani a fájt, ami amiatt fontos, hogy lássuk, működik-e a cache invalidálás, azaz érzékeli-e a cache, hogy módosult a fájl. Ha az iterációk számát felvesszük a példában 5000-re, akkor már lesznek olyan esetek, amikor a CacheDependency észreveszi a fájváltozást:

File has been modified sucessfully.
Content (re)cached
File has been modified sucessfully.
Content (re)cached
File has been modified sucessfully.
Content (re)cached
File has been modified sucessfully.
File is locked while reading, retrying...
File has been modified sucessfully.
Content (re)cached
Content (re)cached
File has been modified sucessfully.
File is locked while reading, retrying...
File has been modified sucessfully.
Content (re)cached
Content (re)cached
Elapsed time: 00:00:00.7075406, collections during cached test: 5

Remélem tanulságos volt, de ha benéztem valamit, és nem érvényes a teszt, szóljatok, javítom.

2009.02.23.

.NET teljesítményhangolási tapasztalatok 6.

A Connection Poolról még pár gondolat. Jó, hogy van ez a pool, meg gyorsít is, örülünk neki, főleg webalkalmazásokban. Olyan appokban viszont, ahol állandóan futnak a dolgaink, mint egy asztali alkalmazás esetén, nem biztos, hogy érdemes mohón nyitni-zárni a kapcsolatot.
A tőzsdei kereskedési algoritmusaim backtestje során sok százezer paraméterkombinációt néz végig a gép, próbálja meghatározni a legvalószínűbb nyerési esélyűt vagy legnagyobb profit/szórással rendelkezőt (ennél jóval bonyolultabb a dolog, de ez most nem tőzsdés bejegyzés lesz).
A DAL-t úgy írtam meg ahogy webalkalmazásokban megszoktam, így minden egyes trade mentésénél nyitottam-zártam a connectiont. Profilerrel megnézve kiderült, hogy a pool ellenére is a futási idő harmada az open/close-zal ment el. Emiatt készítettem kétféle SqlHelper osztályt, az egyik állandóan nyitott kapcsolattal működik, a másik az egyéb helyekre nyit-zár mind eddig.
Összegezve, a connection pool kiváló dolog pillanatokra futó alkalmazásokhoz, mint a webalkalmazások, de nem érdemes erőltetni, ha több százezerszer kell nyitni-zárni a kapcsolatot.

2009.02.19.

Webkiszolgálás sebességmérése logparserrel

Filed under: IIS,IIS7,Optimalizálás,Szakmai élet — Soczó Zsolt @ 15:05

Elég kevesen használják a logparsert, pedig szenzációs. Azt képzeld el, hogy szöveges adatokon tudsz sql lekérdezéseket futtatni. Pl. IIS logokon. Relációs adatbázis nélkül, és elég gyorsan. Van még kérdés? Nagyon jó, na. :)

Ha pl. szükséged van egy baseline-ra mielőtt nekiállnál optimalizálni a website-ot, jó lenne tudni az átlagos kiszolgálási időket mindenféle tartalomra, pl. aspxekre, akkor a logparserrel pillanatok alatt ki lehet nyerni statisztikákat.

Bővebben itt.

2009.02.12.

.NET teljesítményhangolási tapasztalatok 5.

Szeretjük a connection poolt, de nem mindig.

Tudjuk, hogy manapság nem illik sokáig nyitva tartani az adatbázis kapcsolatot, hanem bemegyünk, kihozzuk a szükséges adatokat, majd lezárjuk a kapcsolatot. Régen (n > 10 év) ez nem volt okos dolog, mert lassú volt a belépés-kilépés. Ezért találták ki a Connection Poolt, amit OLEDB-ben még Session Poolnak hívtak. Egykutya.
Ez arról szól, hogy a kliens oldali adatelérő komponens, pl. az ADO.NET SqlClient providere (System.Data.SqlClient névtér és kölkei) újrahasznosítja a lezárt kapcsolatot. Azaz mi SqlConnection.Close-t mondunk, ő viszont a háttérben nem zárja le a kapcsolatot az adatbázis felé. Ha ezek után újra meg akarjuk nyitni egy kapcsolatot az adatbázis felé, nem hoz létre új kapcsolatot a provider, hanem visszaad egy használtat a poolból. Miért jó ez? Nyilván egy tényleges kapcsolat kiépítése igen költséges, TCP csatorna vagy Named Pipe session felépítése, autentikáció, ecetera. Ezt nagyon sokszor megússzuk, hála a poolnak. A pool 4-8 perc közötti véletlenszerű időközönként azért lezárogatja a használt kapcsolatokat, ott ne zápuljanak.
Nyilvánvaló, hogy felhasználónként egyedi poolok vannak, így egy sysadmin által eldobott kapcsolatot nem adja oda nekem, lópicinek a provider, ez csúnya luk lenne.
Lehet azért így is aljaskodni az új bérlőkkel. Mi van, ha becsatlakozok, kiadok egy use másik adatbázist, majd röhögve lezárom a kapcsolatot? És ha egy-ket set opciót átírok, pl. SET LANGUAGE urdi? Mit lát a következő hívó, aki megkapja a használt kapcsolatot? Hát amit beállítottunk az előző lépésben. Azért ez durván hangzik, ugye? Szerencsére a helyzet az, hogy alapban tiszta környezetet kapunk, köszönhetően annak, hogy a provider pool manager-e végrehajt egy sp_reset_connection hívást mielőtt visszaadná nekünk a használt kapcsolatot. Hogy ez pontosan mit csinál, azt egyszer már leírtam, tessék megnézni a listát.
Azaz bár nem kell minden egyes kéréshez új kapcsolatot megnyitni, azért egy sp hívás csak történik pluszban a háttérben. SQL Profilerben látszik, hogy ez nagyon gyors, de azért ez mégis csak egy hálózati körülfordulás.
És most jön a trükk. Ha teljesen biztosak vagyunk benne, hogy nem tolunk ki a következő hívóval aki megkapja a használt kapcsolatunkat, akkor kikapcsolhatjuk a takarítást. A connection stringbe ezt kell beírni:

Connection Reset=false;

Nézzük meg az alábbi kódot:

SqlConnection conn = new SqlConnection(“data source=.;initial catalog=ATS;Integrated security=true;”);
SqlCommand cmd = new SqlCommand(“sp_who”, conn);
cmd.CommandType = CommandType.StoredProcedure;

for (int i = 0; i < 3; i++) { conn.Open(); cmd.ExecuteNonQuery(); conn.Close(); } [/source] Az SQL Profilerben ez látszik: [source='C'] EventClass TextData SPID Audit Login -- network protocol: LPC RPC:Completed exec sp_who 52 Audit Logout 52 RPC:Completed exec sp_reset_connection 52 Audit Login -- network protocol: LPC RPC:Completed exec sp_who 52 Audit Logout 52 RPC:Completed exec sp_reset_connection 52 Audit Login -- network protocol: LPC RPC:Completed exec sp_who 52 Audit Logout 52 [/source] Hoppá, ez nem az, amit vártunk! Csak 1 logint az elején és 1 logoutot a végén, közben meg az sp_resetconnectiont és az sp_who-nkat. Nem működik a pooling? De. Csibész a profiler. "The Audit Login event class indicates that a user has successfully logged in to Microsoft SQL Server. Events in this class are fired by new connections or by connections that are reused from a connection pool." Ha bekapcsoljuk a profilerben az Event Subclasst, kicsit tisztul a kép: [source='C'] EventClass TextData SPID EventSubClass Audit Login -- network protocol: LPC 1 - Nonpooled RPC:Completed exec sp_who 52 Audit Logout 52 2 - Pooled RPC:Completed exec sp_reset_connection 52 Audit Login -- network protocol: LPC 2 - Pooled RPC:Completed exec sp_who 52 Audit Logout 52 2 - Pooled RPC:Completed exec sp_reset_connection 52 Audit Login -- network protocol: LPC 2 - Pooled RPC:Completed exec sp_who 52 Audit Logout 52 1 - Nonpooled [/source] Szóval van pooling, csak a profiler ravaszkodik. Már kivert a víz. Viszont valami ebben zavar. A pooling ugye a kliensen van. Honnan tudja a szerver egy új parancs beérkeztekor az egy új parancsnak számít a kliens részéről a még nyitott kapcsolaton, vagy csak a poolból visszakapott kapcsolaton hajtanak végre egy új parancsot? Szerintem ő mesterségesen szintetizálja a poolos login-logoutokat, az sp_reset_connection-ök beérkeztekor veszi őket körbe login-logout párossal, a feeling kedvéért. De ez csak tipp. Kapcsoljuk ki a connection resetet: [source='C#'] SqlConnection conn = new SqlConnection( "data source=.;initial catalog=ATS;Integrated security=true;connection reset=false;"); [/source] Mit látunk a profilerben? Semmi nem változott! .NET fw. 2.0 felett nem lehet kikapcsolni az sp_reset_connection-t! Ezért nincs már a legújabb doksiban benne ez a beállítás.

Foly. köv.

2009.02.05.

.NET teljesítményhangolási tapasztalatok 4.

Filed under: .NET,C#,Optimalizálás,Szakmai élet — Soczó Zsolt @ 11:26

Óvakodj az enumtól, mert lassú lészen az – áll a .NET bibliában.

Ártatlan kis jószágnak néz ki ez az enum, mégis sokszor láttam már, hogy miatta lassul be egy rendszer. Mi a lassú rajta? Minden. A Parsolás pl. De ez még érthető is. De hogy a ToString() is tetű, az már kevésbé. Sajnos azonban reflectiont használ ezekhez a műveletekhez, ami köztudottan lassú.
Szerencsére elég könnyű segíteni a baján. Ha Parsolni kell, azaz egy string alapján kell egy enum értéket megszülni, akkor ezt egyszerűen meg lehet oldani egy Dictionary-vel, ami string kulcsokkal és az konkrét enum értékekkel van feltöltve.
Most a másik oldalt mutatom meg, a turbó ToString()-et.
Egyszerű demó enumunk:

public enum TradeDirection
{
Short,
Long
}

Ebből tehát ha van egy példányunk amin ToString()-et hívunk, az lassú lesz. Hozzunk létre egy Dictionary-t, ami segít az enum-string asszociációban.

private static Dictionary tradeDirectionNames = new Dictionary();

Ezt kellene felölteni értékekkel. A C# generikus dolgaival szépen meg lehet ezt általánosan is fogalmazni, így mindenféle enumra működni fog:

tradeDirectionNames.FillEnumCache();

public static void FillEnumCache(this Dictionary enumNames)
{
foreach (T enumValue in Enum.GetValues(typeof(T)))
{
enumNames.Add(enumValue, enumValue.ToString());
}
}

Mivel extension methodként írtam meg olyan, mintha a Dictionary tudná ezt a funkciót, a T típusparamétert meg kitalálja a compiler, így nem kell kiírjam a teljes alakot:

tradeDirectionNames.FillEnumCache();

Hogy jogos-e erre az extension method az objektumorientált szempontból erősen vitatható, de nekem így kézre esett.

A generizálhatóság viszont szenzációs. Ezért imádom a C#-ot.

Miután a fenti megoldással gyorsítottam a rendszeren kiderült (profilerrel, mi mással), hogy egy összetett típusban ami két enumból állt lassú volt a GetHashCode implementációm, ami így nézett ki:

public override int GetHashCode()
{
return dir.GetHashCode() + zone.GetHashCode();
}

A dir és a zone egy-egy enum.

A gyorsításhoz így írtam át:

public override int GetHashCode()
{
return (int)dir << 4 + (int)zone; } [/source] Ez nem csak gyorsabb, de jobban is szór, mint az előző.

2009.02.04.

.NET teljesítményhangolási tapasztalatok 3.

Filed under: .NET,Optimalizálás,Szakmai élet — Soczó Zsolt @ 14:01

Ne emelj (raise) kivételeket (exception), mert az erőforrásigényes (lassú).

A továbbiakban tartózkodok a zárójelektől. :)

Kivétel, benne van a nevében, csak kivételes, nem várt helyzetekben kell használni. Ennek ellenére vannak programozók, akik úgy hiszik nincs kúlabb kommunikációs forma a hívó és a hívott között, mint kivételt hányni az arczába. Cool biztos, és piszok lassú is.

Megint csak egy third-party gyártó vezérlőit használó appban láttam profilerrel, hogy az exceptionök kezelése igen sok időt elvitt a form felépüléséből. Csak az nem volt világos, miért kell ezerszámra exceptionöket dobni a vezérlőnek? Az egyik konstruktora ugyanis dobálta a NullReferenceException-öket, amit aztán elkaptak belül, de ettől még lassú lett a program.
Még profiler se kell az ilyen sunyi kivételdobálókat elcsípni, mert az Output Window tele van az ő exception-jeikkel, 1254 egy form open során.
Javasoltam a szerző cégnek, javítsák ki, mert nem normális, hogy egy library ennyi exceptiont dobjon és nyeljen el. Ez meglepően sok időt elvitt, és nagyon sok ágon megjelent a profilerben a nyoma.

A gyártó átírta a kódját, és láss csodát, kb. 3x gyorsabban töltődött be a form. 10 mp helyett 3mp nagyon nagy különbség ám pszichológiailag.

Konklúzió: ésszel azokkal az exceptionökkel, csak arra használjuk őket, amire tervezték.

Ehhez kapcsolódik még, hogy néha egy rosszul megírt API kényszerít minket felesleges exceptionkezelésre. A .NET Fw. 1.x-ben még csak olyan pl. Int32.Parse(string) volt, ami exceptiont dobott, ha nem volt megfelelő a kapott sztring. Képzelj el mondjuk egy csv olvasó programot, ami milliónyi sort olvas fel, egyes oszlopokat számmá alakítva. Ha mondjuk a sorok 10%-a hibás, százezer exceptiont kell elkapnunk. Meglesz az ára.

Powered by WordPress