gpt4 book ai didi

java - Java比较和交换语义和性能

转载 作者:行者123 更新时间:2023-12-02 08:31:24 25 4
gpt4 key购买 nike

Java中比较和交换的语义是什么?就是说,AtomicInteger的比较和交换方法只是确保不同线程之间对原子整数实例的特定内存位置的有序访问,还是保证对内存中所有位置的有序访问,即,就像是 volatile (内存栅栏)。

docs:

  • weakCompareAndSet原子地读取并有条件地写入变量,但不会在排序之前创建任何事件,因此无法保证除weakCompareAndSet的目标以外,对任何变量的先前或后续读取和写入。
  • compareAndSet和所有其他读取和更新操作(例如getAndIncrement)具有读取和写入 volatile 变量的存储效果。

  • 从API文档中可以明显看出 compareAndSet就像是一个volatile变量。但是, weakCompareAndSet应该只是更改其特定的内存位置。因此,如果该内存位置是单个处理器的高速缓存专用的,则 weakCompareAndSet应该比常规 compareAndSet快得多。

    我之所以这样问是因为我已经通过以下方式对以下方法进行了基准测试:运行不同线程的 threadnum,将 threadnum从1更改为8,并使用 totalwork=1e9(该代码以静态编译的JVM语言Scala编写,但其含义和字节码转换在这种情况下与Java是同构的-这个简短的摘要应该很清楚):

    val atomic_cnt = new AtomicInteger(0)
    val atomic_tlocal_cnt = new java.lang.ThreadLocal[AtomicInteger] {
    override def initialValue = new AtomicInteger(0)
    }

    def loop_atomic_tlocal_cas = {
    var i = 0
    val until = totalwork / threadnum
    val acnt = atomic_tlocal_cnt.get
    while (i < until) {
    i += 1
    acnt.compareAndSet(i - 1, i)
    }
    acnt.get + i
    }

    def loop_atomic_weakcas = {
    var i = 0
    val until = totalwork / threadnum
    val acnt = atomic_cnt
    while (i < until) {
    i += 1
    acnt.weakCompareAndSet(i - 1, i)
    }
    acnt.get + i
    }

    def loop_atomic_tlocal_weakcas = {
    var i = 0
    val until = totalwork / threadnum
    val acnt = atomic_tlocal_cnt.get
    while (i < until) {
    i += 1
    acnt.weakCompareAndSet(i - 1, i)
    }
    acnt.get + i
    }

    在具有4个双2.8 GHz内核和2.67 GHz 4核i7处理器的AMD上。 JVM是Sun Server Hotspot JVM 1.6。结果表明没有性能差异。

    规格:AMD 8220 4x双核@ 2.8 GHz

    测试名称:loop_atomic_tlocal_cas
  • 线程数:1

  • 运行时间:(显示最后3个)
    7504.562 7502.817 7504.626(平均= 7415.637分钟= 7147.628最大= 7504.886)
  • 线程数:2

  • 运行时间:(显示最后3个)
    3751.553 3752.589 3751.519(平均= 3713.5513分钟= 3574.708最大= 3752.949)
  • 线程数:4

  • 运行时间:(显示最后3个)
    1890.055 1889.813 1890.047(平均= 2065.7207分钟= 1804.652最大= 3755.852)
  • 线程数:8

  • 运行时间:(显示最后3个)
    960.12 989.453 970.842(平均= 1058.8776分钟= 940.492最大= 1893.127)

    测试名称:loop_atomic_weakcas
  • 线程数:1

  • 运行时间:(显示最后3个)
    7325.425 7057.03 7325.407(平均= 7231.8682分钟= 7057.03最大= 7325.45)
  • 线程数:2

  • 运行时间:(显示最后3个)
    3663.21 3665.838 3533.406(平均= 3607.2149分钟= 3529.177最大= 3665.838)
  • 线程数:4

  • 运行时间:(显示最后3个)
    3664.163 1831.979 1835.07(平均= 2014.2086分钟= 1797.997最大值= 3664.163)
  • 线程数:8

  • 运行时间:(显示最后3个)
    940.504 928.467 921.376(平均= 943.665分钟= 919.985最大= 997.681)

    测试名称:loop_atomic_tlocal_weakcas
  • 线程数:1

  • 运行时间:(显示最后3个)
    7502.876 7502.857 7502.933(平均= 7414.8132分钟= 7145.869最大值= 7502.933)
  • 线程数:2

  • 运行时间:(显示最后3个)
    3752.623 3751.53 3752.434(平均= 3710.1782分钟= 3574.398最大= 3752.623)
  • 线程数:4

  • 运行时间:(显示最后3个)
    1876.723 1881.069 1876.538(平均= 4110.4221最小值= 1804.62最大值= 12467.351)
  • 线程数:8

  • 运行时间:(显示最后3个)
    959.329 1010.53 969.767(平均= 1072.8444分钟= 959.329最大= 1880.049)

    规格:Intel i7四核@ 2.67 GHz

    测试名称:loop_atomic_tlocal_cas
  • 线程数:1

  • 运行时间:(显示最后3个)
    8138.3175 8130.0044 8130.1535(平均= 8119.2888最小值= 8049.6497最大值= 8150.1950)
  • 线程数:2

  • 运行时间:(显示最后3个)
    4067.7399 4067.5403 4068.3747(平均= 4059.6344最小值= 4026.2739最大值= 4068.5455)
  • 线程数:4

  • 运行时间:(显示最后3个)
    2033.4389 2033.2695 2033.2918(平均= 2030.5825分钟= 2017.6880最大值= 2035.0352)

    测试名称:loop_atomic_weakcas
  • 线程数:1

  • 运行时间:(显示最后3个)
    8130.5620 8129.9963 8132.3382(平均= 8114.0052分钟= 8042.0742最大= 8132.8542)
  • 线程数:2

  • 运行时间:(显示最后3个)
    4066.9559 4067.0414 4067.2080(平均= 4086.0608最小值= 4023.6822最大值= 4335.1791)
  • 线程数:4

  • 运行时间:(显示最后3个)
    2034.6084 2169.8127 2034.5625(平均= 2047.7025最小值= 2032.8131最大值= 2169.8127)

    测试名称:loop_atomic_tlocal_weakcas
  • 线程数:1

  • 运行时间:(显示最后3个)
    8132.5267 8132.0299 8132.2415(平均= 8114.9328分钟= 8043.3674最大值= 8134.0418)
  • 线程数:2

  • 运行时间:(显示最后3个)
    4066.5924 4066.5797 4066.6519(平均= 4059.1911最小值= 4025.0703最大值= 4066.8547)
  • 线程数:4

  • 运行时间:(显示最后3个)
    2033.2614 2035.5754 2036.9110(平均= 2033.2958分钟= 2023.5082最大= 2038.8750)

    虽然上面示例中的线程局部变量有可能以相同的缓存行结尾,但在我看来,常规CAS及其弱版本之间没有明显的性能差异。

    实际上,这可能意味着比较弱和交换作用就像完全成熟的内存屏障,即好像它是一个可变变量一样。

    问题:这个观察正确吗?另外,是否存在已知的体系结构或Java发行版,其弱比较和设置实际上更快?如果不是,那么首先使用弱CAS有什么好处?

    最佳答案

    当然,弱比较和交换可以充当完整的volatile变量,具体取决于JVM的实现。实际上,如果在某些体系结构上不可能以比常规CAS更高的性能来实现弱CAS,我也不会感到惊讶。在这些体系结构上,很可能实现弱CAS与完全CAS完全相同。或者可能只是因为您的JVM在使弱CAS变得特别快方面没有进行太多优化,所以当前的实现只是调用完整的CAS,因为它实现起来很快,以后的版本会对此进行完善。

    JLS只是说一个弱的CAS不会建立事前关联,所以这仅仅是没有保证导致的修改在其他线程中可见。在这种情况下,您所获得的只是保证比较和设置操作是原子的,但不保证(可能)新值的可见性。这与保证它不会被看到是不同的,因此您的测试与此一致。

    通常,尝试通过实验避免对与并发相关的行为做出任何结论。有太多变量需要考虑,如果您不遵循JLS保证的正确性,则您的程序可能会随时中断(也许是在不同的体系结构上,也许是由于略微提示而导致的更积极的优化)更改代码的布局,也许是在将来不存在的JVM的 future 版本等下)。 从不是一个假设您可以摆脱某些无法保证的东西的原因,因为实验表明“可以使用”。

    关于java - Java比较和交换语义和性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4183202/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com