우리는 프로그래밍을 하다보면, 한번쯤은 반복문을 사용하게 되는 경우가 있다.
이 때 반복문에서 사용할 변수를 선언하게 되는데, 이 경우 구체적으로 어디에 변수를 선언하면 좋을지 고민하게 된다.
// 1. 반복문 안에 선언하는 경우
public void foo() {
while () {
int i = 10;
}
}
// 2. 반복문 밖에 선언하는 경우
public void foo() {
int i = 10;
while () {
}
}
위의 두 경우를 가지고, 자바에서 각 상황에서 어떤 것이 구조에 있어서 장점을 가지고, 성능에 어떤 영향을 주는지 살펴보고자 한다.
1️⃣ 구조적 장점
우선 구조적 장점으로는 반복문 안에 선언하는 것이 가장 바람직하다.
이유로는 지역변수는 자동 변수이기 때문이다.
지역변수는 stack이라는 메모리 공간에 저장되기 때문에, 스코프 종료와 함께 자동으로 회수된다.
이 때문에 지역변수는 자동변수라는 이름으로 함께 불리기도 한다.
지역변수의 스코프를 제한하면, 메모리를 효율적으로 사용할 수 있으며 다른 스코프에서 참조하여 값이 변하는 걱정도 덜 수 있다.
예를 들어 반복문 밖에 선언한 i 변수는 반복문이 한 사이클을 돌더라도 해당 값이 남아있기 때문에, 이를 참조하는 다른 코드에서 문제가 발생할 여지가 있다.
반대로 반복문 안에 선언한 i 변수는 반복문이 돌 때마다 해당 값을 새로 선언하고, 초기화하기 때문에 잘못된 값이 들어갈 염려가 없다.
2️⃣ 성능
이 부분은 감히 어떤 방식이 더 좋은 지 말하기 어려운데, 그래도 상황별로 나눠서 생각해볼 수는 있겠다.
Stack에 저장되는 변수
stack에 저장될 지역변수라면, 크게 성능 차이가 날만한 부분은 딱히 없을 것으로 생각된다.
굳이 꼽자면, 지역변수가 반복문마다 선언되고 초기화 되는 부분이 반복된다는 부분 일 것이다.
이건 컴파일러 단에서 최적화를 해줘서 성능에 그렇게 큰 영향을 주지 못할 것 같다는 생각이 든다.
(구체적으로 바이트 코드를 까보고, 어셈블리를 봐야 알겠지만은…)
그렇기에 stack에 저장되는 형식의 변수라면 두 방식 역시 성능적인 차이가 크게 의미있을 것 같진 않다.
Heap에 저장되는 변수
다만 heap에 저장될 변수라면 이야기는 다르다.
이 때 위의 코드를 다음과 같이 수정하고 정수형 캐쉬는 없다고 가정한다.
// 1. 반복문 안에 선언하는 경우
public void foo() {
while () {
Integer i = 10;
}
}
// 2. 반복문 밖에 선언하는 경우
public void foo() {
Integer i = 10;
while () {
Integer i2 = i;
}
}
우선 첫 번째 경우는 매번 반복문에서 새로운 정수형 객체를 생성하는 방식이다 (new Integer()
)
두 번째 경우는 이미 생성된 정수형 객체를 가르키는 새로운 정수형 변수를 선언하고, 초기화한 것이다.
따라서 엄밀히 말하면 첫 번째 경우와는 다르게 new Integer()
를 사용하지 않았기에, heap에 새로운 메모리가 생성되지 않는다.
이 말은 GC의 대상이 되지 않기에, 크게 봤을 때 GC로 인한 성능 저하가 첫 번째에 비해 적다는 뜻이다.
자바에서는 문자열을 생성하는 과정에서도 new 키워드로 생성하냐, 아니면 리터럴로 생성하냐에 따라서 GC 퍼포먼스가 달라질 정도로 주의를 요한다.
과연 적지 않은 수의 반복문으로 객체를 생성하면 GC 성능에 영향이 적다고 말 할 수 있을까?
결론
반복문 내부에 생성되는 변수의 타입과 변수가 레퍼런스 하는 메모리 공간에 따라 권장하는 방법이 달라질 수 있다.
일반적인 원시형 변수를 사용하는 경우에는 코드 가독성 및 코드 구조를 위해 반복문 내부에 선언하는 것을 권장하고,
객체형 변수를 사용하는 경우에는 GC 성능에 영향을 줄 수 있기 때문에, 반복문 밖에서 선언하는 것을 권장할 수 있겠다.
참고자료
PS. 자동 변수가 선언되는 것이 성능에 주는 영향이 적은 지 알려주는 글이 있어서 가져온다.
우선 C언어 같은 경우에는, 일반적인 타입은 이미 해당 크기가 정해져있기 때문에 컴파일 때 얼마만큼 스택에 저장해야할 지 알 수 있다.
따라서 미리 해당 크기만큼 stack에 저장하는 것으로 컴파일러가 최적화 한다면, 그만큼 선언에 필요한 cpu 사이클이 줄어드는 셈이다.