使用自己的int容量比使用数组的.length字段更快?

Ada*_*zyk 11 java concurrency performance caching

马丁·汤普森"95%的表现是关于清洁的代表性模型"谈话中,在17到21分钟之间,这样的代码被呈现:

public class Queue
{
    private final Object[] buffer;
    private final int capacity;

    // Rest of the code

}
Run Code Online (Sandbox Code Playgroud)

在20:16他说:

你可以获得更好的性能,所以留下像这样的东西capacity 是正确的.

我试图提出一个代码示例,其capacity速度会快得多buffer.length,但我失败了.

马丁说两个场景中出现问题:

  1. 在一个并发的世界里.但是,length字段也是final,JLS 10.7.所以,我不知道这可能是一个什么问题.
  2. 当缓存未命中时.试着打电话capacityVS buffer.length一百万次(具有元素一百万队列),但没有显著差异.我使用JMH进行基准测试.

能否请您提供一个代码示例,该示例演示了一个capacity优于buffer.length性能的案例?

更常见的情况(经常在实际代码中发现)越好.

请注意,我完全取消了美学,清洁代码,代码重新分解等方面的内容.我只询问性能.

apa*_*gin 9

正常访问数组时,JVM length仍然使用它来执行边界检查.但是当你通过sun.misc.Unsafe(像马丁那样)访问数组时,你不必支付这种隐含的惩罚.

Array的length字段通常位于与其第一个元素相同的缓存行中,因此当多个线程同时写入第一个索引时,您将具有错误共享.使用单独的字段来缓冲容量将打破这种错误共享.

这是一个基准测试,显示了capacity字段如何使数组访问速度更快:

package bench;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Threads;
import sun.misc.Unsafe;

import java.lang.reflect.Field;
import java.util.concurrent.atomic.AtomicReferenceArray;

@State(Scope.Benchmark)
@Threads(4)
public class Queue {
    private static final Unsafe unsafe = getUnsafe();
    private static final long base = unsafe.arrayBaseOffset(Object[].class);
    private static final int scale = unsafe.arrayIndexScale(Object[].class);

    private AtomicReferenceArray<Object> atomic;
    private Object[] buffer;
    private int capacity;

    @Param({"0", "25"})
    private volatile int index;

    @Setup
    public void setup() {
        capacity = 32;
        buffer = new Object[capacity];
        atomic = new AtomicReferenceArray<>(capacity);
    }

    @Benchmark
    public void atomicArray() {
        atomic.set(index, "payload");
    }

    @Benchmark
    public void unsafeArrayLength() {
        int index = this.index;
        if (index < 0 || index >= buffer.length) {
            throw new ArrayIndexOutOfBoundsException();
        }
        unsafe.putObjectVolatile(buffer, base + index * scale, "payload");
    }

    @Benchmark
    public void unsafeCapacityField() {
        int index = this.index;
        if (index < 0 || index >= capacity) {
            throw new ArrayIndexOutOfBoundsException();
        }
        unsafe.putObjectVolatile(buffer, base + index * scale, "payload");
    }

    private static Unsafe getUnsafe() {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            return (Unsafe) f.get(null);
        } catch (IllegalAccessException | NoSuchFieldException e) {
            throw new AssertionError("Should not happen");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

结果:

Benchmark                  (index)   Mode  Cnt      Score      Error   Units
Queue.atomicArray                0  thrpt    5  41804,825 ±  928,882  ops/ms
Queue.atomicArray               25  thrpt    5  84713,201 ± 1067,911  ops/ms
Queue.unsafeArrayLength          0  thrpt    5  48656,296 ±  676,166  ops/ms
Queue.unsafeArrayLength         25  thrpt    5  88812,863 ± 1089,380  ops/ms
Queue.unsafeCapacityField        0  thrpt    5  88904,433 ±  360,936  ops/ms
Queue.unsafeCapacityField       25  thrpt    5  88633,490 ± 1426,329  ops/ms
Run Code Online (Sandbox Code Playgroud)

  • 如果你添加一个普通的数组访问,而不是使用`Unsafe`作为第三种变体,那将是很好的比较. (3认同)