Archive for February, 2009

És amikor a bűz már az égig ér…

Saturday, February 28th, 2009

A fenti mondatrész az Ördög ügyvédje c. filmben van, az egyik kedvencemben.

A rosszindulatú, ártalmas hazugság mindig nagyon felháborít, így a következő is.

Kiderült, hogy cigány önkormányzati képviselő az egyik pécsi cigánygyilkos:
http://tenyek.tv2.hu/Belfold/TenyekCikkek/2009-02-27-pecs_granat

Erre azt mondja Mohácsi Viktória: attól, hogy valaki annak vallja magát, még nem cigány, és akkor is a rasszista indítékot kell erőltetni:

http://napkelte.wildom.hu/naptv/jsp/program/wmv.jsp?filename=/naptv_upload/visszanezo/200902/20090228-0630.wmv

Mindenáron, még a végén is azt mondja, hogy itt eltitkolják a dolgokat, és akkor is rasszista a motiváció, ha nem. Elképesztő.

Nyilván befigyelt nála a kognitív disszonancia, annak ellenére, hogy Verebes nem dörgölte az orra alá a korábban az ügyben tett nyilatkozatait, pedig az lett volna a hatásos, ha összevágva visszanézhette volna magát.

Kéne már a gyűlölettörvény az ilyenek ellen, akik csak azért is rasszizmust kiabálnak, és a nemzetközi sajtót is telekürtölik ezzel. Ez a gyűlöletkeltés szerintem, ráadásul nem csak hazai, de nemzetközi szinten is. A magyarság ellen.

Az ilyen hazug megélhetésiek miatt lettem Jobbik-párti, annak ellenére, hogy belül liberálisabb vagyok, mint az SZDSZ bármelyik tagja. Majd egyszer leírom a folyamat evolúcióját, de most inkább készülök a jövő heti konferenciára.

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

Thursday, February 26th, 2009

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);
    }
}

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.

Új cikkem: snapshot elszigetelési szint az SQL Server 2008-ban

Wednesday, February 25th, 2009

Eléggé elfeledett téma ez az SQL Serverben, pedig önmagában ez az új szolgáltatás eladta volna az SQL 2005-öt.
Cikk.

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

Monday, February 23rd, 2009

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.

Kolompár Orbán: “Aki szeret dolgozni, az hazudik”

Saturday, February 21st, 2009

Akkor én egy retkes, büdös, hazug kutya vagyok, ám amint kidühöngtem magam, nekilátok dolgozni, és furcsa mód szeretem azt, amit csinálok. Ez van, perverz vagyok, kisebbség a társadalomban.

Mondják, hogy fejétől bűzlik a hal, ez nyilvánvalóan kiviláglik ebből az interjúból.

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

Thursday, February 19th, 2009

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.

SQL BI konferencia március 3-án

Thursday, February 19th, 2009

(Sose tudom, kell pont ilyenkor a dátumba?)
Előadok a fenti konfon Reporting Services témában. Mivel az SQL Server BI része (OLAP, SSIS, SSRS) sokkal kevésbé ismertek mint a relációs motor, ezért ezen az Informatika Tisztán nem a technet vagy msdn konferenciákon megszokott itt a legújabb cucc, nézd milyen kúl megközelítés lesz, hanem a kezdő lökést szeretnénk megadni az érdeklődőknek.

Érdekes nekem ez a konferencia?

Ha már régóta érzed, hogy jó lenne érteni az Analysis Serviceshez (OLAP izék), vagy nem mertél még belefogni, mert olyan megfoghatatlan katyvasznak tűnik.
Ha tudod, hogy az Integration Services a DTS utódja, ahhoz még értettél (vagy azt se tudod mi volt az), de ezt még nem nézted meg, mert azt mondták az annyira más, hogy meg se merted nézni.
Ha hallottál róla, hogy nem menő már a Crystal Reports, vagy azt se tudod hogyan kellene belefogni az első riportba (amit egyébként szó szerint 5 perc alatt össze tudsz dobni), és érdekel a Reporting Services.
Ha hallottál valami adatbányászatról, de nem tudod, hogy melyik földkéreg tartalmaz adatokat, és érdekel, hogy ez tényleg valami használható technológia, vagy csak ráérős Microsoft Research kutatók gumicicája?

