Fredag den 29. maj 2009 er som bekendt ninjas CS tid. Vi spiller på Boomtown fra kl. 19. For dem som ønsker at lade op til strabadserne, så prøver vi at finde et (tilfældigt) bord på den nærliggende Cafe Rene omkring kl. 17. Smid gerne en kommentar om hvor (og hvornår du dukker op).

Jeg har et stykke tid overvejet om jeg skulle opgradere min trusted Synology NAS DS-101 med en DS-207+ eller en Windows Home Server. Løsning kom lidt af sig selv, idet Synology udfaser deres 7’er serie og erstatter den med en 9’er serie som er markant dyrere og at Microsoft valgte at forære mig en fuld kopi af Window Home Server softwaren i et fremstød for TechNet.

Udgangspunktet var 2 x 1.0 TB WD Caviar Green harddiske, så hvis eksperimentet mislykkedes kunne de flyttes over i en Synology NAS når prisen var kommet lidt ned. Målet var at få en server som ikke larmer, som kunne slukkes om natten og ikke mindst tændes via Wake-On-LAN. Server er nemlig fysisk placeret i to meters højde. DS-101 slukker harddiskene efter længere tids inaktivitet, men falder ikke selv i søvn. Det er en udmærket løsning, når den altså ikke vælger at starte på meget mystiske tidspunkter.

Som CPU valgte jeg en Atom 330 fordi den skulle være strømbesparende (noget Core 2 dog kan rivalisere i Idle tilstand), men også fordi processor og bundkort kommer samlet – så slipper man for at sættes sig ind i sokkel typer og chipsæts. RAM priserne er meget fornuftige for tiden (har ikke fulgt med i udviklingen, så jeg ved ikke om det kan tilskrives finanskrisen), så jeg gik all-in og smed en 2 GB klods i maskinen – bundkortet har kun plads til en enkelt klods. Det var svært at finde et minikabinet som giver plads til to 3,5” harddiske, så jeg gik op i et noget større kabinet, men så skulle frit airflow i kabinettet også være sikret. Strømforsyning er fra Chill Innovation (pga køb dansk profilen, ikke reklamerne… eller noget) og skulle være meget lydløs, men en blæser er nu engang en blæser, så det med lyden kommer vi lige tilbage til.

Installationen af Windows Home Server var ganske smertefri og blev klaret med mit eksterne USB DVD-drev. Når først det er klaret, kan man smide keyboard og monitor tilbage i skabet – maskinen kræver kun strømkabel og netkabel. Serveren tilgås med speciel Connector Software på din PC. Man kan det meste via WHS Console programmet, ellers kan man bruge remote desktop. Jeg havde nogle udfald i starten fordi serveren valgte at genstarte efter at have installeret nogle kritiske opdateringer, hvilket er helt fint – en advarsel når jeg var connect havde dog været i orden.

Dernæst har jeg installeret et par plugins som hjælper mig med at opnå mine mål med serveren. WOL er tændt som default på Atom bundkortet, så jeg skulle bare finde nogle plugins som kunne ta’ sig af kontrollen. Lights Out er nok det som kommer tættest på det jeg ønsker. Det giver mulighed for at slukke serveren et antal minutter efter sidste klient er disconnected og starte automatisk når den første logger på igen. Der er også mulighed for at holde serveren levende i specielle tidsrum hvis den f.eks. skal være tilgængelig for din Sonos. Alternativ giver AutoExit 2008 bedre mulighed for at starte serveren når man ønsker det (hvis man f.eks. ikke bruger den så tit) og bedre mulighed for at vække din pc for backup.

Som det kører nu virker det hele fint og er en god erstatning for min Synology. Det er dog et lille men, som bliver det næste projekt i rækken. Det lyder som om jeg har et lille jetfly stående i min gang – måske fordi den sidder oppe over en dør hvor der er lidt varmere, måske fordi der er 5 blæsere til en lille hygge CPU. Strømforsyningsblæseren er meget fornuftig og da maskinen ikke trækker ret meget strøm, så køre blæseren heller ikke så hurtigt. Lige nu bliver den klart overdøvet at de andre blæsere. Kabinetblæserne er ikke noget at prale med og bliver nok skiftet når jeg har undersøgt markedet. Nu kom bundkort og cpu sammen, og dermed kommer der også en standard CPU blæser – en lille 40mm blæser som larmer mere end de andre tilsammen. Den er helt sikkert mål for udskiftning og jeg har da også fundet forumindlæg med andre som har samme problem. Men det er jo altid rart at ha’ et projekt.

Som sagt, så spiller teknikken – lyden spiller bare for højt (og det skyldes ikke at jeg er blevet ældre).

Jeg husker den glade sommer (husker dog ikke helt hvornår det var) hvor jeg lå i solen og prøvede at læse op til en SQL Server eksamen. Det lykkedes aldrig at komme forbi kapitlet om opbygningen af den fysiske filstruktur i databasen. Det hører jo med til en MS certificering, så den blev ikke til noget.

SQL Server er den database jeg har arbejdet mest med, men til mit seneste hobby webprojekt var det naturlige valg MySql. Pandasan var så flink at vise mig XAMPP som findes i en light udgave, hvilket var præcis det jeg skulle bruge til mit hygge project. Uden at skulle installere services kan man ha' MySql kørerende på sin lokale maskine på få minutter.

For et hobby website har MySql syntax en meget nyttig kommando LIMIT. Denne kommando alene har tidligere gjort at jeg har prøvet kræfter med MySql. Paging bliver et spørgsmål om man lige kan gange sidestørrelse med sidenummer og det bør være overkommelig matematik for en programør. MySql provideren til .Net findes på MySql's website og er dermed ikke en 3rd party komponent som det var for nogle år siden.

Det virker generelt som om der er sket en del med MySql siden jeg sidst så på det. Og man kan også spørge om Are Commercial Databases Worth It?. Star Wars sammenligninger giver altid letforståelige forklaringer. Jeg har ikke selv oplevet de store forbedringer i SQL Server for mine små projekter. Og t4rzsan har jo set lidt på Parametriserede queries vs. stored procedures, så den konto er også brugt selvom MySql har fået Stored Procedures for snart lang tid siden.

Betyder det så noget at Oracle har købt SUN og dermed også MySql? Jeg har i forbindelse med et kursus på ITU stødt på Oracle og det var ikke overbevist. Det virker unødigt komplekst for at sælge nogle konsulenttimer, hvilket jeg mener er at narre folk. Selvom der er andre firmaer som bygger deres forretning på denne model, er det jo ikke et argument for at gøre det selv.

Nok med brok, se at komme igang med MySql. XAMPP kan downloades som zip og køres næsten uden setup. Det er et værdigt alternativ til SQL Server (også Express).

Det er tid til at vise dine ninjaevener på Counter-Strike banen igen. Og det er ikke en undskyldning at man er n00b (eller ikke ved hvad n00b betyder), for det er vi alle efter sidste års aflysning. Desuden er det næsten sommer. Vi har antaget at spillet findes endnu og vi kan få nogle pladser på boomtown

Fredag den 29. maj

Der kan på opfordring arrangeres at vi finder en bid brød før massakren og evt. et par pils bagefter til at diskuttere resultatet.

Tilmelding sker ved at skrive en kommentar til dette indlæg senest den 18. maj kl 12.00.

I en tidligere post så vi hvor nemt det var at komme igang med IronPython og jeg håber det kom frem at det virkligt er mærkbart nemmere end at skulle til at bruge C#/VB.Net som scriptsprog. Hvis ikke så prøv at forklar dig selv hvordan et enkelt delegate kald (som f.eks. add(2, 3)) skal wrappes for at køre som C# script. Eller hvor meget reflection du skal bruge for at få adgang til en delegate eller kallse du har definere i dit script.

Nu vil jeg forsøge at beskrive det sidste i IronPython, altså hvordan man kan bruge Python klasser i C#. Vi begynder med at definere en simpel Python klasse.

class MyClass(object):
    def add(self, a, b): return a + b

Når koden er kørt vil vores scope indeholde en variabel med navnet på klassen, her MyClass.

object myClass = scope.GetVariable("MyClass"); 

Dette er klassedefinition som vi skal bruge til at oprette en ny instans of klassen og til at kalde methoder på klassen. Vi skal også bruge ObjectOperations fra vores engine.

ObjectOperations op = engine.Operations; 

Herefter er det bare at gå igang med at oprette en instans, finde methoden og kalden metoden:

object instance = op.Call(myClass);
object method = op.GetMember(instance, "add");
int sum = (int)op.Call(method, 2, 3);

