<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://antme.net/utility/FeedStylesheets/atom.xsl" media="screen"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><title type="html">AntMe! 2.0 Blog</title><subtitle type="html">Gedanken zur neuen AntMe! Version.</subtitle><id>http://antme.net/blogs/antme2/atom.aspx</id><link rel="alternate" type="text/html" href="http://antme.net/blogs/antme2/default.aspx" /><link rel="self" type="application/atom+xml" href="http://antme.net/blogs/antme2/atom.aspx" /><generator uri="http://communityserver.org" version="3.1.20917.1142">Community Server</generator><updated>2007-10-28T20:33:00Z</updated><entry><title>Statische Generische Klassen und Bastis Tutorial</title><link rel="alternate" type="text/html" href="http://antme.net/blogs/antme2/archive/2007/11/17/statische-generische-klassen-und-bastis-tutorial.aspx" /><link rel="enclosure" type="application/x-zip-compressed" length="17134" href="http://antme.net/blogs/antme2/attachment/151.ashx" /><id>http://antme.net/blogs/antme2/archive/2007/11/17/statische-generische-klassen-und-bastis-tutorial.aspx</id><published>2007-11-17T11:56:00Z</published><updated>2007-11-17T11:56:00Z</updated><content type="html">&lt;p&gt;Vor ein paar Tagen hat Basti mir eine Ameisenklasse geschickt, die er von jemandem bekommen hat, die einen Bug in AntMe! demonstriert. Für solche Einsendungen sind wir immer dankbar, denn genau wie bei der Rechtschreibung gilt bei der Programmierung auch, dass man seine eigenen Fehler meistens nicht selbst findet. Nach Bastis E-Mail habe ich noch eine E-Mail von jemand anderem bekommen, der den selben Bug demonstriert hat. Es handelt sich hierbei um eine neue Version des Bug-Bugs durch die man einen Käfer mehrfach töten und damit sehr viele Punkte sammeln konnte. Das ist jetzt natürlich behoben. Wenn Käfer tot, dann Käfer tot. Nochmal draufhauen bringt nichts.&lt;/p&gt;
&lt;p&gt;Die zweiten Person hat mir dann noch eine andere Ameisenklasse geschickt, durch die ich einen anderen lange gesuchten Bug gefunden habe. Vielen Dank Philipp! Dank dir bewegen sich Äpfel nun nicht mehr von alleine und in die falsche Richtung.&lt;/p&gt;
&lt;p&gt;Wie auch immer. Mir war an Philipps Ameisenvolk eine Hilfklasse aufgefallen, die mir ein wenig seltsam erschien und in dem anderen Volk fand ich selbige Klasse auch wieder. Ich kam dann darauf: beide haben den &lt;em&gt;KoordinatenSpeicher&lt;/em&gt; aus Bastis Globales Gedächtnis Tutorial verwendet. Das habe ich, weil ich was Ameisen angeht ein Fan von überhaupt-kein-Gedächtnis-und-schon-gar-nicht-global bin, zugegebenermaßen nie gelesen. Inzwischen habe ich das nachgeholt und das Tutorial ist (wie erwartet) richtig gut, aber eine kleine Sache darin ist sicher nicht im Sinne des Autors und damit kommen wir endlich zum Titel dieses Blog Eintrags.&lt;/p&gt;
&lt;p&gt;Basti schreibt&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Kommen wir also schon zu unserem ersten Schritt. Wir erstellen uns eine neue Klasse mit dem Namen „KoordinatenSpeicher“. Diese Klasse machen wir statisch und erstellen uns dort drin 3 Collections.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;public static class KoordinatenSpeicher&amp;lt;K,V&amp;gt; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; private static Dictionary&amp;lt;K, V&amp;gt; zuckerKoordinaten = new Dictionary&amp;lt;K, V&amp;gt;();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; private static Dictionary&amp;lt;K, V&amp;gt; obstKoordinaten = new Dictionary&amp;lt;K, V&amp;gt;();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; private static Collection&amp;lt;int&amp;gt; geloeschteObjekte = new Collection&amp;lt;int&amp;gt;();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // Properties...&lt;br /&gt;}&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Jetzt Fragt ihr euch bestimmt was hat der Typ da gemacht. Ich erkläre es euch. Also wir ihr seht haben wir jetzt 3 Collections erstellt. Die erste Collection „zuckerKoordinaten“ ist dafür bestimmt alle Zucker Koordinaten abzuspeichern. Die 2te Collection „obstKoordinaten“ ist für die Obst Koordinaten zuständig. Dann seht ihr hier noch, dass ich hier generische Typen benutzt habe und zwar K und V. K steht in diesem Falle für den Typen des Keys und V für den Typen des Values. Als Key nehmen wir im weiteren Tutorial immer die ID des Obstes oder Zuckers und also Value immer das Obst oder den Zucker selbst, aber dazu später mehr. Als drittes haben wir noch die „geloeschteObjekte“, hier werden alle IDs der Obst oder Zuckerobjekte gespeichert, die schon gelöscht wurden, auch dazu später mehr. Das war dann schon erst einmal alles was wir für unseren Speicher benötigen um ihn zu benutzen.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Gestört hat mich daran der generische Typparameter &lt;em&gt;&amp;lt;K,V&amp;gt;&lt;/em&gt; an der statischen Klasse &lt;em&gt;KoordinatenSpeicher&lt;/em&gt;. Der Typparameter &lt;em&gt;K&lt;/em&gt; für die &lt;em&gt;Dictionary&lt;/em&gt; Instanzen ist im Tutorial immer &lt;em&gt;int&lt;/em&gt;, bei der &lt;em&gt;Collection&lt;/em&gt; Instanz steht aber nicht &lt;em&gt;K&lt;/em&gt; sondern direkt &lt;em&gt;int&lt;/em&gt;. Das ist meiner Meinung nach ein wenig inkonsequent. Der Zugriff auf den Speicher erfolgt unterschiedlich, je nachdem auf welche der internen Listen zugegriffen wird.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;KoordinatenSpeicher&amp;lt;int, Zucker&amp;gt;.zuckerKoordinaten.Add(zucker.Id, zucker);&lt;br /&gt;KoordinatenSpeicher&amp;lt;int, Obst&amp;gt;.obstKoordinaten.Add(obst.Id, obst);&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Was nun passiert ist nämlich folgendes: der Compiler merkt sich, mit welchen Typparametern auf den &lt;em&gt;KoordinatenSpeicher&lt;/em&gt; zugegriffen wird und erzeugt für jede Parameterkombination eine neue &amp;quot;Instanz&amp;quot; der statischen Klasse. Am Ende gibt es also nicht nur einen, sondern viele &lt;em&gt;KoordinatenSpeicher&lt;/em&gt;, jeder mit eigenen Versionen der internen Listen. Freilich wird im Tutorial in jedem Speicher nur auf die Liste zugegriffen, die den passenden Namen hat. Aber folgendes ist trotzdem möglich:&lt;/p&gt;&lt;em&gt;KoordinatenSpeicher&amp;lt;int, &lt;strong&gt;Zucker&lt;/strong&gt;&amp;gt;.&lt;strong&gt;obst&lt;/strong&gt;Koordinaten.Add(&lt;strong&gt;zucker&lt;/strong&gt;.Id, &lt;strong&gt;zucker&lt;/strong&gt;);&lt;br /&gt;KoordinatenSpeicher&amp;lt;int, &lt;strong&gt;Obst&lt;/strong&gt;&amp;gt;.&lt;strong&gt;zucker&lt;/strong&gt;Koordinaten.Add(&lt;strong&gt;obst&lt;/strong&gt;.Id, &lt;strong&gt;obst&lt;/strong&gt;);&lt;/em&gt; 
&lt;p&gt;Das ganze ist nicht schlimm. Es werden einfach nur ein paar Listen erzeugt, die nie verwendet werden. Und es hat mich einmal zum Nachdenken angeregt und das kann nie schaden. Damit auch Anders Hejlsberg (der Erfinder von C#) wieder gut schlafen kann, schlage ich vor, die Klasse auf eine der folgenden Arten abzuändern. Ich favorisiere stark die zweite Methode, da dort einfach ersichtlicher ist was passiert:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;public static class KoordinatenSpeicher&amp;lt;K,V&amp;gt; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; private static Dictionary&amp;lt;K, V&amp;gt; koordinaten = new Dictionary&amp;lt;K, V&amp;gt;();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; private static Collection&amp;lt;K&amp;gt; geloeschteObjekte = new Collection&amp;lt;K&amp;gt;();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // Properties...&lt;br /&gt;}&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;public static class KoordinatenSpeicher {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; private static Dictionary&amp;lt;int, Zucker&amp;gt; zuckerKoordinaten = new Dictionary&amp;lt;int, Zucker&amp;gt;();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; private static Dictionary&amp;lt;int, Obst&amp;gt; obstKoordinaten = new Dictionary&amp;lt;int, Obst&amp;gt;();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; private static Collection&amp;lt;int&amp;gt; geloeschteObjekte = new Collection&amp;lt;int&amp;gt;();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // Properties...&lt;br /&gt;}&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Was haben wir nun von der ganzen Sache? Zwei Bugs weniger und etwas dabei gelernt :-) Im Anhang zu diesem Blog Eintrag findet ihr ein kleines Demo Projekt, das die Sache anschaulich demonstriert.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;There is no theory of evolution. Just a list of creatures Chuck Norris has allowed to live.&lt;/em&gt;&lt;/p&gt;&lt;img src="http://antme.net/aggbug.aspx?PostID=151" width="1" height="1"&gt;</content><author><name>wolfgang</name><uri>http://antme.net/members/wolfgang.aspx</uri></author></entry><entry><title>XNA und Windows Forms</title><link rel="alternate" type="text/html" href="http://antme.net/blogs/antme2/archive/2007/11/16/xna-und-windows-forms.aspx" /><id>http://antme.net/blogs/antme2/archive/2007/11/16/xna-und-windows-forms.aspx</id><published>2007-11-16T12:06:00Z</published><updated>2007-11-16T12:06:00Z</updated><content type="html">&lt;p&gt;Im XNA Team Blog stand heute die lang erwartete Meldung, dass die Beta von XNA Game Studio 2.0 bald verfügbar sein wird. Leider stand da auch folgendes:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Unfortunately, there were a few things that didn&amp;#39;t make it into this release. The most prominent being that you cannot host XNA applications in Windows Forms.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Den kompletten Beitrag gibt es hier: &lt;a title="http://blogs.msdn.com/xna/archive/2007/11/15/xna-game-studio-2-0-beta-available-soon.aspx" href="http://blogs.msdn.com/xna/archive/2007/11/15/xna-game-studio-2-0-beta-available-soon.aspx"&gt;http://blogs.msdn.com/xna/archive/2007/11/15/xna-game-studio-2-0-beta-available-soon.aspx&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Gerade auf dieses Feature habe ich gewartet. Das hätte die Entwicklung von XNA Visualisierungen um so vieles eleganter gemacht. Eine rudimentäre 3D Visualisierung gibt es schon länger. Die zeigt bisher nur das Spielfeld und Ameisen an, dafür ist die Kamera voll beweglich. In Vorbereitung auf einen XNA Workshop den ich nächste Woche halte habe ich gestern abend eine 2D Visualisierung gebaut und die 3D Version baue ich dafür eventuell auch noch ein wenig aus. Dabei legt XNA mir folgende Steine in den Weg:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ein &lt;em&gt;Microsoft.Framework.Xna.Game&lt;/em&gt; hat eine eigene Ereigniswarteschleife, die über die &lt;em&gt;Run()&lt;/em&gt; Methode synchron ausgeführt wird. Für reine XNA Spiele, die als Hauptfenster laufen, macht das Sinn. Für AntMe! wäre es aber besser ich könnte eine eigene Schleife bauen und die XNA Ereignisbehandlung (Grafik-Hardware!) darin selbst anstoßen.&lt;/li&gt;
&lt;li&gt;Sämtliche Zugriffe auf die &lt;em&gt;Game&lt;/em&gt; Instanz dürfen nur aus dem Thread heraus erfolgen, der die Instanz erzeugt hat. Eine &lt;em&gt;Invoke()&lt;/em&gt; Methode wie bei Windows Forms gibt es nicht.&lt;/li&gt;
&lt;li&gt;Die &lt;em&gt;Game&lt;/em&gt; Instanz verweist auf eine &lt;em&gt;GameWindow&lt;/em&gt; Instanz. Der fehlen sogar auf dem PC so wichtige Methoden wie &lt;em&gt;Show()&lt;/em&gt; und &lt;em&gt;Hide()&lt;/em&gt;. Außerdem wird der Mauscursor ausgeblendet.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Aber man ist ja nicht auf den Kopf gefallen und weiss sich weiterzuhelfen. Inzwischen bin ich so weit, dass ich XNA zwar nicht als Control in eine bestehende Windows Forms Anwendung einbinden kann, aber beide Techniken in einem Fenster, das nicht das Hauptfenster ist, nebeneinander laufen lasse.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Das Erzeugen der &lt;em&gt;Game&lt;/em&gt; Instanz und der Aufruf der &lt;em&gt;Run()&lt;/em&gt; Methode passieren in einer statischen Methode die von einer anderen statischen Methode in einem neuen Thread gestartet und selbst wiederrum von &lt;em&gt;IPlugin.Start()&lt;/em&gt; aufgerufen wird.&lt;/li&gt;
&lt;li&gt;Über &lt;em&gt;Mouse.GetState()&lt;/em&gt; können die Mauskoordinaten ausgelesen werden. Den Cursor zeichne ich einfach selbst als Sprite.&lt;/li&gt;
&lt;li&gt;Die &lt;em&gt;GameWindow&lt;/em&gt; Instanz namens &lt;em&gt;Window&lt;/em&gt; liefert ein &lt;em&gt;Handle&lt;/em&gt;, das benutzt werden kann um eine Windows Forms &lt;em&gt;Form&lt;/em&gt; Instanz für das Spielfenster zu ergeugen.&lt;/li&gt;
&lt;li&gt;Auf dieser Instanz kann dann endlich auch &lt;em&gt;Show()&lt;/em&gt; und &lt;em&gt;Hide()&lt;/em&gt; aufgerufen werden. So kann das XNA Spiel über mehrere AntMe! Spiele hinweg verwendet und dazwischen nur versteckt werden.&lt;/li&gt;
&lt;li&gt;Außerdem können der &lt;em&gt;Form&lt;/em&gt; Instanz wie gewohnt weitere Kontrollelemente hinzugefügt werden. Diese werden über den XNA Hintergrund gezeichnet. Der Bereich in den XNA zeichnet kann über &lt;em&gt;graphics.GraphicsDevice.Viewport&lt;/em&gt; festgelegt werden Auf diese Weise kann dann auch eine XNA Visualisierung die aus der alten 2D-Visualisierung gewohnte Seitenleiste bekommen.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Hier der Code um an das Fenster zu kommen:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Form form = (Form)Form.FromHandle(game.Window.Handle);&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Und hier der Code um das Fenster aus einem anderen Thread heraus zu verstecken:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;if (form != null &amp;amp;&amp;amp; form.IsHandleCreated)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; form.Invoke(new ThreadStart(form.Hide));&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;So, dann werde ich mal meinen Workshop weiter vorbereiten. Viel Spaß mit XNA!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The chief export of Chuck Norris is pain.&lt;/em&gt;&lt;/p&gt;&lt;img src="http://antme.net/aggbug.aspx?PostID=148" width="1" height="1"&gt;</content><author><name>wolfgang</name><uri>http://antme.net/members/wolfgang.aspx</uri></author></entry><entry><title>Schon wieder Listen in .NET und Profiling</title><link rel="alternate" type="text/html" href="http://antme.net/blogs/antme2/archive/2007/11/15/schon-wieder-listen-in-net-und-profiling.aspx" /><id>http://antme.net/blogs/antme2/archive/2007/11/15/schon-wieder-listen-in-net-und-profiling.aspx</id><published>2007-11-15T14:48:00Z</published><updated>2007-11-15T14:48:00Z</updated><content type="html">&lt;p&gt;Ich habe in der MSDN Library eine sehr schöne englische Artikelreihe über Listen in .NET gefunden. Dort wird unter anderem auch bestätigt, was ich mir über die Funktionsweise der Klassen &lt;em&gt;ArrayList&lt;/em&gt; und &lt;em&gt;List&amp;lt;T&amp;gt;&lt;/em&gt; schon gedacht habe. Die Artikel finden sich hier:&lt;/p&gt;
&lt;p&gt;&lt;a title="http://msdn2.microsoft.com/en-us/library/aa287104(VS.71).aspx" href="http://msdn2.microsoft.com/en-us/library/aa287104(VS.71).aspx"&gt;http://msdn2.microsoft.com/en-us/library/aa287104(VS.71).aspx&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Außerdem habe ich mich ein wenig mit Profiling beschäftigt. Jeder Profi wird mir wahrscheinlich gleich auf den Hinterkopf hauen wollen, wenn ich das sage (Denkvermögen anregen und so), aber wir haben AntMe! bisher nie durch einen Profiler gejagt. Ein Profiler ist ein Programm, das kurz gesagt misst, wie oft jede Methode in einem Programm aufgerufen wird, wie lange das dauert und wie viel Speicher dabei verbraucht wird. Ich werde demnächst einen ausführlichen Blog Eintrag über das Profiling schreiben. Jetzt möchte ich aber bei den Listen bleiben und gebe lediglich folgendes bekannt:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In einem Spiel mit den Demo-A-Meisen wird der Befehl &lt;em&gt;List&amp;lt;T&amp;gt;.Clear()&lt;/em&gt; knapp 200 Millionen mal aufgerufen und dessen Ausführung verschlingt über 38% der gesamten Laufzeit des Spiels. Wird zeit die Listen zu optimieren. Schnell.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Beim Leeren der Listen wird durch die komplette Liste iteriert und alle Elemente werden auf &lt;em&gt;null&lt;/em&gt; gesetzt. Das dauert seite Zeit. ich denke darüber nach meine &lt;em&gt;Group&amp;lt;T&amp;gt;&lt;/em&gt; Klasse so zu ändern, dass das unterlassen wird. Damit bleiben die Elemente in der Liste bis sie überschrieben werden. Das wirkt sich dann positiv auf die Geschwindigkeit aber negativ auf den Speicherverbrauch aus. Ich werde damit ein wenig herumspielen und die Ergebnisse dann hier bekannt geben.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Chuck Norris counted to infinity - twice.&lt;/em&gt;&lt;/p&gt;&lt;img src="http://antme.net/aggbug.aspx?PostID=147" width="1" height="1"&gt;</content><author><name>wolfgang</name><uri>http://antme.net/members/wolfgang.aspx</uri></author></entry><entry><title>Codename "Chuck"</title><link rel="alternate" type="text/html" href="http://antme.net/blogs/antme2/archive/2007/11/11/codename-quot-chuck-quot.aspx" /><id>http://antme.net/blogs/antme2/archive/2007/11/11/codename-quot-chuck-quot.aspx</id><published>2007-11-11T17:51:00Z</published><updated>2007-11-11T17:51:00Z</updated><content type="html">&lt;p&gt;Der Codename für AntMe! 2.0 ist ab sofort &amp;quot;Chuck&amp;quot;, benannt&amp;nbsp;nach den &lt;a class="" href="http://www.chucknorrisfacts.com/" target="_blank"&gt;Chuck Norris Facts&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Outer space only exists because it&amp;#39;s afraid to be on the same planet with Chuck Norris.&lt;/em&gt;&lt;/p&gt;&lt;img src="http://antme.net/aggbug.aspx?PostID=139" width="1" height="1"&gt;</content><author><name>wolfgang</name><uri>http://antme.net/members/wolfgang.aspx</uri></author></entry><entry><title>Nocheinmal Listen in .NET</title><link rel="alternate" type="text/html" href="http://antme.net/blogs/antme2/archive/2007/11/05/nocheinmal-listen-in-net.aspx" /><id>http://antme.net/blogs/antme2/archive/2007/11/05/nocheinmal-listen-in-net.aspx</id><published>2007-11-05T10:35:00Z</published><updated>2007-11-05T10:35:00Z</updated><content type="html">&lt;p&gt;Ich habe den Test aus dem letzten Blogbeitrag gestern einmal mit Visual C# 2008 und LINQ durchgeführt. Der verwendete Code sieht für die vorgeschlagene &lt;em&gt;Group&amp;lt;T&amp;gt;&lt;/em&gt;-Klasse etwa folgendermaßen aus:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;var query = from item in list where item != null select item;&lt;br /&gt;foreach (Item item in query)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; item.Value--;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Für die generische Liste und die verkettete Liste fällt natürlich die &lt;em&gt;where&lt;/em&gt;-Klausel weg, aber hier ist der Zwischenschritt über LINQ auch sinnlos. Die &lt;em&gt;ArrayList&lt;/em&gt; unterstützt LINQ nicht. Auf jeden Fall war die &lt;em&gt;foreach&lt;/em&gt;-Iteration über die LINQ-Abfrage in jedem Fall um den Faktor drei bis fünf langsamer als die &lt;em&gt;foreach&lt;/em&gt;-Iteration direkt über die Liste. Das bedeutet nun nicht, daß LINQ keine Daseinsberechtigung hätte, es bedeutet nur, daß ich mir ein anderes Projekt ausdenken muß, um einmal damit herumspielen zu können ;-)&lt;/p&gt;&lt;img src="http://antme.net/aggbug.aspx?PostID=51" width="1" height="1"&gt;</content><author><name>wolfgang</name><uri>http://antme.net/members/wolfgang.aspx</uri></author></entry><entry><title>Listen in .NET</title><link rel="alternate" type="text/html" href="http://antme.net/blogs/antme2/archive/2007/10/28/listen-in-net.aspx" /><link rel="enclosure" type="application/x-zip-compressed" length="44991" href="http://antme.net/blogs/antme2/attachment/50.ashx" /><id>http://antme.net/blogs/antme2/archive/2007/10/28/listen-in-net.aspx</id><published>2007-10-28T19:33:00Z</published><updated>2007-10-28T19:33:00Z</updated><content type="html">&lt;p&gt;&lt;strong&gt;Ich habe mich die letzten beiden Tage ein wenig mit Listen in .NET beschäftigt. In &lt;em&gt;AntMe! 1&lt;/em&gt; habe ich bisher hauptsächlich die generische List Klasse verwendet und die Listen mit dem foreach Konstrukt durchlaufen. Foreach soll ja bekanntlich langsamer sein als for und da &lt;em&gt;AntMe!&lt;/em&gt; sehr viele Listen verwaltet wollte ich einmal sehen wie viel langsamer es ist.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Die einfachste Liste in .NET ist das Array. Da ein Array eine feste Größe hat, in AntMe!&amp;nbsp;aber laufend neue Elemente zu einer Liste hinzugefügt werden können, braucht es ein wenig mehr Logik um eine Liste zu verwalten. Zunächst habe ich mir dazu die ArrayList und List&amp;lt;T&amp;gt; Klassen angeschaut. Der Quelltext des .NET Frameworks wurde zwar noch nicht veröffentlicht, aber wenn man das Verhalten dieser beiden Listen-Typen betrachtet, bekommt man eine gute Vorstellung davon wie sie funktionieren. Prinzipiell funktionieren beide Listen-Typen gleich und unterscheiden sich nur im Typ der enthaltenen Elemente. Elemente können am Ende und an beliebiger Position hinzugefügt und über ihren Index oder ihre Referenz gelöscht werden.&amp;nbsp;Beiden Listen&amp;nbsp;können über einen Indexer wie ein Array angesprochen werden und benutzen intern ein Array zum Speichern der Elemente. Wenn nun ein Element aus der Mitte des internen Arrays gelöscht wird, dann werden die nachfolgenden Elemente nach links aufgerückt. Wenn ein Element in die Mitte eingefügt wird, werden die Elemente ab der Einfügeposition zunächst nach rechts verschoben und dann das neue Element an&amp;nbsp;der Einfügeposition gespeichert. So ist das Array immer lückenlos gefüllt. Bei Bedarf wird das Array vergrößert oder verkleinert&amp;nbsp;wenn es zu wenig Elemente enthält. Das sind&amp;nbsp;wie gesagt alles Vermutungen, bestätigt durch das beobachtete Verhalten.&lt;/p&gt;
&lt;p&gt;Ein Benchmark sollte mir nun zeigen welcher Listen-Typ mit welcher Art des Durchlaufens wie schnell ist. Die Messwerte unterliegen dabei sehr starken Schwankungen. Man weiß nie, wann der Garbage Collector einspringt und was der Computer sonst noch so treibt. Qualitative Aussagen kann ich dennoch machen. Ein Unterschied zwischen einer ArrayList und einer generischen List&amp;lt;T&amp;gt; lässt sich nicht feststellen.&amp;nbsp;Nehmen wir zunächst eine Liste in der Größenordnung von 10.000 Elementen. Wenn wir diese Liste nun 10.000mal durchlaufen, dann braucht ein foreach&amp;nbsp;etwa&amp;nbsp;150% mehr Zeit als ein for. Bei einer Liste mit etwa 1.000.000 Elementen die 100mal durchlaufen wird, ist foreach nur noch etwa 10% langsamer, obwohl nominell die selbe Anzahl an Elementen durchlaufen wurde. Wenn der Compiler auf ein foreach stößt erzeugt er&amp;nbsp;MSIL-Code der ein Iterator-Objekt erzeugt und über Methodenaufrufe auf dem Iterator die Liste durchläuft. Das Erzeugen dieses Iterators kostet offensichtlich Zeit, während die Methodenaufrufe dank Optimierung durch den Compiler (Inlining) recht billig sind. Auch wenn der Code damit nicht mehr ganz so schön aussieht, ist für &lt;em&gt;AntMe! 2&lt;/em&gt; das for-Konstrukt auf jeden Fall empfehlenswert.&lt;/p&gt;
&lt;p&gt;Das Befüllen der Listen wird sehr schnell abgearbeitet und die benötigte Zeit ist vernachlässigbar. Was nicht vernachlässigt werden darf ist das Auffinden von Elementen in der Liste. Wenn zum Beispiel ein Element gelöscht werden soll, dann muß vorher der Array-Index des Elements gefunden werden. Das Löschen unter Angabe eines Index ist damit schneller als das Löschen eines Elements dessen Index nicht bekannt ist. Die Mehrkosten&amp;nbsp;liegen in der Größenordnung&amp;nbsp;um&amp;nbsp;100%&amp;nbsp;bis 1000%. Daraus ergibt sich, daß es für ein Element immer gut ist, seinen Array-Index zu kennen. Durch das wiederholte Verschieben von Teilen des Arrays verändern sich aber die Indizes seiner Elemente.&lt;/p&gt;
&lt;p&gt;Bei genauer Betrachtung haben die beiden Listen-Typen eine Eigenschaft, die ich nicht einfordere und das ist die Reihenfolge-Treue der Elemente. Mir reicht es so gut wie immer, daß ein Element in einer Liste enthalten ist. Seine Position relativ zu den anderen Elemente ist mir aber egal. Nehmen wir wieder ein Array her um die Elemente zu speichern. Genaugenommen haben wir in verwaltetem Code auch gar keine andere Möglichkeit um eine unbekannte Anzahl von Elementen typsicher zu verwalten. Beim Entfernen eines Elements sollen die nachfolgenden Elemente aber nicht aufrücken, sondern eine leere Stelle zurücklassen. Neue Elemente sollen an einer beliebigen freien Position eingefügt werden. Damit das Array nicht bei jedem Einfügen komplett durchlaufen werden muß, um einen freien Index zu finden, soll eine Liste freier Indizes gespeichert werden. Hier darf man natürlich nicht in die Falle treten, dafür einen der betrachteten Listen-Typen zu verwenden. Sämtliche Vorteile der neuen Liste wären dadurch wieder dahin. Am Anfang soll das Array von vorne beginnend aufgefüllt werden&amp;nbsp;und sobald einzelnen Elemente entfernt werden, sollen die frei gewordenen Indizes gespeichert und wiederverwendet werden. Hierfür bietet sich ein Ringpuffer oder ein Stapel (Stack) an. Ich habe&amp;nbsp;eine entsprechende Listen-Klasse implementiert und mit den anderen Listen-Typen verglichen. Für das Speichern der Indizes habe ich einen eigenen Ringpuffer fester Größe benutzt. Das .NET Framework bietet zwar eine Stack-Klasse an, aber ich wollte eine leichtgewichtige Lösung von der ich weiß, was sie tut. Ich werde den Ringpuffer wohl noch durch einen (eigenen)&amp;nbsp;Stapel ersetzen. Der ist einfacher zu verwalten als der Ringpuffer, aber ich habe auf die Schnelle&amp;nbsp;einfach nicht daran gedacht. Beim Iterieren über das Array muß man natürlich dafür sorgen, daß leere Indizes nicht beachtet werden.&lt;/p&gt;
&lt;p&gt;Aussagen über die Geschwindigkeit lassen sich aber auch so treffen. Das Einfügen von neuen Elementen dauert je nach Füllungsgrad des Arrays und Größe des Ringpuffers länger, ist aber weiter vernachlässigbar. Das Iterieren mit for und foreach dauert etwa genau so lange wie bei den vorhandenen Listen-Typen. Das Löschen von Elementen über ihren Index geht unabhängig von der Listen-Größe beinahe in Null-Zeit. Und das Löschen über eine Referenz benötigt grob 85% weniger Zeit als vorher! In &lt;em&gt;AntMe! 2&lt;/em&gt; werde ich diese Klasse auf jeden Fall verwenden. Die Elemente der Liste werden dabei auch ihren Array-Index speichern. Da sich die Indizes nicht verändern müssen in der Beziehung auch keine weiteren Vorkehrungen getroffen werden (wie ein Subject-Observer-Beziehung die den Elementen ihren neuen Index mitteilt). Natürlich kann ein Element in mehreren Listen enthalten sein, so daß man hier aufpassen muß, den gespeicherten Index nur in der richtigen Liste zu verwenden.&lt;/p&gt;
&lt;p&gt;Ich habe auch eine verkettete Liste implementiert um zu sehen wie dieses Konzept sich schlägt. Die Liste ist doppelt verkettet, aber es wird nur die Vorwärts-Referenz verwendet. Das Löschen von Elementen verhält sich verständlicherweise genau umgekehrt wie bei dem eben vorgestellten Listen-Typ. Das Löschen über eine Referenz ist billig und das Löschen über einen Index ist teuer. Das Hinzufügen eines Elements dauert unabhängig von der Listengröße immer gleich lang da es nur aus dem Verbiegen einiger Referenzen besteht. Das Iterieren durch die Liste dauert im Rahmen der Meßungenauigkeit so lange wie bisher. Lohnenswerte Vorteile ergeben sich keine und wenn ein Element in mehreren Listen erhalten sein soll, muß man noch wesentlich mehr Aufwand treiben. Mit verketteten Listen werde ich mich daher zunächst&amp;nbsp;nicht weiter beschäftigen.&lt;/p&gt;
&lt;p&gt;Der verwendete Quelltext findet sich im Anhang dieses Posts. Wenn ihr den Quelltext geladen habt, dann erzeugt einen Release Build und startet ihn mit Strg+F5. Nur so werden alle Compiler-Optimierungen angewandt und das Programm ohne Debug-Code gestartet. Mehr über &lt;em&gt;AntMe! 2&lt;/em&gt; bald in diesem Blog &lt;img src="http://community.antspiel.de/emoticons/emotion-1.gif" alt="Smile" /&gt;&lt;/p&gt;&lt;img src="http://antme.net/aggbug.aspx?PostID=50" width="1" height="1"&gt;</content><author><name>wolfgang</name><uri>http://antme.net/members/wolfgang.aspx</uri></author></entry></feed>