Last night I got a little time to profile the C# implementation of the serialization library that we evaluated in the previous post. With one good optimization and one gross one, I was able to get the C# implementation to execute in less time than the native C++ implementation.
The good optimization was adding a "thicker" method to the buffer manager, thereby allowing us to do a few things at once: grow the buffer if needed, set the new length, and return the buffer, all in one call. The gross optimization involved manually inlining the most commonly used methods, so that a duplicate call could be removed. A call graph may help explain this second optimization:
ClientCode
WriteProperty
WritePropertyHeader
GetAndGrowBuffer
WritePropertyValue
GetAndGrowBuffer
By manually inlining the implementation of WritePropertyHeader and WritePropertyValue inside WriteProperty, one can remove one of the two calls to GetAndGrowBuffer. This change results in "shared" code now being littered across all of the WriteProperty overloads, resulting in a poor tradeoff of maintainability for performance. Only in the most critical situations would I ever tradeoff maintainability for performance.
As a reminder, here are the numbers from before:
Native | 90 ms |
Managed C++ wrapper | 130 ms |
C# rewrite | 230 ms |
The optimized version:
Optimized C# rewrite | 75 ms |
This is now 15% faster than the native implementation! I gave back some of the time to use System.String, rather than hiding the UTF-8 conversion cost like we did last time. That version is roughly 10% slower than native, a very small price to pay:
Optimized C# rewrite, w/String | 100 ms |
This isn't a fair fight, since I did not take time to optimize the native or C++ wrapper implementations. But it does illustrate that performance should not be the deciding factor when making these C++/Managed C++ wrapper/C# rewrite tradeoffs.
-randy
Comments
You can follow this conversation by subscribing to the comment feed for this post.