After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 776811 - Responsiveness problem with long lines in combination with the GtkSourceView no-spell-check tag
Responsiveness problem with long lines in combination with the GtkSourceView ...
Status: RESOLVED FIXED
Product: gspell
Classification: Other
Component: general
1.3.x
Other Linux
: Normal normal
: ---
Assigned To: gspell maintainers
gspell maintainers
Depends on:
Blocks:
 
 
Reported: 2017-01-03 13:27 UTC by Sébastien Wilmet
Modified: 2017-04-02 13:35 UTC
See Also:
GNOME target: ---
GNOME version: ---



Description Sébastien Wilmet 2017-01-03 13:27:05 UTC
With the following line in LaTeXila (1113 chars), typing new text is not responsive. Holding backspace pressed is not a problem, in that case deleting the text is responsive, the problem occurs only when typing new text.

LaTeXila uses GtkSourceView, and with the LaTeX syntax highlighting, the no-spell-check tag is used. When editing the same line in the gspell test-text-view (which doesn't use GtkSourceView), there is no responsiveness problem.

-----

\paragraph{Problème rencontré} La communication par Xbee n'est pas robuste, il y a eu souvent des problèmes : l'Arduino ne reçoit pas la commande (il y a donc un time-out au niveau de \texttt{laser-gui-controller}), ou bien l'Arduino répond plusieurs fois, etc. Ce problème arrive aussi avec l'IDE d'Arduino, mais plus rarement. C'est sans doute la configuration de la communication série qui est différente\footnote{Voir à ce sujet \url{http://mirror.datenwolf.net/serial/} \textit{Serial Programming Guide for POSIX Operating Systems} : il y a \emph{beaucoup} d'options possibles. Pour la communication série entre un Arduino et un PC, ce code a été trouvé : \url{https://github.com/todbot/arduino-serial}, ce qui a amélioré les choses, mais ce n'est toujours pas parfait.}. Mais ça reste une communication série, ce n'est pas aussi fiable que TCP/IP par exemple. TCP fait des vérifications pour savoir si un paquet a bien été transmis, et renvoie le paquet si besoin ; avec une communication série, il n'y a pas ce genre de vérification, ce serait à nous de le faire nous-même, en numérotant les messages etc.

-----
Comment 1 Sébastien Wilmet 2017-02-19 17:00:57 UTC
I think the GtkSourceView no-spell-check tag is removed and re-applied for the whole line. It's maybe possible in GtkSourceView to do less changes for the context class tags.

Another solution, in gspell, that would be beneficial in any case: instead of using timeouts, use a timer to analyze the buffer during only a certain maximum amount of time, with an idle function. So as soon as an idle iteration exceed the time threshold, we return from the idle function to let the main loop process other events. I think a good time threshold is 1/4 of one frame at 60Hz, so around 4ms. So if the main loop becomes busy, gspell takes 4ms, which should leave enough time for the other stuff to run. If gspell takes 1/2 of a frame, it's maybe too much.
Comment 2 Sébastien Wilmet 2017-03-31 13:07:54 UTC
I've started to debug the problem.

(In reply to Sébastien Wilmet from comment #1)
> I think the GtkSourceView no-spell-check tag is removed and re-applied for
> the whole line.

Yes I confirm this, each time the line is modified, the whole line is re-checked.

Re-checking the whole line (approximately 180 words) takes 200ms on my computer (Intel core i5). After removing the assertions in check_subregion(), it takes 170ms, still too much for a single main loop iteration.

Here are some timing debug infos:

check_visible_region(): start.
check_subregion(): [line=0,offset=0 ; line=0,offset=1112]
check_subregion(): 1.287000 ms: before loop.
check_visible_region_in_view(): 169.605000 ms: check subregion
check_visible_region(): took 169.774000 ms.

The loop in check_subregion() takes 169.605 - 1.287 = 168.318 ms.
The rest of the code executed during check_visible_region() takes 169.774 - 168.318 = 1.456ms.

In the loop, it takes on average a bit less than 1ms per word. The worst loop iteration took 2ms.

I think that the _gspell_text_iter functions are quite slow. From experience in refactoring similar code in latexila and gtksourceview, working on strings instead of GtkTextIters was ~4x faster. Even if the loop is 4x faster, it would take 42ms for 180 words, which is far from 60FPS. And there can be longer lines, 180 words is not that much.

So I think that I'll add a GTimer and stop the loop after a certain threshold, but ensure that at least one word has been checked (to avoid an infinite loop on slow computers). It will be a bit trickier for removing the tag, it must not be removed for the whole subregion at once, to avoid flickering.

I think that the GTimer approach will be easier to implement than working on strings (moreover by working on strings the obvious implementation is to do line per line, but in some languages a word can span multiple lines IIRC).
Comment 3 Sébastien Wilmet 2017-03-31 13:22:58 UTC
(In reply to Sébastien Wilmet from comment #1)
> I think a good time threshold is 1/4 of
> one frame at 60Hz, so around 4ms.

From the above data, 4ms would be a bit short, it could only check 2 or 3 words at a time on a relatively powerful machine. Doing 60FPS for a text editor is not critically important, here it's just removing or adding underlines, it's not a sophisticated 3D animation (although one could run in parallel, with GSK arriving it'll maybe even be the case in the not-too-distant future). So if the text editor does 30FPS, it'll still feel responsive. Anyway it'll be easy to change the time threshold, it'll just be a #define.
Comment 4 Sébastien Wilmet 2017-03-31 17:49:04 UTC
I've pushed those two commits:
commit e4ff54033584f1602c20985f5e1cc7fee0fc81b4
commit 12b7f2dd0c44caaa17e8f9a5231dccf67af10f59

to avoid a full redraw of the GtkTextView each time an underline is removed. So it'll give more time for each main loop iteration during spell-checking with the GTimer solution.
Comment 5 Sébastien Wilmet 2017-04-01 16:13:33 UTC
1ms per word is very slow.

I think the following method will give better results:

The goal is to minimize the number of GtkTextView function calls.

1. Adjust GtkTextIters with the _gspell_text_iter*() functions (those functions are called only once).
2. Call gtk_text_buffer_get_slice().
3. Compute the PangoLogAttr array.
4. Skip the no-spell-check region by removing word boundaries in the PangoLogAttrs.
5. Improve word boundaries in the PangoLogAttrs.
6. Spell-check the words (no need to re-allocate strings, we navigate through the extracted slice).
7. Remove the tag in the whole subregion.
8. Insert the tag for misspelled words.

It needs to be implemented to see how much faster is it, to see if the GTimer is still necessary. With such an implementation we need to decide up front how much text we want to analyze synchronously: it can be one line and we check the GTimer after each line, or it can be less than one line if necessary, but it's more difficult to stop the function in the middle of the above steps.
Comment 6 Sébastien Wilmet 2017-04-02 13:35:46 UTC
Fixed!

commit 36368aff96c26948fcfae18073d2fd2b4b09a6f0 and some commits before and after.

Sometimes it doesn't do 60 FPS, but it feel responsive at least.

The algo described in comment #5 was not followed exactly, step 4 is done differently, step 7 is done at the beginning and step 8 is done at the same time as step 6.

There is still room for performance improvement, but it's already much better than before, so I consider it good enough. And good enough is good enough.