AfterAccessPolicy and DiscretePolicy both store the current time in a local Time variable to skip repeated calls to get the current time. On x86, this could result in a torn write/bad time value. Instead, use Interlocked read/write on 32bit.
Results are pretty close for x64, 3x penalty for x86 (this is still incredibly cheap, it's 3x slower than reading a register). This is still at least 2x faster than calling the function to get the time.
Method
Jit
Platform
Runtime
Mean
Error
StdDev
Ratio
TimeOriginal
RyuJit
X64
.NET 6.0
0.2273 ns
0.0351 ns
0.0360 ns
1.00
Time2
RyuJit
X64
.NET 6.0
0.2583 ns
0.0089 ns
0.0079 ns
1.16
TimeOriginal
RyuJit
X64
.NET Framework 4.8
0.0000 ns
0.0000 ns
0.0000 ns
?
Time2
RyuJit
X64
.NET Framework 4.8
0.0137 ns
0.0223 ns
0.0239 ns
?
TimeOriginal
LegacyJit
X86
.NET Framework 4.8
2.4370 ns
0.1943 ns
0.1817 ns
1.00
Time2
LegacyJit
X86
.NET Framework 4.8
7.8259 ns
0.1746 ns
0.1548 ns
3.25
Disassembly on X64 shows that assembly is identical for .NET6, and very similar for .NET 4.8, so the JIT is able to elide the branch and this does not introduce any overhead.
.NET 6.0.30 (6.0.3024.21525), X64 RyuJIT AVX2
; BitFaster.Caching.Benchmarks._32bitTImeTest.TimeOriginal()
; return timeOrig.Last;
; ^^^^^^^^^^^^^^^^^^^^^
mov rax,26C40258DE0
mov rax,[rax]
mov rax,[rax+8]
ret
; Total bytes of code 18
.NET 6.0.30 (6.0.3024.21525), X64 RyuJIT AVX2
; BitFaster.Caching.Benchmarks._32bitTImeTest.Time2()
; return time.Last;
; ^^^^^^^^^^^^^^^^^
mov rax,1CCEE588DE8
mov rax,[rax]
mov rax,[rax+8]
ret
; Total bytes of code 18
coverage: 99.13% (+0.003%) from 99.127%
when pulling a6b6c38ca63199fdefb3bd3a32f6c7596417d58d on users/alexpeck/ttime
into 80ba526a7e8f947544bed9e2afd82c26c2fb46e5 on main.
AfterAccessPolicy
andDiscretePolicy
both store the current time in a localTime
variable to skip repeated calls to get the current time. On x86, this could result in a torn write/bad time value. Instead, use Interlocked read/write on 32bit.Results are pretty close for x64, 3x penalty for x86 (this is still incredibly cheap, it's 3x slower than reading a register). This is still at least 2x faster than calling the function to get the time.
Disassembly on X64 shows that assembly is identical for .NET6, and very similar for .NET 4.8, so the JIT is able to elide the branch and this does not introduce any overhead.
.NET 6.0.30 (6.0.3024.21525), X64 RyuJIT AVX2
.NET 6.0.30 (6.0.3024.21525), X64 RyuJIT AVX2
.NET Framework 4.8.1 (4.8.9241.0), X64 RyuJIT VectorSize=256
.NET Framework 4.8.1 (4.8.9241.0), X64 RyuJIT VectorSize=256