By: 존 레이 토마스 (John Ray Thomas)

사용자 삽입 이미지
엠바카데로는 C++을 중요하게 여기고 있습니다. 최근에 C++ 제품의 미래에 대해 그다지 많이 알려드리지 않았습니다만, 우리는 지난 몇년간 대단히 멋진 기능들을 갖춘 차세대 C++ 플랫폼을 개발하느라 극도로 바빴었습니다. 이제 우리는 여러분에게 로드맵을 공개하는 것을 더 이상 미룰 수 없게 되었습니다.

우리가 작업해온 기능들 중에는 64비트 윈도우 C++ 개발툴, 업계 최고 수준의 C/C++ 표준 호환성을 포함한 C++11 지원, iOS와 안드로이드를 포함한 모바일 플랫폼을 위한 ARM 지원 등이 포함되어 있습니다. 이 기능들은 지난 18개월 동안 개발되어왔고, 이 기능들중 대부분은 2012년 하반기에, 그리고 나머지는 2013년 상반기에 발표될 예정입니다.

새로운 타겟 플랫폼들


64-bit
C++Builder XE2에는 인텔 MacOS X 컴파일러가 포함되어 있고, 32비트 윈도우와 MacOS X 모두를 지원하는 파이어몽키로 단일 소스코드 애플리케이션을 빌드할 수 있습니다. 32비트 윈도우와 MacOS X에 더하여, 개발중인 새로운 C++ 컴파일러는 64비트 윈도우, 거기다가 ARM 프로세서를 iOS와 안드로이드를 네이티브하게 지원하게 됩니다.

사용자 삽입 이미지
64비트 윈도우 시스템이 이미 널리 퍼져있기 때문에, 64비트 지원은 C++빌더 고객들이 가장 많이 요청해온 기능입니다. 특히, 개발자들은 64비트 지원 인터페이스가 필요한 64비트 드라이버, IIS, 셸 익스텐션, SQL 서버 지원 등의 64비트 서브시스템 지원을 많이 요구해왔습니다. 개발자들은 애플리케이션의 4GB 메모리 한계를 넘어설 수 있는 64비트 메모리 어드레싱도 많이 요구했습니다. 새로운 C++ 컴파일러는 윈도우에서 64비트 어드레싱과 64비트 서브시스템 지원을 가능하게 하는 고성능 64비트 애플리케이션과 라이브러리를 완벽하게 지원하도록 계획되었습니다. 인텔 64비트 지원 외에도, 새 컴파일러 플랫폼은 ARM 하드웨어 아키텍처와 VCL 및 파이어몽키 애플리케이션 프레임워크 모두를 완벽하게 지원합니다.

물론, RAD C++의 속성, 메소드, 이벤트 확장, RAD IDE와 비주얼 디자이너 통합, 델파이 호환성은 새로운 C++ 개발툴에도 그대로 유지됩니다.

모바일


사용자 삽입 이미지
새로운 C++ 개발툴은 새로운 데스크톱 타겟 플랫폼들을 지원하도록 계획되었으며, 우리는 처음으로 C++ 개발툴이 공유 코드 베이스로 네이티브 모바일 개발을 할 수 있도록 ARM 프로세서를 지원하도록 작업을 진행하고 있습니다. iOS와 안드로이드 지원 두 가지 모두 개발중인 상태입니다. 두 모바일 플랫폼들에서 새로운 컴파일러는 최적화된 ARM v7 바이너리를 생성하게 됩니다.

사용자 삽입 이미지
파이어몽키 프레임워크는 C++ iOS와 안드로이드 모바일 개발을 완벽하게 지원하도록 업데이트중이며, 대단히 충실한 네이티브 및 커스텀 UI와 네이티브 플랫폼 서비스, GPS/카메라/가속도 등의 센서들을 지원합니다.

C++ 11 표준 지원


사용자 삽입 이미지
지난해, ANSI/ISO C++ 위원회는 10년 이상 진행되어왔던 새로운 언어 및 라이브러리 규격을 승인했으며, C++11이라고 불리고 있습니다. 우리는 새로운 C/C++ 컴파일러가 업계 최고의 C++11 및C99 언어/라이브러리 호환성을 갖추도록 진행중이라는 것을 알릴 수 있게 되어 대단히 기쁘게 생각합니다. 덧붙여, 이 컴파일러들은 Boost, ACE 등의 중요한 C++ 라이브러리들을 최신 버전으로 지원합니다.

C++이 돌아왔다


사용자 삽입 이미지
최근 몇년 동안 C++의 인기는 결코 줄어들지 않았으며, 언론 보도 등에서도 다른 언어 및 플랫폼들과 같은 점유율을 유지해왔습니다. C++은 지난 10년 동안 묵묵하게 제 몫을 해왔습니다. 하지만, 최근 C++의 중요성은 더 널리 논의되고 있습니다. 윈도우 8의 WinRT 네이티브 C++ 지원과 안드로이드 네이티브 개발자 킷(NDK)이 그 예입니다. 새로운 C++ 컴파일러들은 높은 표준 호환성을 갖출 것이며, VCL과 파이어몽키를 완벽하게 지원할 것이며, 새로운 플랫폼들과 새로운 컴파일러 아키텍처들을 지원할 것이며, 게다가 RAD의 무적의 생산성도 그대로 가져갈 것입니다. C++빌더 개발자로서는 더 없이 굉장한 시간이 다가왔습니다.

이 글에서 알려드린 정보들은 현 시점에서의 엠바카데로의 대략적인 제품 방향을 설명한 것입니다. 미래의 개발, 발표, 기능들의 시기에 대한 결정은 엠바카데로의 재량권 하에 있으며 언제든 사전 공지 없이 변경될 수도 있습니다. 기술적인 계획들은 때때로 변경되기도 하며, 우리는 현재 이들 기능들 다수에 대해 베타1인 상태로, 곧 베타2 상태로 넘어가게 됩니다. 현재 우리의 계획은 C++11, 64비트 윈도우, ARM iOS 지원을 2012년 하반기에, 그리고 안드로이드 지원을 2013년 상반기에 출시할 예정입니다. 새로운 기능들이 발표되면 액티브 서포트 및 메인터넌스 계약 기간에 있는 C++Builder XE2 고객들에게 추가 비용 부담 없이 제공될 예정입니다.

지금 써볼 수 있습니다


사용자 삽입 이미지
우리는 많은 분들이 이 새로운 컴파일러를 오래 기다리기 어려워한다는 것을 알고 있습니다. 그래서 우리는 C++Builder 개발자들을 위한 특별한 프로그램으로서, XE2를 지금 메인터넌스와 함께 구입하면 이 컴파일러의 프리뷰 버전에 대한 우선 액세스를 할 수 있게 해주는 기회를 드립니다. 2012년 6월 29일까지는 라이선스 비용을 20% 할인해드립니다. 자세한 내용은 http://www.embarcadero.com/landing-pages/cbuilder-bundle 에서 살펴보실 수 있습니다.


원문 : http://edn.embarcadero.com/article/42275
번역 : 박지훈.임프


C++빌더나 델파이를 반드시 데브기어에서 미국 가격보다 150%나 비싼 바가지 가격으로 구입할 필요는 없습니다. 미국의 달러 가격으로 구입해도 위의 모든 혜택은 당연히 똑같습니다. 자세한 내용은 http://blog.devquest.co.kr/imp/255 에서 참고하세요.
크리에이티브 커먼즈 라이센스
Creative Commons License
2012/05/20 00:52 2012/05/20 00:52

trackback :: http://blog.devquest.co.kr/imp/trackback/266

  1. C 빌더 로드맵: 64비트/C 11/ARM/iOS/안드로이드

    Tracked from 볼랜드포럼 2012/05/20 01:36  delete

    By: 존 레이 토마스 (John Ray Thomas) <img src="http://img.turboforum.co.kr/impboard/attach/0000136676/Icon_CBuilder.png" align=right width=175>엠바카데로는 C 을 중요하게 여기고 있습니다. 최근에 C 제품의 미래에 대해 그다지 많이 알려드리지 않았습니다만

아래 글은 원래 2002년에 Robert Lee의 홈페이지인 http://www.optimalcode.com 라는 사이트에 실렸던 "Delphi Optimization Guideline"이라는 글의 일부입니다. 지금 이 사이트는 없어진 상태이고 원래의 필자도 전혀 연락이 안되는 상태입니다. 하지만 최근에 예전의 컨텐츠들을 http://delphitools.info 에서 되살렸습니다. 델파이 개발자분들께 도움이 될 부분이 많을 것 같아서 번역을 시작해봅니다. 전체는 4개의 시리즈로 되어 있으며, 이 글은 그중 마지막인 Floating Point Optimization Guideline 입니다.

원문 : http://delphitools.info/OptimalCode/float.htm


사용자 삽입 이미지

Delphi Optimization Guidelines


(1) 일반 가이드라인
(2) 정수 가이드라인
(3) 문자열 가이드라인
(4) 부동소수점 가이드라인



(4) 부동소수점 가이드라인

스타일 가이드라인



절대적으로 필요한 경우가 아니면 extended를 사용하지 말라

extended의 정밀도(80비트)는 내부적으로 FPU가 계산을 수행하기는 하지만 로드와 저장에 있어 그다지 효율적이지 못합니다. 따라서 extended 타입을 사용하는 것은 단순한 산술 연산(+, -, *)의 전체 실행 시간을 두배까지 늘릴 수 있습니다. 이것은 연산 자체를 수행하는 데 들어가는 추가 시간 때문이 아니라 extended 값을 로드하고 저장하는 데 들어가는 추가 시간입니다. 또한, extended 타입 변수의 크기의 불편함(10바이트, doubleword 정렬에서 12바이트를 차지) 때문에 캐시 라인에도 비효율적이 될 가능성이 높아지며 그로 인해 성능 손실이 생기게 됩니다.

마지막으로, 델파이 버전 2에서 4까지에서는, extended 타입의 지역 변수는 할당된 12바이트(3 dword) 중 첫 10바이트가 아니라 뒤의 10바이트로 정렬되는 문제가 있습니다. 이것은 extended가 지역 변수로서는 잘못 정렬된다는 뜻입니다. 이 문제는 델파이 5 버전에서 픽스되었지만, 아직도 컴파일러가 생성한 임시 변수들에서는 문제가 남아있습니다.

부동소수점 타입들을 섞지 말라

기본적인 문제는, 두 가지 경우에 불필요한 타입 변환 단계를 강제하게 된다는 것입니다. 1) 한 변수를 다른 변수에 대입, 2) 변수를 파라미터로 전달. 이런 경우, 한 변수의 두 인스턴스가 FP 스택에 로드되어야 하며 그냥 복사되는 대신 새로운 타입으로 저장되게 됩니다. 이로 인해 3, 4배 시간을 더 소모하게 됩니다.

각 대입문에서 함수 호출은 한번으로 제한하도록 노력하라

FPU(부동소수점 유닛)의 레지스터 스택은 8개 밖에 없습니다. 따라서 스택이 넘치는 것을 막기 위해, 하나의 표현식 내의 함수 호출이 일어나면 호출을 실행하기 전에 레지스터 스택을 먼저 비워야 합니다. 오직 하나의 예외는 인자들이 결정된 직후 그리고 나머지 표현식의 나머지 부분이 평가되기 전에 호출될 수 있어서 표현식 내의 첫번째 함수 호출이 이런 비우기 작업과 무관할 경우입니다. 델파이는 모든 저장된 값들을 임시적인(그리고 보이지 않는) extended 변수에 저장함으로써 스택을 비웁니다. 앞에서 설명했던 것처럼, extended는 성능이 낮으므로, 개발자가 스스로 임시 변수를 만들고 한 변수 대입마다 하나의 함수 호출만 일어나도록 표현식을 나누어야 합니다. 이 규칙은 System 유닛에 있는 컴파일러 "매직" 함수에서도 볼 수 있는데, Abs나 Sqr 함수 등입니다. 이것은 "중첩된" 호출을 포함하지 않는데, 파라미터 표현식에 포함된 함수 호출이 다른 함수 호출을 가지는 경우를 말합니다. 부동소수점 파라미터는 항상 스택을 통해 전달되므로, 각 파라미터 표현식은 분리된 표현식을 나타냅니다.

부동소수점 상수

부동소수점 상수는 실행 파일 내에 특정 타입으로 저장됩니다 (single, double or extended). 기본적으로, 정수 부분만으로 된 상수는 single로 저장되고 소수점 아래 부분이 있는 상수는 extended로 저장됩니다. 앞에서 언급한대로, extended를 사용하는 것은 높은 비용이 들기 때문에, 타입 지정 상수(typed constant)로 만들어서 지정된 크기(single or double)가 되도록 해야 합니다. 해당 값은 어떤 식으로든 바이너리에 포함되므로 전체 실행파일의 크기는 늘어나지 않습니다. 예를 들어봅시다.
const
  e: Double = 2.71828; // 오일러 상수
begin
  ...
  SomeVariable := e*sqr(r);
  ...

위 코드는 extended 타입을 사용하는 코드보다 빠를 뿐만 아니라 더 작습니다(double은 8바이트만 사용). $J+ 지시어를 사용하면 타입 지정 상수의 값도 변경할 수 있다는 점도 기억해둡시다.

또한, 컴파일러는 컴파일 중에 가능하다면 상수들을 결합합니다. 두 상수 사이의 연산이 그 상수들과 변수들, 변수 표현식 등이 관련된 다른 다른 연산들보다 우선순위에서 높을 경우, 그 상수들은 상수 폴딩(constant folding)이 됩니다. 추가로, 델파이 2와 3에서는 상수로 나누는 나눗셈은 항상 곱셈으로 바뀝니다. 불행히도 이 기능은 4 버전에서 없어졌습니다. 따라서, 예를 들면 델파이 2와 3에서 다음의 문장은,
fp := fp*3*4/5+3*4/2;

실제로는 아래와 같이 계산됩니다.
 fp:=fp*3*4*0.2+6

델파이 4에서는 위의 문장은 아래와 같이 계산됩니다.
 fp:=fp*3*4/5+6

변수들보다 상수를 앞에 두면 상수 폴딩의 효과가 더 나아집니다.
 fp:=3*4/5*fp+3*4/2;

위 코드는 실제로는 아래와 같이 계산됩니다.
 fp:=2.4*fp+6


컨트롤 워드 정밀도를 적절한 레벨로 설정하라

부동소수점 나눗셈과 제곱근 명령은 상당한 시간을 소모합니다. 하지만, 최대의 정확도가 필요한 경우가 아니라면 일정 시간을 아낄 수 있습니다. FPU의 컨트롤 워드를 변경하면 정확도의 수준을 바꿀 수 있습니다. 델파이 런타임 라이브러리에 의해 초기화되는 기본 정확도는 가장 느리지만 가장 정밀합니다(즉, extended). 델파이는 Set8087CW 프로시저와 전역 변수 Default8087CW를 통해 FPU의 컨트롤 워드에 대한 직접 변경을 지원합니다. 컨트롤 워드를 다른 정밀도 레벨로 바꾸려면 다음과 같이 하면 됩니다.
Single:   Set8087CW(Default8087CW and $FCFF); 
Double:   Set8087CW((Default8087CW and $FCFF) or $0200); 
Extended: Set8087CW(Default8087CW or $0300);

컨트롤 워드를 변경하면 나눗셈의 실행 시간만 달라지며, 펜티엄 II와 펜티엄 III 프로세서에서는 제곱근의 실행시간도 영향을 받습니다. 델파이 6 이후로는 간단히 SetPrecisionMode()를 호출하면서 적절한 정밀도 레벨 상수(pmSingle, pmDouble, pmExtended)를 넘겨주기만 하면 됩니다.

Trunc보다 Round를 선호하라

Trunc는 FPU 컨트롤 워드를 읽고 쓰므로 대단히 느립니다. 반면 Round 함수는 이런 동작을 하지 않으므로 Pentium II의 경우 2.5배 정도 빠릅니다.

함수보다는 var 파라미터를 가진 프로시저를 선호하라

이것은 오버헤드 관리 이슈이며, 따라서 전체 처리 시간에서 오버헤드가 더 많은 비율을 차지하는 작은 함수들의 경우에 적용됩니다. 다음의 예에서,
function Calc(a: Double): Double;
begin
  result := a*1.1;
end;

아래와 같이 바꾸면,
procedure Calc(var Result; a: Double);
begin
  Result := a*1.1;
end;

처리 시간을 절반으로 줄일 수 있습니다(펜티엄 II 기준). 이것은 넘겨주는 값을 실질적으로 사용하기보다는 주로 값을 넘기기만 하는 경우(단순한 대입 등) 더 효과적입니다. 예를 들면,
function SetValue(NewValue: Double): Double;
begin
  Result := Value;
  Value := NewValue;
end;

위 코드의 실행 시간은 오버헤드가 대부분을 차지하게 됩니다.

이 테크닉의 불리한 점은, 변경되지 말아야 할 파라미터들에 대해서도 const 대신 var를 사용해야 한다는 것입니다. const는 컴파일 타임에 파라미터가 확실히 변경되지 않는지 여부만 확인할 뿐 부동소수점 파라미터에 대해 실질적으로 아무것도 하지 않습니다.

부동소수점 예외

FP 예외(divide by zero 등)는 에러가 발생한 순간에 일어나지 않습니다. 대신 다음 부동소수점 명령까지 지연됩니다. 아마도 이렇게 구현한 이유는 해당 에러를 테스트하고 처리할 수 있도록 하기 위해 사용되었던 것으로 생각됩니다. 하지만, 이로 인해 예외가 실제로 일어났을 때 엉뚱한 코드가 문제가 있는 것으로 보이게 되는 어이없는 상황이 벌어집니다. 이 문제에 대한 해결책은 예외를 강제로 발생시키기 위해 대기나 FWait 명령을 끼워넣는 것입니다. 이것은 컴파일러가 부동소수점 문 이후 수행하는 것과 동일한 것입니다. 이렇게 대기를 실행하는 것은 당연히 시간을 소모하기 때문에, 일단 디버그를 마치고 나면 직접 작성한 부동소수점 어셈블리 루틴에서 루틴의 끝에 하나만 추가하기를 원할 수도 있습니다. 이렇게 하면 비용을 낮추면서도 발생한 모든 예외가 정확한 루틴을 가리킬 수 있게 됩니다.

물론, 모든 규칙에는 예외가 있습니다. 이 규칙이 적용되지 않는 예는, 윈도우 95에서 아래의 코드를 실행했을 때입니다(Stefan Hoffmeister의 FPU 데모에서 발췌)
  x := -1;
  asm
    fld x
    // IEEE invalid operation 예외 발생
    //   sqrt(-1)
    fsqrt


    fwait
  end;

윈도우 95와 달리 NT에서는 이 코드는 정확하게 FP 예외를 발생시킵니다. 윈도우 95에서는 FWAIT 이전에 FXAM를 끼워넣으면 예외가 발생합니다. 고마워, 마이크로소프트.


최적화 테크닉


스스로 부동소수점 최적화를 해야 한다

델파이는 부동소수점 최적화를 수행하지 않습니다. 여러분이 작성한 코드 그대로 실행되게 됩니다. 따라서 일반적인 표현식들이 합쳐질 것이라는 등의 추측을 하지 마십시오. 여러분이 직접 수행해야 합니다.

나눗셈을 줄이기 위해 노력을 다하라

나눗셈은 곱셈, 덧셈, 뺄셈에 비해 20~40배의 시간이 들 정도로 매우 큰 비용이 듭니다. 가능할 때는 항상 나눗셈을 루프 바깥으로 옮겨야 합니다. 상수로 나눗셈을 할 때는 그와 동등한 곱셈으로 바꾸는 것을 잊지 마십시오.

