当前位置: 首页 > 知识库问答 >
问题:

为什么Java不安全的CAS(getAndAddInt)比手工编写的代码快

孙翰墨
2023-03-14

我手动编写了cas代码(compare_and_set的while循环),而不是直接调用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个答案

田冥夜
2023-03-14

执行的代码不一定与您在源代码中看到的相匹配。在运行JDK代码时Java JIT作弊吗?

众所周知的方法可能会被特殊的实现所取代,不管原始声明是native还是纯Java实现。请参见JVM源代码中的“intrinsify”是什么意思?

当我们查看JVM源文件vmsymbols.hpp的第1031行时,我们将看到sun.misc.unsafe.getandAddInt是JVM已知的。

您可以使用
-xx:compileCommand=print,castest.castest-xx:compileCommand=print,castest.manualcastest
检查得到的本机代码(通常这是评估基准结果的好方法)。

在X64上,您将看到manualcastest将按照您编写的那样进行编译,一个围绕
lock cmpxchg dword ptr[rsi],ebx指令的循环,而castest包含一个不需要循环的
lock xadd dword ptr[rdx+0ch],r8d指令(细节可能会有所不同)。

 类似资料:
  • 为什么,给定: 这是否不安全: 但这是安全的: 我所说的安全是指保证不受溢出的影响(我正在编写一个整数的)。

  • 问题内容: 请用代码示例说明为什么SimpleDateFormat不是线程安全的。这节课有什么问题? 是SimpleDateFormat的格式功能问题吗?请提供一个在课堂上演示此错误的代码。 FastDateFormat是线程安全的。为什么?SimpleDateFormat和FastDateFormat有什么区别? 请用代码说明这个问题? 问题答案: 将中间结果存储在实例字段中。因此,如果两个线程

  • 我试图理解Scala代码如何在Java的IDE中与Java一起工作。我在使用Spark Java时遇到了这个疑问,在Spark Java中,我看到Scala包也在代码中,并且使用了相应的类和方法。 我的理解是,Scala代码需要Scala的编译器转换成Java.class文件,然后从它们开始JDK在JVM中完成它的部分,转换成二进制文件并执行操作。如果我说错了,请指正。 之后,在eclipse中的

  • 问题内容: 在方法或类范围内,下面的行进行编译(带有警告): 在类范围中, 变量获取其默认值 ,以下给出“未定义引用”错误: 它不是第一个应该以相同的“未定义参考”错误结束吗?还是第二行应该编译?还是我缺少什么? 问题答案: tl; dr 对于 字段 ,是非法的,因为它是对的非法前向引用。您实际上可以通过编写来解决此问题,该文件可以毫无抱怨地进行编译。 对于 局部变量 ,是非法的,因为未在使用前进

  • 我最近用Java写了一个计算密集型算法,然后把它翻译成C++。令我吃惊的是,C++的执行速度要慢得多。我现在已经编写了一个更短的Java测试程序,以及一个相应的C++程序-参见下面。我的原始代码具有大量的数组访问功能,测试代码也是如此。C++的执行时间要长5.5倍(请参阅每个程序末尾的注释)。 以下1st21条评论后的结论... null null Java代码: C++代码:

  • 问题内容: 在循环中修改要迭代的序列是不安全的(这仅适用于可变序列类型,例如列表)。如果需要修改要遍历的列表(例如,复制选定的项目),则必须遍历一个副本。切片符号使这一点特别方便: 为什么做起来不安全? 问题答案: 无需太过技术: 如果您要遍历Python中的可变序列,并且在遍历序列时对其进行更改,则并非总是很清楚会发生什么。如果您在迭代序列时在序列中插入元素,那么现在可以合理地认为序列中的“下一