Implement NSDate as a small object (tagged pointer) - clang/libobjc2 only
Apple implemented __NSTaggedDate around macOS 10.9-10.10 as the new concrete class for NSDate. __NSTaggedDate.
libobjc2 just like Apple's Objective-C runtime implement small object classes, or tagged pointers in the case of Apple's runtime, to store a 60-bit payload and 4-bit metadata in a 64-bit pointer. This avoids constructing a full object on the heap.
NSDate stores the time as a double-precision floating-point number representing the number of seconds since the reference date, the Cocoa epoch (2001-01-01 00:00:00 UTC). This is a 64-bit value. This poses a problem for small object classes, as the time value is too large to fit in the 60-bit payload.
To solve this problem, we look at the range of values that we need to accurately represent reduce the width of the exponent and add a bias. Ideally, this would include dates before distant past and beyond distant future.
I'm using the same algorithm that Apple uses for __NSTaggedDate.
The concept/implementation looks generally good, but I have one reservation: does it actually help to do this? The incentive is clearly to reduce object allocation/deallocation overheads, saving time and space, but we are doing this at with some obvious costs: removing the ability to directly access the double value (adding a method call overhead) and adding a little computation to build the double from the compressed format. It's plausible, but not obvious, that we'd get a real world performance benefit from this optimisation.
We are a bit faster in constructing NSDate objects, but it is not a huge improvement. However, this is just a synthetic benchmark. I've mainly implemented this because Foundation.framework has the same optimisation, trusting Apple that they carefully evaluated this.
#import <Foundation/Foundation.h>
#import <benchmark/benchmark.h>
static void BM_NSDateCurrentTime(benchmark::State& state) {
for (auto _ : state) {
NSDate *date = [NSDate date];
NSTimeInterval i = [date timeIntervalSinceReferenceDate];
[date release];
benchmark::DoNotOptimize(date);
benchmark::DoNotOptimize(i);
}
}
BENCHMARK(BM_NSDateCurrentTime);
BENCHMARK_MAIN();
GSSmallDate:
2024-10-16T13:35:11+02:00
Running ./a.out
Run on (12 X 2000 MHz CPU s)
Load Average: 0.08, 0.03, 0.01
---------------------------------------------------------------
Benchmark Time CPU Iterations
---------------------------------------------------------------
BM_NSDateCurrentTime 86.3 ns 86.3 ns 7417978
NSGDate:
2024-10-16T13:34:44+02:00
Running ./a.out
Run on (12 X 2000 MHz CPU s)
Load Average: 0.04, 0.01, 0.00
---------------------------------------------------------------
Benchmark Time CPU Iterations
---------------------------------------------------------------
BM_NSDateCurrentTime 133 ns 133 ns 5092596