From 5bcfb8ded7a762567e2a824aacf1686a05382a41 Mon Sep 17 00:00:00 2001 From: Sebastien Pouliot Date: Fri, 27 May 2022 15:12:09 -0400 Subject: [PATCH] [corefoundation] Cache `kCFNull` to avoid native calls Calling `Dlfcn.GetIntPtr` is quite slow and doing for each element of an native array slows them down. **Before** ``` BenchmarkDotNet=v0.12.1, OS=macOS 12.3.1 (21E258) [Darwin 21.4.0] Apple M1 2.40GHz, 1 CPU, 8 logical and 8 physical cores .NET Core SDK= 6.0.100 [/usr/local/share/dotnet/sdk] [Host] : .NET Core 6.0 (CoreCLR 6.0.522.21309, CoreFX 6.0.522.21309), X64 RyuJIT Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=3 LaunchCount=1 WarmupCount=3 ``` | Method | Length | Mean | Error | StdDev | |-------------------- |------- |----------------:|--------------:|-------------:| | ArrayFromHandleFunc | 0 | 140.98 ns | 10.995 ns | 0.603 ns | | ArrayFromHandleFunc | 1 | 650.99 ns | 34.037 ns | 1.866 ns | | ArrayFromHandleFunc | 16 | 6,884.68 ns | 284.458 ns | 15.592 ns | | ArrayFromHandleFunc | 256 | 101,171.27 ns | 675.731 ns | 37.039 ns | | ArrayFromHandleFunc | 4096 | 1,634,001.93 ns | 55,113.362 ns | 3,020.949 ns | **After** ``` BenchmarkDotNet=v0.12.1, OS=macOS 12.3.1 (21E258) [Darwin 21.4.0] Apple M1 2.40GHz, 1 CPU, 8 logical and 8 physical cores .NET Core SDK= 6.0.100 [/usr/local/share/dotnet/sdk] [Host] : .NET Core 6.0 (CoreCLR 6.0.522.21309, CoreFX 6.0.522.21309), X64 RyuJIT Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=3 LaunchCount=1 WarmupCount=3 ``` | Method | Length | Mean | Error | StdDev | |-------------------- |------- |--------------:|-------------:|-----------:| | ArrayFromHandleFunc | 0 | 141.48 ns | 3.905 ns | 0.214 ns | | ArrayFromHandleFunc | 1 | 307.06 ns | 9.457 ns | 0.518 ns | | ArrayFromHandleFunc | 16 | 1,227.28 ns | 13.334 ns | 0.731 ns | | ArrayFromHandleFunc | 256 | 14,076.72 ns | 418.940 ns | 22.963 ns | | ArrayFromHandleFunc | 4096 | 251,251.85 ns | 7,341.339 ns | 402.404 ns | --- src/CoreFoundation/CFArray.cs | 3 +++ src/corefoundation.cs | 2 +- tests/perftest/NativeArrayPerf.cs | 12 ++++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/CoreFoundation/CFArray.cs b/src/CoreFoundation/CFArray.cs index c597ec4e0fe8..15f4ec6a5a51 100644 --- a/src/CoreFoundation/CFArray.cs +++ b/src/CoreFoundation/CFArray.cs @@ -53,6 +53,9 @@ namespace CoreFoundation { // interesting bits: https://github.com/opensource-apple/CF/blob/master/CFArray.c public partial class CFArray : NativeObject { + // this cache the handle instead of issuing a native call + internal static NativeHandle CFNullHandle = _CFNullHandle; + #if !NET internal CFArray (NativeHandle handle) : base (handle, false) diff --git a/src/corefoundation.cs b/src/corefoundation.cs index c454a2ea6a60..1f1d063157b1 100644 --- a/src/corefoundation.cs +++ b/src/corefoundation.cs @@ -33,7 +33,7 @@ interface CFAllocator { interface CFArray { [Internal][Field ("kCFNull")] - IntPtr /* CFNullRef */ CFNullHandle { get; } + IntPtr /* CFNullRef */ _CFNullHandle { get; } } [Partial] diff --git a/tests/perftest/NativeArrayPerf.cs b/tests/perftest/NativeArrayPerf.cs index 1432a3f9e542..7ba8c67418b5 100644 --- a/tests/perftest/NativeArrayPerf.cs +++ b/tests/perftest/NativeArrayPerf.cs @@ -32,5 +32,17 @@ public void Create () var native = CFArray.Create (array); CFString.ReleaseNative (native); // that's a `CFObject.CFRelease` with a null-check } + + int error; + + [Benchmark] + public void ArrayFromHandleFunc () + { + var native = CFArray.Create (array); + var managed = CFArray.ArrayFromHandle (native); + if (managed.Length != array.Length) + error++; + CFString.ReleaseNative (native); // that's a `CFObject.CFRelease` with a null-check + } } }