vimtex icon indicating copy to clipboard operation
vimtex copied to clipboard

Faster syntax highlighting

Open ces42 opened this issue 1 year ago • 4 comments

Hi, I finally found the time to clean up my work on speeding up syntax highlighting.

Are the comments in autoload/vimtex/syntax/core.vim somewhat understandable? Are there other things that need changing?

There's an explicit functional change in this branch: I've added highlighting for \lVert and \rVert. There's also some new restrictions on the arguments of the vimtex#syntax#core#new_env function.

ces42 avatar Sep 30 '24 03:09 ces42

Thanks for this! At a first glance, it looks very good, and I'm very glad to get help at speeding things up! I very much like how you've documented your commits and the speedup from each separate change.

For reference, how are you benchmarking things here? I mean, when you refer to an x % increased performance, what is the exact approach to calculate the speedup? Did you check with both Vim and neovim? I want to verify things on my end, and perhaps also add a CI test that in which I can track progress or changes to the performance in the future.

I'm still very surprised by the conclusion that the old regex engine is faster - it really "feels wrong". I'm curious if we should ask some of the Vim or neovim devs to ask what is happening here..?

Finally, I've made a few updates to the syntax rules which lead to a conflict here. Sorry! I can probably take the job of merging this and sorting out the conflict after the discussions.

Also, for future reference to me and others: See the earlier related discussion in https://github.com/lervag/vimtex/issues/2877.

lervag avatar Oct 05 '24 19:10 lervag

For reference, how are you benchmarking things here? I mean, when you refer to an x % increased performance, what is the exact approach to calculate the speedup? Did you check with both Vim and neovim?

I was using hyperfine before, but later I realized that (at least on linux) you can just count processor cycles which has less noise than wall time. I've only tested nvim so far, but I'll test vim right now. The numbers in the commit messages on this branch were obtained using this script:

#!/bin/bash
vim=vi

