11from __future__ import annotations
22
3- import shutil
4- import pickle
53import warnings
6- import tempfile
4+ import platform
75from pathlib import Path
86from tqdm .auto import tqdm
97
8+ from concurrent .futures import ProcessPoolExecutor
9+ import multiprocessing as mp
10+ from threadpoolctl import threadpool_limits
11+
1012import numpy as np
1113
1214from spikeinterface .core .sortinganalyzer import register_result_extension , AnalyzerExtension
@@ -314,11 +316,13 @@ def _run(self, verbose=False, **job_kwargs):
314316 job_kwargs = fix_job_kwargs (job_kwargs )
315317 n_jobs = job_kwargs ["n_jobs" ]
316318 progress_bar = job_kwargs ["progress_bar" ]
319+ max_threads_per_process = job_kwargs ["max_threads_per_process" ]
320+ mp_context = job_kwargs ["mp_context" ]
317321
318322 # fit model/models
319323 # TODO : make parralel for by_channel_global and concatenated
320324 if mode == "by_channel_local" :
321- pca_models = self ._fit_by_channel_local (n_jobs , progress_bar )
325+ pca_models = self ._fit_by_channel_local (n_jobs , progress_bar , max_threads_per_process , mp_context )
322326 for chan_ind , chan_id in enumerate (self .sorting_analyzer .channel_ids ):
323327 self .data [f"pca_model_{ mode } _{ chan_id } " ] = pca_models [chan_ind ]
324328 pca_model = pca_models
@@ -411,12 +415,16 @@ def run_for_all_spikes(self, file_path=None, verbose=False, **job_kwargs):
411415 )
412416 processor .run ()
413417
414- def _fit_by_channel_local (self , n_jobs , progress_bar ):
418+ def _fit_by_channel_local (self , n_jobs , progress_bar , max_threads_per_process , mp_context ):
415419 from sklearn .decomposition import IncrementalPCA
416- from concurrent .futures import ProcessPoolExecutor
417420
418421 p = self .params
419422
423+ if mp_context is not None and platform .system () == "Windows" :
424+ assert mp_context != "fork" , "'fork' mp_context not supported on Windows!"
425+ elif mp_context == "fork" and platform .system () == "Darwin" :
426+ warnings .warn ('As of Python 3.8 "fork" is no longer considered safe on macOS' )
427+
420428 unit_ids = self .sorting_analyzer .unit_ids
421429 channel_ids = self .sorting_analyzer .channel_ids
422430 # there is one PCA per channel for independent fit per channel
@@ -436,13 +444,18 @@ def _fit_by_channel_local(self, n_jobs, progress_bar):
436444 pca = pca_models [chan_ind ]
437445 pca .partial_fit (wfs [:, :, wf_ind ])
438446 else :
439- # parallel
447+ # create list of args to parallelize. For convenience, the max_threads_per_process is passed
448+ # as last argument
440449 items = [
441- (chan_ind , pca_models [chan_ind ], wfs [:, :, wf_ind ]) for wf_ind , chan_ind in enumerate (channel_inds )
450+ (chan_ind , pca_models [chan_ind ], wfs [:, :, wf_ind ], max_threads_per_process )
451+ for wf_ind , chan_ind in enumerate (channel_inds )
442452 ]
443453 n_jobs = min (n_jobs , len (items ))
444454
445- with ProcessPoolExecutor (max_workers = n_jobs ) as executor :
455+ with ProcessPoolExecutor (
456+ max_workers = n_jobs ,
457+ mp_context = mp .get_context (mp_context ),
458+ ) as executor :
446459 results = executor .map (_partial_fit_one_channel , items )
447460 for chan_ind , pca_model_updated in results :
448461 pca_models [chan_ind ] = pca_model_updated
@@ -674,6 +687,12 @@ def _init_work_all_pc_extractor(recording, sorting, all_pcs_args, nbefore, nafte
674687
675688
676689def _partial_fit_one_channel (args ):
677- chan_ind , pca_model , wf_chan = args
678- pca_model .partial_fit (wf_chan )
679- return chan_ind , pca_model
690+ chan_ind , pca_model , wf_chan , max_threads_per_process = args
691+
692+ if max_threads_per_process is None :
693+ pca_model .partial_fit (wf_chan )
694+ return chan_ind , pca_model
695+ else :
696+ with threadpool_limits (limits = int (max_threads_per_process )):
697+ pca_model .partial_fit (wf_chan )
698+ return chan_ind , pca_model
0 commit comments