databájt 2012.03.08. 17:10

"What ain't no country I've ever heard of."

Jó napot, jó kódolást!

A minap eszembe jutott, hogy túl sokat kerülgettem már a .NET fókaságait, így a mai topic eköré fog épülni.

Először is, elővenném kedvenc területemet, a Reflection namespace-t, amit oly sokan szidtak már az idők során a lassúsága miatt. Azok, akik hisznek a pici és puha óriás erejében, ajánlok egy oldalt tőlük, ahol megideologizálják kifejtik, hogy mikor érdemes és hogyan Reflection-t használni, hogy az gyors is legyen. Az illúzió konzerválása érdekében akkor itt abba is hagynám a helyükben az olvasást, és folytatnám a Windows 8-at népszerűsítő oldalak valamelyikével, például.

Azoknak, akik nem portugál őslakosnak születtek, akik bedőlnek a csillogó tükör ámításának, álljon itt elrettentő példaként egy kedvencem.

Ősi mantra kezdő reflektáláshoz

Mit kell tennünk, ha valami okból (pl. adatelérési rétest sütünk) elkerülhetetlenül Reflection-nel kell dolgoznunk?
Cachelni, cachelni, CACHELNI. Azért, mert egyrészt minden, egy típusról kideríthető információt nem szeretnénk újra és újra kiszámoltatni, másrészt mert esetleg építenénk köré különféle segítő classokat, stb.

Ha úgy gondoljuk, hogy ilyen mélyen belemászni a lecsóba merő időpazarlás, annak az embernek a meglepettségével fogunk szembenézni amikor ránk szakad mondjuk kétmillió hívás egy ilyen területen, aki éppen most ébredt kómásan egy sötét pincében, szájában gag-ball, kezei lánccal egy oszlophoz kötve, és két bájos, kopasz, vetkőző uriember közelít feléje.

FullName, avagy a legtriviálisabb veszteség

Azt hinné az ember, hogy ha egy fajt elneveznek, azt nagy biztonsággal lehet konstansnak venni. Mert hát ugye az se nagyon változik, hogy ki milyen nemű. Na, ez a .NET szerint nem hogy változhat, hanem rendre a Type.FullName property ki is számolja, valahányszor azt lekérdezzük. Az nem számít, hogy normális kód ugye nem változik menet közben, ergo a namespace-ek se vándorolnak délnek ha melegebb van. Az se számít, hogy az első használatnál - egye fene - legyen csak kiszámolva valami, ami kvázi-konstans, azt' egy mezőből majd előrántjuk, ha legközelebb kell, neeem.

Vessünk egy pillantást az alábbi ábrára.

WhatAintNoCountry_1.png

Igen, 9,6 másodperc színtiszta pazarlás, dobozolva.

"How much time do you want to waste today?"

Ugyanez persze, ha a mi kezünkben a varázsbot, úgy nézne ki, hogy fogunk egy egységsugarú string mezőt, beállítjuk az értékét, azt kalap.

Valahogy így:

WhatAintNoCountry_2.png

Aztán nézzük, mennyi az annyi:

WhatAintNoCountry_3.png

Vagyis 2026 ms nettó, amiből ugye lejön az egyszeri költség, 500 ms, az annyi mint 1526 ms. Kb 6,32-szeres (menedzserek: 632%) gyorsulás. Ennek az aránynak a töredékéért ölnek a tőzsdén.

Nyilván ennek is van hátránya, mert ugye azt az extra mezőt le kell tárolni, de annyi példányt tipikusan nem tartunk memóriában Type osztályból, hogy ezen múljon.

Konklúzió

  • Harmincezer, háromszázezer, vagy cache-elés.
  • .NET Type FullName property-jét tessék óvatosan kezelni (ugyanez igaz a Namespace-re is btw)
  • Soha ne bízzunk meg olyan kódban, ami képes a lényegét alkotó információt újra meg újra megkérdőjelezni

4 komment

databájt 2012.03.07. 15:00

"With the quiet words of the virgin Mary: Come again."

 Jó napot, jó kódolást! 

Rég nem osztottam meg semmilyen tanácsot lelkes társaságunkkal, ezúton szeretném egy aktualitás kapcsán ezt pótolni. 

Közben szaporodtunk rendesen, sok új kolléga kezdett (de Mikó? – bocs, Nyúlónyíl ;)), ezúton csókoltatom őtet mind.

Hogy fel is vegyétek rögtön a ritmust, ez az a vitathatóan hasznos tudnivalók hírlevele, amit egyszer végső elkeseredésemben okító jelleggel és teljesen önhatalmúlag elkezdtem mívelni. 

Egyúttal igyekszem a jövőben némi személyeset csepegtetni az írásokba olyan idézetek/szóösszetételek formájában, amiket számomra kedves mozgóképek jeleneteiben lelhet fel a lelkes szemlélődő. 

No, ennyi bevezető után kezdődjék a napi móka, hadd osztyak meg egy újabb koncepciót veletek. 

„Nna, így nem szabad szoftvert fejleszteni...” 

Bizonyára járt már mindenki így legalább egyszer, de azért a teljesség kedvéért. 

Történt, hogy nyomtam egy rutin build-et, hogy megnézzem, a legutolsó módosítások után lefordul-é az Invenio master.

Mivel megelőlegeztem magamnak a sikert, egyúttal elindítottam a szokásos release bináris frissítést végző command line tool-omat, ami szintén buildez, csak release konfigurációban. 

Miután ezzel megvoltam, kaptam egy figyelmeztetést, hogy vészesen elfogyott a memória (Zoli, hogy állunk DDR ügyben?), így gondoltam mégse várom meg a debug buildet, sőt be is zárom az ablakot, hogy annyival több maradjon a másiknak. 

Igen ám, de a VS fejlesztői csapata a jelek szerint azt a koncepciót hogy „egy lépésben betenni az elefántot a hűtőbe” sose hallotta, és esze ágába se jut magától leállítani a build folyamatot, ha úgyis ki akarunk lépni. Ha mégis megpróbálnátok, így jártok:

 Nna, így nem szabad szoftvert fejleszteni. Meg úgy, ahogy a Generali csinálja, akinek az oldala nem képes magától cappelni a megadott intervallumot felülről, hanem kiírja az áldott, hogy „Hibásan kitöltött mező!”. Rajta, próbáljatok 2012. December körüli (vagy az aktuális dátumnál nagyobb) időpontot megadni „Meddig” értéknek. 

Értelmesen kell működjenek a szoftvereink, ezért az elkészült terméket tessék nyugodtan megkóstolni! Nem ám beleborítunk mindent a kolbásznak való darált húsba, ész nélküli mértékben, aztán meg b@szunk megkóstolni, nem. Megírjuk a kaudot, aztán szépen elkezdjük használni, és megnézzük, mennyire áll kézre, tűri a hibás életszerű inputot, logol értelmesen vagy csak három bötűt kanyarint a hímzett fájl végére. 

És ha véletlenül annyira egyszerű dolgunk van, hogy a fentiekre (meg ugye ízlés szerint egy rakás más kérdésre) „igen” a válasz, elkezdhetünk máris azon gondolkodni, hogy jól neveztük vajon el, milyen egyéb funkció kellhet hozzá, statikus vagy extension metódust szeretnénk inkább, kell-e gyorsítótárazni bármit (pl. dictionary-vel, akit érdekel, elküldöm a korábbi okfejtést erről). 

Tanulság 

Röviden összefoglalva:

  • Nagyon idegesítő a felhasználóknak, ha azzal szembesülnek, hogy a szoftver, vagy egy része átgondolatlanul készült
  • Igyekezzünk használni is az általunk készített kódot/UI-t/komponenst stb., és amit egy kicsit is kényelmetlennek érzünk, azonnal orvosoljuk
  • Írjuk bele kommentezve a kódba, hogy milyen potenciális továbbfejlesztést eszközölnénk a jövőben (később gyakran jól jön, nem kell újra átgondolni az egészet, másrészt más esetleg tényleg meg is csinálja, míg nem nézünk oda)

Szólj hozzá!

databájt 2010.12.01. 13:46

All your properties are belong to us

 Jó napot, jó kódolást! 

Bizonyára járt már minden fejlesztő abban a cipőben, hogy adott problémára valamilyen általános, „Reflection-alapú” megoldásra volt szüksége. Azoknak ez az írás talán felbecsülhetetlen információt jelent a .NET egy újabb turpisságával kapcsolatban.

De ne szaladjunk előre, ejtsünk pár szót arról, mi is az a reflection, és hogy mire használható! 

Definíció, as per MSDN:

„Reflection provides objects that encapsulate assemblies, modules, and types.

You can use reflection to dynamically create an instance of a type, bind the type to an existing object, or get the type from an existing object.

You can then invoke the type's methods or access its fields and properties.” 

Vagyis különböző, egy adott típusra jellemző metódusokat, property-ket használhatunk úgy, hogy nem egy konkrét típusra írunk kódot.

A property-k leírója a reflection-ben PropertyInfo, aminek segítségével beállíthatunk (SetValue) vagy lekérdezhetünk (GetValue) egy értéket, valahogy így: 


User user = new User();
PropertyInfo property = typeof (User).GetProperty("Name");
string name = (string) property.GetValue(user, null); // name = user.Name
property.SetValue(user, "Admin", null); // user.Name = "Admin"
 


 Ez nyilván egy olyan helyen, mint pl. az Invenio adatelérési rétege, lehetővé teszi számunkra, hogy egy adott feladatra általános megoldást adjunk. Például, amikor az adatbázisból olvasunk ki adatokat, az adatbázisrekordok objektumokká alakításánál rendre kiolvasunk egy értéket a rekordból, majd azt egy .NET objektumon, mint property értéket beállítunk. Az Invenio minden típusról nyilvántart egy TypeContainer példányt, ami tartalmazza a típusra jellemző tulajdonságokat, attribútumokat, property-ket, utóbbiakat MemberContainer példányokban. 

Ezek segítségével így olvasunk ki a rekordokból: 


internal class EntityReader : IEntityReader
{
       private readonly TypeContainer _container = null;
       private List<RecordBinder> _binders = null;
                ... 

public object ReadItem(IDataRecord dataRecord, CultureInfo currentCulture)
{
       using (NDC.Push("ReadItem"))
       {
             object instance = _container.Instantiate(); // pl. adott esetben User u = new User()
             int binderCount = _binders.Count; // vö. bindelhető property-k száma
             for (int i = 0; i < binderCount; i++)
             {
                    RecordBinder binder = _binders[i]; // Egy adott property-hez tartozó binder
                    if (!binder.IsBoundToNull(dataRecord))
                    {
                           binder.Bind(dataRecord, instance);
                           if (binder is MultilingualStringRecordBinder)
                           {
                                 ((MultilingualString)binder.Member.GetValue(instance)).DefaultCulture = currentCulture;
                           }
                    }
             }
             return instance;
       }
}
...

}


 Ez az egyetlen kód felel az összes típus kezeléséért, legyen az Content, Segment, User, stb. A konkrét property olvasást a RecordBinder absztrakt class különböző implementációi végzik, a leggyakrabban használt a SimpleRecordBinder ami pl. DateTime, string, stb. típusokat kezel. Íme egy részlet a SimpleRecordBinder kódjából: 


/// <summary>
/// Data record binder class.
/// </summary>
private class SimpleRecordBinder : RecordBinder
{
       ...
       /// <inheritdoc/>
       public override bool IsBoundToNull(IDataRecord dataRecord)
       {
             return dataRecord.IsDBNull(_ordinal);
       }
       /// <inheritdoc/>
       public override void Bind(IDataRecord dataRecord, object instance)
       {
             Member.SetValue(instance, dataRecord.GetValue(_ordinal));
       }
       ...
}
 


 

A metódusban a Member property az absztrakt ősben található MemberContainer példány, aminek a SetValue metódusa leegyszerűsítve valahogy így néz ki:


 

...
public void SetValue(object instance, object value)
{
       if (null == value)
             return; 

       object preparedValue = value.GetType() == _actualType ? value : PrepareValue(_actualType, value);
       ((PropertyInfo)_innerInfo).SetValue(instance, preparedValue, null);
}
...


 

Ezzel el is érkeztünk a lényegi részhez. Történt ugyanis, hogy napi performancia nézegetésem közben rápillantottam a fenti sor  futási idejére, és megdöbbentem:

 



 

Mit látnak szemeink? Egy egyszerű értékadás (429-es sor), ami nagyságrendileg 800-1000 tick környékén szokott tartani, itt durván 38.000. HarmincnyolcEZER!

Na, itt kinyílt a zsebemben a refaktor gomb aranyveretes doboza, és utánaszaglásztam kicsit. 

Háttal nem kezdünk mondatot, de mivel a mondat előttem van... hát a reflector szerint ez történik a PropertyInfo.SetValue metódus bugyraiban:



 

A sorok közül az időrablásért felelős  a setMethod.Invoke kezdetű.

-          Egy nagyon egyszerű értékadásról beszélünk, miért várunk rá ennyit?

-          Mert írva vagyon, azért! 

És mi lenne, ha átírnánk a játékszabályokat, és megoldanánk okosba’, hogy a PropertyInfo.SetValue hókuszpókusza helyett közvetlenül a kívánt műveletet, és csak azt hajsuk végre?

Éppen erre használható a System.Reflection.Emit namespace-ben található DynamicMethod. 

Nosza legózzunk egyet, lássuk, hogyan lehet futási időben építeni egy olyan setter metódust, ami paraméterként egy objektumpéldányt és a beállítandó értéket várja, majd elvégzi a property = value műveletet!


...
Type[] arguments = new Type[2];
arguments[0] = arguments[1] = typeof(object);
System.Reflection.Emit.DynamicMethod setter =
       new System.Reflection.Emit.DynamicMethod("_Set" + propertyInfo.Name + "_", typeof(void), arguments, propertyInfo.DeclaringType);
ILGenerator generator = setter.GetILGenerator();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
generator.Emit(OpCodes.Ldarg_1); 

if (propertyInfo.PropertyType.IsClass)
       generator.Emit(OpCodes.Castclass, propertyInfo.PropertyType);
else
       generator.Emit(OpCodes.Unbox_Any, propertyInfo.PropertyType);
generator.EmitCall(OpCodes.Callvirt, setMethod, null);
generator.Emit(OpCodes.Ret); 