부동소수점수의 0 비교를 피하는 방법

부동소수점 변수내에 0이 들어있는지 확인할 때, 특정한 어떤 상황에서는 직접 비교를 피하고 대신 타입 캐스트를 이용하는 것이 이득일 수 있습니다. 이것은 부동소수점 비교가 0이 저장된 방식을 이용하는 부동소수점 기반의 0 확인을 필요로 하기 때문입니다. 이 테크닉은  가독성을 상당히 저하시키므로 절제해서 사용해야 합니다.

single 변수가 0인지 확인하려면 다음과 같은 코드를 사용합니다.
DWord(Pointer(SomeSingleVar)) shl 1 = 0

double 변수를 확인하는 방법은 좀 더 복잡합니다.
type
  PDoubleData = ^TDoubleData
  TDoubleData = record lo, hi: DWord end;

// 두 가지 방법이 있음
var
  DoubleData: PDoubleData;
...
  DoubleData := @SomeDoubleVar;
  if (DoubleData.hi shl 1 ) + DoubleData.Lo = 0 then
...

// 혹은
var
  DoubleData: TDoubleData absolute SomeDoubleVar;
...
  if (DoubleData.hi shl 1 ) + DoubleData.Lo = 0 then
...

이 테크닉은 펜티엄 II에서 비교 시간을 30~40% 정도 줄여줍니다.
크리에이티브 커먼즈 라이센스
Creative Commons License
2012/05/18 16:09 2012/05/18 16:09

trackback :: http://blog.devquest.co.kr/imp/trackback/264

  1. 델파이 최적화 가이드라인 (4) - 부동소수점 가이드라인

    Tracked from 볼랜드포럼 2012/05/18 16:22  delete

    <h1>(4) 부동소수점 가이드라인</h1> <h2>스타일 가이드라인</h2> <h3>절대적으로 필요한 경우가 아니면 extended를 사용하지 말라</h3> extended의 정밀도(80비트)는 내부적으로 FPU가 계산을 수행하기는 하지만 로드와 저장에 있어 그다지 효율적이지 못합니다. 따

아래 글은 원래 2002년에  Robert Lee의 홈페이지인 http://www.optimalcode.com 라는 사이트에 실렸던 "Delphi Optimization Guideline"이라는 글의 세번째 파트입니다. 지금 이 사이트는 없어진 상태이고 원래의 필자도 전혀 연락이 안되는 상태였습니다. 하지만 최근에 예전의 컨텐츠들을 http://delphitools.info 에서 되살렸습니다. 델파이 개발자분들께 도움이 될 부분이 많을 것 같아서 번역을 시작해봅니다. 전체는 4개의 시리즈로 되어 있으며, 이 글은 그중 세번째인 String Guideline 입니다.

원문 : http://delphitools.info/OptimalCode/string.htm


사용자 삽입 이미지

Delphi Optimization Guidelines


(1) 일반 가이드라인
(2) 정수 가이드라인
(3) 문자열 가이드라인
(4) 부동소수점 가이드라인



(3) 문자열 가이드라인

스타일 가이드라인



스트링을 두번 초기화하지 말라

디폴트 스트링 타입인 AnsiString은 생성된 직후 자동으로 빈 값으로 초기화됩니다. 따라서 AnsiString을 다시 초기화할 필요는 없습니다. 예를 들어 아래 코드에서 s := ''; 부분은 불필요합니다.
procedure GreatestOnEarth;
var 
  S: string; // a long string, not short!
begin
  S := '';
  ...
end;

주의할 것은, 이런 특성은 스트링을 리턴하는 함수들에는 적용되지 않는다는 것입니다. 이것은 함수의 result 변수가 지역 변수보다는 var 파라미터와 더 비슷하게 행동하기 때문입니다.

AnsiString에는 최대한 SetLength를 사용하라

동적 할당 특성은 AnsiString을 매우 강력하게 해줍니다. 불행히도, 이 강력함을 잘못 사용하기가 아주 쉽습니다. 그 전형적인 상황은 아래와 같습니다.
S2 := '';
for I := 2 to length(S1) do 
  S2 := S2 + S1[I];

이런 경우에 Delete를 쓸 수 있다는 점은 일단 논외로 하고, 여기서 문제는 S2 스트링의 메모리가 루프 내에서 반복적으로 계속 재할당된다는 것입니다. 당연히 이것은 시간을 소모합니다. 간단하고 잠정적으로 훨씬 효과적인 대안은 다음과 같습니다.
SetLength(S2, Length(S1) - 1);
for I := 2 to length(S1) do
  S2[I-1] := S1[I];
여기서 S2의 메모리는 루프의 이전에서 단 한번만 할당됩니다.

이런 종류의 "메모리 매니저 오용"은 AnsiString에서 일반적으로 벌어지는 일인데, 그것은 재할당이 자동으로 이루어져 잘 무시되기 때문입니다. PChar에서는 직접 메모리를 할당하므로 프로그래머들은 이런 코딩 스타일과 관련된 문제가 쉽지 않다는 점을 잘 알고 있죠. 옛 파스칼 스타일 스트링은 정적 할당을 사용하므로 이런 문제가 없습니다.

스트링과 동적 배열의 쓰레드 안전성 (델파이 5 이후와 펜티엄 II/애슬론에 해당)

스트링과 동적 배열의 쓰레드 안전성(thread safety)은 레퍼런스 카운트 문제를 차단하여 개선되었습니다. 델파이 4 버전까지는 멀티 프로세서 환경에서 AnsiStrting의 레퍼런스 카운트가 쓰레드 안정성에 문제가 있었습니다. 이 문제는 레퍼런스 카운트를 직접 수정하고 선점을 방지하는 단일 명령을 락 함으로써 픽스되었습니다. 불행히도 모든 것에는 그 대가가 따릅니다. 이렇게 쓰레드 안전성을 위해 사용된 lock CPU 명령 프리픽스는 펜티엄 II 프로세서에서 상당히 많은 시간을 소모합니다. 이 수정으로 인한 효과를 필자가 측정한 바에 따르면, 한 레퍼런스 카운트 조정 때마다 28 사이클이 추가되었습니다. 이로 인해 최악의 경우 2배의 성능 저하가 나타날 수 있습니다. 실제 사례에서는 20% 범위의 영향이 나타났습니다.

델파이 4까지의 AnsiString처럼 되돌리기

위에서 설명한 긴 스트링(AnsiStrting)의 동작을 델파이 4 이전처럼 되돌리면 긴 스트링의 속도가 더 빨라집니다. 이렇게 하려면, system.pas의 내용을 약간 수정하고 재컴파일해야 합니다. system.pas를 재컴파일하는 가장 쉬운 방법은 make 유틸리티와
/source/Rtl 디렉토리에 있는 makefile을 이용하는 것입니다. 원래의 파일들을 남겨두기 위해 소스 파일을 새 디렉토리로 복사합니다. make는 또한 lib, bin 등 다른 서브디렉토리들을 필요로 합니다. 새 위치에 이런 서브디렉토리들을 생성하십시오. 또한 함께 컴파일되어야 하는 많은 외부 어셈블리 파일들이 있으므로 TASM도 필요합니다.

변경할 내용은 상당히 간단합니다. 먼저, 모든
lock 프리픽스를 제거해야 합니다. 필자는 'lock'을 '{lock}'로 전체 치환하는 방법을 좋아합니다. 이렇게 하면 스트링과 동적 배열을 델파이 5보다 이전의 성능 수준으로 되돌립니다. 성능을 더욱 높이려면 xchg 명령 두 개를 제거해야 합니다. 이 명령은 암시적으로 lock 프리픽스를 발생시킵니다. 원래의 코드는 아래와 같습니다.
procedure _LStrAsg(var dest: AnsiString; source: AnsiString);
...
@@2:    XCHG    EDX,[EAX]
...
procedure _LStrLAsg(var dest: AnsiString; source: AnsiString);
...
        XCHG    EDX,[EAX]                       { fetch str   }
...

두 경우 모두
 세 개의 mov 명령과 ecx를 임시 레지스터로 사용함으로써 XCHG 명령을 대체할 수 있습니다.

procedure _LStrAsg(var dest: AnsiString; source: AnsiString);
...
@@2: {   XCHG    EDX,[EAX]}
        mov ecx,[eax]
        mov [eax],edx
        mov edx,ecx 
...
procedure       _LStrLAsg(var dest: AnsiString; source: AnsiString);
...
        {XCHG    EDX,[EAX]}                       { fetch str    }
        mov ecx,[eax]
        mov [eax],edx
        mov edx,ecx 
...

위와 같이 변경하면, 스트링 대입이 델파이 5에서보다 6배 빠르게 실행됩니다. (델파이 4보다는 2배 빠릅니다)

ShortString 사용을 피하라 (델파이 5 이후)

아마도 오래된
ShortString 방식을 점차 몰아내고 스트링 조작 루틴을 한 세트만 유지하기 위해서라고 보이는데, ShortString은 많은 조작이 들어갈 때는 긴문자열로 변환됩니다. 이런 이유로 이런 ShortString 작업은 훨씬 느려지게 됩니다.

동적 임시 문자열이 생기는 Copy 사용을 피하라

이것도 메모리 매니저의 오용과 관련된 문제입니다. 일반적인 상황은 아래와 같습니다.
if Copy(S1,23,64) = Copy(S2,15,64) then
  ...

여기서 문제는 임시 문자열을 위해 메모리 할당이 일어나고, 그래서 시간을 잡아먹는다는 것입니다. 이것은 유감스러운 일이지만, 네이티브 AnsiString 함수들에서는 아래와 같은 방식 외에는 다른 대안을 별로 제공해주지 않고 있습니다.
I := 1;
Flag := False;
repeat
  Flag := S1[I+22] <> S2[I+14];
  Inc(I);
until Flag or (I>64);
if not Flag then
  ...


AnsiString만을 사용하고 필요하면 PChar로 캐스트하라

AnsiString은 어찌됐든 본질적으로 그다지 효율적이지 못하다는 근거 없는 믿음이 많이 퍼져 있는데요. 이런 믿음은 잘못된 코딩 관행이나 메모리 매니저 오용, 그리고 위에서 설명한 네이티브 지원 함수의 부족 때문에 나온 것입니다. AnsiString은 일단 동적으로 할당이 일어나고 나면 다른 문자열과 별 다를 바가 없습니다. 메모리에 선형의 바이트들로 이루어지므로, 딱히 더 효율적이거나 덜 효율적이지도 않습니다. 충분한 지원 함수들과 적절한 코딩을 한다면 다른 스트링에 비해 AnsiString의 성능의 차이는 무시할 정도입니다.

(역주: 델파이 2009 이후 버전에서는 AnsiString은 PChar가 아닌 PAnsiChar로 캐스트해야 합니다)


스트링의 끝을 제거하기 위해서는 Copy보다 Delete를 선호하라

Copy 함수는 항상 전체 스트링을 복사합니다. 하지만 Delete는 현재 스트링의 끝 부분을 잘라낼 뿐입니다.
AString := Copy(AString, 1, Length(AString)-10);
Delete(AString, Length(AString)-10, 10);


위 두 라인의 코드는 동일한 역할을 하지만 Delete를 사용한 경우가 더 빠릅니다.

스트링 합치기

스트링들을 합치는 가장 좋은 방법은 아주 간단합니다. s1 := s2+s3+s4; 이런 방식이 가장 좋은 결과를 냅니다. 합치는 스트링의 갯수가 몇개이든, 또 그 안에 컴파일타임 상수가 있든 없든 마찬가지입니다.

노트: 델파이 2에서는, 컴파일타임 상수가 관련되어 있을 경우 s1 := Format([%s%s],s2,s3) 이런 방식이 더 빠를 수도 있습니다.

PChar로의 캐스팅

본질적으로, 스트링을 PChar로 변환하는 데에는 3가지 방법이 있습니다. PChar로 타입 캐스트하는 방법, 첫번째 문자의 주소를 이용하는 방법, 스트링을 일반 포인터로 타입 캐스트 하는 방법 등입니다. 이 세가지 방법의 실제 동작은 모두 다 다릅니다.

첫번째 문자의 주소를 가져오는 방법(예: p:=@s[1];)은 리턴되는 PChar가 오직 해당 스트링 변수만이 가리키는 유니크한 문자열을 가리키도록 하기 위해 UniqueString을 호출하게 됩니다. 스트링을 PChar로 타입 캐스트하는 방법은 첫번째 문자의 주소를 리턴하거나 혹은 스트링이 비어있는 경우 null의 주소를 리턴합니다. 따라서 PChar 값은 항상 nil이 아니게 됩니다. 가장 간단한 방법은 일반 포인터(예: p:=pointer(s);)로 캐스팅하는 방법입니다. 이 방법에는 숨겨진 함수 호출이 일어나지 않기 때문에 가장 빠릅니다.


(역주: 델파이 2009 이후 버전에서는 AnsiString은 PChar가 아닌 PAnsiChar로 캐스트해야 합니다)
크리에이티브 커먼즈 라이센스
Creative Commons License
2012/05/16 17:22 2012/05/16 17:22

trackback :: http://blog.devquest.co.kr/imp/trackback/263

  1. 델파이 최적화 가이드라인 (3) - 문자열 가이드라인

    Tracked from 볼랜드포럼 2012/05/16 18:34  delete

    <h1>(2) 문자열 가이드라인</h1> <h2>스타일 가이드라인</h2> <h3>스트링을 두번 초기화하지 말라</h3> 디폴트 스트링 타입인 AnsiString은 생성된 직후 자동으로 빈 값으로 초기화됩니다. 따라서 AnsiString을 다시 초기화할 필요는 없습니다. 예를 들어 아래 코드

아래 글은 원래 2002년에  Robert Lee의 홈페이지인 http://www.optimalcode.com 라는 사이트에 실렸던 "Delphi Optimization Guideline"이라는 글의 일부입니다. 지금 이 사이트는 없어진 상태이고 원래의 필자도 전혀 연락이 안되는 상태입니다. 하지만 최근에 예전의 컨텐츠들을 http://delphitools.info 에서 되살렸습니다. 델파이 개발자분들께 도움이 될 부분이 많을 것 같아서 번역을 시작해봅니다. 전체는 4개의 시리즈로 되어 있으며, 이 글은 그중 두번째인 Integer Optimization Guideline 입니다.

원문:  http://delphitools.info/OptimalCode/integer.htm



사용자 삽입 이미지

Delphi Optimization Guidelines


(1) 일반 가이드라인
(2) 정수 가이드라인
(3) 문자열 가이드라인
(4) 부동소수점 가이드라인




(2) 정수 최적화 가이드라인


스타일 가이드라인



가능한 한 32비트 변수를 사용하라

델파이 2 이후 버전에서 개발된 코드처럼 32비트 코드에서는 32비트 크기를 갖는 값이 모든 면에서 더 낫습니다. 16비트 변수(Word, ShortInt, WideChar)는 프로세서가 해당 변수를 작업하기 위해 일시적으로 16비트 모드로 넘어가기 때문에 특히 더 느립니다. 이 문제 때문에 16비트 값들로 작업할 때는 두 배의 시간을 소모하게 됩니다. 반면 8비트 변수들(Byte, SmallInt, Char)은 32비트 변수와 섞어 쓰지 않는 이상은 그렇게 많이 나쁘지 않습니다. 하지만 8비트 변수는 32비트 레지스터에서 나머지 공간을 0으로 초기화하는 추가적인 명령을 포함하게 되기는 합니다.

호환성 때문에 작은 크기의 타입을 사용해야만 한다면, 가능한 한 32비트로 변환해서 사용하고 필요한 때에 작은 크기로 다시 되돌리는 것이 낫습니다. 단지 32비트 변수에 대입하기만 하면 되죠.

부분범위를 피하라

전통적으로 파스칼이 가진 장점들 중 하나는 강력한 타입 체크를 한다는 것입니다. 이로 인해 특수한 부분범위(subrange) 타입을 만들 수 있고 열거형도 여기에 포함됩니다. 불행히도, 부분범위와 열거형은 성능 최적화를 시도할 때 문제를 일으킬 수 있습니다. 문제는, 부분범위나 열거형 변수의 내부적인 크기가 부분범위의 크기에 따른다는 것입니다. 예를 들면, 256개 미만의 요소수를 가진 열거형이나 0에서 255까지의 경계 값을 가진 부분범위는 byte로 저장됩니다. 이 문제로 내부적인 변수의 크기가 효율적으로 처리되지 않을 수 있습니다. 예를 들어, 다음과 같은 부분범위를 살펴봅시다.

 
type
  TYear = 1900..2000;

TYear 타입의 변수는 16비트 크기로 저장될 것입니다. 앞에서 설명한 것처럼 16비트 변수는 특히 느립니다.


최적화 테크닉


복잡한 표현식을 나누기 위해 임시 변수 추가를 고려하라

일반적으로, 모든 것을 단 하나의 표현식에 밀어넣으면 최적화에 가장 좋습니다만, 항상 그렇지는 않습니다. 어떤 경우에는 표현식이 너무나 복잡해서 컴파일러가 자체적으로 나누려고 할 수도 있습니다. 여러분이 직접 나눴을 때 컴파일러보다 더 나은 결과가 나오는 경우가 잦습니다. 직접 해보세요.

정수 곱셈

펜티엄 II 이전에는 정수 곱셈이 상당히 느렸습니다. 하지만 펜티엄 II 이후로는 정수 곱셈은 대부분의 다른 명령들처럼 한 사이클에 실행되도록 빨라졌습니다. 또한 덧셈이나 시프트 연산, LEA 명령(아래에서 설명)으로 같은 결과를 낼 수 있는 경우 컴파일러는 곱셈을 피합니다. 따라서, 곱셈을 사용할 것인지 아니면 다른 동등한 방법을 사용할 것인지를 선택할 때, 대상 PC의 프로세서를 고려할 필요가 있습니다.

변수를 여러 서수 상수들과의 비교하는 경우

이 주제는 어렵게 들리지만 사실 아래와 같은 문에 핵심이 있습니다.

if (x > = 0) and (x < = 10) then
  DoSomething;

if (((c > = 'a') and (c < = 'z')) or 
    ((c > = '0') and (c < = '9'))) then
  DoSomething;

위 두 케이스에는 단 하나의 변수가 있고 그것이 여러 상수들과 비교됩니다. 다음과 같이 표현하면 조금 더 효율적이고, 또 더 명료하게 됩니다.

if x in [0..10] then
  DoSomething;

if c in ['0'..'9', 'a'..'z'] then
  DoSomething;

효율성의 개선은 x가 범위 안에 있을지 아니면 바깥에 있을지의 가능성에 따라 달라집니다. 범위 안에 있을 가능성이 더 크다면, set 표기법이 더 낫습니다. set 표기법의 효율성은 부분범위의 갯수가 많아질 수록 더 높습니다. 하지만 set은 본질적으로 요소 수가 256개로 제한됩니다. 이 제한으로 정수 타입에 대해 이 방법을 사용하는 것은 제한적입니다. 전체 정수 범위에 대해서는 아래의 방법을 사용할 수 있습니다.


case x of
  0..10: DoSomething;
end;

case c of 
  'a'..'z',
  '0'..'9' : DoSomething;
end;

이 방법도 동일한 코드를 만들어냅니다만, 그다지 우아해보이지는 않지요.

고급 노트: 이 방법은 추가적인 CPU 레지스터를 이용할 수 있습니다.

movzx vs xor/mov