Hvis sum ikke er 5, så gik det galt (and you're on your own). Det var det. Måske nogle kan genkende et mønster fra IDispatch.Invoke, eller der det bare mig der fik et deja-vu. Med dynamic i C# 4.0 skulle det blive meget nemmere med disse operationer, fordi det er speciel designet til at kode op mod f.eks. Office objekt modellen, men det ser vi på til den tid.

Jeg var egentlig i gang med at skrive et indlæg om hvor let det var at integrere IronPython i egne programmer, da jeg stødte på A 3 minute guide to embedding IronPython in a C# application. Så indlægget blev sat på stand-by, men det er et sjovt emnu og nu kommer det så alligevel - men vi prøver at gøre det på kun 2 minutter.

Lige en hurtig disclaimer inden kommer for godt i gang: Jeg er ny til Python som sprog og platform. Det med de to minutter er måske også en underdrivelse, men den tid det ta'r at komme i gang med IronPython er stærkt korreleret med hastigheden for på din internetforbindelse.

Det først der skal ske er at du downloader IronPython fra http://www.codeplex.com/IronPython. Minimum requirement for IronPython 2.0 er .NET 2.0 SP1. Det vil kræve den største del af de to minutter. Du kan bruge Python konsollen (ipy.exe) til at eksperimentere med Python og så er du sådan set i gang. Den medfølgende tutorial kan anbefales hvis du vide mere om integrationen mellem .NET og Python.

Det sjove kommer når man begnder at integrere IronPython med egne programmer. IronPython er baseret på Dynamic Language Runtime (DLR) og bortset fra det rent Python specifikke, så vil andre DLR sprog kunne integreres på samme måde. Så selvom Python konsollen er smart vil du nok begynde at kede dig efter to minutter.

Det eneste du skal bruge er en ScriptEngine og et ScriptScope.

ScriptEngine engine = Python.CreateEngine();
ScriptScope scope = engine.CreateScope();

Tada. Du skal naturligvis referere IronPython.dll, Microsoft.Scripting og Microsoft.Scripting.Core. ScriptEngine er det springende punkt for valgt af sprog. Andre sprog, som f.eks. IronRuby, sættes op på lignende måde, men med en anden engine.

Så er det bare at fyre noget kode afsted. Lad os starte med et simpelt udtryk:

string input = "3+3";
ScriptSource source = engine.CreateScriptSourceFromString(input, SourceCodeKind.Expression);
source.Execute(scope);

Det er i sig selv ikke så imponerende, de fleste af os kunne regne det ud i hovedet. Men hvis vi skal køre egentlig Python kode skal SourceCodeKind blot ændres til f.eks. Statements eller File.

Vi er endnu ikke kommet meget længere end vi var med ipy.exe, så lad mig lige bruge det sidste af de 2 minutter på at vise hvor nemt det er at integrere dine C# klasser. For at gøre variable tilfængelige i IronPython skal de blot tilføjes til dit scope.

scope.SetVariable("xyz", new Xyz());
scope.SetVariable("add", new Func<double, double, double>(delegate(double a, double b) { return a + b; }));

Den sidste variable bliver en funktion og svarer til det klassiske begynder-eksempel:

def add(a, b):
    return a + b;

Xyz klassen fungerer nu som enhver anden klasse, som beskrevet i Tutorial. Prøv evt med dir(xyz).

Håber det giver dig inspiration til at prøve hvordan du kan integrere IronPython med dine egne programmer.

En af de løsningsmetoder som jeg skitserede under mit indlæg Thread-safe Random Numbers var at oprette en tråd som stod for genereringen af de tilfældige tal. Den metode skulle sikre at resultat var reproducerbart og at man stadig kunne bruge sin favorit talgenerator (Mersenne Twister er et godt bud på en favoritgenerator). Selvom det er en relativt simpel løsning, betyder det jo ikke at man ikke skal tænke sig om. Min første indskydelse var at generere en masse uniform variable i en pool og så transformere dem til f.eks. normalfordelte i løbet af selve simuleringen. Det minimerer tidsforbruget i generatortråden og overlader det til simuleringstrådene.
Det giver dog et lille problem.

Den bedste måde at generere normalfordelte tilfældige tal er at benytte polar Box-Müller som er givet ved

do 
{ 
  x = 2.0 * Random() - 1.0; 
  y = 2.0 * Random() - 1.0; 
  w = x * x + y * y; 
} 
while (w >= 1.0 || w == 0.0) 
w = Math.Sqrt(-2.0 * Math.Log(w) / w); 
x *= w; 
y *= w; 

Ovenstående kode giver os to normaltfordelte tal, men bruger MINDST to ligefordelte tal. Vi ved altså ikke på forhånd hvor mange ligefordelte tal vi skal bruge, hvilket forkaster min udmiddelbare indskydelse. I stedet vil jeg nu skabe en række streams som er generede med den ønskede fordeling. Generator tråden får altså mere arbejde, men alternativet er ikke et reelt alternativ, da det er svært at vide om man er på den sikre side hvis man vil generere rigeligt med ligefordelt tal.

Man kunne indvende at jeg kunne vælge en anden form af Box-Müller, som bruger præcis to ligefordelte tal til at generere to normal fordelte. Hvis vi ser bort fra at denne metode er langsommere og mindre numerisk stabil, så er der jo andre fordelinger som har samme problem, f.eks. Poissonfordelingen. Det antyder at vi bare skal løse problemet med det samme.

Hvad er bedre til at teste YouTube plugin til BlogEngine.Net end den nye Mass Effect 2 Teaser Trailer?

Hvis man har en Xbox 360 (og det har alle Microsoft fanboys som jer), så skal man også ha' Mass Effect. BioWare arbejder på at Mass Effect 2 bliver klar i 2010. Som om ventetiden ikke var lang nok i forvejen, så bliver vi præsenteret for denne trailer som gør det hele værre. Det grænser til tortur. Anyway... enjoy!

[youtube:oIOaJA-apis&hl=en&fs=1,width=480,height=295]

Når man sidder og kigger på en Monte Carlo simulering okser derudaf med 50 % af processorkraften, så begynder man at overveje om man ikke skulle ha' nogle tråde i spil. Microsoft's Parallel Programming team har skrevet et indlæg på deres blog om Getting random numbers in a thread-safe way. Hvis vi lige et kort øjeblik ser bort fra at Random er en håbløs generator til Monte Carlo simulering og se på de problemer indlægget beskriver.

Random er IKKE thread-safe. Det er de implementeringer af Mersenne Twister jeg har set heller ikke. Hvis vi antager, at vi er i besiddelse af en generator som er thread-safe. Er vi så nået meget videre?

Indlægget behandler det faktum at Random initialiseres med TickCount hvis man ikke angiver et seed og at TickCount har en omløsning på omkring 15ms. Derfor vil mange af de tilfældige tal være ens, specielt når man benytte flere tråde. Det gives der så flere løsninger på, men hvis man kører Monte Carlo simulering på lidt mere end hyggeniveau så bruger man ikke et tilfældigt seed. I hvert fald ikke mere tilfældigt end at det stadig ligger på din personlige top-10 favorit seed liste.

Problemet er reproducerbarhed (godt dansk ord). Hvis jeg kører simulering igen med samme seed, får jeg så samme resultat? Sandsynligvis ikke, hvis jeg bruger flere tråde. Selvom jeg sikrer mig at kun en enkelt har adgang til min generator på et givet tidspunkt, så kender jeg ikke rækkefølgen.

En løsning kunne være at give hver tråd sin egen generator som så er initialiseret på en deterministisk måde. Det er implementeret i RandomGen2 eksemplet, hvis bare den globale generator initialiseres med et kendt seed. Problemet er bare om man introducerer noget afhængighed.

Hvis jeg trækker en række tal tilfældigt, så er de genereret så de er indbyrdes stokastiske uafhængige. Men hvad sker der hvis man trækker to rækker med tilfældige tal fra hver sin generator (samme type, men forskellige seed)? Kan de så antages at være uafhængige? Sandsynligvis ikke. Vi skal ikke glemme at tilfældige tal genereret af en computer er mindre tilfældige end dagligdags begivenheder som vi tilskriver højere magter. Det er ren matematik det hele. Det overlades til en øvelse, men ser man på Linear Congruential Generators, så er det relativt simpelt at finde to seeds så man får to forskudte processor - altså det der med LaTeX notation er x_i = y_{i+1}.

Man kunne antage at når jeg nu har påpeget et problem som er relevant for mig, så har jeg også fundet løsningen. Men I må nøjes med nogle indspark, for det er endnu ikke implementeret (gammel C++/COM kode har ikke en Parallel.For). Mit mål er at hver sti i Monte Carlo simuleringen skal behandles af en enkelt tråd. Generelt er der for lidt optimering i at bruge Parallel.For (eller lign. via OpenMP) på enkelte loops inden for hver sti og det forhindrer reproducerbare stier. Men en sti per tråd kan hver tråd få sin egen generator.

Problemet er nu, som beskrevet ovenfor, at undgå at introducere afhængigheder mellem de enkelte stier. Der er flere løsninger på det

  1. Hvis beregningerne er den tunge del kan man vælge at generere alle tilfældige tal up-front. Hver tråd får så tildelt en pool af tilfældige til. Den første tråd kan sættes i gang så en pool er genereret færdig.
  2. Til ovenstående skal man kende antallet at tilfældige tal som skal benyttes i hver pool. Som alternativ til at generere alle tallene up-front  kan man evt. spole frem i generatoren for hver sti. For Random og Mersenne Twister betyder det dog at man generer en masse tal og så smider dem væk.
  3. Der findes generatorer hvor det at spole frem i rækken af tilfældige tal er en simpel operation. Hvis perioden er tilstrækkelig stor kan man spole 2^n tal frem, hvor n er stor nok til at man ikke overlapper med næste sti. Hvis n er stor nok behøver man ikke kende det præcise antal tilfældige tal i hver sti.

Jeg arbejder videre med den sidste ide, men forslag er velkomne.

Jeg har tidligere arbejdet i et firma hvor vi brugte mange soft-delete (eller som det hedder i salgsbrochuren - Fuldt revisionsspor), dvs. man sletter ikke i databasen, men markerer istedet rækken for slettet. I vores tilfælde blev slettetidspunktet markeret, så objektets tilstand kunne genskabes på forskellige valørdatoer. De første man lægger mærke til er naturligvis at databasen vokser hurtigt og at alle queries indeholder 'DeletionTime NOT NULL'. Det var praksis og påkrævet af revisorer.

Man kan mene mange ting om soft-delete og Frans Bouma mener at Soft-deletes are bad. Inden vi ser lidt nærmere på hans løsningsforslag, så lad mig give ham ret i at, hvis du ikke har revisorer på nakken, så bør du nok finde på en anden løsning end soft-deletes. Gammelt data er trods alt.. ja, gammelt. Jeg ved godt at vi lever i Google-tid og man bare kan søge igennem alt, men lader du Google indeksere dine business data?

Vi har allerede etableret et behov for at bevare data af juridiske grunde, men det er også værd at nævne at data forbliver slettet. Gammelt data bruges ikke til at "spole" objektets tilstand tilbage ved at "undelete" data, altså fjerne slettetidspunktet i databasen. Derimod oprettes en ny linie som en kopi af den gamle. Ellers ville vi miste det fulde revisionsspor. Vi er ikke i det Frans Bouma kalder tilfælde to (men beskriver først). Det er også noget rod.

En naturlig løsning er at arkivere gammelt data, som man arkiverer gamle emails (eller hvordan er det nu lige er man får håndteret dem?). Frans Bouma foreslår at bruge database triggers. En delete trigger kan kopiere det slettede data over i anden tabel. Et forslag som jeg ikke kan stå 100% bag. Jeg kan se ideen i at kopiere slettet data over
i en anden tabel. Det er alligevel oftest, at man kun skal bruge de aktuelle data og i de tilfælde hvor man skal se gammelt data vil det kunne klares med et view.

Det er database triggeren som giver mig problemer. Der er mange gode og dårlige grunde til at bruge triggers eller lade være, men min helt store anke i dette tilfælde er manglende triggers. Hvis man får slettet triggeren fra database så får man ingen fejlbeskeder. Uden at vide det kan man få slettet en masse data som burde være kopieret. Man opdager det først når man skal bruge de gamle data. En stored procedure ville kunne gøre det samme og man vil helt sikkert opdage hvis den manglede.

Jeg vil mene at soft-deletes har deres plads, specielt for enterprise data. Men derfor er det stadig tilladt at organisere sine data bedst muligt. Bare lov mig at du ikke bruger en delete trigger som er kritisk for dine data.