Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

False sharing в многопоточном приложении на Java

Anna | 5.06.2014 | нет комментариев

JRE разрешает отвлекаться от определенной платформы, делая написание кросс-платформенного кода гораздо проще. Безусловно до идеала Write once, run anywhere не дотягивает, но жизнь облегчает значительно.

С изобилием framework’ов и полнотой собственной стандартной библиотеки, мысль о том, что программа запускается на абсолютно определенном железе, понемногу отходит на 2-й план. В большинстве случаев это оправдано, но изредка жизнь вносит свои коррективы.

Подавляющее множество современных процессоров имеют кэш-память для хранения Зачастую используемых данных. Кэш-память делится на блоки (Сache line). Механизмы реализующие Cache coherence обеспечивают синхронизацию кэш-памяти между ядрами процессора(ов) в компьютерной системе.

Термин false sharing обозначает доступ к различным объектам в программе, разделяющим один и тот же блок кэш-памяти. False sharing в многопотоковом приложении, когда в одном блоке оказываются переменные модифицируемые из различных потоков, ведет к снижению продуктивности и увеличению нагрузки на Cache coherence механизмы. Детально о том как это происходит, дозволено прочесть в статье на эту тему.

Инструментарий

 

 

Пример

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

Спрятанный текст

public class SArray {
// если не сделать volatile jvm оптимизирует
    private static volatile long globalArray[] = new long[512];

    public static class MThread implements Runnable {
        private int aPos; 
        private  long iterations;

        public MThread(long iterations, int aPos)  {
            this.aPos = aPos;
            this.iterations = iterations;
        }

        @Override
        public void run() {
            for(long l = 0; l < iterations;   l) {
                  globalArray[aPos];
            }
            System.out.printf("A:TID:%d, count: %dn", 
              Thread.currentThread().getId(), globalArray[aPos]);
        }
    }

    private static final int THREAD_COUNT = 
              Runtime.getRuntime().availableProcessors();
    private static final long ITERATIONS = 1870234052L;

    public static void main(String[] args)  throws Throwable {
        Thread[] threads = new Thread[THREAD_COUNT];
        long smillis = System.currentTimeMillis();

        for(int i = 0; i < THREAD_COUNT;   i) {
            threads[i] = new Thread(new MThread(ITERATIONS, i));
        }
        for(Thread t: threads) {
            t.start();
        }
        for(Thread t: threads) {
            t.join();
        }
        System.out.printf("Total iterations on %d threads: %d, took %d msn", 
                THREAD_COUNT,
                ITERATIONS, System.currentTimeMillis() - smillis);
    }
}

Проверим что jvm не внесла лишних оптимизаций:

java -XX: UnlockDiagnosticVMOptions -XX:CompileCommand=print,SArray$MThread::run 
 -XX:PrintAssemblyOptions=intel -cp targetfalseshare-1.0-SNAPSHOT.jar SArray

 

Спрятанный текст

  0x0000000002350540: mov    r11d,DWORD PTR [r13 0xc]
  0x0000000002350544: mov    r10d,DWORD PTR [r8 0x70]  ;*getfield aPos
                                            ; - SArray$MThread::run@15 (line 18)
  0x0000000002350548: mov    r9d,DWORD PTR [r12 r10*8 0xc]
                                            ; implicit exception: dispatches to 0x00000000023505dd
  0x000000000235054d: cmp    r11d,r9d
  0x0000000002350550: jae    0x0000000002350599  ;*laload
                                            ; - SArray$MThread::run@19 (line 18)
  0x0000000002350552: shl    r10,0x3
; >>>>
; счетчик возрастает в памяти 
  0x0000000002350556: inc    QWORD PTR [r10 r11*8 0x10]
                                            ;*goto
                                            ; - SArray$MThread::run@27 (line 17)
  0x000000000235055b: add    rbx,0x1            ; OopMap{r8=Oop r13=Oop off=127}
                                             ;*goto
                                             ; - SArray$MThread::run@27 (line 17)
  0x000000000235055f: test   DWORD PTR [rip 0xfffffffffddefa9b],eax        # 0x0000000000140000
                                             ;*goto
; - SArray$MThread::run@27 (line 17)
                                              ;   {poll}
  0x0000000002350565: cmp    rbx,QWORD PTR [r13 0x10]
  0x0000000002350569: jl     0x0000000002350540  ;*ifge

Если globalArray не сделать volatile, то jvm не будет читать всякий раз из памяти:

Спрятанный текст

  0x00000000021e0592: add    rbx,0x1            ;*ladd
                                            ; - SArray$MThread::run@25 (line 17)
; >>>>
; значение считано 1 раз и в цикле только пишется в массив
  0x00000000021e0596: add    r8,0x1             ;*ladd 
                                            ; - SArray$MThread::run@21 (line 18)
  0x00000000021e059a: mov    QWORD PTR [r11 rcx*8 0x10],r8
                                             ; OopMap{r11=Oop r13=Oop off=127}
                                             ;*goto
                                             ; - SArray$MThread::run@27 (line 17)
  0x00000000021e059f: test   DWORD PTR [rip 0xfffffffffe24fa5b],eax        # 0x0000000000430000
                                             ;*goto
                                             ; - SArray$MThread::run@27 (line 17)
                                             ;   {poll}
  0x00000000021e05a5: cmp    rbx,r10
  0x00000000021e05a8: jl     0x00000000021e0592  ;*ifge

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

В данном случае volatile применен экстраординарно, Дабы сбить с толку оптимизатор. Запись вида volatile long[] array, обозначает что семантика volatile относится к указателю на массив, а не к его элементам.

Создаем новейший план в VTune Amplifier:

Спрятанный текст

image

Создаем и запускаем Generic Exploration analysis. В summary видим:

Спрятанный текст

image

CPI — 2.100, при типичном для расчетных задач 1 и менее. Переходим во view Hardware issues:

Спрятанный текст

image

Присутствует Contested access, обозначающий что данные записанные одним потоком, читаются иным потоком и при этом потоки выполняются на различных ядрах/CPU.
То есть ячейки массива globalArray попали в один cache line.

Для того Дабы избежать этой обстановки, разнесем ячейки в памяти на величину cache linе. В Intel i5 размер cache line составляет 64 байта. Меняем строчку

            threads[i] = new Thread(new MThread(ITERATIONS, i));

на

            threads[i] = new Thread(new MThread(ITERATIONS, (i   1) * 8));

Отчего не i * 8? Потому что в случае массивов, первым элементом позже заголовка объекта идет длина (поле length). При операциях доступа к элементам, jvm может считывать это поле для проверки допустимости индекса.
Запускаем повторный обзор в Vtune:

Спрятанный текст

image

CPI — 0.586, Contested access ушел, Пропорционально с CPI изменилось и время работы, с 13.7 до 4.5 секунд.
Тестирование проводилось на однопроцессорной машине, в случае многопроцессорной конфигурации, убыточные расходы на синхронизацию кэш-памяти будут еще огромнее.

Безусловно, такая же задача может появиться и при доступе к полям объектов. Но от того что наименьший размер объекта (c одним полем) в hostpot jvm 16 байт, то задача будет встречаться реже. Метод избежать false sharing для объектов с поддержкой наследования дозволено посмотреть в исходниках jmh, в реализации BlackHole’ов. Как один из вариантов — не создавать оптом объекты для всех потоков, а разнести данный процесс во времени.

Тестирование проводилось на машине с процессором Intel Core i5 3.3 GHz, 64bit JDK 1.7.0_21, Intel Vtune Amplifier XE 2013 Update 11 (build 300544) Evaluation license, Windows 7 64bit.

PS. Не стоит воспринимать цифры продуктивности приведенные в статье дословно.

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB