• Atomic
    : 원자적 연산은 '원자적인'방법 즉, 중간에 어떠한 방해도 받지 않고 어셈블리어 명령어 하나로 실행할수 있다. 
    : 메모리에 접근하지 않거나 한번 접근하는 어셈블리어 명령은 원자적이다.
    : inc나 dec같이 메모리에서 데이터를 읽고, 이를 갱신하고, 갱신한 값을 메모리에 다시 쓰는 읽기/수정/쓰기 어셈블리어 명령은 읽기와 쓰기 사이에 다른 프로세서가 메모리버스를 점유하지 않는 한 원자적이다. 유니프로세서 시스템에서는 같은 프로세서에서 메모리 접근이 일어나므로 메모리버스를 훔치는일은 발생하지 않는다.

    ∴ 윈도우 프로그램에서 동기화
     : 커널 모드 동기화 vs. 유저모드 동기화
     : 유저모드는 Atomic Operation 과 CRITICAL_SECTION 으로 구분
     : 커널 모드는 세마포어와 뮤텍스로 구분된다. 뮤텍스(두 프로세스간)는 세마포어 기법(멀티 프로세스간)의 일부분으로 포함되는 개념이다.

    : java 1.5 부터 java.util.concurrent.atomic에 Atomic 관련 타입들 존재
    : 속도는 perf3 > perf1 > perf2
    : Atomic이 무조건 좋다는 법은 없다. -> 적절하게 사용하면 된다.

    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    // 1 억 만들기
    
    import org.junit.Test;
    
    import java.util.concurrent.atomic.AtomicLong;
    
    // value += 2; -> Atomic 하지 않다.
    // 1. value 를 메모리부터 로드
    // 2. 2를 더하고
    // 3. 메모리에 저장
    
    public class Example5 {
        private static final int THREAD_COUNT = 2;
        // 1. Atomic 사용
        private static AtomicLong value1 = new AtomicLong(0);
        // private static volatile long value = 0;
    
        private final static Runnable TASK = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 50000000 / THREAD_COUNT; i++)
                    value1.addAndGet(2);
                    // value += 2;
            }
        };
    
        @Test 
        public void perf1() throws InterruptedException {
            Thread[] threads = new Thread[THREAD_COUNT];
            for (int i = 0; i < THREAD_COUNT; i++)
                threads[i] = new Thread(TASK);
    
            for (Thread t : threads)
                t.start();
    
            boolean alive = true;
            while (alive) {
                for (Thread t : threads) {
                    t.join(10);
                    if (alive = t.isAlive())
                        break;
                }
            }
    
            System.out.println("value : " + value1.get());
        }
        
        // 2. volatile 사용 - for문 연산 자체에 동기화
        private static volatile long value2 = 0;
        private final static Runnable TASK2 = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 50000000 / THREAD_COUNT; i++)
                    synchronized (Example5.class) {
                        value2 += 2;
                    }
            }
        };
        @Test
        public void perf2() throws InterruptedException {
            Thread[] threads = new Thread[THREAD_COUNT];
            for (int i = 0; i < THREAD_COUNT; i++)
                threads[i] = new Thread(TASK2);
    
            for (Thread t : threads)
                t.start();
    
            boolean alive = true;
            while (alive) {
                for (Thread t : threads) {
                    t.join(10);
                    if (alive = t.isAlive())
                        break;
                }
            }
    
            System.out.println("value : " + value2);
        }
    
        // 3. volatile 사용 - for문 연산 종료 후 동기화
        private static volatile long value3 = 0;
        private final static Runnable TASK3 = new Runnable() {
            @Override
            public void run() {
                long local_sum = 0;
                for (int i = 0; i < 50000000 / THREAD_COUNT; i++)
                    local_sum += 2;
    
                synchronized (Example5.class) {
                    value3 += local_sum;
                }
            }
        };
        @Test
        public void perf3() throws InterruptedException {
            Thread[] threads = new Thread[THREAD_COUNT];
            for (int i = 0; i < THREAD_COUNT; i++)
                threads[i] = new Thread(TASK3);
    
            for (Thread t : threads)
                t.start();
    
            boolean alive = true;
            while (alive) {
                for (Thread t : threads) {
                    t.join(10);
                    if (alive = t.isAlive())
                        break;
                }
            }
    
            System.out.println("value : " + value3);
        }
    }
    

  • 가짜 공유 (False Sharing)
    멀티 코어 CPU에서 발생할 수 있는 문제다. 멀티 코어 CPU에서는 데이터를 word 단위로 읽어오는 대신 메모리 I/O 효율성을 위해서 cache-line로 읽어오는데, 이때 문제가 생길 수 있다. 두개의 메모리 연산이 동일한 캐쉬라인에서 실행될 경우, CPU<->Memory 버스 사이에서 하드웨어적인 락이 걸리는데, 이때 하드웨어적인 병목현상이 발생한다.
    : 멀티코어에서는 A스레드와 B스레드에서 인접 메모리를 접근할때, 캐시에 있던 내용을 메모리에 반영하려 시도. 인접 메모리를 읽고 있는 상태이기에 병행 수행시 데이터의 유효성을 조금이라도 높이기 위해 메모리에 반영하는 과정에서 속도 저하가 발생하는 것.
    캐시 라인이라 함은 지역성에 근거해 인접한 데이터를 미리 읽어옴으로써 속도향상을 노리는 것

    지역성(locality)은 아래 추정에 근거합니다.
    1. 지금 읽힌 데이터는 이후에도 자주 사용될 가능성이 높다.
    2. 지금 읽힌 데이터와 인접한 데이터는 이어서 사용될 가능성이 높다.

    패딩(padding)를 통한 해결 -> 메모리를 손해보더라도 속도에서 이득을 보자
    : p1, p2, p3, p4, p5, p6(64byte)를 선언하여 캐쉬라인에 대한 패딩 확보 (annotation으로도 존재한다.)

    public final class X {
        public volatile int f1;
        public volatile int f2;
    }
    




    참고 : http://www.smallake.kr/?p=2271, http://elky.tistory.com/318, http://daniel.mitterdorfer.name/articles/2014/false-sharing/

    package kr.co.ioacademy; //iocademy 윤찬식 강사님 import org.junit.Test; import sun.misc.Contended; class C { public final static int NUM_THREADS = 8; public final static long ITERATIONS = 50L * 1000L * 1000L; } // 가짜 공유 문제(False Sharing) // 개념 : 두 스레드간의 공유가 발생하지 않음에도 캐시라인의 공유에 의해서 // 서로의 캐시를 무효화함으로 성능이 저하되는 문제. public class Example6 { @Test public void runTest() throws InterruptedException { Thread[] threads = new Thread[C.NUM_THREADS]; for (int i = 0; i < threads.length; i++) { threads[i] = new Task(i); } for (Thread t : threads) { t.start(); } for (Thread t : threads) { t.join(); } } } class Task extends Thread { static class Long { public volatile long value = 0L; public long p1, p2, p3, p4, p5, p6; // 64byte padding } private static Long[] longs = new Long[C.NUM_THREADS]; static { for (int i = 0; i < longs.length; i++) longs[i] = new Long(); } private int index; public Task(int index) { this.index = index; } @Override public void run() { long i = C.ITERATIONS + 1; while (0 != --i) { longs[index].value = i; } } }


'Programing > Java' 카테고리의 다른 글

Effective Java - Annotation  (0) 2016.03.08
Effective Java - Reflection  (0) 2016.03.08
Effective Java - 스레드(2)  (0) 2016.03.07
Effective Java - 스레드(1)  (0) 2016.03.07
Effective Java - 불변 객체  (0) 2016.03.07
Effective Java - 객체 비교, 복제  (0) 2016.03.04

+ Recent posts