Szóval alapozást ígérünk, földközelbe hozzuk ezeket a kevésbé szem előtt lévő technológiákat. Gyere, várunk.

A magyar tőzsdepiac kintről szemlélve

Thursday, February 19th, 2009

Feliratkoztam egy-két angol nyelvű trading (tőzsde) blogra, és az egyikben látom, hogy egy táblázatban ki van emelve Magyarország és a környező országok. Kiemelték, mert akkora az államadósság, hogy garantáltan lehet fogadni pl. a Ft esésre vagy a hazai blue-chipek esésére. Kérdezi is valaki egy kommentben, hogy van-e kelet-európai ETF (Exchage Traded Fund, olyan több részvényt magába foglaló alap, amivel lehet kereskedni), amin lehetne egy jót shortolni.
Szomorú ez, kívülről végül is csak pár kis diagram vagyunk, ami szépen zuhan lefelé, elég biztosan lehet az esésre fogadni. De, hogy közben mi lesz itthon velünk, akik ennek részesei vagyunk, azt nem tudom.
Okos politikusaink szépen bevezetnek minket a mocsár közepére, ahol elsüllyedhetünk. Mi meg megyünk mint a birkák. :(

64 bites laptopon

Tuesday, February 17th, 2009

Pénteken megjött az új 64 bites laptopom, egy Dell Latitude E6500. 8 G RAM van benne. :)
Sajnos az ára kb. nettó 60e-rel drágább lesz, mint amikor megrendeltem, mert közben a Ft elszállt a fenébe. :(
Először felraktam rá Windows 7-et, ám se az SQL Server, se a VS nem akart rá felmenni, így feladtam a vele való harcot - egyelőre. Tudom, hogy másnak mennek ezek, de nem tudom, mit rontottam el.
Most már egy Windows 2008 van rajta, workstation-ösítve.
Az aero még nem megy rajta, csak a sima Vista theme. Nem tudom miért, de első körben ettől nem lesz kisebb a produktivitásom. :)
A multimédiás dolgokhoz átállítottam a kernel ütemezőjét (ennek működéséről majd írok a Winternals sorozatban), kíváncsi vagyok hd filmek hogyan fognak majd menni rajta. Egyelőre néha még egy winampos zenelejátszásnál is beszaggat.
Azt gondoltam 8 G mindenre elég lesz. Erre tegnap elindítottam egy lekérdezést a tőzsdei adatbázisomon, és elszállt a skype, eltűntek az ikonok, és még a task manager se indult el. Tisztára mint a Win 3.1-es időkben, amikor elfogytak a GDI handle-ök. :)
Kissé vissza kellett venni az SQL Server arczából, most már csak 6 G-t kap, ossza be.

Update: raktam be két fotót, az egyiken a gép van, a másikon a mesteremberek a processzor vízhűtésén fáradoznak. :)

Design Patterns tanfolyam - újra, kibővítve .NET 3.5-tel

Thursday, February 12th, 2009

Marcival újra összeállunk pár akcióra, ennek első megnyilvánulása, hogy újra lesz Design Patterns tanfolyam.
Már írom át az anyagot, a 3.5-ös .NET Fw. tele van szebbnél-szebb design példákkal, illetve az utóbbi 2 év gyakorlati programozása során jó pár dolog tovább formálódott, tisztult a fejemben, ezeket is beépítem az anyagba.

A referenciáim között megtekinthető, hogy volt, amikor cégek szinte összes programozója részt vett a tanfolyamon, felismerve a dolog hasznát a fejlesztési folyamatok minőségére.

És végül, aki bemásolja a következő kódot a jelentkezési lapjára, 20% kedvezményt kap a tanfolyam árából:


public class DP: Course, ISupportDiscount
{
    public HappyStudent GiveMeThisDirtyGoodCourseCheaply() { }
    public string Author { get { return "soci"; } }
    public string Trainer { get { return "soci"; } }
    public DateTime ActionTime { get { return DateTime.Parse("2009.03.09"); } }
}

Szeretettel várok mindenkit.

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

Thursday, February 12th, 2009

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();
}

Az SQL Profilerben ez látszik:


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

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:


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

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:


SqlConnection conn = new SqlConnection(
    "data source=.;initial catalog=ATS;Integrated security=true;connection reset=false;");

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.

Silverlight 2.0 webalkalmazások fejlesztése tanfolyam

Tuesday, February 10th, 2009

