Using idea of SplitView, Document::SetStyles() and Document::SetStyleFor() can be optimized to update style on each segment, this eliminated four boundary check (style.ValueAt() + style.SetValueAt()) for each byte. when precise range for ModificationFlags::ChangeStyle notification is not needed (like Document::SetStyleFor()), memcmp, memcpy, and memset can be used to set new styles, attached patch does this (use single method for CellBuffer to reduce code size, Document::SetStyleFor() and Document::SetStyles() can be changed to call a new method to reduce duplication).
This appears to produce a larger range in the
ChangeStylenotification that could require more work for the application to process.It's same as
Document::SetStyleFor(), which report entail range as changed.SetStylesis the main method used by lexers so is the dominant cause ofChangeStylenotifications.It may be useful to report the truly changed range for
SetStyleFor.Better performance might be possible by pushing more of
SetStylesdeeper intoCellBufferorSplitVectorbut this should only be done if there is a significant benefit.Styling time is reduced by 1/3 (measured with Notepad4) with the patch, not measured byte comparison/updating/precise range version.
Last edit: Zufu Liu 2026-03-16
The result for https://dumps.wikimedia.org/enwiki/20260301/enwiki-20260301-pages-articles-multistream11.xml-p6899367p7054859.bz2 (unpack, then add empty line at begging to avoid brace match) on my system:
Styling performance for SciTE is measured with following changes:
Following is changes for Notepad4 (tested x64 build):
Not figured out why SciTE is about 1 second slow than Notepad4 (LexHTML in Notepad4 does not handles PHP, Python and other templates).
Providing a more precise change range can enable large optimizations when the modification drives an expensive operation like linting, spell checking or remote view updating.
Only updating the necessary region has been an ongoing direction for other features like the
ReplaceType::minimaloption toReplaceTarget.Something like the following could be a basis for a faster implementation. It would need to be expanded to handle the buffer split instead of compacting the buffer. It uses two simple functions
firstDifferenceandlastDifferencethat takes 2 equally long blocks and returns a pointer to the first or last char in the first buffer that is different to the corresponding char in the second buffer.Last edit: Neil Hodgson 2026-03-19
A different implementation for precise range changes:
It can be changed to return a pair/range to simplify function signature, if the range is empty (end position is zero), then no style changes.
Return style changed range version of above changes.
The
truepath in the templates is always taken before thefalseand therangealways starts out empty so thesegment1template argument and hence the template isn't needed. Maybe add a range extending method toStyleChangeRangeas this is common to the functions.A similar reduction in modification range for unchanged values is currently implemented in
Document::TrimReplacementso it may be useful in other contexts to include this inSplitVector. There is currently aSplitVector::GetRangeto retrieve a range of values so aSplitVector::SetRangemay be useful here and also in other situations. ThenCellBuffer::SetStylesis mostly a combination of the two.segment1template argument is attempt to make compiler inline the function (as it only instantiated once), without it msvc will generate a function call for second segment (though removedrange.Empty()check for first segment).Inlining was one of the reasons for separating the range trimming from the memory copying. Functions like
firstDifferenceare simple and likely to inline.It can be widened to consider 4 or 8 bytes at a time if that performs better. Couldn't convince Visual C++ to auto-vectorize though.
Many user actions are small and localized: typing a character in a comment or adding to the end of an identifier often only changes a single style byte.
std::mismatch is the standard library version of a check for first difference. Its likely less well optimized than a loop although it does take an
std::executionso could potentially be parallelized or vectorized.parallelized (also find_first_not_of and find_last_not_of) looks overkill for a 4KB range.
Attached my current version. The generated code seems good.
(position < 0) || (length <= 0) || ((position + length) > style.Length())assuming initial (after call
Document::StartStyling())endStyledis within document position range, this changed previously truncation behavior, though updatedendStyledmay larger then document length.truncation can be used to simplify lexer:
StyleContextcould usestyler.ColourTo(currentPos - 1, state);instead ofstyler.ColourTo(currentPos - ((currentPos > lengthDocument) ? 2 : 1), state);.StyleContextdoes. for LexHTML it's only needed for PHP where?>can be omitted and is preferred to avoid extra content.OK. Truncating length in
SetStylesandSetStyleForseems reasonable. Moving this and subdividing over the gap into a commonSplitUpdate. Whenlength1is 0,SplitUpdatecould movelength2tolength1similar to thelength1 ? length1 : length2logic. There's also similar splitting logic inGetRange.Edit: added
CopyBytessince it addedoffsetargument.Edit 2: adding the
offsetargument toCopyBytesstops inlining so dropping that. The single range code runs for initial styling and the two range code runs for small edits since styling restarts at the beginning of a line and the editing mostly occurs after the line start, moving the gap to the caret.Last edit: Neil Hodgson 2026-03-24
Source code with your
CopyBytesis shorter (msvccl /utf-8 /W4 /c /EHsc /std:c++20 /O2 /GS- /GR- /FAcs /DNDEBUG /I../include CellBuffer.cxxnot inlineCopyBytes), but msvc generated asm with mySetStylesRangeis shorter.changes compared to SetStyles0319Range.diff.
Scintilla commonly builds with link time code generation which enables more inlining. That is
cl /GLandlink -LTCG.Depending on which function gets more of the code, the inliner is bouncing between inlining
CopyBytesintoCellBuffer::SetStylesor inliningCellBuffer::SetStylesintoDocument::SetStyles. It is unlikely saving one call (the potential secondCopyBytes) in this context is important..OK.
Committed with [4f2ea7] and [281e29].
Related
Commit: [281e29]
Commit: [4f2ea7]
Update for
endStyled += length;isn't consistent: one before the notification one after.