Avoid heap allocations in Median class

Create a MedianDouble class and V2 version of BlockSizeManager, which
use a fixed size array of double (since we always use 7 elements
to calculate the median anyway).

Change-Id: Ife90b90336a9a8c037b90726dee4cd2a1b8b6cd9
Reviewed-by: Marc Mutz <marc.mutz@kdab.com>
This commit is contained in:
Tobias Koenig 2016-01-19 17:45:42 +01:00
parent c3850dd636
commit 24d851dcd2
3 changed files with 145 additions and 1 deletions

View File

@ -182,6 +182,58 @@ int BlockSizeManager::blockSize()
return m_blockSize;
}
/*! \internal
*/
BlockSizeManagerV2::BlockSizeManagerV2(int iterationCount)
: maxBlockSize(iterationCount / (QThreadPool::globalInstance()->maxThreadCount() * 2)),
beforeUser(0), afterUser(0),
m_blockSize(1)
{ }
// Records the time before user code.
void BlockSizeManagerV2::timeBeforeUser()
{
if (blockSizeMaxed())
return;
beforeUser = getticks();
controlPartElapsed.addValue(elapsed(beforeUser, afterUser));
}
// Records the time after user code and adjust the block size if we are spending
// to much time in the for control code compared with the user code.
void BlockSizeManagerV2::timeAfterUser()
{
if (blockSizeMaxed())
return;
afterUser = getticks();
userPartElapsed.addValue(elapsed(afterUser, beforeUser));
if (controlPartElapsed.isMedianValid() == false)
return;
if (controlPartElapsed.median() * TargetRatio < userPartElapsed.median())
return;
m_blockSize = qMin(m_blockSize * 2, maxBlockSize);
#ifdef QTCONCURRENT_FOR_DEBUG
qDebug() << QThread::currentThread() << "adjusting block size" << controlPartElapsed.median() << userPartElapsed.median() << m_blockSize;
#endif
// Reset the medians after adjusting the block size so we get
// new measurements with the new block size.
controlPartElapsed.reset();
userPartElapsed.reset();
}
int BlockSizeManagerV2::blockSize()
{
return m_blockSize;
}
} // namespace QtConcurrent
QT_END_NAMESPACE

View File

@ -82,6 +82,32 @@ private:
Q_DISABLE_COPY(BlockSizeManager)
};
// ### Qt6: Replace BlockSizeManager with V2 implementation
class Q_CONCURRENT_EXPORT BlockSizeManagerV2
{
public:
explicit BlockSizeManagerV2(int iterationCount);
void timeBeforeUser();
void timeAfterUser();
int blockSize();
private:
inline bool blockSizeMaxed()
{
return (m_blockSize >= maxBlockSize);
}
const int maxBlockSize;
qint64 beforeUser;
qint64 afterUser;
MedianDouble controlPartElapsed;
MedianDouble userPartElapsed;
int m_blockSize;
Q_DISABLE_COPY(BlockSizeManagerV2)
};
template <typename T>
class ResultReporter
{
@ -190,7 +216,7 @@ public:
ThreadFunctionResult forThreadFunction()
{
BlockSizeManager blockSizeManager(iterationCount);
BlockSizeManagerV2 blockSizeManager(iterationCount);
ResultReporter<T> resultReporter(this);
for(;;) {

View File

@ -121,6 +121,72 @@ private:
bool dirty;
};
// ### Qt6: Drop Median<double> in favor of this faster MedianDouble
class MedianDouble
{
public:
enum { BufferSize = 7 };
MedianDouble()
: currentMedian(), currentIndex(0), valid(false), dirty(true)
{
}
void reset()
{
std::fill_n(values, static_cast<int>(BufferSize), 0.0);
currentIndex = 0;
valid = false;
dirty = true;
}
void addValue(double value)
{
++currentIndex;
if (currentIndex == BufferSize) {
currentIndex = 0;
valid = true;
}
// Only update the cached median value when we have to, that
// is when the new value is on then other side of the median
// compared to the current value at the index.
const double currentIndexValue = values[currentIndex];
if ((currentIndexValue > currentMedian && currentMedian > value)
|| (currentMedian > currentIndexValue && value > currentMedian)) {
dirty = true;
}
values[currentIndex] = value;
}
bool isMedianValid() const
{
return valid;
}
double median()
{
if (dirty) {
dirty = false;
double sorted[BufferSize];
::memcpy(&sorted, &values, sizeof(sorted));
std::sort(sorted, sorted + static_cast<int>(BufferSize));
currentMedian = sorted[BufferSize / 2];
}
return currentMedian;
}
private:
double values[BufferSize];
double currentMedian;
int currentIndex;
bool valid;
bool dirty;
};
} // namespace QtConcurrent
#endif //Q_QDOC