From 24d851dcd2a140bf346343c5eb64d944cd1c1a87 Mon Sep 17 00:00:00 2001 From: Tobias Koenig Date: Tue, 19 Jan 2016 17:45:42 +0100 Subject: 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 --- src/concurrent/qtconcurrentiteratekernel.cpp | 52 ++++++++++++++++++++++ src/concurrent/qtconcurrentiteratekernel.h | 28 +++++++++++- src/concurrent/qtconcurrentmedian.h | 66 ++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/concurrent/qtconcurrentiteratekernel.cpp b/src/concurrent/qtconcurrentiteratekernel.cpp index 49056406c3..3b9b186b39 100644 --- a/src/concurrent/qtconcurrentiteratekernel.cpp +++ b/src/concurrent/qtconcurrentiteratekernel.cpp @@ -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 diff --git a/src/concurrent/qtconcurrentiteratekernel.h b/src/concurrent/qtconcurrentiteratekernel.h index 32acf03338..40e2c6c115 100644 --- a/src/concurrent/qtconcurrentiteratekernel.h +++ b/src/concurrent/qtconcurrentiteratekernel.h @@ -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 class ResultReporter { @@ -190,7 +216,7 @@ public: ThreadFunctionResult forThreadFunction() { - BlockSizeManager blockSizeManager(iterationCount); + BlockSizeManagerV2 blockSizeManager(iterationCount); ResultReporter resultReporter(this); for(;;) { diff --git a/src/concurrent/qtconcurrentmedian.h b/src/concurrent/qtconcurrentmedian.h index 2ae7954b7a..e35e2aec70 100644 --- a/src/concurrent/qtconcurrentmedian.h +++ b/src/concurrent/qtconcurrentmedian.h @@ -121,6 +121,72 @@ private: bool dirty; }; +// ### Qt6: Drop Median 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(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(BufferSize)); + currentMedian = sorted[BufferSize / 2]; + } + + return currentMedian; + } + +private: + double values[BufferSize]; + double currentMedian; + int currentIndex; + bool valid; + bool dirty; +}; + } // namespace QtConcurrent #endif //Q_QDOC -- cgit v1.2.3