while [[ $# -gt 0 ]]; do
    case "$1" in
        --vim)
            vim="$2"
            shift 2
            ;;
	esac
done

if vim --version | grep nvim > /dev/null; then
	vim2="$vim --headless"
else
	vim2="$vim"
fi

fname="nvim0_vimtex$(git log -1 --format="%h").term"
 
export TERM=xterm-kitty
"$vim" -u NONE -c 'color desert' -c 'set nospell' -c 'source work.vim' | sed 's/\x1b\[?\(12\|25\|1004\)[lh]//g' | cat -v > "$fname"

echo 'diff:'
if ! diff "$fname" base.term > /dev/null; then
	echo 'first diff failed, retrying'
	diff "$fname" base.term
fi
echo '---------------------'
echo ''
eval sudo taskset -c 0 nice -n -11 perf stat -r 4 "$vim2 -u NONE -c 'source work.vim'" 2>&1 | sed 's/ \+$//g' | grep -A1 -P 'cycles'

It also checks that the output is identical to the baseline, so you need to run vi -u NONE -c 'color desert' -c 'set nospell' -c 'source work.vim' | sed 's/\x1b\[?\(12\|25\|1004\)[lh]//g' | cat -v > base.term at the commit where the branches split. My work.vim is

set nocompatible
set runtimepath^=../..
set runtimepath+=../../after
filetype plugin indent on
if exists('g:nosyntax') == 0
    syntax enable
endif
set nolazyredraw

set columns=130

function! Scroll()
    let LINES = line('$')
    for s:x in range(2*LINES/winheight(0))
        exe "norm! \<C-D>"
      redraw!
    endfor
    for s:x in range(2*LINES/winheight(0))
      exe "norm! \<C-U>"
      redraw!
    endfor
endfunction

let g:vimtex_syntax_conceal_disable = 1
"let g:vimtex_syntax_conceal_disable = 0
"set conceallevel=2
let g:vimtex_syntax_match_unicode = 0
silent edit main1.tex
if exists('g:nolink') == 0
    syntax link
endif
call Scroll()

echo 'moving to second file...'
silent edit main2.tex
if exists('g:nolink') == 0
    syntax link
endif
call Scroll()

quitall

and I've been using the tex files of https://arxiv.org/abs/1512.07213 and https://arxiv.org/abs/1405.0401 to test things.

ces42 avatar Oct 05 '24 19:10 ces42

OK I've just been testing with vim, and everything looks good. Output is identical to 80c9bc17 and speedup is ~83%.

I had to modify the testing script a bit:

#!/bin/bash
vim=vi
base=0

while [[ $# -gt 0 ]]; do
    case "$1" in
        --vim)
            vim="$2"
            shift 2
            ;;
		--base)
			shift 1
			base=1
			;;
	esac
done

if vim --version | grep nvim > /dev/null; then
	vim2="$vim --headless"
	name='nvim'
else
	vim2="$vim"
	name='vim'
fi

if [ $base -gt 0 ]; then
	fname="base_$name.term"
else
	fname="$name""0_vimtex$(git log -1 --format="%h").term"
fi
 
export TERM=xterm-kitty
"$vim" -u NONE -c 'color desert|set nospell|source work.vim' \
	| sed 's/\x1b\[?\(12\|25\|1004\)[lh]//g' \
	| sed 's/\x1b\[[0-9]\+;1H/\0\n/g' \
	| head -n -1 \
	| cat -v > "$fname"

if [ $base -eq 0 ]; then
	echo 'diff:'
	if ! diff "$fname" base_$name.term > /dev/null; then
		echo 'first diff failed, retrying'
		"$vim" -u NONE -c 'color desert|set nospell|source work.vim' \
			| sed 's/\x1b\[?\(12\|25\|1004\)[lh]//g' \
			| sed 's/\x1b\[[0-9]\+;1H/\0\n/g' \
			| head -n -1 \
			| cat -v > "$fname"
		diff "$fname" base_$name.term
	fi
	echo '---------------------'
	echo ''
fi
eval sudo taskset -c 0 nice -n -11 perf stat -r 4 "$vim2 -u NONE -c 'source work.vim'" 2>&1 | sed 's/ \+$//g' | grep -A1 -P 'cycles'

ces42 avatar Oct 05 '24 20:10 ces42

I'm still very surprised by the conclusion that the old regex engine is faster - it really "feels wrong". I'm curious if we should ask some of the Vim or neovim devs to ask what is happening here..?

I've been looking into this a bit -- there are definitely plenty of inefficiencies in the code of the NFA engine. On linux one of them is that it spends something like 25% of the time allocating and freeing memory. This might however be different on another platform. Also, the old engine still seems to be faster when accounting for this.

ces42 avatar Oct 05 '24 20:10 ces42

I've tried to measure the same as you now between the current master and your latest syntax2 branch. I do observe a significant speedup, but on my end it is closer to ~40~ 60 %. Not sure why it is lower than on your end. It is a little bit lower on Vim than on neovim. I find that hyperfine gives consistent results. So, it seems measuring the cpu cycles directly is a faster way to benchmark here.

lervag avatar Oct 23 '24 20:10 lervag

I'm curious, since you delved deep into this rabbit hole - did you find that there was any significant benefit of changing \( to \%( anywhere?

lervag avatar Oct 23 '24 21:10 lervag

Ok, I've merged your proposed changes now. I did a rebase of everything on top of master and I made a few minor adjustments. I also added the performance tests to the test framework.

Thanks for this, it's a major contribution and very much appreciated!

lervag avatar Oct 23 '24 21:10 lervag

I'm curious, since you delved deep into this rabbit hole - did you find that there was any significant benefit of changing ( to %( anywhere?

Barely. I just tried replacing all \%( with \( in the core syntax and that slows down things by roughly 0.5% on my computer.

ces42 avatar Oct 25 '24 07:10 ces42