2016-05-11 2 views
1

Я написал простую программу для проверки пропускной способности блокировки CLH. У меня есть код, как описано в книге «Искусство многоядерного программирования». Затем я запустил счетчик на изменение количества потоков в течение 10 секунд и определил счетчик/10.0 в качестве пропускной способности.Масштабируемость и ограничения SpinLock

Вопрос заключается в том, находятся ли результаты, которые я получил, в пределах логического диапазона и какова может быть причина, по которой они такие, как они. Я спрашиваю, потому что падение пропускной способности для блокировки CLH происходит очень быстро. Это результаты для блокировки cLH, где слева указано количество потоков, а справа - пропускная способность (размер счетчика, приходящийся на каждый поток, увеличивающий его один раз в критическом разделе, защищенном блокировкой CLH, деленный на 10).

CLH 1 2.89563825E7 2 1.33501436E7 4 5675832.3 8 15868.9 16 11114.4 32 68.4

Как вы видите безумие оставит машину и заставляет меня думать, что я, возможно, испортил что-то еще до.

Это мой код блокировки CLH (так же, как и в упомянутой выше книге):

static class CLHLock implements Lock { 
    AtomicReference<QNode> tail; 
    ThreadLocal<QNode> myNode, myPred; 

    public CLHLock() { 
     tail = new AtomicReference<QNode>(new QNode()); 

     this.myNode = new ThreadLocal<QNode>() { 
      protected QNode initialValue() { 
       return new QNode(); 
      } 
     }; 

     this.myPred = new ThreadLocal<QNode>() { 
      protected QNode initialValue() { 
       return null; 
      } 
     }; 
    } 

    public void lock() { 
     QNode qnode = this.myNode.get(); 
     qnode.locked.set(true);   

     QNode pred = this.tail.getAndSet(qnode); 
     myPred.set(pred);   
     while (pred.locked.get()) {}  
    } 

    public void unlock() { 
     QNode qnode = this.myNode.get(); 
     qnode.locked.set(false);  
     this.myNode.set(this.myPred.get()); 
    } 

    static class QNode { 
     public AtomicBoolean locked = new AtomicBoolean(false); 
    } 
} 

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

+1

В моем опыте большая деградация вызвана cpu hogging во вращении. 'while (pred.locked.get()) {}', вероятно, может быть более общительным с 'while (pred.locked.get()) {Thread.yield();}'. Не имеет значения, так что только комментирование. – OldCurmudgeon

ответ

1

Об CLH реализации блокировки

Реализация выглядит довольно стандартно, за исключением шумного спина. Вы, вероятно, лучше сдавать или парковать (хотя для этого потребуется немного больше кода).

Об бенчмаркинга результаты

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

Возможно, вы наблюдаете множество побочных эффектов, не связанных напрямую с вашим кодом. Чтобы свести к минимуму эти эффекты, используйте инструмент сравнения, например JMH, иначе вы измеряете то, что не обязательно является вашим кодом.

Вот спекулятивное объяснение о результатах, которые могут быть неправильными, но совершенно правдоподобно:

  • С 1 нитью, ваш код выполняется очень быстро, потому что практически нет разногласий на замке и нет кэша обмолота. Вероятно, вы выиграли от успешного предсказания ветвления и от нокаута JIT в начале без последующей деоптимизации.
  • С потоками 2 и 4 вы получаете пропускную способность. Это не так уж плохо, потому что у вас все еще есть аппаратные потоки, но теперь вы испытываете переполнение кэша (возможно, даже ложное разделение), некоторый когерентный трафик и, возможно, неправильное предсказание филиала (из-за общей инфраструктуры вашего теста). Хотя вы не получаете увеличения пропускной способности от параллельного выполнения, вы все еще в порядке.
  • С 8 и 16 потоками вы теперь находитесь за пределами доступных аппаратных потоков на вашем компьютере. Вы начинаете испытывать эффекты планирования ОС, значительно более существенное переполнение кэша, а также значительную конкуренцию в вашем коде.
  • С 32 потоками вы переходите через предел некоторых быстрых механизмов аппаратного кэширования (кеш L1, TLB) и переходите к следующему самому быстрому механизму. Нет необходимости переходить к пределу размера кеша, чтобы испытать это, вы также можете воспользоваться лимитом ассоциативности.
Смежные вопросы