File indexing completed on 2024-04-28 15:39:58
0001 // SPDX-FileCopyrightText: 2018-2020 The KPhotoAlbum Development Team 0002 // SPDX-FileCopyrightText: 2022 Johannes Zarl-Zierl <johannes@zarl-zierl.at> 0003 // 0004 // SPDX-License-Identifier: GPL-2.0-or-later 0005 0006 #include "ImageScout.h" 0007 0008 #include <kpabase/Logging.h> 0009 0010 #include <QAtomicInt> 0011 #include <QDataStream> 0012 #include <QFile> 0013 #include <QMutexLocker> 0014 #include <QThread> 0015 0016 extern "C" { 0017 #include <fcntl.h> 0018 #include <unistd.h> 0019 } 0020 0021 using namespace DB; 0022 0023 namespace 0024 { 0025 constexpr int DEFAULT_SCOUT_BUFFER_SIZE = 1048576; // *sizeof(int) bytes 0026 // We might want this to be bytes rather than images. 0027 constexpr int DEFAULT_MAX_SEEKAHEAD_IMAGES = 10; 0028 constexpr int SEEKAHEAD_WAIT_MS = 10; // 10 milliseconds, and retry 0029 constexpr int TERMINATION_WAIT_MS = 10; // 10 milliseconds, and retry 0030 } 0031 0032 // 1048576 with a single scout thread empirically yields best performance 0033 // on a Seagate 2TB 2.5" disk, sustaining throughput in the range of 0034 // 95-100 MB/sec with 100-110 IO/sec on large files. This is close to what 0035 // would be expected. A SATA SSD (Crucial MX300) is much less sensitive to 0036 // I/O size and scout thread, achieving about 340 MB/sec with high CPU 0037 // utilization. 0038 0039 class DB::ImageScoutThread : public QThread 0040 { 0041 friend class DB::ImageScout; 0042 0043 public: 0044 ImageScoutThread(ImageScoutQueue &, QMutex *, QAtomicInt &count, 0045 QAtomicInt &preloadCount, QAtomicInt &skippedCount); 0046 0047 protected: 0048 void run() override; 0049 void setBufSize(int); 0050 int getBufSize(); 0051 void setMaxSeekAhead(int); 0052 int getMaxSeekAhead(); 0053 void setReadLimit(int); 0054 int getReadLimit(); 0055 void setPreloadFunc(PreloadFunc); 0056 PreloadFunc getPreloadFunc(); 0057 0058 private: 0059 void doRun(char *); 0060 ImageScoutQueue &m_queue; 0061 QMutex *m_mutex; 0062 QAtomicInt &m_loadedCount; 0063 QAtomicInt &m_preloadedCount; 0064 QAtomicInt &m_skippedCount; 0065 int m_scoutBufSize; 0066 int m_maxSeekAhead; 0067 int m_readLimit; 0068 PreloadFunc m_preloadFunc; 0069 bool m_isStarted; 0070 }; 0071 0072 ImageScoutThread::ImageScoutThread(ImageScoutQueue &queue, QMutex *mutex, 0073 QAtomicInt &count, 0074 QAtomicInt &preloadedCount, 0075 QAtomicInt &skippedCount) 0076 : m_queue(queue) 0077 , m_mutex(mutex) 0078 , m_loadedCount(count) 0079 , m_preloadedCount(preloadedCount) 0080 , m_skippedCount(skippedCount) 0081 , m_scoutBufSize(DEFAULT_SCOUT_BUFFER_SIZE) 0082 , m_maxSeekAhead(DEFAULT_MAX_SEEKAHEAD_IMAGES) 0083 , m_readLimit(-1) 0084 , m_preloadFunc(nullptr) 0085 , m_isStarted(false) 0086 { 0087 } 0088 0089 void ImageScoutThread::doRun(char *tmpBuf) 0090 { 0091 while (!isInterruptionRequested()) { 0092 QMutexLocker locker(m_mutex); 0093 if (m_queue.isEmpty()) { 0094 return; 0095 } 0096 DB::FileName fileName = m_queue.dequeue(); 0097 locker.unlock(); 0098 // If we're behind the reader, move along 0099 m_preloadedCount++; 0100 if (m_loadedCount.loadRelaxed() >= m_preloadedCount.loadRelaxed()) { 0101 m_skippedCount++; 0102 continue; 0103 } else { 0104 // Don't get too far ahead of the loader, or we just waste memory 0105 // TODO: wait on something rather than polling 0106 while (m_preloadedCount.loadRelaxed() >= m_loadedCount.loadRelaxed() + m_maxSeekAhead && !isInterruptionRequested()) { 0107 QThread::msleep(SEEKAHEAD_WAIT_MS); 0108 } 0109 // qCDebug(DBImageScoutLog) << ">>>>>Scout: preload" << m_preloadedCount.loadRelaxed() << "load" << m_loadedCount.loadRelaxed() << fileName.relative(); 0110 } 0111 if (m_preloadFunc) { 0112 (*m_preloadFunc)(fileName); 0113 } else { 0114 // Note(jzarl): for Windows, we'd need a functional replacement for open(), read(), close() in unistd.h 0115 int inputFD = open(QFile::encodeName(fileName.absolute()).constData(), O_RDONLY); 0116 int bytesRead = 0; 0117 if (inputFD >= 0) { 0118 while (read(inputFD, tmpBuf, m_scoutBufSize) && (m_readLimit < 0 || ((bytesRead += m_scoutBufSize) < m_readLimit)) && !isInterruptionRequested()) { 0119 } 0120 (void)close(inputFD); 0121 } 0122 } 0123 } 0124 } 0125 0126 void ImageScoutThread::setBufSize(int bufSize) 0127 { 0128 if (!m_isStarted) 0129 m_scoutBufSize = bufSize; 0130 } 0131 0132 int ImageScoutThread::getBufSize() 0133 { 0134 return m_scoutBufSize; 0135 } 0136 0137 void ImageScoutThread::setMaxSeekAhead(int maxSeekAhead) 0138 { 0139 if (!m_isStarted) 0140 m_maxSeekAhead = maxSeekAhead; 0141 } 0142 0143 int ImageScoutThread::getMaxSeekAhead() 0144 { 0145 return m_maxSeekAhead; 0146 } 0147 0148 void ImageScoutThread::setReadLimit(int readLimit) 0149 { 0150 if (!m_isStarted) 0151 m_readLimit = readLimit; 0152 } 0153 0154 int ImageScoutThread::getReadLimit() 0155 { 0156 return m_readLimit; 0157 } 0158 0159 void ImageScoutThread::setPreloadFunc(PreloadFunc scoutFunc) 0160 { 0161 if (!m_isStarted) 0162 m_preloadFunc = scoutFunc; 0163 } 0164 0165 PreloadFunc ImageScoutThread::getPreloadFunc() 0166 { 0167 return m_preloadFunc; 0168 } 0169 0170 void ImageScoutThread::run() 0171 { 0172 m_isStarted = true; 0173 char *tmpBuf = new char[m_scoutBufSize]; 0174 doRun(tmpBuf); 0175 delete[] tmpBuf; 0176 } 0177 0178 ImageScout::ImageScout(ImageScoutQueue &images, 0179 QAtomicInt &count, 0180 int threads) 0181 : m_preloadedCount(0) 0182 , m_skippedCount(0) 0183 , m_isStarted(false) 0184 , m_scoutBufSize(DEFAULT_SCOUT_BUFFER_SIZE) 0185 , m_maxSeekAhead(DEFAULT_MAX_SEEKAHEAD_IMAGES) 0186 , m_readLimit(-1) 0187 , m_preloadFunc(nullptr) 0188 { 0189 if (threads > 0) { 0190 for (int i = 0; i < threads; i++) { 0191 ImageScoutThread *t = new ImageScoutThread(images, 0192 threads > 1 ? &m_mutex : nullptr, 0193 count, 0194 m_preloadedCount, 0195 m_skippedCount); 0196 m_scoutList.append(t); 0197 } 0198 } 0199 } 0200 0201 ImageScout::~ImageScout() 0202 { 0203 if (m_scoutList.count() > 0) { 0204 for (QList<ImageScoutThread *>::iterator it = m_scoutList.begin(); 0205 it != m_scoutList.end(); ++it) { 0206 if (m_isStarted) { 0207 if (!(*it)->isFinished()) { 0208 (*it)->requestInterruption(); 0209 while (!(*it)->isFinished()) 0210 QThread::msleep(TERMINATION_WAIT_MS); 0211 } 0212 } 0213 delete (*it); 0214 } 0215 } 0216 qCDebug(DBImageScoutLog) << "Total files:" << m_preloadedCount << "skipped" << m_skippedCount; 0217 } 0218 0219 void ImageScout::start() 0220 { 0221 // Yes, there's a race condition here between isStartd and setting 0222 // the buf size or seek ahead...but this isn't a hot code path! 0223 if (!m_isStarted && m_scoutList.count() > 0) { 0224 m_isStarted = true; 0225 for (QList<ImageScoutThread *>::iterator it = m_scoutList.begin(); 0226 it != m_scoutList.end(); ++it) { 0227 (*it)->start(); 0228 } 0229 } 0230 } 0231 0232 void ImageScout::setBufSize(int bufSize) 0233 { 0234 if (!m_isStarted && bufSize > 0) { 0235 m_scoutBufSize = bufSize; 0236 for (QList<ImageScoutThread *>::iterator it = m_scoutList.begin(); 0237 it != m_scoutList.end(); ++it) { 0238 (*it)->setBufSize(m_scoutBufSize); 0239 } 0240 } 0241 } 0242 0243 int ImageScout::getBufSize() 0244 { 0245 return m_scoutBufSize; 0246 } 0247 0248 void ImageScout::setMaxSeekAhead(int maxSeekAhead) 0249 { 0250 if (!m_isStarted && maxSeekAhead > 0) { 0251 m_maxSeekAhead = maxSeekAhead; 0252 for (QList<ImageScoutThread *>::iterator it = m_scoutList.begin(); 0253 it != m_scoutList.end(); ++it) { 0254 (*it)->setMaxSeekAhead(m_maxSeekAhead); 0255 } 0256 } 0257 } 0258 0259 int ImageScout::getMaxSeekAhead() 0260 { 0261 return m_maxSeekAhead; 0262 } 0263 0264 void ImageScout::setReadLimit(int readLimit) 0265 { 0266 if (!m_isStarted && readLimit > 0) { 0267 m_readLimit = readLimit; 0268 for (QList<ImageScoutThread *>::iterator it = m_scoutList.begin(); 0269 it != m_scoutList.end(); ++it) { 0270 (*it)->setReadLimit(m_readLimit); 0271 } 0272 } 0273 } 0274 0275 int ImageScout::getReadLimit() 0276 { 0277 return m_readLimit; 0278 } 0279 0280 void ImageScout::setPreloadFunc(PreloadFunc scoutFunc) 0281 { 0282 if (!m_isStarted) { 0283 m_preloadFunc = scoutFunc; 0284 for (QList<ImageScoutThread *>::iterator it = m_scoutList.begin(); 0285 it != m_scoutList.end(); ++it) { 0286 (*it)->setPreloadFunc(m_preloadFunc); 0287 } 0288 } 0289 } 0290 0291 PreloadFunc ImageScout::getPreloadFunc() 0292 { 0293 return m_preloadFunc; 0294 } 0295 0296 // vi:expandtab:tabstop=4 shiftwidth=4: