Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance issue - A lot of time for invoke js method #756

Open
omarmahamid opened this issue Aug 30, 2023 · 8 comments
Open

Performance issue - A lot of time for invoke js method #756

omarmahamid opened this issue Aug 30, 2023 · 8 comments
Assignees
Labels
performance Performance of the engine (peak or warmup)

Comments

@omarmahamid
Copy link

omarmahamid commented Aug 30, 2023

Hello All,

Describe the issue

I've encountered a performance problem while using the GraalVM Polyglot API to run JavaScript scripts as a script engine. The specific section of code that invokes the JavaScript function seems to have a significant delay, taking approximately 400 ms on average when executed 6,000 times.

Interestingly, when I attempted to run the same JavaScript code using Node.js in the WebStorm IDE, it only took about 20 ms. This stark difference in rendering time has me wondering if such a substantial delay is to be expected when using GraalVM Polyglot.

I noticed that other script engines, such as V8 and J2V8, tend to complete this task in approximately 50 ms, which further highlights the performance gap I'm encountering with GraalVM.

Below is the code snippet I'm using:

Code snippet or code repository that reproduces the issue


import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.Charset;

public class GraalVM{
public static void main(String[] args) {

        try (Context context = Context.create()) {

            URL resourceURL = GraalVM.class.getClassLoader().getResource("script.js");

            URL resourceJson = GraalVM.class.getClassLoader().getResource("story.json");

            context.eval("js", Resources.toString(resourceURL, Charset.defaultCharset()));

            Value functionsObject = context.getBindings("js");


            Value processor = functionsObject.getMember("scripting");

            String subjectJson = Resources.toString(resourceJson, Charset.defaultCharset());

            long start = System.currentTimeMillis();

            System.out.println(processor.execute(subjectJson));

            System.out.println("time took is " + (System.currentTimeMillis() - start) + " ms");


        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }


    }
} ````


Expected behavior

I'm hoping to optimize the execution time by utilizing the GraalVM Polyglot API more effectively. To provide some context, the story.json file contains around 50,000 rows of data, and the scripting method involves the usage of jsep for parsing and some Visitor Design Pattern, which includes recursion.

Some Input Context

the story.json is a json of about 50K rows with data
the method scripting, is using

```` var jsep = require("jsep"); ```` 

and some Vistor Design Pattern .. that go through it.

and it contains some recursion

val = callee.call(args);

any ideas ??
@oubidar-Abderrahim oubidar-Abderrahim self-assigned this Sep 5, 2023
@oubidar-Abderrahim oubidar-Abderrahim added the performance Performance of the engine (peak or warmup) label Sep 5, 2023
@oubidar-Abderrahim
Copy link
Member

Hi, Thank you for reaching out about this, Could you please share the full example with steps to reproduce using GraalJS and other engines you've tested like V8, and we will take a look into it.

@omarmahamid
Copy link
Author

@oubidar-Abderrahim

the backend is something like

import java.io.IOException;
import java.net.URL;
import java.nio.charset.Charset;

public class GraalVM{
public static void main(String[] args) {

        try (Context context = Context.create()) {

            URL resourceURL = GraalVM.class.getClassLoader().getResource("script.js");

            URL resourceJson = GraalVM.class.getClassLoader().getResource("story.json");

            context.eval("js", Resources.toString(resourceURL, Charset.defaultCharset()));

            Value functionsObject = context.getBindings("js");


            Value processor = functionsObject.getMember("scripting");

            String subjectJson = Resources.toString(resourceJson, Charset.defaultCharset());

            long start = System.currentTimeMillis();

            System.out.println(processor.execute(subjectJson));

            System.out.println("time took is " + (System.currentTimeMillis() - start) + " ms");


        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }


    }
}````


the subjectJson is a json file with 50K row


the js is expression resolver 


something like (not exactly the js code, but some idea)