Action<object, object> setterMethodDelegate = (Action<object, object>)setter.CreateDelegate(typeof(Action<object, object>));
...


 

A fenti módszert használva úgy módosítottam a MemberContainer.SetValue metódust, hogy a röptében készített setter metódust használja a hagyományos, reflection-ben található PropertyInfo.SetValue helyett.

Alább látható a megismételt mérés eredménye.

 



 

Bizony, a 440-es sort összehasonlítva a korábbival 38.250 helyett immár csak 1.325 tick volt a property érték beállítása. A mérések során az utóbbi érték valahol 1000 és 1400 környékén mozgott, ez nyilván a gép egyéb háttérben futó folyamataitól is függ.

Normál esetben azért azt mondhatjuk, hogy durván 20-30-szoros a különbség a DynamicMethod javára. Ugyanez a tendencia egyébként a GetValue esetében is megfigyelhető, ezt is érdemes DynamicMethod-al kiváltani. 

Tanulság 

Röviden összefoglalva:

·         A reflection jó lehetőségeket kínál a típusfüggő műveletek általános megoldására (1 egyszerű, általános eljárás a soktíz-sokszáz gondosan felépített típusfüggő megoldás helyett)

·         A PropertyInfo SetValue() és GetValue() metódusai (vélhetően a FieldInfo ezen metódusai is) látványosan lomhák, különösen érdemes helyettük dinamikusan getter/setter metódusokat építeni ha számít a sebesség

·         A menzás ebéd még mindig drága, de legalább jó a körömpörköltjük

Szólj hozzá!

databájt 2010.11.10. 13:25

To log, or not to log

 Jó napot, jó kódolást!

„Bizony mondom néktek, boldogok a debugtalanok, mert övék a performancia országa...”

Történt a minap, hogy megkérdeztem Főnöktörpöt, mi a szerinte elfogadható idő egy Logon hívásra. Valahogy így: 

-Szerinted mennyi idő az elfogadható egy SessionService.Logon esetén?
-Tíz. Köbö tíz miliszek.
-Hmm... Most olyan 30-40 körül van. Na, majd megnézem. 

Azzal megnéztem. Release mód, log level ERROR, hoster.exe a profiler-be, adjunk neki.

Logon. Nem jó. Egy adat nem fog pontos eredményt adni. Logoff. 

Némi molyolás után összedobtam egy szokásos T01 commandot a DAMCON-ban, valahogy így: 


public static class TestMethods
{
       [CommandMethod("Test", RequiresAuthentication = false, SysCommand = true)]
       public static void T01()
       {
             for (int i = 0; i < 100; i++)
             {
                    Session session = Session.Logon("admin", "admin");
                    session.Close();
             }
       }
}


Egyszerű, mint a kalapács, dehát ezt szeretnénk mérni. Nosza. Adatbázis újrahúz, profiler újraindít, perceg a diszk (na jó, másodperceg), jön a feketeleves.

Bizony, a logon ideje 500 hívásra átlagolva ~12-15 ms között alakul. Meg is néztem, miért. Nos, kód kódot követett, és eljutottam egy általam írt SqlCommandHelper metódusig: 


             /// <summary>
             /// Logs the parameters of an SqlCommand to the provided logger.
             /// </summary>
             /// <param name="sqlCmd">The SqlCommand to log.</param>
             /// <param name="logger">Logger to log to.</param>
             private static void LogCommandParameters(SqlCommand sqlCmd, ILog logger)
             {
                    //#if DEBUG
                    if (null != logger && logger.IsDebugEnabled)
                    {
                           string pref =
                                  "==================================================================================================";

                           string query = GetQueryText(sqlCmd);
                           logger.Debug("\n" + pref + "\nExecuting SqlCommand:\n" + pref + "\n" + query + "\n" + pref);
                    }
                    //#endif
             }


 

Akár hiszitek, akár nem, ERROR szintű logolás mellett a fenti kód rendre meghívódott. Néhány ezer alkalommal. Aucs. 

Nézzük figyelmesen, mit vizsgálunk:

-          Van-e logger insztanszunk? – Check

-          Debug mód van bekapcsolva? – Nope! 

Hoppá! Vagyis a mérések szerint kb. 50%-át az teszi ki egy-egy SQL query futtatásának, hogy elkészítjük, majd ignoráljuk a lognak szánt információkat.

Mert ugye a fentiek ellenére a logger.Debug("\n" + pref + "\nExecuting SqlCommand:\n" + pref + "\n" + query + "\n" + pref) utasítás nem írt a logba egy kanyi bájt se

Az idő jó részét tehát kidobtuk, hiszen a szerver hívások entry-point-ján hasonló mechanizmus ellenőriz/logol szükséges esetben.

Hogy is van ez? Debug enabled, de mégsem? 

Erre már megremegett a kezem a refaktor gomb fölött, aztán mást gondolva összeraktam a LoggerHelper-ben egy metódust, ami egzaktul arra vizsgál, hogy a config szerint DEBUG level-t állítottunk-e, vagy sem.

Át is írtam erre a kódot, amit demonstráció céljából ERROR loglevel mellett debugoltam azon melegében. 

Valahogy így nézett a képernyő megrökönyödött arcomra: 



 

Igen, a logger szerint mi most itten DebugEnabled környezet vagyunk, pedig nem. Nos, valami erősen sanda gyanú fogalmazódott meg bennem. Tudniillik a log appender példányokat a LoggerHelper explicite beállítja a config fájlban található LogThreshold szerint, ami esetünkben ERROR. Mi történik akkor itt? Nem számít, hogy milyen szintet állítunk be az appendereknek? 

Clickety-click, már jön is fel a debug watch window, nézem a logger-t, a repository-ját, és mit látok? Threshold = ALL. Hmm...

Innen persze már könnyű volt megtalálni a probléma forrását, konkrétan az ILoggerRepository példány létrehozásakor explicite nem mondtunk Thresholdot, ezért ő úgy gondolta, hogy minden érdekes nekünk.

Viszont az appenderek tették a dolgukat, és a Repository-ból áramló üzeneteket szépen ignorálták. Nade ugye azokat a log üzeneteket előállítani sokszor nem kevés idő!

Pótoltam hát a Repository beállítás hiányosságát, alapos alázattal átnéztem még a Logon implementációt, és némi kulturált default használat beiktatásával, illetve statikus elemek megtartásával végül az alábbi mérési eredmény született:  

 

A képről jól látható, hogy a LogonInternal hívás (a tényleges munkavégzés) effektíve ~3.5ms környékére csökkent. No, ezért megérte belevágni. 

Tanulság 

Röviden összefoglalva:

·         A log4net futási időben történő konfigurálásakor nem csak az appenderek Threshold-ját, de az őket használó repository Threshold-ját is be kell állítani.

·         A kvázi-statikus, nagyon ritkán változó dolgokat célszerű nem eldobálgatni (pl. Server-ek nyilvántartása az Invenio-ban).

·         Rengeteget számít, hogyan, mit és mennyit logolunk.

Szólj hozzá!

databájt 2010.11.08. 13:51

How to beat the compiler

 Jó napot, jó kódolást! 

Azon tűnődtem, mi történik, ha jószándék ide, cache-elés oda, extrém terhelés van egy metóduson?

Mondjuk több tízezres nagyságrendű hívásszám? Befolyásolja-e mérhetően a lookup eredményének felhasználási módja a futás idejét?

