Skip to content

Commit 219e4e4

Browse files
committed
Improved performance of resample thread
While the resapmle algorithm released the GIL, we did not take advantage of the fact that this meant we could resample multiple channels in parallel. A ThreadPool is now created to resample channels in parallel. The code was also optimised to remove logging lines and reduce the number of calls in `inmain`, thus limiting the contention with pyqtgraph redraws and mouse move events.
1 parent fc4e42a commit 219e4e4

File tree

1 file changed

+44
-29
lines changed

1 file changed

+44
-29
lines changed

runviewer/__main__.py

Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import ast
3434
import pprint
3535
import signal
36+
import concurrent.futures
3637

3738
splash.update_text('importing labscript suite modules')
3839
from labscript_utils.setup_logging import setup_logging
@@ -1226,40 +1227,54 @@ def __resample3(self, x_in, y_in, x_out, stop_time):
12261227
return y_out
12271228

12281229
def _resample_thread(self):
1229-
logger = logging.getLogger('runviewer.resample_thread')
1230-
while True:
1231-
if self._resample:
1232-
self._resample = False
1233-
# print 'resampling'
1234-
ticked_shots = inmain(self.get_selected_shots_and_colours)
1235-
for shot, (colour, shutters_checked) in ticked_shots.items():
1236-
for channel in shot.traces:
1237-
if self.channel_checked_and_enabled(channel):
1238-
try:
1239-
xmin, xmax, dx = self._get_resample_params(channel, shot)
1240-
1241-
# We go a bit outside the visible range so that scrolling
1242-
# doesn't immediately go off the edge of the data, and the
1243-
# next resampling might have time to fill in more data before
1244-
# the user sees any empty space.
1245-
if self.scale_time:
1246-
xnew, ynew = self.resample(shot.scaled_times(channel), shot.traces[channel][1], xmin, xmax, shot.stop_time, dx)
1247-
else:
1248-
xnew, ynew = self.resample(shot.traces[channel][0], shot.traces[channel][1], xmin, xmax, shot.stop_time, dx)
1249-
inmain(self.plot_items[channel][shot].setData, xnew, ynew, pen=pg.mkPen(QColor(colour), width=2), stepMode=True)
1250-
except Exception:
1251-
#self._resample = True
1252-
pass
1253-
else:
1254-
logger.info('ignoring channel %s' % channel)
1255-
time.sleep(0.5)
1230+
# logger = logging.getLogger('runviewer.resample_thread')
1231+
with concurrent.futures.ThreadPoolExecutor(max_workers=os.cpu_count()) as executer:
1232+
while True:
1233+
if self._resample:
1234+
self._resample = False
1235+
channel_data = self.__get_all_resample_params()
1236+
1237+
results = []
1238+
for args in channel_data:
1239+
results.append(executer.submit(self.__pool_resample, *args))
1240+
1241+
# wait for all inmain_later calls from threadpool to finish before we trigger a new resample
1242+
for future in results:
1243+
result = future.result()
1244+
if isinstance(result, Queue):
1245+
result.get()
1246+
1247+
time.sleep(0.1)
1248+
1249+
@inmain_decorator(wait_for_return=True)
1250+
def __get_all_resample_params(self):
1251+
return [(channel, shot, colour, *self._get_resample_params(channel, shot))
1252+
for shot, (colour, shutters_checked) in self.get_selected_shots_and_colours().items()
1253+
for channel in shot.traces
1254+
if self.channel_checked_and_enabled(channel)
1255+
]
1256+
1257+
def __pool_resample(self, channel, shot, colour, xmin, xmax, dx):
1258+
try:
1259+
# We go a bit outside the visible range so that scrolling
1260+
# doesn't immediately go off the edge of the data, and the
1261+
# next resampling might have time to fill in more data before
1262+
# the user sees any empty space.
1263+
if self.scale_time:
1264+
xnew, ynew = self.resample(shot.scaled_times(channel), shot.traces[channel][1], xmin, xmax, shot.stop_time, dx)
1265+
else:
1266+
xnew, ynew = self.resample(shot.traces[channel][0], shot.traces[channel][1], xmin, xmax, shot.stop_time, dx)
1267+
return inmain_later(self.plot_items[channel][shot].setData, xnew, ynew, pen=pg.mkPen(QColor(colour), width=2), stepMode=True)
1268+
except Exception:
1269+
#self._resample = True
1270+
pass
12561271

12571272
@inmain_decorator(wait_for_return=True)
12581273
def channel_checked_and_enabled(self, channel):
1259-
logger.info('is channel %s enabled' % channel)
1274+
# logger.info('is channel %s enabled' % channel)
12601275
index = self.channel_model.index(0, CHANNEL_MODEL__CHANNEL_INDEX)
12611276
indexes = self.channel_model.match(index, Qt.DisplayRole, channel, 1, Qt.MatchExactly)
1262-
logger.info('number of matches %d' % len(indexes))
1277+
# logger.info('number of matches %d' % len(indexes))
12631278
if len(indexes) == 1:
12641279
check_item = self.channel_model.itemFromIndex(indexes[0])
12651280
if check_item.checkState() == Qt.Checked and check_item.isEnabled():

0 commit comments

Comments
 (0)