Синхронизация и производительность Java в аспекте - PullRequest
5 голосов
/ 16 мая 2009

Я только что понял, что мне нужно синхронизировать значительное количество кода для сбора данных в аспекте, но производительность - это реальная проблема. Если производительность сильно снижается, мой инструмент будет выброшен. Я буду писать целые и длинные значения индивидуально и для различных массивов, списков массивов и карт. В моем приложении будет несколько потоков, которые будут выполнять вызовы функций. Какие вещи я должен остерегаться, это отрицательно скажется на производительности? Какие шаблоны кода более эффективны?

В частности, у меня есть метод, который вызывает много других методов записи данных:

void foo() {
    bar();
    woz();
    ...
}

Методы в большинстве случаев добавляют добавление полей аспекта

void bar() {
    f++; // f is a field of the aspect
    for (int i = 0; i < ary.length; i++) {
        // get some values from aspect point cut
        if (some condiction) {
            ary[i] += someValue; // ary a field of the aspect
        }
     }
 }

Должен ли я синхронизировать foo или bar, woz и другие по отдельности, или я должен переместить весь код в bar, woz и т. Д. В foo и просто синхронизировать его? Должен ли я синхронизироваться на this, на специально созданном объекте синхронизации:

private final Object syncObject = new Object();

(см. этот пост) или для отдельных элементов данных в методах:

ArrayList<Integer> a = new ArrayList<Integer>();

void bar() {    
    synchronize(a) {
        // synchronized code
    }
}

Ответы [ 5 ]

9 голосов
/ 16 мая 2009

Параллелизм чрезвычайно сложен. Это очень легко понять неправильно, и очень трудно понять это правильно. Я бы не стал слишком беспокоиться о производительности на этом этапе. Моей первой и главной задачей было бы обеспечить безопасную работу параллельного кода (без блокировок и условий гонки).

А вот по вопросу производительности: если есть сомнения, профиль. Трудно сказать, насколько разные схемы синхронизации влияют на производительность. Нам еще труднее давать вам предложения. Нам нужно было бы увидеть намного больше вашего кода и получить более глубокое понимание того, что делает приложение, чтобы дать вам действительно полезный ответ. Напротив, профилирование дает вам веские доказательства того, что один подход медленнее другого. Это может даже помочь вам определить, где замедление.

В настоящее время для Java существует множество отличных инструментов профилирования. Профилировщики Netbeans и Eclipse хороши.

Кроме того, я бы рекомендовал вообще избегать необработанной синхронизации. Попробуйте использовать некоторые классы в пакете java.util.concurrency. Они делают написание параллельного кода намного проще и намного менее подвержены ошибкам.

Кроме того, я рекомендую вам прочитать Параллелизм Java на практике Брайана Гетца и др. Это очень хорошо написано и охватывает много вопросов.

4 голосов
/ 16 мая 2009

Практическое правило - не синхронизировать на this - чаще всего это снижение производительности - все методы синхронизируются на одном объекте.

Подумайте об использовании блокировок - это очень хорошая абстракция и множество замечательных функций, таких как: попытка блокировки на определенный период времени и затем сдача:

if(commandsLock.tryLock(100, TimeUnit.MILLISECONDS)){
     try { 
         //Do something
     }finally{
          commandsLock.unlock();
     }
}else{
     //couldnt acquire lock for 100 ms
}   

Я второе мнение об использовании java.util.concurrent. Я бы сделал два уровня синхронизации

  • синхронизировать доступ к коллекции (если это необходимо)
  • синхронизировать доступ к полю

Доступ к коллекции

Если ваша коллекция read-only т.е. никакие элементы не будут удалены-вставлены (но элементы могут измениться), я бы сказал, что вы должны использовать синхронизированные коллекции (но это может быть не нужно ...) и не синхронизировать итерации:

Только для чтения:

for (int i = 0; i < ary.length; i++) {
    // get some values from aspect point cut
    if (some condiction) {
        ary += someValue; // ary a field of the aspect
    }
 }

и ary является экземпляром, полученным Collections.synchronizedList.

чтения-записи

synchronized(ary){
    for (int i = 0; i < ary.length; i++) {
        // get some values from aspect point cut
        if (some condiction) {
            ary += someValue; // ary a field of the aspect
        }
     }
 }

Или используйте несколько одновременных коллекций (например, CopyOnWriteArrayList ), которые по своей сути безопасны для therad.

Основное отличие состоит в том, что в первом варианте только для чтения любое количество потоков может проходить по этим коллекциям, а во втором - только один за раз. В обоих случаях только один тэрад за раз должен увеличивать любое заданное поле.

Доступ к полю

Синхронизация приращений в полях отдельно от синхронизации итераций.

нравится:

  Integer foo = ary.get(ii); 
  synchronized(foo){
      foo++;
  }

избавиться от синхронизации

  1. Использовать одновременные коллекции (из java.util.concurrent - не из `Collections.synchronizedXXX ', последние по-прежнему нуждаются в синхронизации при обходе).
  2. Используйте java.util.atomic, которые позволяют вам атомарно увеличивать поля.

Что-то, что вы должны смотреть:

Модель памяти Java - доклад, который дает очень хорошее понимание того, как работает синхронизация и выравнивание данных в JAVA.

3 голосов
/ 26 октября 2011

Должна быть разница в производительности между встроенной языковой конструкцией и библиотекой, но опыт научил меня не догадываться, когда дело доходит до производительности.

3 голосов
/ 16 мая 2009

Upadte: с момента написания ниже, я вижу, вы немного обновили вопрос. Простите мое невежество - я понятия не имею, что такое «аспект», - но из примера кода, который вы опубликовали, вы также можете рассмотреть возможность использования атомарных / параллельных коллекций (например, AtomicInteger, AtomicIntegerArray) или средства обновления атомных полей, Впрочем, это может означать ре-факторинг вашего кода. (В Java 5 на гиперпоточном Xeon с двумя процессами пропускная способность для AtomicIntegerArray значительно лучше, чем для синхронизированного массива; извините, я еще не успел повторить тест на более поздней версии JVM для procs / более поздней версии - обратите внимание, что с тех пор производительность «синхронизированного» улучшилась.)

Без более конкретной информации или метрик о вашей конкретной программе лучшее, что вы можете сделать, это просто следовать хорошему дизайну программы . Стоит отметить, что производительность и оптимизация блокировок синхронизации в JVM превзошли одну из областей (если не области), которой уделялось больше всего внимания и внимания в течение последних нескольких лет. И поэтому в последних версиях JVM все не так плохо.

В общем, я бы сказал, синхронизировать минимально, не сходя с ума . Под «минимально» я подразумеваю, чтобы вы удерживали блокировку как можно меньше времени, и чтобы только те части, которые должны использовать эту конкретную блокировку, использовали эту конкретную блокировку. Но только если изменение легко сделать и легко доказать, что ваша программа все еще верна. Например, вместо этого:

synchronized (a) {
  doSomethingWith(a);
  longMethodNothingToDoWithA();
  doSomethingWith(a);
}

рассмотрите возможность сделать это тогда и только тогда, когда ваша программа будет по-прежнему правильной:

synchronized (a) {
  doSomethingWith(a);
}
longMethodNothingToDoWithA();
synchronized (a) {
  doSomethingWith(a);
}

Но помните, что странное простое обновление поля с блокировкой, удерживаемой без необходимости, вероятно, не будет иметь большого значения и может фактически улучшить производительность. Иногда может быть полезно удерживать замок немного дольше и делать меньше «уборки» замка. Но JVM может принять некоторые из этих решений , так что вам не нужно быть слишком параноиком - просто делайте разумные вещи, и у вас все будет хорошо.

В общем, попробуйте установить отдельную блокировку для каждого набора методов / обращений, которые вместе образуют «независимый процесс». Кроме этого, наличие отдельного объекта блокировки может быть хорошим способом инкапсуляции блокировки внутри класса, которым он используется (т.е. предотвращение его использования внешними вызывающими лицами способом, который вы не предсказывали), но, вероятно, нет никакой разницы в производительности от использования одного объекта к другому в качестве блокировки (например, использование самого экземпляра против частного объекта, объявленного просто как блокировка в этом классе, как вы предлагаете), при условии, что два объекта в противном случае будут использоваться в точно так же.

1 голос
/ 16 мая 2009

Если вы скомпилируете аспект в приложение, то у вас практически не будет снижения производительности, если вы сделаете это во время выполнения (ткачество нагрузки), то вы увидите снижение производительности.

Если вы хотите, чтобы каждый аспект был перманентным, тогда это может снизить потребность в синхронизации.

У вас должна быть как можно меньшая синхронизация, как можно более короткое время, чтобы уменьшить любые проблемы.

Если возможно, вы можете захотеть разделить как можно меньшее состояние между потоками, сохраняя как можно больше локального, чтобы уменьшить любые проблемы взаимоблокировки.

Более подробная информация приведет к лучшему ответу между прочим. :)

...