Skip to content

Commit a42999b

Browse files
committed
fix macOS test
1 parent 017aefd commit a42999b

6 files changed

Lines changed: 319 additions & 38 deletions

File tree

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,4 +360,6 @@ MigrationBackup/
360360
.ionide/
361361

362362
# Fody - auto-generated XML schema
363-
FodyWeavers.xsd
363+
FodyWeavers.xsd
364+
/.idea
365+
/ProcessorLatencyTool/Native/build

ProcessorLatencyTool/Helpers/HighPrecisionLatencyTester.cs

Lines changed: 191 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
cmake_minimum_required(VERSION 3.10)
2+
project(arm64_registers)
3+
4+
# Set C standard
5+
set(CMAKE_C_STANDARD 11)
6+
set(CMAKE_C_STANDARD_REQUIRED ON)
7+
8+
# Set output directories
9+
if(APPLE)
10+
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_INSTALL_PREFIX})
11+
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_INSTALL_PREFIX})
12+
endif()
13+
14+
# Add library
15+
add_library(arm64_registers SHARED
16+
arm64_registers.c
17+
)
18+
19+
# Set output name
20+
if(APPLE)
21+
set_target_properties(arm64_registers PROPERTIES
22+
PREFIX "lib"
23+
SUFFIX ".dylib"
24+
OUTPUT_NAME "arm64_registers"
25+
)
26+
endif()
27+
28+
# Installation
29+
install(TARGETS arm64_registers
30+
LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}
31+
RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}
32+
)
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#include <stdint.h>
2+
3+
#ifdef __aarch64__
4+
5+
uint64_t read_tpidr_el0(void) {
6+
uint64_t value;
7+
__asm__ volatile(
8+
"isb \n"
9+
"mrs %0, TPIDR_EL0 \n"
10+
"isb \n"
11+
: "=r"(value)
12+
);
13+
return value;
14+
}
15+
16+
uint64_t read_cntvct_el0(void) {
17+
uint64_t value;
18+
__asm__ volatile(
19+
"isb \n"
20+
"mrs %0, CNTVCT_EL0 \n"
21+
"isb \n"
22+
: "=r"(value)
23+
);
24+
return value;
25+
}
26+
27+
uint64_t read_cntfrq_el0(void) {
28+
uint64_t value;
29+
__asm__ volatile(
30+
"isb \n"
31+
"mrs %0, CNTFRQ_EL0 \n"
32+
"isb \n"
33+
: "=r"(value)
34+
);
35+
return value;
36+
}
37+
38+
#else
39+
40+
uint64_t read_tpidr_el0(void) {
41+
return 0;
42+
}
43+
44+
uint64_t read_cntvct_el0(void) {
45+
return 0;
46+
}
47+
48+
uint64_t read_cntfrq_el0(void) {
49+
return 0;
50+
}
51+
52+
#endif

0 commit comments

Comments
 (0)