회사에서 필드의 접근제한자를 private 가 아닌 public으로 사용하고, Getter/Setter 없이 직접 필드에 접근하여 연산처리하는 소스를 만났다. 아무래도 Getter/Setter 메서드를 호출할 때마다 Call Stack에 메서드가 쌓였다가 사라질 테니 필드에 직접 접근하여 연산하는 것보다 성능이 좋지 않을 것이라고 생각했다. 하지만, 은닉, 캡슐화를 위반하는 듯하여 이를 Getter/Setter로 변경하였을 때 성능이 얼마나 안 좋아질지 시간을 측정해보고자 하였다.
대략 간단하게
필드 직접 접근 10억번 반복 vs Getter/Setter 10억 번 반복을 비교해 보았다.
package practice;
public class GetterDirect {
public static void main(String[] args) {
Test t1 = new Test();
long s1 = System.currentTimeMillis();
for(int i=0; i<1_000_000_000; i++){
t1.a += t1.a;
t1.a -= t1.a;
t1.a *= t1.a;
t1.a -= t1.a;
}
System.out.println("t1.a = " + t1.a);
long e1 = System.currentTimeMillis();
System.out.println("(e1-s1) = " + (e1-s1));
Test t2 = new Test();
long s2 = System.currentTimeMillis();
for(int i=0; i<1_000_000_000; i++){
t2.setB(t2.getB()+t2.getB());
t2.setB(t2.getB()-t2.getB());
t2.setB(t2.getB()*t2.getB());
t2.setB(t2.getB()-t2.getB());
}
System.out.println("t2.getB() = " + t2.getB());
long e2 = System.currentTimeMillis();
System.out.println("(e2-s2) = " + (e2-s2));
}
static class Test{
public int a;
private int b;
public void setB(int b){
this.b = b;
}
public int getB(){
return b;
}
}
}
결과는 다음과 같았다.

Getter/Setter를 사용하는 소스가 3ms 빠르다.
어째서 게터세터를 사용하는 소스가 더 빠른 것일까.
뭔가 소스의 순서에 영향이 있을 것 같아서 순서를 바꿔보았다.
package practice;
public class GetterDirect {
public static void main(String[] args) {
Test t2 = new Test();
long s2 = System.currentTimeMillis();
for(int i=0; i<1_000_000_000; i++){
t2.setB(t2.getB()+t2.getB());
t2.setB(t2.getB()-t2.getB());
t2.setB(t2.getB()*t2.getB());
t2.setB(t2.getB()-t2.getB());
}
System.out.println("t2.getB() = " + t2.getB());
long e2 = System.currentTimeMillis();
System.out.println("(e2-s2) = " + (e2-s2));
Test t1 = new Test();
long s1 = System.currentTimeMillis();
for(int i=0; i<1_000_000_000; i++){
t1.a += t1.a;
t1.a -= t1.a;
t1.a *= t1.a;
t1.a -= t1.a;
}
System.out.println("t1.a = " + t1.a);
long e1 = System.currentTimeMillis();
System.out.println("(e1-s1) = " + (e1-s1));
}
static class Test{
public int a;
private int b;
public void setB(int b){
this.b = b;
}
public int getB(){
return b;
}
}
}
그 결과는 다음과 같았다.

