윈도우 환경에서 Eclipse Galileo 버전으로 C, C++ 개발을 위한 환경을 만드는 것을 설명하는데 목표가 있다. 기존에 Eclipse기반으로 Flash Builder 플러그인을 설치해 Flash 개발을 하거나 Java 개발하시는 분들이 같은 환경에서 C, C++을 개발하고자 한다면 이 글은 유용한 팁정도가 될 것이다.


1. MinGW를 설치한다.
MinGW(한국어 발음 밍우?)는 무료로 쓰고 배포할 수 있는 MS 윈도우 헤더 파일과 라이브러리로, 어떠한 써드 파티 C 런타임 DLL에 의존하지 않고 네이티브 윈도우 프로그램을 만들 수 있는 GNU 툴을 제공한다. 쉽게 이야기해 MinGW는 윈도우에서 툴이나 dll에 의존하지 않는 동작하는 프로그램을 만들 수 있도록 도와준다.

MinGW에 대해 : http://ko.wikipedia.org/wiki/MinGW 

물론 C/C++를 개발하기 위해 Cygwin을 이용해도 된다. 하지만 cygwin 기반으로 제작한 것은 항상 cygwin1.dll이 필요한다. 또한 개발한 결과물은 라이센스 문제로 상용으로 팔기가 곤란하다. 게다가 윈도우에서 직접 개발한다기 보다 가상의 리눅스 콘솔을 이용하는 것이다. 그래서 여러가지로 MinGW이 장점이 많다.


1.1 MinGW 받기
공식 사이트 http://www.mingw.org/ 로 간다. 좌측 메뉴에 Downloads 페이지로 이동한다. Download Now 버튼을 눌러 최신버전인 MinGW-5.1.6.exe(2010.04.20)를 다운로드 받는다.


1.2 MinGW 설치
  • 다운받은 MinGW-5.1.6.exe를 실행한다.
  • Welcome 화면 Next를 클릭한다.
  • Download and install을 선택후 Next 버튼을 클릭한다.
  • I agree를 클릭한다.
  • Candidate를 선택후 Next를 클릭한다.
  • 설치하고자 하는 것을 선택후 Next를 클릭한다. g++ compiler와 MinGW Make는 반드시 선택한다.
  • 설치하고자 하는 디렉토를 선택한 다음 Next 클릭
  • Install을 클릭하면 이제 필요한 것을 다운로드 받아 설치를 시작한다.
  • 설치가 완료되면 Next 클릭후 Finish를 누른다.

 

1.3 GCC를 실행하기 위한 윈도우 환경변수 설정하기

이제 어느 경로에 있던지 gcc 컴파일러를 실행할 수 있도록 환경을 조성해줄 필요가 있다.

이 방법은 Cygwin을 사용하지 않고 오로지 MinGW만 사용하는 경우에 해당한다. 만약 Cygwin을 함께 사용하시는 분이라면 배치파일을 만들어 사용하는 방법도 있다. (http://kldp.org/node/48962)

  • 제어판에서 시스템 폴더 클릭. window 7환경에서 개발한다면 검색창에서 시스템을 입력한뒤 시스템 환경 변수 편집을 선택한다.
  • 아래 표시된 방법대로 입력하면 되겠다. 최종적으로 MinGW 설치된 폴더에 Bin 폴더를 입력하는 것을 목표로 한다.


  • CMD창에서 gcc --version과 mingw32-make를 입력하고 다음과 같이 나오면 성공적으로 설치된것임

 이제 MinGW가 설치되었으므로 window 기반에서 C, C++등을 개발할 수 있는 환경이 만들어진 것이다. 다음에 나오는 Eclipse기반이 아니더라도 메모장에서 C코드를 짜고 GCC로 컴파일할 수 있다. 하지만 개발툴을 메모장을 사용할 수는 없는 노릇아닌가?


2. Eclipse Galileo 버전을 설치한다.
Eclipse는 기존에 설치했던 사람이 대부분일 것이다. 이 설명은 필요없는 내용일 수 있으나 그냥 적어본다.

일단 Eclipse는 아래 링크에서 자신의 개발하고자 하는 목적별(Java, C/C++, PHP등)로 다운로드 받아 설치할 수 있다.

http://www.eclipse.org/downloads/

필자는 Java기반에서 개발하는 일이 많으므로 Java EE Developers를 위한 Eclipse IDE를 설치했다. Windows 32bit 기반을 다운로드 받았다. 설치는 받은 압축파일을 원하는 곳에 압축만 풀어주는 것으로 완료가 된다. 본인은 E:\eclipse 에 설치했다. C 드라이브에 설치하지 않은 이유는 나중에 운영체제를 다시 설치하는 경우에 Eclipse를 보존하기 위함이다.

Eclipse를 처음 설치하는 사람이라면 반드시 JRE가 자신의 컴퓨터에 미리 설치가 되어 있어야 Eclipse 구동이 가능하다. 다음 링크에서 JRE나 JDK 최신버전을 설치하면 되겠다.

http://java.sun.com/javase/downloads/index.jsp

만약 C/C++기반인 Eclipse를 다운로드 받아 설치하면 다음에 "3. CDT 플러그인을 설치한다"를 넘겨도 된다.


3. CDT 플러그 인을 설치한다.

CDT는 C/C++ Development Tool이다. 기존에 이클립스 기반으로 개발하던 사람이라면 C/C++도 Visual Studio와 같은 툴을 활용하지 않고 개발하고자 하는 욕구(?)가 들지도 모르겠다. CDT를 Eclipse에 설치하면 C,C++ 개발이 가능한 환경이 된다.

CDT는 개발하는 툴이지 컴파일러가 아니다. CDT를 설치했더라도 각각 운영체제 기반에서 제공하는 C/C++ 컴파일러와 연결하는 작업은 필요하다. 여기서는 CDT를 Eclipse에 설치하는 방법만 소개한다.

Eclipse 메뉴에서 Help > Install New Software... 를 선택한다.

아래와 같은 화면이 나오면 Add 버튼을 누른다.


http://www.eclipse.org/cdt/downloads.php 에 가면 최신버전(현재 CDT 6.0.x)을 다운로드 받을 수 있는 링크가 소개되어 있다. Eclipse가 Galileo버전이므로 http://download.eclipse.org/tools/cdt/releases/galileo 를 URL로 삼아 플러그인을 설치하면 되겠다.

아래처럼 Add Site창에 Name과 Location을 입력한다. Name은 아무거나 입력하면 된다.


참고로 위처럼 등록하면 Windews > Preferences > Install/Update > Avaliable Software Sites에 아래처럼 등록되어 있다.


이제 Work with 란에 CDT를 입력해보면 금방 등록했던 CDT 정보를 선택할 수 있다. 그럼 아래처럼 나온다. CDT Main Features, CDT Optional Features 모두 Check하고 Next버튼을 누른다.


아래처럼 설치할 목록들이 나온다. Next버튼을 누른다.



아래와 같은 화면이 나오면 I accept the terms of the license agreements를 선택후 Finish 버튼을 누르면 설를 시작한다. 시작이 완료된 다음에는 Eclipse를 재구동한다.



Eclipse가 구동된 다음 C/C++ Perspective를 열어보자. Windows>Open Perspective>Other 를 선택한다. 아래처럼 창이 뜨면 C/C++를 선택한다. 
 

Perspective창에 아래처럼 추가 된것을 확인하자. 이제 Eclipse에서

File > New를 가도 C, C++ 프로젝트를 만들 수 있게 되었다.


4. 간단한 C/C++ 개발해보기

MinGW의 bin 폴더에 보면 gcc, g++등 각종 컴파일 도구가 있다. 이클립스의 CDT환경에서 C/C++로 개발할때는 MinGW의 컴파일 도구를 이용해 컴파일 한다는 사실을 기억하자.

다음과 같이 C++ 프로젝트를 만들어보자.
  • 이클립스 메뉴에서 File > New > C++ Project를 선택한다.
  • 다음 그림과 같이 프로젝트를 만들기 위한 창이 나오면 Project name을 넣고 Project type을 Executable > Hello World C++ Project를 선택한다음 Toolchains를 MinGW GCC로 선택하도록 한다. 이렇게 하면 자동으로 Hello World CPP가 만들어지면서 컴파일은 MinGW의 g++컴파일러를 이용해 컴파일 할 수 있도록 설정 되는 것이다. Next 버튼을 누른다.
  • 간단한 셋팅을 하는데 이 설정에서 중요한 것은 Source 파일은 src 폴더에 들어가게 된다는 것이다. Next 버튼을 누른다.


  • 다음 그림과 같이 Debug, Release 두 버전으로 프로그램 결과를 별 수 있도록 셋팅된다. Finish 버튼을 누른다.

 

이클립스의 Project > Build Automatically가 선택되어 있다면 위처럼 프로젝트를 만들게 되면 자동으로 MinGW의 g++을 찾아 컴파일을 실시하게 된다. 아래와 같은 메시지가 이클립스의 Console창에 나오면 제대로 실행된 것이다.


위 메시지 처럼 컴파일을 하지 못하고  아래처럼 Nothing to build for Test1 메시지가 뜬다면 이것은 수정된 내용이 없으므로 컴파일할 필요가 없다는 것을 의미한다. 대신 Ctrl+B 하면 강제로 컴파일한다.


이 프로젝트는 C++ 프로젝트이므로 gcc 컴파일러가 연결되어야 한다. 만약 컴파일에 실패한다면 Mingw/bin에 gcc.exe를 확인해보자. 만약에 없다면 위에 Mingw 설치하기를 다시 한번 따라해보길 바란다.

실행해보자. 아래처럼 만들어진 프로젝트의 src폴더를 열어 Test1.cpp를 선택한다. 그리고 빨간 박스로 표시된 아이콘을 클릭하거나 Ctrl+F11을 하면 실행할 수 있다.

Console창에 아래처럼 Hello World가 출력된것을 확인하자.

Build Automatically를 체크한 경우에는 코드를 수정하고 저장할 때마다 자동 Build처리된다. 하지만 수동으로도 할 수 있는데 Ctrl+B 또는 아래 그림처럼 망치툴을 클릭해서 Build할 수 있다.



개발시에는 Debug버전과 Release 버전이 있다는 것을 기억하자. 아래 보이는 도구는 현재 개발 환경이 Debug인지 Release인지 결정해 주는 도구이다. Debug는 개발용이고 Release는 외부로 배포할때 사용한다.


실제로 이 도구를 이용해 Debug에서 Release로 바꿔보자. 아래처럼 Release 폴더가 생겨나고 컴파일 결과가 이 폴더안에 만들어진다. 이때부터는 실행할때 Release폴더에 있는 exe파일이 실행되게 된다.


참고로 makefile을 기반으로한 개발을 하고 싶은 경우가 있을 수 있다. 이런 경우에는 C++ 프로젝트를 생성시에 아래 그림처럼 Makefile Project를 선택해서 생성해야한다.

하지만 실제로 이 상태로 프로젝트를 생성해 컴파일하면 Cannot run program "make": Launching failed 메시지가 console창에 보이면서 컴파일이 불가능하다. 이 메시지는 컴파일을 수행할 수 있는 make 명령어를 찾지 못한다는 것을 의미한다. Eclipse에서는 기본적으로 make가 지정되어 있지만 MinGW의 make 명령은 mingw32-make로 다르다. 그러므로 이 문제를 해결하기 위해 mingw32-make.exe를 복사해 같은 폴더에 make.exe로 이름을 바꿔주면 된다. (물론 찾아보면 다른 방법도 있지만 이 방법이 가장 빠르고 쉽다.)

makefile을 이용해 개발해보는 간단한 예제는 다음글을 참고한다.
http://kkamagui.springnote.com/pages/446531


5. 간단한 win32용 프로그램 개발하기
지금까지의 예제는 단순히 Console창에 결과가 나온다. MinGW는 Windows API를 이용한 개발도 지원하므로 앞서 설명한 같은 C++ 프로젝트로 만들고 아래 코드로 변경해도 컴파일이 가능하다.


#include <windows.h>

int WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
        MessageBox(NULL, "Hello in EclipseCDT", "EclipseCDT", MB_OK);
        return 0;
}


실행해 보면 아래와 같은 다이얼로그 창이 나온다.

경험자들에 따르면 Win32 로 개발하는 경우에는 한가지 중요한 C++ 링커 설정을 해야한다고 한다.

먼저 해당 Win32 프로젝트를 선택후(필자의 경우 Test3) 이클립스 메뉴에서 Project > Properies로 들어갑니다. 아래처럼 창이 뜨면 C/C++ Build > Setting으로 들어가 Tool Setting 탭을 선택합니다. 거기에 MinGW C++ Linker>Miscellaneous를 선택해 Linker flags에 -mwindows를 입력한다. 완료하면 Apply버튼이나 OK 버튼을 누르면 적용된다.

이 설정을 해야 군데 군데 "undefined reference"와 같은 에러를 발생시키지 않는다고 한다. 수정했는데도 에러를 띄우면 File->Save all 또는 이클립스 종료후 다시 시작하면 된다.

Windows API 학습을 위해 다음 링크를 참고한다.
http://www.winapi.co.kr/

6. 디버깅 환경 만들기
지금까지 개발환경으로 개발자체는 문제 없지만 디버깅은 할 수 없다. MinGW에 디버깅을 하기 위한 도구가 설치되어야 하는데 이를 가능하게 하는 것이 gdb이다.

먼저 gdb를 다운로드 받자.
http://downloads.sourceforge.net/project/mingw/GNU%20Source-Level%20Debugger/GDB-7.1/gdb-7.1-2-mingw32-bin.tar.gz

압축을 풀고 bin과 share 폴더를 그대로 설치된 MinGW 폴더에 복사해준다. 이것으로 MinGW의 C/C++ 디버깅 환경이 구축되었다.

이제 Eclipse에서 F11 누르거나 아래처럼 벌레모양의 아이콘을 눌러 Debugging을 할 수 있게 된다.


디버깅을 하게 되면 Debug Perspective창으로 바뀌면서 아래처럼 디버깅이 가능해진다.

참고로, 이클립스에서 디버깅 설정은 Run > Debug Configurations에서 할 수 있다. 좌측 리스트에서 C/C++ Application을 선택해 해당 exe를 선택한뒤 우측에서 Debugger 탭을 선택하면 Debugger를 선택할 수 있게 된다.

만약 gdb를 설치했음에도 불구하고 에러를 내면서 디버깅을 할 수 없는 경우 CMD창에서 다음과 같이 입력해보길 바란다.



만약 위처럼 메시지가 나오지 않고 libexpat-1.dll이 없다고 경고창이 나오면 현재 설치한 gdb버전이 libexpat-1.dll과 의존성이 있는 것이다. 이때는 libexpat-1.dll도 함께 다운로드 받아 MingGW/bin에 복사해줘야 한다.

libexpat-1.dll은 아래 링크에서 다운로드 받는다.
http://downloads.sourceforge.net/project/mingw/MinGW%20expat/expat-2.0.1-1/libexpat-2.0.1-1-mingw32-dll-1.tar.gz

다음 글을 참고한다.
http://forums.codeblocks.org/index.php/topic,11301.msg77273.html

7. 정리하며
프로젝트 중에 C와 MySQL을 연동하여 개발할 경우가 있어서 전체 환경설정을 정리한다는 마음으로 글을 적었다. 기존에 나와 있는 많은 글들이 너무 옛날 버전이라서 그런지 지금과 맞지 않는 부분도 있었다. 앞으로 MySQL과 연동하게 되면 관련 내용도 소개해 볼까 한다. 개발에 있어서 환경을 구축하고 적응하는게 반인 것 같다. ^^;


8. 참고글

이클립스(Eclipse) CDT 설치
(Eclipse Galileo) C/C++ Developments User Guide
CDT를 이용한 Windows C/C++ 개발 환경 만들기
Eclipse/CDT
Eclipse Project CDT (C/C++) Plugin Tutorial 1, 2By Brian Lee
이클립스(eclipse)를 이용해서 C/C++ 프로그래밍 환경설정
MinGW + Eclipse 를 이용한 Windows 개발 환경 구현

