The test of. Net template engine jntemplate v2.0 needs both domestic and speed

Time:2021-2-12

Jntemplate is a domestic text parsing engine (template engine). Recently, the version 2.0 has been reconstructed. The author selects several template engines on gihub to do a simple small comparative test to see how the quality of domestic products is!

Environment Description:

operating system:Windows 10.0.19041.746

CPU: Intel Xeon CPU E3-1231 v3 3.40GHz

Memory:8G

. net framework:  net 5

Test tools:BenchmarkDotNet 0.12.1

Participating engines:

JinianNet.JNTemplate 2.0.0
Mustachio 2.1.0
RazorEngineCore 2020.10.1
RazorLight 2.0.0-rc.3
Scriban 3.4.2

To illustrate, razor is officially a view engine, which is not convenient for direct testing. Therefore, we found two third-party engines based on razor to participate in the testing. Because the version of razorengine is too old to support. Net 5, we chose razorlight and razorengine core

The above files are installed from nuget. The grammar is written according to official documents and unified as far as possible.

 

Test 1: output a variable or attribute

Test code:

using BenchmarkDotNet.Attributes;
using JinianNet.JNTemplate;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Test
{
    [MemoryDiagnoser]
    public class TestVariable
    {
        [Benchmark]
        public void RunJntemplate()
        {
            var template = Engine.CreateTemplate("Hello $model.Id !");
            template.Set("model", new UserInfo { Id = 10, Name = "your name!" });
            var value = template.Render();
        }

        [Benchmark]
        public void RunMustachio()
        {
            var template = Mustachio.Parser.Parse("Hello {{Id}} !");
            dynamic model = new System.Dynamic.ExpandoObject();
            model.Id = 10;
            model.Name = "your name!";
            var value = template(model);
        }

        [Benchmark]
        public void RunScriban()
        {
            var template = Scriban.Template.Parse("Hello {{Id}} !");
            var value = template.Render(new UserInfo { Id = 10, Name = "your name!" });
        }



        [Benchmark]
        public void RunRazorEngineCore()
        {
            //RazorEngineCore.IRazorEngineCompiledTemplate template;
            var razorEngine = new RazorEngineCore.RazorEngine();
            var template = razorEngine.Compile("Hello @Model.Id");
            var value = template.Run(new UserInfo { Id = 10, Name = "your name!" });
        }


        [Benchmark]
        public void RunRazorLight()
        {
            var engine = new RazorLight.RazorLightEngineBuilder()
                // required to have a default RazorLightProject type,
                // but not required to create a template from string.
                .UseEmbeddedResourcesProject(typeof(UserInfo))
                //.SetOperatingAssembly(typeof(UserInfo).Assembly)
                .UseMemoryCachingProvider()
                .Build();

            string template = "Hello @Model.Id";
            var model = new UserInfo { Id = 10, Name = "your name!" };

            string result =  engine.CompileRenderStringAsync("templateKey", template, model).GetAwaiter().GetResult();
        }
    }
}

  

Test results:

 

It can be seen here that the two razor engines take the longest time. This is because they are compiled engines and need to be compiled for initial operation, so they will take more time.

But why is it that jntemplate is also a compiler engine and takes the least time?

 

Test 2: the code in test 1 ran 100000 times

That’s to say, one test doesn’t mean much. Next, let’s run the above code 100000 times.

Test code:

using BenchmarkDotNet.Attributes;
using JinianNet.JNTemplate;
using RazorEngineCore; 
using System.Collections.Concurrent; 

namespace Test
{
    [MemoryDiagnoser]
    public class TestVariableMultiple
    {
        private int Max = 100000;

        [Benchmark]
        public void RunJntemplate()
        {
            string text = "Hello $model.Id";
            var hashCode = text.GetHashCode().ToString();
            for (var i = 0; i < Max; i++)
            {
                var template = JinianNet.JNTemplate.Engine.CreateTemplate(hashCode, text);
                template.Set("model", new UserInfo { Id = 10, Name = "your name!" });
                var value = template.Render();
            }
        }

        [Benchmark]
        public void RunScriban()
        {
            Scriban.Template template = Scriban.Template.Parse("Hello {{Id}} !");
            for (var i = 0; i < Max; i++)
            {
                var value = template.Render(new UserInfo { Id = 10, Name = "your name!" });
            }

        }

        [Benchmark]
        public void RunMustachio()
        {
            var template = Mustachio.Parser.Parse("Hello {{Id}} !");
            for (var i = 0; i < Max; i++)
            {
                dynamic model = new System.Dynamic.ExpandoObject();
                model.Id = 10;
                model.Name = "your name!";
                var value = template(model);
            }
        }