A válasz az alább látható fejtegetés végén található. 

Lássuk a kiindulási állapotot. 

Adott egy metódus: 


public string GetCached(int key)

{
        // Hit Count | Avg Time (ticks) | Time (ticks)
        string value = null; // 91 615 | 508 | 46 547 159

       if (!SomeProperty.TryGetValue(key, out value)) // 91 615 | 13 057 | 1 196 238 202
               value = SomeProperty.DefaultValue; // 652 | 6 931 | 4 519 494

       return value; // 91 615 | 508 | 46 547 159
}


 

Láthatjuk, hogy az első sorban adott null értéket rögtön felülírjuk a TryGetValue() metódus eredményével, ezért az az utasítás elhagyható volna.

Érdekes, hogy a C# fordító nem veszi ezt észre, ez kapásból 46 millió (!!!) tickbe kerül nekünk. 

A TryGetValue() sor érdekessége továbbá, hogy a negálás művelet minden kiértékelés után le fog futni, márpedig ez csak ritkán (lásd később) szükséges.

Írjuk át úgy a kódunkat, hogy az első sorban az értékadást elhagyjuk, és a negálást elhagyjuk. 

Így alakul a kód: 


public string GetCached(int key)

{
        // Hit Count | Avg Time (ticks) | Time (ticks)

       string value; // 91 615 | 0 | 0
        if (SomeProperty.TryGetValue(key, out value)) // 91 615 | 11 597 | 1 062 487 321

             return value; // 90 963 | 441 | 40 138 767
        return SomeProperty.DefaultValue; // 652 | 7 049 | 4 596 520
}


 

Látható, hogy az értékadás elhagyását illetve a default érték kezelését megváltoztatva újabb súlyos tick milliókat nyertünk.

Az, hogy kb. 91000-szer negáltunk egy boolean-t , amit később nem használtunk, itt durván 133 750 000 ticket jelent. Az sok. Nagyon sok. 

Az utolsó két sort összevetve az előző változat kódjával minimális eltérést (kb. 600 000 tick) láthatunk, ez érdekes, de akár mérési hiba is lehet.

Százalékban kifejezve az első és második megoldás lényegi különbségét:

 

Before

 

After

Line

Avg time

Total time

 

Line

Avg time

Total time

1

508

46 547 159

 

1

0

0

2

13 057

1 196 238 202

 

2

11 597

1 062 487 321

3

6 931

4 519 494

 

3

441

40 138 767

4

508

46 547 159

 

4

7 049

4 596 520

Total

 

1 293 852 014

 

Total

 

1 107 222 608

             
 

Before:

1 293 852 014

       
 

After:

1 107 222 608

       
 

Diff:

16,856%

       

 

 

Hát igen. Durván 17%-al gyorsabb lett a kódunk.

 

Gyorsabb. Lehetne ennél is gyorsabb? – Adódik a kérdés. 

Debizony hogy... Ugyanis még 2 metódus hívását megspórolhatjuk. Hogy nincs is hívva csak a TryGetValue()? Dehogynincs.

Ne  feledjük, hogy minden property gettelés a háttérben metódushívást jelent, esetünkben get_SomeProperty() nevűt. 

Lássuk, mi történik, ha galád módon nem használjuk az oly sokat propagált property mechanizmust! 


public string GetCached(int key)
{
       // Hit Count | Avg Time (ticks) | Time (ticks)
       string value; // 91 615 | 0 | 0
       if (_somePropertyField.TryGetValue(key, out value))  / / 91 615 | 10 621 | 973 096 244

       return value; // 90 963 | 290 | 26 381 642
       return _somePropertyField.DefaultValue; // 652 | 5 888 | 3 839 036
}


 Hoppá. Nem is olyan elhanyagolható az a két metódushívás. Hogy mennyire nem? Hasonlítsuk össze a legutolsó eredményt az elsővel: 

Before

 

After

Line

Avg time

Total time

 

Line

Avg time

Total time

1

508

46 547 159

 

1

0

0

2

13 057

1 196 238 202

 

2

10 621

973 096 244

3

6 931

4 519 494

 

3

290

26 381 642

4

508

46 547 159

 

4

5 888

3 839 036

Total

 

1 293 852 014

 

Total

 

1 003 316 922

             
 

Before:

1 293 852 014

       
 

After:

1 003 316 922

       
 

Diff:

28,957%

       

 

Bizony, bizony. Durván 29% az eltérés. Az eltérés majdnem felét az adja, hogy 2 property get helyett 2 field getet használunk.

Kriminális. Hogy ezt miért nem csinálja így automatikusan C# compiler? Na, az jó kérdés.

 

Tanulság 

Röviden összefoglalva:

·         A C# compiler nem szűri ki a legnyilvánvalóbb pazarlást sem (value = null a TryGetValue() előtt)

·         A property get-nek mérhető hátránya van a sima field get-tel szemben (~20%)

·         Kellően nagy számban a negálás művelete sem elhanyagolható (~90.000 hívásnál ~46 millió tick)

Szólj hozzá!

databájt 2010.11.05. 16:47

How time flies

 Jó napot, jó kódolást! 

Rég nem volt idő arra, hogy kézbe vevén sorsunk alakulását a hatékony kódolásról írjak.

S hogy az idő hogyan repül, mi sem mutatja jobban, hogy míg szeretett Inveniónk keresőmotorjának alapjai felépülnek egy-egy telepítés után, arcunkat megcserzi LCD kijelzőnk gyengécske fénye. 

Nekiláttam hát megnézni, hogyan repülhet el több perc, míg a „search schema generálódik”. 

Az első profile mérést követően ilyesmi fogadott: 

 

A képről jól látható, hogy a legforróbb (jujjdejó! J) kód a MetadataTools.GetAclIdFieldsForCacheTable() metódus hívása, rögtön az első két leglassabb kód dobogós helyezését megkapta.

További nézelődés után kiderült, hogy az említett metódus belseje 9000-nél is többször hívódik, ami tekintve az adatbázis táblák számát azt jelenti, hogy 1-1 táblát legalább 10-szer újra processzálunk. 

Hogyan is szól az ismerős mantra? Cachelni, cachelni, khm... CACHELNI. Lássuk, hol lehet gyorsítani az eljárásunkon! 

Az eredeti kód ez volt:


IEnumerable<TypeContainer> containers = DALProvider.Metadata.TypeContainers.Where(

       t => t.IsMapped && !t.IsInfoContainer && !t.IsInfoBaseContainer && t.TableMapping.SchemaName == baseTableSchema && t.TableMapping.TableName == baseTableName); 

if (containers.Count() > 0)
{
       container = containers.First();
       if (container.SecurityDomains != null)
       {
             foreach (var  item in container.SecurityDomains.Values)
             {
                    if (item is AuthorizationSecuredObjectTypeAttribute)
                    {
                           var securedObjectType = (AuthorizationSecuredObjectTypeAttribute)item;
                            var aclField = new Field(securedObjectType.AclColumnName, Service.GetAclcolumnType, true);
                           aclIdFields.Add(aclField);
                    }
             }
       }
}


 

 

Nosza, ásó-kapa-refaktor gomb, a csúnya lookup részt imígyen faragtam élesebbre egy meglévő, cache-elt lookup-oláshoz készült metódus segítségével:

 


