Groovy ASTBuilder плохая производительность с несколькими потоками - PullRequest
0 голосов
/ 13 января 2019

Я использую Groovy ASTBuilder (версия 2.5.5) в проекте. Он используется для анализа и анализа нестандартных выражений, полученных через REST API. Эта служба REST получает тысячи запросов, и анализ выполняется на лету.

Я заметил некоторые серьезные проблемы с производительностью в многопоточной среде. Ниже приведено моделирование, в котором 100 потоков выполняются параллельно:

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();
}

Я пытаюсь с различными нагрузками потока. Чем больше число, тем медленнее.

  • 100 потоков => ~ 1800 мс
  • 200 потоков => ~ 2500 мс
  • 300 потоков => ~ 4000 мс

Однако, если я сериализую потоки (например, установив размер пула в 1), я получу намного лучшие результаты, около 10 мс каждый поток. Может кто-нибудь, пожалуйста, помогите мне понять, почему это происходит?

Ответы [ 2 ]

0 голосов
/ 13 января 2019

Динамическая оценка выражений включает в себя множество ресурсов, включая загрузку классов, менеджер безопасности, компиляцию и выполнение. Он не рассчитан на высокую производительность. Если вам просто нужно оценить выражение по его значению, вы можете попробовать groovy.util.Eval. Может потреблять не так много ресурсов, как AstBuilder. Тем не менее, это, вероятно, не будет сильно отличаться, поэтому не ожидайте слишком многого.

Если вы хотите получить только AST, а не какую-либо дополнительную информацию, такую ​​как типы, вы можете вызвать синтаксический анализатор более напрямую. Это потребовало бы гораздо меньше ресурсов. Смотрите org.codehaus.groovy.control.ParserPluginFactory для более прямого доступа к анализатору источника.

0 голосов
/ 13 января 2019

Выполняя многопоточный код, компьютер распределяет потоки между физическими ядрами ЦП. Это означает, что чем больше число потоков превышает количество ядер, тем меньше вы получаете выгоды от каждого потока. В вашем примере количество потоков увеличивается с количеством задач. Таким образом, с ростом числа задач каждое ядро ​​процессора вынуждено обрабатывать все больше и больше потоков. В то же время вы можете заметить, что разница между numthreads = 1 и numthreads = 4 очень мала. Потому что в этом случае каждое ядро ​​обрабатывает только несколько (или даже только один) поток. Не устанавливайте количество потоков намного больше, чем количество физических потоков ЦП, потому что это не имеет большого смысла.

Кроме того, в вашем примере вы пытаетесь сравнить, как разное количество потоков выполняет с разным количеством задач . Но чтобы увидеть эффективность многопоточного кода, вы должны сравнить, как различное число потоков выполняет с одинаковым количеством задач . Я бы изменил пример следующим образом:

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();

Попробуйте этот код для threadNumber=1 и, скажем, threadNumber=16, и вы увидите разницу.

...