졸려서 귀찮지만
그래도 생각난 김에
 
미디엄에서 글을 보다가
아래와 같은 코드를 봤다.
 

public boolean isOdd(int num) {
	return (num & 1) != 0;
}

 
홀수를 판정할 때 나머지 연산자보다 위와 같이 비트 연산자를 사용하면 더 빠르다는 내용이었다.
 
왜 더 빠르고
얼마나 더 빠를까?
 
복습할겸 정리해봤다.
 

왜 더 빠른가?

알다시피 1을 이진법으로 나타내면 00000001 이다.
9를 이진법으로 나타내면 00001001 이다. 
 
* 왜 앞에 0이 여러개 붙는지 모르는 사람들을 위해
컴퓨터에서 일반적으로 제일 작은 단위는 1비트이며, 1비트는 2진수 한 자리값, 즉 0 혹은 1의 값을 갖는데 이걸 8개 붙여서 8비트가 되면 우리가 아는 1Byte가 된다. 따라서 위는 숫자 1과 9를 8비트로 저장한 형태이다.
 
&(AND) 비트 연산자는 각 비트를 비교하여 둘 다 1인 경우에만 결과 비트가 1이 되고, 그 외에는 0이 된다.
 
그럼 9와 1을 &연산으로 비교해보면
1      : 00000001
9      : 00001001
결과 : 00000001
가 나온다. 
 
홀수는 일반적으로 맨 오른쪽 비트가 1의 값을 갖고 짝수는 0을 갖기 때문에 
& 연산을 거쳐서 결과로 나온 값이 1이면 홀수, 아니면 짝수라고 볼 수 있다.
 
(왜 홀수의 맨 오른쪽 비트는 1이고 짝수는 0인지 이해가 안되면 직접 숫자를 이진법으로 1부터 쭉 나열해보면 규칙성을 알 수 있다.)
 
이러한 연산 과정은 나머지 연산보다 간단하며, 비트 연산자는 컴퓨터의 비트 단위 논리 연산 기능을 사용하여 매우 효율적으로 구현되어 있고 이러한 구현은 하드웨어 수준에서 최적화되어 있기 때문에 빠른 실행 속도를 제공한다.
 

얼마나 빠른가?

얼마나 빠른지 어떻게 알 수 있을까.
찾아보니 자바에서는 JMH(Java Microbenchmark Harness) 라는 것을 사용해 성능을 측정할 수 있었다.

 

음... 사실 이 글에서 JMH를 이용해 나머지 연산자와 비트 연산자 방식의 성능 차이를 직접 보려고 했는데

막상 해보니까 그렇게 유의미한 차이가 나지 않았다.

 

어쩌면 내가 성능 테스트 설정을 잘못한 걸수도 있고, 또 검색해보니 하드웨어나 컴파일러마다 차이가 있을 수 있다고 해서 이건 조금도 자세히 알아보고 나서 다른 포스팅으로 올릴 예정이다.

'JAVA' 카테고리의 다른 글

Thread-Safe한 Singleton 패턴  (1) 2022.08.25

 

싱글톤은 인스턴스를 1개만 생성하기 위해 사용하는 디자인 패턴이다.

 

소켓이랑 스레드 공부하다가 싱글톤은 스레드 세이프하지 않을거 같다는 생각이 들어서 찾아보다가 나중에도 까먹으면 다시 보려고 정리. (사실 더 여러 개 있는데 몇 개는 생략..)

 

1. 기본적인 싱글톤

public class Singleton {
    private static Singleton instance;
    public static Singleton getInstance() {
        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

Thread-safe 하지 않다.

멀티 스레드 환경에서 if(instance == null) 을 여러 개의 스레드가 동시에 실행한다면 인스턴스가 여러 개 생성될 수 있다.

 

2. Synchronized 메소드 적용

public class Singleton {
    private static Singleton instance;

    public static synchronized Singleton getInstance() {
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

Thread-safe는 보장된다.

하지만 성능 문제가 있다. 싱글톤을 최초로 생성하는 경우에만 Lock을 걸면 되는데, 이 경우는 싱글톤 인스턴스를 가져올때 마다 Lock을 걸기 때문이다.

 

3. 클래스 로드와 동시에 초기화

public class Singleton {
    private static Singleton instance = new Singleton();

    private Singleton(){

    }

    public static Singleton getInstance() {
        return instance;
    }

}

클래스가 로드될 때 한 번만 초기화가 되기 때문에 Thread-safe는 보장된다.

하지만 해당 싱글톤이 한 번도 사용되지 않아도 인스턴스가 생성되는 것이기 때문에 성능상 좋지는 않다.

 

4. LazyHolder 기법

public class Singleton {

    private Singleton() {
    }

    public static Singleton getInstance() {
        return LazyHolder.instance;
    }

    private static class LazyHolder {
        private static final Singleton instance = new Singleton();
    }
}

자바 진영에서 가장 많이 사용되는 기법이라고 한다. 

sychronized 나 volatile 같은 키워드도 필요 없고 JAVA 버전도 상관없이 Thread-safe 를 보장한다.

내가 이해한 동작 방식은 아래와 같다..

- Singleton 클래스에는 LazyHolder 클래스의 변수가 없기 때문에 클래스 로딩 시 LazyHolder 클래스를 초기화하지 않음.

- getInstance() 호출 시 LazyHolder 클래스가 초기화되는데, 클래스가 로딩 되고 초기화되는 시점은 Thread-safe가 보장되므로 LazyHolder 안에 선언된 static instance 변수를 클래스 초기화 시점에 딱 한번만 생성되게 된다. 더불어 final 변수이므로 다시 instace가 할당되는 것 또한 막을 수 있다.

 

여기서 잠깐,

JVM은 static을 맨처음에 다 메모리에 올리는걸로 알고 있는데 LazyHolder가 왜 getInstance 호출 시점에 초기화 된다는건지 이해가 안된다면? 아래 블로그를 참고하자.

 

https://kdhyo98.tistory.com/70

 

[Java] static inner class 는 언제 로드가 될까? 로드와 초기화?

😣서론 최근 싱글톤을 직접 구현하여 사용하게 되었고, 스레드 세이프를 하기 위해서 static inner class를 사용하게 되었다. public class LottoTicketBooth { private LottoTicketBooth() { } private static cl..

kdhyo98.tistory.com

요약하자면, static Inner 클래스는 처음에 로드되어 메모리에 올라가는 것은 맞지만 Outer 클래스에서 호출하기 전까지는 클래스가 초기화 되지는 않는다는 것.

 

 

references

https://injae-kim.github.io/dev/2020/08/06/singleton-pattern-usage.html

 

'JAVA' 카테고리의 다른 글

비트연산자로 홀수판정이 더 빠르다 왜  (1) 2023.05.17

+ Recent posts