Groovy ASTBuilder bad performance with multiple threads

Viewed 171

I'm using Groovy's ASTBuilder (version 2.5.5) in a project. It's being used to parse and analyze groovy expressions received via a REST API. This REST service receives thousands of requests, and the analysis is done on the fly.

I'm noticing some serious performance issues in a multithreaded environment. Below is a simulation, running 100 threads in parallel:

int numthreads = 100;

final Callable<Void> task = () -> {
    long initial = System.currentTimeInMillis();
    // Simple rule
    new AstBuilder().buildFromString("a+b");

    System.out.print(String.format("\n\nThread took %s ms.", 
        System.currentTimeInMillis() - initial));
    return null;
};

final ExecutorService executorService = Executors.newFixedThreadPool(numthreads);
final List<Callable<Void>> tasks = new ArrayList<>();
while (numthreads-- > 0) {
    tasks.add(task);
}
for (Future<Void> future : executorService.invokeAll(tasks)) {
    future.get();
}

Im trying with different thread loads. The greater the number, the slower.

  • 100 threads => ~1800ms
  • 200 threads => ~2500ms
  • 300 threads => ~4000ms

However, if I serialize the threads, (like setting the pool size to 1), I get much better results, around 10ms each thread. Can someone please help me understand why is this happening?

2 Answers

Performing multiple threaded code, computer shares threads between physical CPU cores. That means the more the number of threads exceeds number of cores, the less benefit you get from every thread. In your example the number of threads increases with number of tasks. So with growing up of the task number every CPU core forced to process the more and more threads. At the same time you may notice that difference between numthreads = 1 and numthreads = 4 is very small. Because in this case every core processes only few (or even just one) thread. Don't set number of threads much more than number of physical CPU threads because it doesn't make a lot of sense.

Additionally in your example you're trying to compare how different numbers of threads performs with different numbers of tasks. But in order to see the efficiency of multiple threaded code you have to compare how the different numbers of threads performs with the same number of tasks. I would change the example the next way:

int threadNumber = 16;
int taskNumber = 200;

//...task method

final ExecutorService executorService = Executors.newFixedThreadPool(threadNumber);
final List<Callable<Void>> tasks = new ArrayList<>();
while (taskNumber-- > 0) {
    tasks.add(task);
}

long start = System.currentTimeMillis();
for (Future<Void> future : executorService.invokeAll(tasks)) {
    future.get();
}
long end = System.currentTimeMillis() - start;
System.out.println(end);
executorService.shutdown();

Try this code for threadNumber=1 and, lets say, threadNumber=16 and you'll see the difference.

Dynamic evaluation of expressions involves a lot of resources including class loading, security manager, compilation and execution. It is not designed for high performance. If you just need to evaluate an expression for its value, you could try groovy.util.Eval. It may not consume as many resources as AstBuilder. However, it is probably not going to be that much different, so don't expect too much.

If you want to get the AST only and not any extra information like types, you could call the parser more directly. This would involve a lot fewer resources. See org.codehaus.groovy.control.ParserPluginFactory for more direct access to the source parser.

Related