From f686f2f262dcfccb8d943a9eeeb4fcea7f010b00 Mon Sep 17 00:00:00 2001 From: Mark Liversedge Date: Mon, 2 May 2011 10:33:58 +0100 Subject: [PATCH] Ride Statistics performance improvements The metric code for calculating NP was sub-optimal (actually it was pretty crap). This patch improves the performance of the calculation quite substantially (>50% improved). Additionally, the critical durations code has been adjusted to reduce the amount of work for long rides (>3hrs or more). --- src/Coggan.cpp | 56 ++++++++++++++++++++---------------- src/RideFileCache.cpp | 66 ++++++++++++++++--------------------------- src/RideFileCache.h | 8 ++---- 3 files changed, 57 insertions(+), 73 deletions(-) diff --git a/src/Coggan.cpp b/src/Coggan.cpp index c38910050..5b387b042 100644 --- a/src/Coggan.cpp +++ b/src/Coggan.cpp @@ -41,39 +41,45 @@ class NP : public RideMetric { const HrZones *, int, const QHash &, const MainWindow *) { - QVector last30secs; - double secsDelta = ride->recIntSecs(); - if(secsDelta == 0) - return; - double sampsPerWindow = ceil(30.0 / secsDelta); - last30secs.resize(sampsPerWindow + 1); // add 1 just in case + if(ride->recIntSecs() == 0) return; - double total = 0.0; + int rollingwindowsize = 30 / ride->recIntSecs(); + + double total = 0; int count = 0; - foreach(const RideFilePoint *point, ride->dataPoints()) { - if (count < sampsPerWindow) { - last30secs[count] = point->watts; - count++; - } else { + // no point doing a rolling average if the + // sample rate is greater than the rolling average + // window!! + if (rollingwindowsize > 1) { - double rolling = 0.0; - for (int i=0; i rolling(rollingwindowsize); + int index = 0; + double sum = 0; - last30secs.remove(0); - last30secs.resize(sampsPerWindow+1); // add 1 to the end - last30secs[sampsPerWindow-1] = point->watts; - count++; + // loop over the data and convert to a rolling + // average for the given windowsize + for (int i=0; idataPoints().size(); i++) { + + sum += ride->dataPoints()[i]->watts; + sum -= rolling[index]; + + rolling[index] = ride->dataPoints()[i]->watts; + + total += pow(sum/rollingwindowsize,4); // raise rolling average to 4th power + count ++; + + // move index on/round + index = (index >= rollingwindowsize-1) ? 0 : index+1; } } - if (count > sampsPerWindow) - np = pow(total / (count-sampsPerWindow), 0.25); - secs = count * secsDelta; + if (count) { + np = pow(total / (count), 0.25); + secs = count * ride->recIntSecs(); + } else { + np = secs = 0; + } setValue(np); setCount(secs); diff --git a/src/RideFileCache.cpp b/src/RideFileCache.cpp index ef5936b8c..3faa840d2 100644 --- a/src/RideFileCache.cpp +++ b/src/RideFileCache.cpp @@ -230,8 +230,6 @@ void RideFileCache::RideFileCache::compute() computeDistribution(cadDistribution, RideFile::cad); computeDistribution(nmDistribution, RideFile::nm); computeDistribution(kphDistribution, RideFile::kph); - computeDistribution(xPowerDistribution, RideFile::xPower); - computeDistribution(npDistribution, RideFile::NP); // wait for them threads thread1.wait(); @@ -247,7 +245,8 @@ void MeanMaxComputer::run() { // xPower and NP need watts to be present - RideFile::SeriesType baseSeries = (series == RideFile::xPower || series == RideFile::NP) ? RideFile::watts : series; + RideFile::SeriesType baseSeries = (series == RideFile::xPower || series == RideFile::NP) ? + RideFile::watts : series; // only bother if the data series is actually present if (ride->isDataPresent(baseSeries) == false) return; @@ -295,7 +294,7 @@ MeanMaxComputer::run() QVector ride_bests(total_secs + 1); // loop through the decritized data from top - // FIRST 5 MINUTES DO BESTS FOR EVERY SECOND + // FIRST 6 MINUTES DO BESTS FOR EVERY SECOND // WE DO NOT DO THIS FOR NP or xPower SINCE // IT IS WELL KNOWN THAT THEY ARE NOT VALID // FOR SUCH SHORT DURATIONS AND IT IS VERY @@ -344,6 +343,10 @@ MeanMaxComputer::run() // data now to 5s samples - but if the recording interval // is > 5s we won't bother, just set the interval used to // whatever the sample rate is for the device. + // + // NOTE: 5s was chosen since it is a common denominator + // of 25s used for EWMA for xPower and 30s used + // for Normalised Power. QVector downsampled(0); double samplerate; @@ -356,11 +359,11 @@ MeanMaxComputer::run() } else { - // moving to 10s samples is DECREASING the work... + // moving to 5s samples is DECREASING the work... samplerate = 5; - // we are downsampling to 10s - long five=5; // start at 1st 10s sample + // we are downsampling to 5s + long five=5; // start at 1st 5s sample double fivesum=0; int fivecount=0; for (int i=0; i sums(downsampled.size()); int windowsize = slice / samplerate; @@ -475,6 +478,15 @@ MeanMaxComputer::run() ride_bests[slice] = pow((sums.last() / windowsize), 0.25); else ride_bests[slice] = sums.last() / windowsize; + + // increment interval duration we are going to search + // for next, gaps increase as duration increases to + // reduce overall work, since we require far less + // precision as the ride duration increases + if (slice < 3600) slice +=20; // 20s up to one hour + else if (slice < 7200) slice +=60; // 1m up to two hours + else if (slice < 10800) slice += 300; // 5mins up to three hours + else slice += 600; // 10mins after that } // XXX Commented out since it just 'smooths' the drop @@ -497,7 +509,7 @@ MeanMaxComputer::run() #endif // - // FILL IN THE GAPS + // FILL IN THE GAPS AND FILL TARGET ARRAY // // We want to present a full set of bests for // every duration so the data interface for this @@ -506,38 +518,20 @@ MeanMaxComputer::run() // future if some fancy new algorithm arrives // double last = 0; + array.resize(ride_bests.count()); for (int i=ride_bests.size()-1; i; i--) { if (ride_bests[i] == 0) ride_bests[i]=last; else last = ride_bests[i]; - } - - // - // Now copy across into the array passed - // encoding decimal places as we go - array.resize(ride_bests.count()); - for (int i=0; i &array, RideFile::SeriesType series) { - // Derived data series are a special case - if (series == RideFile::xPower) { - computeDistributionXPower(); - return; - } - - if (series == RideFile::NP) { - computeDistributionNP(); - return; - } - // only bother if the data series is actually present if (ride->isDataPresent(series) == false) return; @@ -560,18 +554,6 @@ RideFileCache::computeDistribution(QVector &array, RideFile::Seri } } -void -RideFileCache::computeDistributionNP() {} - -void -RideFileCache::computeDistributionXPower() {} - -void -RideFileCache::computeMeanMaxNP() {} - -void -RideFileCache::computeMeanMaxXPower() {} - // // AGGREGATE FOR A GIVEN DATE RANGE // diff --git a/src/RideFileCache.h b/src/RideFileCache.h index ad8a93951..d1b680148 100644 --- a/src/RideFileCache.h +++ b/src/RideFileCache.h @@ -118,15 +118,11 @@ class RideFileCache void serialize(QDataStream *out); // write to file void compute(); // compute all arrays + + // NOW replaced computeMeanMax with MeanMaxComputer class see bottom of file //void computeMeanMax(QVector&, RideFile::SeriesType); // compute mean max arrays void computeDistribution(QVector&, RideFile::SeriesType); // compute the distributions - // derived values are processed slightly differently - void computeDistributionNP(); - void computeDistributionXPower(); - void computeMeanMaxNP(); - void computeMeanMaxXPower(); - private: