Zephir: PHP 확장 기능을 손쉽게 만들어보자

세상에 나와있는 수많은 PHP 프레임워크 중 성능이 말 그대로 깡패인 Phalcon이라는 것이 있다.

이미지 출처 http://systemsarchitect.net/performance-benchmark-of-popular-php-frameworks/

이 중 2위를 차지한 Slim은 이름에서 짐작할 수 있듯 기능도 굉장히 간결한 초경량 프레임워크라 벤치마킹 결과가 상대적으로 좋은 것이 당연하다는 점을 감안하면 중형 프레임워크라 볼 수 있는 Phalcon의 성능은 압도적이라해도 과언이 아닐 것이다. 이렇게 뛰어난 성능을 갖추게 된 이유는 의외로 단순하다. 바로 Phalcon이 C로 만들어진 PHP 확장기능이기 때문이다.

하지만 굉장히 유연한 PHP에 반해 많은 제약이 따르는 C 사이에 존재하는 넓디 넓은 간극으로 인해 PHP 확장기능을 직접 만들어 사용하는 PHP 개발자는 거의 없다. 그러니 PHP 확장기능이 가지는 성능 상의 이점을 취하기도 어려웠을 것이다.

바로 이 부분이 Zephir라는 언어가 탄생한 배경이다. Phalcon의 개발팀이 만들고 있는 이 새로운 언어는 순전히 PHP 확장기능을 더 편하게 만들 목적으로 태어났다. Zephir 공식 문서를 보면 PHP나 C를 대체하거나 혹은 웹에 새로운 언어를 더해보자는 목적으로 만들지 않았으며 단순히 PHP와 C 확장기능 사이를 이어주는 보완재로서의 무언가를 만들고 싶었다고 밝히고 있다.

Hello, Zephir

Zephir는 사실 Phalcon 프레임워크를 개선하기 위해 기획된 프로젝트이다. 나중에는 독립적으로 발전할 수도 있겠지만 현재로서는 Phalcon을 테스트베드 삼아 Zephir를 개선하고 있는 듯 보이는데, 이 때문인지 Zephir로 만들 수 있는 확장 기능은 모두 클래스 형태를 띄어야 하며 또한 네임스페이스도 반드시 사용해야 한다. 따라서 가장 간단한 코드는 다음과 같다.

namespace Test;

class Hello
{
    public function say()
    {
        echo 'Hello, Zephir!';
    }
}

이 코드를 컴파일하면 다음과 같이 확장 기능을 만들 수 있는 C 코드가 생성된다.

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_test.h"
#include "test.h"

#include "kernel/main.h"

ZEPHIR_INIT_CLASS(Test_Hello) {
    ZEPHIR_REGISTER_CLASS(Test, Hello, hello, test_hello_method_entry, 0);
    return SUCCESS;
}

PHP_METHOD(Test_Hello, say) {
    php_printf("%s", "Hello, Zephir!");
}

설치부터 컴파일까지

Zephir를 사용하려면 다음의 라이브러리가 미리 설치되어 있어야 한다.

둘 중 re2c는 우분투를 사용하다면 간단하게 apt-get install re2c 명령을 통해 설치할 수 있다. 두 라이브러리 외에도 다음과 같은 환경이 갖추어져 있어야 한다.

  • g++ >= 4.4/clang++ >= 3.x/vc++ 9
  • gnu make 3.81 이상
  • PHP 개발 도구 및 헤더

참고로 우분투라면 apt-get install php5-dev 명령을 통해 PHP 개발 도구와 헤더를 설치할 수 있다. 필요한 환경이 다 갖추어졌다면 다음과 같은 순서로 명령을 실행하여 Test 확장 기능을 빌드해 볼 수 있다.

git clone https://github.com/phalcon/zephir.git
cd zephir
./install
./bin/zephir compile

다시 Hello, Zephir

이제 앞서 작성했던 Test 확장 기능을 직접 컴파일해보자. 먼저 zephir 디렉토리에 있는 config.json 파일을 열어보면 다음과 같은 내용을 볼 수 있을 것이다.

{"namespace":"test"}

여기서 test를 자신이 원하는 이름으로 바꾸어주자. 예를 들어 소스 코드에서 namespace를 greeting으로 사용할 것이라면 다음과 같이 수정하면 된다.

{"namespace":"greeting"}

이제 namespace에 맞춰 greeting이라는 디렉토리를 새로 만들어준다. 이 시점에서 디렉토리 구조는 다음과 같을 것이다. (+는 디렉토리, -는 파일)

zephir + Library
       + bin
       + ext
       + greeting
       + parser
       + scripts
       + templates
       + unit-tests
       - CONTRIBUTTING.md
       - LICENSE
       - README.md
       - compiler.php
       - config.json

이제 greeting 디렉토리 안에 파일을 추가하면 된다. 한 파일당 하나의 클래스가 오도록 작성한다. 예를 들어, 인수를 받아 인사말을 출력하는 클래스는 다음과 같이 작성한다.

참고로 파일 이름은 hello.zep로 사용했다.

namespace Greeting;

/*
 * 네임스페이스와 클래스 이름은 반드시 필요하다.
 */
class Hello
{
    public function say(name)
    {
        // 사용할 변수를 미리 선언한다.
        // 선언과 동시에 리터럴을 할당할 수는 있으나 변수와의 연산을 할당할 수는 없다.
        var str;

        // 객체, 배열을 다루거나 문자열을 합칠 때는 let 키워드를 통해
        // PHP 컨텍스트의 변수를 사용해야 한다.
        let str = "Hello, ".name."!";

        // 합친 문자열 값을 반환한다.
        return str;
    }
}

