Skip to content

Commit 6f336ba

Browse files
committed
Multithreaded waveform data file reads for scopesession loading
1 parent b4b5e7c commit 6f336ba

File tree

6 files changed

+150
-40
lines changed

6 files changed

+150
-40
lines changed

lib

release-notes/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ This is a running list of significant bug fixes and new features since the last
55
## New features since v0.1.1
66

77
* Core: Changed rate limiting sleep in InstrumentThread loop from 10ms to 1ms to avoid bogging down high performance instruments like the ThunderScope
8+
* Core: Scopesession loading now uses multithreaded IO for significant performance gains especially when many channels and deep history are involved
89
* Drivers: ThunderScope now overlaps socket IO and GPU processing of waveforms giving a significant increase in WFM/s rate
910
* Filters: Added GPU acceleration for several filters including CDR PLL (7.5x speedup), 100baseTX (10x speedup), eye pattern (25x speedup), histogram (12x speedup), TIE (5.3x speedup) and more (https://github.com/ngscopeclient/scopehal/issues/977).
1011
* Filters: CDR PLL now outputs the input signal sampled by the recovered clock in a second data stream (https://github.com/ngscopeclient/scopehal/issues/991)

src/ngscopeclient/HistoryManager.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,33 @@ void HistoryManager::LoadEmptyHistoryToSession(Session& session)
214214
}
215215
}
216216

217+
/**
218+
@brief Retroactively modify history for an instrument
219+
220+
This is used during session loading if we find waveforms in certain legacy formats that need to be converted
221+
ex post facto
222+
*/
223+
void HistoryManager::Retcon(shared_ptr<Oscilloscope> scope, size_t chan, size_t stream, WaveformBase* wfm)
224+
{
225+
TimePoint tp(wfm->m_startTimestamp, wfm->m_startFemtoseconds);
226+
227+
for(auto& point : m_history)
228+
{
229+
if(point->m_time != tp)
230+
continue;
231+
232+
//Make sure we have history for this scope
233+
auto jt = point->m_history.find(scope);
234+
if(jt == point->m_history.end())
235+
return;
236+
237+
//Overwrite the waveform.
238+
//Does not free the old one, it's assumed this is done by SetData() on the scope before calling this function
239+
jt->second[StreamDescriptor(scope->GetChannel(chan), stream)] = wfm;
240+
break;
241+
}
242+
}
243+
217244
/**
218245
@brief Adds new data to the history
219246
@@ -262,6 +289,7 @@ void HistoryManager::AddHistory(
262289

263290
//If we already have a history point for the same exact timestamp, do nothing
264291
//Either a bug or we're in append mode
292+
//TODO: when loading multiscope scopesessions, do we want to add stuff here??
265293
if(HasHistory(tp))
266294
return;
267295

src/ngscopeclient/HistoryManager.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* *
33
* ngscopeclient *
44
* *
5-
* Copyright (c) 2012-2024 Andrew D. Zonenberg and contributors *
5+
* Copyright (c) 2012-2026 Andrew D. Zonenberg and contributors *
66
* All rights reserved. *
77
* *
88
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the *
@@ -77,6 +77,8 @@ class HistoryManager
7777

7878
bool OnMemoryPressure(MemoryPressureLevel level, MemoryPressureType type, size_t requestedSize);
7979

80+
void Retcon(std::shared_ptr<Oscilloscope> scope, size_t chan, size_t stream, WaveformBase* wfm);
81+
8082
void AddHistory(
8183
const std::vector<std::shared_ptr<Oscilloscope>>& scopes,
8284
bool deleteOld = true,

src/ngscopeclient/Session.cpp

Lines changed: 115 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -438,11 +438,82 @@ bool Session::LoadWaveformData(int version, const string& dataDir)
438438
}
439439
}
440440

441+
//Third pass: history and refresh filters
442+
double hstart = GetTime();
443+
double cdt = 0;
444+
bool converted = false;
445+
for(auto point : m_history.m_history)
446+
{
447+
point->LoadHistoryToSession(*this);
448+
449+
double cstart = GetTime();
450+
if(ConvertLegacyUniformWaveforms())
451+
converted = true;
452+
cdt += GetTime() - cstart;
453+
454+
RefreshAllFilters();
455+
}
456+
double hdt = GetTime() - hstart;
457+
LogTrace("History replay took %.3f ms\n", hdt * 1000);
458+
if(converted)
459+
LogTrace("Legacy waveform conversion took %.3f ms\n", cdt * 1000);
460+
441461
m_history.SetMaxToCurrentDepth();
442462

443463
return true;
444464
}
445465

466+
/**
467+
@brief Check for any legacy waveforms stored in sparsev1 format that are actually uniform
468+
*/
469+
bool Session::ConvertLegacyUniformWaveforms()
470+
{
471+
bool converted = false;
472+
473+
for(auto scope : m_oscilloscopes)
474+
{
475+
for(size_t i=0; i < scope->GetChannelCount(); i++)
476+
{
477+
auto oc = scope->GetOscilloscopeChannel(i);
478+
if(!oc)
479+
continue;
480+
481+
for(size_t j=0; j<oc->GetStreamCount(); j++)
482+
{
483+
auto data = oc->GetData(j);
484+
if(!data)
485+
continue;
486+
487+
auto sacap = dynamic_cast<SparseAnalogWaveform*>(data);
488+
489+
//Quickly check if the waveform is dense packed, even if it was stored as sparse.
490+
//Since we know samples must be monotonic and non-overlapping, we don't have to check every single one!
491+
if(sacap)
492+
{
493+
int64_t nlast = sacap->size() - 1;
494+
if( (sacap->m_offsets[0] == 0) &&
495+
(sacap->m_offsets[nlast] == nlast) &&
496+
(sacap->m_durations[nlast] == 1) )
497+
{
498+
LogDebug("Found legacy uniform waveform stored on disk as sparse, converting to uniform\n");
499+
500+
//Waveform was actually uniform, so convert it
501+
auto cap = new UniformAnalogWaveform(*sacap);
502+
oc->SetData(cap, j);
503+
504+
//Update history
505+
m_history.Retcon(scope, i, j, cap);
506+
507+
converted = true;
508+
}
509+
}
510+
}
511+
}
512+
}
513+
514+
return converted;
515+
}
516+
446517
/**
447518
@brief Loads waveform data for filters that need to be preserved
448519
*/
@@ -520,7 +591,7 @@ bool Session::LoadWaveformDataForFilters(
520591

521592
//Actually load the waveform
522593
string fname = datdir + "/stream" + to_string(i) + ".bin";
523-
DoLoadWaveformDataForStream(f, i, fmt, fname);
594+
DoLoadWaveformDataForStream(cap, fmt, fname);
524595
}
525596
}
526597