Régi jó viszonyunk örömére meghívtak a fenti tanfolyamra a NetAcademiába, márciusban megtekintem. Köszönet érte.
A Silverlight nekem egy olyan technológia, amiről tudom, hogy jó lenne tudni, de vagyok annyira lusta, hogy leüljek egy könyv elé megtanulni.
Kipróbálom milyen egy tanfolyam a másik oldalról. :)

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

Thursday, February 5th, 2009

Ó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<TradeDirection, string> tradeDirectionNames = new Dictionary<TradeDirection, string>();

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<T>(this Dictionary<T, string> 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<TradeDirection>.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;
}

Ez nem csak gyorsabb, de jobban is szór, mint az előző.

Microsoft Application Request Routing for IIS 7

Wednesday, February 4th, 2009

Érdekes kis apróságra akadtam. A fenti cucc egy alkalmazásszintű router, amivel IIS-ekből álló webfarm gépeire lehet szelektíven ráirányítani a terhelést.
Tehát nem azt csinálja, mint az NLBS, hogy IP szinten dönt, hanem általunk megírt logika alapján osztja szét a terhelést a webszerverek között, ami adott esetben igen hasznos lehet.
Még nem látom át hogyan lehet ezzel kiküszöbölni a single point of failure-t, de rajta fogom tartani a szemem.

# HTTP based routing decisions
Unlike hardware load balancers that make the routing decisions at the IP level, Application Request Routing makes the routing decisions at the application level. Working with URL rewrite module, powerful routing rules can be written based on HTTP headers and server variables.
# Load balance algorithms
A user selected load balance algorithm is applied to determine which content server is most appropriate to service the HTTP requests. Six algorithms are provided.
# Health monitoring
Both live traffic and specific URL test are used to determine the health of content servers. A set of configuration parameters are provided to define the meaning of server health.
# Client affinity
Using a cookie, Application Request Routing can affinitize all requests from a client to a content server. It differentiates the clients behind NAT, so each client is treated independently. This feature requires that the clients accept cookies.
# Host name affinity
“Host name affinity” is a specific feature for shared hosters. It changes the deployment topology to minimize and streamline administration and to create additional business opportunities.
# Multiple server groups
Application Request Routing can manage multiple server groups, which are logical groupings of content servers in an environment. This feature allows Application Request Routing to be used in pilot management and A/B testing scenarios.
# Management and monitoring via UI
All configuration settings and aggregated runtime statistics of Application Request Routing are managed and viewable via IIS Manager.
# Failed Request Tracing Rules
Specific traces have been added to quickly troubleshoot and diagnose Application Request Routing.

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

Wednesday, February 4th, 2009

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.

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

Tuesday, February 3rd, 2009

Regexek használata esetén lehetőség van a regex előfordítására, amely során a konkrét regex kifejezésre generálnak egy kiértékelő assemblyt, ami aztán gyorsabban képes a regexet lefuttatni egy adott bemeneti szövegen, mint a nem előfordított megfelelője. Az előfordítás költsége igen magas, amely csakis akkor éri meg, ha utána nagyon sokszor kell a regexet különböző szövegekre lefuttatni. Ha csak egyszer használjuk fel a regexet és csak rövid bemenetekre, akkor sokkal de sokkal többet veszítünk az előfordítással, mint nyerünk.

Az egyik third-party vezérlőben volt az alábbi kód:


Regex regex = new Regex(@"\w+|[^A-Za-z0-9_]", RegexOptions.Compiled |
RegexOptions.IgnoreCase);

A RegexOptions.Compiled ebben a felhasználásban (egysoros textbox) nagyon sok időt elvitt, indokolatlanul.

Az előfordítás további problémás jellemzője, hogy sokféle regex esetén minden egyes regexhez létrejön és betöltődik egy dinamikus assembly a memóriába, amely csak az appdoman unload esetén (Windows appnál ez a legtöbb esetben csak az app leállításakor, kevesen használnak saját appdomaineket) esik ki belőle. Sok regex esetén ez memóriaszivárgásként észlelhető, amely során a private memory fogy (mivel a generált majd jittelt kód nem osztható meg).

Mindezek ellenére senkit nem akarok lebeszélni az előfordításról, mert tetemes gyorsulás érhető el vele, ha kevés fajta regexet kell lefuttatni hosszú bemenetre.