글쓴이 : 지돌스타(http://blog.jidolstar.com/677)

빵집 개발자 양병규님께서 이 글의 이전 글인 Adobe Alchemy 속도 테스트에 대해 이런 답변을 달아주셨다.


컴파일러에 대해서 조금 관심을 가져보시면 알 수 있을텐데요…
음… 뭐랄까…
그 최적화라는 것이 불필요한 일은 안하고 같은 기능이라면 더 빠른 기능으로 대치하고.. 그런일을 하는 것이 컴파일러의 최적화인데요..
제가 보기에는
for( i=0; i < 1000000; i++ )
{
result = sin( 0.332 ) + cos( 0.123 ) + tan(0.333) * log(3);
}
이 부분이.. 최적화를 하면 루프를 1000000번 돌지 않고 한방에 끝낼것같습니다. 최적화알고리즘중에서 루프문안에서의 내용이 반복을 해도 변화가 없는 경우에는 루프를 한방에 끝내도록 하는 알고리즘이 있습니다. 이 예가 그런 예일것 같은데요..

암튼.. ActionScript에는 그런 최적화가 없는데 이미 오랜 역사동안 만들어진 C/C++언어의 최적화 기술이 적용되면 분명히 나은 결과를 보일 것 으로 예상됩니다. ^^;(물론 코딩하는 사람의 실력이 좋을 수록 최적화의 효과가 떨어지겠지만)

그리고..
저는 아무리 봐도 C/C++로 코딩한 것이 결국 몽땅 ActionScript로 만들어지는 것이 맞는 것 같습니다. SWF에는 ActionScript 이 외에 다른 실행코드는 존재하지 않습니다. 최종적으로 만들어진 결과가 *.SWF(*.SWC)이지 않습니까? SWF File Format Specification Version 10 문서 278페이지를 다 훑어봐도 ActionScript 이외에는 실행코드가 없습니다. 머.. DLL을 임포트한다거나 별도의 실행코드용 외부 라이브러리가 있다거나… 그런건 전혀 없습니다.

malloc 함수 같은 경우의 예를 들어보면 ActionScript에는 malloc과 동일한 역할을 하는 어셈블명령은 없습니다. 하지만 ActionInitArray와 같이 긴 변수를 사용할 수 있는 명령으로 ‘구현’은 할 수 있을겁니다. 아마도 그런 방법으로 구현되지 않았나싶습니다.

어쨋든 Alchemy는 C/C++을 100% ActionScript로 변환해 주는게 맞고.. 함수사용, 변수사용, 최적화, 수학함수의 구현, 각종 기초 알고리즘등 기초적인 부분에서 훨씬 우월하기 때문에 좋은 효과를 나타내고 있는 것으로 생각됩니다.

시간이 나면 Alchemy에서 만들어진 SWF 파일을 분석해 보면 아주 좋은 공부가 될것같습니다. ^^;

양병규님의 의견중 최적화에 대한 것을 다시 살펴보자면 -O3 옵션으로 인해 최적화 알고리즘이 적용되어 for문이 없어지고 result = sin( 0.332 ) + cos( 0.123 ) + tan(0.333) * log(3); 만 남는다는 것을 지적하고 ActionScript 3.0에는 이런 최적화 기능이 없기 때문에 실제로 돌려보면 C코드는 1번만 수행하는 반면 ActionScript 3.0에서는 원래대로 1백만번 루프를 돈다는 것이다.

 

또한 여러 상황에 비추어볼때 Alchemy는 C/C++ 코드를 100% ActionScript 3.0으로 변경하는게 맞다고 말씀해 주었다.

 

이 의견을 읽고 맞는 말인 것 같다고 생각해서 코드를 다음과 같이 수정했다.

 

1. C 코드 최적화를 방지해서 다시 속도 테스트

speedtest.c

#include <math.h>
#include <stdlib.h>
#include "AS3.h"

static AS3_Val c_speed_test(void* self, AS3_Val args)
{
    int i;
    double result = 0;
    for( i=0; i < 10000000; i++ )
    {
        result += sin( i ) * cos( i );
    }
    return AS3_Number(result);
}

int main()
{
    AS3_Val method = AS3_Function( NULL, c_speed_test );
    AS3_Val result = AS3_Object( "c_speed_test: AS3ValType", method );
    AS3_Release( method );
    AS3_LibInit( result );
    return 0;
}

 

AlchemySpeedTest.as

package {
    import cmodule.speedtest.CLibInit;

    import flash.display.Sprite;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.text.TextField;
    import flash.text.TextFieldAutoSize;
    import flash.text.TextFormat;
    import flash.utils.getTimer;

    public class AlchemySpeedTest extends Sprite
    {
        public function AlchemySpeedTest()
        {
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.align = StageAlign.TOP_LEFT;

            var loader:CLibInit = new CLibInit();
            var lib:Object = loader.init();
            var startTime:Number;

            startTime = getTimer();
            var cResult:String = "c returned value : " + lib.c_speed_test(null) + " delay time(ms) : " + (getTimer()-startTime);

            startTime = getTimer();
            var asResult:String = "as3 returned value : " + as3_speed_test() + " delay time(ms) : " + (getTimer()-startTime);

            var textField:TextField = new TextField();
            textField.text = cResult + "\n" + asResult;
            textField.width = 500;
            textField.autoSize = TextFieldAutoSize.LEFT;
            textField.wordWrap = true;
            textField.defaultTextFormat = new TextFormat( null, 12 );
            addChild(textField);

        }
        public function as3_speed_test():Number
        {
            var i:int;
            var result:Number = 0;
            for (i = 0; i < 10000000; i++)
            {
                result += Math.sin( i ) + Math.cos( i );
            }
            return result;
        }
    }
}

 

이 코드에서 달라진 것은 gcc로 c를 컴파일 할 때 -O3와 같은 최적화 알고리즘 적용을 하더라도 전과 같이 for문이 삭제되는 것을 방지하도록 했다.

 

이전 코드

for( i=0; i < 1000000; i++ )
{
        result = sin( 0.332 ) + cos( 0.123 ) + tan(0.333) * log(3);
}

 

새로 바꾼 코드

for( i=0; i < 10000000; i++ )
{
        result += sin( i ) * cos( i );
}

새로 바꾼 코드는 sin(), cos()함수에 계속 변화되는 값을 넣어주었고 그 결과가 누적되도록 했다. 이로써 최적화를 하더라도 for문이 적용되지 않는 경우는 없을 것이다.

컴파일은 아래와 같이 한다.

gcc speedtest.c –03 –Wall –swc –o speedtest.swc
mxmlc.exe –library-path+=speedtest.swc –target-player=10.0.0 AlchemySpeedTest.as

결과는 다음과 같다.

c returned value : 0.24755567269650028 delay time(ms) : 3425
as3 returned value : 2.873882594355213 delay time(ms) : 3184

예상대로 gcc –O3 로 인해 최적화가 적용되었던 것이다. 양병규 님의 말에 힘이 실리는 듯하다.

 

2. ActionScript 3.0 Native Vector3D, Matrix3D와 C++의 Vector3D, Matrix3D 속도테스트

 

Flash Player 10부터 추가된 ActionScript 3.0 API중에 Vector3D, Matrix3D가 있다. 이 클래스들은 3D 표현을 위한 다양한 기능을 제공하고 있다. Native 코드이므로 소스코드는 직접 볼 수 없다.

 

ActionScript 3.0의 Vector3D와 Matrix3D 대신 C++로 Vector3D, Matrix3D를 만들어서 서로 수행속도를 비교해보도록 하겠다.

 

아래는 Vector3D와 Matrix3D C++코드, 그리고 이들을 속도 테스트 할 수 있는 C++코드, ActionScript 3.0과 C++과 속도 테스트 할 수 있도록 제작한 ActionScript 3.0 코드가 나열되어 있다.

 

Vector3D.h

#ifndef VECTOR3D_H_
#define VECTOR3D_H_

#include "AS3.h"

class Vector3D {

public :
  Vector3D();
  Vector3D(double x, double y, double z);
  Vector3D(const AS3_Val& as3Vector);
  virtual ~Vector3D();

  double dot(const Vector3D& v) const;
  Vector3D cross(const Vector3D& v) const;
  double modulus() const;
  Vector3D normalise() const;

  void setX(double x);
  void setY(double y);
  void setZ(double z);
  double getX() const;
  double getY() const;
  double getZ() const;

private :
  double _x;
  double _y;
  double _z;

};

#endif /*VECTOR3D_H_*/

 

Matrix3D.h

#ifndef MATRIX3D_H_
#define MATRIX3D_H_

#include "Vector3D.h"

class Matrix3D {

public :
  Matrix3D();
  virtual ~Matrix3D();

  void setRotationX(double degrees);
  void setRotationY(double degrees);
  void setRotationZ(double degrees);

  void setIdentity();

  Vector3D transformVector(const Vector3D& vector) const;

private :
  double _M00;
  double _M01;
  double _M02;
  double _M10;
  double _M11;
  double _M12;
  double _M20;
  double _M21;
  double _M22;

};

#endif /*MATRIX3D_H_*/

 

Vector3D.cpp

#include "Vector3D.h"
#include <cmath>

Vector3D::Vector3D() :
  _x(0),
  _y(0),
  _z(0) {
}

Vector3D::Vector3D(const AS3_Val& as3Vector) {
  AS3_ObjectValue(as3Vector, "x:DoubleType, y:DoubleType, z:DoubleType", &_x, &_y, &_z);
}

Vector3D::Vector3D(double x, double y, double z) :
  _x(x),
  _y(y),
  _z(z) {
}

Vector3D::~Vector3D() {
}

double Vector3D::dot(const Vector3D& v) const {
  return v._x*_x + v._y*_y + v._z*_z;
}

Vector3D Vector3D::cross(const Vector3D& v) const {

  Vector3D result;
  result._x = _y*v._z - _z*v._y;
  result._y = _z*v._x - _x*v._z;
  result._z = _x*v._y - _y*v._x;
  return result;
}

double Vector3D::modulus() const {
  return std::sqrt(_x*_x + _y*_y + _z*_z);
}

Vector3D Vector3D::normalise() const {
  double mod = modulus();
  return Vector3D(_x/mod, _y/mod, _z/mod);
}

void Vector3D::setX(double x) {
  _x = x;
}

void Vector3D::setY(double y) {
  _y = y;
}

void Vector3D::setZ(double z) {
  _z = z;
}

double Vector3D::getX() const {
  return _x;
}

double Vector3D::getY() const {
  return _y;
}

double Vector3D::getZ() const {
  return _z;
}

Matrix3D.cpp

#include "Matrix3D.h"
#include <cmath>

Matrix3D::Matrix3D() :
  _M00(1),
  _M01(0),
  _M02(0),
  _M10(0),
  _M11(1),
  _M12(0),
  _M20(0),
  _M21(0),
  _M22(1) {
}

Matrix3D::~Matrix3D() {
}

void Matrix3D::setIdentity() {
  _M00 = 1;
  _M01 = 0;
  _M02 = 0;
  _M10 = 0;
  _M11 = 1;
  _M12 = 0;
  _M20 = 0;
  _M21 = 0;
  _M22 = 1;
}

void Matrix3D::setRotationX(double degrees) {
  setIdentity();
  double radians = degrees / 180 * M_PI;
  _M11 = cos(radians);
  _M12 = -sin(radians);
  _M21 = sin(radians);
  _M22 = cos(radians);
}

void Matrix3D::setRotationY(double degrees) {
  setIdentity();
  double radians = degrees / 180 * M_PI;
  _M00 = cos(radians);
  _M02 = sin(radians);
  _M20 = -sin(radians);
  _M22 = cos(radians);
}

void Matrix3D::setRotationZ(double degrees) {
  setIdentity();
  double radians = degrees / 180 * M_PI;
  _M00 = cos(radians);
  _M01 = -sin(radians);
  _M10 = sin(radians);
  _M11 = cos(radians);
}

Vector3D Matrix3D::transformVector(const Vector3D& vector) const {
  Vector3D result;
  result.setX(_M00*vector.getX() + _M01*vector.getY() + _M02*vector.getZ());
  result.setY(_M10*vector.getX() + _M11*vector.getY() + _M12*vector.getZ());
  result.setZ(_M20*vector.getX() + _M21*vector.getY() + _M22*vector.getZ());
  return result;
}

alchemy_speed_test.cpp

#include "AS3.h"
#include "Vector3D.h"
#include "Matrix3D.h"

AS3_Val speedTest1(void* self, AS3_Val args) {

  // Declare AS3 variables
  AS3_Val as3Vector1;
  AS3_Val as3Vector2;
  // Extract variables from arguments array
  AS3_ArrayValue(args, "AS3ValType, AS3ValType", &as3Vector1, &as3Vector2);
  // Create native C++ objects with AS3 parameters
  Vector3D vector1(as3Vector1);
  Vector3D vector2(as3Vector2);
  Vector3D vector3;
  // Speed test : calculate cross products and normalise
  for (int i = 0; i < 1000000; i++) {
    vector3 = vector1.cross(vector2);
    vector3 = vector3.normalise();
    vector1 = vector2;
    vector2 = vector3;
  }

  // Obtain a class descriptor for the AS3 Vector3D class
  AS3_Val vector3DClass = AS3_NSGet(AS3_String("flash.geom"), AS3_String("Vector3D"));
  AS3_Val params = AS3_Array("");
  // Construct a new AS3 Vector3D object with empty parameters
  AS3_Val result = AS3_New(vector3DClass, params);
  // Set the x, y and z properties of the AS3 Vector3D object, casting as appropriate
  AS3_Set(result, AS3_String("x"), AS3_Number(vector3.getX()));
  AS3_Set(result, AS3_String("y"), AS3_Number(vector3.getY()));
  AS3_Set(result, AS3_String("z"), AS3_Number(vector3.getZ()));

  // Release what’s no longer needed
  AS3_Release(params);
  AS3_Release(vector3DClass);
  // return the AS3 Vector
  return result;
}

AS3_Val speedTest2(void* self, AS3_Val args) {

  // Declare AS3 variable
  AS3_Val as3Vector;
  // Extract variables from arguments array
  AS3_ArrayValue(args, "AS3ValType", &as3Vector);

  // Create native C++ object with AS3 parameters
  Vector3D vector(as3Vector);
  Vector3D copy = vector;
  Matrix3D rotationX;
  Matrix3D rotationY;
  Matrix3D rotationZ;
  // Speed test : calculate rotation matrices and transform vector
  for (int i = 0; i < 1000; i++) {
    vector = copy;
    for (double ang = 0; ang < 180; ang++) {
      rotationX.setRotationX(ang);
      rotationY.setRotationY(ang);
      rotationZ.setRotationZ(ang);
      vector = rotationX.transformVector(vector);
      vector = rotationY.transformVector(vector);
      vector = rotationZ.transformVector(vector);
    }
  }

  // Obtain a class descriptor for the AS3 Vector3D class
  AS3_Val vector3DClass = AS3_NSGet(AS3_String("flash.geom"), AS3_String("Vector3D"));
  AS3_Val params = AS3_Array("");
  // Construct a new AS3 Vector3D object with empty parameters
  AS3_Val result = AS3_New(vector3DClass, params);
  // Set the x, y and z properties of the AS3 Vector3D object, casting as appropriate
  AS3_Set(result, AS3_String("x"), AS3_Number(vector.getX()));
  AS3_Set(result, AS3_String("y"), AS3_Number(vector.getY()));
  AS3_Set(result, AS3_String("z"), AS3_Number(vector.getZ()));

  // Release what’s no longer needed
  AS3_Release(params);
  AS3_Release(vector3DClass);
  // return the AS3 Vector
  return result;
}

/**
* Main entry point for Alchemy compiler. Declares all functions available
* through the Alchemy bridge.
*/
int main() {
  // Declare all methods exposed to AS3 typed as Function instances
  AS3_Val speedTest1Method = AS3_Function(NULL, speedTest1);
  AS3_Val speedTest2Method = AS3_Function(NULL, speedTest2);

  // Construct an object that contains references to all the functions
  AS3_Val result = AS3_Object("speedTest1:AS3ValType, speedTest2:AS3ValType", speedTest1Method, speedTest2Method);

  // Release what’s no longer needed
  AS3_Release(speedTest1Method);
  AS3_Release(speedTest2Method);

  // Notify the bridge of what has been created — THIS DOES NOT RETURN!
  AS3_LibInit(result);

  // Should never get here!
  return 0;
}

AlchemySpeedTest.as

package {

  import cmodule.alchemy_speed_test.CLibInit;
  import flash.display.Sprite;
  import flash.display.StageAlign;
  import flash.display.StageScaleMode;
  import flash.geom.Matrix3D;
  import flash.geom.Vector3D;
  import flash.text.TextField;
  import flash.text.TextFieldAutoSize;
  import flash.utils.getTimer;

  public class AlchemySpeedTest extends Sprite {

    private var vectorUtils:Object;

    public function AlchemySpeedTest() {

      // Set up the stage
      stage.align = StageAlign.TOP_LEFT;
      stage.scaleMode = StageScaleMode.NO_SCALE;

      // Create the Alchemy bridge to C++ methods
      var loader:CLibInit = new CLibInit;
      vectorUtils = loader.init();

      // Create a text field          
      var timerText1:TextField = new TextField();
      var timerText2:TextField = new TextField();
      var timerText3:TextField = new TextField();
      var timerText4:TextField = new TextField();
      timerText1.autoSize = TextFieldAutoSize.LEFT;
      timerText2.autoSize = TextFieldAutoSize.LEFT;
      timerText3.autoSize = TextFieldAutoSize.LEFT;
      timerText4.autoSize = TextFieldAutoSize.LEFT;
      timerText1.y = 0;
      timerText2.y = 30;
      timerText3.y = 60;
      timerText4.y = 90;
      addChild(timerText1);
      addChild(timerText2);
      addChild(timerText3);
      addChild(timerText4);
      var time0:int;
      var totalTime1:int;
      var totalTime2:int;
      var totalTime3:int;
      var totalTime4:int;

      // Perform the speed test
      time0 = getTimer();
      var vector1:Vector3D = speedTest1();
      totalTime1 = getTimer() - time0;

      time0 = getTimer();
      var vector2:Vector3D = speedTest2();
      totalTime2 = getTimer() - time0;

      time0 = getTimer();
      var vector3:Vector3D = nativeSpeedTest1();
      totalTime3 = getTimer() - time0;

      time0 = getTimer();
      var vector4:Vector3D = nativeSpeedTest2();
      totalTime4 = getTimer() - time0;
      // Display elapsed time and final vector
      timerText1.text = "1. Alchemy Time taken = " + totalTime1 + " vector = (" + vector1.x + ", " + vector1.y + ", " + vector1.z + ")";
      timerText2.text = "1. Native  Time taken = " + totalTime3 + " vector = (" + vector3.x + ", " + vector3.y + ", " + vector3.z + ")";
      timerText3.text = "2. Alchemy Time taken = " + totalTime2 + " vector = (" + vector2.x + ", " + vector2.y + ", " + vector2.z + ")";
      timerText4.text = "2. Native  Time taken = " + totalTime4 + " vector = (" + vector4.x + ", " + vector4.y + ", " + vector4.z + ")";
    }

    /**
     * Speed test using C++ to iteratively calculate the cross products of two vectors
     */
    private function speedTest1():Vector3D {
      var vector1:Vector3D = new Vector3D(0.123, 0.456, 0.789);
      var vector2:Vector3D = new Vector3D(0.987, 0.654, 0.321);

      return vectorUtils.speedTest1(vector1, vector2);
    }

    /**
     * Speed test using C++ to iteratively calculate rotation matrices and apply these to a vector
     */
    private function speedTest2():Vector3D {
      var vector:Vector3D = new Vector3D(0.123, 0.456, 0.789);
      return vectorUtils.speedTest2(vector);    
    }

    /**
     * Speed test using AS3 to iteratively calculate the cross products of two vectors
     */
    private function nativeSpeedTest1():Vector3D {
      var vector1:Vector3D = new Vector3D(0.123, 0.456, 0.789);
      var vector2:Vector3D = new Vector3D(0.987, 0.654, 0.321);
      var vector3:Vector3D;
      var time0:int = getTimer()
      for (var i:int = 0; i < 1000000; i++) {
        vector3 = vector1.crossProduct(vector2);
        vector3.normalize();
        vector1 = vector2;
        vector2 = vector3;
      }
      return vector3;
    }

    /**
     * Speed test using AS3 to iteratively calculate rotation matrices and apply these to a vector
     */
    private function nativeSpeedTest2():Vector3D {
      var vector:Vector3D = new Vector3D(0.123, 0.456, 0.789);

      var copy:Vector3D = vector.clone();

      var rotationX:Matrix3D = new Matrix3D();
      var rotationY:Matrix3D = new Matrix3D();
      var rotationZ:Matrix3D = new Matrix3D();

      for (var i:int = 0; i < 1000; i++) {
        vector = copy.clone();
        for (var ang:Number = 0; ang < 180; ang++) {
          rotationX.identity();
          rotationX.appendRotation(ang, Vector3D.X_AXIS);
          rotationY.identity();
          rotationY.appendRotation(ang, Vector3D.Y_AXIS);
          rotationZ.identity();
          rotationZ.appendRotation(ang, Vector3D.Z_AXIS);
          vector = rotationX.transformVector(vector);
          vector = rotationY.transformVector(vector);
          vector = rotationZ.transformVector(vector);
        }
      }
      return vector;
    }

  }
}

위 코드에서 alchemy_speed_test.cpp에 speedTest1(), speedTest2()가 만들어져 있고 AlchemySpeedTest.as 안에는 C++ 코드를 호출할 수 있는 함수와 ActionScript 3.0 Native Vector3D와 Matrix3D 속도를 테스트해보는 nativeSpeedTest1(), nativeSpeedTest2() 함수가 정의되어 있다.

 

speedTest1()과 nativeSpeedTest1()은 2개의 Vector간에 dot product 연산과 Normalisation을 1백만번 반복해서 결과를 반환하다. 반면 speedTest2()와 nativeSpeedTest2()는 x,y,z축 회전에 대한 Matrix를 3개 만들어 Vector를 회전을 반복한 다음 결과를 반환한다.

 

C++파일들을 컴파일 하기 위해 Makefile을 만들었다.

.SUFFIXES: .cpp .c .o

NAME=alchemy_speed_test
PATH:=${ALCHEMY_HOME}/achacks:${PATH}
CC = gcc
CCPP = g++
FLAGS = -O3 -Wall
OBJS =  $(NAME).o Matrix3D.o Vector3D.o

.cpp.o:
    $(CCPP) $(FLAGS) -c $<
.c.o:
    $(CC) $(FLAGS) -c $<

$(NAME) : $(OBJS)
    $(CC) $(FLAGS) -swc -o $(NAME).swc $(OBJS)

clean:
    rm $(OBJS) $(NAME).swc
    rm swfbridge.log
    rm *achacks*
    rm -r _sb_*

Window사용자중 Cygwin을 사용하시는 분은 make 패키지를 설치해야 이 Makefile을 구동할 수 있다.

 

프롬프트 상에서 make 명령을 하면 Makefile을 구동하여 C++파일을 컴파일 할 수 있다.  

 


 

만약 make를 사용하지 않으면 아래와 같이 컴파일해도 된다.

g++ –O3 –Wall –c alchemy_speed_test.cpp
g++ –O3 –Wall –c Matrix3D.cpp
g++ –O3 –Wall –c Vector3D.cpp
gcc –O3 –Wall –swc –o alchemy_speed_test.swc alchemy_speed_test.o Matrix3D.o Vector3D.o

 

마지막으로 컴파일된 alchemy_speed_test.swc 을 AlchemySpeedTest.as에 추가해서 컴파일 한다.

mxmlc.exe –library-path+=alchemy_speed_test.swc –target-player=10.0.0 AlchemySpeedTest.as

 

생성된 AlchemySpeedTest.swf 를 Flash Player 10에서 구동하면 다음과 같은 결과가 나온다.

1. Alchemy Time taken = 200 vector = (-0.40824829046386324, 0.8164965809277259, -0.4082482904638632)

1. Native  Time taken = 1065 vector = (-0.4082482904638631, 0.816496580927726, -0.4082482904638629)

2. Alchemy Time taken = 290 vector = (-0.6365049502756422, 0.662044570582514, -0.04630804289556738)

2. Native  Time taken = 893 vector = (-0.6365604400634766, 0.6619919538497925, -0.04631434381008148)

 

결국 결과는 다음과 같다.

Vector dot Product 연산과 Normalisation의 경우
- Alchemy : 201 ms
- Native : 1090 ms

회전행렬 생성과 Vector 변환의 경우
- Alchemy : 301 ms
- Native : 908 ms

이전과 다르게 수행속도의 차이를 보이고 있다. 이 결과만 놓고 볼 때 필요할 때는 C/C++로 만들어 최적화 할 수 있도록 만들 수 있겠다는 생각을 가진다. 하지만 많은 차이를 보이지 않아 실제 속도해결에 있어서 필요성에 대해서는 의문을 가질 수 있겠다. 아래서부터 Alchemy를 더욱 이해해 보도록 하겠다.

 

Alchemy를 깊게 이해하자.

 

Adobe labs에서 Alchemy에 대한 소개를 보면 ActionScript 3.0 코드보다 더욱 빠를 것이다라고 언급하고 있다. 하지만 지금까지의 결과를 볼 때 실제로는 기대만큼 효과적으로 빠른 것 같지는 않다. Alchemy를 사용해서 수행속도에 대한 이점을 이끌어내기 위해서는 Alchemy 본질을 어느 정도 이해할 필요가 있다고 생각했다.

 

아래 Alchemy에 대한 내용은 나름대로 자료를 찾아서 안되는 영어로 번역해보고 분석해봐서 정리한 내용이다. 혹시 잘못된 내용이 있을 수 있다. 따끔한 지적도 마다하지 않겠다. ^^

 

Alchemy를 이용해 C/C++ 코드를 SWF로 컴파일 하는 과정은 다음과 같다.

  1. C/C++ 코드를 LLVM(Low Level Virtual Machine) 바이트 코드로 변환한다.(확장자 .bc)
  2. LLVM 코드를 ActionScript 3.0 코드로 변환한다. (확장자 .as)
  3. ActionScript 3.0 코드를 ActionScript Byte Code로 변환한다.(확장자 .abc)
  4. 마지막으로 swc를 만든다. (확장자 .swc)

Alchemy의 중요 요소중 하나는 LLVM(Low Level Virtual Machine)이다. LLVM은 컴파일러 컴포넌트로 여러 언어를 중간코드로 변경하여 언어와 플랫폼에 독립적이고 컴파일, 링크시 런타임등 각 시점에서 최적화를 시켜준다. 자체적인 중간코드(intermediate representation)을 바탕으로 프로그래밍 언어와 CPU에 독립적인 최적화기, GCC기반의 C/C++ 프론트엔드(front-end), 그리고 X86, X86-64, PowerPC, ARM, IA-64, SPARC, Mips, Alpha, c 소스 백엔드(back-end), 또한 메모리에 코드를 생성해 실행할 수 있는 JIT 백엔드 등을 포함하고 있다.

 

Alchemy 설치 디렉토리를 $ALCHEMY_HOME 이라고 한다면 $ALCHEMY_HOME/achacks 내에 gcc가 있다. gcc는 sh코드로 만들어져 있고 이 안에서 C/C++코드를 SWC로 컴파일하기 위한 모든 과정이 들어간다. gcc 파일에서 첫번째 하는 과정은 해당 C/C++코드를 LLVM 바이트 코드(.bc)로 바꾸는 일이다. 이때 사용되는 도구가 $ALCHEMY_HOME/bin/llvm-gcc와 llvm-ld이다. 예를 들어 아래와 같은 코드가 실행된다.

$ llvm-gcc -v -emit-llvm -nostdinc -I$ALCHEMY_HOME/avm2-libc/include -I/usr/local/include –include $ALCHEMY_HOME/avm2-libc/avm2/AVM2Env.h echotest.c -c -o echo.o

$ llvm-ld -o=echotest -O5 -internalize-public-api-list=_start,malloc,free,__adddi3,__anddi3,__ashldi3,__ashrdi3,__cmpdi2,__divdi3, __fixdfdi,__fixsfdi,__fixunsdfdi,__fixunssfdi,__floatdidf,__floatdisf,__floatunsdidf, __iordi3,__lshldi3,__lshrdi3,__moddi3,__muldi3,__negdi2,__one_cmpldi2,__qdivrem, __adddi3,__anddi3,__ashldi3,__ashrdi3,__cmpdi2,__divdi3,__qdivrem,__fixdfdi, __fixsfdi,__fixunsdfdi,__fixunssfdi,__floatdidf,__floatdisf,__floatunsdidf,__iordi3, __lshldi3,__lshrdi3,__moddi3,__muldi3,__negdi2,__one_cmpldi2,__subdi3, __ucmpdi2,__udivdi3,__umoddi3,__xordi3,__subdi3,__ucmpdi2,__udivdi3, __umoddi3,__xordi3,__error $ALCHEMY_HOME/avm2-libc/lib/avm2-libc.l.bc echotest.o

위 과정은 llvm-gcc로 echotest.c를 echotest.o로 만들고 llvm-ld로 echotest.o를 avm2-libc.l.bc와 링크해서 echotest와 echotest.bc를 만들어준다. echotest는 echotest.bc를 실행하는 sh파일이다.

 

참고로 LLVM 바이트 코드인 echotest.bc는 $ALCHEMY_HOME/bin/llvm-dis를 이용해 이 도구는 disassemble하는 역할을 한다.  LLVM 홈페이지에 online demo를 보면 쉽게 이런 과정을 볼 수 있다. online demo에서 LLVM disassemble를 보면 다음 c코드가 어떻게 LLVM 바이트 코드로 바뀌는지 확인해볼 수 있다.

#include <stdio.h>
int main(int argc, char **argv) {
    printf("%d\n", 1);
}

다음은 위 코드를 LLVM 바이트 코드로 바꾼 것은 disassemble 한 것이다.

; ModuleID = ‘/tmp/webcompile/_5616_0.bc’
target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32"
target triple = "i386-pc-linux-gnu"
@.str = internal constant [4 x i8] c"%d\0A\00"        ; <[4 x i8]*> [#uses=1]

define i32 @main(i32 %argc, i8** %argv) nounwind {
entry:
    %0 = tail call i32 (i8*, …)* @printf(i8* noalias getelementptr ([4 x i8]* @.str, i32 0, i32 0), i32 1) nounwind        ; <i32> [#uses=0]
    ret i32 undef
}

declare i32 @printf(i8*, …) nounwind

 

다음으로 $ALCHEMY_HOME/bin에 있는 llc 도구를 이용해 ActionScript 3.0 코드를 만든다.

$ llc -march=avm2 -avm2-use-memuser -o=echotest.as -avm2-package-name=cmodule.echotest echotest.bc

다음 코드가 만들어진 ActionScript 3.0 코드이다.

package cmodule.speedtest {
// Start of file scope inline assembly
import flash.utils.*
import flash.display.*
import flash.text.*
import flash.events.*
import flash.net.*
import flash.system.*

public var gdomainClass:Class;
public var gshell:Boolean = false;

public function establishEnv():void
{
  try
  {
    var ns:Namespace = new Namespace("avmplus");
    gdomainClass = ns::["Domain"];
    gshell = true;
  }
  catch(e:*) {}
  if(!gdomainClass)
  {
    var ns:Namespace = new Namespace("flash.system");
    gdomainClass = ns::["ApplicationDomain"];
  }
}

establishEnv();

……(생략)

public const _c_speed_test:int = regFunc(FSM_c_speed_test.start)

public final class FSM_c_speed_test extends Machine {

    public static function start():void {
            var result:FSM_c_speed_test = new FSM_c_speed_test
        gstate.gworker = result
    }

    public var i0:int

    public static const intRegCount:int = 1
    public var f0:Number, f1:Number, f2:Number, f3:Number

    public static const NumberRegCount:int = 4
    public final override function work():void {
        Alchemy::SetjmpAbuse { freezeCache = 0; }
        __asm(label, lbl("_c_speed_test_entry"))
        __asm(push(state), switchjump(
            "_c_speed_test_errState",
            "_c_speed_test_state0",
            "_c_speed_test_state1"))
    __asm(lbl("_c_speed_test_state0"))
    __asm(lbl("_c_speed_test__XprivateX__BB75_0_F"))
        mstate.esp -= 4; __asm(push(mstate.ebp), push(mstate.esp), op(0×3c))
        mstate.ebp = mstate.esp
        mstate.esp -= 0
        f0 =  (0)
        i0 =  (0)
    __asm(jump, target("_c_speed_test__XprivateX__BB75_1_F"), lbl("_c_speed_test__XprivateX__BB75_1_B"), label, lbl("_c_speed_test__XprivateX__BB75_1_F"));
        f1 = f0
        f2 =  (Number(i0))
        f0 = f2
        //InlineAsmStart
    f0 =  Math.sin(f0);

    //InlineAsmEnd
        f3 = f0
        f0 = f2
        //InlineAsmStart
    f0 =  Math.cos(f0);

    //InlineAsmEnd
        f0 =  (f3 * f0)
        i0 =  (i0 + 1)
        f0 =  (f0 + f1)
        __asm(push(i0==10000000), iftrue, target("_c_speed_test__XprivateX__BB75_3_F"))
    __asm(lbl("_c_speed_test__XprivateX__BB75_2_F"))
        __asm(jump, target("_c_speed_test__XprivateX__BB75_1_B"))
    __asm(lbl("_c_speed_test__XprivateX__BB75_3_F"))
        mstate.esp -= 8
        __asm(push(f0), push(mstate.esp), op(0×3e))
        state = 1
        mstate.esp -= 4;(mstate.funcs[_AS3_Number])()
        return
    __asm(lbl("_c_speed_test_state1"))
        i0 = mstate.eax
        mstate.esp += 8
        mstate.eax = i0
        mstate.esp = mstate.ebp
        mstate.ebp = __xasm<int>(push(mstate.esp), op(0×37)); mstate.esp += 4
        //RETL
        mstate.esp += 4
        mstate.gworker = caller
        return
    __asm(lbl("_c_speed_test_errState"))
        throw("Invalid state in _c_speed_test")
    }
}

……(생략)

 

 

Alchemy로 C 또는 C++ 코드의 컴파일 결과는 기본적으로 ActionScript 와 AVM2 바이트 코드로 만들어진 하나의 클래스가 된다. 이 과정에서 결과물인 SWC는 C와 C++이 C 라이브러리와 POSIX를 지원에 필요할 수 있는 것도 함께 컴파일 되므로 크기가 커진다. 가령 54줄의 C코드를 Alchemy를 통해 컴파일 하면 27415줄의 ActionScript 코드가 만들어진다. 방금 위에서 이러한 현상을 확인할 수 있었다.

 

다음으로 asc.jar를 이용해 만들어진 ActionScript 파일을 swf로 만든다. 이 과정에서 playerglobal.abc와 global.abc를 포함한다.

$ java -Xms16M -Xmx1024M -jar $ALCHEMY_HOME/bin/asc.jar -AS3 -strict -import $ALCHEMY_HOME/flashlibs/global.abc -import $ALCHEMY_HOME/playerglobal.abc -d -config Alchemy::Shell=false -config Alchemy::NoShell=true -config Alchemy::LogLevel=0 -config Alchemy::Vector=true -config Alchemy::NoVector=false -config Alchemy::SetjmpAbuse=false -swf cmodule.echotest.EchoTest,800,600,60 echotest.as

다음으로 만들어진 echotest.swf를 ActionScript 바이트 코드로 만들어주고 라이브러리와

$ GetABC2.pl echotest echotest.swf.abc

$ALCHEMY_HOME/achacks에 있는 swctmpl.swf를 위에서 만든 abc로 대체해 새로운 swf를 만들고 SWF 버전 태그를 10으로 한다.

$ PutABC2.pl $ALCHEMY_HOME/swctmpl.swf temp.swf echotest.swf.abc cmodule/echotest/CLibInit 
$ V10SWF.pl temp.swf library.swf

마지막으로 다음과 같은 catalog.xml과 함께 최종적으로 SWC를 만든다.

<? xml version = "1.0"encoding = "utf-8"?>
<swc xmlns = "http://www.adobe.com/flash/swccatalog/9">
  <versions>
    <swc version = "1.0" />
    <flex version = "2.0"    build = "143452"/>
  </ versions>
  <features>
    <feature-script-deps />
    <feature-files />
  </ features>
  <libraries>
    <library path = "library.swf">
      <script name = "cmodule/echotest/ CLibInit" mod = "1177909560000">
        <def id = "cmodule.hello:CLibInit" /> 
        <dep id = "Date"    type = "e"/> 
        <dep id = "Date"    type = "s"/> 
        <dep id = "flash.utils : Dictionary" type = "e"/> 
        <dep id = "flash.utils : Dictionary" type = "s"/> 
:
(생략)
:
        <dep id = "AS3" type = "n"/> 
        <dep id = "Object" type = "i"/> 
      </ script>
    </ library>
  </ libraries>
  <Files>
  </ files>
</ swc>

$ zip echotest.swc catalog.xml library.swf

 

이처럼 Alchemy를 통해 얻는 결과물은 일반적으로 SWC이다(SWF의 경우 사용가치가 떨어진다.). 이 SWC는 Flex 3(Flash Player 10을 겨냥해서 만들어져야 함), Flex 4(아직 개발중인 “Gumbo”) 그리고 Flash CS4에서 사용할 수 있다.

 

C와 C++로 만들어졌기 때문에 Alchemy로 컴파일 된 SWF가 일반 C/C++의 규칙과 제한을 따른다고 생각하면 곤란하다. C/C++는 일반 SWF와 같이 Flash Player의 보안샌드박스안에 포함되고 모든 화면출력결과물은 display list를 통과하도록 만들어진다. 그리고 네트워크 및 파일엑세스도 이미 존재하는 클래스를 사용하게 된다. Alchemy로부터 컴파일된 라이브러리에서 만들어진 코드는 적절한 메모리 덩어리를 ByteArray로 할당한다. 이것은 Alchemy가 어떻게 포인터, malloc, free등이 ActionScript 에서 어떻게 작업되는가 알려주는 개념이 된다. 이말은 즉 C/C++언어가 Alchemy를 통해 100% ActionScript 3.0으로 변경되어 원천적으로 가지고 있는 SWF의 제약사항을 넘어갈 수는 없다는 것을 의미한다.

 

그럼에도 불구하고 Alchemy의 AVM2 코드가 Flash 와 Flex에서 만들어진 AVM2 코드보다 어떻게 수행속도가 빠를 수 있는지 궁금할 거다. 실제로는 더 빠르지 않다. 그럼 Adobe 에서 주장하는 “수행속도가 빠를 것이다”라는 것은 거짓말인가? 100% ActionScript 3.0으로 바뀌는데 수행속도가 빠를 수 없다고 생각할 수 있다. 하지만 이 부분에서 수행속도의 개선을 찾으면 안된다. 실제로는 Alchemy의 LLVM은 많은 최적화를 이루는 반면 Flash와 Flex의 컴파일러는 최적화 과정이 없다. 또한 Alchemy는 ActionScript 3.0 자체가 가지는 오버헤드의 문제로 인한 수행속도 저하를 극복하는데 쓰일 수 있다.

 

그럼 Alchemy의 수행속도에 대해 기대할 수 있는 부분은 구체적으로 무엇일가? 바로 메모리 엑세스와 함수 호출이다. Alchemy를 통해 컴파일된 코드는 ByteArray를 적용하기 위해 Flash Player 10에 적용된 새로운 ByteArray를 사용한다. 마치 이것은 RAM처럼 이용된다. ActionScript 3.0의 함수를 호출할 때 그들의 인자가 박싱(boxed) 와 언박싱(unboxed)를 하게 된다. 하지만 Alchemy로 만들어진 코드는 그러한 과정이 없다. 이 두가지 이유로 Alchemy로 만들어진 결과물이 수행속도 향상에 도움이 될 수 있다는 것이다.

 

ActionScript 코드와 Alchemy 코드간에 통신과정에서 수행속도가 떨어질 수 있다. 이 과정은 마샬링(marshaling)이 된다. 마샬링은 어떤 한 언어에서 작성된 출력 데이터를 다른 언어의 입력단으로 전달해야하는 경우 데이터 변환과정이 생기는 것을 말하는데 ActionScript 코드와 Alchemy 코드간에 빈번한 상대방의 함수 호출은 이런 과정으로 인해 전체적인 수행속도를 죽이는 일을 발생시킬 수 있다. 그러므로 어떤 처리를 위해 C/C++코드에서 한꺼번에 처리하는 과정이 들어가도록 코딩하고 상대방 함수로 넘겨주는 파라미터의 수를 줄여주는 것이 속도향상에 도움이 될 것이다.

 

Alchemy는 매우 강력하지만 마법사는 아니다. Alchemy의 이점이 무엇인지 잘 파악해서 사용하는 것이 현명하다. 본인이 생각할 때 Alchemy를 사용해야 하는 경우는 다음과 같다.

1. Alchemy로 포퍼먼스 향상에 어느 정도 이득이 있는 경우
2. 기존 C/C++ 라이브러리를 ActionScript 3.0으로 바꾸기 힘들거나 별도의 변경 없이 그래로 Flash/Flex/AIR 프로젝트에서 사용하고 싶은 경우

위 2가지 경우 중 1가지라도 있다고 판단하면 Alchemy를 활용해서 자신의 Adobe RIA 프로젝트에 접목시킬 수 있다고 생각한다. 2번째의 경우 C/C++ 개발자와 Adobe RIA 개발자간에 협업도 기대할 수 있는 부분이다.

 

한가지 더 언급할 것은 Alchemy는 아직 정식 배포상태가 아니라는 점이다. 그래서 약간 개발하기도 불편하고 버그도 간혹 보인다. 하지만 정식 배로가 된다면 그러한 문제점은 해결될 것이라 생각하고 지금까지 보여줬던 것 보다 더욱 큰 포퍼먼스 향상을 기대할 수 있다. 추후에는 Flash Catalyst에 포함되어 개발에 큰 도움이 될 것이다.

 

정리하며

 

Alchemy의 장점은 메모리 엑세스와 함수호출에 따른 수행속도의 개선과 기존 C/C++ 라이브러리를 그대로 활용할 수 있다는 점에 있다. 이 장점을 잘 살려서 현업에 적용할 수 있도록 적절한 예제를 찾아보려한다.

 

이 글을 적으면서도 Alchemy의 컴파일 과정 및 내용에 대해서 확실히 이해할 수 없었다. 하지만 실망하지 않는다. 어쨌던 이러한 노력은 나중에 큰 도움이 될 것이다라고 생각하기 때문이다.

 

이 글을 보신 분 중 공유할 수 있는 내용이나 지적사항이 있다면 언제든지 댓글 환영한다. ^^


한국에도 Alchemy를 활용한 많은 예제와 현업에서 적용 예가 많이 나왔으면 한다.

 

참고내용

 

아래 테스트 코드는 문제가 있었습니다. 왜냐하면 GCC 최적화 옵션에 대해서 명확히 이해를 못하고 사용했기 때문입니다. 그래서 잘못된 지식을 전하는 것을 우려해 그냥 내용만 훑어보시되 “Adobe Alchemy 수행속도 테스트와 깊이 이해하기”글을 덧붙여 읽어주시길 바랍니다. 제가 잘못된 내용을 적었다면 지적해주셨으면 합니다. 그리고 실무에 썼거나 테스트를 해보신 분이라면 그에 대해 소개도 부탁드릴께요.

Alchemy는 C/C++ 코드를 AVM2(ActionScript 3 Virtual Machine) 환경에서 동작하도록 해주는 도구이다. Alchemy를 이용해 기존에 있는 C/C++ 코드를 Adobe AIR, Adobe Flex, Adobe Flash 프로젝트에서 직접 활용할 수 있는 SWF 또는 SWC를 만들어낼 수 있다. 이에 대한 더욱 자세한 사항은 본인이 게재한 “C/C++와 Flash 플랫폼과의 만남. Alchemy 1부” 를 참고하기 바란다.

 

이 글은 Alchemy를 이용해 만들어진 SWC를 이용했을 때와 순수 ActionScript 3.0으로만 코딩했을 때 수행속도 차이를 비교해보는 것을 목적으로 한다.

 

Alchemy 속도 테스트

 

과연 Alchemy를 이용해서 C언어를 SWC로 변환한 코드의 수행속도에 진전이 있을까? 아래 c코드와 ActionScript 3.0 코드는 이를 테스트할 수 있도록 한다. c언어와 ActionScript 3.0에서 동일하게 ”sin( 0.332 ) + cos( 0.123 ) + tan(0.333) * log(3)” 식을 1백만번 반복한다.

 

컴파일하고 수행하는 방법은 “C/C++와 Flash 플랫폼과의 만남. Alchemy 1부”를 참고한다.

1. speedtest.c

#include <math.h>
#include "AS3.h"

static AS3_Val c_speed_test(void* self, AS3_Val args)
{
    int i;
    double result;
    for( i=0; i < 1000000; i++ )
    {
        result = sin( 0.332 ) + cos( 0.123 ) + tan(0.333) * log(3);
    }
    return AS3_Number(result);
}

int main()
{
    AS3_Val method = AS3_Function( NULL, c_speed_test );
    AS3_Val result = AS3_Object( "c_speed_test: AS3ValType", method );
    AS3_Release( method );
    AS3_LibInit( result );
    return 0;
}

 

2. AlchemySpeedTest.as

package {
    import cmodule.speedtest.CLibInit;

    import flash.display.Sprite;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.text.TextField;
    import flash.text.TextFieldAutoSize;
    import flash.text.TextFormat;
    import flash.utils.getTimer;

    public class AlchemySpeedTest extends Sprite
    {
        public function AlchemySpeedTest()
        {
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.align = StageAlign.TOP_LEFT;

            var loader:CLibInit = new CLibInit();
            var lib:Object = loader.init();
            var startTime:Number;

            startTime = getTimer();
            var cResult:String = "c returned value : " + lib.c_speed_test(null) + " delay time(ms) : " + (getTimer()-startTime);

            startTime = getTimer();
            var asResult:String = "as3 returned value : " + as3_speed_test() + " delay time(ms) : " + (getTimer()-startTime);

            var textField:TextField = new TextField();
            textField.text = cResult + "\n" + asResult;
            textField.width = 500;
            textField.autoSize = TextFieldAutoSize.LEFT;
            textField.wordWrap = true;
            textField.defaultTextFormat = new TextFormat( null, 12 );
            addChild(textField);

        }
        public function as3_speed_test():Number
        {
            var i:int;
            var result:Number;
            //var sin:Function = Math.sin;
            //var cos:Function = Math.cos;
            for (i = 0; i < 1000000; i++)
            {
            result = Math.sin( 0.332 ) + Math.cos( 0.123 ) + Math.tan( 0.333 ) * Math.log(3);
            }
            return result;
        }
    }
}

 

speedtest.c를 아래처럼 Alchemy로 컴파일하고 만들어진 speedtest.swc를 AlchemySpeedTest.as와 함께 mxmlc로 컴파일 한다.

gcc speedtest.c –swc –o speedtest.swc
mxmlc.exe –library-path+=speedtest.swc –target-player=10.0.0 AlchemySpeedTest.as

아래는 위 프로그램을 시행한 결과이다.

c returned value : 1.6983678388082433 delay time(ms) : 559
as3 returned value : 1.6983678388082433 delay time(ms) : 694

이 결과는 의외의 결과이다. C와 ActionScript 3.0과 같은 수행속도를 보였기 때문이다. 하지만 이 결과는 C코드를 컴파일 할 때 최적화하지 않았기 때문이다. 아래처럼 –03 –Wall 옵션을 넣고 다시 수행해보자. -O3는 최적화 옵션이고 –Wall(또는 –W)는 모든 경고를 출력하도록 한다.

gcc speedtest.c –03 –Wall –swc –o speedtest.swc
mxmlc.exe –library-path+=speedtest.swc –target-player=10.0.0 AlchemySpeedTest.as

컴파일한 SWF파일을 실행한 결과는 다음과 같다.

c returned value : 1.6983678388082433 delay time(ms) : 1
as3 returned value : 1.6983678388082433 delay time(ms) : 601

 

결론

 

위 실험 결과를 통해 Alchemy를 이용해 C/C++의 수행속도를 잘 활용하면 좋다는 것을 알 수 있었다. 가령, ActionScript 3.0으로 구동 시에 많은 부하가 있을 가능성이 있는 부분을 C로 만들어 Alchemy를 통해 최적화된 SWC로 라이브러리화 해서 사용한다면 여러분의 Flash/Flex/AIR 애플리케이션의 속도를 크게 향상시킬 수 있다. 구체적으로 Audio/Video, 데이터 형식 변환, 데이터 조작, XML 분석 및 암호화 기능, 물리 시뮬레이션등이 들어가는 코드에서 포퍼먼스 향상을 기대할 수 있겠다.

아래는 Alchemy가 C/C++코드를 ActionScript 3.0 코드로 완벽하게 변경하는가에 대한 의견이다.

(글쓴이 의견)

어떤 분은 Alchemy로 SWF를 만들어준다고 해서 C/C++코드가 전부 ActionScript 3.0으로 변환될 것이다라는 생각을 가질 수 있다. 하지만 실험 결과만 가지고 볼 때 틀리다는 것을 유추할 수 있다. Alchemy는 C/C++ 코드를 AVM2 환경에서 동작하도록 만들되 ActionScript 3.0과 함께 사용할 수 있도록 만들어준다. 즉 C/C++를 적절하게 변경하고 ActionScript 3.0과 통신할 수 있는 통로구를 마련해주는 것이 Alchemy의 목적이다. 만약 ActionScript 3.0으로만 변경되는 것이라면 빠른 수행속도를 기대하기 어려울 것이다.

(양병규님 의견)

컴파일러에 대해서 조금 관심을 가져보시면 알 수 있을텐데요…
음… 뭐랄까…
그 최적화라는 것이 불필요한 일은 안하고 같은 기능이라면 더 빠른 기능으로 대치하고.. 그런일을 하는 것이 컴파일러의 최적화인데요..
제가 보기에는
for( i=0; i < 1000000; i++ )
{
result = sin( 0.332 ) + cos( 0.123 ) + tan(0.333) * log(3);
}
이 부분이.. 최적화를 하면 루프를 1000000번 돌지 않고 한방에 끝낼것같습니다. 최적화알고리즘중에서 루프문안에서의 내용이 반복을 해도 변화가 없는 경우에는 루프를 한방에 끝내도록 하는 알고리즘이 있습니다. 이 예가 그런 예일것 같은데요..

암튼.. ActionScript에는 그런 최적화가 없는데 이미 오랜 역사동안 만들어진 C/C++언어의 최적화 기술이 적용되면 분명히 나은 결과를 보일 것 으로 예상됩니다. ^^;(물론 코딩하는 사람의 실력이 좋을 수록 최적화의 효과가 떨어지겠지만)

그리고..
저는 아무리 봐도 C/C++로 코딩한 것이 결국 몽땅 ActionScript로 만들어지는 것이 맞는 것 같습니다. SWF에는 ActionScript 이 외에 다른 실행코드는 존재하지 않습니다. 최종적으로 만들어진 결과가 *.SWF(*.SWC)이지 않습니까? SWF File Format Specification Version 10 문서 278페이지를 다 훑어봐도 ActionScript 이외에는 실행코드가 없습니다. 머.. DLL을 임포트한다거나 별도의 실행코드용 외부 라이브러리가 있다거나… 그런건 전혀 없습니다.

malloc 함수 같은 경우의 예를 들어보면 ActionScript에는 malloc과 동일한 역할을 하는 어셈블명령은 없습니다. 하지만 ActionInitArray와 같이 긴 변수를 사용할 수 있는 명령으로 ‘구현’은 할 수 있을겁니다. 아마도 그런 방법으로 구현되지 않았나싶습니다.

어쨋든 Alchemy는 C/C++을 100% ActionScript로 변환해 주는게 맞고.. 함수사용, 변수사용, 최적화, 수학함수의 구현, 각종 기초 알고리즘등 기초적인 부분에서 훨씬 우월하기 때문에 좋은 효과를 나타내고 있는 것으로 생각됩니다.

시간이 나면 Alchemy에서 만들어진 SWF 파일을 분석해 보면 아주 좋은 공부가 될것같습니다. ^^;

Alchemy가 유용한 것은 C/C++ 코드를 ActionScript 3.0와 함께 쉽게 사용할 수 있게끔 해주는 것과 C/C++자체가 가지는 빠른 수행속도를 Flash 컨텐츠에서 이용할 수 있는 것으로 생각한다. Alchemy가 앞으로 사용 편의성 향상과 여러 환경에서 문제없이 개발할 수 있도록 개선된다면 Adobe RIA기술이 한 단계 업그레이드 될 것이라 생각한다.

 

한가지 주의할 사항은 전부 C/C++로 만드는 것은 좋지 못하다. Alchemy를 적용하기에 앞서 본문에서 실험한 것과 같은 방법으로 여러가지 수행 테스트를 해보고 순수하게 ActionScript 3.0으로 한 것보다 큰 포퍼먼스를 기대할 수 있다고 판단했을 때 적용하는 것이 좋다. 때로는 어떤 코드냐에 따라 별로 큰 차이가 없을 수 있기 때문이다.

국내에 Alchemy를 응용한 많은 예제와 애플리케이션들이 나오길 바란다.

 

참고할 사이트

 

+ Recent posts