        [Benchmark]
        public void RunRazorEngineCore()
        {
            var TemplateCache = new ConcurrentDictionary();
            string text = "Hello @Model.Id";
            int hashCode = text.GetHashCode();

            for (var i = 0; i < Max; i++)
            {

                IRazorEngineCompiledTemplate compiledTemplate = TemplateCache.GetOrAdd(hashCode, i =>
                {
                    RazorEngine razorEngine = new RazorEngine();
                    return razorEngine.Compile(text);
                });

                var value = compiledTemplate.Run(new UserInfo { Id = 10, Name = "your name!" });
            }
        }

        [Benchmark]
        public void RunRazorLight()
        {
            var engine = new RazorLight.RazorLightEngineBuilder() 
                .UseEmbeddedResourcesProject(typeof(UserInfo)) 
                .UseMemoryCachingProvider()
                .Build();

            string template = "Hello @Model.Id";
            var model = new UserInfo { Id = 10, Name = "your name!" };
            for (var i = 0; i < Max; i++)
            {
                if (i==0)
                {
                    string result = engine.CompileRenderStringAsync("templateKey", template, model).GetAwaiter().GetResult();
                }
                else
                {
                    var cacheResult = engine.Handler.Cache.RetrieveTemplate("templateKey");
                    if (cacheResult.Success)
                    {
                        var templatePage = cacheResult.Template.TemplatePageFactory();
                        string result = engine.RenderTemplateAsync(templatePage, model).GetAwaiter().GetResult();
                    }
                }
            }
        }
    }
}

  

Test results:

 

After running 100000 times, the speed of compiled engine is obviously superior

 

Test 3: foreach traverses 100000 times

Test code:

using BenchmarkDotNet.Attributes;
using JinianNet.JNTemplate;
using RazorEngineCore;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Test
{
    /// 
    /// /
    /// 
    [MemoryDiagnoser]
    public class TestForeach
    {
        private int[] arr;
        private int max = 100000;

        public TestForeach()
        {
            arr = new int[max];
            for(var i = 0; i < max; i++)
            {
                arr[i] = i;
            }
        }

        [Benchmark]
        public void RunScriban()
        {
            var template = Scriban.Template.Parse(@"

  {{ for product in products }}
    {{ product }}
  {{ end }}

");
            var result = template.Render(new { Products = arr });


        }

        [Benchmark]
        public void RunJntemplate()
        {
            string text = @"

$for(node in list)
$node
$end

";
            var hashCode = text.GetHashCode().ToString();
            var template = JinianNet.JNTemplate.Engine.CreateTemplate(hashCode, text);
            template.Set("list", arr);
            var value = template.Render();
        }

        [Benchmark]
        public void RunRazorEngineCore()
        {
            var razorEngine = new RazorEngineCore.RazorEngine();

            var TemplateCache = new ConcurrentDictionary();
            string text = @"

@{
foreach (var item in Model)
{
    @item
 }
}

"; 
            var template = razorEngine.Compile(text);
            var value = template.Run(arr);
        }

        [Benchmark]
        public void RunRazorLight()
        {
            var engine = new RazorLight.RazorLightEngineBuilder()
                // required to have a default RazorLightProject type,
                // but not required to create a template from string.
                .UseEmbeddedResourcesProject(typeof(UserInfo))
                //.SetOperatingAssembly(typeof(UserInfo).Assembly)
                .UseMemoryCachingProvider()
                .Build();

            string text = @"

@{
foreach (var item in Model)
{
    @item
 }
}

";

            string result = engine.CompileRenderStringAsync("templateKey", text, arr).GetAwaiter().GetResult();
        }
    }
}

  

Test results:

 

Mustachio didn’t find the syntax of foreach. There is no test here. Scriban can’t support such a large array, so there is no result.

 

There are also some complex usages, which will not be tested for the moment today. I need not say the conclusion. Of course, this test is not complete. After all, there are many places involved in an engine. Only one or two usages can not represent all, so the results are for reference only.

In addition, mustachio’s function is too simple to compare with other engines. I just want to make up a few here!

What do you think?

Recommended Today

Practice analysis of rust built-in trait: partialeq and EQ

Abstract:Rust uses traits in many places, from simple operator overloading to subtle features like send and sync. This article is shared from Huawei cloud community《Analysis of rust built-in trait: partialeq and EQ》Author: debugzhang Rust uses traits in many places, from simple operator overloading to subtle features like send and sync. Some traits can be automatically […]