Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Source rendering is really slow #9

Closed
bjorn3 opened this issue Jul 10, 2018 · 19 comments
Closed

Source rendering is really slow #9

bjorn3 opened this issue Jul 10, 2018 · 19 comments

Comments

@bjorn3
Copy link
Collaborator

bjorn3 commented Jul 10, 2018

It is responsible for more than ~90% of cpu time during single stepping through liballoc.

bjorn3 added a commit to bjorn3/priroda that referenced this issue Jul 10, 2018
@bjorn3
Copy link
Collaborator Author

bjorn3 commented Jul 21, 2018

trishume/syntect#182

@trishume
Copy link

Just so you know, that bug probably won't significantly impact syntect performance, but the eventual switch to fancy-regex might.

Although syntect should highlight 15,000 lines of Rust per second according to my benchmarks, so if highlighting is taking too much time maybe you can either use syntect's features to only highlight what you need to display, or maybe the real culprit is this line of code which reallocates and copies the entire string, taking time quadratic in the length of the string.

@bjorn3
Copy link
Collaborator Author

bjorn3 commented Jul 25, 2018

that bug probably won't significantly impact syntect performance

When trishume/syntect#182 gets merged I can switch https://github.com/oli-obk/priroda/blob/master/src/render/source.rs#L10-L21 to lazy_static, which is nicer.

or maybe the real culprit is this line of code which reallocates

That may be possible. (Edit: hopefully removed the quadratic complexity. Can't measure its impact at the moment)

@bjorn3
Copy link
Collaborator Author

bjorn3 commented Aug 4, 2018

Right now syntect::easy::HighlightLines::highlight is ~90% of all time spend on rendering a page.

schermafbeelding 2018-08-04 om 16 51 57

@trishume
Copy link

trishume commented Aug 4, 2018

Huh, do you know how much code you usually need to highlight to render a page?

Provided you're compiling in release mode you should get 15k lines/sec like I said, which should be sufficient for most applications, but do you need to highlight a ton of code per page?

There's a few syntaxes and cases where syntect hits exponential backtracking in the regex engine and becomes super slow, if you're not highlighting a lot of code, could you post a gist of the test file you're using?

@bjorn3
Copy link
Collaborator Author

bjorn3 commented Aug 4, 2018

Huh, do you know how much code you usually need to highlight to render a page?

200-2000 lines I think

Provided you're compiling in release mode you should get 15k lines/sec like I said, which should be sufficient for most applications, but do you need to highlight a ton of code per page?

Forgot to use release mode. It does solve the slowness for shortish files, but doesn't fix it for longer files.

could you post a gist of the test file you're using?

It is from the rustc: https://gist.github.com/bjorn3/a7c6e9fbe570befd93ec463f14047248 (please note that my code inserts a comment not shown here to give a part of the code a different background. /*BEG_HIGHLIGHT*/ and /*END_HIGHLIGHT*/)

@trishume
Copy link

trishume commented Aug 4, 2018

So that file takes about 60ms to highlight for me, which is about as fast as can be expected from syntect at this time. If an extra 60ms on your page loads because of highlighting on large files like that is a problem, then yah at the moment there's nothing I can suggest, other than maybe only highlighting a smaller region you need or some kind of caching.

If you're seeing syntect take substantially longer than 60ms to highlight that file in release mode, then maybe the problem is somewhere else that can be fixed.

@bjorn3
Copy link
Collaborator Author

bjorn3 commented Aug 4, 2018

So that file takes about 60ms to highlight for me

Same for me. However I got 200ms in release mode for https://gist.github.com/bjorn3/4ea8ba8a703b93973f9feb8bf8eb4c36 (4800 lines)

@bjorn3
Copy link
Collaborator Author

bjorn3 commented Aug 4, 2018

To quote myself:

Right now syntect::easy::HighlightLines::highlight is ~90% of all time spend on rendering a page.

It is down to ~80% in release mode

@bjorn3
Copy link
Collaborator Author

bjorn3 commented Aug 4, 2018

other than maybe only highlighting a smaller region you need

I could try that, but I don't know where I can begin that region without beginning in the middle of a string.

@trishume
Copy link

trishume commented Aug 4, 2018

One option if you think that the tradeoff between page load time and having syntax highlighting tips over at some point, is you could disable highlighting only for files larger than a certain number of lines. I think a lot of users would probably prefer the typical <500 line file be highlighted even if their pages take an extra 30ms to load, but you know your use case better than me.

Also judging by the description in the readme of what this project does, if interactive actions you want to be snappy like stepping are currently re-highlighting the file, you could consider caching the highlighted file to avoid re-highlighting every step.

@bjorn3
Copy link
Collaborator Author

bjorn3 commented Aug 4, 2018

is you could disable highlighting only for files larger than a certain number of lines.

+1

you could consider caching the highlighted file to avoid re-highlighting every step.

Is there an api to map from source bytes to highlighted bytes? That would be needed to cache, without losing the ability to mark the currently executed part of the code.

@trishume
Copy link

trishume commented Aug 4, 2018

Two options for caching:

  • You could cache at the line level, and just rehighlight all lines that include the executed range.
  • Substantially more difficult but maybe better: You could cache the result of the highlight function, with maybe a transform to own the strings, then instead of using your comment inserting and replacing trick, syntect the original file, and then insert the highlight HTML snippet after the fact. You can do this because h.highlight(&l) returns a Vec<(Style, &str)> which using some logic you can split into a before highlight, in highlight and after highlight part (potentially splitting spans by splitting the string and cloning the Style) and insert your highlighting spans as needed.

I'll think about introducing a feature in syntect to make this easier if I can think of a way, I think some of the static site generators also want a way to highlight parts of the code.

Edit: if I find time today I might try my hand at writing the trickiest bit of logic and adding it as a helper to syntect, which is a function fn split<A: Clone>(s: &[(A, &str)], i: usize) -> (Vec<(A, &str)>, Vec<(A, &str)>) which can then be used to implement what I specified above fairly easily.

@bjorn3
Copy link
Collaborator Author

bjorn3 commented Aug 5, 2018

Substantially more difficult but maybe better: [...]

That could work! It is also cleaner than using comments.

@bjorn3
Copy link
Collaborator Author

bjorn3 commented Aug 5, 2018

Edit: if I find time today I might try my hand at writing the trickiest bit of logic and adding it as a helper to syntect, which is a function fn split<A: Clone>(s: &[(A, &str)], i: usize) -> (Vec<(A, &str)>, Vec<(A, &str)>) which can then be used to implement what I specified above fairly easily.

Just wrote it myself:

// Split between i and i+1
fn split<'s, A: Clone>(mut s: &[(A, &'s str)], mut i: usize) -> (Vec<(A, &'s str)>, Vec<(A, &'s str)>) {
    let mut before = Vec::new();
    let mut after = Vec::new();

    while s.len() > 0 && s[0].1.len() < i {
        i -= s[0].1.len();
        before.push(s[0].clone());
        s = &s[1..];
    }

    if s.len() > 0 {
        let (data, str) = s[0].clone();
        s = &s[1..];
        before.push((data.clone(), &str[..i]));
        after.push((data, &str[i..]));
    }

    after.extend(s.iter().cloned());
    (before, after)
}

Edit: it has a bug :(
Edit2: hopefully fixed it

@trishume
Copy link

trishume commented Aug 7, 2018

Oh lol I wrote one almost immediately after I wrote that comment but didn't submit it since I hadn't written tests yet, sorry. Looks like we did it in almost exactly the same way, although I haven't tested if mine compiles or works yet:

/// Split a highlighted line at a byte index in the line into a before and
/// after component. It's just a helper that does the somewhat tricky logic
/// including splitting a span if the index lies on a boundary.
///
/// This can be used to extract a chunk of the line out for special treatment
/// like wrapping it in an HTML tag for extra styling.
pub fn split_at(mut v: &[(Style, &str)], mut split_i: usize) -> (Vec<(Style, &str)>, Vec<(Style, &str)>) {
  // Consume all tokens before the split
  let before = Vec::new();
  for tok in v {
    if tok.1.len() > split_i {
      break;
    }
    before.push(tok.clone());
    split_i -= tok.1.len();
  }
  v = &v[before.len()..];

  let after = Vec::new();
  // If necessary, split the token the split falls inside
  if v.len() > 0 && split_i > 0 {
    let (sa, sb) = v[0].1.split_at(split_i);
    before.push((v[0].0.clone(), sa));
    after.push((v[0].0.clone(), sb));
    v = &v[1..];
  }

  after.extend_from_slice(v);

  return (before, after);
}

Anyway, I'm going to try to get around to contributing it in a PR to syntect soon, along with some tests and a higher level helper that uses it to apply a StyleModifier to a range of a line.

@bjorn3
Copy link
Collaborator Author

bjorn3 commented Aug 7, 2018

You don't have to use extend_from_slice, because extend is specialized for slices and extend_from_slice literally calls the same function as extend (https://doc.rust-lang.org/stable/src/alloc/vec.rs.html#1389-1391)

Your version is better overall I think.

@bjorn3 bjorn3 closed this as completed in ba7d999 Aug 7, 2018
@bjorn3
Copy link
Collaborator Author

bjorn3 commented Aug 7, 2018

When the cache is hit, source rendering now requires ~3ms :)

@trishume
Copy link

trishume commented Aug 7, 2018

Yay nice! This project looks pretty cool, good luck with it!

I'll try and remember to tag you when I add the helper to syntect so you can remove the code from your project if you want, not that it would make any difference. Thanks for the digging about extend, I'll switch to that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants