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 <jlu...@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.las...@oracle.com 
> <mailto:james.las...@oracle.com>> wrote:
> 
>> On Dec 6, 2016, at 9:56 AM, Jesus Luzon <jlu...@riotgames.com 
>> <mailto:jlu...@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.las...@oracle.com 
>> <mailto:james.las...@oracle.com>> wrote:
>> 
>>> On Dec 6, 2016, at 9:19 AM, Jesus Luzon <jlu...@riotgames.com 
>>> <mailto:jlu...@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.las...@oracle.com <mailto:james.las...@oracle.com>> wrote:
>>> Jesus,
>>> 
>>> Probably the most informative information is in this blog.
>>> 
>>> https://blogs.oracle.com/nashorn/entry/nashorn_multi_threading_and_mt 
>>> <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 <jlu...@riotgames.com 
>>>> <mailto:jlu...@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 <http://summoner.id/> = result[i].id;\n" +
>>>>>                "    summoner.name <http://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 <http://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
>> 
>> 
> 
> 

Reply via email to