이번에는 Getter/Setter를 사용하는 10억번의 연산이 약 12ms 느리다.
연산을 많이 넣어 보았다.
package practice;
public class GetterDirect {
public static void main(String[] args) {
Test t2 = new Test();
long s2 = System.currentTimeMillis();
for(int i=0; i<1_000_000_000; i++){
t2.setB(t2.getB()+t2.getB());
t2.setB(t2.getB()-t2.getB());
t2.setB(t2.getB()*t2.getB());
t2.setB(t2.getB()-t2.getB());
t2.setB(t2.getB()+t2.getB());
t2.setB(t2.getB()-t2.getB());
t2.setB(t2.getB()*t2.getB());
t2.setB(t2.getB()-t2.getB());
t2.setB(t2.getB()+t2.getB());
t2.setB(t2.getB()-t2.getB());
t2.setB(t2.getB()*t2.getB());
t2.setB(t2.getB()-t2.getB());
t2.setB(t2.getB()+t2.getB());
t2.setB(t2.getB()-t2.getB());
t2.setB(t2.getB()*t2.getB());
t2.setB(t2.getB()-t2.getB());
t2.setB(t2.getB()+t2.getB());
t2.setB(t2.getB()-t2.getB());
t2.setB(t2.getB()*t2.getB());
t2.setB(t2.getB()-t2.getB());
t2.setB(t2.getB()+t2.getB());
t2.setB(t2.getB()-t2.getB());
t2.setB(t2.getB()*t2.getB());
t2.setB(t2.getB()-t2.getB());
}
System.out.println("t2.getB() = " + t2.getB());
long e2 = System.currentTimeMillis();
System.out.println("(e2-s2) = " + (e2-s2));
Test t1 = new Test();
long s1 = System.currentTimeMillis();
for(int i=0; i<1_000_000_000; i++){
t1.a += t1.a;
t1.a -= t1.a;
t1.a *= t1.a;
t1.a -= t1.a;
t1.a += t1.a;
t1.a -= t1.a;
t1.a *= t1.a;
t1.a -= t1.a;
t1.a += t1.a;
t1.a -= t1.a;
t1.a *= t1.a;
t1.a -= t1.a;
t1.a += t1.a;
t1.a -= t1.a;
t1.a *= t1.a;
t1.a -= t1.a;
t1.a += t1.a;
t1.a -= t1.a;
t1.a *= t1.a;
t1.a -= t1.a;
t1.a += t1.a;
t1.a -= t1.a;
t1.a *= t1.a;
t1.a -= t1.a;
}
System.out.println("t1.a = " + t1.a);
long e1 = System.currentTimeMillis();
System.out.println("(e1-s1) = " + (e1-s1));
}
static class Test{
public int a;
private int b;
public void setB(int b){
this.b = b;
}
public int getB(){
return b;
}
}
}
그 결과는 다음과 같다.
Getter/Setter 가 65ms 느리다.

마지막 테스트에서는 Getter/Setter 를 총 720억 번 호출한다. 이 정도 연산량에 65ms이면 작은 것인가 큰 것인가. 성능이 조금이라도 빠른 것이 중요하다면 필드 직접 접근을 사용하는 것도 나쁘지 않을 것 같다.
같은 연산인데 소스의 순서에 따라 먼저 실행될 때 더 느린 이유는 무엇일까? JVM이 무언가 하고 있는 것일까.
마지막 테스트에서 또다시 순서를 바꿔서 필드 직접 접근 소스를 먼저 실행되도록 한 결과는 아래와 같다.

GPT 문의

간단한 소스임에도 성능 결과를 명확하게 이해하고 왜 그런 것인지 설명하기 어렵다. GPT 답변을 참고하여 JVM동작 원리 등을 공부해야겠다.
//todo: 소스 순서에 따라 성능 측정에 차이가 생기는 이유 찾기.
https://chat.openai.com/share/d3b2408b-8b20-48da-9f8c-f312bffc90ff
ChatGPT
A conversational AI system that listens, learns, and challenges
chat.openai.com
지피티 선생님의 답변으로 키워드를 얻을 수 있었다.
JIT최적화
나중에 실행되는 코드가 더 빠른 이유이다.
테스트 시에는 JIT최적화를 염두에 두고 진행해야 한다고 한다.
'개발 > 개발' 카테고리의 다른 글
[개발관련] Docker_ MariaDB Container 만들기 (0) | 2024.06.01 |
---|---|
[개발관련] JAVA_ SLF4J와 Logback에 대한 이해(+ServiceLoader) (0) | 2024.05.24 |
[개발관련] firewall-cmd, zone, service (0) | 2024.05.06 |
[개발관련] Rocky8 비밀번호 5회 틀림 잠김 처리 (authselect, pam.d, faillock) (0) | 2024.05.04 |
[개발관련] snmpwalk 인터페이스 정보 (0) | 2024.04.30 |