Simple analysis and comparison of popular expression engines

order
        Recently, there is a demand for energy consumption monitoring, that is, threshold early warning and risk control after some business calculation of equipment reported data. After disassembling the requirements, it is found that the most difficult point to remove the business is the threshold comparison. It is easy for experienced coders to think of expression calculation. After the basic rules are added, deleted, modified and checked, and the logical operation expression is generated, we should start to bite the hardest bone. Considering the amount of data reported by the equipment, the performance must be considered, so the type selection of the expression is made first.

1, Dependency introduction

        Here, I will import all the expression engine dependency packages that I plan to use. There will be no single description later. For each introduction, see the comments on the imported code. In addition, there are some old engines without maintenance. I just pass directly here. It depends on the vitality of the engine. When maven looks for its jar, it depends on the update time and cycle.
Here's another sentence: CSDN browser plug-in is really fragrant. Let's find jar in maven central library. The shift + c provided by the CSDN browser plug-in calls out the plug-in, enters mvn, and you can immediately enter the jar name or skeleton name. Press enter, and the jar will come out. The last effect picture.

 <!-- A collection of useful tools -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.13</version>
</dependency>
<!-- aviator Expression engine support -->
<dependency>
    <groupId>com.googlecode.aviator</groupId>
    <artifactId>aviator</artifactId>
    <version>5.2.7</version>
</dependency>

<!-- jexl3 Expression engine support -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-jexl3</artifactId>
    <version>3.2.1</version>
</dependency>

<!-- javascript-graalvm support -->
<dependency>
    <groupId>org.eclipse.dirigible</groupId>
    <artifactId>dirigible-engine-javascript-graalvm</artifactId>
    <version>5.12.0</version>
</dependency>

<!-- graalvm js support -->
<!-- <dependency>
    <groupId>org.graalvm.js</groupId>
    <artifactId>js</artifactId>
    <version>21.3.0</version>
</dependency>-->

<!-- mvel support -->
<dependency>
    <groupId>org.mvel</groupId>
    <artifactId>mvel2</artifactId>
    <version>2.4.13.Final</version>
</dependency>

Here is the explanation:

  1. hutool is because I want to use its expression engine encapsulation tool
  2. The latest version of JavaScript graalvm was originally 6.1.2. The 5.12.0 used here is because my JDK is 8. If you are a blogger with a higher version, you can use 6 +, which may obtain the engine in a different way. You can check the api
  3. graalvm js is annotated because JavaScript graalvm relies on it. You can click in to see it

2, Performance test demo

        Today, we are doing the performance test of pure expression, because at present, we only need one line of logical operation expression for threshold verification. In fact, some engines are very powerful and can directly support the execution of the whole method of some languages. For example, graalvm, which is strongly recommended by oracle, is very awesome. Finally, I said my thoughts on it.
The test demo is the Boolean calculation result of the logical operator after replacing the value of the expression. The expression is as follows:
((dayUse > 3 && dayUse < 7 ) || (aloneUse > 100 || aloneUse < 0 )) && (totalUse > 1000 )

@Test
void checkExpre() throws Exception {
    log.info("--Expression verification test--");

    //General conditions and parameter processing
    Object eval = null;
    String orginExpre = "((dayUse > 3 && dayUse < 7 ) || (aloneUse > 100 || aloneUse < 0 )) && (totalUse > 1000 ) ";
    AtomicReference<String> expre = new AtomicReference<>(orginExpre);
    Dict dict = Dict.create().set("dayUse", 4).set("aloneUse", 3).set("totalUse", 1);

    dict.entrySet().stream().forEach(t -> {
        String tmpExpre = expre.get().replace(t.getKey(), t.getValue().toString());
        expre.set(tmpExpre);
    });

    log.info("-----Pure expression performance testing begins----");

    StopWatch sw = new StopWatch();
    sw.start();
    eval = ExpressionUtil.eval(expre.get(), dict);
    sw.stop();
    log.info("--aviator--expre:{} = {},time consuming:{}ms", expre.get(), eval, sw.getTotalTimeMillis());

    //Get js engine instance
    sw = new StopWatch();
    sw.start();
    ScriptEngineManager sem = new ScriptEngineManager();
    ScriptEngine engine = sem.getEngineByName("javascript");
    eval = engine.eval(expre.get());
    sw.stop();
    log.info("--ScriptEngine--expre:{} = {},time consuming:{}ms", expre.get(), eval, sw.getTotalTimeMillis());

    sw = new StopWatch();
    sw.start();
    engine = new JexlScriptEngine();
    eval = engine.eval(expre.get());
    sw.stop();
    log.info("--Jexl3--expre:{} = {},time consuming:{}ms", expre.get(), eval, sw.getTotalTimeMillis());

    sw = new StopWatch();
    sw.start();
    ExpressionParser p = new SpelExpressionParser();
    Expression exp = p.parseExpression(expre.get());
    eval = exp.getValue();
    sw.stop();
    log.info("--spel--expre:{} = {},time consuming:{}ms", expre.get(), eval, sw.getTotalTimeMillis());

    sw = new StopWatch();
    sw.start();
    GraalJSEngineFactory graalJSEngineFactory = new GraalJSEngineFactory();
    GraalJSScriptEngine graalJSScriptEngine = graalJSEngineFactory.getScriptEngine();
    eval = graalJSScriptEngine.eval(expre.get());
    sw.stop();
    log.info("--graalvm--expre:{} = {},time consuming:{}ms", expre.get(), eval, sw.getTotalTimeMillis());

    sw = new StopWatch();
    sw.start();
    Context context = Context.newBuilder().allowAllAccess(true).build();
    eval = context.eval("js", expre.get());

    log.info("--graalvm js Mode 1--expre:{} = {},time consuming:{}ms", expre.get(), eval, sw.getTotalTimeMillis());

    sw = new StopWatch();
    sw.start();
    ScriptEngine eng = new ScriptEngineManager().getEngineByName("js");
    eval = eng.eval(expre.get());
    sw.stop();
    log.info("--graalvm js Mode 2--expre:{} = {},time consuming:{}ms", expre.get(), eval, sw.getTotalTimeMillis());

    sw = new StopWatch();
    sw.start();
    eval = MVEL.eval(orginExpre, dict);
    sw.stop();
    log.info("--MVEL--expre:{} = {},time consuming:{}ms", expre.get(), eval, sw.getTotalTimeMillis());

}

3, demo execution results


Obviously, the graalvm expression engine is really unique.
Pure expression (rigorous here) execution performance conclusion:
graalvm < Jexl3 < MVEL2 < spel < aviator < ScriptEngine

  1. ScriptEngine is the slowest. No wonder jdk8 it was removed later
  2. Mvel hasn't been updated for some time. Here is mvel2
  3. A line of pure expression is executed without inconsistency, which is visible and reliable

4, Summary

  1. Calling method MVEL is really elegant
  2. The Context mode of graalvm is really fast (don't doubt that the personal test shielding all other methods are still the result of this 0, that is, men like light)
  3. The encapsulated expression tool ExpressionUtil of hutool is even nice r if it provides getEngin("enginName"). The current custom engine needs to forcibly handle exceptions, which is not elegant enough
  4. graalvm is a multilingual engine that can integrate ruby, js, python, groovy, kotlin, etc. in short, it is very powerful, with the blessing of eclipse and oracle
  5. JEXL expression language is standard, flexible and mainly standard, so that inconsistent execution results will not occur
  6. SpelExpressionParser is built-in to spring. Spring supports 7. aviator's high-performance and lightweight java
    Language implementation, google blessing

        Finally, that's all for this simple comparison. For the selection and details, you can dig deep and study by yourself.
I decided to choose graalvm for the following reasons:

  1. After all, there are two Dharma protector blessings, although the development is not comprehensive (the version iteration is fast)
  2. In the later stage, an entrance can be opened so that those who know json and js can do some things, such as equipment instruction parsing and reporting data exception alarms
    And so on. Even some business verifications can be opened to the foreground to write js, so as to avoid the front-end and back-end adding, deleting and changing data, doing business verification and writing dead code.

        Next time there is a driving scenario, I will share more advanced expression verification, scenario introduction, etc. I hope it can help you, Progress together

Tags: IDE

Posted on Fri, 29 Oct 2021 06:48:19 -0400 by nologin666