Lithe 7 - Apples to Apples


Confession Time

My performance testing methodology so far has been extremely flawed. It serves mostly as an indicator for how rigorous performance testing might go when I eventually get around to it. Which I think is reasonable, but it doesn't have to be quite so unfair to Svelte. Svelte is doing quite a lot more than Lithe, so I really shouldn't be comparing them side by side. It'd be good to re-run some earlier tests, but with the JavaScript equivalent of what Lithe is doing. Which, let's be honest, is mostly just parsing the HTML into an AST.

Disambiguation

It's good to have names for things, so let's call this super-basic JavaScript version LitheJS.

The Specifics

Lithe has been using the html_parser crate. For LitheJS I'll use the very popular Fast HTML Parser. It's got 2.3M downloads a week, and it's even got Fast in the name. If anything's going to give Lithe a run for its money it's going to be that.

The Methodology

We'll do the same as before: super simple HTML files, just N <span>Hello world!</span> elements. No nesting, no scripting, and I'm not even going to ask LitheJS to produce any output or perform any transformations. I just want to know: how long does it take to parse the HTML into an AST, and how does that compare with Lithe?

My thought is if Lithe can still beat out LitheJS, even after giving it all these advantages, then we're on the right track.

Notably, though, I'm not going to bother improving any of the rest of my methodology just yet. I'm not averaging multiple runs, I'm not going to nest HTML elements, I'm measuring performance directly in Node, all results are in ms, etc.

The Results

Number of spansSvelteLithe - nativeLithe - WASMLitheJS
17ms3ms1ms1ms
1010ms1ms1ms1ms
100108ms4ms3ms1ms
1000348ms6ms9ms3ms
10,000N/A (SO)53ms81ms125ms

Wow, that Fast HTML Parser really is fast! At least until you get into really big files. But let's dig into that a bit more. Right now I'm just parsing these file contents and throwing away the result:

function simple_html_parser(contents: String) {
	const root = parse(contents);
}

I haven't read the source, but I suppose it's possible that Fast HTML Parser is being super lazy and not fully parsing the HTML string it was given until it has to. Let's make it work a little harder:

function simple_html_parser(contents: String) {
	const root = parse(contents);
	const result = contents.toString();
	return result;
}

Let's re-run those last couple of tests. I included a range here since the results were highly variable:

Number of spansLitheJS
10008ms-18ms
10,00058ms-140ms

Alright, so it's a little bit slower, but not by that much. I'm guessing there's no lazy evaluation going on here: Fast HTML Parser is just fast. For fun I added timing inside the native version of Lithe just around the HTML parsing bit. And if we get rid of the toString() call in our simple_html_parser we can compare just the DOM parsing.

Number of spansLitheJS (just HTML parsing)Lithe - native (just HTML parsing)
1001ms0ms
10004ms-13ms1ms
10,00041ms-142ms18ms-27ms

And with that we're finally comparing like with like. And the results look how I'd expect. If we take out the FFI overhead and only parse the HTML then the Rust version is looking to be a bit faster, especially for larger files.


Suggested reading

Up next: Secret Santa: a Saga Previously: Lithe 6: What about WASM?