32비트보다 작은 값을 레지스터에 로드해야 하는 경우는 종종 있습니다. 이런 값들은 32비트인 레지스터 전체를 덮어쓰지 못하기 떄문에 먼저 레지스터를 0으로 초기화를 할 필요가 있습니다. 그 대신, 내장된 movzx (move with zero extend) 명령을 사용할 수도 있습니다. 펜티엄과 그 이전의 프로세서에서는 이 명령이 reg,reg/mov reg,{value} 보다 느렸었습니다. 하지만 펜티엄 II는 이 명령을 더 능률화하였으므로, xor/mov 명령을 사용하는 것보다 낫습니다. 컴파일러는 이 두가지 방법 중에서 상당히 복잡한 규칙들에 따라 선택을 합니다.

LEA 어셈블러 명령을 활용하라

LEA (Load Effective Address)라는 어셈블러 명령이 있는데, 두 개의 동작을 한번에 처리할 수 있습니다. 델파이에서 이 명령을 사용할 수 있는 방법은 한 가지 뿐인데, 배열 표기법을 사용하는 것입니다. 확실히 동작되도록 하기 위해서는 원하는 코드 다음에 배열 변수 자체가 한번 더 사용되어야 합니다. 예로서, 다음의 코드는 PChar 문자열의 길이를 계산하는 루틴(StrLen )의 일부입니다(첫번째 #0 문자의 위치를 찾습니다). 네 개의 문자가 한번에 처리됩니다. r2의 계산에서 q를 사용하는 것을 눈여겨 보십시오.

function StrLenPas(tStr: PChar): integer; 
var
  p: ^Cardinal; 
  q: PChar; 
  bytes, r1, r2: Cardinal; 
begin 
...
  q := PChar(p^);                  // 4개의 문자를 q에 읽어들임
  r2 := Cardinal(@q[-$01010101]);  // 각 문자에서 1을 뺌 (LEA가 이용됨) 
  r1 := Cardinal(q) xor $80808080; // 최상위 비트 체크 (q가 한번 더 사용됨)
  bytes := r1 and r2;              // 127보다 큰 문자들과 0을 구분함  inc(p); 
... 
end;


큰 정수 타입의 성능

longint 크기보다 더 큰 정수를 사용해야 하는 경우 몇가지 중 하나를 선택할 수 있습니다(int64, comp, double, extended). 이중 세가지는 실제로는 부동소수 타입입니다. 따라서 이들은 서로 완벽하게 대체가능한 것이 아닙니다. 비교적 최근에 추가된 타입인 int64만이 정수로서 완벽하게 처리됩니다. comp는 하이브리드 타입으로서, int64와 동일하게 8바이트 정수로서 저장되지만 모든 동작은 부동소수점 타입으로 수행됩니다. 볼랜드는 공식적으로 comp 타입을 obsolete로 지정했으며, 그 대신 int64를 추천하고 있습니다. 하지만 아래의 표에서 볼 수 있는 것처럼, comp는 어떤 상황에서는 성능 면에서 상당히 더 뛰어납니다. Extended와 double도 큰 정수를 다룰 때 사용될 수 있지만, 주기적인 반올림의 부족이 축적되어 답을 바뀌지 않도록 주의해야 합니다. 아래는 펜티엄 II CPU에서 일정 범위(0 < x < 2^63)의 랜덤 값들로 각 연산을 측정한 결과입니다. "Ovhd"는 각 타입으로 함수 호출을 하고 대입했을 때의 오버헤드를 의미합니다. LongInt는 비교를 위해서 추가되었습니다.
              ovhd     add      mult     div
     Longint    2      1        1        4.7
     Comp      40      4.3      4.4     34
     int64     19      2.6     26.2    804
     double    25      3.1      1.3     35.8
     extended  43      4.1      3.2     34.4

참고로, 펜티엄 II의 비순차 실행 기능 때문에 각각의 연산에 대한 정확한 시간 측정은 거의 불가능합니다. 따라서 위에서 보여드린 사이클 수는 근사치로만 이해해야 합니다.

int64의 극악스러운 나눗셈 성능을 제외하면, 명백한 최선의 선택은 없습니다. 세 가지 부동소수점 기반 타입들 중에 double이 가장 낫습니다만 약간 적은 자리수를 가지고 있습니다. int64는 comp보다 덧셈에서 더 낫습니다만 다른 연산에서는 나쁜 결과를 보여줍니다.

그럼 어떻게 해야 할까요. 가장 좋은 해답은 섞어 사용하는 것입니다. int64를 기본 타입으로 사용합니다. 나눗셈은 Int64A div Int64B 대신 trunc(Int64A/Int64B)를 사용하면 쉽게 처리할 수 있습니다. 최고의 성능을 얻는 것은 좀 더 복잡합니다. comp와 int64는 동일한 포맷을 가지고 있으므로 둘 사이의 변환이 자유롭습니다. 이것을 이용하면 정수 기반의 곱셈을 부동소수점 곱셈으로 바꿔 계산할 수 있습니다. 아래에서 그 예를 보시죠.

var
  A, B, C, D: int64;
  CA: comp absolute A;
  CC: comp absolute C;
begin
  // Result := A * B * C * D;  // 원래의 표현식
  Result := round(CA * B * C * D);  // 개선된 버전
end;

위에서 CA를 사용한 것은 부동소수점수로 연산을 하기 위한 것입니다. 전체 항을 부동소수점으로 계산하도록 강제하기 위해서는 부동소수점 타입은 하나만 있으면 됩니다. 하지만, 항이 여러개일 때는 각각에 대해 부동소수점 변수가 필요합니다.
Result := round(CA*B + CC*D)

나눗셈에 사용되었던 trunc 대신 round가 사용되었다는 점도 눈여겨 봅시다. 표현식 내에서 정수로 다시 변환하는 것도 가능하겠지만, round 내에서 덧셈을 두세번씩 하는 경우가 아니라면 일반적으로 속도를 증가시키지 못합니다.
크리에이티브 커먼즈 라이센스
Creative Commons License
2012/05/13 20:34 2012/05/13 20:34

trackback :: http://blog.devquest.co.kr/imp/trackback/262

  1. 델파이 최적화 가이드라인 (2) - 정수 가이드라인

    Tracked from 볼랜드포럼 2012/05/14 07:08  delete

    <h1>(2) 정수 최적화 가이드라인</h1> <h2>스타일 가이드라인</h2> <h3>가능한 한 32비트 변수를 사용하라</h3> 델파이 2 이후 버전에서 개발된 코드처럼 32비트 코드에서는 32비트 크기를 갖는 값이 모든 면에서 더 낫습니다. 16비트 변수(Word, ShortInt, WideChar)

아래 글은 원래 2002년에  Robert Lee의 홈페이지인 http://www.optimalcode.com 라는 사이트에 실렸던 "Delphi Optimization Guideline"이라는 글의 일부입니다. 지금 이 사이트는 없어진 상태이고 원래의 필자도 전혀 연락이 안되는 상태입니다. 하지만 최근에 예전의 컨텐츠들을 http://delphitools.info 에서 되살렸습니다. 델파이 개발자분들께 도움이 될 부분이 많을 것 같아서 번역을 시작해봅니다.
전체는 4개의 시리즈로 되어 있으며, 이 글은 그중 첫번째인 General Guideline 입니다. 개인적으로 볼 때 이 필자의 글은 애매하거나 너무 어렵거나 딱 적당하지 않은 단어를 쓴 경우가 너무 많아서 번역에 애를 많이 먹었습니다. 그럼에도 아직도 완전히 명확하지 않은 부분들이 꽤 남아있는 점을 양해 부탁드립니다.

원문:  http://delphitools.info/OptimalCode/general.htm


사용자 삽입 이미지

Delphi Optimization Guidelines


여기에서 제시되는 가이드라인은 상당히 일반적인 내용이지만, 델파이 측면에서만 초점을 두었습니다. 또 최근의 인텔 CPU들(펜티엄 및 펜티엄 II)에 초점을 맞추었습니다. AMD K6-2 등의 다른 CPU들도 좋은 선택이며 여기서 거론되는 최적화로 좋은 결과를 낼 수 있을 것입니다. 하지만 다른 CPU들은 공개적으로 문서화가 잘 되어 있지 않아 확실하지는 않습니다.

이 가이드라인들은 두 부분으로 나뉘어집니다. 1) 코딩 스타일과 2) 특정 최적화 테크닉 입니다. 코딩을 진행하는 데는 몇가지 방법들이 있을 것입니다. 이 방법들 중 일부는 일반적으로 더 빠른 코드를 만들어냅니다. 이런 코딩을 소극적인 최적화라고 부를 수 있을 것입니다. 이런 부분을 설명한 부분이 "코딩 스타일" 파트입니다. 반대로, 성능 병목을 갖고 있는 특정 루틴들의 경우 이런 방법만으로 충분하지 않습니다. 이런 경우들에는 루틴들을 적극적으로 최적화할 필요가 있습니다. "최적화 테크닉" 파트가 이런 경우에 대한 것입니다. 또한, 전체 가이드라인은 주제에 따라 네 개의 아티클로 나눠져 있습니다.


(1) 일반 가이드라인
(2) 정수 가이드라인
(3) 문자열 가이드라인
(4) 부동소수점 가이드라인



(1) 일반 최적화 가이드라인


스타일 가이드라인



코딩 스타일 vs. 효율성

'최적화'란 코드 자체의 속도 뿐만 아니라 여러분의 코드를 만들고 디버그하는 속도와도 관련된 문제입니다. 이것은 빠르지만 이해할 수 없는 코드를 만드는 것은 스스로 도움이 되지 않는다는 것을 의미합니다. 다행히도, 델파이에서 최적화된 코드를 만들었을 때 코드가 지저분해지는 경우는 흔치 않습니다. 사실, 최적화된 코드가 곧 우아한 코드인 경우가 많습니다. 덧붙여서, 동일한 애플리케이션 내에서 종종 같은 종류의 테크닉들이 사용되는 경향이 있습니다. 따라서, 여러분은 근본적으로 최고의 성능을 내는 방식으로 코딩 스타일을 정할 수 있습니다.

단순하게 유지하라

델파이 옵티마이저를 고려할 때, 복잡성은 치명적입니다. 루틴들은 단순하게 만들어야 합니다(5~8개 정도 이상의 변수가 동작하 않도록). 단일 루프 내에서 너무 많은 작업을 하지 마십시오. 루프에서 너무 많은 작업을 하면 매번 반복될 때마다 변수 주소들, 배열 인덱스들과 같은 것들이 다시 로드됩니다. 루프 자체의 오버헤드는 상당히 적기 때문에, 복잡한 루프를 여러 개의 루프로 나누거나 가장 안쪽의 루프 한두 개를 별도의 루프로 빼내는 것이 더 효율적인 경우가 종종 있습니다. 제대로만 하면 여러분의 코드의 가독성(readability)까지도 높여줄 수도 있습니다.

로컬 변수를 선호하라

지역 변수들은 루틴에 전달되는 파라미터들에 더해 루틴에서 선언되는 변수입니다. 지역 변수들만이 레지스터 변수로 변환이 가능하며, 레지스터 변수들은 모두 동일하게 빠릅니다. 따라서, 데이터를 사용하기 전에 지역 변수로 복사하는 것이 나은 경우가 종종 있습니다. 일반적으로 변수가 루프 내에서 사용될 경우에는 가장 유리합니다. 복사된 데이터를 더 빠르게 재사용할 수 있으므로 데이터를 복사하는 오버헤드는 충분히 상쇄됩니다. 이 최적화 방법은 타이트한 루프 안에서 클래스 멤버들이 사용될 경우 특히 유용합니다. 델파이는 루프 내에서 포인터와 클래스 멤버들이 사용되기 직전에 가서야 읽어들이는 경향이 있는데, 이것은 많은 불필요한 오버헤드를 발생시킵니다.

이 규칙에는 한가지 예외가 있습니다. 단순 타입의 요소들을 가진 배열의 경우입니다. 고정된 크기의 상수 데이터의 배열이 있다면, 전역 변수로 만들면 연산 중에 레지스터를 아낄 수 있습니다. 레지스터 하나를 아끼는 것은 큰 가치가 없으므로, 이 방법은 변환 테이블 등의 상수 구조에만 사용해야합니다.

파라미터 수를 적게 하라

작지만 대단히 자주 사용되는 루틴들은 3개 이상의 파라미터를 가져선 안됩니다. 3개는 레지스터를 통해 전달할 수 있는 최대치입니다. 이 규칙을 따르면 레지스터의 사용을 극대화할 수 있고 델파이 옵티마이저가 여러분의 코드를 더 개선할 수 있게 됩니다. 클래스의 메소드는 숨겨진 파라미터 Self가 있어 항상 내부적으로 전달되므로 이런 경우 2개의 파라미터만 남는다는 것도 알아두십시오.

중첩 루틴을 사용하지 말라

중첨 루틴(nested routine; 다른 루틴 내의 루틴, "로컬 프로시저"라고도 부름)은 바깥쪽 루틴의 변수가 안쪽 루틴에서도 보일 수 있도록 하기 위해 특별한 스택 조작을 필요로 합니다. 이것은 상당한 오버헤드를 일으킵니다. 중첩 대신에 프로시저를 유닛 스코프 레벨로 옮기고 필요한 변수들을 전달하도록 바꿉니다. 바이 레퍼런스(by reference) 전달이 필요하다면 var 키워드를 이용하거나 변수를 유닛 스코프의 전역 변수로 바꾸면 됩니다.

포인터 변수

중요한 테크닉 하나는 포인터를 활용하는 것입니다. 많은 프로그래머들이 액세스 바이올레이션, 메모리 누수, 다른 저수준 문제들의 잠재적인 위험을 이유로 포인터 사용을 꺼립니다. 하지만, 포인터는 델파이에서 코드를 최적화하는 데 있어 중요한 도구입니다. 다행히도, 이것은 여러분의 모든 데이터 참조를 포인터로 바꿔야 한다는 의미는 아닙니다. 여러분의 데이터에 대한 임시 참조로서 포인터를 활용하라는 것입니다. 이런 임시 변수는 일반적으로 레지스터 변수로 최적화됩니다. 결과적으로, 여러분은 실제로 머신 코드를 추가하지는 않지만 컴파일러가 중간 주소를 계속 유지할 단서를 제공하게 됩니다. 여러분은 포인터를 with 문을 사용하던 것과 아주 비슷한 방식으로 사용할 수 있습니다. 그것은, 복잡한 데이터 구조의 복잡하거나 헝크러진 주소 지정을 단순화하기 위해 포인터를 사용하라는 것입니다. with 문의 경우, 이런 작업은 컴파일러가 내부적으로 하는 일과 정확하게 일치합니다. 예를 들자면,

with Structure1.Structure2[i] do
begin
  ...
end;

위와 같은 코드는, 컴파일러 레벨에서는 다음과 같이 됩니다.

InnerStructure := Structure1.Structure2[i];   // 클래스인 경우
InnerStructure := @Structure1.Structure2[i];  // 클래스가 아닌 경우
begin
  ...  // 이 블럭 내부의 코드는 InnerStructure를 참조함
end;

링크드 리스트 vs. 배열

링크드 리스트와 배열 사이에서 적당한 방법을 찾아내는 것은 설계에 있어 고전적인 문제입니다. 구형 컴퓨터들(펜티엄과 그 이전)에서는 정수 곱셈은 느린 작업이었습니다. 배열을 액세스할 때 곱셈이 핵심이기 때문에, 일부 케이스들에서는 링크드 리스트가 성능상 유리하기도 했습니다. 랜덤 액세스냐 순차 액세스냐? 여러분이 정말로 랜덤 액세스가 필요하다면 5개 가량 이상의 요소를 가진 모든 데이터에서 배열이 나은 선택입니다(이것은 실험에 근거한 경험 규칙입니다). 순차 액세스이거나 유사 순차 액세스인 경우 짧은 답은, 요소 타입이 단순 타입일 경우 배열이, 더 큰 타입일 경우에는 링크드 리스트가 낫다는 것입니다. 펜티엄 II 이후에서의 곱셈은 이전보다 아주 아주 빨라졌습니다. 따라서, 배열 액세스가 항상 더 빠릅니다.

배열의 종류

델파이에서 배열은 여러 종류가 있습니다. 정적, 동적, 포인터, 오픈 배열입니다. 정적 배열은 전통적인 파스칼 배열 타입입니다(A: array[0..100] of Byte). 동적 배열은 델파이 4에서 도입된 것입니다(A: array of Byte). 포인터 배열은 정적 배열에 대한 포인터이지만, 실제 요소들의 갯수는 배열의 경계와 일치하지 않을 수도 있습니다. 마지막으로 오픈 배열은 동적 배열과 비슷해보이지만 오직 루틴의 파라미터로만 사용되는 것입니다. 이 각각의 배열들의 내부 구현은 상당히 크게 다릅니다. 효율성의 관점에서는 정적 배열과 포인터 배열이 최고의 선택이고 다음으로는 오픈 배열과 동적 배열입니다. 하지만, 정적 배열은 종종 너무 유연성이 없고 포인터 배열은 관리 문제들이 까다롭습니다. 다행히, 이 다양한 배열 타입들은 서로 변환 가능합니다. 고정된 크기가 없는 배열에 대해서 현재로서 최고의 선택은, 동적 배열로 관리하고 필요할 경우 포인터 베열로 변환하는 것입니다.

동적 배열은 변수가 실제로는 배열의 첫번째 요소에 대한 포인터라는 점에서 큰 문자열(AnsiString)과 아주 비슷합니다. 그러므로, 동적 배열을 포인터 배열로 변환하는 것은 실제로는 단순한 대입일 뿐입니다. 동적 배열의 길이(크기)는 첫번째 요소의 바로 앞에 저장되며, 동적 배열에 대해 High나 Length를 사용하면 배열로부터 길이를 뽑아내는 "컴파일러 매직" 대신 실제로 함수 호출이 일어납니다(High를 사용하면 Length가 호출됩니다). 따라서, 반복적으로 이런 함수들을 사용해서 배열의 크기를 알아내려고 하지 마십시오. 루틴에서 한번 알아낸 후 저장해둡시다.

동적 배열과 오픈 배열은 모두 파라미터로 사용할 때 const나 var로 지정하지 않으면 대단히 많은 오버헤드를 일으킵니다. 클래스 파라미터에서처럼, 동적 배열에 const를 지정하는 것은 그 내용의 변경을 막는 것이 아니라 전체 배열에 대한 변경만을 막는다는 것을 기억해둡시다.

다양한 배열 타입들을 변환하는 예제로 마무리합니다.

type
  TDoubleArray = array of Double;

  TStaticDoubleArray = array[0..0] of Double;
  PDoubleArray = ^TStaticDoubleArray;
  
function Sum(const X: TDoubleArray): Double;
var
  P: PDoubleArray;
  i: Integer;
begin
  P := Pointer(X);
  Result:=0;
  for i := 0 to Length(X)-1 do
    Result := Result + P[i];
end;

예외(exception)

코드 부분을 탈출하기 위한 목적만으로, 혹은 입력 오류에 대해 무조건 catch하기 위해 예외를 사용하지 마십시오. 이들은 오버헤드를 발생시키며, try..finally 블럭과 예외 자체를 throw하는 것도 마찬가지입니다. 통상적이지 않은 흐름 제어를 위해서는 break, continue, exit를 사용하고, 입력 내용에 대한 검증은 가능한 일찍, 하지만 루프의 바깥에서 해야 합니다.

absolute보다는 타입 캐스트를 사용하라

타입 캐스트를 피하기 위해 사용되는 테크닉들 중 하나는 absolute 키워드를 사용하여 한 변수를 다른 타입의 변수에 덮어쓰는 것입니다. 하지만 이 방법은 변수가 빠른 레지스터 변수가 되는 것을 막습니다. 타입 캐스트를 하고 원래의 값을 새 변수에 저장하는 것이 낫습니다. 예를 들면,

