devcken.io

Thoughts, stories and ideas.

자바 성능 튜닝: 자바 성능 향상을 위한 완벽 가이드란 책을 보고...

'...란 책을 보고'라는 제목을 붙였지만, 사실 24쪽까지 읽은게 전부다.

난 이 책을 사서 읽기 위해 34,000원이라는 돈을 지불했다. 좋은 책을 읽기 위한 34,000원이라는 돈은 사실 푼돈에 불과하다. 30,000원도 안되는 좋은 책들이 이 세상에는 널렸다.

우리 나라의 IT 서적들은 크게 4가지 종류로 나뉜다. 첫째, 한국인 저자가 직접 썼고 내용도 좋은 책. 둘째, 한국인 저자가 썼으나 좋지 못한 책. 셋째, 외쿡 살람이 쓴 좋은 책. 넷째, 외쿡 살람이 썼으나 좋지 못한 책.

난 외쿡 살람이 쓴 좋은 책을 가장 선호한다. 물론 한국인 저자가 쓴 책 중에도 좋은 책들이 많다. 그 중에서도 조영호님이 쓴 객체 지향의 사실과 오해라는 책은 내가 읽은 어떤 OOP 책 중에서도 최고다. 아무튼, 내가 외쿡 살람이 쓴 좋은 책을 선호하는 이유는 이 포스트에서 중요한 내용은 아니므로 넘어가자.

거꾸로 내가 제일 싫어하는 책은? 한국 살람이 쓴 안좋은 책? 아니다. 안 좋은 책을 쓰는 이유는 많을 것이다. 문장 능력 자체가 떨어진다던가, 책이 다루는 주제에 대한 지식이 책을 쓸만큼 갖춰져 있지 않다던가... 뭐 여러 가지 이유가 있을텐데, 물론 저러한 이유들도 납득하기 쉽지 않다. 허나 내가 제일 싫어하는 책은, '외쿡 살람이 쓴 좋은 책을 한쿡 살람이 이상하게 번역한 책'이다.

'자바 성능 튜닝: 자바 성능 향상을 위한 완벽 가이드'란 책이 딱 그런 책이다. 살때는 몰랐는데 이 책의 제목이 이젠 마음에 안든다. 원제는 Java Performance: The definitive guide다. 자바 성능 완벽 가이드라고 하는게 맞다. 왜냐하면 긴 이름이 마음에 안들기도 하지만, 책의 내용 상 튜닝에만 초점을 맞춘게 아니라, 중점은 성능에 있다. 이는 책에서도 거듭 강조한 부분이고 역자가 제대로 변역한 부분에도 분명 나와있다. 그러므로, 원제처럼 자바 성능이라고 하는 것이 맞다.

내가 이 책을 받아들고 19페이지를 읽을 때쯤 원서를 다운로드 받았다. 이유는 딱 하나.

이게 무슨 뜻이야?

비판하는 몇 가지 증거를 제시하겠다.

이 번역서의 21페이지, 첫 단락을 보면 이렇게 나와있다.

여기서 세 번째 위험 요소는 테스트의 입력 값 범위다. 임의의 수를 선택하는 데 코드를 사용하는 방법을 나타낼 필요는 없다. 이와 같은 경우 (음수일 때) 테스트 중인 메서드에 대한 호출 대신 바로 예외 처리를 한다. 가장 큰 피보나치 수는 두 배로 나타날 수 있으므로 1476보다 큰 파라미터 값이 들어오면 예외 처리한다.

원서의 내용은 다음과 같다.

The third pitfall here is the input range of the test: selecting arbitrary random values isn’t necessarily representative of how the code will be used. In this case, an exception will be immediately thrown on half of the calls to the method under test (anything with a negative value). An exception will also be thrown anytime the input parameter is greater than 1476, since that is the largest Fibonacci number that can be represented in a double.

이 단락을 이해하려면 맥락을 이해해야 한다. 두 가지 코드를 볼 것이다.

먼저,

for (int i = 0; i < nLoops; i++) {  
  l = fibImpl1(random.nextInteger());
}

여기서 fibImpl1 메서드는 피보나치 수를 구하는 함수로 한 개의 정수형 인자를 받는다. 그런데 컴파일러의 똑똑함을 극복(?)하기 위해서 균일한 값이 아닌 랜덤 값을 입력시키고자 난수 발생기로 정수를 가져와 입력하는 코드다.

그리고,

int[] input = new int[nLoops];  
for (int i = 0; i < nLoops; i++) {  
  input[i] = random.nextInt();
}
long then = System.currentTimeMillis();  
for (int i = 0; i < nLoops; i++) {  
  try {
    l = fibImpl1(input[i]);
  } catch (IllegalArgumentException iae) {
  }
}
long now = System.currentTimeMillis();  

