Optimize copy of State
Previous time with highlight_benchmark: 20.2s
New Time with highlight_benchmark: 19.5s
My analysis for the next stage:
Looking at how State is used and how it could be used more efficiently, I think we should change the interface for KF6:
- Prohibit copying and only allow moving
- no longer have QExplicitlySharedDataPointer as a member
Zero copy
The 3 projects I looked at make copies of objects that are ultimately temporary. Copies that can be avoided with references and moves. Note that when I say copy, I mean increment the internal counter. But as this commit shows, which reduces its use, there is an impact that is not so negligible (3.4% here (which seems a lot to me, but I've done the measurements several times and used ksyntaxhighlighter6 -fansi
on 25K, 100K and 600K line files, I still get around 3%)).
const KSyntaxHighlighting::State initialState(!prevLine ? KSyntaxHighlighting::State() : prevLine->highlightingState()); // copy
const KSyntaxHighlighting::State endOfLineState = highlightLine(textLine->text(), initialState);
if (textLine->highlightingState() != endOfLineState) {
textLine->setHighlightingState(endOfLineState); // copy internally
}
The first copy can be avoided by first declaring a State
variable initialized by default and using a reference like this:
/*static*/ const KSyntaxHighlighting::State emptyState; // we could provide `static const State &State::emptyState() noexcept`
// or global variable
const KSyntaxHighlighting::State &initialState(!prevLine ? emptyState : prevLine->highlightingState()); // no-copy
const KSyntaxHighlighting::State endOfLineState = highlightLine(textLine->text(), initialState);
The second copy can be replaced by a move: textLine->setHighlightingState(std::move(endOfLineState))
KSyntaxHighlighting::State previousLineState;
if (TextBlockUserData *data = TextDocumentLayout::textUserData(previousBlock))
previousLineState = data->syntaxState(); // copy + copy (syntaxState returns a copy instead of a reference)
KSyntaxHighlighting::State oldState;
if (TextBlockUserData *data = TextDocumentLayout::textUserData(block)) {
oldState = data->syntaxState(); // copy + copy
}
KSyntaxHighlighting::State state = highlightLine(text, previousLineState);
if (oldState != state) {
TextBlockUserData *data = TextDocumentLayout::userData(block);
data->setSyntaxState(state); // copy + copy
}
Same principle as for KTE: initialized variable + use of reference / pointer. 6 copies replaced by just one construct and 1 move.
- This project with SyntaxHighlighter behaves in the same way as the other two.
Following this analysis, I think that disallowing copying will result in optimal code. We could add a clone()
function that does explicit copying, but I doubt it would have any real use.
No SharedDataPointer
The second point concerns QExplicitlySharedDataPointer
. Since copying is useless, so is sharing. At worst, the user can put State in his own class that implements sharing. Something already done in KTE, for example, where textLine is, if I've understood correctly, always in a std::shared_ptr. I get the impression that shared classes containing shared values is a very pre-C++11 recurring pattern, when move semantics did not exist...