위 코드는 다음과 같이 컴파일 된다. 컴파일 된 파일은 zephir/ext/greeting에서 확인할 수 있다.

#ifdef HAVE_CONFIG_H
#include "../ext_config.h"
#endif

#include <php.h>
#include "../php_ext.h"
#include "../ext.h"

#include <Zend/zend_operators.h>
#include <Zend/zend_exceptions.h>
#include <Zend/zend_interfaces.h>

#include "kernel/main.h"
#include "kernel/concat.h"
#include "kernel/memory.h"

ZEPHIR_INIT_CLASS(Greeting_Hello) {
    ZEPHIR_REGISTER_CLASS(Greeting, Hello, greeting, hello, greeting_hello_method_entry, 0);

    return SUCCESS;
}

PHP_METHOD(Greeting_Hello, say) {
    zval *name, *str;

    ZEPHIR_MM_GROW();
    zephir_fetch_params(1, 1, 0, &name);

    ZEPHIR_INIT_VAR(str);
    ZEPHIR_CONCAT_SVS(str, "Hello, ", name, "!");
    RETURN_CCTOR(str);
}

그리고 컴파일된 확장 모듈은 zephir/ext/modules와 PHP 확장기능 폴더에 greeting.so라는 이름으로 저장되어 있으므로, php.ini에 다음과 같은 줄을 추가하여 확장 기능을 읽어들이도록 설정한다.

[greeting]
extension=greeting.so

이제 서버를 재시작하고 난 후 phpinfo()를 실행해보면 다음과 같이 우리가 작성한 확장기능이 로드된 것을 볼 수 있다.
greeting_extension

현재까지는 여기에 새로운 설정을 추가할 수 없고, 심지어 버전조차도 0.0.1로 하드코딩 되어 있어 바꿀 수 없다. =_=;; 여하튼 이제 PHP에서는 다음과 같이 이 클래스를 사용할 수 있다.

<?php
$greeting = new Greeting\Hello();
echo $greeting->say('Zephir');

이 코드를 실행하면 화면에 Hello, Zephir가 출력된다.

그 밖의 특징

앞서 소개한 특징 외에 Zephir가 가진 특징 또는 장점은 다음과 같다.

  • 읽기 전용 속성을 다음과 같이 간편하게 만들 수 있다.
    class Hello
    {
        protected myProperty {
            get
        }
    }

    쓰기 속성도 필요하다면 get, set을 사용하면 된다.

  • PHP 함수를 그대로 사용할 수 있다.
    let money = number_format(12345);

    단, 순수 함수 형태만 사용 가능하며 클래스 형태로 만들어진 기능은 사용할 수 없다.

  • 변수, 함수 반환값, 함수 인수의 타입 힌팅이 가능하다.
  • PHP 컨텍스트에 만드는 변수는 동적으로도 할당할 수 있다.
    var name = "greet";
    let {name} = "Hello";
    // 위 코드는 let greet = "Hello"; 와 같다.
    
  • C/C++ 라이브러리를 사용할 수 있는 방법은 (현재로서는) 없다. 하지만 C/C++에 익숙하다면 컨버팅된 C 코드를 편집하여 C/C++ 라이브러리를 사용할 수도 있다. 경우에 따라서는 바닥부터 작성하는 것보다는 한결 편할 수 있다.

결론

PHP 성능에 한 번쯤 불만을 가져본 개발자는 적지 않을 것이다. 처음 밝힌 대로 성능면에서는 PHP 확장기능이 가장 좋은 대안이겠지만 C/C++라는 장벽은 쉽게 극복하기 어려웠을 것이다. 그러나, Zephir 언어 자체는 PHP만 아는 개발자도 그리 어렵지 않게 접근해 볼 수 있을 수준이다.
속도를 얻는 것과 동시에 PHP 개발자라면 한번쯤 고민해봤을만한 소스 보안에도 어느 정도 기여할 수 있다. 자주 사용되는 핵심 코드를 zep으로 작성한 후 컴파일된 확장기능만 제공해주면 속도와 보안 두 마리 토끼를 동시에 잡는 결과도 얻을 수 있다.
물론 아직은 겨우 0.2 버전 대의 알파 상태인 프로젝트라 현재로서는 실제 서비스에 적용하기에 무리가 있지만 하루가 다르게 발전하는 모습을 보이고 있으므로 충분히 기대해볼만 하다고 생각한다. PHP를 그대로 컴파일한다던 HipHop for PHP도 현재는 VM으로 방향을 선회한 지금, (잘 만들어진다는 가정 하에) PHP 세계에서 가장 빠른 속도를 자랑할 것은 Zephir가 될 것이다.

  1. phalcon 짱~~~
    작년에 드루팔 오픈소스 cms가 워낙에 느려터져서..(모든 오픈소스가 다 그렇지만..) hiphop , phc등등을 사용해서 속도증가를 시도를 10번은 넘게 했던거 같네요..모두 설치단계에서 좌절…ㅋㅋ
    원래 phalcon 프로젝트로 개발중에 zephir가 나왔네요…
    이것으로 드루팔 코어를 모두 extension으로 만들고 있습니다..ㅋㅋㅋ

Leave a Reply