[ktexteditor] much faster positionFromCursor
ClosedPublic

Authored by jtamate on Apr 7 2018, 11:16 AM.

Details

Summary

Use mutable variables to store information about last call to positionFromCursor.
As most of the time the cursor is close to the previous position, avoid to calculate every time the size of the lines from the beginning of the document, just calculate it from the previous cursor position.

Test Plan

Open a callgrind log file with more than 1.00.000 lines,
go to the end of the file, and then play with the PageUp, PageDown and cursor keys.
Before: it was unable to do continuous PageUp refreshes.
After: the PageUp at any part of the file is as fast as in the beginning of the document.

Diff Detail

Repository
R39 KTextEditor
Lint
Lint Skipped
Unit
Unit Tests Skipped
jtamate created this revision.Apr 7 2018, 11:16 AM
Restricted Application added projects: Kate, Frameworks. · View Herald TranscriptApr 7 2018, 11:16 AM
Restricted Application added a subscriber: Frameworks. · View Herald Transcript
jtamate requested review of this revision.Apr 7 2018, 11:16 AM
cullmann requested changes to this revision.Apr 8 2018, 9:16 AM
cullmann added a subscriber: cullmann.

Hi,
I appreciate the intend to speed this up.
But I think, to do this proper, it might make more sense to cache the current position in the KateViewAccessible and invalidate it on changes to the document.
The way you do it now, it might not be correct if text changes occur in-between. (if I don't overlook something)

This revision now requires changes to proceed.Apr 8 2018, 9:16 AM
jtamate updated this revision to Diff 31733.Apr 9 2018, 11:08 AM
jtamate edited the summary of this revision. (Show Details)
jtamate edited the test plan for this revision. (Show Details)
jtamate added a reviewer: Frameworks.

Cached the position in static variables of KateViewAccessible.
The cache is invalidated when the signal Document::textChanged is received.
Unfortunately, KateViewAccessible must inherit also QObject or connect will not work, because KateViewAccessible didn't inherit QObject.
Is this change Binary compatible?

At first I thought the changes will come in setText, but in my tests it has never been called.

To check the correctness of the result, I've run the fast and slow paths in kate, with several windows over the same document, changing it in sereveral ways, and the results always match.

Hi, must the variable be static or would an object member be good enough?

Hi, must the variable be static or would an object member be good enough?

It can't be a member object, because some methods that call positionFromCursor are const. I tried :-(

You can use a mutable member for that.

jtamate updated this revision to Diff 31737.Apr 9 2018, 1:22 PM
jtamate edited the summary of this revision. (Show Details)

Implemented now as object method instead of static.
Changed the calls from KateViewInternal from static to use the reference the method QAccessible::queryAccessibleInterface(this) returns.

brauch added a subscriber: brauch.Apr 9 2018, 4:47 PM

Re. binary compatibility: should be fine because this class is not exported (no KTEXTEDITOR_EXPORT macro).

brauch added a comment.Apr 9 2018, 4:49 PM

Oh and, you do not need to inherit QObject to use connect; you can connect to a lambda calling the member function AFAIK or so. Just omit the third argument in connect(). What you lose by doing this is the automatic disconnect of the connection when the receiver object is deleted, so make sure that doesn't happen.

jtamate updated this revision to Diff 31760.Apr 9 2018, 6:30 PM

No QObject inheritance, only a QMetaObject::Connection member to disconnect the connection.
As this is a non exported class, use the same method name and make it non-static.
KateViewInternal::cursorMoved checks if QAccessible::isActive().

mwolff added a subscriber: mwolff.Apr 10 2018, 8:12 PM

some nits, otherwise lgtm assuming all tests pass

bonus points for a proper benchmark that shows the before/after gain in hard numbers

src/view/kateviewaccessible.h
191 ↗(On Diff #31733)

I know the old code used int already, but shouldn't this better be a quint64 as it's a file offset?

192 ↗(On Diff #31733)

const auto *doc =

204 ↗(On Diff #31733)

style: join with next line

215 ↗(On Diff #31733)

dito

231 ↗(On Diff #31733)

unrelated change?

270 ↗(On Diff #31733)

maybe also mention that this is a file offset?

jtamate updated this revision to Diff 31853.EditedApr 11 2018, 8:18 AM
jtamate marked 5 inline comments as done.

Included more comments.
Addressed style coding.

In a callgrind log file with 1606189 lines, at the end of the file, pressing PageUp, using the code included at the end:

old implementation elapsed = 122
new implementation elapsed = 0
old implementation elapsed = 119
new implementation elapsed = 0

After some insertions:
old implementation elapsed = 137
new implementation elapsed = 118
old implementation elapsed = 123
new implementation elapsed = 0

--- src/view/kateviewaccessible.h
+++ src/view/kateviewaccessible.h
@@ -29,6 +29,8 @@
 #include <QAccessible>
 #include <KLocalizedString>
 #include <QAccessibleWidget>
+#include <QElapsedTimer>
+#include <QDebug>
 
 /**
  * This class implements a QAccessible-interface for a KateViewInternal.
@@ -194,6 +196,9 @@ public:
      */
     int positionFromCursor(KateViewInternal *view, const KTextEditor::Cursor &cursor) const
     {
+        QElapsedTimer t;
+        t.start();
+
         int pos = m_lastPosition;
         const auto *doc = view->view()->document();
 
@@ -229,6 +234,22 @@ public:
         m_lastCursor = cursor;
         m_lastPosition = pos;
 
+        qWarning() << "new implementation elapsed = " << t.restart();
+        // previous implementation
+        int _pos = 0;
+        for (int _line = 0; _line < cursor.line(); ++_line) {
+            // length of the line plus newline
+            _pos += view->view()->document()->line(_line).size() + 1;
+        }
+        _pos += cursor.column();
+        qWarning() << "old implementation elapsed = " << t.elapsed();
+
+        if (_pos != (pos + cursor.column())) {
+            qWarning() << "implementations differ, old=" << _pos << " new=" <<
+pos + cursor.column();
+        }
+
         return pos + cursor.column();
     }
jtamate marked an inline comment as done.Apr 11 2018, 8:19 AM
src/view/kateviewaccessible.h
191 ↗(On Diff #31733)

All the methods of QAccesibeTextInrface expect an int.
What should be done if the document has more data than INT_MAX?
deactivate the class and show a warning? In any case, I think this is out of scope of this patch.

mwolff added a comment.May 1 2018, 6:15 PM

lgtm, @cullmann ?

regarding the accessibility interface, I agree that it's out of the scope of this patch. I'd say let's keep it like that for now...

cullmann accepted this revision.May 2 2018, 5:20 AM

I am ok with it as it is ;=)
I would have been ok with the version that still inherits from QObject, too, but now that one has done all the hassle to avoid that ok ;)

This revision is now accepted and ready to land.May 2 2018, 5:20 AM

So design is broken,for me. Function should be static and cursor from view (KateViewInternal) should be compared against cursor parameter.

Hmm, I don't understand that comment?
Now you have one (or more) object per view, or?
That looks sane to me.
Or do I misunderstand how that works?

Maybe i miss something, but i think if something should be extended it's view not accessible.

Maybe i miss something, but i think if something should be extended it's view not accessible.

Do you mean to extend KateViewInternal in such a way that positionFromCursor in KateViewAccessible becomes unnecessary?

Do you mean to extend KateViewInternal in such a way that positionFromCursor in KateViewAccessible becomes unnecessary?

Yes, mainly it should be a tiny helper.

Actually, given the functionality is only used there, if that code is in the view or in that helper class makes no real difference, or?

Actually, given the functionality is only used there, if that code is in the view or in that helper class makes no real difference, or?

I've tried to extend KateViewInternal in such a way that positionFromCursor in KateViewAccessible becomes unnecessary, but it became a mess.
The problem is that all the "coordinates" used there are Cursors, that are in the form line+column.
But in KateViewAccessible it uses characters from the beginning, and caching only in one place (positionFromCursor) does not create a mess.

If the stuff still works for you and is fast now, I am still for merging that.
If somebody has time to do a even nicer integration, a new request can be done afterwards.

This revision was automatically updated to reflect the committed changes.
volkov added a subscriber: volkov.Sep 11 2018, 10:52 AM

I get the following warning from valgrind because of this change:

==3621== Conditional jump or move depends on uninitialised value(s)
==3621==    at 0x4FF3605: KateViewAccessible::positionFromCursor(KateViewInternal*, KTextEditor::Cursor const&) const (kateviewaccessible.h:201)
==3621==    by 0x4FE41CA: KateViewInternal::documentTextInserted(KTextEditor::Document*, KTextEditor::Range const&) (kateviewinternal.cpp:3830)
==3621==    by 0x967762A: call (qobjectdefs_impl.h:376)
==3621==    by 0x967762A: QMetaObject::activate(QObject*, int, int, void**) (qobject.cpp:3754)
==3621==    by 0x50C25E6: KTextEditor::DocumentPrivate::textInserted(KTextEditor::Document*, KTextEditor::Range const&) (moc_katedocument_6APBFLF6VF33HC.cpp:985)
==3621==    by 0x4F498DE: KTextEditor::DocumentPrivate::editInsertText(int, int, QString const&) (katedocument.cpp:1235)
==3621==    by 0x4F4DF67: KTextEditor::DocumentPrivate::insertText(KTextEditor::Cursor const&, QString const&, bool) (katedocument.cpp:780)
==3621==    by 0x4F51838: KTextEditor::DocumentPrivate::typeChars(KTextEditor::ViewPrivate*, QString const&) (katedocument.cpp:3024)
==3621==    by 0x4FE9BF4: KateViewInternal::keyPressEvent(QKeyEvent*) (kateviewinternal.cpp:2522)
==3621==    by 0x4FF1F12: KateViewInternal::eventFilter(QObject*, QEvent*) (kateviewinternal.cpp:2370)
==3621==    by 0x96493EC: QCoreApplicationPrivate::sendThroughObjectEventFilters(QObject*, QEvent*) (qcoreapplication.cpp:1174)
==3621==    by 0x8284834: QApplicationPrivate::notify_helper(QObject*, QEvent*) (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.11.0)
==3621==    by 0x828D30E: QApplication::notify(QObject*, QEvent*) (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.11.0)
==3621==  Uninitialised value was created by a heap allocation
==3621==    at 0x4C2C25F: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==3621==    by 0x4FE3EF0: accessibleInterfaceFactory(QString const&, QObject*) (kateviewaccessible.h:288)
==3621==    by 0x8A70D2C: QAccessible::queryAccessibleInterface(QObject*) (in /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5.11.0)
==3621==    by 0x8A7172E: QAccessibleEvent::accessibleInterface() const (in /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5.11.0)
==3621==    by 0x8A71BFC: QAccessible::updateAccessibility(QAccessibleEvent*) (in /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5.11.0)
==3621==    by 0x82C0A72: QWidgetPrivate::show_helper() (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.11.0)
==3621==    by 0x82C3CC4: QWidget::setVisible(bool) (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.11.0)
==3621==    by 0x82C0957: QWidgetPrivate::showChildren(bool) (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.11.0)
==3621==    by 0x82C09DE: QWidgetPrivate::show_helper() (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.11.0)
==3621==    by 0x82C0946: QWidgetPrivate::showChildren(bool) (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.11.0)
==3621==    by 0x82C09DE: QWidgetPrivate::show_helper() (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.11.0)
==3621==    by 0x82C3CC4: QWidget::setVisible(bool) (in /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5.11.0)

I get the following warning from valgrind because of this change:

Fix in D15420