tmhedberg/SimpylFold

Slow

Closed this issue · 7 comments

Even opening moderately small files can cause up to a second lag.

Here's the timings for opening the file "seaborn/categorical.py" from the popular Python plotting package Seaborn. Per the documentation (:help profile), the "self" time will be wrong since SimpylFold is called recursively.

FUNCTION  SimpylFold()
Called 6982 times
Total time:   0.809100
Self time:   0.360778

count  total (s)   self (s)

                                " If we are starting a new sweep of the buffer (i.e. the current line
                                " being folded comes before the previous line that was folded), initialize
                                " the cache of results of calls to `s:NumContainingDefs`
6982              0.016330     if !exists('b:last_folded_line') || b:last_folded_line > a:lnum
50              0.000235         let b:cache_NumContainingDefs = {}
50              0.000052         let b:in_docstring = 0
50              0.000013     endif
6982              0.008365     let b:last_folded_line = a:lnum

                                " If this line is blank, its fold level is equal to the minimum of its
                                " neighbors' fold levels, but if the next line begins a definition, then
                                " this line should fold at one level below the next
6982              0.010268     let line = getline(a:lnum)
6982              0.024489     if line =~ s:blank_regex
1944              0.003536         let next_line = nextnonblank(a:lnum)
1944              0.001479         if next_line == 0
                                        return 0
                                    elseif getline(next_line) =~# s:def_regex
168              0.000292             return SimpylFold(next_line) - 1
                                    else
1776              0.001048             return -1
                                    endif
                                endif

5038              0.013018     let fold_docstrings = !exists('g:SimpylFold_fold_docstring') || g:SimpylFold_fold_docstring
5038              0.031019     let docstring_match = matchlist(line, s:docstring_start_regex)
5038              0.009477     let prev_line = getline(a:lnum - 1)
5038              0.033692     if !b:in_docstring && ( prev_line =~# s:def_regex || prev_line =~ s:multiline_def_end_regex ) && len(docstring_match)
    4   0.000179   0.000013         let this_fl = s:NumContainingDefs(a:lnum) + fold_docstrings
    4              0.000004         let b:in_docstring = 1
    4              0.000006         if docstring_match[1] == '"""'
    4              0.000008             let b:docstring_end_regex = s:docstring_end_double_regex
    4              0.000001         else
                                        let b:docstring_end_regex = s:docstring_end_single_regex
                                    endif
    4              0.000003     elseif b:in_docstring
10   0.000444   0.000036         let this_fl = s:NumContainingDefs(a:lnum) + fold_docstrings
10              0.000036         if line =~ b:docstring_end_regex
    4              0.000006             let b:in_docstring = 0
    4              0.000002         endif
10              0.000005     else
                                    " Otherwise, its fold level is equal to its number of containing
                                    " definitions, plus 1, if this line starts a definition of its own
5024   0.385522   0.035029         let this_fl = s:NumContainingDefs(a:lnum) + (line =~# s:def_regex)

5024              0.002473     endif
                                " If the very next line starts a definition with the same fold level as
                                " this one, explicitly indicate that a fold ends here
5038              0.024561     if getline(a:lnum + 1) =~# s:def_regex && SimpylFold(a:lnum + 1) == this_fl
                                    return '<' . this_fl
                                else
5038              0.003546         return this_fl
                                endif


FUNCTION  <SNR>103_NumContainingDefs()
Called 6380 times
Total time:   0.356571
Self time:   0.351067

count  total (s)   self (s)

                                " Recall memoized result if it exists in the cache
6380              0.011093     if has_key(b:cache_NumContainingDefs, a:lnum)
1382              0.001994         return b:cache_NumContainingDefs[a:lnum]
                                endif

4998              0.007690     let this_ind = indent(a:lnum)

4998              0.003350     if this_ind == 0
250              0.000129         return 0
                                endif

                                " Walk backwards to the previous non-blank line with a lower indent level
                                " than this line
4748              0.005298     let i = a:lnum - 1
15716              0.006771     while 1
15716              0.060302         if getline(i) !~ s:blank_regex
11524              0.014986             let i_ind = indent(i)
11524              0.008927             if i_ind < this_ind
1342              0.002669                 let ncd = s:NumContainingDefs(i) + (getline(i) =~# s:def_regex)
1342              0.000836                 break
                                        elseif i_ind == this_ind && has_key(b:cache_NumContainingDefs, i)
3406              0.005980                 let ncd = b:cache_NumContainingDefs[i]
3406              0.001698                 break
                                        endif
6776              0.002618         endif

10968              0.008147         let i -= 1

                                    " If we hit the beginning of the buffer before finding a line with a
                                    " lower indent level, there must be no definitions containing this
                                    " line. This explicit check is required to prevent infinite looping in
                                    " the syntactically invalid pathological case in which the first line
                                    " or lines has an indent level greater than 0.
10968              0.005700         if i <= 1
                                        let ncd = getline(1) =~# s:def_regex
                                        break
                                    endif

10968              0.004633     endwhile

                                " Memoize the return value to avoid duplication of effort on subsequent
                                " lines
4748              0.010655     let b:cache_NumContainingDefs[a:lnum] = ncd

4748              0.003551     return ncd

Recently i was working with long multi-line lists (3000+ lines) in python files, search and replacing, adding deleting. Working with text blocks of this size while having SimpylFold makes undo unusable. In a "normal" workflow this goes unnoticed and it took me a while to narrow it down.

it is very easy to reproduce:

  1. copy print "test" 3000x
  2. :%s/$/ "other string"/
  3. u
  4. wait wait wait

I confirm that it is very slow with long python file
The current work-around solution for me, while editing the file, is to disable folding.

With a 100 line file if I toggle all the lines to comments MacVim will hang for nearly 10 seconds.

I corroborate the slowness remarks, but honestly I've had the same problem with other folding plugin (for other languages) I've come across and have come to expect it as inherent. Have you ever seen really fast folding in vim?

What I ended up doing is using Pathogen to install SimpylFold then run vim without folding when I'm editing (VIMBLACKLIST=SimpylFold vim) and with folding when I'm browsing/reading... not ideal but practical.

The built-in indent foldmethod is pretty quick even for large files:
autocmd BufEnter * :if line('$') > 1000 | set foldmethod=indent | endif
This prevents the hanging when loading/entering a large buffer, while still providing some form of folding.

foldmethod=indent foldlevel=1 foldnestmax=2
works rather well and it's quick.
perhaps not as nice as SimpylFold, but no lags and got used to it very quickly.

nfnty commented

This is solved in #73. Would be great if you could test the PR.