Spring CGLIB 프록시 생성 로직 개선으로 성능 9.9배 향상!
스프링 환경에서 CGLIB 프록시 생성 시 클래스명 조합 로직의 비효율성 발견
ClassNameReader의 getClassInfo 메서드가 불필요한 바이트코드 파싱 및 문자열 연산 수행 문제점 지적
ASM의 ClassReader.getClassName() 메서드 활용으로 객체 할당 7회→2회 감소, 성능 9.9배 향상 및 메모리 할당 47% 감소 달성
JMH 벤치마크로 성능 개선 효과를 정량적으로 증명하여 Spring 7.1.0-M1에 기여함
CGLIB 프록시 클래스명 생성의 비효율성
스프링 프레임워크는 인터페이스가 없는 클래스에 프록시를 적용할 때 CGLIB를 사용하며, 이 과정에서 런타임에 동적으로 프록시 클래스를 생성합니다. 기존 `ClassNameReader.getClassInfo()` 메서드는 클래스 이름뿐만 아니라 부모 클래스 및 모든 인터페이스 이름까지 바이트코드(Bytecode)를 파싱하고 UTF-8 디코딩 및 문자열 치환 연산을 수행했습니다. 하지만 `getClassName()` 메서드는 이 정보들 중 첫 번째 원소인 클래스 이름만 사용하므로, 나머지 연산은 불필요한 오버헤드(Unnecessary Overhead)를 발생시켰습니다. 이는 특히 인터페이스가 많을수록 성능 저하가 심화되는 요인이었습니다.
ASM ClassReader API를 활용한 최적화
개선된 방식은 ASM 라이브러리가 제공하는 `ClassReader.getClassName()` 메서드를 직접 활용합니다. 이 메서드는 클래스 파일의 상수 풀 인덱싱 정보를 이용해 별도의 바이트코드 순회 없이 클래스 이름을 효율적으로 추출합니다. 변경 후에는 `ClassNameReader.getClassInfo()`에서 수행하던 객체 할당(Object Allocation) 횟수가 약 7회에서 2회로 감소했으며, 부모 클래스 및 인터페이스 정보 디코딩과 같은 불필요한 연산이 제거되었습니다. 또한, Visitor 객체 생성 및 예외 처리 오버헤드가 사라져 실행 속도가 약 9.9배에서 14.5배까지 향상되었습니다.
JMH 기반 성능 검증 및 결과 분석
기여자는 제안된 변경의 성능 향상을 객관적으로 입증하기 위해 JMH(Java Microbenchmark Harness)를 사용했습니다. CGLIB의 `Enhancer`와 커스텀 `CapturingGeneratorStrategy`를 활용하여 실제 프록시 생성 시 발생하는 바이트코드를 캡쳐하고, 이를 `ClassReader`로 분석하는 환경을 구축했습니다. 벤치마크 결과, 인터페이스가 없는 경우 약 9.9배, 인터페이스 3개 구현 시 약 14.5배의 실행 시간 단축 효과를 확인했습니다. 또한, 메모리 할당량도 약 47%에서 58%까지 감소하는 것을 확인하여 개선의 유의미성을 입증했습니다.
오픈소스 기여를 위한 데이터 기반 설득
본 기여는 오픈소스 프로젝트에 성능 개선 제안 시 단순한 주장만으로는 부족하다는 점을 시사합니다. 메인테이너를 설득하기 위해서는 '실제로 성능이 개선되는가?' 와 '그 개선이 유의미한 수준인가?' 라는 두 가지 질문에 대한 명확한 답을 제시해야 합니다. 이를 위해 JMH를 이용한 정량적 벤치마크 결과를 바탕으로 변경의 필요성과 효과를 증명했으며, 이는 Spring 프레임워크 7.1.0-M1 릴리스에 반영되는 성과로 이어졌습니다.