Skip to content

Commit c204cf1

Browse files
committed
try fix arm testing
1 parent 3f74746 commit c204cf1

5 files changed

Lines changed: 128 additions & 47 deletions

File tree

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System;
2+
using System.Threading;
3+
4+
namespace ProcessorLatencyTool.Helpers.LatencyTester;
5+
6+
public class AtomicBoolean
7+
{
8+
private int _value;
9+
10+
public AtomicBoolean(bool initialValue = false)
11+
{
12+
_value = initialValue ? 1 : 0;
13+
}
14+
15+
public bool Value
16+
{
17+
get => Interlocked.CompareExchange(ref _value, 0, 0) != 0;
18+
set => Interlocked.Exchange(ref _value, value ? 1 : 0);
19+
}
20+
21+
public bool CompareExchange(ref bool comparand, bool value)
22+
{
23+
int expected = comparand ? 1 : 0;
24+
int desired = value ? 1 : 0;
25+
int original = Interlocked.CompareExchange(ref _value, desired, expected);
26+
comparand = original != 0;
27+
return original == expected;
28+
}
29+
}

ProcessorLatencyTool/Helpers/LatencyTester/LatencyTesterBase.cs

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
using System.Threading;
2-
using System;
1+
using System;
32
using System.Collections.Generic;
4-
using System.Linq;
3+
using System.Diagnostics;
54
using System.Runtime.InteropServices;
5+
using System.Runtime.Versioning;
6+
using System.Threading;
67
using System.Threading.Tasks;
78
using ProcessorLatencyTool.Models;
89

910
namespace ProcessorLatencyTool.Helpers.LatencyTester;
1011

11-
public abstract class LatencyTesterBase
12+
public abstract partial class LatencyTesterBase
1213
{
1314
private const int CacheLineSize = 64;
1415
private const int NumSamples = 1000;
@@ -21,10 +22,56 @@ private struct CacheLineAlignedBool
2122
public bool Value;
2223
}
2324

25+
[LibraryImport("arm64_registers", EntryPoint = "read_cntvct_el0", SetLastError = true)]
26+
[SupportedOSPlatform("linux")]
27+
[SupportedOSPlatform("osx")]
28+
private static partial ulong ReadCntvctEl0();
29+
30+
[LibraryImport("arm64_registers", EntryPoint = "read_cntfrq_el0", SetLastError = true)]
31+
[SupportedOSPlatform("linux")]
32+
[SupportedOSPlatform("osx")]
33+
private static partial ulong ReadCntfrqEl0();
34+
2435
protected abstract void SetThreadAffinity(int core);
2536
protected abstract void SetThreadPriority();
26-
protected abstract ulong GetCurrentTimer();
27-
protected abstract double GetTimerPeriodNs();
37+
protected abstract void SetThreadQoS();
38+
39+
protected virtual ulong GetCurrentTimer()
40+
{
41+
if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
42+
{
43+
try
44+
{
45+
return ReadCntvctEl0();
46+
}
47+
catch
48+
{
49+
return (ulong)Stopwatch.GetTimestamp();
50+
}
51+
}
52+
return (ulong)Stopwatch.GetTimestamp();
53+
}
54+
55+
protected virtual double GetTimerPeriodNs()
56+
{
57+
if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
58+
{
59+
try
60+
{
61+
var freq = ReadCntfrqEl0();
62+
if (freq == 0)
63+
{
64+
return 1_000_000_000.0 / Stopwatch.Frequency;
65+
}
66+
return 1_000_000_000.0 / freq;
67+
}
68+
catch
69+
{
70+
return 1_000_000_000.0 / Stopwatch.Frequency;
71+
}
72+
}
73+
return 1_000_000_000.0 / Stopwatch.Frequency;
74+
}
2875

2976
public LatencyResult MeasureLatencyBetweenCores(int coreA, int coreB)
3077
{
@@ -37,6 +84,7 @@ public LatencyResult MeasureLatencyBetweenCores(int coreA, int coreB)
3784
{
3885
SetThreadAffinity(coreB);
3986
SetThreadPriority();
87+
SetThreadQoS();
4088
barrier.SignalAndWait();
4189

4290
var value = false;
@@ -50,22 +98,23 @@ public LatencyResult MeasureLatencyBetweenCores(int coreA, int coreB)
5098

5199
SetThreadAffinity(coreA);
52100
SetThreadPriority();
101+
SetThreadQoS();
53102
barrier.SignalAndWait();
54103

55104
var value = true;
56105
for (var sample = 0; sample < NumSamples; sample++)
57106
{
58107
var start = GetCurrentTimer();
59-
108+
60109
for (var trip = 0; trip < NumRoundTrips; trip++)
61110
{
62111
while (Volatile.Read(ref ownedByPong.Value) != value) { }
63112
Volatile.Write(ref ownedByPing.Value, value);
64113
value = !value;
65114
}
66-
115+
67116
var end = GetCurrentTimer();
68-
117+
69118
var duration = (end - start) * GetTimerPeriodNs();
70119
results.Add(duration / NumRoundTrips / 2.0); // Divide by 2 for one-way latency
71120
}
@@ -101,7 +150,7 @@ public static LatencyTesterBase Create()
101150
{
102151
return new MacOsLatencyTester();
103152
}
104-
153+
105154
throw new PlatformNotSupportedException("Current platform is not supported.");
106155
}
107156
}

ProcessorLatencyTool/Helpers/LatencyTester/LinuxLatencyTester.cs

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ public sealed unsafe partial class LinuxLatencyTester : LatencyTesterBase
1212
[LibraryImport("libc", EntryPoint = "sched_setaffinity", SetLastError = true)]
1313
private static partial int SchedSetAffinity(int pid, int cpusetsize, UIntPtr* mask);
1414

