This project has moved. For the latest updates, please go here.

Crazy overhead calling V8 functions in a method being invoked for the first time

May 4, 2014 at 10:30 AM
Edited May 4, 2014 at 10:32 AM
Hi people, I'm using ClearScript for some JavaScript integration recently, and noticed that calling a V8 function is exceptionally expensive if the calling method is being invoked for the first time, creating high latency. To demonstrate the problem I created this:
    class Program
    {
        static V8ScriptEngine engine;

        static void Main(string[] args)
        {
            engine = new V8ScriptEngine(V8ScriptEngineFlags.DisableGlobalMembers);
            dynamic func = engine.Evaluate("(function(){})");

            Console.WriteLine("3 times per call (1st)");
            CallMultipleTimes(func);
            Console.WriteLine();

            Console.WriteLine("3 times per call (2nd)");
            CallMultipleTimes(func);
            Console.WriteLine();

            Console.WriteLine("once per call");
            CallOnce(func);
            CallOnce(func);
        }

        static void CallMultipleTimes(dynamic func)
        {
            var sw1 = new Stopwatch();
            var sw2 = new Stopwatch();
            var sw3 = new Stopwatch();

            sw1.Start();
            func();
            sw1.Stop();

            sw2.Start();
            func();
            sw2.Stop();

            sw3.Start();
            func();
            sw3.Stop();

            Console.WriteLine("1st: " + sw1.ElapsedTicks.ToString());
            Console.WriteLine("2nd: " + sw2.ElapsedTicks.ToString());
            Console.WriteLine("3rd: " + sw3.ElapsedTicks.ToString());
        }

        static void CallOnce(dynamic func)
        {
            var sw = new Stopwatch();
            sw.Start();
            func();
            sw.Stop();
            Console.WriteLine(sw.ElapsedTicks.ToString());
        }
    }
Which outputs something like:
3 times per call (1st)
1st: 123257
2nd: 38059
3rd: 37722

3 times per call (2nd)
1st: 114
2nd: 93
3rd: 76

once per call
38188
112
I tried profiling method calls, but the results aren't very assuming (70% in V8ScriptEngine.ctor, some 20% in Action.Invoke). I wonder if there is any way to mitigate this, beside creating a method just for calling functions? If it's not a ClearScript problem could ClearScript be modified to workaround, or it has to be left up to the application?

Thanks!
Coordinator
May 5, 2014 at 2:28 PM
Hi tadokoro,

That's just the way .NET's dynamic infrastructure works. The initial pass through a dynamic call site is expensive, but inline caching greatly speeds up subsequent passes.

One possibility, as you suggested, would be to replace dynamic calls with normal calls to a common method that holds a single dynamic call site. You'd still take the hit for the very first call though.

Another possibility would be to eliminate the use of the dynamic infrastructure. Unfortunately ClearScript currently doesn't support any alternate API for interacting with script objects.

Thanks!
Marked as answer by tadokoro on 5/6/2014 at 10:49 PM