var aclIdFields2 = new List<Field>();
TypeContainer container2 = null;

if (DALProvider.Metadata.TryGetMappedContainer(baseTableSchema, baseTableName, out container2))
{
       if (container2.SecurityDomains != null)
       {
             foreach (var item in container2.SecurityDomains.Values)
             {
                    if (item is AuthorizationSecuredObjectTypeAttribute)
                    {
                            var securedObjectType = (AuthorizationSecuredObjectTypeAttribute)item;
                            var aclField = new Field(securedObjectType.AclColumnName, Service.GetAclcolumnType, true);
                           aclIdFields2.Add(aclField);
                    }
            }
     }
} 


 

Hogy validáljam az egyszerűsítést, mindkét kódblokkot meghagytam egy mérés erejéig, diff-et készítettem a két megoldásról az ellenőrzés végett, és Stopwatch-al kimértem az egyes futási időket.

Ilyen lett a végső metódus: 


internal static List<Field> GetAclIdFieldsForCacheTable(string baseTableSchema, string baseTableName)
{
       var aclIdFields = new List<Field>(); 
       InitializeDalProvider(); 
       TypeContainer container = null;
       //DALProvider.Metadata.TryGetMappedContainer(baseTableSchema, baseTableName, out container); 
       Stopwatch sw1 = Stopwatch.StartNew();
       // DEVNOTE: Do NOT do reverse-lookups like below, there are cached lookup-methods available!
        var containers = DALProvider.Metadata.TypeContainers.Where(t => t.IsMapped && !t.IsInfoContainer && !t.IsInfoBaseContainer && t.TableMapping.SchemaName == baseTableSchema && t.TableMapping.TableName == baseTableName);
       if (containers.Count() > 0)
       {
             container = containers.First();
             if (container.SecurityDomains != null)
             {
                    foreach (var item in container.SecurityDomains.Values)
                    {
                           if (item is AuthorizationSecuredObjectTypeAttribute)
                           {
                                   var securedObjectType = (AuthorizationSecuredObjectTypeAttribute)item;
                                   var aclField = new Field(securedObjectType.AclColumnName, Service.GetAclcolumnType, true);
                                  aclIdFields.Add(aclField);
                           }
                    }
             }
       }
       sw1.Stop();

        var sw2 = Stopwatch.StartNew();
        var aclIdFields2 = new List<Field>();
        var container2 = null;
       if (DALProvider.Metadata.TryGetMappedContainer(baseTableSchema, baseTableName, out container2))
       {
             if (container2.SecurityDomains != null)
             {
                    foreach (var item in container2.SecurityDomains.Values)
                    {
                           if (item is AuthorizationSecuredObjectTypeAttribute)
                           {
                                   var securedObjectType = (AuthorizationSecuredObjectTypeAttribute)item;
                                   var aclField = new Field(securedObjectType.AclColumnName, Service.GetAclcolumnType, true);
                                  aclIdFields2.Add(aclField);
                           }
                    }
             }
       }
       sw2.Stop(); 

       _log.Info("Original lookup took " + sw1.Elapsed.TotalMilliseconds + " ms (" + sw1.Elapsed.Ticks + " ticks), optimized lookup took " + sw2.Elapsed.TotalMilliseconds + " ms (" + sw2.Elapsed.Ticks + " ticks), difference was " + ((100 * ((double)sw1.Elapsed.Ticks / (double)sw2.Elapsed.Ticks)) - 100).ToString() + "%");

       Differencer.AddKeyAccessor(typeof(Field), new Getter(f => ((Field)f).Name), false);
       Diff diff = Differencer.Compare(aclIdFields, aclIdFields2);
       if (diff.Type != DiffType.Unchanged)
       {
             _log.Error("Second lookup of [" + baseTableSchema + "].[" + baseTableName + "] resulted in different AclID field list!");
       }
       return aclIdFields;
} 


 

Ha a százalékszámítást valaki szerint rosszul csináltam, szóljatok rám, azért álljon itt példának okáért néhány bejegyzés a logból: 


2010-11-05 16:21:24,771 [6] INFO  MetadataManager [SearchConfigurationService.CreateSearchStructures CreateMetadataObjects CreateCacheTables] - Original lookup took 4,2451 ms (42451 ticks), optimized lookup took 0,7844 ms (7844 ticks), difference was 441,190719020908%

2010-11-05 16:21:24,774 [6] INFO  MetadataManager [SearchConfigurationService.CreateSearchStructures CreateMetadataObjects CreateCacheTables] - Original lookup took 0,4821 ms (4821 ticks), optimized lookup took 0,0101 ms (101 ticks), difference was 4673,26732673267%

2010-11-05 16:21:24,775 [6] INFO  MetadataManager [SearchConfigurationService.CreateSearchStructures CreateMetadataObjects CreateCacheTables] - Original lookup took 0,5033 ms (5033 ticks), optimized lookup took 0,0074 ms (74 ticks), difference was 6701,35135135135%

2010-11-05 16:21:24,776 [6] INFO  MetadataManager [SearchConfigurationService.CreateSearchStructures CreateMetadataObjects CreateCacheTables] - Original lookup took 0,5063 ms (5063 ticks), optimized lookup took 0,0039 ms (39 ticks), difference was 12882,0512820513%

2010-11-05 16:21:24,777 [6] INFO  MetadataManager [SearchConfigurationService.CreateSearchStructures CreateMetadataObjects CreateCacheTables] - Original lookup took 0,509 ms (5090 ticks), optimized lookup took 0,0039 ms (39 ticks), difference was 12951,2820512821%

2010-11-05 16:21:24,778 [6] INFO  MetadataManager [SearchConfigurationService.CreateSearchStructures CreateMetadataObjects CreateCacheTables] - Original lookup took 0,5591 ms (5591 ticks), optimized lookup took 0,0039 ms (39 ticks), difference was 14235,8974358974%

2010-11-05 16:21:24,779 [6] INFO  MetadataManager [SearchConfigurationService.CreateSearchStructures CreateMetadataObjects CreateCacheTables] - Original lookup took 0,4016 ms (4016 ticks), optimized lookup took 0,0039 ms (39 ticks), difference was 10197,4358974359%

2010-11-05 16:21:24,780 [6] INFO  MetadataManager [SearchConfigurationService.CreateSearchStructures CreateMetadataObjects CreateCacheTables] - Original lookup took 0,5803 ms (5803 ticks), optimized lookup took 0,0039 ms (39 ticks), difference was 14779,4871794872%

2010-11-05 16:21:24,781 [6] INFO  MetadataManager [SearchConfigurationService.CreateSearchStructures CreateMetadataObjects CreateCacheTables] - Original lookup took 0,5314 ms (5314 ticks), optimized lookup took 0,0039 ms (39 ticks), difference was 13525,641025641%

2010-11-05 16:21:24,783 [6] INFO  MetadataManager [SearchConfigurationService.CreateSearchStructures CreateMetadataObjects CreateCacheTables] - Original lookup took 0,5904 ms (5904 ticks), optimized lookup took 0,0096 ms (96 ticks), difference was 6050%

2010-11-05 16:21:24,783 [6] INFO  MetadataManager [SearchConfigurationService.CreateSearchStructures CreateMetadataObjects CreateCacheTables] - Original lookup took 0,5349 ms (5349 ticks), optimized lookup took 0,0035 ms (35 ticks), difference was 15182,8571428571%