@@ -538,6 +609,7 @@ bool Session::LoadWaveformDataForScope(
538609
{
539610
LogTrace("Loading waveform data for scope \"%s\"\n", scope->m_nickname.c_str());
540611
LogIndenter li;
612+
double start = GetTime();
541613

542614
TimePoint time(0, 0);
543615
TimePoint newest(0, 0);
@@ -563,7 +635,16 @@ bool Session::LoadWaveformDataForScope(
563635
chan->SetData(nullptr, j);
564636
}
565637

566-
//Load the data for each waveform
638+
//Save info for each waveform so we know where to load it from
639+
struct WaveformLoadInfo
640+
{
641+
WaveformBase* wfm;
642+
string format;
643+
string fname;
644+
};
645+
vector<WaveformLoadInfo> waveformsToLoad;
646+
647+
//First pass: Load metadata and allocate waveforms etc
567648
for(auto it : wavenode)
568649
{
569650
//Top level metadata
@@ -706,32 +787,49 @@ bool Session::LoadWaveformDataForScope(
706787
nstream);
707788
}
708789

709-
DoLoadWaveformDataForStream(
710-
scope->GetOscilloscopeChannel(nchan),
711-
nstream,
712-
formats[i],
713-
tmp);
790+
//Figure out what needs to be loaded
791+
WaveformLoadInfo info;
792+
info.wfm = scope->GetOscilloscopeChannel(nchan)->GetData(nstream);
793+
info.format = formats[i];
794+
info.fname = tmp;
795+
waveformsToLoad.push_back(info);
714796
}
715797

798+
//TODO: merge history in multi scope sessions?
716799
vector<shared_ptr<Oscilloscope>> temp;
717800
temp.push_back(scope);
718801
m_history.AddHistory(temp, false, pinned, label);
802+
}
719803