15+
[LibraryImport("libc", EntryPoint = "sched_setscheduler", SetLastError = true)]
16+
private static partial int SchedSetScheduler(int pid, int policy, ref SchedParam param);
17+
18+
[StructLayout(LayoutKind.Sequential)]
19+
private struct SchedParam
20+
{
21+
public int sched_priority;
22+
}
23+
1524
protected override void SetThreadAffinity(int core)
1625
{
1726
var mask = new UIntPtr(1UL << core);
@@ -24,16 +33,17 @@ protected override void SetThreadAffinity(int core)
2433

2534
protected override void SetThreadPriority()
2635
{
27-
Thread.CurrentThread.Priority = ThreadPriority.Highest;
28-
}
29-
30-
protected override ulong GetCurrentTimer()
31-
{
32-
return (ulong)Stopwatch.GetTimestamp();
36+
var param = new SchedParam { sched_priority = 99 }; // SCHED_FIFO with highest priority
37+
var result = SchedSetScheduler(0, 1, ref param); // 1 = SCHED_FIFO
38+
if (result != 0)
39+
{
40+
throw new Exception($"Failed to set thread priority: {Marshal.GetLastWin32Error()}");
41+
}
3342
}
3443

35-
protected override double GetTimerPeriodNs()
44+
protected override void SetThreadQoS()
3645
{
37-
return 1_000_000_000.0 / Stopwatch.Frequency;
46+
// Linux uses SCHED_FIFO for real-time scheduling
47+
SetThreadPriority();
3848
}
39-
}
49+
}

ProcessorLatencyTool/Helpers/LatencyTester/MacOsLatencyTester.cs

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ public sealed unsafe partial class MacOsLatencyTester : LatencyTesterBase
1818
[LibraryImport("libSystem.dylib", EntryPoint = "pthread_setname_np")]
1919
private static partial int pthread_setname_np(byte* name);
2020

21+
[LibraryImport("libSystem.dylib", EntryPoint = "pthread_set_qos_class_self_np")]
22+
private static partial int pthread_set_qos_class_self_np(int qos_class, int relative_priority);
23+
2124
[LibraryImport("arm64_registers", EntryPoint = "set_realtime_policy")]
2225
private static partial int set_realtime_policy();
2326

@@ -39,7 +42,7 @@ private void SetThreadName(string name)
3942
var threadNameBytes = new byte[64];
4043
var nameBytes = System.Text.Encoding.UTF8.GetBytes(name);
4144
Array.Copy(nameBytes, threadNameBytes, Math.Min(nameBytes.Length, 63));
42-
45+
4346
fixed (byte* namePtr = threadNameBytes)
4447
{
4548
pthread_setname_np(namePtr);
@@ -111,22 +114,9 @@ protected override void SetThreadPriority()
111114
// This is handled in SetThreadAffinity
112115
}
113116

114-
protected override ulong GetCurrentTimer()
117+
protected override void SetThreadQoS()
115118
{
116-
if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
117-
{
118-
return ReadCntvctEl0();
119-
}
120-
return (ulong)Stopwatch.GetTimestamp();
121-
}
122-
123-
protected override double GetTimerPeriodNs()
124-
{
125-
if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
126-
{
127-
var freq = ReadCntfrqEl0();
128-
return 1.0 / freq * 1_000_000_000.0;
129-
}
130-
return 1_000_000_000.0 / Stopwatch.Frequency;
119+
// QOS_CLASS_USER_INTERACTIVE = 0x21
120+
pthread_set_qos_class_self_np(0x21, 0);
131121
}
132122
}
Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
using System;
2-
using System.Diagnostics;
32
using System.Runtime.InteropServices;
43
using System.Runtime.Versioning;
5-
using System.Threading;
64

75
namespace ProcessorLatencyTool.Helpers.LatencyTester;
86

@@ -15,6 +13,10 @@ public sealed partial class WindowsLatencyTester : LatencyTesterBase
1513
[LibraryImport("kernel32.dll", EntryPoint = "SetThreadAffinityMask")]
1614
private static partial UIntPtr SetThreadAffinityMask(IntPtr hThread, UIntPtr dwThreadAffinityMask);
1715

16+
[LibraryImport("kernel32.dll", EntryPoint = "SetThreadPriority")]
17+
[return: MarshalAs(UnmanagedType.Bool)]
18+
private static partial bool SetThreadPriority(IntPtr hThread, int nPriority);
19+
1820
protected override void SetThreadAffinity(int core)
1921
{
2022
var mask = new UIntPtr(1UL << core);
@@ -28,16 +30,17 @@ protected override void SetThreadAffinity(int core)
2830

2931
protected override void SetThreadPriority()
3032
{
31-
Thread.CurrentThread.Priority = ThreadPriority.Highest;
32-
}
33-
34-
protected override ulong GetCurrentTimer()
35-
{
36-
return (ulong)Stopwatch.GetTimestamp();
33+
var handle = GetCurrentThread();
34+
if (!SetThreadPriority(handle, 15)) // THREAD_PRIORITY_TIME_CRITICAL
35+
{
36+
throw new Exception($"Failed to set thread priority: {Marshal.GetLastWin32Error()}");
37+
}
3738
}
3839

39-
protected override double GetTimerPeriodNs()
40+
protected override void SetThreadQoS()
4041
{
41-
return 1_000_000_000.0 / Stopwatch.Frequency;
42+
// Windows doesn't have a direct equivalent to QoS classes
43+
// We use the highest thread priority instead
44+
SetThreadPriority();
4245
}
43-
}
46+
}

0 commit comments

Comments
 (0)