2010-11-05 16:21:24,795 [6] INFO  MetadataManager [SearchConfigurationService.CreateSearchStructures CreateMetadataObjects CreateCacheTables] - Original lookup took 0,4725 ms (4725 ticks), optimized lookup took 0,0052 ms (52 ticks), difference was 8986,53846153846%

 


 

Nos igen. 

Ha egy-egy lookup ~4400 helyett 40 tick, az annyi mint... 100-szoros gyorsulás, köbö 1000%. Plusz-mínusz néhány száz %, ahogy elnézem. 

Tanulság 

Röviden a lényeg:

·         A cache-elt lookup-olás, főleg gyakran használt ám sosem/ritkán változó  elemeknél a 21. században nem opció – kötelező!

·         A LINQ query támogatás jó, jó... de nem mindenre.

·         Egymás kódjába belesz@rni nem szép dolog... kivéve, ha 100%-nál nagyobb gyorsulást eredményez változatlan működés mellett.

Szólj hozzá!

databájt 2010.07.21. 09:49

Ajándék nyakkendőtűvel

 Jó napot, jó kódolást!

 

Unalmasak az estéid?
Szigorúan monoton nő a napi munkaidőd?
Az idegeidre mennek az ügyfelek?
A munkatársak is?
Harmincezer?
Nincs WiFi a dohányzóban?
Előtted itták ki az utolsó kávét a gépből?
Utálod az Applet/Microsoftot (a megfelelő rész törlendő)?
Best office?
Beszélsz olyan nyelven?
Szereted a nőket/férfiakat (ízlés szerint és/vagy)?
Lassú a search?
Megint változott az XYZ API?
Nem jutott az import Guidokból?
Azt hiszed levegőt lélegzel?
Saját logging repository-t szeretnél? 

Itt az ágytál, amire vágytál! Ma este elmondhatod, levezetheted, diagrammal szemléltetheted!

Az esti órákban barátságos vitaestet tartunk egy még eldöntetlen tetthelyen.

Akinek van kedve csalatkozni csatlakozni, jelezze szándékát/szomját/óhaját/sávváltási szándékát (a megfelő rész kiszínezendő) a nap folyamán a +36/30-51-22222 számon, vagy személyesen szerver ügyfélszolgálatunkon. 

A várható érdeklődés hiányára tekintettel szeretném jelezni, hogy italt csak korlátlan mennyiségben szabad fogyasztani az est folyamán, a dohányzóknak külön kijelölt helyet biztosítunk a detoxikálásra várakozók sorában.

Legyél bánatos/vidám, szőke/barna, partiba vágunk, és meg se állunk... a kora esti órákig, hiszen holnap leadási határidő. 

Az első 3 jelentkezőnek korlátlan türelmet és ajándék szervdonor jelentkezési lapot adunk!

Szólj hozzá!

databájt 2010.07.20. 10:57

Sanitizing input

 Jó napot, jó kódolást! 

Mai versenyzőnk a „hogyan írjunk instabil kódot” kategória dobogósa. Hangolódjunk rá a témára ezzel a frappáns képsorral: 

  

Ezzel el is érkeztünk a témához – az inputok ellenőrzése. Lám az alábbi kódrészlet, milyen ártalmatlan: 


...
_attributeInfoList = new List<AttrInfo>(); 
string attrs = AppConfigHelper.Get("AvidAttributes");
string[] al = attrs.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string s in al)
{
    string[] ae = s.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
    _attributeInfoList.Add(new AttrInfo() { attrScope = ae[0].Trim(), attrName = ae[1].Trim() });
}

_attributeList = new StringList();
foreach (AttrInfo ai in _attributeInfoList)
    _attributeList.Add(ai.attrName);
...


 

Aki megtalálta benne a patás részletet, szokás szerint NEM kap pirospontot – ez a mi szintünkön elvárható. 

A probléma akkor kezdődik, amikor történetesen eme kódrészlet egy olyan alkalmazásban hívódik meg, amiben nincsen definiálva az AvidAttributes nevű app setting.

Első körben a kód 3. sorában kapunk egy végzetes kivételt (lánykori neve ArgumentNullException). 

Ha valami csoda folytán lenne ilyen setting, de nincs értéke, máris jobb lenne a helyzet. 

Ámde itt nem áll meg a gépezet, hiszen ugye arról vagyunk híresek, hogy emberből vagyunk, és néha elgépelünk dolgokat.