procedure DoSomething(s: PChar);
var
  ByteArray: PByteArray absolute s;
begin
  ...


위의 코드는 다음과 같이 바꿉니다.
procedure DoSomething(s: PChar);
var
  ByteArray: PByteArray;
begin
  ByteArray := PByteArray(s);
  ...

집합 다루기

Include와 Exclude라는 두 가지 컴파일러 매직 함수가 있는데, 단일 요소를 집합(set)에 추가하거나 제거할 때 아주 효율적입니다. 그러므로, "s := s + [a];" 같은 문장 대신 이런 함수를 사용해야 합니다. 사실, 하나의 요소 뿐만 아니라 몇번 정도는 Include나 Exclude를 반복적으로 호출하는 것이 앞서의 코드와 같이 + 연산자를 쓰는 것보다는 더 효율적입니다.

펜티엄 II 병목

문득, 여기서 제시한 테크닉들 중 많은 것들이 펜티엄 II 프로세서 병목에 기반하고 있지만 이 프로세서가 어떻게 동작하는지 설명한 적이 없다는 것을 깨달았습니다. 인텔의 문서들과 Agner Fogs Pentium Optimization Guide에서 길고 상세한 설명을 찾아볼 수 있습니다만, 여기서 저는 델파이의 컴파일러 결과물에 관련된 부분만 가지고 짧게 설명하겠습니다. 이 프로세스에 대한 이해는 최적화가 필요한 코드가 어떤 것인지 판단하는 데 도움을 줄 것입니다.

우선, 펜티엄 II는 비순차적 실행 기능을 갖춘 수퍼스칼라 파이프라인 프로세서입니다. 기본적으로 이것은 각 명령어들이 단계별로 실행되고 몇개의 서로 다른 채널들 중에서 하나로 진행하게 됩니다. 특히, 각 명령어는 로드 및 실행 단계들 사이에서 "대기실" 역할을 하는 비순차적 버퍼에서 로드되고, 실행되고, 리타이어(retire)되어야 합니다. 이것은 간단할 것 같지만 다중 채널 부분이 추가되면 복잡해지기 시작합니다. 모든 채널이 모든 명령을 처리할 수 있는 것은 아니기 때문입니다. 프로세서의 3개의 로딩 채널이 있으며, 그중 하나는 어떤 명령어이든 처리할 수 있지만 나머지 2개는 단순한 명령어만을 처리할 수 있습니다. 5개의 실행 채널(인텔에서는 포트라고 부름)이 있는데, 하나는 범용의 정수, 다른 하나는 범용 정수 및 실수, 세번째는 주소 연산, 네번째와 다섯번째는 데이터를 로드하고 저장합니다. 리타이어 단계도 3개의 채널을 가지고 있습니다. 여기에는 지연시간의 이슈도 있습니다. 실행에 1 사이클 이상이 걸리는 명령어들이 여럿 있기 때문입니다.

그럼, 이런 것들이 의미하는 게 뭘까요? 병목은 수많은 서로 다른 곳에서 발생할 수 있다는 것입니다. 기본적으로, 특정 유닛을 필요로 하는 명령들을 너무 많이 몰려오면 어떤 단계의 어떤 채널이든 병목이 될 수 있습니다. 따라서, 이론적으로 CPU가 사이클당 3개의 명령어를 처리할 수 있다고 하더라도, 실제로는 현재 실행되고 있는 명령어들의 조합에 따라 2개나 1개, 심지어는 그 이하로도 제한될 수 있다는 것입니다. 비순차적 "대기실"은 이런 상황에서 특정 포트를 기다리고 있는 명령어들 중 현재 실행중인 작업에 영향을 받지 않는 명령어들이 우회해서 다른 포트에서 실행될 수 있도록 해줍니다. 이런 방식은 특정 포트에 일시적인 백업이 있을 경우 작은 병목에 도움이 됩니다만, 대규모 백업에는 아무런 도움이 되지 않습니다. 예를 들어, 복잡한 수학 식에 대한 루프의 경우처럼 대량의 부동소수점 연산은 부동소수 계산이 가능한 포트가 하나 뿐이라는 제한에 부딛히게 됩니다. 따라서 처리 속도는 사이클당 1 명령어로 떨어지게 됩니다. 루프 자체와 관련된 오버헤드(루프 변수를 증가하고 점프하는) 명령어도 있는데, 이들 명령어들은 백업된 부동소수점 포트에 맞기 때문에 기본적으로 0 사이클을 소모합니다.

파스칼의 관점에서 보면, 타이트하고 반복적인 작업이 작업 전체에서 단 하나의 상태에서 제한될 수 있다는 것입니다. 그것은 위에서 예로 든 것처럼 부동소수점일 수도 있고, 정수 연산이나 메모리 주소 지정일 수도 있습니다. 어떤 경우이든, 효과가 있을 수 있는 유일한 최적화는 주요 측면을 쫓아가는 것입니다. 부수적인 코드를 쳐내려고 하는 것은 효과가 없습니다.

for 문의 내부

for 문을 구현하는 것은 컴파일러가 처리해야 하는 작업들 중 복잡한 축에 속합니다. 컴파일러는 정수 곱셈을 피하기 위해 많은 희생을 하는데, 이 정수 곱셈은 펜티엄 II 이전에는 상당히 느린 작업이었습니다. 이런 관계로 for 루프는 다음과 같은 수도코드로 분해됩니다.

  for i := m to n do
    A[i] := A[i] + B[i];


위와 같은 원래의 루프는 아래와 같이 됩니다.
  PA := @A[m];
  PB := @B[m];
  counter := m - n + 1;
  if counter > 0 then
    repeat
      PA^ := PA^ + PB^
      inc(PA);
      inc(PB);
      dec(Counter);
    until counter = 0;


다른 설정도 있지만 이런 방식이 가장 흔하며, 또한 이런 방식이 문제를 일으키게 됩니다. 문제는 변수 i가 분해된 버전의 코드 어디서도 나타나지 않는다는 것 때문입니다. 하지만, 이 코드를 디버거에서 스텝 단위로 디버깅하면서 i 변수의 값을 추적하면 i에 가장 가까운 값인 counter를 보여주게 될 것입니다. 이것은 많은 프로그래머들이 자신의 루프가 역방향으로 실행되고 있다고 불평하게 만들었습니다. 물론 사실은 그렇지 않습니다. 단지 디버거가 잘못된 정보를 알려주고 있는 것입니다.

위 예제는 또한 for 루프에서 발생하는 상당한 오버헤드를 보여줍니다. 세 변수가 각각의 반복마다 증가되어야 하며, 초기화 코드들도 있습니다. 어떤 경우에는 이 오버헤드가 적어지기도 합니다. 예를 들어 m과 n이 컴파일 타임 상수일 경우, 또는m=0 이고 A와 B가 코드에서 더는 사용되지 않는 포인터일 경우, 오버헤드 코드는 줄어듭니다.

인터페이스

여기서는 인터페이스 사용의 영향에 대한 기본적인 내용들을 살펴보겠습니다. 기본적으로, 인터페이스는 스트링과 클래스를 섞어놓은 것과 비슷합니다. 스트링이 생성하거나 복사할 때마다 레퍼런스 카운트되는 것처럼, 인터페이스에서도 인터페이스 변수가 스코프 바깥으로 나갈 때마다 일정한 오버헤드가 발생합니다. 따라서, 인터페이스 변수는 객체 변수를 다루는 방식이 아니라 스트링 변수를 다루는 방식과 비슷하게 다루어야 합니다. (가능하면 const로 전달하고, 너무 많은 임시 변수를 사용하지 않도록 주의하는 등) 내부적으로, 인터페이스는 버추얼 메소드만 가진 객체와 비슷하게 동작하지만 더 나쁩니다. 사실 두 단계의 간접 참조가 일어납니다. 따라서 그에 맞게 다루십시오.

인터페이스 관련의 다른 노트.
Hallvard Vassbotn: 모든 전역 인터페이스 변수에 대해, 컴파일러는 자동으로 첫번째 변수의 주소를 가지는 전역 변수를 하나 더 추가합니다. 외부 유닛에서 그 변수를 액세스하면 이 암시적인 포인터 변수를 통해 접근하게 됩니다. 이렇게 한 이유는 패키지를 지원하기 위한 것이지만 패키지를 사용하지 않는 경우에도 동일하게 동작합니다. (더 자세히 알아보려면 The Delphi Magazine 이슈 43 아티클을 참고하십시오)


최적화 테크닉


열린 마음을 유지하라

최적화는 탑-다운 방식으로 접근하는 것이 가장 좋은 접근 방식입니다. 최적화에서 가장 강력한 컨셉은, "답을 알아내기 어려울 정도로 너무 오래 걸리면, 질문을 바꿔라" 입니다. 최고의 성능 개선은 설계와 알고리즘 레벨의 변경에서 나옵니다. 코딩의 세세한 부분까지 내려가면 여러분의 선택 가능한 방법은 상당히 제한되게 됩니다. 불행히도, 이런 하이 레벨 최적화를 멋지게 규칙들로 정리해내기는 상당히 어렵습니다. 그렇기는 해도, 성능 개선이 필요한 경우 가장 먼저 해야 할 일은 전체 문제를 모두 준비해놓고 가장 위에서부터 아래로 살펴가며 최적화를 해나가는 것입니다.

코드의 시간을 측정하라

코드 시간 측정은 보통 "프로파일링"이라고 부릅니다. 여러분의 코드의 성능을 개선시키고 싶다면, 먼저 그 성능이 무엇인지 정확히 알아야 할 필요가 있습니다. 그리고 여러분의 코드의 각각의 변경을 다시 측정해야 합니다. 애플리케이션이 정확하게 어디에서 시간을 소모하고 있는지 분석해서 결정하기 전에는 성능을 개선하려고 코드를 만지작거리지 마십시오. 이건 아무리 강조해도 충분치 않습니다.

코드의 배치

여러분의 코드의 정확한 위치와 실행 모듈 내에서의 배치가 실행 시간에 영향을 미칠 수 있다는 것을 알아두십시오. 그 이유는 최적이 아닌 주소 배치로 점프할 때 불리한 경우가 있기 때문입니다. 이런 코드 배치는 링커 작업이기 때문에 여러분이 이에 대해 잘 알고 있지 못하다면 여기에 영향을 주기 위해 할 수 있는 것은 매우 적습니다. 공간을 비워두는 코드를 추가하는 것은 가능하지만, 32비트 델파이는 4바이트(DWord) 경계에만 배치를 하기 때문에 여러분이 배치를 위해 노력을 하더라도 그것이 계속 유지되기는 어렵습니다. 따라서, 다음에 현재 루틴 위쪽의 루틴이나 유닛을 변경하면 여러분의 코드가 이동될 수 있습니다. 이 문제는 타이트한 루프에서는 최대 30%까지 속도 불이익을 일으킬 수 있지만, 더 일반적인 루프에서는 상당히 적습니다. 또한 이 문제는 전혀 무해한 코드를 이동시켰는데도 성능에 영향을 미치게 되므로 코드 시간 측정과 최적화를 어렵게 만든다는 점을 알아둘 필요가 있습니다. 결론적으로, 여러분이 성능을 높일 것으로 알고 있는 변경을 했는데 기대한 결과가 나오지 않았다면, 코드 배치의 이동 때문에 여러분 코드의 개선을 덮어버렸을 가능성이 있습니다.

CPU 윈도우를 활용하라

CPU 윈도우를 이용하기 위해 어셈블러 프로그래머가 될 필요는 없습니다. 아주 최소한, CPU 윈도우는 여러분에게 각 코드 문장들과 관계된 내부적인 복잡도에 대한 아이디어를 줄 수 있습니다. 단순히 특정 작업에서 생성된 명령어의 수를 세어보기만 해도 특정 최적화 테크닉의 효과를 추정할 수 있는 경우가 아주 흔합니다. 예를 들어, 루프 안에서 EBP 레지스터에 대한 참조가 아주 많다면(mov eax,[ebp-$04] 와 같은 명령), 변수들이 끊임없이 다시 로드되고 있다는 것을 의미합니다. 이런 리로드는 불필요하며 따라서 최적화의 최우선 대상이 됩니다.

델파이 4 이후의 버전에는 CPU 윈도우가 메인 메뉴에 있어 쉽게 찾을 수 있습니다. 하지만 델파이 2와 3 버전에도 숨겨진 CPU 윈도우가 있습니다. 이 숨겨진 CPU 윈도우를 사용하려면 레지스트리 에디터를 이용하여 레지스트리에 새 항목을 추가해야 합니다.

[HKEY_CURRENT_USER\Software\Borland\Delphi\2.0\Debugging]
"EnableCPU"="1"

[HKEY_CURRENT_USER\Software\Borland\Delphi\3.0\Debugging]
"EnableCPU"="1"

작은 루프를 언롤하라

루프 언롤(unroll)은 고전적인 최적화 테크닉이며, 델파이에서 아주 쉽게 할 수 있습니다. 하지만 이 방법은 상당히 작은 루프에서만 할 만한 가치가 있습니다. 언롤은 본질적으로 여러번의 반복 작업을 한번으로 줄이는 것입니다. 이것은 상대적인 루프 오버헤드를 줄여줍니다. 펜티엄 II CPU의 분기 예측 메커니즘은 매우 타이트한 루프에서는 그다지 잘 동작하지 않기 때문에 언롤을 하는 것이 유리할 수 있습니다.

델파이에서 언롤을 하는 가장 좋은 방법은 보통 while 루프에 하는 것입니다. 예를 들면,

i := 0;
while i < Count do
begin
  Data[i] := Data[i] + 1;
  Inc(i);
end;


위와 같은 코드를 아래와 같이 바꾸면 됩니다.
i := 0;
while i < Count do
begin
  Data[i] := Data[i] + 1;
  Data[i+1] := Data[i+1] + 1;
  Inc(i, 2);
end;


루프 언롤에서 주의할 점은, Count가 배수 2로 나눠지지 않을 경우를 고려해야 한다는 것입니다. 이 문제에 대해 다음과 같은 방식으로 처리할 수 있습니다.

i := 0;
if Odd(Count) then
begin
  Data[i] := Data[i] + 1;
  Inc(i);
end;

while i < Count do
begin
  Data[i] := Data[i] + 1;
  Data[i+1] := Data[i+1] + 1;
  Inc(i, 2);
end;

여러분은 원하는 만큼의 배수로 언롤을 할 수 있지만, 언롤링의 배수를 계속 높였을 때 코드가 점점 더 복잡해지면서 일어나는 한계수익 체감의 이슈 때문에, 4 배수 이상으로 언롤링을 하는 경우는 그다지 흔하지 않습니다.

루프 내의 조건을 제거하라

루프 내의 if 문에서 조건식이 루프 인덱스에 기반하는 경우가 흔히 있습니다. 이것은 루프를 언롤하거나 한 루프를 두 개의 루프로 나눔으로써 없앨 수 있는 경우가 많습니다. 전자의 예로는문장이 매 2회마다 실행되어야 하는 경우가 있습니다. 또한 후자의 예로는 문장이 특정 반복 번째에서 실행되는 경우입니다.

루프 조건문의 수를 줄여라

어떤 조건이 true이고 루프 인덱스가 어떤 값보다 작은 동안 반복하는 루프는 흔히 볼 수 있는 코딩 구조입니다. 루프가 작다면(루프가 루프 인덱스를 증가시키는 코드만으로 구성된 경우처럼), 루프의 실행 시간의 대부분은 루프 조건문을 확인하는 데 소모됩니다. 어떤 때는 한 조건이 발생할 때 다른 조건도 충족하도록 하여 이런 조건의 수를 줄이는 것이 가능한 경우도 있습니다. 스트링 내에서 특정 문자를 찾는 예를 살펴봅시다.

i := 1;
l := Length(s);

while ((i <= l) and (s[i] <> c)) do
  Inc(i);
...

스트링의 가장 마지막에 검사할 문자를 두면 조건식 두 개를 하나로 합칠 수 있습니다.

i := 1;
l := Length(s);

lastc := s[l];
s[l] := c;
while s[i] <> c do
  Inc(i);
s[l] := lastc;
...

이렇게 코드를 바꾸면 속도가 거의 2배 가까이 개선됩니다. 이 최적화를 위해서는 데이터의 마지막에 빈 공간이 있는지에 대한 깊은 고려가 필요합니다. 스트링과 PChar에는 항상 마지막에 사용할 수 있는  null이 있습니다. 또한 이 테크닉은 스캔되는 데이터를 변경시키기 때문에 멀티 쓰레드 환경에서는 부작용이 발생할 수도 있습니다. 이 테크닉은 부분 반복을 필요로 하는 것과 관련된 문제들을 간단하게 만들어주기 때문에 언롤과도 잘 어울립니다. 이 테크닉에 대한 더 자세한 예는 FindMax 예제를 참고하시기 바랍니다.

점프를 없애라

이 테크닉은 오래되어 현재는 큰 의미가 없습니다만, 여전히 약간의 참고를 할 필요는 있습니다. 원래의 이유(점프 자체가 너무 오래 걸린다는)는 현재는 더 이상 문제가 아닙니다. 현재의 문제는 코드 배치와 분기 예측과 더 관계되어 있습니다. 배치의 측면은, 점프를 하지 않으면 배치의 문제도 발생하지 않는다는 것입니다. 분기 예측 측면은, 한번 지나가지 않으면 아예 예측을 하려고 시도조차 하지 않는다는 것입니다.

break, exit, continue를 활용하라

이들 흐름 제어문들을 많이 쓰면 종종 "잘못된 프로그래밍"으로 비웃음을 받습니다. 하지만 이런 흐름 제어문들도 쓰일 필요가 있을 때가 있고, 특히 성능 최적화에 더욱 그렇습니다. 이런 문들이 필요한 경우는 보통 루프의 중간에서 어떤 조건이 결정되지 않을 때입니다. 이럴 때 보통 boolean 변수를 추가하고 조건을 확인하는 코드를 넣어서 이런 상황을 피할 수 있습니다. 하지만 이 방식을 썼을 때의 추가 시간 비용은 코드를 간단하게 만드는 대신 더 복잡하게 만드는 경우가 많습니다.

어셈블러 사용을 자제하라

펜티엄 II 이상의 CPU에서는 성능 개선을 위해 어셈블러를 사용하려고 시도하지 마십시오. 여기에는 논란이 있긴 하지만, 보통은 상당히 좋은 경험 법칙입니다. 펜티엄 II 이후 CPU의 비순차 실행 기능은 알고리즘을 어셈블러로 기술했을 때의 장점을 대부분 제거해버립니다. 테스트 결과 저는 어셈블러와 최적화 코딩을 한 파스칼 코드의 성능 차이가 10% 이상 차이가 나는 경우를 거의 보지 못했습니다. 물론 항상 예외는 있고(예를 들면 알파 블렌딩 코드처럼), 어셈블러가 파스칼보다 확실히 더 깔끔한 경우도 있지만, 중요한 점은 코드가 너무 느리다고 느꼈다고 해서 바로 어셈블러로 직행해서는 안된다는 것입니다.

