From 91f921154b76ce2ab6500249f8ad9fb46c0b60c4 Mon Sep 17 00:00:00 2001 From: Michael Niksa Date: Wed, 8 Jul 2020 16:30:46 -0700 Subject: [PATCH] Cache VT buffer line string to avoid (de)alloc on every paint (#6840) A lot of time was spent between each individual line in the VT paint engine in allocating some scratch space to assemble the clusters then deallocating it only to have the next line do that again. Now we just hold onto that memory space since it should be approximately the size of a single line wide and will be used over and over and over as painting continues. ## Validation Steps Performed - Run `time cat big.txt` under WPR. Checked before and after perf metrics. --- src/renderer/vt/paint.cpp | 21 ++++++++++----------- src/renderer/vt/state.cpp | 3 ++- src/renderer/vt/vtrenderer.hpp | 3 +++ 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/renderer/vt/paint.cpp b/src/renderer/vt/paint.cpp index 0a4fa55817..cf345211e9 100644 --- a/src/renderer/vt/paint.cpp +++ b/src/renderer/vt/paint.cpp @@ -334,17 +334,17 @@ using namespace Microsoft::Console::Types; { RETURN_IF_FAILED(_MoveCursor(coord)); - std::wstring wstr; - wstr.reserve(clusters.size()); + _bufferLine.clear(); + _bufferLine.reserve(clusters.size()); short totalWidth = 0; for (const auto& cluster : clusters) { - wstr.append(cluster.GetText()); + _bufferLine.append(cluster.GetText()); RETURN_IF_FAILED(ShortAdd(totalWidth, gsl::narrow(cluster.GetColumns()), &totalWidth)); } - RETURN_IF_FAILED(VtEngine::_WriteTerminalAscii(wstr)); + RETURN_IF_FAILED(VtEngine::_WriteTerminalAscii(_bufferLine)); // Update our internal tracker of the cursor's position _lastText.X += totalWidth; @@ -371,21 +371,21 @@ using namespace Microsoft::Console::Types; return S_OK; } - std::wstring unclusteredString; - unclusteredString.reserve(clusters.size()); + _bufferLine.clear(); + _bufferLine.reserve(clusters.size()); short totalWidth = 0; for (const auto& cluster : clusters) { - unclusteredString.append(cluster.GetText()); + _bufferLine.append(cluster.GetText()); RETURN_IF_FAILED(ShortAdd(totalWidth, static_cast(cluster.GetColumns()), &totalWidth)); } - const size_t cchLine = unclusteredString.size(); + const size_t cchLine = _bufferLine.size(); bool foundNonspace = false; size_t lastNonSpace = 0; for (size_t i = 0; i < cchLine; i++) { - if (unclusteredString.at(i) != L'\x20') + if (_bufferLine.at(i) != L'\x20') { lastNonSpace = i; foundNonspace = true; @@ -479,8 +479,7 @@ using namespace Microsoft::Console::Types; RETURN_IF_FAILED(_MoveCursor(coord)); // Write the actual text string - std::wstring wstr = std::wstring(unclusteredString.data(), cchActual); - RETURN_IF_FAILED(VtEngine::_WriteTerminalUtf8(wstr)); + RETURN_IF_FAILED(VtEngine::_WriteTerminalUtf8({ _bufferLine.data(), cchActual })); // GH#4415, GH#5181 // If the renderer told us that this was a wrapped line, then mark diff --git a/src/renderer/vt/state.cpp b/src/renderer/vt/state.cpp index 432779c0df..05dab05e15 100644 --- a/src/renderer/vt/state.cpp +++ b/src/renderer/vt/state.cpp @@ -49,7 +49,8 @@ VtEngine::VtEngine(_In_ wil::unique_hfile pipe, _newBottomLine{ false }, _deferredCursorPos{ INVALID_COORDS }, _inResizeRequest{ false }, - _trace{} + _trace{}, + _bufferLine{} { #ifndef UNIT_TESTING // When unit testing, we can instantiate a VtEngine without a pipe. diff --git a/src/renderer/vt/vtrenderer.hpp b/src/renderer/vt/vtrenderer.hpp index dd0158c2c4..0d346229fc 100644 --- a/src/renderer/vt/vtrenderer.hpp +++ b/src/renderer/vt/vtrenderer.hpp @@ -204,6 +204,9 @@ namespace Microsoft::Console::Render bool _WillWriteSingleChar() const; + // buffer space for these two functions to build their lines + // so they don't have to alloc/free in a tight loop + std::wstring _bufferLine; [[nodiscard]] HRESULT _PaintUtf8BufferLine(std::basic_string_view const clusters, const COORD coord, const bool lineWrapped) noexcept;