위 코드는 첫번째 코드를 실제적인 성능 측정을 위해 개선한 것이다. 랜덤 값 발생 비용이 측정에 포함되므로 이를 제외하기 위한 코드 분리를 추가한 것이다.

원서의 문장에서 selecting arbitary random values라고 말한 부분이 바로 이것을 두고 말한 것이다. 즉, 해당 메서드를 실제 사용할 때 랜덤 값 생성은 포함되지 않으므로, 입력 값의 범위를 정의해주어야 한다는 것이 이 단락 그리고 절의 핵심이다.

그래서, 임의의 수를 선택하는 데 코드를 사용하는 방법을 나타낼 필요는 없다.라고 번역한 것은 문맥을 고려하지 않고 그냥 번역한 것이다. 이는 랜덤 값 선택이 반드시 코드 사용 방법을 나타내는 것은 아니다.라고 변역되거나 좀 더 의역하여 랜덤 값 선택은 해당 메서드의 내용이 아니다.라고 변역해야 한다.

그 다음 문장 이와 같은 경우 (음수일 때) 테스트 중인 메서드에 대한 호출 대신 바로 예외 처리를 한다.은 심지어 내용을 완전히 왜곡한 것도 모자라 수동태를 능동태로 옮겨버렸다. 원서의 문장 In this case, an exception will be immediately thrown on half of the calls to the method under test (anything with a negative value).에서 핵심은 on half of the calls다. 이 문장에서 말하고자 하는 내용은 테스트 대상 메서드에 대한 호출 중 절반(음수 값을 가진 모든 호출)에 대해 예외가 즉시 발생한다.는 것이다. 즉, random.nextInt() 메서드가 음수 값을 발생시킬 확률이 50% 정도는 될 것이므로 호출의 절반이 예외를 발생시킬 것이라고 말하고 있는 것이다.

위 단락에서 마지막 문장 가장 큰 피보나치 수는 두 배로 나타날 수 있으므로 1476보다 큰 파라미터 값이 들어오면 예외 처리한다.는 이 책의 정수(?)를 보여준다. 이 문장을 설명하려면 코드 하나를 더 봐야 한다(코드의 내용은 일단 무시하자).

public double fibImplSlow(int n) {  
  if (n < 0) throw new IllegalArgumentException("Must be > 0"); 
  if (n > 1476) throw new ArithmeticException("Must be < 1476");
  return verySlowImpl(n);
}

나는 이러한 번역이 왜 나왔는지를 역으로 추적해봄으로써 올바른 번역을 소개하고자 한다.

첫번째로, '가장 큰 피보나치 수는 두 배로 나타날 수 있다'라는 문장이 도대체 무엇을 의미하는걸까? 이것은 원서의 문장 중 뒤에 that절을 봐야한다. 대충 직역하자면, '그것이 가장 큰 피보나치 숫자이고, double로 표현될 수 있다' 정도일텐데, 여기서 역자는 a double를 두배라고 번역했다. 그렇게 번역하려면 원래의 문장에서 in a doubleto be doubled가 되어야 한다. 관사 a는 도대체 어디로 날려먹은걸까? 여기서 a double은 우리가 흔히(?) 얘기하는 double 변수, 즉 배정밀도 변수를 말한다. 갑자기 왠 double? 일단 다음으로 넘어가자.

두번째로, 1476이다. 1476이라는 숫자는 위 코드에서 경계값으로 예외 조건이다. 내 생각에 저자는 이 1476이라는 숫자가 무엇을 의미하는지 몰랐던 것 같다. 만약 알았다면 첫번째 내용을 이해했어야 한다. 만약 피보나치 메서드에 1476이 대입된다면 코드의 내용 상 아마도 그에 대한 피보나치 값이 나올 것이라는 것을 추정할 수 있다. 그렇다면 1477은 왜 안되는 걸까?

피보나치 수는 입력 값이 커질 수록 급격하게 증가한다. 내 기억으로 long이나 double이 아닌 int로 구현할 경우 입력값 한계가 300도 안됐던걸로 기억한다. 1476가 입력됐을 때 double 변수에 담을 수 있는 최대의 피보나치 수가 결과로 나온다. 즉, 1477이 입력되면 overflow가 발생할 것이고 결과는 음수가 나온다. 이를 사전에 막기위한 조치가 바로 if (n > 1476)이다.

이 책의 구현 방식으로는 사실 1476은 고사하고 50만 입력해도 엄청 오래 걸린다. 이 책의 구현 방식은 피보나치 수에 대한 알고리즘을 그대로 구현한 것으로 꼬리 재귀를 만족시키지 못한다. 예제에서 50을 입력하도록 되어 있는데, 이 부분이 원서의 수준을 의심케 하는 대목이다. 19페이지를 보면 내가 의심하는 이유를 알 것이다. 내가 직접 해본 바로는 책에서 말하는 내용을 증명할 수 없었다.

그러므로 원래의 문장 An exception will also be thrown anytime the input parameter is greater than 1476, since that is the largest Fibonacci number that can be represented in a double.또한, 1476이 한 개의 double 변수로 나타낼 수 있는 가장 큰 피보나치 수를 만들어내므로 입력 파라메터가 그 수보다 크면 예외가 발생한다.라고 번역되어야 한다.

다음은 내가 결국 이 책을 덮게 만든 문장(24페이지)이다.

벤치마크 내에서 전반적으로 시간 차이가 나서 루프를 많이 도는 동안에는 몇 초 후에 측정되지만 각 반복 직후에는 나노초 후에 측정되곤 한다.

이 문장이 도저히 이해가 안가 다시 원서를 봤다.

The overall time difference in a benchmark such as the one discussed here may be measured in seconds for a large number of loops, but the per-iteration difference is often measured in nano‐ seconds.

그 뒷문장은 다음과 같다(원서와 번역서의 문장을 같이 보여주겠다).

Yes, nanoseconds add up, and “death by 1,000 cuts” is a frequent performance issue.(이 나노초들이 더해져서 "가랑비에 옷 젖게 되는" 경우는 흔한 성능상의 이슈다)

뒷 문장은 그런대로 번역이 되었다. 그런데... 앞 문장을 왜 저렇게 번역했을까?

이 문장에서 의도하는 바를 대충 번역하자면, 여기서 거론되고 있는 벤치마크처럼 하나의 벤치마크 상의 전체 시간 차는 많은 회수의 루프의 경우에 초단위로 측정되지만, 이터레이션 당(즉 루프 한번) 시간 차는 대게 나노 초로 측정될 것이다. 정도가 될 것 같다. 그래야 뒷문장에서 말하는 나노초의 합산과 death by 1,000 cuts로 인한 상습적인 성능 이슈가 설명된다.

번역서의 의미로는 도저히 의미 파악도 안되고 문제의 문장과 그 뒷문장이 연결되지도 않는다.

이들 외에도 지적할 부분들이 있지만, 더해봐야 시간 낭비다(이미...).

내가 이러한 오역서들에 짜증이 나는 이유는 잘못된 정보 전달이 제일 크다. 책을 볼 때 의심 내지 비평을 하면서 보는 자세는 당연히 필요하지만, 이 책은 정보서다. 정보서라는 것은 정확한 정보만을 명확하게 전달해야 한다. 물론 환경이나 여러 가지 이유로 똑같이 재현이 안되거나 일부 틀린 내용이 있을 수는 있다. 허나 이 번역서에서 보여준 실수는 실수가 아니다. 역자는 그 내용이 어떤 내용인지도 모르고 책을 번역했음이 분명하다.

나는 내가 책 읽는 속도가 느리다고 생각했었는데(사실 좀 답답하긴 하다), 오늘에서야 그 의심을 좀 거둘 수 있을거 같다. 이 책의 24페이지까지 읽는데 무려 1시간 20분이 걸렸는데, 문장을 보는 내내 턱턱 막히고 그 의미를 다시 찾아보고 원서와 비교하다보니 그랬다. 이는 정보서적으로써는 0점이다.

생각보다 엄청 긴 글이 됐는데, 역자에게는 죄송스러운 마음이 든다. 뭐 사실 이 포스트를 얼마나 많은 사람이 보겠냐마는... 책 한권을 번역한다는게(이 책이 무려 500페이지가 넘는다) 엄청난 일이라는 것을 나도 대충 알기에 이렇게 비판하는 것이 그리 속시원하지만은 않다. 하지만, 번역서는 원서의 명성은 둘째치고 있는 그대로의 정보를 전달하기 위해 정성을 들여야 한다는 점은 포기하지 못하겠다.

혹자가 이런 말을 할까 하여 한마디 덧붙인다.

번역이 좀 틀릴 수도 있는거고, 번역한 거 자체도 고마워해야 하는거 아닌가요?

예전에 웹에 어떤 문서를 번역한 내용이 틀렸다라는 것을 알려주면서(상대방에게는 지적으로 들렸을 수도? 아니 사실 지적 맞다) 들은 말이다. 그 때는 그냥 별거 아니라 생각하고 넘겼는데, 지금은 제대로 말해주고 싶다.

번역서라고 해도 원저자의 의도를 훼손할 권리는 없는 것이고, 돈을 받고 판매를 하는 책에 대한 의무가 있는 겁니다. 그리고 설령 어떤 내용을 번역하여 무료로 웹에 올렸다고 해도 오역에 대한 비판 내지 충고는 들을 자세가 되어 있어야 합니다.