Я говорю Grails, так как существует так много библиотек Java. Но я немного пристрастен из-за того, что я родом из Java.
Если приложение не будет большим, то и того будет достаточно - и выбор должен зависеть от существующей инфраструктуры. Скажем, если у вас уже запущен сервер-контейнер Java-сервлетов, вы можете также придерживаться grails вместо того, чтобы предоставлять другой сервер для rails.
Изменить: Я изменил ваш пример и позволил ему работать на моем маленьком двухъядерном ноутбуке x200.
provisioned 2 batches to be executed
simpleCompuation:14
computationWithObjCreation:17
computationWithObjCreationAndExecutors:9
Как вы видите в исходном коде, я также исключил из измерения пакетное обеспечение и жизненный цикл исполнителя. Это более справедливо по сравнению с двумя другими методами.
Посмотрите результаты сами ...
import java.util.List;
import java.util.Vector;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ExecServicePerformance {
private static int count = 100000;
public static void main( String[] args ) throws InterruptedException {
final int cpus = Runtime.getRuntime().availableProcessors();
final ExecutorService es = Executors.newFixedThreadPool( cpus );
final Vector< Batch > batches = new Vector< Batch >( cpus );
final int batchComputations = count / cpus;
for ( int i = 0; i < cpus; i++ ) {
batches.add( new Batch( batchComputations ) );
}
System.out.println( "provisioned " + cpus + " batches to be executed" );
// warmup
simpleCompuation();
computationWithObjCreation();
computationWithObjCreationAndExecutors( es, batches );
long start = System.currentTimeMillis();
simpleCompuation();
long stop = System.currentTimeMillis();
System.out.println( "simpleCompuation:" + ( stop - start ) );
start = System.currentTimeMillis();
computationWithObjCreation();
stop = System.currentTimeMillis();
System.out.println( "computationWithObjCreation:" + ( stop - start ) );
// Executor
start = System.currentTimeMillis();
computationWithObjCreationAndExecutors( es, batches );
es.shutdown();
es.awaitTermination( 10, TimeUnit.SECONDS );
// Note: Executor#shutdown() and Executor#awaitTermination() requires
// some extra time. But the result should still be clear.
stop = System.currentTimeMillis();
System.out.println( "computationWithObjCreationAndExecutors:"
+ ( stop - start ) );
}
private static void computationWithObjCreation() {
for ( int i = 0; i < count; i++ ) {
new Runnable() {
@Override
public void run() {
double x = Math.random() * Math.random();
}
}.run();
}
}
private static void simpleCompuation() {
for ( int i = 0; i < count; i++ ) {
double x = Math.random() * Math.random();
}
}
private static void computationWithObjCreationAndExecutors(
ExecutorService es, List< Batch > batches )
throws InterruptedException {
for ( Batch batch : batches ) {
es.submit( batch );
}
}
private static class Batch implements Runnable {
private final int computations;
public Batch( final int computations ) {
this.computations = computations;
}
@Override
public void run() {
int countdown = computations;
while ( countdown-- > -1 ) {
double x = Math.random() * Math.random();
}
}
}
}
Это несправедливый тест для пула потоков по следующим причинам:
Учитывая следующие дополнительные шаги, которые пул потоков должен делать помимо создания объекта и выполнения задания,
Когда у вас есть реальное задание и несколько потоков, преимущества пула потоков будут очевидны.
Во-первых, есть несколько проблем с микробенчмарком. Вы делаете разминку, и это хорошо. Тем не менее, лучше провести тест несколько раз, чтобы понять, действительно ли он разогрелся, и расхождения в результатах. Также, как правило, лучше проводить тестирование каждого алгоритма в отдельных прогонах, иначе вы можете вызвать деоптимизацию при изменении алгоритма.
Задача очень мала, хотя я не совсем уверен, насколько она мала. Так что количество раз быстрее бессмысленно. В многопоточных ситуациях он будет касаться одних и тех же изменчивых мест, поэтому потоки могут вызвать действительно плохую производительность (используйте экземпляр Random
для каждого потока). Также цикл в 47 миллисекунд - это немного мало.
Конечно, переход к другому потоку для выполнения крошечной операции не будет быстрым. Если возможно, разделите задачи на более крупные. JDK7 выглядит так, как будто у него будет структура fork-join, которая пытается поддерживать мелкие задачи из алгоритмов разделения и владения, предпочитая выполнять задачи в одном потоке по порядку, а более крупные задачи вытягиваются из незанятых потоков.
Я не думаю, что это вообще реально, так как вы создаете новую службу-исполнитель каждый раз, когда вызываете метод. Если только у вас нет очень странных требований, которые кажутся нереалистичными - обычно вы создаете службу при запуске приложения, а затем отправляете ей задания.
Если вы снова попробуете тестирование, но инициализируете службу как поле , один раз, вне цикла синхронизации; тогда вы увидите фактические накладные расходы на отправку Runnables в службу по сравнению с их запуском самостоятельно.
Но я не думаю, что вы полностью уловили суть - исполнители предназначены не для эффективности, они ' re там, чтобы упростить координацию и передачу работы пулу потоков. Они всегда будут менее эффективны, чем простой вызов Runnable. run ()
самостоятельно (так как в конце дня служба-исполнитель все еще должна это сделать, предварительно выполнив некоторую дополнительную уборку). Они действительно сияют, когда вы используете их из нескольких потоков, требующих асинхронной обработки.
Также учтите, что вы смотрите на относительную разницу во времени при в основном фиксированной стоимости (накладные расходы исполнителя одинаковы, независимо от того, занимают ли ваши задачи 1 мс или 1 час для запуска) по сравнению с очень небольшим переменным количеством (ваш тривиальный запуск). Если сервис-исполнитель требует дополнительных 5 мсек для выполнения задачи в 1 мс, это не очень благоприятный показатель. Если для выполнения 5-секундной задачи (например, нетривиального SQL-запроса) требуется дополнительно 5 мсек, это совершенно незначительно и полностью того стоит.
Так что в некоторой степени это зависит от вашей ситуации - если у вас чрезвычайно критичный по времени раздел, выполняя множество небольших задач, которые не нужно выполнять параллельно или асинхронно, вы ничего не получите от Executor. Если вы обрабатываете более тяжелые задачи параллельно и хотите отвечать асинхронно (например, веб-приложение), то Executors отлично подходят.
Являются ли они лучшим выбором для вас, зависит от вашей ситуации, но на самом деле вам нужно попробовать тесты с реалистичными репрезентативными данными. Я не думаю, что было бы уместно делать какие-либо выводы из проведенных вами тестов, если только ваши задачи не настолько тривиальны (и вы не хотите повторно использовать экземпляр исполнителя ...).
веб-приложение), то исполнители великолепны.Являются ли они лучшим выбором для вас, зависит от вашей ситуации, но на самом деле вам нужно попробовать тесты с реалистичными репрезентативными данными. Я не думаю, что было бы уместно делать какие-либо выводы из проведенных вами тестов, если только ваши задачи не настолько тривиальны (и вы не хотите повторно использовать экземпляр исполнителя ...).
веб-приложение), то исполнители великолепны.Являются ли они лучшим выбором для вас, зависит от вашей ситуации, но на самом деле вам нужно попробовать тесты с реалистичными репрезентативными данными. Я не думаю, что было бы уместно делать какие-либо выводы из проведенных вами тестов, если только ваши задачи не настолько тривиальны (и вы не хотите повторно использовать экземпляр исполнителя ...).