720-
//TODO: this is not good for multiscope
721-
//TODO: handle eye patterns (need to know window size for it to work right)
722-
RefreshAllFilters();
804+
//Second pass: Actually load the waveform data in parallel
805+
#pragma omp parallel for
806+
for(size_t i=0; i<waveformsToLoad.size(); i++)
807+
{
808+
auto info = waveformsToLoad[i];
809+
DoLoadWaveformDataForStream(info.wfm, info.format, info.fname);
723810
}
811+
812+
//Copy it all to the GPU in one go
813+
//For now, use the global transfer queue for this
814+
{
815+
std::lock_guard<std::mutex> lock(g_vkTransferMutex);
816+
g_vkTransferCommandBuffer->begin({});
817+
818+
for(size_t i=0; i<waveformsToLoad.size(); i++)
819+
waveformsToLoad[i].wfm->PrepareForGpuAccessNonblocking(*g_vkTransferCommandBuffer);
820+
821+
g_vkTransferCommandBuffer->end();
822+
g_vkTransferQueue->SubmitAndBlock(*g_vkTransferCommandBuffer);
823+
}
824+
825+
double dt = GetTime() - start;
826+
LogTrace("Scope data loaded in %.3f ms\n", dt * 1000);
827+
724828
return true;
725829
}
726830

727-
void Session::DoLoadWaveformDataForStream(
728-
OscilloscopeChannel* chan,
729-
int stream,
730-
string format,
731-
string fname
732-
)
831+
void Session::DoLoadWaveformDataForStream(WaveformBase* cap, string format, string fname)
733832
{
734-
auto cap = chan->GetData(stream);
735833
auto sacap = dynamic_cast<SparseAnalogWaveform*>(cap);
736834
auto uacap = dynamic_cast<UniformAnalogWaveform*>(cap);
737835
auto sdcap = dynamic_cast<SparseDigitalWaveform*>(cap);
@@ -837,21 +935,6 @@ void Session::DoLoadWaveformDataForStream(
837935
ccap->m_durations[j] = stime[1];
838936
}
839937
}
840-
841-
//Quickly check if the waveform is dense packed, even if it was stored as sparse.
842-
//Since we know samples must be monotonic and non-overlapping, we don't have to check every single one!
843-
int64_t nlast = nsamples - 1;
844-
if(sacap)
845-
{
846-
if( (sacap->m_offsets[0] == 0) &&
847-
(sacap->m_offsets[nlast] == nlast) &&
848-
(sacap->m_durations[nlast] == 1) )
849-
{
850-
//Waveform was actually uniform, so convert it
851-
cap = new UniformAnalogWaveform(*sacap);
852-
chan->SetData(cap, stream);
853-
}
854-
}
855938
}
856939

857940
//Dense packed
@@ -880,7 +963,6 @@ void Session::DoLoadWaveformDataForStream(
880963
}
881964

882965
cap->MarkModifiedFromCpu();
883-
cap->PrepareForGpuAccess();
884966

885967
#ifdef _WIN32
886968
delete[] buf;

src/ngscopeclient/Session.h

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -387,11 +387,8 @@ class Session
387387
int version,
388388
const YAML::Node& node,
389389
const std::string& dataDir);
390-
void DoLoadWaveformDataForStream(
391-
OscilloscopeChannel* chan,
392-
int stream,
393-
std::string format,
394-
std::string fname);
390+
void DoLoadWaveformDataForStream(WaveformBase* cap, std::string format, std::string fname);
391+
bool ConvertLegacyUniformWaveforms();
395392

396393
///@brief Version of the file being loaded
397394
int m_fileLoadVersion;

0 commit comments

Comments
 (0)