Skip to content

Commit 0c98f24

Browse files
committed
ENHANCEMENTS:
- Autodetect P/E-core CPUs and set affinity accordingly
1 parent 5fa9909 commit 0c98f24

File tree

3 files changed

+145
-4
lines changed

3 files changed

+145
-4
lines changed

core/data/impl/serverimpl.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
import tempfile
1313
import traceback
1414

15-
from core.utils.cpu import get_cpus_from_affinity, get_p_core_affinity
16-
1715
if sys.platform == 'win32':
1816
import win32con
1917
import win32gui
@@ -462,10 +460,10 @@ def do_startup(self):
462460
self.set_affinity(self.locals.get('affinity'))
463461
else:
464462
# make sure, we only use P-cores for DCS servers
465-
p_core_affinity = get_p_core_affinity()
463+
p_core_affinity = utils.get_p_core_affinity()
466464
if p_core_affinity:
467465
self.log.info(f" => P/E-Core CPU detected.")
468-
self.set_affinity(get_cpus_from_affinity(get_p_core_affinity()))
466+
self.set_affinity(utils.get_cpus_from_affinity(p_core_affinity))
469467
self.log.info(f" => DCS server starting up with PID {p.pid}")
470468
except Exception:
471469
self.log.error(f" => Error while trying to launch DCS!", exc_info=True)

core/utils/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from .campaigns import *
22
from .coalitions import *
3+
from .cpu import *
34
from .dcs import *
45
from .discord import *
56
from .helper import *

core/utils/cpu.py

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import ctypes
2+
from ctypes import wintypes
3+
4+
# Define ULONG_PTR
5+
if ctypes.sizeof(ctypes.c_void_p) == 8: # 64-bit system
6+
ULONG_PTR = ctypes.c_uint64
7+
else: # 32-bit system
8+
ULONG_PTR = ctypes.c_uint32
9+
10+
# Relation types define the type of processor data returned (for core info, we use RelationProcessorCore)
11+
RelationProcessorCore = 0 # Type used to identify cores
12+
13+
14+
# Define GROUP_AFFINITY structure
15+
class GROUP_AFFINITY(ctypes.Structure):
16+
_fields_ = [
17+
("Mask", ULONG_PTR), # Bitmap for logical processors
18+
("Group", wintypes.WORD),
19+
("Reserved", wintypes.WORD * 3),
20+
]
21+
22+
23+
# Define PROCESSOR_RELATIONSHIP structure
24+
class PROCESSOR_RELATIONSHIP(ctypes.Structure):
25+
_fields_ = [
26+
("Flags", ctypes.c_byte), # Flags to identify the type (P-core, E-core, SMT, etc.)
27+
("EfficiencyClass", ctypes.c_byte), # Efficiency class (higher = P-core, lower = E-core)
28+
("Reserved", ctypes.c_byte * 20),
29+
("GroupCount", wintypes.WORD), # Number of groups
30+
("GroupMask", GROUP_AFFINITY * 1), # Array of group masks
31+
]
32+
33+
34+
# SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX base container
35+
class SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX(ctypes.Structure):
36+
_fields_ = [
37+
("Relationship", wintypes.DWORD), # RelationProcessorCore, RelationCache, etc.
38+
("Size", wintypes.DWORD), # Size of the structure
39+
("Processor", PROCESSOR_RELATIONSHIP), # Embedded processor structure
40+
]
41+
42+
43+
def get_processor_info() -> list[tuple[int, int]]:
44+
ret: list[tuple[int, int]] = []
45+
46+
# Load the kernel32.dll library
47+
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
48+
49+
# Specify the `GetLogicalProcessorInformationEx` function
50+
logical_proc_info_ex = kernel32.GetLogicalProcessorInformationEx
51+
logical_proc_info_ex.restype = wintypes.BOOL
52+
logical_proc_info_ex.argtypes = [wintypes.DWORD, ctypes.c_void_p, ctypes.POINTER(wintypes.DWORD)]
53+
54+
# Determine the size of the buffer
55+
required_size = wintypes.DWORD(0)
56+
if not logical_proc_info_ex(RelationProcessorCore, None, ctypes.byref(required_size)):
57+
if ctypes.get_last_error() != 122: # ERROR_INSUFFICIENT_BUFFER
58+
raise ctypes.WinError(ctypes.get_last_error())
59+
60+
# Allocate the buffer
61+
buffer = ctypes.create_string_buffer(required_size.value)
62+
63+
# Call the function to populate the buffer
64+
if not logical_proc_info_ex(RelationProcessorCore, buffer, ctypes.byref(required_size)):
65+
raise ctypes.WinError(ctypes.get_last_error())
66+
67+
# Parse the buffer
68+
offset = 0
69+
while offset < required_size.value:
70+
info = ctypes.cast(ctypes.byref(buffer, offset),
71+
ctypes.POINTER(SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)).contents
72+
if info.Relationship == RelationProcessorCore:
73+
flags = info.Processor.Flags
74+
efficiency_class = info.Processor.EfficiencyClass
75+
ret.append((efficiency_class, flags))
76+
77+
# Move to the next structure in the buffer
78+
offset += info.Size
79+
80+
return ret
81+
82+
83+
def get_p_core_affinity():
84+
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
85+
logical_proc_info_ex = kernel32.GetLogicalProcessorInformationEx
86+
logical_proc_info_ex.restype = wintypes.BOOL
87+
logical_proc_info_ex.argtypes = [wintypes.DWORD, ctypes.c_void_p, ctypes.POINTER(wintypes.DWORD)]
88+
89+
# Step 1: Retrieve the buffer size required
90+
required_size = wintypes.DWORD(0)
91+
if not logical_proc_info_ex(RelationProcessorCore, None, ctypes.byref(required_size)):
92+
if ctypes.get_last_error() != 122: # ERROR_INSUFFICIENT_BUFFER
93+
raise ctypes.WinError(ctypes.get_last_error())
94+
95+
# Step 2: Allocate the buffer
96+
buffer = ctypes.create_string_buffer(required_size.value)
97+
98+
# Step 3: Populate the buffer with processor information
99+
if not logical_proc_info_ex(RelationProcessorCore, buffer, ctypes.byref(required_size)):
100+
raise ctypes.WinError(ctypes.get_last_error())
101+
102+
# Step 4: Parse the buffer to find all P-cores
103+
affinity_mask = 0
104+
offset = 0
105+
while offset < required_size.value:
106+
info = ctypes.cast(
107+
ctypes.byref(buffer, offset),
108+
ctypes.POINTER(SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)
109+
).contents
110+
111+
if info.Relationship == RelationProcessorCore: # Only handle processor core relationships
112+
efficiency_class = info.Processor.EfficiencyClass
113+
flags = info.Processor.Flags
114+
mask = info.Processor.GroupMask[0].Mask # Logical processors bitmask
115+
116+
# Check if it's a P-core (EfficiencyClass > 0)
117+
if efficiency_class > 0: # Adjust to match your P-core/E-core criteria
118+
affinity_mask |= mask # Add this core's logical processors to the affinity mask
119+
120+
# Move to the next entry in the buffer
121+
offset += info.Size
122+
123+
return affinity_mask
124+
125+
126+
def get_cpus_from_affinity(affinity_mask: int) -> list[int]:
127+
core_ids = []
128+
bit_position = 0
129+
130+
while affinity_mask: # While there are still bits set in the mask
131+
if affinity_mask & 1: # Check if the least significant bit is set
132+
core_ids.append(bit_position)
133+
affinity_mask >>= 1 # Shift the mask to the right to examine the next bit
134+
bit_position += 1
135+
136+
return core_ids
137+
138+
139+
if __name__ == '__main__':
140+
print(get_processor_info())
141+
print(get_p_core_affinity())
142+
print(get_cpus_from_affinity(get_p_core_affinity()))

0 commit comments

Comments
 (0)