Mondjuk nem kettőspontot használunk a fránya inputban, hanem vesszőt. Ebben az esetben a 6. sorban, ahol feltételezzük, hogy volt kettőspont a szövegben és ezért 2 elemű a tömbünk (hivatkozás: ae[1].Trim()

Nos, célszerű egy ellenőrzést beiktatni, valahogy így: 


_attributeInfoList = new List<AttrInfo>();
string attrs = AppConfigHelper.Get("AvidAttributes");
if (null != attrs) // Ez a sor a lényeg
{
      string[] al = attrs.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
      foreach (string s in al)
      {
            string[] ae = s.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
            _attributeInfoList.Add(new AttrInfo() { attrScope = ae[0].Trim(), attrName = ae[1].Trim() });
      } 

      _attributeList = new StringList();
      foreach (AttrInfo ai in _attributeInfoList)
            _attributeList.Add(ai.attrName);
}


 

Azután, ha tényleg tutira akarunk menni, csekkoljuk le, hogy valóban van 2 elemünk a második split után. A legvégén, valami ilyesmit kapunk: 


_attributeInfoList = new List<AttrInfo>();
string attrs = AppConfigHelper.Get("AvidAttributes");
if (null != attrs)
{
      string[] al = attrs.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
      foreach (string s in al)
      {
            string[] ae = s.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
            if(ae.Length > 1)
            {
                  _attributeInfoList.Add(new AttrInfo() { attrScope = ae[0].Trim(), attrName = ae[1].Trim() });
            }else
            {
                  _attributeInfoList.Add(new AttrInfo() { attrScope = ae[0].Trim(), attrName = string.Empty });
            }
      }
      _attributeList = new StringList();
      foreach (AttrInfo ai in _attributeInfoList)
            _attributeList.Add(ai.attrName);
}


 

Itt nyilván a string.Empty helyett használhatunk értelmes default-ot, és illik is. 

Tanulság 

Röviden tehát a lényeg: 

  • SOSE bízzunk abban, hogy a felhasználó MEGAD egy input értéket
  • SOSE bízzunk abban, hogy a felhasználó JÓL ad meg bármilyen input értéket
  • Használjunk értelmes default értékeket

Szólj hozzá!

databájt 2010.07.15. 15:44

How NOT to use string.Format

 Jó napot, jó kódolást! 

Egy ideje érik egy elhatározásom, hogy megosztok néhány tapasztalatot a hatékony kódolásról. 

Azért, mert mindig azt hallom vissza, hogy lassú a szoftverünk.
Azért, mert nem feltétlenül tudjuk, hogyan működik belülről drága jó Májkrokosztunk .NET keretrendszere.
Azért, mert mindig tanulhatunk a jó példából. 

Először is, mitől gyors egy program? Hát attól, hogy NEM  PA-ZA-ROL. Bővebben, nem szórja az erőforrásokat a négy égtáj felé könnyü pipafüstöt eregetve közben, elektronokat számolgatva. 

Na, akkor lássunk egy metódust, aminek a belseje (a foreach blokk) több, mint 13000-szer fut le (hogy miért, nem lényeges):


internal static Dictionary<string, TableDefDescriptor> GetDescriptorTables()
{
      var descriptorTables = new Dictionary<string, TableDefDescriptor>(); 

      foreach (var  kvP in MetadataManager.FlatProperties)
      {
            if (!kvP.Value.TableName.ToUpper().StartsWith("ALL_"))
            {
                  string classID = kvP.Value.TableSchema + "_" + kvP.Value.TableName;
                  try
                  {
                        if (MetadataManager.FlatClasses[classID].IsDescriptor)
{
                             string tableName = string.Format("{0}_{1}", MetadataManager.FlatClasses[classID].Schema, MetadataManager.FlatClasses[classID].CoreTable);
 

                             if (!descriptorTables.ContainsKey(tableName))
                             {
                                   TableDefDescriptor tbl = new TableDefDescriptor(MetadataManager.FlatClasses[classID].Schema, MetadataManager.FlatClasses[classID].CoreTable, MetadataManager.FlatClasses[classID].CoreTableKey);

                                    if (kvP.Value.DescriptorInternal != null || kvP.Value.DescriptorExternal != null)
                                         tbl.AddQualifier(kvP.Value);
                                   descriptorTables.Add(tableName, tbl);
                             }
                             else
                             {
                                   if (kvP.Value.DescriptorInternal != null || kvP.Value.DescriptorExternal != null)
                                         descriptorTables[tableName].AddQualifier(kvP.Value);
                             }
                        }
                  }
                  catch (Exception)
                  {
                        throw;
                  }
            }
      }
       return descriptorTables;
}


 

A fenti kód nagyszerű.  Arra, hogy megnézzük rajta, mit NEM lenne szabad csinálni, ha gyors programra vágyunk. 

String.Format, avagy mire NE használjuk 

Konkatenálásra. Hát, röhögtetős a különsége a két, egyébiránt egyforma kimenetet produkáló sornak: 

string classID = string.Format("{0}_{1}", kvp.Value.TableSchema, kvp.Value.TableName); // Kb 15000 tick

string classID2 = kvP.Value.TableSchema + "_" + kvp.Value.TableName; // Kb 750 tick, 20x gyorsabb 

Azért, mert a string.Format() implementációja StringBuilder-t instanciál, formátumot parszol, hókusz-pókuszol, csirkecsontokat szór a földre az aktuális csillagállástól függően, miegymás.

A konkatenálás meg összefűzi a két szövegrészt. A fenti metódusban, mivel 13000 –szor fut le kb. a foreach-en belüli rész,

csak a konkatenáláson buktunk 185 250 000 ticket, azaz ~80ms alatt csináltuk meg, amihez elég ~4ms is. Jó, mi? J 

Dictionary, avagy hogyan NE használjuk 

A Dictionary faj, mint olyan, arra való, hogy gyorsan lehessen valamit előkaparni sok elem közül. Ezért van kulcsa, nem azért, hogy mindenféle olvasható (értsd: string) dolgokat használjunk kulcsnak.

De egy dologról semmiképp nem szabad megfeledkezni: minden lookup-nak ára van!

Számoljuk meg, hányszor keressük elő a fenti kódban ezt: MetadataManager.FlatClasses[classID] 

...

Igen, jól számoljátok, 6x, azaz hatszor. Vagyis 5x feleslegesen dolgozik a szerencsétlen mikroprucesszor.

A fenti metódus kb. így nézne ki, ha én írtam volna: 


internal static IDictionary<string, TableDefDescriptor> GetDescriptorTables()
{
       var descriptorTables = new Dictionary<string, TableDefDescriptor>();

      foreach (var  kvP in MetadataManager.FlatProperties)
      {
            if (!kvP.Value.TableName.ToUpper().StartsWith("ALL_"))
            {
                  string classID = kvP.Value.TableSchema + "_" + kvP.Value.TableName;
                   // A lényeg, hogy nem dobjuk el a már kikotort cuccost
                  PLClass plClass = MetadataManager.FlatClasses[classID];

                  try
                  {
                        if (plClass.IsDescriptor)
                        {
                             string tableName = plClass.Schema + "_" + plClass.CoreTable;

                             if (!descriptorTables.ContainsKey(tableName))
                             {
                                   TableDefDescriptor tbl = new TableDefDescriptor(plClass.Schema, plClass.CoreTable, plClass.CoreTableKey);
                                   if (kvP.Value.DescriptorInternal != null || kvP.Value.DescriptorExternal != null)
                                         tbl.AddQualifier(kvP.Value);
                                   descriptorTables.Add(tableName, tbl);
                             }
                             else
                             {
                                   if (kvP.Value.DescriptorInternal != null || kvP.Value.DescriptorExternal != null)
 descriptorTables[tableName].AddQualifier(kvP.Value);
                             }
                        }
                  }
                  catch (Exception)
                  {
                        throw;
                  }
            }
      }
      return descriptorTables;
}


 

A kettő között kb. annyi a különbség, mintha poénból ötször (5x!!!) visszadobnánk a tűt a kazalba. 

Konklúzió, ha van ilyen

 Röviden, csak áttekintő jelleggel:

  • String.Format-tal konkatenálni -> PATÁS
  • Dictionary lookup eredményét újra meg újra eldobni -> PATÁS

Szóval, ha azt szeretnétek, hogy legyen keret mindig WC papírra, új, puccos gépekre, meg székekre, amire mindenkinek volt egy grimasza, akkor kezdjétek el megfogadni az ilyen apróságokat.

Vagy lehet kódolni Kovácsné és Társa módszerrel, akkor viszont ne vegyetek tartós tejet.

Szólj hozzá!

databájt 2010.07.14. 09:26

Hand of fate

 Jó napot, jó kódolást! 

Nos, hogy jó példával járjak elöl, rögtön kedves BuildBreak rovatunk indulása után sikerült egy break-et összehozni – higgyétek el, nem kétes hírnevemet akartam öregbíteni. 

Mai buidbraker: Nadabán György 

A 13-ai esti build (18:30) nem futott le, a következő hiba miatt: 

c:\WINDOWS\Microsoft.NET\Framework\v3.5\Microsoft.Common.targets(3397,13): error MSB3073: The command "COPY d:\ccnet\projects\inveniointegration\src\invenio\trunk\dam-con\binaries\Release\..\..\..\contracts\binaries\Release\Harris.Dam.Contracts.Server.* d:\ccnet\projects\inveniointegration\src\invenio\trunk\dam-con\binaries\Release\ /Y

c:\WINDOWS\Microsoft.NET\Framework\v3.5\Microsoft.Common.targets(3397,13): error MSB3073: COPY d:\ccnet\projects\inveniointegration\src\invenio\trunk\dam-con\binaries\Release\..\..\..\server\binaries\Release\Harris.Dam.Server.SqlTools.dll.* d:\ccnet\projects\inveniointegration\src\invenio\trunk\dam-con\binaries\Release\ /Y" exited with code 1.

c:\WINDOWS\Microsoft.NET\Framework\v3.5\Microsoft.Common.targets(3397,13): error MSB3073: The command "COPY d:\ccnet\projects\inveniointegration\src\invenio\trunk\dam-con\binaries\Release\..\..\..\contracts\binaries\Release\Harris.Dam.Contracts.Server.* d:\ccnet\projects\inveniointegration\src\invenio\trunk\dam-con\binaries\Release\ /Y

c:\WINDOWS\Microsoft.NET\Framework\v3.5\Microsoft.Common.targets(3397,13): error MSB3073: COPY d:\ccnet\projects\inveniointegration\src\invenio\trunk\dam-con\binaries\Release\..\..\..\server\binaries\Release\Harris.Dam.Server.SqlTools.dll.* d:\ccnet\projects\inveniointegration\src\invenio\trunk\dam-con\binaries\Release\ /Y" exited with code 1.

Failed to start MSBuild.

External Program Failed: C:\WINDOWS\Microsoft.NET\Framework\v3.5\msbuild.exe (return code was 1) 

Nos, a lényegét tekintve egy post-build task kellett volna fusson ehelyett a hiba helyett, amit láthatóan nem sikerült msbuild-kompatibilisre készíteni.

Ellenben Iwo személyében akadt megoldója is rögtön a problémának, itt le van írva, hogyan lehet elkerülni a hasonló végzetet.

Mivel másodlagos a project működése szempontjából a build task, javításig dizablázva lett.

Tanulság

Mindig célszerű tágabb környezetben is kipróbálni a post-build event-ek hatását, hátha az msbuild tud újat mondani a Visual Studio-hoz képest.

Tud. ;)

