Some time ago, while testing various configurations of a plugin that makes 
very heavy use of syntax regions, I noticed that in certain configurations, 
redraws were timing out after the default 2 second 'redrawtime'. In an 
attempt to understand why, I profiled Vim and discovered that much of the 
time was spent in the in_id_list() function, which was called over 115 
million times in the space of a few seconds. Shocked that any function 
could need to be called so frequently, I began digging around in the Vim source 
to understand what was happening. What I discovered was that in_id_list() 
is used in a very inefficient strategy for finding all syntax groups in the 
current group's 'contains' or 'nextgroup' list. The calls are made within 
syn_current_attr() in a loop over *all* syntax items, and because  
in_id_list() performs a linear search of the 'contains' or 'nextgroup' list 
for each iteration of this outer loop, the cost of the algorithm is O(M x 
N), where M is the number of defined syntax items, and N is the size of the 
current item's 'contains' or 'nextgroup' list. Moreover, this nested loop 
is executed each time the current syntax item changes, so buffers with lots 
of syntax regions in a small screen area will be hit especially hard.

This approach seemed needlessly inefficient. Clauses like 'nextgroup' and 
'contains' provide constraints that ought to make finding next/contained 
groups faster, but the current implementation doesn't take advantage of 
this. Also, the searches of these lists are linear, and thus, slower than 
they could be. As a proof-of-concept, I prototyped an algorithm that 
attempts to "invert" the following loop within syn_current_attr():

for (idx = syn_block->b_syn_patterns.ga_len; --idx >= 0; ) { ... }

Under the prototyped approach, iteration is controlled by an inline 
stateful iterator function called syn_current_attr_loop_iter(), which 
yields the next syntax item to check each time it's called. The function 
operates in several modes, which govern how the list of candidate items is 
generated. At the fast end of the spectrum is the "IDS" mode, which 
iterates over syntax groups and clusters in the current item's 'contains' 
or 'nextgroup' list; at the slow end of the spectrum is the "SLOW" mode, 
which iterates over *all* syntax items, just like the original 
implementation on master. In between the two extremes are modes "IDXS" and 
"CTDIN": the former handles virtual groups like TOP and CONTAINED; the 
latter ensures we check items that aren't in a 'contained' list, but 
mention the current group in their own 'containedin' list. In certain 
scenarios, special iterator logic ensures "fall back" to "CTDIN" mode after 
all the groups found by "IDS" mode have been processed. Partially sorted, 
denormalized growarrays have been added to support some of this, and the 
search within in_id_list() has been changed from linear to binary. Although 
I didn't do exhaustive testing on my prototype, I did see a significant 
performance improvement with no loss of functionality on a very complex 
syntax. For instance, a single redraw that had taken 3.38 seconds on master 
took only 0.96 seconds on the prototype branch. The performance increase 
was even more dramatic before I used the information gleaned during my 
investigation to optimize my plugin's syntax definition. No doubt, 
developers more familiar with Vim's syntax engine could achieve much 
greater performance gains. The handling of 'containedin' could definitely 
be improved. In my prototype, I consider all items with a 'containedin' 
clause to be candidates for inclusion in the current group; however, it 
should be possible to create additional denormalized data structures 
representing the reverse associations (container => containee) and update 
them whenever a 'containedin' clause is processed. Complicating this 
approach is the fact that the containing group's syntax items may not yet 
exist when the corresponding 'containedin' clause is processed, so there 
would probably need to be a mechanism for expanding placeholders at the 
point of first use.

For anyone interested, the prototype is on branch "invloop" of the 
following (Vim 8) fork:
https://github.com/bpstahlman/vim.git

The plugin I used for benchmarking and testing is Txtfmt.
https://github.com/bpstahlman/txtfmt

This plugin is a natural stress test because it creates *many* syntax 
regions (thousands in some configurations) and its test page creates lots 
of regions in a single screen.

Thanks, Brett S.

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/45eeecf4-278e-4070-8508-535ecac39853n%40googlegroups.com.

Raspunde prin e-mail lui