이 글은, 예전에 작성한 글을 옮기던 중, 해당 글1의 유통 기한(?)이 지났음을 확인하고, 내용을 간추려 새로 정리한 글이다.
Function.apply()
Function.apply()는 일반적으로 위임 호출을 구현하는데 사용하지만, 또 한 편으로는 String.fromCharCode(...parameters):String 과 같은, ... rest 형식의 파라미터를 받는 함수의 퍼포먼스 튜닝에 사용되기도 한다.
아래의 예를 보자.
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/halo"
minWidth="1024" minHeight="768"
applicationComplete="applicationCompleteHandler(event)">
<s:layout>
<s:VerticalLayout/>
</s:layout>
<s:Label text="firstTry() 수행 시간 - {firstElapsedTime} ms"/>
<s:Label text="secondTry() 수행 시간 - {secondElapsedTime} ms"/>
<fx:Script>
<![CDATA[
import mx.events.FlexEvent;
private const length:int = 1000000;
private const char:int = 97;
[Bindable] private var firstElapsedTime:int;
[Bindable] private var secondElapsedTime:int;
protected function applicationCompleteHandler(event:FlexEvent):void
{
var time:int;
time = getTimer(); // apply를 이용한 테스트
firstTry();
firstElapsedTime = getTimer() - time; // 70ms
time = getTimer(); // concat을 이용한 테스트
secondTry();
secondElapsedTime = getTimer() - time; // 485ms
}
private function firstTry():String
{
var array:Array = [];
// 생성
for (var i:int = 0; i < length; i++)
array[i] = char;
// 병합
return String.fromCharCode.apply(this, array);
}
private function secondTry():String
{
var string:String = "";
for (var i:int = 0; i < length; i++)
// 생성과 동시에 병합
string += String.fromCharCode(char);
return string;
}
]]>
</fx:Script>
</s:Application>
위 예를 보면, fromCharCode()를 여러번 호출하는 것 보다, 배열에 일단 저장한 뒤, Function.apply()를 이용하여 한 번에 호출하는 것이 빠르다.
성능 향상의 원리
성능이 향상되는 원리는 매우 간단하다. 함수의 호출 횟수가 줄어들었기 때문이다. 더 정확히 말하면, 내부적으로 중복하여 실행되는 코드를 한 번만 실행하도록 한 것이다.
여기서, 내부적으로 중복 실행되는 코드란, String.fromCharCode() 실행 시, 입력받은 파라미터의 수와는 상관없이 항상 수행되는, String의 메모리 할당을 예로 들 수 있다.
일례로, Math.max()로 테스트를 해보면, 물론 약간의 성능 차이는 있으나, 그다지 큰 차이를 보이지는 않는다. 입력받은 파라미터를 앞에서부터 하나씩 비교하여 큰 값을 남기는 로직만 있을 뿐, 중복해서 실행되는 코드는 거의 없기 때문이다.
실제 사용 예
이와 같은 튜닝은, 암호화 알고리즘과 같은, 특히 캐릭터 단위의 문자열 연산이 빈번하게 이루어지는 곳에서 가장 많이 사용한다. 현재 Flex SDK 내에서는, Base64Encoder와 StringUtil, UIDUtil 등에서 사용하고 있다.
주의할 점
이 튜닝 기법을 도입할 때 주의할 점은, String.concat()과 같은 함수는 += 라는 operator를 지원한다는 것이다. 일반적으로 Flash Player에서 operator는 함수 호출에 비해 속도가 상당히 빠른 편이어서, 이러한 경우에는 Function.apply()를 한 번 실행하는 것보다 +=를 여러번의 실행하는 것이 더 빠른 경우가 많다.2
또한, Flash Player마다, 하나의 메소드에서 한 번에 호출할 수 있는 최대 파라미터 갯수가 정해져있다. 즉, Function.apply()에서 parameters로 넣을 Array의 길이가 일정 이상을 초과하면, 에러가 발생한다. 그렇기 때문에, 일반적으로는 buffer를 이용하여 구현을 하게 된다. (자세한 구현 방법은 mx.utils.Base64Encoder 클래스 등의 내부 구현을 참조)
그뿐 아니라, 각각의 성능은 Flash Player마다 다를 수 있고, 또한 버전이 올라감에 따라서도 언제든지 변할 수 있으므로, 항상 적용 전에 확인을 거쳐야 한다.3
결론
Function.apply()를 이용하면, 의외의 곳(?)에서 성능 향상을 얻을 수 있다. 단, 성능 튜닝이 꼭 필요한 시점에만 진행하자.4
Reference
- 2008/06/12 - [Flex] - Function.apply를 이용한 퍼포먼스 튜닝 - 옛버전
- 2009/11/28 - [Flex] - 성능을 논하기 전에
- 2007/05/06 - [Software Engineering] - 성급한 퍼포먼스 튜닝
'Adobe Flash Platform > Flex' 카테고리의 다른 글
| Array.indexOf()가 괴롭힐 때 (0) | 2009/11/28 |
|---|---|
| 성능을 논하기 전에 (0) | 2009/11/28 |
| Function.apply()를 이용한 퍼포먼스 튜닝 (3) | 2009/11/27 |
| 커스텀 메타데이터 태그 (22) | 2009/11/26 |
| FlexUnit 4, Ant Task 지원 (4) | 2009/11/26 |
| int와 uint, 그리고 Number (2) | 2009/11/23 |




댓글을 달아 주세요
인자가 ... 시리즈인 경우에 사용하는 테크닉이군요 ^^
정말이지 ... 인자의 경우엔 apply나 call이 약인듯.
개인적으로는 컴파일에러체크가 안되어서 ...을 거의 사용하지 않지만 기저api자체가 이렇게 생긴 경우엔 유용한 경우가 많은듯해용 ^^
네. 저도 ...args 형태의 파라미터를 받는 메소드는 가능한 피하고 있어요.
위의 예시도 사실은 그리 적절치 않은 것이, ByteArray를 이용하면 가독성과 성능 모두를 꾀할 수 있는.. ㅎㅎ
하지만 또 한편으로는, 성능 향상이 꼭 필요한 시점에, 중복 호출의 오버헤드가 크면서, 동시에 다른 대안이 없는 상황은 언제든지 발생할 수 있고, 또 상황에 따라서는, 기존 메소드를 ...args 형태로 변경하게 될 수도 있으니..
사실 이번 글은, 마무리하면서 '과연 이 글이 가치가 있는 글일까'라는 고민을 많이 했어요.
특히 최근 제가 퍼포먼스와 관련한 글을 너무 빈번히 작성하는 것 같아서요. ㅎㅎ (아직 보이진 않지만, 글 등록 대기열에 잔뜩..)
퍼포먼스는 결국 최후(?)에 고려되어야 할 사항이라.. 자칫 Flex를 처음 익히시는 분들께 악영향이 가지 않을까하는 걱정이 드네요.
일단 유통 기한이 지난 이전 글을 업데이트하는 측면에서, 또 약간의 잡지식은 도움이 될 수 있다는 생각에 등록 ㅎㅎ