Sometimes you find performance improvements in the simplest places. Last night I improved the time-stepping precision of NTP by a factor of up to a thousand. With a change of less than 20 lines.
The reason I was able to do this is because the NTP code had not caught up to a change in the precision of modern computer clocks. When it was written, you set time with settimeofday(2), which takes a structure containing seconds and microseconds. But modern POSIX-conformant Unixes have a clock_settime(2) which takes a structure containing seconds and nanoseconds.
Internally, NTP represents times to a precision of under a nanosecond. But because the code was built around the old settimeofday(2) call, until last night it rounded to the nearest microsecond too soon, throwing away precision which clock_settime(2) was capable of passing to the system clock.
Once I noticed this it was almost trivial to fix. The round-off only has to happen if your target platform only has settimeofday(2). Moving it into the handler code for that case, and changing one argument-structure declaration, sufficed.
Now, in practice this is not going to yield a full thousand-fold improvement in stepping accuracy, because you can’t get clock sources that accurate. (Well, not unless you’re a national time authority and can afford a cesium-fountain clock.) This change only helps to the extent that your time-server is actually delivering corrections with sub-microsecond accuracy; otherwise those extra bits will be plain noise.
You won’t get that accuracy from a plain GPS, which is seriously wobbly in the 100-millisecond range. Nor from a GPS with 1PPS, which delivers around one microsecond accuracy. But plug in a GPS-conditioned oscillator (GPSDO) and now you’re talking. These commonly have accuracy in about the 100-nanosecond range, so we can expect computing in nanoseconds to actually pass through an order of magnitude in stepping precision.
Pretty good for a 20-line change!
What are our lessons for today?
First…roundoff is insidious. You should always compute at the highest available precision and round off, when you have to, at the latest possible moment. I knew this and had a to-do item in my head to change as many instances of the old struct timeval (microsecond precision) to struct timespec (nanosecond precision) as possible. This is the first place in the NTP code I’ve found that it makes a provable difference. I’ll be hunting others.
Second…you really ought to beat the dust out of your code every couple of years even if it’s working. Because APIs will improve on you, and if you settle for a quick least-effort shim you may throw away significant functional gains without realizing it. A factor of ten is not bupkis, and this one was stupid-easy to collect; I just had to be paying attention. Clearly the NTP Classic maintainers were not.
So, this is my first non-security-related functional improvement in NTP. To be followed by many others, I hope.