Some years ago my then boss was going on vacation. Before he left he asked me to investigate how to improve the performance of a WCF service that was being developed at Microsoft. So I did due diligence and talked with the owners of the system and they mentioned that at some point in the past they had taken profiles and that WCF was spending too much time deserializing headers.
After that I talked with some WCF experts and they mentioned that indeed it could be the case that cracking open the headers to grab/modify a value could take some time. This being a messaging system I figured that was the problem and started working on fixing it.
My solution was (I thought) very clever. This service had a couple of parts. See the attached diagram
The first part was the Frontdoor which received the message from our customers, added some headers (the message would have headers from our customers so we would just append new ones), modified some others and then forwarded it to a different server
The business logic server would then take that message and use the information in the headers that we added to decide what to do with it (persist it, run some procedure, etc).
And the reason why adding and receiving headers can be expensive in WCF is that they use XML so we need to create a valid XML document and deserialize it to grab the property value. So my solution was to allow the front door to set a property instead of a header. Now typically these are not passed through the wire so my extension would take the message properties and append them to the message buffer. Then the business logic layer would have another piece that removes the property from the message buffer and creates a new property with that value. So there was cost of moving data around but it was measurably faster in my micro-benchmark (6-7% faster if I remember correctly).
The project was very fun, I had to read the WCF Framing specification, learn about MultiByteInt31s (unsigned 31 bit integers in a variable length packet), how to create my custom payload so that it would not break WCF and how to insert it efficiently with minimum copying of bytes and with little pressure on the Garbage collector. It took me a couple of weeks of coding and testing it.
All of this was fine until I went back to the team and asked them for an end to end test to validate my improvements.
And that's when I had to face reality. Even though my micro-benchmark showed a measurable improvement in the end to end test the performance gains were not what I expected. I saw less than 2% improvement and profiles showed that the bulk of the CPU was being spent elsewhere.
This really brought home to me the point of measuring before working on a performance improvement. Of course I learned quite a bit about WCF so I would like to think it was not wasted time 🙂
So my recommendations when you are trying to fix the performance of a product are:
- Measure before you start coding.
- Do not trust "word of mouth" data
- Do not trust micro-benchmarks. it's very easy to game them. Even if you wrote them yourself.
- Look at the profile with your own eyes 🙂
So for those interested I have put the code in github. I've tried to comment the code so that it's obvious what each piece is doing. Here are the interesting files (the ones doing the bulk of the work are the first two):
- WrappedEncodingBindingElement - This bindingElement class is the one that actually modifies the MessageBuffer on the WritePath and extracts our property from the MessageBuffer on the ReadPath
- BinaryFormatHelper - This is the Helper class that modifies a Buffer to include the desired payload (AppendPayloadAsHeader). It also verifies if a given buffer includes our payload and if it does it extracts it from the buffer (GetAndRemoveHeaderPayload). It uses bufferManager for pooling our buffers (avoid generating garbage) and it also uses Buffer.BlockCopy to copy data efficiently.
- OutOfBandPayloadProperty - Our custom property. Includes the payload and the name so that we can distinguish it from other message properties
- WcfHelpers - Helpers for creating WCF messages, custom bindings and the test service
- Program.cs - includes 3 tests: EncodeDecodeNoWcf, EncodeDecodeWithWcf and EncodeDecodeWithSecureWcf
Again, learn from my mistake. I would only use something like this if you can measurably proof that you are spending too much time serializing/deserializing headers for your real scenarios.