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

Improve engine creation performance #615

Closed
sebastienros opened this issue Mar 16, 2019 · 9 comments
Closed

Improve engine creation performance #615

sebastienros opened this issue Mar 16, 2019 · 9 comments

Comments

@sebastienros
Copy link
Owner

While doing some memory profiling I found that the main cost was during the initialization of the constructor objects when we create many ClrFunctionInstance and PropertyDescriptor instances like here:

https://github.com/sebastienros/jint/blob/dev/Jint/Native/Date/DateConstructor.cs#L77

I assume these objects should be immutable (they can still be replaced) and we should be able to mark them static and reuse them.

Snapshot of 50 requests in Orchard Core:

image

@sebastienros
Copy link
Owner Author

@lahma what is your thought on that?

@lahma
Copy link
Collaborator

lahma commented Mar 16, 2019

That sounds reasonable. There's the immutable property descriptor already for undfined, but it suffers slight performance penalty of having the CustomValue flag set that directs to virtual call. IIRC descriptors are created as new instances when re-configured, hopefully there are no pitfalls.

I've only profiled the run time of initialized engine so this is a path I've not set my foot on 🙂

@lahma
Copy link
Collaborator

lahma commented May 17, 2019

PR #625

@sebastienros
Copy link
Owner Author

sebastienros commented May 17, 2019

You are mentioning descriptors, but I was thinking about reusing the ClrFunctionInsrance instances inside them as static. These won't be accessed or changed.

@lahma
Copy link
Collaborator

lahma commented May 17, 2019

The problem with static ClrFunctionInstance is that they reference engine, engine is used for error reporting etc. As ClrFunctionInstance is ObjectInstance, it holds a field and reference for the engine. So reusing would probably require a bigger refactor.. Probably ClrFunctionInstance could override exception throwing to use non-engine-bound rerporting, that would allow this - but it can get messy when ClrFunctionInstance would be a special case where you can't rely to have _engine set, like in FunctionInstance etc methods that have been inherited.

@sebastienros
Copy link
Owner Author

Having the engine be part of the state being an issue in some cases, this could be solved by passing a "context" as arguments where it makes sense. This would probably be the engine itself of something that contains it plus some other local information (like steps, debug info, ...)

@unruledboy
Copy link

unruledboy commented Sep 15, 2019

@sebastienros

Talking about performance, I wonder:

  1. the parsed result can be reused to speed up the performance (I mean, is cache built-in?)
  2. native compilation: think RegExp Compiled, so it won't be interpreted every time, like V8

So, I did an initial perf test, pretty awesome speed on my Surface Book 2 (i7 + 16GB), I am impressed:

Direct: 37,425 times / sec
Cache: 595,238 times / sec

Basically the cached version is roughly 15 times faster than the one that parses every time.

With the following test codes:


private static void TestPerfDirect()
        {
            var t = Stopwatch.StartNew();
            const int count = 100000;
            var r = new Random();
            for (int i = 0; i < count; i++)
            {
                var engine = new Engine()
            .Execute(@"function process(a, b) { 
    if (a.type === 'foo') {
        a.value = b;
        return true;
    }
    return false;
}");
                var value = new Foo { Type = "foo", Value = 321.64 };
                var result = engine.Invoke("process", value, r.NextDouble() * 100);
                var b = result.AsBoolean();
            }
            Console.WriteLine("Direct: {0} times / sec", count * 1000.0 / t.ElapsedMilliseconds );
        }
private static void TestPerfCached()
        {
            var t = Stopwatch.StartNew();
            var r = new Random();
            const int count = 100000;
            var engine = new Engine()
        .Execute(@"function process(a, b) { 
    if (a.type === 'foo') {
        a.value = b;
        return true;
    }
    return false;
}");
            for (int i = 0; i < count; i++)
            {
                var value = new Foo { Type = "foo", Value = 321.64 };
                var result = engine.Invoke("process", value, r.NextDouble() * 100);
                var b = result.AsBoolean();
            }
            Console.WriteLine("Cache: {0} times / sec", count * 1000.0 / t.ElapsedMilliseconds);
        }
        public class Foo
        {
            public string Type { get; set; }
            public double Value { get; set; }
        }

The randomized 2nd parameter (b) was used to determine it is indeed executing with different parameter every run.

@lahma
Copy link
Collaborator

lahma commented Sep 16, 2019

Can you share us the actual caching logic? It can be a slippery slope to find a caching logic that works with variable input.

@unruledboy
Copy link

unruledboy commented Sep 16, 2019

@lahma it is not really a native cache mechanism per say. I use DynamicExpresso (https://github.com/davideicardi/DynamicExpresso) in a project to do expression evaluation, and ConcurrentDictionary<string, List<Parameter>> is used to maintain the parameters per biz (an artificial text key based on the biz object).

For Jint, the cache I mentioned it is nothing but just not running .Execute() every time, it will run once and call .Invoke() repeatedly. I am not sure if I am using it properly, but with the test above, I believe that's what I am after.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants