This is the last post, now I need to clean up some more and will release the tool on the weekend. Update: Not quite the weekend, a lot of small and large things slowing me down…
This article provides some of the things I did to optimize the code of the tool from a C# perspective. As a bit of background, the first version of the tool required a considerable amount of time to find an answer, while the final version just takes some seconds to create the fusion caches after the first query and then can calculate the remaining data very fast. I can’t offer real comparisons for each optimization in terms of less time/memory/…, I will just write down my thoughts behind them.
Effective lookup of Persona
Some information about Persona always has to be looked up while the algorithms are running, for example, whether a certain Persona is in the set of currently available Persona. I decided to associate integers (the internal numbers of the Persona) with the Persona and do a lookup using those, in the hope that this is faster than by comparing object references.
When looking at the algorithms themselves, I realized that pretty much everything that is required for the algorithms can be cached. For efficient lookup into these caches, the Persona number is used again, in order to be able to use simple multi-dimensional arrays. I don’t think the implementation of a sparse array would be needed here.
In my initial posts, I wrote that I simply created lists of lists of Persona. That was of course a huge memory overhead since the information wasn’t really required. I decided that it would be good to have all information required for the algorithms in a list that could be worked on in-place, i.e. which wouldn’t have to be recreated after each iteration. The major problem with this was that I needed to iterate over the elements in the set while items were added, yet these new items shouldn’t be used in calculations. This lead to the ICommitCollection interface, which is basically a collection which only applies changes after the Commit()-function has been called. The implementation then uses the internal Persona number for fast lookup. It could also have been done with a List and some care with indices when inserting new Persona, but it feels somewhat cleaner this way.
Use specialized structs for temporary data
One thing I noticed is that creating List objects for data that isn’t around very long leads to some overhead. For example, I often returned small lists of Persona from functions. If you know the number of Persona, it’s a lot faster to simply create a small struct or generic with enough fields for each Persona. Lists should then only be used for cases where the data will be around for a longer time.
Again looking back at the projekt, I can take two pieces of insight with me: First, optimizing at the algorithmic level is indeed far more efficient than looking at bottlenecks in the code. The thoughts on the algorithmic optimization lead to very substantial speed-ups, while I’m not sure about the efficiency of most code optimizations I did at some point. Which is the second insight: If you do optimize code, make sure (e.g. in a small test application) that your optimization really does optimize the speed/memory footprint/…