// Create a utility object for handling expressions
var ExpressionProcessor = {
    // Compile an expression
    compile: function (exp) {
        var originalExp = exp;
        var dateMatch = exp.match(dateRegex);
        var value;
        var format;

        if (dateMatch) {
            value = dateMatch[1];
            format = null;

            if (dateMatch.length > 2 && dateMatch[2]) {
                format = "'" + dateMatch[2] + "'";
            }

            exp = "formatDate(" + value + "," + format + ")";
        }

        var amountMatch = exp.match(amountRegex);
        if (amountMatch) {
            value = amountMatch[1];
            format = null;
            var currency = null;

            var amountMatch2 = amountMatch[2];
            if (typeof amountMatch2 !== "undefined" && amountMatch2 !== null && amountMatch2.length > 0) {
                format = "'" + amountMatch[2] + "'";
            }

            if (typeof amountMatch[3] !== "undefined" && amountMatch[3] !== null) {
                currency = amountMatch[3];
            }

            if (typeof currency == "string") {
                currency = currency.replace(/^\s+|\s+$/g, "");
                if (currency === "") {
                    currency = '""';
                }
            }

            exp = "formatAmount(" + value + ", " + currency + ", " + format + ")";
            exp = originalExp.replace(amountRegex, function () {
                return exp;
            });
        }

        var emojiMatch = exp.match(emojiRegex);
        if (emojiMatch) {
            exp = "'{{" + exp + "}}'";
        }

        var stringMatch = exp.match(stringRegex);
        var maxLength = null;
        if (stringMatch) {
            value = stringMatch[1];
            var stringMatch2 = stringMatch[2];
            if (typeof stringMatch2 !== "undefined" && stringMatch2 !== null && stringMatch2.length > 0) {
                maxLength = stringMatch[2];
            }

            exp = "limitString(" + value + "," + maxLength + ")";
            exp = originalExp.replace(stringRegex, exp);
        }
        return exp;
    },

    // Evaluate an expression
    eval: function (exp, ctx, parentCtx, options) {
        var hasError = false;
        var errorObj = null;
        try {
            console.log("Evaluating expression: '" + exp + "'");
            console.log(exp);
            var d = new Date();
            var result = this.evalNode(exp, ctx, parentCtx, options);
            var t = new Date();
            console.log("Expression evaluation time (ms): " + (t - d));
            if (typeof result === "undefined" || result === null) {
                hasError = true;
                errorObj = new Error("Invalid result after evaluating expression: '" + result + "'");
            }
        } catch (e) {
            hasError = true;
            errorObj = { message: "Error - Could not evaluate expression Error Message: " + e.message, stack: e.stack };
        }

        if (hasError) {
            console.error(errorObj.message);
        } else {
            return result;
        }
    },

    // Evaluate a node within an expression
    evalNode: function (node, ctx, parentCtx, options) {
        // Implement node evaluation logic here
    },

    // Get a property from an object
    getChild: function (obj, prop, ctx, parentCtx, options) {
        // Implement property retrieval logic here
    },

    // Check if an object is a valid node
    isNode: function (node) {
        // Implement node type checking logic here
    }
};

// Regular expressions for matching different expression patterns
var dateRegex = /* ... */;
var amountRegex = /* ... */;
var emojiRegex = /* ... */;
var stringRegex = /* ... */;



@omarmahamid
Copy link
Author

@oubidar-Abderrahim

anyway,

the issue is that with V8 wrapper to JAVA, it works much much faster.

but with graaljs, it works much much slower

my question is, should I use specific compiler for graaljs ? to optimize the process ? like JIT compiler ?

how ? any example ?

@oubidar-Abderrahim
Copy link
Member

Hi @woess, can you take a look into this please?

@oubidar-Abderrahim
Copy link
Member

@omarmahamid could you please verify this issue with the newest build of graalJS, also please specify the version you were using before. Thank you

@omarmahamid
Copy link
Author

@oubidar-Abderrahim

can you please explain what changed since 23.0.0 and the new version 23.1.1 ?
which features the new version contains ?

@oubidar-Abderrahim
Copy link
Member

You can check the release notes for 23.0 here and for 23.1 here

@woess
Copy link
Member

woess commented Oct 26, 2023

I can't comment on this without a runnable reproducer that demonstrates the performance problem.

So my only advice would be to ensure that compiled code can be cached between runs, e.g. in case of multiple contexts evaluating the same sources, create the Contexts with a shared Engine, evaling shared Sources; and in case of one-off runs, consider use of Native Image.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
performance Performance of the engine (peak or warmup)
Projects
None yet
Development

No branches or pull requests

3 participants