void SearchOperation::doSearch( SearchData& searchData, qint64 initialLine ) { const qint64 nbSourceLines = sourceLogData_->getNbLine(); int maxLength = 0; int nbMatches = searchData.getNbMatches(); SearchResultArray currentList = SearchResultArray(); // Ensure no re-alloc will be done currentList.reserve( nbLinesInChunk ); LOG(logDEBUG) << "Searching from line " << initialLine << " to " << nbSourceLines; if (initialLine < startLine_) { initialLine = startLine_; } const qint64 endLine = qMin(nbSourceLines, endLine_); for ( qint64 i = initialLine; i < endLine; i += nbLinesInChunk ) { if ( *interruptRequested_ ) break; const int percentage = ( i - initialLine ) * 100 / ( endLine - initialLine ); emit searchProgressed( nbMatches, percentage ); const QStringList lines = sourceLogData_->getLines( i, qMin( nbLinesInChunk, (int) ( endLine - i ) ) ); LOG(logDEBUG) << "Chunk starting at " << i << ", " << lines.size() << " lines read."; int j = 0; for ( ; j < lines.size(); j++ ) { if ( regexp_.match( lines[j] ).hasMatch() ) { // FIXME: increase perf by removing temporary const int length = sourceLogData_->getExpandedLineString(i+j).length(); if ( length > maxLength ) maxLength = length; currentList.push_back( MatchingLine( i+j ) ); nbMatches++; } } // After each block, copy the data to shared data // and update the client searchData.addAll( maxLength, currentList, i+j ); currentList.clear(); } emit searchProgressed( nbMatches, 100 ); }
void SearchOperation::doSearch( SearchData& searchData, LineNumber initialLine ) { const auto& config = Persistable::get<Configuration>(); const auto nbSourceLines = sourceLogData_->getNbLine(); LineLength maxLength = 0_length; LinesCount nbMatches = searchData.getNbMatches(); const auto nbLinesInChunk = LinesCount( config.searchReadBufferSizeLines() ); LOG( logDEBUG ) << "Searching from line " << initialLine << " to " << nbSourceLines; if ( initialLine < startLine_ ) { initialLine = startLine_; } const auto endLine = qMin( LineNumber( nbSourceLines.get() ), endLine_ ); using namespace std::chrono; high_resolution_clock::time_point t1 = high_resolution_clock::now(); QSemaphore searchCompleted; QSemaphore blocksDone; using SearchBlockQueue = moodycamel::BlockingConcurrentQueue<SearchBlockData>; using ProcessMatchQueue = moodycamel::BlockingConcurrentQueue<PartialSearchResults>; SearchBlockQueue searchBlockQueue; ProcessMatchQueue processMatchQueue; std::vector<QFuture<void>> matchers; const auto matchingThreadsCount = [&config]() { if ( !config.useParallelSearch() ) { return 1; } return qMax( 1, config.searchThreadPoolSize() == 0 ? QThread::idealThreadCount() - 1 : static_cast<int>( config.searchThreadPoolSize() ) ); }(); LOG( logINFO ) << "Using " << matchingThreadsCount << " matching threads"; auto localThreadPool = std::make_unique<QThreadPool>(); localThreadPool->setMaxThreadCount( matchingThreadsCount + 2 ); const auto makeMatcher = [this, &searchBlockQueue, &processMatchQueue, pool = localThreadPool.get()]( size_t index ) { // copy and optimize regex for each thread auto regexp = QRegularExpression{ regexp_.pattern(), regexp_.patternOptions() }; regexp.optimize(); return QtConcurrent::run( pool, [regexp, index, &searchBlockQueue, &processMatchQueue]() { auto cToken = moodycamel::ConsumerToken{ searchBlockQueue }; auto pToken = moodycamel::ProducerToken{ processMatchQueue }; for ( ;; ) { SearchBlockData blockData; searchBlockQueue.wait_dequeue( cToken, blockData ); LOG( logDEBUG ) << "Searcher " << index << " " << blockData.chunkStart; const auto lastBlock = blockData.lines.empty(); if ( !lastBlock ) { blockData.results = filterLines( regexp, blockData.lines, blockData.chunkStart ); } LOG( logDEBUG ) << "Searcher " << index << " sending matches " << blockData.results.matchingLines.size(); processMatchQueue.enqueue( pToken, std::move( blockData.results ) ); if ( lastBlock ) { LOG( logDEBUG ) << "Searcher " << index << " last block"; return; } } } ); }; for ( int i = 0; i < matchingThreadsCount; ++i ) { matchers.emplace_back( makeMatcher( i ) ); } auto processMatches = QtConcurrent::run( localThreadPool.get(), [&]() { auto cToken = moodycamel::ConsumerToken{ processMatchQueue }; size_t matchersDone = 0; int reportedPercentage = 0; auto reportedMatches = nbMatches; LinesCount totalProcessedLines = 0_lcount; const auto totalLines = endLine - initialLine; for ( ;; ) { PartialSearchResults matchResults; processMatchQueue.wait_dequeue( cToken, matchResults ); LOG( logDEBUG ) << "Combining match results from " << matchResults.chunkStart; if ( matchResults.processedLines.get() ) { maxLength = qMax( maxLength, matchResults.maxLength ); nbMatches += LinesCount( static_cast<LinesCount::UnderlyingType>( matchResults.matchingLines.size() ) ); const auto processedLines = LinesCount{ matchResults.chunkStart.get() + matchResults.processedLines.get() }; totalProcessedLines += matchResults.processedLines; // After each block, copy the data to shared data // and update the client searchData.addAll( maxLength, matchResults.matchingLines, processedLines ); LOG( logDEBUG ) << "done Searching chunk starting at " << matchResults.chunkStart << ", " << matchResults.processedLines << " lines read."; blocksDone.release( matchResults.processedLines.get() ); } else { matchersDone++; } const int percentage = static_cast<int>( std::floor( 100.f * ( totalProcessedLines ).get() / totalLines.get() ) ); if ( percentage > reportedPercentage || nbMatches > reportedMatches ) { emit searchProgressed( nbMatches, std::min( 99, percentage ), initialLine ); reportedPercentage = percentage; reportedMatches = nbMatches; } if ( matchersDone == matchers.size() ) { searchCompleted.release(); return; } } } ); auto pToken = moodycamel::ProducerToken{ searchBlockQueue }; blocksDone.release( nbLinesInChunk.get() * ( static_cast<uint32_t>( matchers.size() ) + 1 ) ); for ( auto chunkStart = initialLine; chunkStart < endLine; chunkStart = chunkStart + nbLinesInChunk ) { if ( *interruptRequested_ ) break; LOG( logDEBUG ) << "Reading chunk starting at " << chunkStart; const auto linesInChunk = LinesCount( qMin( nbLinesInChunk.get(), ( endLine - chunkStart ).get() ) ); auto lines = sourceLogData_->getLines( chunkStart, linesInChunk ); LOG( logDEBUG ) << "Sending chunk starting at " << chunkStart << ", " << lines.size() << " lines read."; blocksDone.acquire( static_cast<uint32_t>( lines.size() ) ); searchBlockQueue.enqueue( pToken, { chunkStart, std::move( lines ), PartialSearchResults{} } ); LOG( logDEBUG ) << "Sent chunk starting at " << chunkStart << ", " << lines.size() << " lines read."; } for ( size_t i = 0; i < matchers.size(); ++i ) { searchBlockQueue.enqueue( pToken, { endLine, std::vector<QString>{}, PartialSearchResults{} } ); } searchCompleted.acquire(); high_resolution_clock::time_point t2 = high_resolution_clock::now(); auto duration = duration_cast<microseconds>( t2 - t1 ).count(); LOG( logINFO ) << "Searching done, took " << duration / 1000.f << " ms"; LOG( logINFO ) << "Searching perf " << static_cast<uint32_t>( std::floor( 1000 * 1000.f * ( endLine - initialLine ).get() / duration ) ) << " lines/s"; emit searchProgressed( nbMatches, 100, initialLine ); }