4

Evaluate computation expression-based HTML for better rendering performance · Is...

 2 years ago
source link: https://github.com/fsbolero/Bolero/issues/249
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

TL;DR: F# 6 makes it possible to use computation expressions for HTML and have much better performance than the current list-based syntax. It could look like this:

div {
    text "Welcome to "
    a {
        attr.href "https://fsbolero.io"
        attr.id "link"
        text "Bolero"
    }
    text "!"
}

Background: Blazor rendering

Blazor uses a diffing render engine, like React; but unlike React, it doesn't diff between two tree representations of the DOM. Instead, a DOM fragment is represented by a linear sequence of instructions. For example, the following Razor file:

<div>Welcome to <a href="https://fsbolero.io" id="link">Bolero</a>!</div>

is compiled into approximately the following C#:

void Render(RenderTreeBuilder builder)
{
    builder.OpenElement(0, "div");
    builder.AddContent(1, "Welcome to ");
    builder.OpenElement(2, "a");
    builder.AddAttribute(3, "href", "https://fsbolero.io");
    builder.AddAttribute(4, "id", "link");
    builder.AddContent(5, "Bolero");
    builder.CloseElement();
    builder.AddContent(6, "!");
    builder.CloseElement();
}

Notice the sequence numbers in most of these calls. They are used to efficiently diff things like conditionals and loops. They are generated at compile time, and they are the main reason why generating such code as a library, rather than a compile-time tool such as Razor, is quite non-trivial. Naively keeping an incrementing runtime counter could cause inconsistent numbering between consecutive renders.

Bolero's current solution

In Bolero, the HTML functions build a tree representation of the DOM as a discriminated union. Then, in a second pass, this tree representation is transformed into a series of calls similar to the above. The sequence numbers are generated dynamically, and the functions cond and forEach ensure that they remain consistent.

Unfortunately, this has a performance cost, as we allocate the DOM tree union before every render.

The opportunity

Instead of using lists of attributes and elements to build a DOM tree, another possibility would be to use a computation expression to directly build up the sequence of builder calls. The Node and Attr types would be delegates that take a RenderTreeBuilder. If this is done naively, it's not really more efficient than the union way; it just allocates a bunch of lambdas instead of a bunch of union values.

However, F# 6's [<InlineIfLambda>] changes this. By making very liberal use of this attribute in the computation expression builder, it is possible to entirely flatten the generated nested lambdas, and end up with completely linear IL, just like C#!

Here is a very basic proof of concept. With it, the following code:

div {
    text "Welcome to "
    a {
        attr.href "https://fsbolero.io"
        attr.id "link"
        text "Bolero"
    }
    text "!"
}

is decompiled by ILSpy into the following C#:

// Program.d@107
internal static int Invoke(RenderTreeBuilder tb, int i)
{
	tb.OpenElement(i, div.name);
	int num = i + 1;
	tb.AddContent(num, "Welcome to ");
	int num2 = num + 1;
	tb.OpenElement(num2, a.name);
	int num3 = num2 + 1;
	tb.AddAttribute(num3, "href", "https://fsbolero.io");
	int num4 = num3 + 1;
	tb.AddAttribute(num4, "id", "link");
	int num5 = num4 + 1;
	tb.AddContent(num5, "Bolero");
	int num6 = num5 + 1;
	tb.CloseElement();
	int num7 = num6;
	tb.AddContent(num7, "!");
	int result = num7 + 1;
	tb.CloseElement();
	return result;
}

It still uses a dynamically generated sequence number; that is inevitable without compile-time code generation. But it is completely linear and doesn't allocate anything more than the equivalent Razor, which is a huge improvement!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK