2015-07-02 3 views
6

Я смотрел статью Мартина Томпсона. Это объяснение ложного обмена.Почему ложное разделение проблемы, если переменная, изменяемая потоком, помечена как измененная.

http://mechanical-sympathy.blogspot.co.uk/2011/07/false-sharing.html

public final class FalseSharing 
    implements Runnable 
    { 
     public final static int NUM_THREADS = 4; // change 
     public final static long ITERATIONS = 500L * 1000L * 1000L; 
     private final int arrayIndex; 

     private static VolatileLong[] longs = new VolatileLong[NUM_THREADS]; 


     static 
     {  
      for (int i = 0; i < longs.length; i++) 
      { 
       longs[i] = new VolatileLong(); 
      } 
     } 

     public FalseSharing(final int arrayIndex) 
     { 
      this.arrayIndex = arrayIndex; 
     } 

     public static void main(final String[] args) throws Exception 
     { 
      final long start = System.nanoTime(); 
      runTest(); 
      System.out.println("duration = " + (System.nanoTime() -start)); 
     } 

     private static void runTest() throws InterruptedException 
     { 
      Thread[] threads = new Thread[NUM_THREADS]; 

      for (int i = 0; i < threads.length; i++) 
      { 
       threads[i] = new Thread(new FalseSharing(i)); 
      } 

      for (Thread t : threads) 
      { 
       t.start(); 
      } 

      for (Thread t : threads) 
      { 
       t.join(); 
      } 
     } 

     public void run() 
     { 
      long i = ITERATIONS + 1; 
      while (0 != --i) 
      { 
       longs[arrayIndex].value = i; 
      } 
     } 

     public final static class VolatileLong 
     { 
      public volatile long value = 0L; 
      public long p1, p2, p3, p4, p5, p6; // comment out 
     } 
    } 

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

На рисунке 1 выше показана проблема с ложным совместным использованием. Поток, выполняющийся на ядре 1, хочет обновить переменную X, а поток на ядре 2 хочет обновить переменную Y. К сожалению, эти две горячие переменные находятся в одной и той же строке кэша. Каждый поток будет гоняться за владением линией кэша, чтобы они могли его обновить. Если ядро ​​1 получает право собственности, тогда подсистеме кеша необходимо будет аннулировать соответствующую строку кэша для ядра 2. Когда Core 2 получит право собственности и выполнит его обновление, ядро ​​1 будет объявлено недействительным для его копии строки кэша. Это будет пинг-понг назад и вперед через кеш L3, значительно влияя на производительность. Эта проблема будет еще более усугубляться, если конкурирующие ядра находятся в разных гнездах и, кроме того, придется пересекать межсоединение сокетов.

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

Так что в соответствии с моим пониманием. Если нить одна недействительна для кеширования нити два, это не станет apparant для потока два, пока не будет прочитано значение из собственной строки кэша. Значение, которое он читает, является изменчивым значением, поэтому это фактически делает грязный кеш, в результате чего получается чтение из основной памяти.

Где я ошибся в своем понимании?

Благодаря

+0

'volatile' является подсказкой для компилятора, позволяя ему знать, что значение может измениться, даже если оно не отображается так локально. Хотя это вызывает перезагрузку, его можно прочитать из кеша. –

+0

@JohnnyCage Это бессмыслица. Volatile - важное ключевое слово для многопоточности (а не «подсказка» для компилятора), как и синхронизированное ключевое слово и спецификация модели Java Memory Model (http://docs.oracle.com/javase/specs/jls/) se8/html/jls-17.html # jls-17.4) описывает его работу. Как реализуется модель памяти, зависит от JVM, архитектуры процессора и памяти и т. Д., И реальная реализация, вероятно, оказывает большее влияние на это, чем на JMM. –

+2

Ваше предположение о том, что оно вынуждает чтение/запись синхронной памяти некорректно. Он вставляет [барьеры памяти] (https://www.kernel.org/doc/Documentation/memory-barriers.txt), но CPU позволяет оптимизировать его, если он поддерживает согласованность. –

ответ

4

Если все переменные обновляются изменчивы, почему это обивка вызывает увеличение производительности?

Итак, есть две вещи, которые собираются здесь:

  1. Мы имеем дело с массивом VolatileLong объектов с каждой нити работают на своих собственных VolatileLong. (См. private final int arrayIndex).
  2. Каждый объект VolatileLong имеет одно поле volatile.

volatile доступа означает, что нити должны и аннулирует кэш «линию», которая держит их volatile long value и они должны блокировать эту строку кэша, чтобы обновить его. Как указывается в статье, строка кэша обычно составляет ~ 64 байта или около того.

В статье говорится, что, добавляя дополнение к объекту VolatileLong, он перемещает объект, который каждый из потоков блокирует в разные строки.Поэтому, несмотря на то, что разные потоки все еще пересекают барьеры памяти, поскольку они назначают свои volatile long value, они находятся в другой строке кэша, поэтому не будут вызывать чрезмерную пропускную способность кеша L2.

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

+0

+1 Любил ваше объяснение! Но у меня нет чертовой идеи о линиях кеша (на аппаратном уровне) и L2..L (N) кеша. Можете ли вы, пожалуйста, помочь мне с некоторой ссылкой, чтобы понять эти термины на уровне аппаратного/программного обеспечения? –

+1

Я просто выполнил поиск: cpu cache line. Страница wikipedia выглядит как хорошее место для запуска @VishalK: https://en.wikipedia.org/wiki/CPU_cache – Gray

Смежные вопросы