Getting started with Phoenix

Phoenix is still in early development and the following benchmarks should be taken with a grain of salt and may not reflect the final version of the code. Changes may occur in the future, both positive and negative from a speed perspective. Furthermore, current benchmarks were done redumentary and not using propper benchmarking technics like using JMH.

As a starter, the template for Rocker and Thymeleaf from this repository was used, with an additional template for Phoenix which rendered the same result. However, due to Phoenix being in early stages of development and because it needs a Spring Context to compile the template, a simple @SpringBootTest was used. For Rocker, the template was compiled before execution.

Using 100_000 iterations, the template was called and rendered. Execution time in ms was monitored using System.currentTimeMillis(). I know this is NOT the best approach, but wanted to have a general idea of the performance. Propper benchmarking weill be done in the future.

Below is a table with the results of the benchmarks on my machine (using a Ryzen 7800x3D) as well as source code of the test class that was used:

Template Engine Execution Time
Phoenix 786ms
Rocker 818ms
Thymeleaf 19251ms
                                
import org.example.model.Stock;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.thymeleaf.context.IContext;
import tech.petrepopescu.phoenix.controllers.FragmentController;
import tech.petrepopescu.phoenix.parser.ElementFactory;
import tech.petrepopescu.phoenix.parser.PhoenixParser;
import tech.petrepopescu.phoenix.parser.compiler.Compiler;
import tech.petrepopescu.phoenix.parser.compiler.DynamicClassLoader;
import tech.petrepopescu.phoenix.parser.route.RouteGenerator;
import tech.petrepopescu.phoenix.special.PhoenixSpecialElementsUtil;
import tech.petrepopescu.phoenix.spring.PhoenixErrorHandler;
import tech.petrepopescu.phoenix.spring.PhoenixMessageConverter;
import tech.petrepopescu.phoenix.spring.SecurityConfig;
import tech.petrepopescu.phoenix.spring.config.PhoenixConfiguration;
import tech.petrepopescu.phoenix.spring.config.ViewsConfiguration;
import tech.petrepopescu.phoenix.views.View;

import java.io.StringWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

@SpringBootTest(classes = {SecurityConfig.class, PhoenixMessageConverter.class, RouteGenerator.class, Compiler.class, ElementFactory.class,
        DynamicClassLoader.class, PhoenixConfiguration.class, PhoenixSpecialElementsUtil.class, FragmentController.class,
        PhoenixErrorHandler.class})
@ImportAutoConfiguration(ThymeleafAutoConfiguration.class)
class FullBenchmark {
    @Autowired
    private Compiler compiler;

    @Autowired
    private RouteGenerator routeGenerator;

    @Autowired
    private PhoenixSpecialElementsUtil phoenixSpecialElementsUtil;

    @Autowired
    private TemplateEngine engine;

    @Test
    void benchmark() throws Exception {
        List<Stock> items = Stock.dummyItems();
        preparePhoenix();
        IContext context = new Context(Locale.getDefault(), getThymeleafContext(items));

        System.out.println("Running benchmark...");
        long rocker = runBenchmark(() -> stocks.template(items).render().toString());
        long phoenix = runBenchmark(() -> View.of("phoenix", items).getContent(phoenixSpecialElementsUtil));
        long thymeleaf = runBenchmark(() -> {
            Writer writer = new StringWriter();
            engine.process("stocks.thymeleaf.html", context, writer);
            writer.toString();
        });

        System.out.println("Phoenix Benchmark: " + phoenix + "ms");
        System.out.println("Thymeleaf Benchmark: " + thymeleaf + "ms");
        System.out.println("Rocker Benchmark: " + rocker + "ms");
    }

    private long runBenchmark(Runnable runnable) {
        long start = System.currentTimeMillis();
        for (int count = 0; count<100_000; count++) {
            runnable.run();
        }
        long end = System.currentTimeMillis();
        return end - start;
    }

    private void preparePhoenix() {
        PhoenixConfiguration phoenixConfiguration = new PhoenixConfiguration();
        phoenixConfiguration.setViews(new ViewsConfiguration());
        phoenixConfiguration.getViews().setPath("src/test/resources/views");

        PhoenixParser parser = new PhoenixParser(new ElementFactory(null), routeGenerator, compiler, phoenixConfiguration);
        parser.parse();
    }

    protected Map<String, Object> getThymeleafContext(List<Stock> items) {
        Map<String, Object> context = new HashMap<>();
        context.put("items", items);
        return context;
    }
}