Can't get Multithreaded Nashorn uses to Scale

Jesus Luzon jluzon at riotgames.com
Tue Dec 6 23:03:54 UTC 2016


When we share one invocable across many threads and run invokeFunction it
happens, such as this:

ExecutorService executor = Executors.newFixedThreadPool(50);
>
>         Invocable invocable = generateInvocable(script);
>
>         AtomicLong count = new AtomicLong();
>
>         for (int i = 0; i < 50; i++) {
>
>             executor.submit(new Runnable() {
>
>                 @Override
>
>                 public void run() {
>
>                     try {
>
>                             while(true) {
>
>                                 invocable.invokeFunction("transform",
>> something);
>
>                                 count.incrementAndGet();
>
>                             }
>
>                         } catch (NoSuchMethodException | ScriptException
>> e) {
>
>                             e.printStackTrace();
>
>                         }
>
>                     }
>
>             });
>
>         }
>
>


On Tue, Dec 6, 2016 at 2:59 PM, Jim Laskey (Oracle) <james.laskey at oracle.com
> wrote:

> Intersting.  The example you posted demonstrates this behaviour?  If so
> I’ll file a bug and dig in.  It sounds like an object is being reused
> across invocations and accumulating changes to the property map.
>
> — Jim
>
>
> On Dec 6, 2016, at 5:12 PM, Jesus Luzon <jluzon at riotgames.com> wrote:
>
> With more threads you are impacting the same 8 cores, so it will taper off
>> after 8 threads.  If it’s a 2x4 core machine then I can see 4 being a
>> threshold depending on System performance.  Transport: I meant if you were
>> using sockets to provide the script.
>
> This makes sense. This one's on me then.
>
>
>> So you are using the same invocable instance for all threads?  If so,
>> then you are probably good to go.  As far as leaks are concerned, not sure
>> how you would get leaks from Nashorn.  The JSON object is written in Java,
>> and little JavaScript involved.
>
>
>
>> In your example, pull up Invocable invocable = generateInvocable(script);
>> out of the loop and use the same invocable for all threads.
>
>
> We were using one invocable across all threads and we were getting
> slowdowns on execution, high CPU Usage and memory leaks that led to
> OutOfMemory errors. I could trace the leak to
>
> jdk.nashorn.internal.objects.Global -> *objectSpill* Object[8] ->
> jdk.nashorn.internal.scripts.JO4 -> *arrayData*
> jdk.nashorn.internal.runtime.arrays.SparseArraysData -> *underlying*
> jdk.nashorn.internal.runtime.arrays.DeletedArrayFilter
>
> which just keeps growing forever.
>
> On Tue, Dec 6, 2016 at 6:30 AM, Jim Laskey (Oracle) <
> james.laskey at oracle.com> wrote:
>
>>
>> On Dec 6, 2016, at 9:56 AM, Jesus Luzon <jluzon at riotgames.com> wrote:
>>
>> The cost of creating a new engine is significant.  So share an engine
>>> across threads but use *eval
>>> <https://docs.oracle.com/javase/7/docs/api/javax/script/ScriptEngine.html#eval(java.lang.String,%20javax.script.ScriptContext)>*
>>> (String
>>> <https://docs.oracle.com/javase/7/docs/api/java/lang/String.html>
>>>  script, ScriptContext
>>> <https://docs.oracle.com/javase/7/docs/api/javax/script/ScriptContext.html>
>>>  context) instead, separate context per execution.  If your JavaScript
>>> code does not modify globals you can get away with using the same engine,
>>> same compiled script on each thread.
>>
>>
>> I guess there's a few things here I don't understand. One thing I'm
>> trying to do is sharing a CompiledScript (which is why I'm using
>> invocable). Also, what exactly does modify globals mean? All our filters do
>> the same thing, make a function that takes a JSON String, turns it into a
>> JSON, modifies it and then stringifies it back. No state is changed of
>> anything else but there are temporary vars created inside the scope of the
>> function. When we run this multithreaded, running invokeFunction slows down
>> significantly and we get crazy memory leaks.
>>
>>
>> So you are using the same invocable instance for all threads?  If so,
>> then you are probably good to go.  As far as leaks are concerned, not sure
>> how you would get leaks from Nashorn.  The JSON object is written in Java,
>> and little JavaScript involved.
>>
>>
>> Of course there are many factors involved n performance.  How many cores
>>> do you have on the test machine?  How much memory in the process?  What
>>> transport are you using between threads?  That sort of thing.  Other than
>>> constructing then engine and context Nashorn performance should scale.
>>
>> I'm using an 8 core machine to test with 2.5Gs of RAM allocated to the
>> process. Not sure what transports between threads means, but this is the
>> code I'm benchmarking with. Increasing the number of threads actually makes
>> it go faster until about 4 threads, then adding more threads takes the same
>> amount to get to 1000 and and after a certain point it is just slower to
>> get to 1000 counts. Some of our filters need to be able to run over 1000
>> times a second (across all threads) and the fastest time I could actually
>> get with this was about 2.4 seconds for a 1000 counts.
>>
>>>         ExecutorService executor = Executors.newFixedThreadPool(50);
>>>
>>>         AtomicLong count = new AtomicLong();
>>>
>>>         for (int i = 0; i < 50; i++) {
>>>
>>>             executor.submit(new Runnable() {
>>>
>>>                 @Override
>>>
>>>                 public void run() {
>>>
>>>
>>>>                         try {
>>>
>>>                             Invocable invocable =
>>>> generateInvocable(script);
>>>
>>>                             while(true) {
>>>
>>>                                 invocable.invokeFunction("transform",
>>>> something);
>>>
>>>                                 count.incrementAndGet();
>>>
>>>                             }
>>>
>>>                         } catch (NoSuchMethodException | ScriptException
>>>> e) {
>>>
>>>                             e.printStackTrace();
>>>
>>>                         }
>>>
>>>                     }
>>>
>>>             });
>>>
>>>         }
>>>
>>>         long lastTimestamp = System.currentTimeMillis();
>>>
>>>         while(true) {
>>>
>>>
>>>>             if (count.get() > 1000) {
>>>
>>>                 count.getAndAdd(-1000);
>>>
>>>                 System.out.println((System.currentTimeMillis() -
>>>> lastTimestamp)/1000.0);
>>>
>>>                 lastTimestamp = System.currentTimeMillis();
>>>
>>>             }
>>>
>>>         }
>>>
>>>
>> With more threads you are impacting the same 8 cores, so it will taper
>> off after 8 threads.  If it’s a 2x4 core machine then I can see 4 being a
>> threshold depending on System performance.  Transport: I meant if you were
>> using sockets to provide the script.
>>
>> In your example, pull up Invocable invocable = generateInvocable(script);
>> out of the loop and use the same invocable for all threads.
>>
>> - Jim
>>
>>
>>
>>
>> On Tue, Dec 6, 2016 at 5:31 AM, Jim Laskey (Oracle) <
>> james.laskey at oracle.com> wrote:
>>
>>>
>>> On Dec 6, 2016, at 9:19 AM, Jesus Luzon <jluzon at riotgames.com> wrote:
>>>
>>> Hey Jim,
>>>
>>> I looked at it and I will look into loadWithNewGlobal to see what
>>> exactly it does since it could be relevant. As for the rest, for my use
>>> case having threads in the JS would not help. We're using Nashorn to build
>>> JSON filters in a Dynamic Proxy Service and need any of the threads
>>> processing a request to be able to execute the script to filter.
>>>
>>>
>>> The cost of creating a new engine is significant.  So share an engine
>>> across threads but use *eval
>>> <https://docs.oracle.com/javase/7/docs/api/javax/script/ScriptEngine.html#eval(java.lang.String,%20javax.script.ScriptContext)>*
>>> (String
>>> <https://docs.oracle.com/javase/7/docs/api/java/lang/String.html>
>>>  script, ScriptContext
>>> <https://docs.oracle.com/javase/7/docs/api/javax/script/ScriptContext.html>
>>>  context) instead, separate context per execution.  If your JavaScript
>>> code does not modify globals you can get away with using the same engine,
>>> same compiled script on each thread.
>>>
>>>
>>> Also, when you say a new engine per threads is the worst case what
>>> exactly do you mean? I would expect an initial cost of compiling the script
>>> on each thread and then each engine should be able to do its own thing, but
>>> what I'm seeing is that when running with more than 10 threads all my
>>> engines get slow at executing code, even though they are all completely
>>> separate from each other.
>>>
>>>
>>> Of course there are many factors involved n performance.  How many cores
>>> do you have on the test machine?  How much memory in the process?  What
>>> transport are you using between threads?  That sort of thing.  Other than
>>> constructing then engine and context Nashorn performance should scale.
>>>
>>>
>>> On Tue, Dec 6, 2016 at 5:07 AM, Jim Laskey (Oracle) <
>>> james.laskey at oracle.com> wrote:
>>>
>>>> Jesus,
>>>>
>>>> Probably the most informative information is in this blog.
>>>>
>>>> https://blogs.oracle.com/nashorn/entry/nashorn_multi_threading_and_mt
>>>>
>>>> This example uses Executors but threads would work as well.
>>>>
>>>> I did a talk that looked at different methods to max out multithreading
>>>> performance.  A new engine per thread is the worst case.  A new context per
>>>> thread does much better.  A new global per thread is the best while
>>>> remaining thread safe.
>>>>
>>>> Cheers,
>>>>
>>>> — Jim
>>>>
>>>>
>>>>
>>>>
>>>>
>>>> On Dec 6, 2016, at 8:45 AM, Jesus Luzon <jluzon at riotgames.com> wrote:
>>>>
>>>> Hey folks,
>>>>
>>>> I've tried many different ways of using Nashorn multithreaded based on
>>>> what
>>>> I've found on the internet and I still can't get a single one to scale.
>>>> Even the most naive method of making many script engines with my script
>>>> tends to bottleneck itself when I have more than 10 threads invoking
>>>> functions.
>>>>
>>>> I'm using the following code to compile my script and
>>>> invocable.invokeFunction("transform", input) to execute:
>>>>
>>>>    static Invocable generateInvocable(String script) throws
>>>> ScriptException {
>>>>        ScriptEngineManager manager = new ScriptEngineManager();
>>>>        ScriptEngine engine =
>>>> manager.getEngineByName(JAVASCRIPT_ENGINE_NAME);
>>>>        Compilable compilable = (Compilable) engine;
>>>>        final CompiledScript compiled = compilable.compile(script);
>>>>        compiled.eval();
>>>>        return (Invocable) engine;
>>>>    }
>>>>
>>>>
>>>>
>>>> The script I'm compiling is:
>>>>
>>>>        String script = "function transform(input) {" +
>>>>                "var result = JSON.parse(input);" +
>>>>                "response = {};\n" +
>>>>                "for (var i = 0; i < result.length; i++) {\n" +
>>>>                "    var summoner = {};\n" +
>>>>                "    summoner.id = result[i].id;\n" +
>>>>                "    summoner.name = result[i].name;\n" +
>>>>                "    summoner.profileIconId =
>>>> result[i].profileIconId;\n" +
>>>>                "    summoner.revisionDate = result[i].revisionDate;\n" +
>>>>                "    summoner.summonerLevel = result[i].level;\n" +
>>>>                "    response[summoner.id] = summoner;\n" +
>>>>                "}\n" +
>>>>                "result = response;" +
>>>>                "return JSON.stringify(result);" +
>>>>                "};";
>>>>
>>>>
>>>>
>>>> I've also tried other more scaleable ways to work with scripts
>>>> concurrently, but given that this is the most naive method where
>>>> everything
>>>> is brand new and I still get slowness calling them concurrently I fear
>>>> that
>>>> maybe I'm overlooking something extremely basic on my code.
>>>>
>>>> Thanks.
>>>> -Jesus Luzon
>>>>
>>>>
>>>
>>
>>
>
>


More information about the nashorn-dev mailing list