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.

Et meget hyppigt anvendt argument for at bruge stored procedures frem for parametriserede queries på SQL Server er performance.  Argumentet er, at eksekveringsplanen for sprocs kan caches på serveren.  Det er korrekt, at sådan forholdt det sig på SQL Server 7 og måske også på 2000, men efter min bedste overbevisning er SQL Server nu i stand til på samme måde at lave caching for parametriserede queries.

Det er svært at finde dokumentation af dette (hvis du har links, så giv dem til mig!), så jeg besluttede at lave et lille forsøg: Jeg lavede en lille testtabel og prøvede at indsætte 100.000 rækker 50 gange med en sproc og med en parametriseret INSERT statement.

Konklusionen?

Jeg kunne ikke se nogen forskel.  Selvfølgelig svinger tiderne noget i forhold til, hvad serveren ellers laver, men man kan ikke se, at den ene metode er hurtigere end den anden.

Der kan selvfølgelig være mange andre argumenter for at sprocs - f.eks. security.  Det vil jeg ikke gå yderligere ind i her, for det plejer at kunne få folk helt op at køre.

Historien er en anden med dynamisk opbygget SQL.  Her kan SQL Server ikke cache.  Men dynamisk opbygget SQL er i forvejen bandlyst pånær i særlige tilfælde, da det øger risikoen for SQL injection.

Med 64-bit og .Net er det jo "ingen problem", da JIT'eren klarer alt det sjove for os. I hvertfald så længe vi kører rent managed code. Der er et lille problem med f.eks. COM (noget jeg nævner helt tilfældig) og p/invoke. Scott Hanselman har skrevet lidt om det her. Det jeg specielt bed mærke i er muligheden, eller mangel på samme, for at loade 32-bit assemblies ind i en 64-bit process. Man får simpelthen en BadImageFormatException. Specielt vil det ikke være muligt at pakke sin COM kode ind i en 32-bit wrapper og så bruge den fra både 32-bit og 64-bit.

Nu mangler jeg bare at finde ud om det er 64-bit versionen af Window 7 Beta jeg har fået installeret og hvordan jeg får mountet iso filer, når nu daemon tools ikke virker. Imens holder jeg mig til det gode gamle 32-bit. Det ved man hvad er, eller?

Jeg synes, at man rundt omkring på forskellige blogs kan spore en vis tvivl om, hvorvidt vi virkelig har brug for alle de programmeringssprog, der skyder frem (se f.eks. Martin Fowler, Daniel F og Neal Ford).

Jeg tror på, at man såvidt muligt skal bruge det rigtige værktøj til at løse en opgave.  Mange inklusive undertegnede lader sig styre af følelser og "religion" ved valg af programmeringssprog.  Det er i bund og grund usagligt og useriøst.  Hvis Excel og VBA er det rigtige værktøj til opgaven, så skal man vælge det.

Derfor er det vigtigt, at man er dygtig til et bredt udvalg af programmeringssprog, så man har et arsenal at vælge fra.  Ellers ender det med, at man strækker sit favoritværktøj til det yderste for at kunne løse en specifik opgave.  I mine øjne er det ligegyldigt, om man bruger VB.NET eller C#.  Man kan have sin favorit, men i bund og grund kan de to sprog det samme.  Det var noget andet med f.eks. VB6 og Visual C++.  De tog værktøjer havde vidt forskellige anvendelser.  Netop VB6 er et godt eksempel på et værktøj, som blev brugt til meget andet end det var beregnet til.  VB6 havde så mange muligheder for at lave lækre memory leaks og andre mærkelige ting, at det i nogle henseender faktisk var sværere at bruge end Visual C++.  Men den typiske VB6 programmør kunne ikke andet.  Til gengæld var den typiske VC++ programmør for stolt til at nedværdige sig til at bruge VB6, selvom det havde været det rigtige valg i nogle tilfælde.

Det stiller høje krav at være god til flere sprog, men de fleste mestrer allerede adskillige sprog som C#, VB.NET, C++, JavaScript, VBS, PowerShell og SQL.  Ellers kommer man jo ingen steder.

Nu har vi så fået F#.

Jeg tror, det er umagen værd at lære F# af én bestemt grund: Parallel processing.

Funktionel programmering har eksisteret i mange år.  Fordelen for mig ved F# ift. f.eks. Erlang er integrationen med .NET.  F# er som udgangspunkt funktionelt, men man kan kommunikere med anden .NET kode, hvis man har behovet.

Man hører mange andre argumenter for at brug F# - herunder god performance.  Jeg tror gerne på, at F# kan give bedre udviklerperformance på nogle typer opgaver som f.eks. matematisk opgaver, som bestemt er relevant for aktuarer.  Mht. runtimeperformance er jeg lidt mere tvivlende.  F# kode kompileres til IL-kode, og IL er i bund og grund objektorienteret.  Så for at kunne lave lækre funktionelle ting som f.eks. ufuldstændige funktionskald (eller hvad det nu hedder på dansk), bliver IL'en virkelig sær.  Det tror jeg vil gå ud over performance i de fleste tilfælde, medmindre selvfølgelig at JIT-kompileren kan gennemskue det og optimere.