diff options
author | Neil <nyamatongwe@gmail.com> | 2023-03-02 22:02:19 +1100 |
---|---|---|
committer | Neil <nyamatongwe@gmail.com> | 2023-03-02 22:02:19 +1100 |
commit | 43ba96d61994db084e61b31df278d2d87c1f313c (patch) | |
tree | 9ec2084b3a8594434889fb622f2fe7c5446dc0a9 /src/Editor.cxx | |
parent | c61df8742a4865ac9c67f8ed017248b82fe5574e (diff) | |
download | scintilla-mirror-43ba96d61994db084e61b31df278d2d87c1f313c.tar.gz |
Add multithreaded wrap to significantly improve performance of wrapping large
files.
Diffstat (limited to 'src/Editor.cxx')
-rw-r--r-- | src/Editor.cxx | 138 |
1 files changed, 128 insertions, 10 deletions
diff --git a/src/Editor.cxx b/src/Editor.cxx index d64a09deb..30a9c9786 100644 --- a/src/Editor.cxx +++ b/src/Editor.cxx @@ -25,6 +25,9 @@ #include <iterator> #include <memory> #include <chrono> +#include <atomic> +#include <thread> +#include <future> #include "ScintillaTypes.h" #include "ScintillaMessages.h" @@ -1473,6 +1476,130 @@ bool Editor::WrapOneLine(Surface *surface, Sci::Line lineToWrap) { return pcs->SetHeight(lineToWrap, linesWrapped); } +namespace { + +// Lines less than lengthToMultiThread are laid out in blocks in parallel. +// Longer lines are multi-threaded inside LayoutLine. +// This allows faster processing when lines differ greatly in length and thus time to lay out. +constexpr Sci::Position lengthToMultiThread = 4000; + +} + +bool Editor::WrapBlock(Surface *surface, Sci::Line lineToWrap, Sci::Line lineToWrapEnd) { + + const size_t linesBeingWrapped = static_cast<size_t>(lineToWrapEnd - lineToWrap); + + std::vector<int> linesAfterWrap(linesBeingWrapped); + + size_t threads = std::min<size_t>({ linesBeingWrapped, view.maxLayoutThreads }); + if (!surface->SupportsFeature(Supports::ThreadSafeMeasureWidths)) { + threads = 1; + } + + const bool multiThreaded = threads > 1; + + ElapsedPeriod epWrapping; + + // Wrap all the short lines in multiple threads + + // If only 1 thread needed then use the main thread, else spin up multiple + const std::launch policy = multiThreaded ? std::launch::async : std::launch::deferred; + + std::atomic<size_t> nextIndex = 0; + + // Lines that are less likely to be re-examined should not be read from or written to the cache. + const SignificantLines significantLines { + pdoc->SciLineFromPosition(sel.MainCaret()), + topLine, + LinesOnScreen() + 1, + view.llc.GetLevel(), + }; + + // Protect the line layout cache from being accessed from multiple threads simultaneously + std::mutex mutexRetrieve; + + std::vector<std::future<void>> futures; + for (size_t th = 0; th < threads; th++) { + std::future<void> fut = std::async(policy, + [=, &surface, &nextIndex, &linesAfterWrap, &mutexRetrieve]() { + // llTemporary is reused for non-significant lines, avoiding allocation costs. + std::shared_ptr<LineLayout> llTemporary = std::make_shared<LineLayout>(-1, 200); + while (true) { + const size_t i = nextIndex.fetch_add(1, std::memory_order_acq_rel); + if (i >= linesBeingWrapped) { + break; + } + const Sci::Line lineNumber = lineToWrap + i; + const Range rangeLine = pdoc->LineRange(lineNumber); + const Sci::Position lengthLine = rangeLine.Length(); + if (lengthLine < lengthToMultiThread) { + std::shared_ptr<LineLayout> ll; + if (significantLines.LineMayCache(lineNumber)) { + std::lock_guard<std::mutex> guard(mutexRetrieve); + ll = view.RetrieveLineLayout(lineNumber, *this); + } else { + ll = llTemporary; + ll->ReSet(lineNumber, lengthLine); + } + view.LayoutLine(*this, surface, vs, ll.get(), wrapWidth, multiThreaded); + linesAfterWrap[i] = ll->lines; + } + } + }); + futures.push_back(std::move(fut)); + } + for (const std::future<void> &f : futures) { + f.wait(); + } + // End of multiple threads + + // Multiply duration by number of threads to produce (near) equivalence to duration if single threaded + const double durationShortLines = epWrapping.Duration(true); + const double durationShortLinesThreads = durationShortLines * threads; + + // Wrap all the long lines in the main thread. + // LayoutLine may then multi-thread over segments in each line. + + std::shared_ptr<LineLayout> llLarge = std::make_shared<LineLayout>(-1, 200); + for (size_t indexLarge = 0; indexLarge < linesBeingWrapped; indexLarge++) { + const Sci::Line lineNumber = lineToWrap + indexLarge; + const Range rangeLine = pdoc->LineRange(lineNumber); + const Sci::Position lengthLine = rangeLine.Length(); + if (lengthLine >= lengthToMultiThread) { + std::shared_ptr<LineLayout> ll; + if (significantLines.LineMayCache(lineNumber)) { + ll = view.RetrieveLineLayout(lineNumber, *this); + } else { + ll = llLarge; + ll->ReSet(lineNumber, lengthLine); + } + view.LayoutLine(*this, surface, vs, ll.get(), wrapWidth); + linesAfterWrap[indexLarge] = ll->lines; + } + } + + const double durationLongLines = epWrapping.Duration(); + const size_t bytesBeingWrapped = pdoc->LineStart(lineToWrap + linesBeingWrapped) - pdoc->LineStart(lineToWrap); + + size_t wrapsDone = 0; + + for (size_t i = 0; i < linesBeingWrapped; i++) { + const Sci::Line lineNumber = lineToWrap + i; + int linesWrapped = linesAfterWrap[i]; + if (vs.annotationVisible != AnnotationVisible::Hidden) { + linesWrapped += pdoc->AnnotationLines(lineNumber); + } + if (pcs->SetHeight(lineNumber, linesWrapped)) { + wrapsDone++; + } + wrapPending.Wrapped(lineNumber); + } + + durationWrapOneByte.AddSample(bytesBeingWrapped, durationShortLinesThreads + durationLongLines); + + return wrapsDone > 0; +} + // Perform wrapping for a subset of the lines needing wrapping. // wsAll: wrap all lines which need wrapping in this single call // wsVisible: wrap currently visible lines @@ -1554,16 +1681,7 @@ bool Editor::WrapLines(WrapScope ws) { if (surface) { //Platform::DebugPrintf("Wraplines: scope=%0d need=%0d..%0d perform=%0d..%0d\n", ws, wrapPending.start, wrapPending.end, lineToWrap, lineToWrapEnd); - const size_t bytesBeingWrapped = pdoc->LineStart(lineToWrapEnd) - pdoc->LineStart(lineToWrap); - ElapsedPeriod epWrapping; - while (lineToWrap < lineToWrapEnd) { - if (WrapOneLine(surface, lineToWrap)) { - wrapOccurred = true; - } - wrapPending.Wrapped(lineToWrap); - lineToWrap++; - } - durationWrapOneByte.AddSample(bytesBeingWrapped, epWrapping.Duration()); + wrapOccurred = WrapBlock(surface, lineToWrap, lineToWrapEnd); goodTopLine = pcs->DisplayFromDoc(lineDocTop) + std::min( subLineTop, static_cast<Sci::Line>(pcs->GetHeight(lineDocTop)-1)); |