@@ -22,6 +22,93 @@ private struct CacheLineAlignedBool
2222 public bool Value ;
2323 }
2424
25+ // macOS specific constants
26+ private const int QOS_CLASS_USER_INTERACTIVE = 0x21 ;
27+ private const int THREAD_AFFINITY_POLICY = 4 ;
28+ private const int THREAD_AFFINITY_POLICY_COUNT = 1 ;
29+
30+ // macOS thread affinity policy structure
31+ [ StructLayout ( LayoutKind . Sequential ) ]
32+ private struct thread_affinity_policy_data_t
33+ {
34+ public int affinity_tag ;
35+ }
36+
37+ // macOS processor set APIs
38+ [ LibraryImport ( "libSystem.dylib" , EntryPoint = "processor_set_default" ) ]
39+ private static partial int processor_set_default (
40+ int host ,
41+ ref int pset ) ;
42+
43+ [ LibraryImport ( "libSystem.dylib" , EntryPoint = "host_processor_set_priv" ) ]
44+ private static partial int host_processor_set_priv (
45+ int host ,
46+ int pset ,
47+ ref int pset_priv ) ;
48+
49+ [ LibraryImport ( "libSystem.dylib" , EntryPoint = "host_self" ) ]
50+ private static partial int host_self ( ) ;
51+
52+ [ LibraryImport ( "libSystem.dylib" , EntryPoint = "thread_assign" ) ]
53+ private static partial int thread_assign (
54+ int thread ,
55+ int pset ) ;
56+
57+ // macOS specific P/Invoke declarations
58+ [ LibraryImport ( "libSystem.dylib" , EntryPoint = "pthread_set_qos_class_self_np" ) ]
59+ private static partial int pthread_set_qos_class_self_np ( int qos_class , int relative_priority ) ;
60+
61+ [ LibraryImport ( "libSystem.dylib" , EntryPoint = "thread_policy_set" ) ]
62+ private static partial int thread_policy_set (
63+ int thread ,
64+ int policy ,
65+ thread_affinity_policy_data_t * policy_info ,
66+ int count ) ;
67+
68+ [ LibraryImport ( "libSystem.dylib" , EntryPoint = "mach_thread_self" ) ]
69+ private static partial int MachThreadSelf ( ) ;
70+
71+ // Native ARM64 register access
72+ [ LibraryImport ( "arm64_registers" , EntryPoint = "read_tpidr_el0" ) ]
73+ private static partial ulong ReadTpidrEl0 ( ) ;
74+
75+ [ LibraryImport ( "arm64_registers" , EntryPoint = "read_cntvct_el0" ) ]
76+ private static partial ulong ReadCntvctEl0 ( ) ;
77+
78+ [ LibraryImport ( "arm64_registers" , EntryPoint = "read_cntfrq_el0" ) ]
79+ private static partial ulong ReadCntfrqEl0 ( ) ;
80+
81+ private static ulong GetCurrentCore ( )
82+ {
83+ if ( RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) &&
84+ RuntimeInformation . ProcessArchitecture == Architecture . Arm64 )
85+ {
86+ return ReadTpidrEl0 ( ) ;
87+ }
88+ return 0 ;
89+ }
90+
91+ private static ulong GetCurrentTimer ( )
92+ {
93+ if ( RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) &&
94+ RuntimeInformation . ProcessArchitecture == Architecture . Arm64 )
95+ {
96+ return ReadCntvctEl0 ( ) ;
97+ }
98+ return ( ulong ) Stopwatch . GetTimestamp ( ) ;
99+ }
100+
101+ private static double GetTimerPeriodNs ( )
102+ {
103+ if ( RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) &&
104+ RuntimeInformation . ProcessArchitecture == Architecture . Arm64 )
105+ {
106+ var freq = ReadCntfrqEl0 ( ) ;
107+ return 1.0 / freq * 1_000_000_000.0 ;
108+ }
109+ return 1_000_000_000.0 / Stopwatch . Frequency ;
110+ }
111+
25112 public static LatencyResult MeasureLatencyBetweenCores ( int coreA , int coreB )
26113 {
27114 var barrier = new Barrier ( 2 ) ;
@@ -32,7 +119,14 @@ public static LatencyResult MeasureLatencyBetweenCores(int coreA, int coreB)
32119 var pongTask = Task . Run ( ( ) =>
33120 {
34121 SetThreadAffinity ( coreB ) ;
35- Thread . CurrentThread . Priority = ThreadPriority . Highest ;
122+ if ( RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) )
123+ {
124+ pthread_set_qos_class_self_np ( QOS_CLASS_USER_INTERACTIVE , 0 ) ;
125+ }
126+ else
127+ {
128+ Thread . CurrentThread . Priority = ThreadPriority . Highest ;
129+ }
36130 barrier . SignalAndWait ( ) ;
37131
38132 var value = false ;
@@ -45,21 +139,33 @@ public static LatencyResult MeasureLatencyBetweenCores(int coreA, int coreB)
45139 } ) ;
46140
47141 SetThreadAffinity ( coreA ) ;
48- Thread . CurrentThread . Priority = ThreadPriority . Highest ;
142+ if ( RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) )
143+ {
144+ pthread_set_qos_class_self_np ( QOS_CLASS_USER_INTERACTIVE , 0 ) ;
145+ }
146+ else
147+ {
148+ Thread . CurrentThread . Priority = ThreadPriority . Highest ;
149+ }
49150 barrier . SignalAndWait ( ) ;
50151
51152 var value = true ;
52153 for ( var sample = 0 ; sample < NumSamples ; sample ++ )
53154 {
54- var start = Stopwatch . GetTimestamp ( ) ;
155+ var start = RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) ?
156+ GetCurrentTimer ( ) : ( ulong ) Stopwatch . GetTimestamp ( ) ;
157+
55158 for ( var trip = 0 ; trip < NumRoundTrips ; trip ++ )
56159 {
57160 while ( Volatile . Read ( ref ownedByPong . Value ) != value ) { }
58161 Volatile . Write ( ref ownedByPing . Value , value ) ;
59162 value = ! value ;
60163 }
61- var end = Stopwatch . GetTimestamp ( ) ;
62- var duration = ( end - start ) * ( 1_000_000_000.0 / Stopwatch . Frequency ) ;
164+
165+ var end = RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) ?
166+ GetCurrentTimer ( ) : ( ulong ) Stopwatch . GetTimestamp ( ) ;
167+
168+ var duration = ( end - start ) * GetTimerPeriodNs ( ) ;
63169 results . Add ( duration / NumRoundTrips / 2.0 ) ; // Divide by 2 for one-way latency
64170 }
65171
@@ -159,14 +265,87 @@ private static void SetThreadAffinityLinux(int core)
159265 [ SupportedOSPlatform ( "osx" ) ]
160266 private static void SetThreadAffinityMacOs ( int core )
161267 {
162- var result = thread_policy_set (
163- MachThreadSelf ( ) ,
164- ThreadAffinityPolicy ,
165- & core ,
166- ThreadAffinityPolicyCount ) ;
167- if ( result != 0 )
268+ try
269+ {
270+ var thread = MachThreadSelf ( ) ;
271+ var host = host_self ( ) ;
272+ var pset = 0 ;
273+ var pset_priv = 0 ;
274+
275+ // Get the default processor set
276+ var result = processor_set_default ( host , ref pset ) ;
277+ if ( result != 0 )
278+ {
279+ // If we can't get processor set access, try a simpler approach
280+ var policy = new thread_affinity_policy_data_t { affinity_tag = core } ;
281+ result = thread_policy_set (
282+ thread ,
283+ THREAD_AFFINITY_POLICY ,
284+ & policy ,
285+ THREAD_AFFINITY_POLICY_COUNT ) ;
286+ if ( result != 0 )
287+ {
288+ Console . WriteLine ( $ "Warning: Could not set thread affinity. The application may need to be run with sudo for accurate measurements.") ;
289+ return ; // Continue without affinity rather than throwing
290+ }
291+ return ;
292+ }
293+
294+ // Get privileged access to the processor set
295+ result = host_processor_set_priv ( host , pset , ref pset_priv ) ;
296+ if ( result != 0 )
297+ {
298+ // Fall back to simple affinity policy
299+ var policy = new thread_affinity_policy_data_t { affinity_tag = core } ;
300+ result = thread_policy_set (
301+ thread ,
302+ THREAD_AFFINITY_POLICY ,
303+ & policy ,
304+ THREAD_AFFINITY_POLICY_COUNT ) ;
305+ if ( result != 0 )
306+ {
307+ Console . WriteLine ( $ "Warning: Could not set thread affinity. The application may need to be run with sudo for accurate measurements.") ;
308+ return ; // Continue without affinity rather than throwing
309+ }
310+ return ;
311+ }
312+
313+ // Assign thread to processor set
314+ result = thread_assign ( thread , pset_priv ) ;
315+ if ( result != 0 )
316+ {
317+ // Fall back to simple affinity policy
318+ var policy = new thread_affinity_policy_data_t { affinity_tag = core } ;
319+ result = thread_policy_set (
320+ thread ,
321+ THREAD_AFFINITY_POLICY ,
322+ & policy ,
323+ THREAD_AFFINITY_POLICY_COUNT ) ;
324+ if ( result != 0 )
325+ {
326+ Console . WriteLine ( $ "Warning: Could not set thread affinity. The application may need to be run with sudo for accurate measurements.") ;
327+ return ; // Continue without affinity rather than throwing
328+ }
329+ return ;
330+ }
331+
332+ // Set thread affinity policy
333+ var finalPolicy = new thread_affinity_policy_data_t { affinity_tag = core } ;
334+ result = thread_policy_set (
335+ thread ,
336+ THREAD_AFFINITY_POLICY ,
337+ & finalPolicy ,
338+ THREAD_AFFINITY_POLICY_COUNT ) ;
339+ if ( result != 0 )
340+ {
341+ Console . WriteLine ( $ "Warning: Could not set thread affinity. The application may need to be run with sudo for accurate measurements.") ;
342+ return ; // Continue without affinity rather than throwing
343+ }
344+ }
345+ catch ( Exception ex )
168346 {
169- throw new Exception ( $ "Failed to set thread affinity: { result } ") ;
347+ Console . WriteLine ( $ "Warning: Error setting thread affinity: { ex . Message } ") ;
348+ // Continue without affinity rather than throwing
170349 }
171350 }
172351
@@ -178,17 +357,4 @@ private static void SetThreadAffinityMacOs(int core)
178357
179358 [ LibraryImport ( "libc" , EntryPoint = "sched_setaffinity" , SetLastError = true ) ]
180359 private static partial int SchedSetAffinity ( int pid , int cpusetsize , UIntPtr * mask ) ;
181-
182- [ LibraryImport ( "libSystem.dylib" , EntryPoint = "thread_policy_set" ) ]
183- private static partial int thread_policy_set (
184- int thread ,
185- int policy ,
186- int * policy_info ,
187- int count ) ;
188-
189- private const int ThreadAffinityPolicy = 4 ;
190- private const int ThreadAffinityPolicyCount = 1 ;
191-
192- [ LibraryImport ( "libSystem.dylib" , EntryPoint = "mach_thread_self" ) ]
193- private static partial int MachThreadSelf ( ) ;
194360}
0 commit comments