Skip to content

Commit caf363c

Browse files
committed
MSR: Optimize I/O
Do not use 'seek()', use 'pread()' and 'pwrite()' instead. Signed-off-by: Artem Bityutskiy <artem.bityutskiy@linux.intel.com>
1 parent 4c32775 commit caf363c

3 files changed

Lines changed: 215 additions & 110 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Versioning practices: [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
88
### Added
99
### Removed
1010
### Changed
11+
- Greatly improve operations by improvein MSR I/O speed.
1112

1213
## [1.6.25] - 2026-03-03
1314
### Fixed
@@ -930,4 +931,4 @@ Versioning practices: [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
930931

931932
## [1.1.0] - 2021-10-29
932933
### Changed
933-
- pepc: first release.
934+
- pepc: first release.

pepclibs/msr/MSR.py

Lines changed: 75 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# -*- coding: utf-8 -*-
22
# vim: ts=4 sw=4 tw=100 et ai si
33
#
4-
# Copyright (C) 2020-2025 Intel Corporation
4+
# Copyright (C) 2020-2026 Intel Corporation
55
# SPDX-License-Identifier: BSD-3-Clause
66
#
77
# Authors: Artem Bityutskiy <artem.bityutskiy@linux.intel.com>
@@ -14,6 +14,7 @@
1414

1515
from __future__ import annotations # Remove when switching to Python 3.10+.
1616

17+
import os
1718
import typing
1819
import pprint
1920
from pathlib import Path
@@ -67,14 +68,14 @@ class MSR(_SimpleMSR.SimpleMSR):
6768
6869
1. Multi-CPU I/O.
6970
- 'read()' - read an MSR.
70-
- 'read_bits()' - read an MSR bits range.
71-
- 'write()' - write to the an MSR.
72-
- 'write_bits()' - write MSR bits range.
71+
- 'read_bits()' - read an MSR bits range.
72+
- 'write()' - write to an MSR.
73+
- 'write_bits()' - write MSR bits range.
7374
2. Single-CPU I/O.
7475
- 'read_cpu()' - read an MSR.
75-
- 'read_cpu_bits()' - read an MSR bits range.
76-
- 'write_cpu()' - write to the an MSR.
77-
- 'write_cpu_bits()' - write MSR bits range.
76+
- 'read_cpu_bits()' - read an MSR bits range.
77+
- 'write_cpu()' - write to an MSR.
78+
- 'write_cpu_bits()' - write MSR bits range.
7879
3. Transactions support.
7980
- 'start_transaction()' - start a transaction.
8081
- 'flush_transaction()' - flush the transaction buffer.
@@ -101,7 +102,7 @@ def __init__(self,
101102
process manager will be used.
102103
enable_cache: If True, enable caching of MSR values. The first read fetches from
103104
hardware, subsequent reads return the cached value. Writes update the
104-
cache and and propagate to hardware immediately (write-through policy). If
105+
cache and propagate to hardware immediately (write-through policy). If
105106
False, caching is disabled, and every read/write operation accesses the
106107
hardware directly.
107108
"""
@@ -117,12 +118,10 @@ def __init__(self,
117118
# make sure writes go to all CPUs, not just one CPU in the scope.
118119
self._enable_scope = False
119120

121+
# The write-through per-CPU MSR values cache.
120122
self._cache = _PerCPUCache.PerCPUCache(cpuinfo, enable_cache=self._enable_cache,
121123
enable_scope=self._enable_scope)
122-
self._cache._cpuinfo = cpuinfo
123124

124-
# The write-through per-CPU MSR values cache.
125-
self._cache = _PerCPUCache.PerCPUCache(cpuinfo, enable_cache=self._enable_cache)
126125
# The transaction buffer. This is a dictionary of dictionaries, where the first key is the
127126
# CPU number, and the second key is the MSR address. The value is a dictionary with
128127
# transaction value and additional information.
@@ -139,7 +138,7 @@ def close(self):
139138

140139
super().close()
141140

142-
def _add_for_transation(self,
141+
def _add_for_transaction(self,
143142
regaddr: int,
144143
regval: int,
145144
cpu: int,
@@ -157,9 +156,6 @@ def _add_for_transation(self,
157156
iosname: The I/O scope name associated with the MSR.
158157
"""
159158

160-
if not self._enable_cache:
161-
raise Error("Transactions support requires caching to be enabled")
162-
163159
if cpu not in self._transaction_buffer:
164160
self._transaction_buffer[cpu] = {}
165161

@@ -171,7 +167,7 @@ def _add_for_transation(self,
171167
f" old: {tinfo['iosname']}, new: {iosname}")
172168
if "verify" in tinfo and tinfo["verify"] != verify:
173169
raise Error(f"BUG: Inconsistent verification flag value for MSR {regaddr:#x}:\n"
174-
f" old: {tinfo['iosname']}, new: {iosname}")
170+
f" old: {tinfo['verify']}, new: {verify}")
175171
else:
176172
tinfo = self._transaction_buffer[cpu][regaddr] = {}
177173

@@ -186,7 +182,7 @@ def start_transaction(self):
186182
When a transaction is active, all writes to MSRs are buffered and only written to hardware
187183
upon calling 'commit_transaction()' or 'flush_transaction()'. Writes to the same MSR are
188184
merged into a single operation to minimize I/O overhead. Transactions do not provide
189-
atomicity or rollback; they are intended solely for optimizing I/O by batching and merging
185+
atomicity or rollback. They are intended solely for optimizing I/O by batching and merging
190186
writes.
191187
"""
192188

@@ -226,26 +222,25 @@ def _verify(self, regaddr: int, regval: int, cpus: list[int], iosname: ScopeName
226222
f"{self._pman.hostmsg}:\n Wrote '{regval:#x}', read back "
227223
f"'{new_val:#x}'", cpu=cpu, expected=regval, actual=new_val)
228224

229-
def _transaction_write(self):
230-
"""Write the contents of the transaction buffer to MSRs."""
225+
def _transaction_write_local(self):
226+
"""Write MSR transactions on a local host."""
231227

232228
for cpu, cpus_info in self._transaction_buffer.items():
233-
# Write all the dirty data.
234229
path = Path(f"/dev/cpu/{cpu}/msr")
235-
with self._pman.open(path, "r+b") as fobj:
230+
fd = os.open(path, os.O_RDWR)
231+
try:
236232
for regaddr, regval_info in cpus_info.items():
237233
regval = regval_info["regval"]
238234
try:
239-
fobj.seek(regaddr)
240235
regval_bytes = regval.to_bytes(self.regbytes, byteorder=_CPU_BYTEORDER)
241-
fobj.write(regval_bytes)
242-
fobj.flush()
243-
_LOG.debug("CPU%d: Commit MSR 0x%x: Wrote 0x%x%s",
244-
cpu, regaddr, regval, self._pman.hostmsg)
245-
except Error as err:
236+
os.pwrite(fd, regval_bytes, regaddr)
237+
except OSError as err:
246238
raise Error(f"Failed to write '{regval:#x}' to MSR '{regaddr:#x}' of CPU "
247-
f"{cpu}{self._pman.hostmsg} (file '{path}'):\n"
248-
f"{err.indent(2)}") from err
239+
f"{cpu}{self._pman.hostmsg} (file '{path}'): {err}") from err
240+
_LOG.debug("CPU%d: Commit MSR 0x%x: Wrote 0x%x%s",
241+
cpu, regaddr, regval, self._pman.hostmsg)
242+
finally:
243+
os.close(fd)
249244

250245
def _transaction_write_remote(self):
251246
"""
@@ -265,20 +260,42 @@ def _transaction_write_remote(self):
265260
transaction_buffer_str = transaction_buffer_str.replace("'", "\"")
266261

267262
cmd = f"""{python_path} -c '
263+
import os
268264
transaction_buffer = {transaction_buffer_str}
269265
for cpu, cpus_info in transaction_buffer.items():
270266
path = "/dev/cpu/%d/msr" % cpu
271-
with open(path, "r+b") as fobj:
267+
fd = os.open(path, os.O_RDWR)
268+
try:
272269
for regaddr, regval_info in cpus_info.items():
273270
regval = regval_info["regval"]
274-
fobj.seek(regaddr)
275271
regval_bytes = regval.to_bytes({self.regbytes}, byteorder="{_CPU_BYTEORDER}")
276-
fobj.write(regval_bytes)
277-
fobj.flush()
272+
os.pwrite(fd, regval_bytes, regaddr)
273+
finally:
274+
os.close(fd)
278275
'"""
279276

280277
self._pman.run_verify(cmd)
281278

279+
def _transaction_write_emulation(self):
280+
"""Write MSR transactions on an emulated host."""
281+
282+
for cpu, cpus_info in self._transaction_buffer.items():
283+
path = Path(f"/dev/cpu/{cpu}/msr")
284+
with self._pman.open(path, "r+b") as fobj:
285+
for regaddr, regval_info in cpus_info.items():
286+
regval = regval_info["regval"]
287+
try:
288+
fobj.seek(regaddr)
289+
regval_bytes = regval.to_bytes(self.regbytes, byteorder=_CPU_BYTEORDER)
290+
fobj.write(regval_bytes)
291+
fobj.flush()
292+
except Error as err:
293+
raise Error(f"Failed to write '{regval:#x}' to MSR '{regaddr:#x}' of CPU "
294+
f"{cpu}{self._pman.hostmsg} (file '{path}'):\n"
295+
f"{err.indent(2)}") from err
296+
_LOG.debug("CPU%d: Commit MSR 0x%x: Wrote 0x%x%s",
297+
cpu, regaddr, regval, self._pman.hostmsg)
298+
282299
def flush_transaction(self) -> bool:
283300
"""
284301
Flush the transaction buffer and write all buffered data to the MSRs.
@@ -303,8 +320,10 @@ def flush_transaction(self) -> bool:
303320

304321
if self._pman.is_remote:
305322
self._transaction_write_remote()
323+
elif isinstance(self._pman, EmulProcessManager.EmulProcessManager):
324+
self._transaction_write_emulation()
306325
else:
307-
self._transaction_write()
326+
self._transaction_write_local()
308327

309328
# Form a temporary dictionary for verifying the contents of the MSRs written to by the
310329
# transaction.
@@ -336,7 +355,7 @@ def commit_transaction(self):
336355
Commit the current MSR transaction by flushing all buffered data to the MSRs and closing the
337356
transaction.
338357
339-
This method does not provide atomicity guarantees; it is intended as an optimization to
358+
This method does not provide atomicity guarantees. It is intended as an optimization to
340359
reduce the number of MSR I/O operations.
341360
"""
342361

@@ -374,10 +393,9 @@ def _read_remote(self,
374393
yield from super()._cpus_read_remote(regaddr, cpus)
375394
return
376395

377-
378396
# CPU numbers to read the MSR for (subset of 'cpus').
379397
do_read = []
380-
# CPU numbers the MSR should not be read for. Instead, the MSR values for these CPU numbres
398+
# CPU numbers the MSR should not be read for. Instead, the MSR values for these CPU numbers
381399
# are available from the cache, or will be available from the cache when a sibling CPU from
382400
# 'do_read' is read.
383401
dont_read = set()
@@ -394,8 +412,7 @@ def _read_remote(self,
394412
do_read.append(cpu)
395413
continue
396414

397-
# Read the MSR only for 'iosname' sibling CPU, because MSR value should be the same
398-
# for the siblings.
415+
# Read only one CPU per 'iosname' scope, because sibling CPUs share the same MSR value.
399416
for sibling in self._cpuinfo.get_cpu_siblings(cpu, iosname):
400417
if sibling == cpu:
401418
do_read.append(sibling)
@@ -457,7 +474,7 @@ def read(self,
457474
cpus = self._cpuinfo.normalize_cpus(cpus)
458475
yield from self._read(regaddr, cpus, iosname)
459476

460-
def read_cpu(self, regaddr, cpu, iosname="CPU"):
477+
def read_cpu(self, regaddr: int, cpu: int, iosname: ScopeNameType = "CPU") -> int:
461478
"""
462479
Read an MSR value from a specific CPU.
463480
@@ -470,11 +487,10 @@ def read_cpu(self, regaddr, cpu, iosname="CPU"):
470487
The value read from the specified MSR on the given CPU.
471488
"""
472489

473-
regval = None
474490
for _, regval in self.read(regaddr, cpus=(cpu,), iosname=iosname):
475-
pass
491+
return regval
476492

477-
return regval
493+
raise Error(f"Failed to read MSR 0x{regaddr:x} from CPU {cpu}{self._pman.hostmsg}")
478494

479495
def read_bits(self,
480496
regaddr: int,
@@ -487,8 +503,8 @@ def read_bits(self,
487503
Args:
488504
regaddr: Address of the MSR to read bits from.
489505
bits: A tuple or list of two integers (msb, lsb) specifying the bit range to extract
490-
from the MSR; msb is the most significant bit and lsb is the least significant
491-
bit.
506+
from the MSR, where msb is the most significant bit and lsb is the least
507+
significant bit.
492508
cpus: CPU numbers to read the MSR from. Special value 'all' means "all CPUs".
493509
iosname: Scope name for the MSR (e.g. "package", "core"). This is used for
494510
optimizing the read operation by skipping unnecessary reads of sibling CPUs.
@@ -500,10 +516,10 @@ def read_bits(self,
500516
"""
501517

502518
for cpu, regval in self.read(regaddr, cpus, iosname=iosname):
519+
val = self.get_bits(regval, bits)
503520
_LOG.debug("CPU%d: MSR 0x%x: Bits %s: Read 0x%x%s", cpu, regaddr,
504-
":".join([str(bit) for bit in bits]), self.get_bits(regval, bits),
505-
self._pman.hostmsg)
506-
yield (cpu, self.get_bits(regval, bits))
521+
":".join([str(bit) for bit in bits]), val, self._pman.hostmsg)
522+
yield cpu, val
507523

508524
def read_cpu_bits(self,
509525
regaddr: int,
@@ -516,8 +532,8 @@ def read_cpu_bits(self,
516532
Args:
517533
regaddr: Address of the MSR to read from.
518534
bits: A tuple or list of two integers (msb, lsb) specifying the bit range to extract
519-
from the MSR; msb is the most significant bit and lsb is the least significant
520-
bit.
535+
from the MSR, where msb is the most significant bit and lsb is the least
536+
significant bit.
521537
cpu: CPU number to read the MSR from.
522538
iosname: Scope name for the MSR (e.g. "package", "core").
523539
@@ -578,8 +594,7 @@ def _write_remote(self,
578594
do_write.append(cpu)
579595
continue
580596

581-
# Write the MSR only on 'iosname' sibling CPU, because MSR value should be the same
582-
# for the siblings.
597+
# Write only one CPU per 'iosname' scope, because sibling CPUs share the same MSR value.
583598
for sibling in self._cpuinfo.get_cpu_siblings(cpu, iosname):
584599
if sibling == cpu:
585600
do_write.append(sibling)
@@ -627,12 +642,11 @@ def _write(self,
627642
if not self._in_transaction:
628643
super().cpu_write(regaddr, regval, cpu)
629644
else:
630-
self._add_for_transation(regaddr, regval, cpu, verify, iosname)
645+
self._add_for_transaction(regaddr, regval, cpu, verify, iosname)
631646

632-
# Note, below 'add()' call is scope-aware. It will cache 'regval' not only for CPU
633-
# number 'cpu', but also for all the 'iosname' siblings. For example, if 'iosname' is
634-
# "package", 'regval' will be cached for all CPUs in the package that contains CPU
635-
# number 'cpu'.
647+
# The '_cache.add()' call below is scope-aware: it caches 'regval' not only for CPU
648+
# 'cpu', but also for all its 'iosname' siblings. For example, if 'iosname' is
649+
# "package", 'regval' is cached for all CPUs in the package containing CPU 'cpu'.
636650
self._cache.add(regaddr, cpu, regval, sname=iosname)
637651

638652
# In case of an ongoing transaction, skip the verification, it'll be done at the end of the
@@ -702,8 +716,8 @@ def write_bits(self,
702716
703717
Args:
704718
regaddr: The address of the MSR to write to.
705-
bits: A tuple or list of two integers (msb, lsb) specifying the bit range to to write
706-
to; msb is the most significant bit and lsb is the least significant bit.
719+
bits: A tuple or list of two integers (msb, lsb) specifying the bit range to write to;
720+
msb is the most significant bit and lsb is the least significant bit.
707721
val: The value to write to the specified bits range.
708722
cpus: CPU numbers to write the MSR on. Special value 'all' means "all CPUs".
709723
iosname: I/O scope name for the MSR address (e.g., "package", "core"). Used for
@@ -755,8 +769,8 @@ def write_cpu_bits(self,
755769
756770
Args:
757771
regaddr: The address of the MSR to write to.
758-
bits: A tuple or list of two integers (msb, lsb) specifying the bit range to to write
759-
to; msb is the most significant bit and lsb is the least significant bit.
772+
bits: A tuple or list of two integers (msb, lsb) specifying the bit range to write to;
773+
msb is the most significant bit and lsb is the least significant bit.
760774
val: The value to write to the specified bits range.
761775
cpu: CPU number to write the MSR on.
762776
iosname: I/O scope name for the MSR address (e.g., "package", "core"). Used for

0 commit comments

Comments
 (0)