거짓 공유 프로필

거짓 공유는 무엇인가

CPU 캐시

우리 모두가 CPU 처리 속도와 하드 드라이브, 메모리 액세스 속도 차이가 너무 큰 알고, 당신은 그렇지 않으면이 크게 영향을받을 수 전체 CPU 처리량으로 이어질 것이라고 CPU 캐시를 통해 실행해야합니다.

다음과 같이 가격에 상관없이 단일 레이어 캐시, 공격 속도, 이제 CPU 수준의 세 가지 캐시 구조가 많이있어왔다, 요구 사항을 충족 할 수있는 속도를 찾을 수 없습니다, 액세스 속도는 다음과 같습니다

CPU 캐시 지연 유닛의 CPU 클럭 사이클은, 그 CPU는 명령 실행 시간으로 이해 될 수있다

L1은 L2, L3의 일부이고, L2는, 캐시의 용량이 점차 증가 L1 L3에 순차적으로 시간이 걸리는 CPU의 증가 순서를 찾을 수는 L1, L2, L3, 메인 메모리를 찾는 부분 집합이다. CPU 코어에 대응하는 L1, 단핵 배타적 다른 핵 문제없이 수정. L2는 독점 일반적으로 단핵입니다. 동일한 데이터를 조작 할 수있다 일반적 L3을 공유하는 다중 코어는 다음의 문제가있을 수있다.

캐시 라인

현대의 CPU들은 일반적으로 데이터를 연속 된 블록 단위로, 즉 캐시 라인 (캐시 라인) 판독이다. 저장된 데이터에 따라서, 연속적인 액세스는, 메모리 어레이는 일반적으로 연속적으로 할당되기 때문에, 통로, 일반적으로 빠른 어레이 구조의 사슬 구조보다, 랜덤 액세스보다 일반적으로 더 빠르다.

PS. JVM 표준은 제공하지 않는다 "배열이 연속 된 공간에 할당해야한다"일부 JVM 구현은 큰 배열이 연속 공간에없는 할당합니다.

캐시 라인 크기에도 오직 1 바이트 데이터 조작되면, CPU는,이 데이터는 위치 데이터를 64 바이트의 연속 최소 읽을 즉, 통상적으로 64 바이트이다.

캐시 무효화

캐시의 효율을 확보하기 위해서는 단순히 MESI 프로토콜, 다른 핵 주류 CPU에 따라 수정 한 후 캐시 라인 실패로 다시 판독 캐시를 필요로하는 곳에 핵이 사용되는 경우 캐시 라인 데이터를 알.

거짓 공유

다른 변수 데이터를 캐시 라인으로 동작 코어 내의 여러 스레드 후 빈번한 캐시 무효가 될 경우, 두 개의 쓰레드 사이의 관계도 코드 레벨 데이터에 작용하지 참조.

이 불합리한 경쟁 의사 과학적인 이름의 자원 공유 (거짓 공유), 심각 동시 기계의 효율성에 영향을 미칠 것입니다.

의사 점유율 예

// 多个线程,每个线程操作一个VolatileLong数组中的元素
// VolatileLong是否进行填充会影响最终结果
// 为填充时会产生伪共享问题,运行更慢,填充后不会
public class FalseShareTest implements Runnable {
    public static int NUM_THREADS = 4;
    public final static long ITERATIONS = 50L * 1000L * 1000L;
    private final int arrayIndex;
    private static VolatileLong[] longs;
    public static long SUM_TIME = 0l;
    public FalseShareTest(final int arrayIndex) {
        this.arrayIndex = arrayIndex;
    }
    public static void main(final String[] args) throws Exception {
        Thread.sleep(10000);
		// 多个线程操作多个VolatileLong
        for(int j=0; j<10; j++){
		// 初始化
            System.out.println(j);
            if (args.length == 1) {
                NUM_THREADS = Integer.parseInt(args[0]);
            }
            longs = new VolatileLong[NUM_THREADS];
            for (int i = 0; i < longs.length; i++) {
                longs[i] = new VolatileLong();
            }
            final long start = System.nanoTime();
			// 构造并启动线程
            runTest();
            final long end = System.nanoTime();
            SUM_TIME += end - start;
        }
        System.out.println("平均耗时:"+SUM_TIME/10);
    }
    private static void runTest() throws InterruptedException {
		// 创建每个线程, 每个线程操作一个VolatileLong
        Thread[] threads = new Thread[NUM_THREADS];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(new FalseShareTest(i));
        }
        for (Thread t : threads) {
            t.start();
        }
        for (Thread t : threads) {
            t.join();
        }
    }
    public void run() {
        long i = ITERATIONS + 1;
        while (0 != --i) {
            longs[arrayIndex].value = i;
        }
    }
    public final static class VolatileLong {
        public volatile long value = 0L;
        public long p1, p2, p3, p4, p5, p6;     // 注释此行,结果区别很大
    }
}
复制代码

여섯 개 긴 변수 작성을 사용할지 여부를 VolatileLong, 결과는 많이 다릅니다. 채우기 위해 사용, 빠르고, 거짓 공유를 방지 할 수 있습니다.

거짓 공유를 방지하는 방법

다음의 Java8 버전

Java8의 다음 버전은 같은 피하는 방법을 채우기 위해 사용될 수있는 바이두가의 눈송이 실현 PaddedAtomicLong 사용 :

/**
 * Represents a padded {@link AtomicLong} to prevent the FalseSharing problem<p>
 * 
 * The CPU cache line commonly be 64 bytes, here is a sample of cache line after padding:<br>
 * 64 bytes = 8 bytes (object reference) + 6 * 8 bytes (padded long) + 8 bytes (a long value)
 * 
 * @author yutianbao
 */
public class PaddedAtomicLong extends AtomicLong {
    private static final long serialVersionUID = -3415778863941386253L;

    /** Padded 6 long (48 bytes) */
    public volatile long p1, p2, p3, p4, p5, p6 = 7L;

    /**
     * Constructors from {@link AtomicLong}
     */
    public PaddedAtomicLong() {
        super();
    }

    public PaddedAtomicLong(long initialValue) {
        super(initialValue);
    }

    /**
     * To prevent GC optimizations for cleaning unused padded references
     */
    public long sumPaddingToPreventOptimization() {
        return p1 + p2 + p3 + p4 + p5 + p6;
    }

}
复制代码

여섯 길이 변수 (48)는 충전 바이트 긴 타입 값, 64 바이트의 총 8 바이트를 사용하여 오브젝트 참조. 회피 또는 GC 컴파일러 최적화하는 데 사용 SumPaddingToPreventOptimization 변수는 사용되지 않습니다.

Java8 이상 버전

시작 Java8 기본 지원이 거짓 공유를 방지하기에서, 당신은 사용할 수있는 @Contended주석을 :

public class Point { 
    int x;
    @Contended
    int y; 
} 
复制代码

참조 @Contended사용하는 메모를.

@Contended 노트는주의해서 사용해야 대상 인스턴스의 크기를 증가시킬 것이다. 기본적으로 JDK 클래스의 내부뿐만 아니라, JVM은 주석을 무시합니다. ,이를 지원하는 코드를 적용하려면 -XX을 설정 : 기본값, -RestrictContended = 거짓 사실 (내부에서만 JDK 클래스를 의미)에. 물론,도 -XX있다 : EnableContented 구성 매개 변수, 주석 기능의 개폐를 제어하는 ​​기본은 true가 false로, 그리고 크기 ConcurrentHashMap의 스레드 클래스를 줄일 수 있습니다. 페이지 "자바 성능 확실한 가이드"(210)를 참여합니다.

참고 자료

거짓 공유 (거짓 공유), 동시 프로그래밍 침묵 성능 킬러 - 공원 블로그

거짓 공유 (거짓 공유)와 캐시 라인 (캐시 라인) 뒤죽박죽 - 제인 책

에서 이동이 문서 내 블로그 를 방문에 오신 것을 환영합니다!

추천

출처juejin.im/post/5d3804ac51882562146f0870