Skip to content

Commit 22f6b7c

Browse files
Reuse the MEF session across reads
_read_segment_file built a fresh MefSession on every call. Parsing the session metadata takes ~38 ms on the 194-channel testing recording and does not depend on the requested range, so a short window cost as much as reading the whole file: get_data(start=0, stop=100, picks=[one channel]) was 39.0 ms, of which 38.0 ms was metadata and 0.02 ms was data. Caching it in _raw_extras, which _ReadSegmentFileProtector already exposes: full read (194ch x 98000) 490.5 -> 451.5 ms 1.1x 1000 samples, all channels 47.2 -> 7.2 ms 6.6x 1000 samples, 4 channels 39.7 -> 0.19 ms 206x 100 samples, 1 channel 39.0 -> 0.07 ms 590x 100 epochs of 2 s 4895.6 -> 914.5 ms 5.4x The epoching row is the one that matters in practice -- every windowed read paid the full metadata parse, so epoching, plotting and scrolling were all dominated by it. MefSession and RawMef are both picklable, so caching in _raw_extras keeps raw.copy(), copy.deepcopy() and pickling (n_jobs) working; all four are checked to return bit-identical data, as are crop and preload=True. Not fixed here: the remaining 451 ms of a full read is pymef decoding channel by channel. pymef does not release the GIL -- reading the channels across 4-14 threads measured 0.99x -- so that floor cannot be moved from MNE.
1 parent 3e783e6 commit 22f6b7c

1 file changed

Lines changed: 17 additions & 3 deletions

File tree

mne/io/mef/mef.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -233,11 +233,9 @@ def __init__(self, fname, password="", *, preload=False, verbose=None):
233233

234234
def _read_segment_file(self, data, idx, fi, start, stop, cals, mult):
235235
"""Read a chunk of raw data."""
236-
from pymef.mef_session import MefSession
237-
238236
extras = self._raw_extras[fi]
239237
ch_names = extras["ch_names"]
240-
session = MefSession(str(self._filenames[fi]), extras.get("password", ""))
238+
session = _get_session(self._filenames[fi], extras)
241239

242240
ch_indices = (
243241
range(*idx.indices(extras["n_channels"])) if isinstance(idx, slice) else idx
@@ -257,6 +255,22 @@ def _read_segment_file(self, data, idx, fi, start, stop, cals, mult):
257255
_mult_cal_one(data, block_out, idx, cals, mult)
258256

259257

258+
def _get_session(fname, extras):
259+
"""Open a MEF session, reusing the one already opened for this file.
260+
261+
Parsing the session metadata costs ~38 ms on a 194-channel recording and
262+
does not depend on the requested range, so opening it per read makes a short
263+
window cost as much as reading the whole recording.
264+
"""
265+
session = extras.get("session")
266+
if session is None:
267+
from pymef.mef_session import MefSession
268+
269+
session = MefSession(str(fname), extras.get("password", ""))
270+
extras["session"] = session
271+
return session
272+
273+
260274
@verbose
261275
def read_raw_mef(
262276
fname: Path | str,

0 commit comments

Comments
 (0)