terminal/src/buffer/out/textBufferCellIterator.cpp
Chester Liu 37e0614554
Optimize hot path in textBufferCellIterator (#10621)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request

<!-- Other than the issue solved, is this relevant to any other issues/existing PRs? --> 
## References

The `+=` operator is an extremely hot path under heavily output load. This PR aims to optimize its speed.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [ ] Supports #10563
* [ ] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [ ] Tests added/passed
* [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx

<!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2021-07-27 15:09:56 +00:00

361 lines
12 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "textBufferCellIterator.hpp"
#include "CharRow.hpp"
#include "textBuffer.hpp"
#include "../types/inc/convert.hpp"
#include "../types/inc/viewport.hpp"
#pragma hdrstop
using namespace Microsoft::Console::Types;
// Routine Description:
// - Creates a new read-only iterator to seek through cell data stored within a screen buffer
// Arguments:
// - buffer - Text buffer to seek through
// - pos - Starting position to retrieve text data from (within screen buffer bounds)
TextBufferCellIterator::TextBufferCellIterator(const TextBuffer& buffer, COORD pos) :
TextBufferCellIterator(buffer, pos, buffer.GetSize())
{
}
// Routine Description:
// - Creates a new read-only iterator to seek through cell data stored within a screen buffer
// Arguments:
// - buffer - Pointer to screen buffer to seek through
// - pos - Starting position to retrieve text data from (within screen buffer bounds)
// - limits - Viewport limits to restrict the iterator within the buffer bounds (smaller than the buffer itself)
TextBufferCellIterator::TextBufferCellIterator(const TextBuffer& buffer, COORD pos, const Viewport limits) :
_buffer(buffer),
_pos(pos),
_pRow(s_GetRow(buffer, pos)),
_bounds(limits),
_exceeded(false),
_view({}, {}, {}, TextAttributeBehavior::Stored),
_attrIter(s_GetRow(buffer, pos)->GetAttrRow().cbegin())
{
// Throw if the bounds rectangle is not limited to the inside of the given buffer.
THROW_HR_IF(E_INVALIDARG, !buffer.GetSize().IsInBounds(limits));
// Throw if the coordinate is not limited to the inside of the given buffer.
THROW_HR_IF(E_INVALIDARG, !limits.IsInBounds(pos));
_attrIter += pos.X;
_GenerateView();
}
// Routine Description:
// - Tells if the iterator is still valid (hasn't exceeded boundaries of underlying text buffer)
// Return Value:
// - True if this iterator can still be dereferenced for data. False if we've passed the end and are out of data.
TextBufferCellIterator::operator bool() const noexcept
{
return !_exceeded && _bounds.IsInBounds(_pos);
}
// Routine Description:
// - Compares two iterators to see if they're pointing to the same position in the same buffer
// Arguments:
// - it - The other iterator to compare to this one.
// Return Value:
// - True if it's the same text buffer and same cell position. False otherwise.
bool TextBufferCellIterator::operator==(const TextBufferCellIterator& it) const noexcept
{
return _pos == it._pos &&
&_buffer == &it._buffer &&
_exceeded == it._exceeded &&
_bounds == it._bounds &&
_pRow == it._pRow &&
_attrIter == it._attrIter;
}
// Routine Description:
// - Compares two iterators to see if they're pointing to the different positions in the same buffer or different buffers entirely.
// Arguments:
// - it - The other iterator to compare to this one.
// Return Value:
// - True if it's the same text buffer and different cell position or if they're different buffers. False otherwise.
bool TextBufferCellIterator::operator!=(const TextBufferCellIterator& it) const noexcept
{
return !(*this == it);
}
// Routine Description:
// - Advances the iterator forward relative to the underlying text buffer by the specified movement
// Arguments:
// - movement - Magnitude and direction of movement.
// Return Value:
// - Reference to self after movement.
TextBufferCellIterator& TextBufferCellIterator::operator+=(const ptrdiff_t& movement)
{
// Note that this method is called intensively when the terminal is under heavy load.
// We need to aggressively optimize it, comparing to the -= operator.
ptrdiff_t move = movement;
if (move < 0)
{
// Early branching to leave the rare case to -= operator.
// This helps reducing the instruction count within this method, which is good for instruction cache.
return *this -= -move;
}
// The remaining code in this function is functionally equivalent to:
// auto newPos = _pos;
// while (move > 0 && !_exceeded)
// {
// _exceeded = !_bounds.IncrementInBounds(newPos);
// move--;
// }
// _SetPos(newPos);
//
// _SetPos() necessitates calling _GenerateView() and thus the construction
// of a new OutputCellView(). This has a high performance impact (ICache spill?).
// The code below inlines _bounds.IncrementInBounds as well as SetPos.
// In the hot path (_pos.Y doesn't change) we modify the _view directly.
// Hoist these integers which will be used frequently later.
const auto boundsRightInclusive = _bounds.RightInclusive();
const auto boundsLeft = _bounds.Left();
const auto boundsBottomInclusive = _bounds.BottomInclusive();
const auto boundsTop = _bounds.Top();
const auto oldX = _pos.X;
const auto oldY = _pos.Y;
// Under MSVC writing the individual members of a COORD generates worse assembly
// compared to having them be local variables. This causes a performance impact.
auto newX = oldX;
auto newY = oldY;
while (move > 0)
{
if (newX == boundsRightInclusive)
{
newX = boundsLeft;
newY++;
if (newY > boundsBottomInclusive)
{
newY = boundsTop;
_exceeded = true;
break;
}
}
else
{
newX++;
_exceeded = false;
}
move--;
}
if (_exceeded)
{
// Early return because nothing needs to be done here.
return *this;
}
if (newY == oldY)
{
// hot path
const auto diff = gsl::narrow_cast<ptrdiff_t>(newX) - gsl::narrow_cast<ptrdiff_t>(oldX);
_attrIter += diff;
_view.UpdateTextAttribute(*_attrIter);
const CharRow& charRow = _pRow->GetCharRow();
_view.UpdateText(charRow.GlyphAt(newX));
_view.UpdateDbcsAttribute(charRow.DbcsAttrAt(newX));
_pos.X = newX;
}
else
{
// cold path (_GenerateView is slow)
_pRow = s_GetRow(_buffer, { newX, newY });
_attrIter = _pRow->GetAttrRow().cbegin() + newX;
_pos.X = newX;
_pos.Y = newY;
_GenerateView();
}
return *this;
}
// Routine Description:
// - Advances the iterator backward relative to the underlying text buffer by the specified movement
// Arguments:
// - movement - Magnitude and direction of movement.
// Return Value:
// - Reference to self after movement.
TextBufferCellIterator& TextBufferCellIterator::operator-=(const ptrdiff_t& movement)
{
ptrdiff_t move = movement;
if (move < 0)
{
return (*this) += (-move);
}
auto newPos = _pos;
while (move > 0 && !_exceeded)
{
_exceeded = !_bounds.DecrementInBounds(newPos);
move--;
}
_SetPos(newPos);
_GenerateView();
return (*this);
}
// Routine Description:
// - Advances the iterator forward relative to the underlying text buffer by exactly 1
// Return Value:
// - Reference to self after movement.
TextBufferCellIterator& TextBufferCellIterator::operator++()
{
return this->operator+=(1);
}
// Routine Description:
// - Advances the iterator backward relative to the underlying text buffer by exactly 1
// Return Value:
// - Reference to self after movement.
TextBufferCellIterator& TextBufferCellIterator::operator--()
{
return this->operator-=(1);
}
// Routine Description:
// - Advances the iterator forward relative to the underlying text buffer by exactly 1
// Return Value:
// - Value with previous position prior to movement.
TextBufferCellIterator TextBufferCellIterator::operator++(int)
{
auto temp(*this);
operator++();
return temp;
}
// Routine Description:
// - Advances the iterator backward relative to the underlying text buffer by exactly 1
// Return Value:
// - Value with previous position prior to movement.
TextBufferCellIterator TextBufferCellIterator::operator--(int)
{
auto temp(*this);
operator--();
return temp;
}
// Routine Description:
// - Advances the iterator forward relative to the underlying text buffer by the specified movement
// Arguments:
// - movement - Magnitude and direction of movement.
// Return Value:
// - Value with previous position prior to movement.
TextBufferCellIterator TextBufferCellIterator::operator+(const ptrdiff_t& movement)
{
auto temp(*this);
temp += movement;
return temp;
}
// Routine Description:
// - Advances the iterator negative relative to the underlying text buffer by the specified movement
// Arguments:
// - movement - Magnitude and direction of movement.
// Return Value:
// - Value with previous position prior to movement.
TextBufferCellIterator TextBufferCellIterator::operator-(const ptrdiff_t& movement)
{
auto temp(*this);
temp -= movement;
return temp;
}
// Routine Description:
// - Provides the difference in position between two iterators.
// Arguments:
// - it - The other iterator to compare to this one.
ptrdiff_t TextBufferCellIterator::operator-(const TextBufferCellIterator& it)
{
THROW_HR_IF(E_NOT_VALID_STATE, &_buffer != &it._buffer); // It's not valid to compare this for iterators pointing at different buffers.
return _bounds.CompareInBounds(_pos, it._pos);
}
// Routine Description:
// - Sets the coordinate position that this iterator will inspect within the text buffer on dereference.
// Arguments:
// - newPos - The new coordinate position.
void TextBufferCellIterator::_SetPos(const COORD newPos)
{
if (newPos.Y != _pos.Y)
{
_pRow = s_GetRow(_buffer, newPos);
_attrIter = _pRow->GetAttrRow().cbegin();
_pos.X = 0;
}
if (newPos.X != _pos.X)
{
const auto diff = gsl::narrow_cast<ptrdiff_t>(newPos.X) - gsl::narrow_cast<ptrdiff_t>(_pos.X);
_attrIter += diff;
}
_pos = newPos;
_GenerateView();
}
// Routine Description:
// - Shortcut for pulling the row out of the text buffer embedded in the screen information.
// We'll hold and cache this to improve performance over looking it up every time.
// Arguments:
// - buffer - Screen information pointer to pull text buffer data from
// - pos - Position inside screen buffer bounds to retrieve row
// Return Value:
// - Pointer to the underlying CharRow structure
const ROW* TextBufferCellIterator::s_GetRow(const TextBuffer& buffer, const COORD pos)
{
return &buffer.GetRowByOffset(pos.Y);
}
// Routine Description:
// - Updates the internal view. Call after updating row, attribute, or positions.
void TextBufferCellIterator::_GenerateView()
{
_view = OutputCellView(_pRow->GetCharRow().GlyphAt(_pos.X),
_pRow->GetCharRow().DbcsAttrAt(_pos.X),
*_attrIter,
TextAttributeBehavior::Stored);
}
// Routine Description:
// - Provides full fidelity view of the cell data in the underlying buffer.
// Arguments:
// - <none> - Uses current position
// Return Value:
// - OutputCellView representation that provides a read-only view into the underlying text buffer data.
const OutputCellView& TextBufferCellIterator::operator*() const noexcept
{
return _view;
}
// Routine Description:
// - Provides full fidelity view of the cell data in the underlying buffer.
// Arguments:
// - <none> - Uses current position
// Return Value:
// - OutputCellView representation that provides a read-only view into the underlying text buffer data.
const OutputCellView* TextBufferCellIterator::operator->() const noexcept
{
return &_view;
}
COORD TextBufferCellIterator::Pos() const noexcept
{
return _pos;
}