Szólj hozzá!

databájt 2010.07.12. 11:21

Unit testing

 Jó napot, jó kódolást! 

Ez a levél azoknak szól, akik valamilyen formában unit test írásra hajlamos jeleket mutatnak, és egy, urambocsá’ több [TestMethod] attribútumot is elhelyeztek már kedvenc teszt-metódusuk szignatúráján (milyen szép, magyar kifejezések, nemde?).

Akinek semmi köze a kódoláshoz, nyugodtan rakhatja is a kukába ezt a levelet. 

Kéretik minden kedves kollégától, hogy a teszt-metódusokat olyan módon készítsék el, hogy a teszt iHö legyen: 

  • Illedelmes: előkészíti magának a szükséges feltételeket, és visszaállítja az eredeti állapotokat futtatás után
  • Helyes: azt vizsgálja/ellenőrizze, amit kell
  • Önjáró: ne építsen más tesztek sikerességére/sikertelenségére, ne függjön a környezettől, ha ez lehetséges

 

Az elvárt viselkedéstől eltérő teszt-esetek készítői kapnak feketepontot az ellenőrzőjükbe, és hajnali 1 és 5 óra közötti rendszeres telefonhívásokra számíthatnak, valamint felmenői ágon heveny csuklásrohamokra.

Szólj hozzá!

databájt 2010.07.12. 09:28

First blood

 Jó napot, jó kódolást! 

Kedves mindenki! Elérkeztünk egy fordulóponthoz. Megint. Mint csapat, mint szakemberek halmaza, tekintsen mindenki saját szájíze szerint a fogalomra.

Eljött az az idő, amikor már annyira összetett rendszert fejlesztünk, hogy nem lehetünk figyelmetlenek vagy felületesek sem a tesztelése, sem a fejlesztése, sem a tervezése során. 

Pontosabban, már nagyon régen elért minket ez a vég, csak még mindig nem alkalmazkodtunk az új kihíváshoz. 

Nos, megelégelve, hogy szinte mindenki bele-bele kap dolgokba, és ezzel alkalomadtán több kárt okoz, mint hasznot, elhatároztam, hogy ha valaki miatt a rendszeres napi buildek nem futnak le,

azt vegytiszta motivációs szándékkal közkinccsé teszem, remélve, hogy mindenki okul belőle - az is, aki elkövette, és azok is, akik még tapasztalatlanok az adott területen. 

Fogadjátok hát sok szeretettel és megértéssel első versenyzőnket, aki minden bizonnyal nem fog örülni a hírnévnek.

 

Mai buidbraker: Gábor 

Előzetesen kérem, hogy ne hordja fenn senki az orrát a triviális, valószínűleg puszta sietségből fakadó hiba láttán, mivel 22:30 körül, vagyis jóval a munkaidőn kívül történt a patch (ami pedig elkötelezettség, így mindenkitől dícséretes). 

A következő (bele)javítás volt a reggeli build break oka: 

Adott volt egy trigger szkriptje, amiben ezt: 

                               INSERT INTO @Result
                              SELECT DISTINCT [LeftID], [RightID], [Level] FROM @Tmp AS T

                               WHERE NOT EXISTS (SELECT * FROM [SomeFancyTableNameSpottedByBence] TC WHERE TC.[LeftID] = T.[LeftID] AND TC.[RightID] = T.[RightID]);
                               INSERT INTO [SomeFancyTableNameSpottedByBence]

                               SELECT DISTINCT [LeftID], [RightID], [Level] FROM @Tmp AS T
                                WHERE NOT EXISTS (SELECT * FROM [SomeFancyTableNameSpottedByBence] TC WHERE TC.[LeftID] = T.[LeftID] AND TC.[RightID] = T.[RightID]); 

javította erre: 

INSERT INTO @Result

                               SELECT [LeftID], [RightID], MIN([Level]) FROM @Tmp AS T

                               WHERE NOT EXISTS (SELECT * FROM [SomeFancyTableNameSpottedByBence] TC WHERE TC.[LeftID] = T.[LeftID] AND TC.[RightID] = T.[RightID]);

                               GROUP BY [LeftID], [RightID]                              

                               INSERT INTO [SomeFancyTableNameSpottedByBence]
                               SELECT [LeftID], [RightID], MIN([Level]) FROM @Tmp AS T
                               WHERE NOT EXISTS (SELECT * FROM [SomeFancyTableNameSpottedByBence] TC WHERE TC.[LeftID] = T.[LeftID] AND TC.[RightID] = T.[RightID]);

                               GROUP BY [LeftID], [RightID] 

Onnan sejtem, hogy tesztelés nélkül, hogy szintaktikailag helytelen. Aki önállóan megtalálta a hibát, az nem kap pirospontot – ez a mi szintünkön már elvárható.

Aki nem látja a szembetűnő pontosvesszőt a szkriptek GROUP BY része előtt, az igyekezzen jobban. 

A változás a 14160-as rev. volt, javítottam. 

Tanulság 

Úgy gondolom, hogy a fenti „benézés” elkerülhető lett volna egy áttekintő pillantással, hogy minden tényleg a helyén van-e.

De végsősoron egy adatbázis-létrehozási teszt is megtette volna, hiszen rögtön a folyamat elején (kb. fél perccel CCNET indítás után) kiderült volna a turpisság.

A reggeli build most különösen fontos lett volna a VA prozsé miatt. Hosszú-hosszú ideig futott volna munkaidő előtt (pl 27 perc csak a replikációs tesztek). 

Óva intek mindenkit attól, hogy kárörömködjön, hiszen előbb vagy utóbb mindenki követ el hibát – és akár az ő neve is szerepelhet egy ilyen levélben. 

Szólj hozzá!

süti beállítások módosítása