An allegory of an optimization story
I got a call today from a team mate about a piece of software that we wrote. We initially planned it to work with data sizes of hundreds to thousands of records, and considering that we wrote that one in a day, I considered it a great success. We didn’t pay one bit of attention to optimization, but the perf was great for what we needed.
However, a new requirement came up which require us to handle hundred thousand records, and our software was working… well under the new constraints. As a matter of fact, it would take about 0.4 seconds to compute a result when the data size was hundred thousand records. That was probably well within acceptable range, but it had me worried, because 0.4 seconds may be acceptable for this single computation, but it was a CPU bound computation, and it was probably going to kill us when we start hammering the server with multiple requests for that.
Moreover, adding 0.4 to the clients of the system would add an unacceptable delay. So we sat down with dotTrace and tried to figure out what we were doing that could be better.
We spent several hours on that. At some point, a simple extraction of a variable from a loop reduced 7% of the total execution time, but we weren’t really interesting in the small fish. We soon identified a O(N) operation in the code, which was taking about 20% of the total time of the calculation, and focused on that.
By changing the way that we worked with this, we changed this from an O(N) operation to O(1). We did it by introducing indexes and doing intersects of those indexes. The usual choice of time vs. memory has saved us again.
Current time for computing a result? 20 – 50 ms, and that include network time.
I think that I’ll find that acceptable.
Comments
Well, all my applications start as true OO (reusable and maintable) code. So every code sniplet that's used more than once is put in a method. However when the 'unit' is finished we get to the profiling part. Sometimes you can really speed up if you make your code a little less true OO. Some methods that are called recursively are copied (the method body I mean) into loop.
Just as you optimize your database with strategic denormalization.
Most of my application are network oriented and as objects can't really travel across machines at some point the OO objects have to be serialized. The service that's closest to the database (and actually retrieving and updating the database) is actually mixing up some presentation and data. Why you might ask? Well, our software also have to play nice with other runtimes like PHP or Java. So binary serialization make thing very difficult and often that leaves XML serialization. I save a lot of time to make my select queries using the 'for xml explicit' clause. That means I have no o/r wrapper and no (de)serialization wrapper. I just get from the database direct the serialized (soap) XML ready to send over the network.
This helped me out. I was working on which had "acceptable" latency, but still slower than I would like. I ran .Trace and found a bottleneck. it was an OO solution to calculate the hashcode for equality using attributes. I removed the attribute method and used the manual gethashcode override. I am really surprised by the performance increase! ganted it means overriding the hashcode is all my domain objects, but that's acceptable considering the performance boost and minimal number of domain entities in my domain.
I'm sure i could find a way to keep the attributes and increase performance using some form of static entity/propertyinfo cache, but the manual override works.
thank you :)
Comment preview