Defer _run substr operation in StateMachine::ProcessString (#10471)

Do not eagerly create run by substring to reduce the unnecessary overhead.

Switching from `.substr` to offset/length tracking saves us 7% CPU time.
This commit is contained in:
Chester Liu 2021-06-30 06:47:27 +08:00 committed by GitHub
parent 4fc283f8b6
commit 6a37818c07
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 47 additions and 15 deletions

View file

@ -1818,7 +1818,7 @@ bool StateMachine::FlushToTerminal()
// that pwchCurr was processed. // that pwchCurr was processed.
// However, if we're here, then the processing of pwchChar triggered the // However, if we're here, then the processing of pwchChar triggered the
// engine to request the entire sequence get passed through, including pwchCurr. // engine to request the entire sequence get passed through, including pwchCurr.
success = _engine->ActionPassThroughString(_run); success = _engine->ActionPassThroughString(_CurrentRun());
} }
return success; return success;
@ -1838,17 +1838,22 @@ void StateMachine::ProcessString(const std::wstring_view string)
size_t start = 0; size_t start = 0;
size_t current = start; size_t current = start;
_currentString = string;
_runOffset = 0;
_runSize = 0;
while (current < string.size()) while (current < string.size())
{ {
// The run will be everything from the start INCLUDING the current one // The run will be everything from the start INCLUDING the current one
// in case we process the current character and it turns into a passthrough // in case we process the current character and it turns into a passthrough
// fallback that picks up this _run inside `FlushToTerminal` above. // fallback that picks up this _run inside `FlushToTerminal` above.
_run = string.substr(start, current - start + 1); _runOffset = start;
_runSize = current - start + 1;
if (_processingIndividually) if (_processingIndividually)
{ {
// If we're processing characters individually, send it to the state machine. // If we're processing characters individually, send it to the state machine.
ProcessCharacter(string.at(current)); ProcessCharacter(til::at(string, current));
++current; ++current;
if (_state == VTStates::Ground) // Then check if we're back at ground. If we are, the next character (pwchCurr) if (_state == VTStates::Ground) // Then check if we're back at ground. If we are, the next character (pwchCurr)
{ // is the start of the next run of characters that might be printable. { // is the start of the next run of characters that might be printable.
@ -1858,14 +1863,15 @@ void StateMachine::ProcessString(const std::wstring_view string)
} }
else else
{ {
if (_isActionableFromGround(string.at(current))) // If the current char is the start of an escape sequence, or should be executed in ground state... if (_isActionableFromGround(til::at(string, current))) // If the current char is the start of an escape sequence, or should be executed in ground state...
{ {
if (!_run.empty()) if (_runSize > 0)
{ {
// Because the _run above is composed INCLUDING current, we must // Because the run above is composed INCLUDING current, we must
// trim it off here since we just determined it's actionable // trim it off here since we just determined it's actionable
// and only pass through everything before it. // and only pass through everything before it.
const auto allLeadingUpTo = _run.substr(0, _run.size() - 1); _runSize -= 1;
const auto allLeadingUpTo = _CurrentRun();
_engine->ActionPrintString(allLeadingUpTo); // ... print all the chars leading up to it as part of the run... _engine->ActionPrintString(allLeadingUpTo); // ... print all the chars leading up to it as part of the run...
_trace.DispatchPrintRunTrace(allLeadingUpTo); _trace.DispatchPrintRunTrace(allLeadingUpTo);
@ -1885,14 +1891,23 @@ void StateMachine::ProcessString(const std::wstring_view string)
// When we leave the loop, current has been advanced to the length of the string itself // When we leave the loop, current has been advanced to the length of the string itself
// (or one past the array index to the final char) so this `substr` operation doesn't +1 // (or one past the array index to the final char) so this `substr` operation doesn't +1
// to include the final character (unlike the one inside the top of the loop above.) // to include the final character (unlike the one inside the top of the loop above.)
_run = start < string.size() ? string.substr(start) : std::wstring_view{}; if (start < string.size())
{
_runOffset = start;
_runSize = std::string::npos;
}
else
{
_runSize = 0;
}
const auto run = _CurrentRun();
// If we're at the end of the string and have remaining un-printed characters, // If we're at the end of the string and have remaining un-printed characters,
if (!_processingIndividually && !_run.empty()) if (!_processingIndividually && !run.empty())
{ {
// print the rest of the characters in the string // print the rest of the characters in the string
_engine->ActionPrintString(_run); _engine->ActionPrintString(run);
_trace.DispatchPrintRunTrace(_run); _trace.DispatchPrintRunTrace(run);
} }
else if (_processingIndividually) else if (_processingIndividually)
{ {
@ -1920,8 +1935,8 @@ void StateMachine::ProcessString(const std::wstring_view string)
// Reset our state, and put all but the last char in again. // Reset our state, and put all but the last char in again.
ResetState(); ResetState();
// Chars to flush are [pwchSequenceStart, pwchCurr) // Chars to flush are [pwchSequenceStart, pwchCurr)
auto wchIter = _run.cbegin(); auto wchIter = run.cbegin();
while (wchIter < _run.cend() - 1) while (wchIter < run.cend() - 1)
{ {
ProcessCharacter(*wchIter); ProcessCharacter(*wchIter);
wchIter++; wchIter++;
@ -1961,7 +1976,13 @@ void StateMachine::ProcessString(const std::wstring_view string)
// If the engine doesn't require flushing at the end of the string, we // If the engine doesn't require flushing at the end of the string, we
// want to cache the partial sequence in case we have to flush the whole // want to cache the partial sequence in case we have to flush the whole
// thing to the terminal later. // thing to the terminal later.
_cachedSequence = _cachedSequence.value_or(std::wstring{}) + std::wstring{ _run }; if (!_cachedSequence)
{
_cachedSequence.emplace(std::wstring{});
}
auto& cachedSequence = *_cachedSequence;
cachedSequence.append(run);
} }
} }
} }

View file

@ -146,7 +146,18 @@ namespace Microsoft::Console::VirtualTerminal
bool _isInAnsiMode; bool _isInAnsiMode;
std::wstring_view _run; std::wstring_view _currentString;
size_t _runOffset;
size_t _runSize;
// Construct current run.
//
// Note: We intentionally use this method to create the run lazily for better performance.
// You may find the usage of offset & size unsafe, but under heavy load it shows noticeable performance benefit.
std::wstring_view _CurrentRun() const
{
return _currentString.substr(_runOffset, _runSize);
}
VTIDBuilder _identifier; VTIDBuilder _identifier;
std::vector<VTParameter> _parameters; std::vector<VTParameter> _parameters;