다른 한편으로, 펜티엄 CPU는 어셈블러 코드를 제대로 활용하는 경우가 많습니다. 하지만 펜티엄 프로세서에 대해 최적화된 코드가 다른 프로세서에서 덜 최적화 결과를 보여주는 경우가 적지 않습니다. 특히 이런 경우는 부동소수점 코드에서도 마찬가지입니다. 어셈블러 방식을 고수하려 한다면, 먼저 Agner Fog의 어셈블러 매뉴얼(http://www.agner.org/optimize/)을 제대로 공부해본 후 하는 것이 좋겠습니다.

for 루프 vs. while 루프

고정된 횟수의 반복을 하는 루프는 for 혹은 while 어느 쪽으로도 구현할 수 있습니다. 통상적으로, for 루프가 더 많이 선택됩니다. 하지만 for 루프의 기반 구현은 일부 사례에서 while 루프만큼 휴율적이지 못합니다(위의 "for 문의 내부"를 참고). 루프의 내용에 배열과 관계가 없거나 혹은 1, 2, 4, 8 바이트의 크기를 가지는 요소들을 가진 1차원 배열과만 관계가 있을 경우, while 루프는 같은 내용의 for 루프보다 더 효율적이고 깔끔합니다. 반대로, 다차원 배열이나 위에서 나열한 사이즈와 다른 크기의 요소 크기를 가지는 배열을 다루는 코드는 for 루프의 효율이 더 높습니다. for 루프와 while 루프를 서로 변환하는 것이 가능한 경우가 많고, 보통은 포인터를 이용하면 됩니다. 이 접근 방법은 코드의 효율성을 높일 가능성이 높습니다.

덧붙여서, while 루프를 사용하면 복잡도를 낮출 수 있습니다. 따라서 루틴 분할을 피하기 위해 for 루프를 대신할 수도 있습니다. 이런 경우의 예는 가우스 소거법의 로우 리덕션(row reduction)입니다. for 루프의 최적화된 설정은 가장 안쪽의 두 루프를 별개의 루틴으로 뽑아내는 것입니다. 하지만 while 루프에서는 세 루프 모두 그대로 유지할 수 있습니다.

물론 예외도 있습니다. 루프 내에서 인덱스가  전혀 사용되지 않는다면 보통 for가 더 나은 선택입니다. 또 루프의 시작-끝이 모두 컴파일 타임 상수일 경우에도 for를 시도해보십시오.

while 루프가 최대한의 효율을 내려면, 루프의 조건이 최대한 단순해야 한다는 점을 알아두십시오. 이것은 while 루프는 for 루프와 달리 반복 횟수에 대한 모든 계산을 while 루프의 바깥으로 내보내고 임시 변수를 사용해야 한다는 것을 의미합니다.

대규모 메모리 요구 문제

펜티엄 II CPU에서는 최적화에 있어 캐시나 메모리 병목이 가장 큰 문제가 되는 경우가 자주 있으며, 특히 다루는 데이터가 클 경우엔 더 그렇습니다. 이런 경우에는 완전히 다른 전략이 필요합니다. 메모리 소요량을 줄이고 데이터 통과의 수를 줄이는 데에 초점을 맞추십시오. 이것은 이 가이드라인에서 제시된 다른 제안들 몇가지와 상반될 수도 있지만, 다른 구현과 실험해보고 프로파일링 해봄으로써 어떤 것이 더 결정적인 요소인지 결정하는 것이 필요합니다. 캐시나 메모리 병목이 지배적인 요인인지 알아챌 수 있는 좋은 신호는, 분명하게 코드를 개선했는데도 성능이 개선되지 않은 경우입니다.

case 문 최적화

case 문은 다음과 같이 구현됩니다. 먼저, 컴파일러는 나열된 값들과 범위들을 정렬합니다. 이것은 case 문 내에서 각 값들의 위치는 무관하다는 의미입니다. 다음으로, 컴파일러는 일종의 2진 검색 트리 전략과 각 케이스들을 테스트하기 위한 점프 테이블을 사용합니다. 점프 테이블과 비교 트리 사이의 결정은 나열된 값들의 "밀도"에 따릅니다. 밀도가 충분히 높다면 점프 테이블이 만들어지게 됩니다. 밀도가 너무 낮다면 리스트는 약 절반으로 나눠집니다(값들의 수가 걸쳐진 갯수가 아니라 리스트에서 1 요소로서의 갯수 범위로). 다음으로 각 하위 분기로 다시 시작합니다. 즉, 밀도 체크를 한 후 점프 테이블을 생성하거나 혹은 분할하는 것입니다. 이것은 모든 값들이 처리될 때까지 계속됩니다.

그럼, 어떤 최적화 기회가 있을까요? 기본적으로 case 문은 상당히 최적화가 잘 되지만, 완벽하지는 않습니다. 2진 비교 트리로의 분할은 어색한 위치에서 일어날 수 있습니다. 따라서, 중간이 빈 연속적인 값들이 있다면, 각 연속되는 범위의 값들을 각각의 case 문으로 만들고 다음으로 전체의 case 문을 만드는 게 더 낫습니다. 이것은 범위는 분할되지 않지만 연속적인 값들은 분할되기 때문입니다. 이렇게 하면 각각의 하위 범위 전체를 커버하는 점프 테이블을 만들 수 있게 됩니다. 예를 들면,

최적화 전:
  Case x of
    100: DoSomething1;
    101: DoSomething2;
    102: DoSomething3;
    103: DoSomething4;
    104: DoSomething5;
    105: DoSomething6;
    106: DoSomething7;
    107: DoSomething8;
    200: DoSomething9;
    201: DoSomething10;
    202: DoSomething11;
    203: DoSomething12;
    204: DoSomething13;
    205: DoSomething14;
    206: DoSomething15;
    207: DoSomething16;
    208: DoSomething17;
    209: DoSomething18;
    210: DoSomething19;
  end;


최적화 후:
  Case x of
    100..107:
      case x of
        100: DoSomething1;
        101: DoSomething2;
        102: DoSomething3;
        103: DoSomething4;
        104: DoSomething5;
        105: DoSomething6;
        106: DoSomething7;
        107: DoSomething8;
      end;
    200..210 :
      case x of
        200: DoSomething9;
        201: DoSomething10;
        202: DoSomething11;
        203: DoSomething12;
        204: DoSomething13;
        205: DoSomething14;
        206: DoSomething15;
        207: DoSomething16;
        208: DoSomething17;
        209: DoSomething18;
        210: DoSomething19;
      end;
  end;


또한, case 문은 실행의 빈도에 따라 대비가 되어 있지 않습니다. 어떤 case가 다른 case보다 더 자주 실행된다는 것을 알고 있다면, 이 정보를 실행 속도를 높이기 위해 이용할 수 있습니다. 그렇게 하려면, if 문과 case 문을 다단계로 배치하여 검색 순서에 우선순위를 주면 됩니다. 예를 들면,

최적화 전:
  Case x of
    100: DoSomething1;
    101: DoSomethingFrequently2;
    102: DoSomething3;
    103: DoSomething4;
    104: DoSomething5;
    105: DoSomething6;
    106: DoSomething7;
    107: DoSomething8;
  end;

최적화 후:
  if x=101 then
    DoSomethingFrequently2
  else
    Case x of
      100: DoSomething1;
      102: DoSomething3;
      103: DoSomething4;
      104: DoSomething5;
      105: DoSomething6;
      106: DoSomething7;
      107: DoSomething8;
    end;

Move와 FillChar

델파이에서 메모리를 옮기고 0 값으로 채우는 내장된 기본 방법은 각각 Move와 FillChar입니다. 이 루틴들은 rep movsd 및 rep stosd 어셈블러 명령에 기반하고 있으며, 상당히 효율적입니다. 하지만, 이 루틴들에는 효율성을 떨어뜨릴 수 있는 추가적인 클린업 코드가 있으며, 특히 소량의 메모리를 다룰 때 더욱 그렇습니다. 또한, 펜티엄 II CPU에는 특수한 데이터 배열 방식이 있어 상당한 영향을 미치게 됩니다.

이 문제에 대한 1차적인 해결책은 간단하게 단순한 루프를 사용하는 것입니다. 이것은 다루는 데이터 요소가 32비트나 64비트일 경우, 혹은 구조가 부분적으로만 제로/이동될 때(예를 들면 행렬의 서브섹션) 특히 효과가 있습니다. 하지만 루프 방법은 큰 레코드들이나 바이트, 워드와 같은 작은 요소를 가진 배열에는 효과가 줄어듭니다. 루프를 언롤하고 타입캐스트로 상황을 더 개선할 수는 있지만, 이렇게 하면 코드가 상당히 더 복잡해지게 되고 향상 효과는 그다지 크지 않습니다.
이제 더 전문화된 루틴에 대해 생각해봅시다. 첫번째 이슈는 구조의 경계(boundary) 크기입니다. 32비트 크기는 항상 가장 빠른 방식입니다. 하지만 구조의 크기가 4바이트로 고르게 나눠지지 않는다면 문제가 될 수 있습니다. Move와 FillChar에서 사용된 해결책은 dword(4바이트) 경계 단위로 잘라내고 나머지는 별도로 복사하는 것입니다. 위에서 언급한 것처럼, 작은 구조에 대해서는 이 추가적인 오버헤드가 상당히 커지게 됩니다. 하지만 사실 의외로 많은 구조들이 고르게 나눠집니다. 모든 메모리 할당은 다음 dword 경계에 맞춰 반올림됩니다. 따라서 2문자의 스트링은 실제로는 4바이트 길이를 갖습니다. 보통 이런 추가 데이터를 함께 카피 혹은 제로 시키는 것이 피하는 것보다 더 빠릅니다. 이 방법을 사용할 때는 주의를 기울여야 하고 잘 문서화해두어야 합니다.

데이터 정렬(alignment) 이슈를 다루는 것은 좀 더 복잡하며, 큰 구조와만 관련이 있습니다. 자세한 설명은 생략하고 대신 코드를 보여드립니다.

procedure ZeroMem(A: PDataArray; N: integer);
var
  i, c: integer;
  B: PDataArray;
begin
  B := Pointer((integer(A)+15) and $FFFFFFF0);
  c := integer(@A[N]) - integer(B);
  FillChar(A^, N*SizeOf(TData)-c, #0);
  FillChar(B^, c, #0);
end;


이 코드에서는  데이터의 일부를 스킵함으로써 16바이트 경계로 정렬하고 있습니다. 스킵된 부분도 물론 제대로 처리되어야 하므로 FillChar를 호출했습니다. FillChar를 두번 호출하는 오버헤드가 있으므로 이것이 가장 빠른 방식은 아닙니다. 최고의 속도를 바란다면, 어셈블러를 이용하는 다음의 방식이 그중 하나입니다.


procedure ZeroMem32(P: Pointer; Size: integer);
// Size = 채워넣을 dword 요소들의 수
// Size>4 라고 간주
asm
  push edi
  mov ecx,edx
  xor edx,edx
  mov dword ptr [eax],edx
  mov dword ptr [eax+4],edx
  mov dword ptr [eax+8],edx
  mov dword ptr [eax+12],edx
  mov edx,eax
  add edx,15
  and edx,-16
  mov edi,edx
  sub edx,eax
  shr edx,2
  sub ecx,edx
  xor eax,eax
  rep stosd
  pop edi
end;


개선된 Move의 경우도 매우 비슷합니다.
procedure MoveMem32(Src, Dest: Pointer; Size: integer);
// Size = 채워넣을 dword 요소들의 수 
// Size>4 라고 간주 
asm
  push edi
  push esi
  push ebx
  mov ebx,[eax]
  mov [eax],ebx
  mov ebx,[eax+4]
  mov [eax+4],ebx
  mov ebx,[eax+8]
  mov [eax+8],ebx
  mov ebx,[eax+12]
  mov [eax+12],ebx
  mov ebx,edx
  add ebx,15
  and ebx,-16
  mov edi,ebx
  sub ebx,edx
  shr ebx,2
  sub ecx,ebx
  lea esi,[eax+4*ebx]
  rep movsd
  pop ebx
  pop esi
  pop edi
end;


전역 데이터를 검토하라

전역 데이터 구조를 사용하는 것이 특별하게 유리한 경우가 있습니다. 단순 타입(simple type)의 2차원 배열의 두 인덱스 모두에 대해 비순차적 액세스를 할 경우입니다. 이런 데이터 구조를 전역으로 만들면 데이터 구조에 액세스할 때 두 인덱스가 동시에 적용되므로, 인덱스들을 조합하기 위한 추가적인 명령들을 피할 수 있습니다.


while 루프를 검토하라

배열 작업을 하는 while 루프에 적용할 수 있는 다른 테크닉으로서 CPU 레지스터를 절약하는 방법 하나는, 모든  인덱스 변수 사용을 음수에서 시작해서 0까지 카운트하도록 바꾸는 것입니다.
이렇게 하면 반복 횟수에 사용되었을 레지스터들을 비워둘 수 있습니다. 예를 들면,

  i := 0;
  while i < Count do
  begin
    Data[i] := Data[i] + 1;
    Inc(i);
  end;



위 코드는 다음과 같이 바꿀 수 있습니다.


type
  TRef = array[0..0] of TheSameThingAsData;
  PRef = ^TRef;
var 
  Ref: PRef;
... 
  Ref := @Data[Count];
  i := -Count;    // 여기서 음수 카운트를 대입한다
  while i < 0 do  // 카운트를 0까지 증가시킨다
  begin
    Ref[i] := Ref[i] + 1;
    Inc(i);
  end;

포인터 변수를 검토하라

앞서 포인터를 이용해서 실행시 참조 해석을 줄일 수 있다는 장점을 설명했는데, 그 외에도 포인터 변수를 사용하면 기존의 포인터 변수에 대한 우선순위를 높일 수도 있습니다. 스트링의 일부를 치환하는 루틴에서 가져온 아래 코드에서, PChar 변수 pSub1는 루프 내에서 다시 로드됩니다( CPU 윈도우에서 확인할 수 있습니다). pSub1을 pTemp에 대입하고 루프 내에서 pTemp를 사용하면 로드는 루프 바깥쪽에서 이루어지게 되며, 따라서 명령 사이클을 줄일 수 있습니다.

pTemp := pSub1;  // pSub1의 우선순위를 높임
while iStr[k] = pTemp[k] do
  Inc(k);

Assigned로 메소드 포인터를 체크하는 것을 피하라

메소드 포인터가 nil인지 확인하는 것은 흔한 작업으로써, 일반적으로 이벤트 호출과 관련되어 있습니다. 불행히도, if assigned(FSomeEvent) then ... 은 메소드 포인터의 코드 주소의 상위 워드에 대해 16비트 비교를 수행합니다. 이것은 상당히 이상한 일이고 완전히 불필요한 것인데, 저로서는 16비트 델파이 1 버전에서부터 내려온 것이 아닌가 추측하고 있습니다. 코드 주소를 직접 체크하는 데 대한 회피책으로 TMethod를 사용할 수 있습니다.
if Assigned(TMethod(FSomeEvent).code) then .... 


이런 코드는 좀 보기가 안좋은 것은 사실이므로, 여러분은 특별히 시간에 크리티컬한 부분에서만 사용할 수도 있습니다.

열거 타입의 크기를 조절하라

여러분이 열거 타입을 사용한다면(TSuits = (Diamonds, Hearts, Clubs, Spades) 처럼), 열거 변수가 32비트가 되도록 {$MinEnumSize 4} 혹은 {$Z4} 지시어를 사용하십시오. 호환성 문제가 우려될 경우라면 필요한 타입 선언에서만 이 지시어를 사용할 수도 있습니다. 예를 들면,

type
{$Z4}
  TSuits = (hearts, clubs, diamonds, spades);
{$Z1}

요소가 256개가 넘는 열거 타입에서 이 지시어를 사용할 경우 특히 더 효과적입니다. 256개 이상의 요소를 가진 열거 타입은 word 크기 변수가 되는데 상당히 느립니다.

버추얼 메소드

스태틱 메소드에 비해 버추얼 메소드가 더 많은 오버헤드를 발생시키는 것은 잘 알려져 있습니다. 버추얼 메소드를 호출하면 포인터 참조 해석이 두 번 일어나고 간접 호출이 되며, 전체 호출 오버헤드를 두배로 만듭니다. 하지만, 잠재적으로 훨씬 더 심각한 불이익이 있을 수 있습니다. 간접 호출은 펜티엄 II CPU에서 상당히 골치아픈 문제인 잘못된 분기 예측을 일으킬 수 있습니다. 이 문제는 호출의 대상이 바뀔 때마다 매번 발생합니다. 따라서, 매번 반복 때마다 바뀔 수 있는 버추얼 메소드를 루프 내에서 호출하는 것은 상당히 큰 문제를 일으킬 수 있습니다. 이에 대한 회피책은 메소드 호출을 정렬하는 것입니다.

예를 들면,

  TBaseClass = class
  public
    procedure VMethod; virtual;
    procedure SMethod;  
  end;

  TDerivedClass = class(TBaseClass)
    procedure VMethod; override;
  end;

  TDerived2Class = class(TBaseClass)
    procedure VMethod; override;
  end;

implementation

type 
  TArray = array[0..100] of TBaseClass;
 
procedure DoStuff;
var
  b: integer;
  j: integer;
  A: TArray;
begin
  A[0] := TBaseClass.Create;
  b := 0;
  for j := 1 to 99 do
  begin
    b := (1+random(2)+b) mod 3;  // 객체가 무작위로 생성하도록 하기 위해
    case b of    
      0: A[j] := TBaseClass.Create;
      1: A[j] := TDerivedClass.Create;
      2: A[j] := TDerived2Class.Create;
    end;    
  end;
  for j := 0 to 99 do
    A[j].VMethod;
  for j := 0 to 99 do    
    A[j].SMethod;
end;

호출들을 정렬하는 것은 약간 복잡합니다. 아래와 같습니다.
Type
  TSomeVirtualMethod = procedure of object;
  TSomeMethodArray = array[0..100] of TSomeVirtualMethod;

var
  SomeMethodArray: TSomeMethodArray;

  // 초기화 단계
  for i:=0 to Count-1 do
    SomeMethodArray[i] := Item[i].SomeVirtualMethod;

  // 실제 동작 단계
  for i:=0 to Count-1 do
    SomeMethodArray[i];

이 자체로서는 실망스럽게도 호출당 겨우 1 클럭 사이클을 아낄 수 있을 뿐이지만, TMethod를 이용하여 호출되는 코드들을 배열로 정렬하여 분기 예측 실패를 최소화할 수 있습니다. 덧붙여서, 베이스 클래스 메소드가 아무 것도 하지 않는 루틴일 경우 프로시저 리스트에서 완전히 제거해버릴 수도 있습니다.

// 초기화 단계
for i:=0 to Count-1 do
begin
  Hold := ClassArray[i].SomeVirtualMethod;
  if TMethod(Hold).Code<>@TBaseClass.SomeVirtualMethod then
  begin
    j := 0;
    while (j>ArrayCount) and (longint(TMethod(Hold).Code)<SomeMethodArray[j]) do
      inc(j);
    for k:=ArrayCount-1 to j do
      SomeMethodArray[k+1] := SomeMethodArray[k];
    SomeMethodArray[j] := Hold;
    inc(ArrayCount);
  end;end; 


이 방법은 용기 없는 개발자들을 위한 것은 아니며, 또한 특정 상황에서만 유용합니다. 하지만, 많은 객체들이 있지만 메소드들 중 몇개의 버전들만이 있고 메소드가 상대적으로 작거나 비어 있는 경우 큰 시간을 절약할 수 있습니다.
크리에이티브 커먼즈 라이센스
Creative Commons License
2012/05/09 17:20 2012/05/09 17:20

trackback :: http://blog.devquest.co.kr/imp/trackback/261

  1. 델파이 최적화 가이드라인 (1) - 일반 가이드라인

    Tracked from 볼랜드포럼 2012/05/14 07:07  delete

    아래 글은 원래 2002년에 Robert Lee의 홈페이지인 http://www.optimalcode.com 라는 사이트에 실렸던 "Delphi Optimization Guideline"이라는 글의 첫 파트입니다. 지금 이 사이트는 없어진 상태이고 원래의 필자도 전혀 연락이 안되는 상태입니다. 하지만 최근에 예전의 컨

사용자 삽입 이미지
이 글을 보실 다른 개발자분들께,

어떻게 보면 이 일은 단순한 감정 싸움으로 보이는 분들도 있을 수 있겠습니다. 제 입장에서는 상당히 섭섭한 일입니다만, 뭐 어쩔 수 없습니다. 다투는 모습은 일단 싫고 당장 급한 개인의 생업에 우선해야겠다는 분은 안읽으셔도 됩니다. 굳이 하루하루가 고단해서 당장 당면한 일이 아닌 일에 신경쓸 여유가 없는 분들이 꼭 이번 일을 다 살펴보셔야 할 이유가 있는 것은 아닙니다.

하지만, 만에 하나 이 일이 전적으로 저와 박범용씨 사이의 감정 싸움의 문제라고 해도, 이 일의 결과는 (여러분이 델파이와 C++빌더 개발자인 이상에는) 여러분의 삶에 적지 않은 영향을 주게 됩니다. 설사 비난할 쪽은 저라고 해도 저라고 해도 말입니다. 그래서, 자신의 미래에 대한 걱정만이 아닌 크게는 대한민국에서 델파이 혹은 C++빌더를 사용하는 개발자들의 거대한 커뮤니티의 미래에 대해서도 걱정하시는 분이라면, 여유가 나실 때 한번쯤 읽어보시고 생각을 해보시기를 바래봅니다. 비난을 하시려거든 일단 읽고 나서 비난하시기 바랍니다.

그리고 박범용씨께,
먼저, 밀실이 아닌 광장으로 나와주셔서 너무나 감사드립니다. 제가 기억하기로는 박범용씨가 델파이 커뮤니티나 페이스북 등 널리 공개된 곳에 자신의 입장을 명확히 밝히는 글로서는 거의 처음인 것 같습니다. 특히 커뮤니티에는 아마 '좋아요' 수준의 짧은 댓글 정도를 모두 합해도 박범용씨가 글을 남긴 건 채 10번도 되지 않는데 말이죠. 저는 박범용씨를 1:1 전화가 아닌 광장으로 끌어내기 위해 무진 노력을 했었는데, 드디어 대답을 하시는군요. 진심으로, 그리고 대단히 환영합니다.


1.
정확히 따지자면, 1:1로 담판을 짓는 것은 밀실을 좋아한다기보단 일을 하는 데 있어 개인적으로 선호하는 방식인 것은 알고 있습니다. 하지만 이유가 무엇이든, 박범용씨가 최근 몇년간 이익을 가져간 그 시장은, 박범용씨가 스스로 쌓은 이 개발자들이 수십년간 쌓아올린 노력의 결실이고, 직접적으로는 박범용씨가 데브기어를 통해 가져가고 있는 그 이익도 거의 대부분 개발자들로부터 나온 것이기 때문에, 델파이 및 C++빌더 개발자들은 무슨 일이 벌어지고 있는지 알 권리가 있습니다. 그리고 그러기 위해 박범용씨로부터의 직접 전화에는 일체 응하지 않은 것은 분명히 사실입니다.

그런데, 그러면 그동안 박범용씨는 저에게 연락할 방법이 없었을까요? 떳떳하게 전화가 안되어 물어볼 필요가 없었다, 라고 말할 정도로 말입니다. 일단 그것부터 밝히자면, 전혀 사실과 다르지요.  일단 박범용씨는 제게 단 한 차례의 메일도 보내지 않았습니다. 게다가 얼마전 퇴사하신 것으로 알고 있습니다만 아시다시피 우리의 절친한 친구였던 이세종 차장은, 제 퇴사 이후로 저와 수십차례 통화를 했고 술도 여러번 마셨습니다. 페이스북을 통해서는 김나래씨를 통해 또 여러번 말을 주고받았고, 박대표에게 보고하라는 얘기도 몇번 했지요. 다 떠나서, 박범용씨가 저에게 의사를 전달할 생각이 1%라도 있었다면, 여전히 저와도 좋은 관계인, 당신과 제가 함께 뽑았던 직원들을 통해 얼마든지 연락할 수 있었는데도 단 한번도 그렇게 하지 않았지요. 그럼 제가 전화를 안받는다는 것이 과연 제게 자신의 생각을 전달하지 못한 이유가 될 수 있는 것입니까?


2.
또 하나 짚고 넘어갈 것. 박범용씨는 데브기어라는 "성"을 쌓아올린 것이 본인과 직원들의 공이라는 투로 말씀하시는데요. 그 성은 델파이, C++빌더 개발자들이 거대하게 쌓아올린 빌딩에다가 꼭대기에 가건물 하나 더 올리고 '데브기어'라고 간판 하나 더 올리고 있는 겁니다. 글너데 마치 창조주 박범용씨가 다 이룬 것처럼 말씀하시니 보기가 안좋습니다. 국내 델파이, C++빌더 시장이 회사를 크게 키우기에는 좀 작기는 해도, 별 노력을 기울이지 않아도 매년 최소 십억 단위 이상의 매출이 나오고, 광고 한번 안해도 개발자들이 스스로 삽질해서 총판을 찾아 구매를 요청하는 시장입니다.

게다가 본사가 미국 달러 가격과 동일하게 판매한다는 전제 하에 보장해준 적정 수익도 결코 작지 않고, 독점 계약을 유지하고 있는 등 다른 대부분의 기업들이 초기에 절대 가지지 못하는 환상적인 환경을 갖추고 있는 것이 델파이, C++빌더 시장입니다. 솔직히 비즈니스맨이라면 이런 상황에 규모의 시장에 독점권을 얻었으면 회사를 키우지 못하는 게 이상한 거죠. 그런데 그런 환경의 대부분은, 여기의 수많은 델파이, C++빌더 개발자들이 쌓아올린 것이란 말입니다.

저는 과거 박범용씨의 동업자로서의 입장은 제쳐놓고서라도, 수십년 델파이와 C++빌더에 미쳐 살아왔고 지금도 델파이와 C++빌더만 파고 있는 개발자의 한 사람으로서, 마치 델파이와 C++빌더 시장을 혼자 고군분투해서 쌓아올린 듯 생색을 내고, 수많은 개발자들이 쌓아올린 유산의 위에 올라서 버티고 서서는 당연한 내것이라고 생각하는 박범용씨의 태도에 대단히 큰 문제가 있다고 봅니다. 그리고 그런 박범용씨의 태도가 이 모든 문제의 가장 근본적인 원인이라고 생각합니다.


3.
재미있게도, 제가 공정위에 제소를 추진한다고 했다가 포기한 것이 박범용씨에게 공격의 빌미가 되는군요. 일단, 당연히 짐작하고 있었습니다만 그런 진행상황을 다 아시면서도 부러 일체의 대응을 하지 않으셨다는 것을 간접적으로나마 시인하신거구요. 그러다가 매출에 직접적인 타격이 생길 것 같으니까 이제야 모습을 드러내셨네요.

제게 단 한푼의 이익도 되지 않는 일을, 하려고 하다 못한 것이 공격의 빌미가 된다는 것이 좀 우습습니다. 박범용씨는 이 모든 일들의 이익을 독점하고 계시지 않습니까. 델파이와 C++빌더를 파는 것이 일이고, 그래서 그 이익을 다 가져가시는 분이, 제 입장에선 10원의 이익도 되지 않는 일을 공익적으로 하려고 하다 못한 것이 왜 저를 비난할 이유가 되지요? 제게 반격을 하시든 동조를 하시든 그 모든 것이 생업의 일환인 박범용씨와 달리, 불행히도 저는 따로 일을 해서 입에 풀칠을 해야 하는 입장이니, 밥을 굶지 않기 위해 상황에 따라 얼마든지 늦어질 수 있습니다. 물론 다짐을 하고 약속을 했다가 그것이 늦어지고 취소되면, 도의적으로 제가 그런 사실을 알렸던 개발자분들께는 실망을 드려 죄송한 일이지만, 박범용씨가 저를 비난할 어떤 이유가 되는지요?

아, 공격 들어온다고 긴장했다가 실제론 기다리기만 해서 스트레스를 준 것이 비난의 이유는 될 수도 있겠네요. 하지만 그런 스트레스조차도 박범용씨의 업무의 일부분입니다. 박범용씨가 델파이를 알기도 전부터 국내에서 독점을 했던 볼랜드코리아와 그 하위 총판들의 임직원들도 방법과 정도의 차이는 있어도 비슷한 스트레스를 받아왔고, 이 모든 델파이, C++빌더의 자산을 쌓아올린 커뮤니티로부터의 정당한 요구라는 것을 부인할 수 없었기 때문에 다들 감내했습니다. 박범용씨는 그것을 못견디시고 불만스러워하시는 것이 안타깝습니다만, 그건 댁의 사정입니다.


4.
다음으로, '델파이 프로그래밍 언어' 출판 문제에 대해서 써보지요. 진심으로 (가슴을 쓸어내릴 정도로) 다행스럽게도, 박범용씨는 제 퇴사 시점에 위 책에 대한 합의가 있었다는 점에 대해서는 최소한 부인은 하지 않으시는군요. 진심으로 다행인 이유가, 제가 서적도매업체의 사장님으로부터 들은 내용에는, 그런 합의가 있었다는 사실 자체를 부인했다고 들었기 때문입니다. '단지 번역자로서 도와주신 분이며 그런 합의 자체는 전혀 없었다' 이렇게 말이지요.

사실 본질적으로 따지자면, 이 책의 출판권 문제로 다툼이 생긴 것은, 제 책임도 상당 부분 있습니다. 구두 약속이 아닌 문서로 받아놓을 수 있었는데도 불구하고, 당시 퇴사를 앞둔 상황에서 공들여 키운 회사를 떠나면서 정리할 것이 너무 많아서 경황이 없어 서류 작성까지 하지 못했던 부분이 제 책임입니다. 이 책이 워낙 돈이 될 책이 아니고 실제로 기대보다 상황이 훨씬 좋았음에도 수익이 몇백만원 정도밖에 되지 않아 박범용씨가 굳이 약속을 어길 이유가 없었다고 판단한 것이 제 안이함의 이유이기도 했습니다. 개정판이 아닌 초판을 추가로 더 찍는 데에는 비용은 미미하고 수익은 올라가니까 박범용씨가 뒤집을 가능성이 있다는 부분을 간과한 것입니다.

('데브기어 설립'이라는 부분에 대해 박범용씨의 문제 제기가 있을 수 있어 더 자세히 쓰자면, 당시 박범용씨는 '비디티지코리아'라는 법인을 가지고 있었고, 이 회사는 당시 코드기어의 APAC 지사 직원이었던 박범용씨가 국내 유통에 따르는 세금 처리를 하기 위한 편의상 설립한 개인 법인이었습니다. '새로운 법인을 만들려면 절차도 복잡하고 비용도 드니까 기존의 비디티지코리아'를 개명해서 그냥 쓰자' 라는  박범용씨의 제안으로, 최초에는 신설법인으로 만들려고 했던 계획을 바꾸어 비디티지코리아에 제 지분을 추가로 넣고 데브기어로 법인 명을 바꾼 것입니다.)

'델파이 프로그래밍 언어'에 대한 제 퇴사 당시의 박범용씨와 저의 합의 사항은, 출판권을 제가 가져간다는 것이었고, 그로부터 모든 것을 정리하고 퇴사일까지 2개월 가까이 더 걸렸는데 그 과정에서 개정판을 몇달 안에 낼 예정이라는 얘기를 해준 적은 있습니다. 그런데 마치 개정판을 몇달 안에 내는 것이 제 의무사항이고 그것을 어기면 다시 '회수하는 것이 당연하다'라는 듯이 말씀하시는군요.

저작권이라는 말 자체를 가지고 꼬투리잡아 전체의 맥락을 뒤집으려고 하시는 것 같기도 한데, 제가 전체 원고를 다 만들고 기획을 하고 표지 디자인 초안도 만들고 편집 시안과 감수, 교정 등 전체 과정을 다 했고, 따라서 당연히 제가 제 손으로 '박지훈 역'이라고 써넣은 책에 대해 제가 저작자라고 우기는 것이 제 주장의 핵심이겠습니까? 우리가 합의했던 것은 출판권의 문제였고, 저작권이라는 단어 하나의 선택의 문제로 트집 잡는 일은 그만하시길 바라구요. 적어도 박범용씨가 쓴 글을 보니 출판권을 제게 넘기는 것을 합의한 사실은 부인하지 않는 것으로 보이고, 또 본인이 단지 제가 '연락이 안되어서' 맘대로 추가 인쇄 유통했다, 라는 사실은 인정하시는 것이니 맥락의 차이는 있어도 사실관계는 크게 틀리지 않아보입니다.

하지만 위에서도 썼다시피 제가 연락이 안되었다라는 것은, 정확하게 따져서 박범용씨가 스스로 전화를 했을 때 제가 받지 않았다는 말로는 수정을 해주셔야겠지요. 추가 인쇄 및 유통을 위한 동의를 구하는 내용 정도는 간단히 문자 메시지만 보내도 되고, 더 길게 쓸 수 있는 메일도 있고, 또 우리가 함께 아꼈던 다른 직원들은 저와 연락하고 있었는데 전화를 안받았다는 것만으로 연락이 안되어서 맘대로 했다, 라고 당당하게 말씀하시는 건 어이없는 일이 아니겠습니까?

더욱이 저는 Call Log와 SMS Log를 지메일로 자동으로 백업하는 프로그램을 쓰고 있어 제게 전화가 온 사람과 시간 정도는 다 기록되어 있는데, 뒤져보니 작년 3월 이후로는 단 한번도 제게 통화를 시도하신 적이 없습니다. 이세종 차장과는 여러번 연락을 했고 술도 많이 마셨지만요. 그럼 제가 퇴사한 시점인 2010년 11월과 2011년 3월 사이에 추가로 인쇄, 유통을 했다는 말씀이신지요? 이건 인터넷서점이나 신한전문서적에 쉽게 확인이 될 문제겠네요.

돈으로 따지자면, '델파이 프로그래밍 언어'보다는 그래도 수익도 좀 될 수 있는 델파이 초중급서를 모 출판사와 진행하고 있고, '델파이 프로그래밍 언어'의 경우 아시다시피 수익성이 거의 없는 책으로서 제가 굳이 집착할 이유가 없는 책입니다. 초기에 박범용씨도 약간 만류했었는데도 제가 데브기어에서 이 책의 출판을 우선했던 이유는, 델파이 언어의 위상 제고를 위해 반드시 필요한 책이고, 흥행성이 나은 책들을 먼저 출간하다보면 리스크에 더 민감해져서 이런 돈 안되는 책은 피하게 되기 때문이었습니다.

그리고 그 생각은 지금도 변함이 없어서, 제가 밥벌이로 바쁜 과정에서도 '델파이 프로그래밍 언어'의 개정판을 준비해온 이유도 역시 델파이 프로그래밍에 있어 기준이 되는 책이 버티고 있어야 하기 때문입니다. 절판시키려고 의도했다는 부분만 잘라가지고 조중동처럼 전체를 왜곡하려 시도하시는데, 제가 어디서 영구 절판이라고 했습니까. 개정판을 제 자비로 출판함으로 해서 손해만이라도 보지 않도록 하기 위해 1년에서 1년 반 정도 일시 절판을 하려고 했다고 쓰지 않았습니까.

백보 양보해서, 너무 바빠서 메일 한통이나 직원에게 전화해보라고 지시하는 것도 귀찮아서 다 생략하고 추가 유통 이전에 제게 동의를 얻는 절차를 빼먹는 것을 이해한다고 해도 말입니다. 제가 지난주에 우연히 인터넷서점에서 이 책이 절판되지 않고 계속 판매중인 것을 발견하고 서적도매유통사 사장님께 문의해서 '데브기어로부터 추가 인쇄분이 입고되어 계속 판매중이다'라는 날벼락같은 대답을 듣기까지, 분명히 박범용씨의 입으로 출판 권한을 박지훈씨에게 넘긴다고 했던 책이 서점에 계속 유통중이라는 사실을 꿈에도 몰랐던 겁니다. 그래도 제가 분노하는 것이 조금도 이해가 되지 않으시는지요?


5.
사실 저도 그렇고, 박범용씨도 그렇고, 깨놓고 말해서 위의 책 한권보다 더 중요한 이슈가 있고, 그것이 제가 박범용씨에게 각을 세우고 항의하는 가장 큰 이유입니다.

이 책의 문제는 위에서 쓴 대로 이번에 처음 알게 된 것이고 그래서 당연히 처음 벌어진 문제입니다만, 박범용씨와 저의 대립 문제는 기본적으로 국내 시장에서의 델파이, C++빌더 폭리의 문제입니다. 저도 그것 때문에 계속 문제를 제기하고 있고, 이번에 제가 데브기어를 통하지 않고 구입할 방법을 알리기 시작하니까 박범용씨도 몇년간의 침묵을 사실상 처음으로 깨고 제 글에 반박하는 글을 커뮤니티에 올리는 것 아닙니까. 그리고 사실 따지자면 박범용씨가 델파이 시장에 등장하기도 전부터, 2000년대 초부터 제가 계속해온 일입니다. 따라서 본질을 흐리고 책 한권의 문제로 국한해서 물타기 하려고 하지 말아주시기 바랍니다.

박범용씨가 커뮤니티나 페북 등에 일절 답변을 하지 않은 이유들 중에 하나가, 가격 문제가 이슈가 될 것을 두려워하기 때문이라는 것을 알고 있습니다. 그래서 제가 박범용씨를 계속 커뮤니티로 끌어내려고 노력했던 거구요. 이번에도 역시, 박범용씨가 정말로 걱정스러워하고 있을 가격 문제에 대한 글이 아닌, 상대적으로 박범용씨에게는 그닥 큰 일이 아닌 책 문제의 글에 댓글을 붙이시고, 그 문제로 일을 축소하려고 하시는군요. 또 가격 문제에 대해서는 일절 언급도 안하셨네요.

하지만 다시 한번 못박건대, 제가 박범용씨에게 가지고 있는 가장 큰 유감은 폭리 문제입니다. 만약 박범용씨가 대승적으로, 그리고 남자답게 약속을 지킨다면, 지금까지 제가 제기했던 다른 모든 것들은 다 접을 용의가 있습니다. 물론 '델파이 프로그래밍 언어' 문제도 포함해서요.

박범용씨는, 저, 그리고 양병규님, 조무영님, 나현호님을 포함한 델파이 커뮤니티 시삽들의 거듭되는 압박 끝에, 2007년 초에 미국 가격과 동일하게 가격을 인하한다고 약속한 바 있고, 실제로 그 직후 그 가격으로 인하했습니다. 박범용씨는 부인했습니다만, 그 직전에 제가 미국 코드기어 부사장에게 직접 메일을 써서 코리안 프라이스 문제로 강력하게 항의한 바가 있고, 당시 새로 부임했던 부사장이 깜짝 놀래서 APAC 지사의 중역이었던 닉 잭슨을 호출하여 박범용씨에게 문제를 수습하도록 지시한 적이 있습니다. 밤 사이 그 일이 있었던 후로 박범용씨가 전화를 받지 않는 저 때문에 애를 태우다가 나현호씨의 중재로 나현호씨와 함께 점심때쯤 제가 전산실장으로 근무하던 결혼정보회사 듀오로 찾아와서 원래 가격을 인하하려고 했었고 단지 제게 답을 주지 않았던 것은 시기를 조정하고 있었을 뿐이라고 변명하면서, 빨리 부사장에게 메일을 보내 무마해달라고 간곡하게 부탁하신 바 있습니다. 부인할 부분 없으시지요?

그리고 그 2007년 한해가 지나갈 때까지는 그 미국 가격을 유지했었습니다. 당시 구입했거나, 구입하지 않았더라도 가격에 민감했던 델파이, C++빌더 개발자분들은 다들 기억하실 것입니다. 그런데 2008년으로 넘어가면서 미국발 금융위기가 발생했고, 그 결과로 환율이 폭등했지요. 그때 박범용씨는 환율 인상으로 한국 가격도 일시적으로 가격을 인상해야겠다고 양해를 구했고, 저를 포함한 커뮤니티 리더들도 이해를 했습니다. 그리고 그해 말쯤에 제가 박범용씨와 함께 데브기어를 설립했습니다. 1년 가까이 제게 구애하신 결과였지요.

저는 데브기어를 함께 설립하는 그 시점까지도 박범용씨를 믿지 못했고 또 그런 사실을 박범용씨에게도 깨놓고 말했습니다. 그래서 '당신을 믿지 못하니, 이러이러한 것을 합류 조건으로 약속해달라, 이 약속들을 깨면 난 나간다' 라고 요구했고, 그 약속들 중 첫번째가 달러 환율이 내려가면 그에 따라 한국 가격도 다시 원상회복시킨다는 거였습니다. 그런데 제가 데브기어에서 함께 일하는 몇년 동안, 이 약속은 지켜지지 않았을 뿐만 아니라, 다른 약속들도 다 깨졌습니다.

가격 문제와는 별개의 문제이지만, 관전중이신 개발자분들의 이해를 돕기 위해 참고로 말씀드리자면, 다른 약속들 중 가장 큰 것이 개발툴 마케팅에 대한 권한을 박범용씨가 아닌 개발툴 사업 총괄인 제가 갖고, 그 예산도 별도로 제가 집행한다는 거였습니다. 2010년초에, 처음으로 약속했던 마케팅 예산의 구체적인 액수가 정해졌고, 예산권을 가진 박범용씨도 그 액수 수준과 목적에 대해 함께 논의하고 결정한 것이었습니다. 그런데 실제로 집행 시점이 되자 박범용씨는 너무 많다 좀 낮추자 해서, 반으로 줄이는 데 동의했고, 마련에 시간이 든다 해서 또 몇달 기다렸습니다. 그리고 다시 그 절반으로 줄어든 예산에 맞춰 마케팅 계획을 재조정한 후 관련 업체들과도 다 협의를 끝낸 후에 집행하자고 하니까 또 너무 많다, 줄이자, 했습니다. 그래서 다시 또 반으로 줄이고 기다렸습니다. 역시나 또 깨더군요. 그리고 귀찮아졌는지 예산 집행권 자체를 박탈하려고 시도했습니다.

결국 박범용씨가 제게 해준 약속은 어느것 하나도 지켜지지 않았고, 제가 처음 약속했던 퇴사를 무기로 박범용씨를 위협도 해봤습니다만 전혀 씨알조차 먹히지 않더군요. 그래서 정말 공들여 키운 회사를 떠나는 수밖에 없었습니다. 박범용씨가 자랑스럽게 내세우는, 출판사업, 세미나, 교육 등 모든 것이, 뭐 자기도 그런 생각했었다고 하기는 했지만, 제가 기획하고 제가 집행했던 것이었고, 그렇게 힘들게 키우고, 신설 법인으로 이름도 없는 데브기어라는 타이틀 대신에, 제가 제 이름을 걸고 전국을 돌아다니며 델파이 개발자들을 한사람 한사람 설득했습니다. 제 얼굴을 보고 우리 회사를 믿어달라고요. 그랬던 회사를, 헐값에 지분을 넘기고 떠난 제 심정은 어땠겠습니까.

다 좋습니다. 저도 20년 가까지 직장 생활 하면서 산전수전 다 겪어봤습니다. 하지만 가슴 아픈 것은, 가격 인하를 주도했던 제가 데브기어에 합류한 것이, 가격 인하를 막는 빌미가 되었다는 사실입니다. 제가 회사 내에서 박범용씨에게 가격 원상 복구를 계속 요구하는 동안, 회사 대외적으로 가격 문제에 이의를 제기하는 델파이 개발자들에게는, '아직 환율이 완전히 안정되지 않아서 그렇다, 내 얼굴을 믿고 조금만 기다려달라' 이러고 다니고 있었습니다. 그리고 그 바가지 가격은 지금도 그대로입니다.

도대체 어디에, 한국 개발자들에게는 미국 가격보다 무려 50%나 가격을 부풀려서 판매할 이유가 있다는 말입니까? 제가 데브기어의 경영진이 아니었다면 또 만에 하나 제가 모르는 원인이 있을 수도 있겠다 할 수도 있겠지만, 제가 박범용씨와 같이 경영진으로 일하는 동안에도 그런 이유는 없었습니다. 미국 본사로부터 소비자 가격으로 받아와서 판다고해도, 재고부담도 없고 유통비도 전혀 안들어가는 소프트웨어에 50%는 폭리입니다. 그런데 미국본사에서 가져올 때는 미국 소비자 가격보다 30%가 넘게 DC해서 가져오지 않습니까? 그러면 다시 말해 박범용씨의 마진은 무려 100%가 넘는 겁니다.

박범용씨는 엄청난 착각을 하고 있습니다. 그 델파이와 C++빌더 시장, 박범용씨가 피땀흘려 일군 시장 아닙니다. 저도 마찬가지입니다. 제가 데브기어에서 한 일은, 델파이 개발자들이 지켜온 시장을 다시 전면에 끌어내는 역할 뿐이었습니다. 1년에 세미나 몇번 더했다고 자기 거라고 자신할 수 있는 시장 아닙니다. 개발자들이 각자의 직장과 프로젝트 현장에서 비주얼 C++같은 다른 개발툴이 더 낫다는 압도적인 편견 아래에서도 버티고 싸워가며 지켜온 시장입니다. 당신의 폭리에는 아무런 도적적이고 합리적인 이유가 없으며, 박범용씨가 APAC 지사에 우연히 연줄이 닿아 가져온 그 독점 영업권이 유일한 이유입니다.


다시 말하지만, 다른 모든 것, 그 출판권까지 포함해서, 다 포기할 수 있습니다. 가장 기본적인 약속 하나만 지켜주면 됩니다. 델파이와 C++빌더의 가격을 미국 가격에 맞춰서 다시 원상복구해주시기 바랍니다.

어찌됐든 박범용씨의 입장에서는 이미 가진 영업권이니까 그것을 기득권, 즉 '당연한 내것'으로 인식하는 거 알고 있습니다. 아직 저는 박범용씨를 압박할 카드 몇장이 더 남아있습니다. 박범용씨가 예상할 수 있는 카드도 있고 전혀 꿈도 못꿀 카드도 있습니다. 저는 다음 카드를 쓸 준비가 되어 있습니다.  박범용씨는 돈이 걸려 있으니까 모든 방법을 다 동원해서 제게 대응하려고 하겠지만, 거꾸로 저는 돈이 걸려 있지 않기 때문에 얻을 것도 없지만 잃을 것도 없고 멈출 이유도 없습니다. 일부 개발자들이 오해를 하고 제게 비난을 날리기도 하지만, 그게 대다수가 아닌 이상은 견디고 나갈 자신 있습니다.

문제 제기는 제가 하지만 선택은 박범용씨가 하는 것입니다. 그리고 그 결과에 대해서도 박범용씨가 책임을 지면 됩니다.
자, 이제 답변 주시지요.







────────────────────────────────────────────────
박범용님께서 작성하신 내용입니다.
────────────────────────────────────────────────

>>
전 동료에 대해 이런 메일을 쓰는 점에 대해 매우 유감스럽게 생각합니다.>>
현재에도 부족하나마 데브기어의 전 임직원은 정말 열심히 일하고 있습니다.
>>
그런데, 이런 생산성없는 답변을 위해 시간을 들이고,
>>
또 이런 생산성없는 답변에 대해 많은 델마당 회원들이 읽는 시간을 들이는 동기를 만드는 것이 안타깝습니다.
>>
하지만, 저와 데브기어 임직원의 명예와 피해 방지를 위해서 최소한의 언급이라도 있어야 할 것으로 생각되어 박지훈씨의 주장에 대한 답변을 드리려고 합니다.
>>
답변에 답변이 달리는 소란이 발생하지 않도록 본 건에 대한 입장은 이 하나의 글로 마감하고자 합니다.
>>

>>
>>
>>
박지훈씨과 델파이프로그래밍 언어 출판에 관한 사실과 제 입장을 전달 드립니다.
>>
(내용증명에 대한 회신증명을 이미 발송하였으며, 해당 내용은 아래 내용 중 노란색 강조로 표시하였습니다)
>>
>> ----내용 나갑니다 --- >>
>>

>> 성냥개비로 성을 쌓는 수고는 힘들고 어려운 일입니다.
>>
부수는 일은 단지 한번 "훅"하로 입바람만 불어도 됩니다.>>
하지만, 성을 쌓는 수고는 그만큼의 가치를 가집니다. 그게 인생일 것입니다. >>
>>

>>
>>
박지훈씨는 볼랜드포럼, 델마당, 페이스북, 트위터 등에서 저와 데브기어에 대한 개인적인 생각을 포장하고 유포하고 있습니다. 인터넷의 위력이 큰 오늘날, 데브기어에도 그리고 많은 델파이 사용자에게도 좋지 않은 영향을 주는 것은 명확한 사실입니다.
>>

>>
>>
제가 지금껏 무대응하는 이유는 단지 하나, 우리가 성을 쌓는 것과 관련하여 얻을 것이 없기 때문입니다.
>>
상대의 소란을, 잘못을 따져서 그 상대방을 상처입히는 것보다는 제가 맡은 일에, 성을 쌓은 일에 최선을 다하기 위함입니다.
>>
하지만, 영업적인 타격과 시장의 신뢰 손상이 너무나 클 수 있으므로 이러한 경우는 대응할 수 밖에 없을 것입니다.
>>

>>
>>
최근의 사례를 보면, 박지훈씨는,
>>
>>
가격문제로 "공정위에 제소하겠다", "공정위에서도 긍정적이다" 라는 글을 썼었습니다.>>
그러나, 이후에는 의외로 본인에게 증거를 제시하는 사람이 없어서(적어서?) 제소를 포기했다고 했습니다.>>

>>
>>
이번엔 델파이프로그래밍 언어의 저작권과 출판권이 본인에게 있다고 하면서, >>
 >> >>
"강력하게 대응하겠다" 고 했고, 몇일 후에는>> "박범용이 구두약속을 뒤집었으므로 도서집필을 포기한다"고 했습니다.
>>

>>
>>
전 박지훈씨가 문제를 제기했다가, 얼버무리고, 핑계대고하는 원맨쇼가 이해가 되지 않습니다.
>>
"정말 정당하고 정말 사실이라면", 시시비비를 사법기관이든, 공정위에서든 분명히 따져주었으면 합니다.
>>
그리고 그 결과가 나온 후에 "법원도, 공정위도 데브기어에서 뒤에서 다 작업했고, 그래서 이나라가 다 썩었다"는 이상한 논리를 대시지는 않으시길 바랍니다. 저희 데브기어는 그럴 시간적 여유도 없습니다.
>>
지금 이시간에도 영업, 도서출판, 세미나 준비, 온라인 델파이 교재 오픈 준비, 고객 지원 등 정신없이 바쁩니다.
>>

>>
>>
도서 저작권과 출판권에 대한 주장은 법률적 권리에 대한 주장인 것처럼 보이는데,
>>
전혀 법률적으로 터무니없는 주장입니다. (주위에 변호사가 있으면 확인해 보시면 됩니다)
>>

>>
>>
서적 델파이 프로그래밍 언어의 원저작권은 미국의 엠바카데로 테크놀러지스 (Embarcadero Technologies, Inc. 이하 엠바카데로”)에 있습니다.
>>
박지훈에게 저작권이 없음은 물론이고 엠바카데로에서 박지훈 개인에게 번역출판을 승낙한 바가 없으므로, 번역자는 이에 번역물에 대한 번역출판의 권리또한 없습니다.
>>

>>
>>
미국본사에서 어느 개인에게 번역출판권을 줄까요? 그렇다면 저작자가 아닌 제가 줄 수 있을까요? 당연히 제가 박지훈씨에게 번역출판권을 줄 수 있는 입장 또한 못됩니다. 그걸 아는 제가 출판권, 저작권을 당연히 줄 수 없고, 그러므로 줄 리도 없습니다.
>>

>>
>>
그리고, 이미 박지훈씨 합류이전 부터교육 강화, 서점에 도서 강화는  제 ToDo 항목 중에 있었으며,
>>
기술팀 여러 분들의 도움으로 데브기어에서 지금도 미력하나마 변함없이 애쓰고 있습니다.
>>
제 이전과 이후의 델파이 환경 변화에 대해서는 굳이 이자리에서 일일이 나열하지는 않겠습니다만,
>>
분명히 델파이는 그 자체 뿐만 아니라 환경 측면에서도 과거 몇년보다 좋아졌고 계속 좋아지고 있습니다.
>>

>>
>>
당시 저에게는 델파이 확산을 위해, 개발자의 자신감을 위해, 책의 내용보다 서점에 책이 있다는 것 자체가 절박한 의미였습니다.
>>
이 의미는 박지훈씨도 잘 알고 있었습니다. 그리고, 번역을 직접하게 된 것도 같은 동기라고 저는 생각했습니다.
>>

>>
>>
그런데 박지훈씨의 주장처럼 자신의 퇴사와 함께 책을 절판 한다면 즉 출간 후 2년도 안되어 절판된다면,
>>
차라리 책을 출간하지 않는 것 만 못할 것입니다. 주위에서 "아, 델파이는 책도 없다가, 데브기어라는 곳에서 한 권 냈는데 그것도 2년이 안되서 절판됬다더라"고 하겠죠. 이런 이야기는 지금 박지훈씨가 하는 거짓 주장보다 훨씬 큰 힘을 가질 것입니다. "주장"이 아닌 "사실"일 테니까요.
>>
 
>>
하지만 지금, 최근 3년동안 3권의 책(C++포함)이 출간되었으며, 하반기를 목표로 현재 또하나의 번역서 작업 중입니다.
>>
이는 언어를 막론하고 최근 개발서적 시장의 전체적인 침체에 비해 오히려 두각되고 있습니다.
>>
지금은 델파이 신규 프로젝트 증가에 비해 델파이 인력이 못따라갈 만큼 크게 위상이 변화하고 있습니다만, 당시에 제가 그 책을 절판 시킬 수 있도록 박지훈씨와 합의하였을까요? 상식적으로 절대 그럴 수 없습니다.
>>

>>
>>
단, 저 자신이 이미 도서출판이라는 것이 적자를 각오해야 하는 것이라는 것을 잘 알고 있습니다.(그렇기 때문에 출판사들이 책을 만들지 않고, 그렇기 때문에 데브기어에서 직접 만든 것입니다. 다행히 지금은 출판사들도 관심을 가지는 듯 합니다)
>>

>>
>>
박지훈씨가 퇴사시점에 더 좋은 책이 되도록 개정판을 수개월 내에 내고 싶다고 했을 때, "탈고가 되는 시점이 정해 지면 알려달라, 그러면, 우리가 책에 대한 인쇄를 멈추고, 당신의 책이 잘 팔릴 수 있도록 돕겠다. 델파이 개발자들에게 더 좋은 책이 나온다면 그만큼 좋은 것이라고 생각한다"고 했습니다. 양쪽에서 책을 찍으면, 시장에서도 혼란스럽고, 양쪽 모두 적자의 폭이 커질 것이 뻔하기 때문이므로 이것이 제가 할 수 있는 최선이라고 생각했었습니다.
>>

>>
>>
하지만, 2010년 퇴사 당시 2011년 봄 쯤되면 개정판 탈고 된다고 했는데, 나오지도 않고, 당시 제고는 소진되어가고,
>>
당시 제가 박지훈씨에게 탈고 진행 상황에 맞추어 추가인쇄 수량을 조율하고자 전화해도 받지도 않고,
>>
결국 2012년 4월 현재 시점까지도 개정판은 탈고되지 않고 있는 것이 현재까지의 사실(Fact)입니다.
>>
그리고 "델파이 프로그래밍 언어"는 그 자체가 독립된 언어 스펙에 대한 설명서이므로 시간의 변화에 따라 개정판이 꼭 필요한 것은 아닙니다.
>>

>>
>>
물론, 그 때 제가 "개정판 출간은 할 수 없다"라고 했으면 문제가 간단했을 수도 있겠습니다만, 비록 번역서라 할지라도 책은 출간하고 나면, 아쉬움이 남을 수 밖에 없으므로, 개정판을 쓰고 싶기 마련입니다. 그런 "긍정적인" 열정을 제가 굳이 막고 싶지 않았고 제가 도울 수 있는 힘도 있을 것이므로 위와 같이 하였던 것입니다.
>>
>>

>>
>>
박지훈씨는 자신이 책으로 인해 손해보지 않으려면, 해당 책이 절판되었어야 하는데 그렇지 못해서 피해가 크다고 주장합니다. 박지훈씨는 자신의 주장과 같이 한국의 델파이 환경에 관심이 있는 것인지, 자신의 이익에 관심이 있는 건지도 모르겠습니다. >>
물론 저희 회사도 전 임직원의 일터이므로 이익이 중요합니다만, 도서에 대해서 만큼은 이런 고려에 앞서서 추진하였습니다.>>
>>

>>
>>
그런데 이제와서 저작권, 출판권을 운운하고, 강력하게 대응하겠다고 해놓고, 마지막에는 박범용의 거짓말 때문에 집필을 포기한다고 하니, 아래와 같은 상상의 장면이 떠오릅니다 (씁쓸합니다):
>>
저는 가만히 있는데, 혼자서,
>>
"이 나쁜 녀석아" "동네사람들 여기좀 보세요" "이제 곧 결투다"....고 하다가
>>
역시 저는 가만히 있는데, 조금 있다가
>>
"에잇 이 나쁜 녀석이 못된 거짓말장이구나" "난 거짓말 장이와 결투 안한다" "동네사람들 OOO 진짜 나쁜 거짓말장이예요"...
>>
과연 OOO 에 누구 이름이 들어가야 할까요?
>>

>>
>>
아무쪼록 박지훈씨든 저든, 데브기어든, 이글을 읽으시는 분이든,
>>
"파괴적"이 아닌 "건설적"인 활동, "마음내키는 데로"가 아닌 "책임있는 행동", "Nagative"가 아닌 "Positive"한 하루가 되시길 기원합니다.
>>
여러분에게 도움되지 않는 글을 이렇게 길게 나열하여 많은 시간 빼앗은 점에 대해 깊이 양해를 구하고자 합니다.
>>
저또한 개인적으로도 회사적으로도 해야할 일이 많고 가야할 길이 멉니다.
>>
 
>>
>>
>> >>
>>
>>
끝으로 저 뿐만 아니라 모두에게 노력과 결실이 함께 하기를 진심으로 기원합니다.
>>
>>
>>
>> 마지막 광고! (세미나 광고입니다)
>>
http://tech.devgear.co.kr/4408 
>>
>>
지난 3월 20명 고객 소규모 초청으로만 보여드렸던 3D 관련 세미나의 반응이 좋았으므로, 이번에 오픈 세미나로 다신 진행하게 된 것입니다. 여기에 더하여 기존 델파이 개발자가 아니라도 누구든지 Mac 이 무엇인지, 윈도우와 어떻게 다른지, 어떻게 개발해야 하는 지 궁금한 개발자라면 매우 도움되는 내용을 추가하여 세미나의 규모를 확장하였습니다. 
>>
>>
크리에이티브 커먼즈 라이센스
Creative Commons License
2012/04/20 16:19 2012/04/20 16:19

trackback :: http://blog.devquest.co.kr/imp/trackback/256

사용자 삽입 이미지
아시다시피, 엠바카데로의 한국 독점 총판인 데브기어에서 판매하는 가격은 미국이나 다른 국가들에 비해 무려 150%나 되는 바가지 가격입니다. 가장 많이 구매하는 델파이나 C++빌더의 XE2 엔터프라이즈 에디션을기준으로 미국내 가격은 1,999 달러, 즉 220만원 내외지만, 데브기어는 부가세 별도로 3,219,000원에 판매하고 있습니다. 국내 독점 영업권을 가지고 있다는 점을 이용한 파렴치한 행태죠.

제가 2008년에 데브기어에 2대 주주로 합류할 때의 기본 약속들 중 첫번째가, 당시 높아졌던 환율이 안정되면 가격을 달러 수준으로 다시 낮춘다는 것이었습니다. 하지만 박대표는 환율이 대폭 하락한 후로도 약속 이행을 계속 미루었고, 또한 델파이 마케팅을 위한 다른 약속들도 끝내 지키지 않았습니다. 결국 제가 애써 키워놓은 회사를 헐값에 넘기고 떠나게 되었죠. 하지만 그 이후로도 여전히 데브기어는 150%의 폭리를 유지하고 있습니다.

물론 국내 시장을 유지하기 위해 그런 정도의 높은 가격이 필수적이라면 납득할 수도 있겠지만, 그렇지 않습니다. 다른 모든 국내 소프트웨어 유통 채널들과 마찬가지로 데브기어도 미국내 소비자 가격에 비해 기본적으로 큰 할인을 받아 미국 가격과 동일하게 팔아도 충분한 합리적인 마진율을 가지고 있으며, 그 할인율은 최소 30%가 넘습니다. 하지만 데브기어는 거기에 다시 50%의 추가 이윤을 더 붙이고 있는 것입니다. 즉 데브기어가 델파이나 C++빌더를 미국에서 사와서 한국에 판매할 때의 마진율은 미국에서 들여온 가격 원가의 100%가 넘는 것입니다.

당연하게도, 소프트웨어는 현물이 아니기 때문에 물류비나 창고비 등 현물 상품일 경우 어쩔 수 없이 들어가는 비용들이 모두 제로입니다. 재고 부담도 전혀 없죠. 또 SW는 관세도 0% 입니다. 게다가 데브기어의 영업정책은 실제 제품을 받기도 전에 먼저 입금부터 해야 하는 선입금 방식만 고수하고 있기 때문에, 당연히 현금 회전에 대한 리스크도 전무입니다. 이런 시장에서 또 한국 전체에 대해 독점입니다. 그런데 그 마진율이 무려 100%가 넘습니다. 세상 어디에 이렇게 손쉽게 돈 벌어먹는 장사가 있을까요?

게다가 델파이와 C++빌더의 시장은 다른 개발툴로는 대체가 아주 어려운 고정 수요가 있습니다. 가격을 말도 안되게 올려놔도 울며 겨자먹기로 살 수 밖에 없는 시장입니다. 이게 데브기어가 수차례의 공개, 비공개 약속을 줄줄이 어기고 모르쇠로 바가지 가격을 고수하는 이유입니다. 바가지인줄 알면서도 울며 겨자먹기로 데브기어에서 살 수밖에 없었으니까요. 경제학 개념으로 가격탄력성이 거의 제로에 가깝다고 합니다. 결국, 여러분의 델파이와 C++빌더에 대한 애착이 데브기어의 불공정한 폭리에 대한 인질인 것입니다. 저와 여러분의 델파이와 C++빌더에 대한 사랑이, 데브기어의 배만 불리고 국내 델파이, C++빌더 시장에는 도리어 마이너스를 내는 상황인 것이죠.

그런데, 더 정확하게 말하자면, 델파이 혹은 C++빌더에 대한 계약은 여러분과 본사가 다이렉트로 하는 것이며 데브기어와 하는 것이 아닙니다. 여러분이 데브기어에 델파이나 C++빌더 주문을 한다고 해도, 법적으로는 데브기어로부터 구입하는 것이 아닙니다. 델파이와 C++빌더 구입 과정에서, 데브기어는 딱 두가지 역할을 하는데요. 첫번째 역할은 주문서를 대신 받아 본사로 전달해주는 것이고, 두번째 역할은 입금 받은 돈에서 대략 반 정도를 떼고 나머지를 본사로 송금하는 것입니다. 그러면 본사에서 여러분이 데브기어에 넘겼던 메일 주소로 직접 라이선스 메일이 날아오며, 그 메일을 받음으로써 본사와 여러분 사이의 계약이 체결된 것입니다.

일단 라이선스 메일을 받은 후에는, 주문을 중개한 데브기어와는 전혀 무관하게 여러분과 본사 사이의 계약이 체결된 것이므로, 여러분이 직접 본사에 기술지원을 요청할 수도 있고, 본사 사이트에서 배포판이나 업데이트를 다운로드할 수 있습니다. 물론 제가 데브기어에서 개발툴 사업 총괄을 맡고 있었을 때는 제가 본사로부터 인정받은 한국 에반젤리스트로서 본사의 기술지원 여부를 넘어서서 국내 개발자들을 지원했습니다만, 현재는 제가 알기로 데브기어에는 에반젤리스트가 없이 제가 아는 개발자 한 분이 파트타임으로 도움을 주고 있는 정도죠.

그럼 도대체 왜 데브기어를 통해서 구입해야 할까요? 둘도 없는 단 한가지 이유는, 본사의 사이트에서는 한국 개발자들이 온라인 구입을 하지 못하도록 막아놨기 때문입니다. 이것도 데브기어의 요청으로 이루어진 것입니다. 즉, 정상적으로 본사로부터 미국 개발자들과 동등한 가격으로 구입할 방법이 있는데, 그 루트를 고의적으로 막아놓고 150%라는 차별적인 바가지 가격을 매기고 있는 것입니다. 금액 면에서의 손해는 둘째 치고, 대한민국에서 살고 대한민국에서 일한다는 이유만으로 받는 이런 차별, 그것도 외국기업인 엠바카데로가 아닌 한국 기업인 데브기어로부터 이런 차별을 받는다는 사실, 기분 나쁘지 않으십니까?

이것이 제가 데브기어를 함께 창업하고 모든 노력을 기울여 키웠으면서도 더 많은 지분을 가진 대표와 싸우고 나올 수밖에 없었던 이유들 중 하나였습니다. 그러면, 언제까지나 한국의 델파이, C++빌더 개발자들은 미국이나 다른 나라들의 개발자들보다 훨씬 더 비싼 가격으로 구입해야 하는 차별적인 대우를 감내하는 것이 당연한 걸까요.


하지만 한국에서도 데브기어를 통하지 않고 델파이나 C++빌더를 미국에서 판매하는 가격과 같은 가격으로 구입하는 방법이 있습니다. 거의 알려져 있지 않습니다만, 아주 간단한 방법이며 또 개인 혹은 각각의 기업 입장에서 즉시 실행할 수도 있습니다. 뉴 유저 제품 뿐만 아니라 업그레이드도 가능하고 본사에서 하는 1+1 같은 이벤트 행사 혜택도 그대로 받을 수 있습니다.

다만 지금 현재로서는 완전히 공개하지는 않고, 개인적으로 문의해주시면 알려드리도록 하겠습니다. 문의하실 제 연락처는 이메일 Jeehoon.Imp.Park 골 gmail.com 입니다. 저로선 지금까지의 데브기어의 행태로 봐서는 또 어떤 황당한 일을 벌일지 불안하니, 제게 연락을 주실 때, 어떤 기업의 누구이신지 구체적으로 알려주시기를 부탁드리겠습니다.


지금 당장으로선 데브기어의 방해를 최대한 피하기 위해 개인 문의를 주신 분께만 알려드립니다만, 한두달 내로 전체 내용을 모두 공개하겠습니다. 제가 몸담고 모든 노력을 기울여 키웠던 회사이고, 또 제가 좋아하는 직원들이 많이 남아있습니다만, 이런 회사라면 더 존속할 필요도 없다고 생각되네요.
크리에이티브 커먼즈 라이센스
Creative Commons License
2012/04/17 14:11 2012/04/17 14:11

trackback :: http://blog.devquest.co.kr/imp/trackback/255

델파이나 C++빌더와 관련된 것은 아니지만... 좀 헤매다가 알게된 것이라서 공유하고자 블로그에 써봅니다.

최근에 사무실용으로 27인치 모니터를 새로 구입했는데요. 해상도가 2560x1440까지 나오더군요. 몇년동안 24인치에서 1920x1200 해상도에 만족하며 살았는데, 역시 견물생심이라고 해상도가 더 높으니 작업하는 맛이 확 다르네요. ^^

그런데, 생각하지 못했던 문제가 원격데스크톱으로 연결을 하면서 생겼습니다. 저는 로컬 시스템보다는 IDC에 있는 개인 서버에서 개발 등 모든 작업을 진행하는데요. 제 모든 자료들이 다 이 서버에 있죠. (이 글도 서버에서 쓰고 있는 겁니다) 그런데, 이 2560 해상도 모니터로 서버로 전체화면 모드로 원격데스크톱으로 연결했더니, 화면의 컬러수가 256 컬러로 툭 떨어져버리는 겁니다. 관리 목적의 서버라면 큰 문제가 아니겠지만 전 모든 작업을 하는 목적으로 서버에 연결하는 거라, 256 컬러는 심히 곤란하죠. 아시겠지만, 16비트 컬러만 되어도 그냥 그렇게 쓸만하지만, 8비트 컬러, 256 컬러는 아이콘이나 이미지 등등이 256컬러로 억지로 디더링되고 하면서 화면이 엉망이 되어버립니다. 디더링 팔레트가 바뀌면서 지맘대로 깜빡거리기도 하고요.

사용자 삽입 이미지

그래서 뒤지고 뒤지고... 그건 RDP에서는 안되는 거고 당연하다.. 어쩌구 이런 절망적인 멘트들만 보다가, 겨우 해결책을 찾았습니다. 마이크로소프트에서 그런 케이스를 접수하고 그에 대한 지원 케이스를 기록해놨던데... 정식으로 핫픽스를 내놓은 게 아니라 불평하는 사람에게만 제공하도록 되어 있더군요.

해당 고객지원 케이스의 링크는 아래와 같습니다.

The color depth is unexpectedly changed to 8-bit when a high screen-resolution setting is used in a terminal-server session that is connected to a Windows Server 2003-based computer
http://support.microsoft.com/kb/942610/en-us/ 

내용을 보자면... 윈도우 서버 2003에 원래 없던 문제였는데, 서비스팩2를 설치하고 나서 생긴 문제라는 겁니다. 한마디로 버그라는 얘긴데, 이걸 제대로 패치하지 않고 숨겨놨네요. 테스트도 제대로 안되었다고 하고요. (살떨리게시리... -.-;;) 그리고 위 링크의 상단에 보시다시피 핫픽스를 퍼블릭하게 공개하지 않고 이메일 요청을 해야만 보내주도록 해놨습니다. 헐.

어쨌든, 실제로 요청을 하면(이메일 주소 입력 필요), 잠시 후에 (자동으로) 다운로드 링크를 알려주는 메일이 날아옵니다. 이 핫픽스를 설치하면 되는데요. 실제로 시도를 하면, 다른 핫픽스(2570222)가 설치되지 않았다면서 설치가 안됩니다. 아래의 링크에서 다운로드할 수 있습니다.

Windows Server 2003 용 보안 업데이트(KB2570222)
http://www.microsoft.com/downloads/ko-kr/details.aspx?FamilyID=694ba1a6-7512-497d-a572-646a6e07b13b 

그런데, 위의 두 핫픽스를 모두 설치해도, 어쩐 일인지 지원 케이스에 써놓은 것과 달리 여전히 8비트로만 연결이 되더군요. 그래서 문서 내용을 하나씩 뜯어봤는데, 레지스트리 얘기가 있어서 혹시나 해서 레지스트리를 확인해봤습니다. 그랬더니 자동으로 레지스트리 값을 설정한다고 되어 있는데 실제로는 해당 값을 설정을 안해놨더군요. 레지스트리 에디터를 실행한 후, 아래와 같이 값을 설정해줘야 합니다.

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server
AllowHigherColorDepth 값을 0 -> 1로 변경

이렇게 한 후 리부팅하고 나니 원격 연결에서 2560x1440 해상도에서 제대로 24비트 컬러가 나오더군요. 아래와 같이 말이죠. ^^
사용자 삽입 이미지

아, 윈도우 2003 서버로 원격데스크탑 연결을 하면 기본적으로는 16비트 컬러로 나오는데요(위와 같은 고해상도에서 컬러가 떨어지는 경우 제외). 최대 24비트 컬러로 설정할 수 있습니다. 원격데스크톱 클라이언트에서 컬러수를 지정하는 옵션이 있긴 하지만, 이건 단지 연결할 때 서버로 요청하는 옵션인 거고, 실제로 어떤 해상도로 보여줄 것인가는 서버측 터미널서버가 결정합니다. 그 옵션은 아래의 위치에 있습니다.

관리 도구 -> 터미널 서버 구성 -> 왼쪽 트리의 "연결" 선택 -> 오른쪽 창에서 "RDP-Tcp" 더블클릭 -> "클라이언트 설정" 탭 -> "최대 색 농도 제한" 값 변경
사용자 삽입 이미지

이 "최대 색 농도 제한"의 기본값이 16비트로 되어 있습니다. 이걸 24비트로 바꾸거나, 혹은 체크박스의 체크를 지워버리면 됩니다. 그럼 원격데스크톱을 16비트 컬러가 아닌 24비트 컬러로 볼 수 있게 됩니다. 이 설정 변경은 리부팅하지 않더라도 단지 원격 연결을 끊었다가 다시 연결하기만 하면 적용됩니다.
크리에이티브 커먼즈 라이센스
Creative Commons License
2012/03/10 01:24 2012/03/10 01:24

trackback :: http://blog.devquest.co.kr/imp/trackback/253

현재의 집필 상황에 대해 문의를 주시는 분들이 적지 않아서, 집필 상황에 대해 한번 알려드리겠습니다.

현재 2권의 델파이 책을 동시에 집필하고 있습니다. 하나는 이전에 번역했던 델파이 프로그래밍 언어의 개정판, 다른 하나는 델파이 초중급서입니다. 그리고 이 책들이 출간된 이후에 집필을 진행할 3권의 책이 기획중입니다.

지난해부터 진행하던 초중급서가 늦어진 이유는 물론 돈벌이에 바쁘다보니 그런 건데... 최근에 출판사 한군데에서 요청이 와서 그쪽 출판사로 출간하기 위해 계획을 다시 검토하고 있습니다. 기획서, 목차부터 다시 쓰고 있구요. 물론 기존 원고는 상당부분 활용하게 되니까 시간이 엄청나게 많이 걸리진 않을 것입니다. 물론 착수 당시에 받았던 여러 의견들도 그대로 유지할 거구요.

"델파이 프로그래밍 언어" 개정판은 같은 출판사에 맡길지 아님 제가 직접 출판할지 아직 맘을 정하지 않았는데, 개정 내용은 물론 델파이 최신 버전의 컴파일러에 추가된 기능들이 추가로 들어가지만, 그 외에 역주라고 할까, 설명이나 샘플 등을 추가해서 편저 형태가 될 것 같습니다. 지나치게 딱딱했던 초판에 비하면 좀 부드러워지는 셈이죠.

사용자 삽입 이미지
현재로서는 "델파이 프로그래밍 언어"의 작업 시간이 좀 더 많이 들어가는 편인데, 다음달 출판사와 초중급서쪽의 초기 기획이 완료되는 대로 초중급서쪽에 90% 이상의 시간을 들일 계획입니다.

장기 과제인 3권은 특정 주제가 있는 것들인데, 하나는 번역이고 다른 둘은 집필입니다. 번역서는 "델파이 프로그래밍 언어"의 연장선상에서 더 깊이 들어가는 거라고 할 수 있겠고요. 다른 두권은 델파이 활용서입니다.

좀더 자세하게 알려드리지 못하는 점은 좀 아쉽지만... 솔직히 말하자면 책을 집필하고 출간하는 것은 들어가는 노력에 비해 너무 턱도 없이 적은 보상이 돌아오기 때문에, 더 급하거나 중요한 일들이 발생하면 자꾸 늘어질 수도 있고 장기 계획은 취소될 수도 있는 것이 어쩔 수 없는 현실이랍니다.

C++빌더 책은? 델파이 초중급서가 나오고 나면 그 내용을 기초로 C++빌더 초중급서를 만들 예정입니다. 물론 델파이와 C++빌더가 비슷한 면들이 많기는 하지만 결정적으로 언어의 차이가 생각보다 크기 때문에, 언어와 조금이라도 관계되는 부분은 물론 완전히 새로 써야 합니다. 그래도 델파이 초중급서 원고가 없는 것보다는 몇배 빠르게 진행할 수 있을 거라고 추측합니다.

제가 확실히 약속드릴 수 있는 것은, 적어도 몇년전처럼 출판해줄 출판사가 없어서 책을 쓰지도 못하는 일은 전혀 없다는 것입니다. 현재 접촉중인 IT전문 출판사도 대단히 적극적이고, 저 자신도 출판 전체 업무를 직접 경험해봤고 책을 직접 출판해서 적어도 제가 금전적으로 손해는 보지 않을 자신이 있기 때문에, 시간의 문제일 뿐 반드시 출간됩니다.
크리에이티브 커먼즈 라이센스
Creative Commons License
2012/02/22 05:35 2012/02/22 05:35

trackback :: http://blog.devquest.co.kr/imp/trackback/252

사용자 삽입 이미지
방송통신위원회 산하 협회인 한국정보통신진흥협회의 격월간 협회지인 "이음n울림" 11-12월호에 기고한 기사입니다. 정보통신진흥협회는 방통위의 관할 사업들 상당수를 집행하는 곳으로, 국내 주요 통신사들과 IT 업체들(주로 통신 및 HW쪽)이 회원사로 속해 있습니다.

기사의 주제는... 이전에 블로그에도 올렸던 액티브X의 문제점과 개선방안에 대한 내용을 좀 더 발전시킨 것인데... 원래 초고에는 방통위를 비롯한 정부기관들을 신나게 비판하는 내용들이 있었으나, 원고를 청탁한 곳이 방통위의 산하 단체인지라... 초고 완성 이후 그쪽의 요청으로 정부 비판 부분은 삭제되고 순화되었네요. -.-;;;; 왜 방통위에 비판적인 제게 기사 청탁을 했는지 도대체 모르겠습니다만...

아마도 실제로 그럴 분은 없겠지만, 정보통신진흥협회 홈페이지에서 이 기사를 보시려면, 협회 홈페이지 http://www.kait.or.kr/ 에서 "e-Book 바로가기"를 클릭하시면 됩니다.

아래 첨부파일을 클릭하시면 기사를 pdf 포맷으로 볼 수 있습니다.


크리에이티브 커먼즈 라이센스
Creative Commons License
2011/12/08 05:37 2011/12/08 05:37
TAG

trackback :: http://blog.devquest.co.kr/imp/trackback/250