For a while, I was writing blog posts around identifing the fastest method for accomplishing particular goals. They can be found on my blog under the "performance stub" tag. This is the repository where I will collect all of the test code along with the framework I used to run the tests.
As I code, I like to make some light notes of alternatives while driving forward with the first implementation that makes it from my brain to my fingers. When I get the chance, I can go back and flesh out the two versions and drop them into some basic Stopwatch timing to determine which is better in terms of raw speed. Factoring those results with clarity of code, I have a method I will likely choose the next time I need the same feature.
Don't take these test results as any end-all answer. These are the numbers I got on some random computer I was using at the time. Feel free to use them as a starting point. If you are really digging into performance, you already know that everything can change once you put the code in question in its natural surroundings (e.g., Windows .NET vs. MonoTouch) or put it under load.
- Intel(R) Core(TM) i7-4850HQ CPU @ 2.30GHz [NOTE: Windows 8.1 running under VMware Fusion]
- Cores: 4
- Current Clock Speed: 2295
- Max Clock Speed: 2295
Average | Method | Ratio |
---|---|---|
0.42 ticks | ObjectAccessorLookup | 12.4X |
5.26 ticks | IDictionaryRouteValueDictionaryLookup | 1.0X |
Average | Method | Ratio |
---|---|---|
0.23 ticks | IDictionaryRouteValueDictionaryLookup | 1.9X |
0.43 ticks | ObjectAccessorLookup | 1.0X |
Average | Method | Ratio |
---|---|---|
0.44 ticks | ObjectAccessorLookup | 13.1X |
5.72 ticks | IDictionaryRouteValueDictionaryLookup | 1.0X |
Average | Method | Ratio |
---|---|---|
44,991.67 ticks | SimpleLoop | 1.6X |
47,769.28 ticks | FirstOrDefaultAs | 1.5X |
61,196.91 ticks | OfTypeFirstOrDefault | 1.2X |
69,718.62 ticks | SelectAsFirstOrDefaultNotNull | 1.0X |
71,449.59 ticks | SelectAsWhereNotNullFirstOrDefault | 1.0X |
Average | Method | Ratio |
---|---|---|
73,176.10 ticks | YieldReturnLoop | 1.4X |
88,929.40 ticks | WhereIsCast | 1.1X |
89,636.05 ticks | OfType | 1.1X |
99,422.73 ticks | SelectAsWhereNotNull | 1.0X |
Average | Method | Ratio |
---|---|---|
4,727.85 ticks | ByteArrayToHexViaLookup32UnsafeDirect | 105.2X |
10,853.96 ticks | ByteArrayToHexViaLookupPerByte | 45.8X |
12,967.69 ticks | ByteArrayToHexViaByteManipulation2 | 38.4X |
16,846.64 ticks | ByteArrayToHexViaByteManipulation | 29.5X |
23,201.23 ticks | ByteArrayToHexViaLookupAndShift | 21.4X |
23,879.41 ticks | ByteArrayToHexViaLookup | 20.8X |
113,269.34 ticks | ByteArrayToHexStringViaBitConverter | 4.4X |
178,601.39 ticks | ByteArrayToHexViaSoapHexBinary | 2.8X |
203,871.66 ticks | ByteArrayToHexStringViaStringBuilderForEachByteToString | 2.4X |
227,942.39 ticks | ByteArrayToHexStringViaStringBuilderAggregateByteToString | 2.2X |
452,639.34 ticks | ByteArrayToHexStringViaStringBuilderForEachAppendFormat | 1.1X |
479,832.66 ticks | ByteArrayToHexStringViaStringBuilderAggregateAppendFormat | 1.0X |
484,575.84 ticks | ByteArrayToHexStringViaStringConcatArrayConvertAll | 1.0X |
497,343.99 ticks | ByteArrayToHexStringViaStringJoinArrayConvertAll | 1.0X |
Average | Method | Ratio |
---|---|---|
0.28 ticks | ByteArrayToHexViaLookup32UnsafeDirect | 99.7X |
0.65 ticks | ByteArrayToHexViaLookupPerByte | 42.7X |
0.70 ticks | ByteArrayToHexViaByteManipulation | 39.5X |
0.73 ticks | ByteArrayToHexViaByteManipulation2 | 37.9X |
1.15 ticks | ByteArrayToHexViaLookup | 23.9X |
1.24 ticks | ByteArrayToHexViaLookupAndShift | 22.3X |
9.98 ticks | ByteArrayToHexStringViaBitConverter | 2.8X |
9.98 ticks | ByteArrayToHexStringViaStringBuilderForEachByteToString | 2.8X |
10.68 ticks | ByteArrayToHexViaSoapHexBinary | 2.6X |
14.27 ticks | ByteArrayToHexStringViaStringBuilderAggregateByteToString | 1.9X |
14.88 ticks | ByteArrayToHexStringViaStringJoinArrayConvertAll | 1.8X |
23.74 ticks | ByteArrayToHexStringViaStringConcatArrayConvertAll | 1.2X |
24.93 ticks | ByteArrayToHexStringViaStringBuilderAggregateAppendFormat | 1.1X |
27.51 ticks | ByteArrayToHexStringViaStringBuilderForEachAppendFormat | 1.0X |
Lookup tables have taken the lead over byte manipulation, especially if you are willing to play in the unsafe realm. Basically, there is some form of precomputing what any given nibble or byte will be in hex. Then, as you rip through the data, you simply look up the next portion to see what hex string it would be. That value is then added to the resulting string output in some fashion. For a long time byte manipulation, potentially harder to read by some developers, was the top-performing approach.
Your best bet is still going to be finding some representative data and trying it out in a production-like environment. If you have different memory constraints, you may prefer a method with fewer allocations to one that would be faster but consume more memory.
If you find something wrong with this stuff or have recommendations for the testing framework, don't hesitate to bring it up. If you have a test idea that would add some value to the world, feel free to write up something that implements IPerformanceTest
and submit it. New issues and pull requests are always welcome. If you submit it, I will assume you don't mind it becoming part of this project and subject to its MIT license.
MIT license. If you do something cool with it, though, I'd love to hear about it.