bitfaster / BitFaster.Caching

High performance, thread-safe in-memory caching primitives for .NET
MIT License
466 stars 28 forks source link

Mitigate torn writes for current time on 32-bit platforms #592

Closed bitfaster closed 4 months ago

bitfaster commented 4 months ago

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

.NET Framework 4.8.1 (4.8.9241.0), X64 RyuJIT VectorSize=256

; BitFaster.Caching.Benchmarks._32bitTImeTest.TimeOriginal()
;             return timeOrig.Last; 
;             ^^^^^^^^^^^^^^^^^^^^^
       mov       rax,[141EBEC8]
       mov       rax,[rax+8]
       ret
; Total bytes of code 13

.NET Framework 4.8.1 (4.8.9241.0), X64 RyuJIT VectorSize=256

; BitFaster.Caching.Benchmarks._32bitTImeTest.Time2()
;             return time.Last; 
;             ^^^^^^^^^^^^^^^^^
       mov       rax,[13B2BED0]
       mov       edx,[rax]
       mov       rax,[rax+8]
       ret
; Total bytes of code 15
coveralls commented 4 months ago

Coverage Status

coverage: 99.13% (+0.003%) from 99.127% when pulling a6b6c38ca63199fdefb3bd3a32f6c7596417d58d on users/alexpeck/ttime into 80ba526a7e8f947544bed9e2afd82c26c2fb46e5 on main.