почему Java небезопасный CAS (getAndAddInt) работает быстрее, чем код, написанный вручную - PullRequest
1 голос
/ 20 февраля 2020

Я написал код cas (в то время как l oop для compare_and_set) вручную вместо прямого вызова метода Unsafe.getAndAddInt. Но когда я использую jmh для тестирования производительности, это показывает большую потерю производительности, хотя я написал тот же код, что и просто копия исходного кода метода Unsafe. Кто может помочь мне, что делает эту большую разницу? Заранее спасибо.

Результат jmh:

Benchmark              Mode  Cnt  Score   Error  Units
CASTest.casTest        avgt       0.047          us/op
CASTest.manualCasTest  avgt       0.137          us/op  

Исходный код:

package org.sample;

import java.lang.reflect.Field;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import sun.misc.Unsafe;

/**
 * @author Isaac Gao
 * @Date 2020/2/20
 */
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Benchmark)
@Threads(2)
@Measurement(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS)
@Warmup(iterations = 2, time = 1)
@Fork(1)
public class CASTest {

  private static Unsafe getUnsafe() {
    try {
      final Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");

      unsafeField.setAccessible(true);
      return (Unsafe) unsafeField.get(null);
    } catch (NoSuchFieldException | IllegalAccessException e) {
      e.printStackTrace();
    }
    return null;
  }
  private static final Unsafe unsafe = getUnsafe();

  private static final long valueOffset;
  static {
    try {
      valueOffset = unsafe.objectFieldOffset
          (CASTest.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
  }

  private volatile  int value;

  @Benchmark
  public void manualCasTest(Blackhole bh) {
    int andAddIntManually = getAndAddIntManually(this, valueOffset, 1);
    bh.consume(andAddIntManually);
  }

  @Benchmark
  public void casTest(Blackhole  bh) {
    int andAddInt = unsafe.getAndAddInt(this, valueOffset, 1);
    bh.consume(andAddInt);
  }

  public final int getAndAddIntManually(Object o, long offset, int delta) {
    int v;
    do {
      v = unsafe.getIntVolatile(o, offset);
    } while (!unsafe.compareAndSwapInt(o, offset, v, v + delta));
    return v;
  }

  public static void main(String[] args) throws RunnerException {
    Options opt = new OptionsBuilder()
        .include(CASTest.class.getSimpleName())
        .build();
    new Runner(opt).run();
  }
}

1 Ответ

2 голосов
/ 20 февраля 2020

Выполненный код не обязательно соответствует тому, что вы видели в исходном коде. Сходная несопоставимая производительность копирования и вставки кода обсуждалась в Читает ли Java JIT при запуске кода JDK?

Хорошо известные методы могут быть заменены специальными реализациями, независимо от того, существует ли оригинальное объявление был native или имел чистую Java реализацию. См. Также Что означает «встроить» в исходный код JVM?

Когда мы посмотрим в исходный файл JVM vmSymbols.hpp, строка 1031 , мы увидим что sun.misc.Unsafe.getAndAddInt известно JVM.

Вы можете использовать
-XX:CompileCommand=print,CASTest.casTest -XX:CompileCommand=print,CASTest.manualCasTest
для проверки результирующего собственного кода (который обычно является хорошей идеей для оценки результатов тестирования производительности).

На X64 вы увидите, что manualCasTest будет скомпилирован так, как вы написали, al oop сосредоточен вокруг инструкции
lock cmpxchg dword ptr [rsi],ebx, тогда как casTest содержит один l oop бесплатно
lock xadd dword ptr [rdx+0ch],r8d инструкция (детали могут отличаться).

...