libs-base icon indicating copy to clipboard operation
libs-base copied to clipboard

Implement NSDate as a small object (tagged pointer) - clang/libobjc2 only

Open hmelder opened this issue 1 year ago • 2 comments

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.

hmelder avatar Oct 10 '24 10:10 hmelder

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.

rfm avatar Oct 16 '24 11:10 rfm

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

hmelder avatar Oct 16 '24 11:10 hmelder