프로그래밍 언어/PHP

PHP: Interpreter, OPCache, ― JIT

이번 시간에는 기존의 포스팅과는 깊이에서 차이가 있다. PHP 를 문법을 넘어 그 너머 살펴보고 코드가 해석되고 실행되는 과정을 살펴본다. 이를 고차원의 관점에서 볼 것이며 그러한 과정이 어떻게 구현되었지까지는 들어가지 않는다. 하지만, PHP 를 사용하는 개발자라면 알아두어야 할 사항을 살펴보도록 하자. 이 부분은 꽤나 중요한 부분이다.

고수준의 언어를 기계어로 바꾸기

가장 기본적인 부분부터 시작해보자. 일반적으로 우리가 사용하는 언어, 한국어, 영어와 같은 고수준의 언어를 컴퓨터는 바로 이해하지 못한다. 따라서 우리는 컴퓨터가 알아먹을 수 있는 기계어로 바꿔야 하는데, 그러한 기계어로 바꿔주기한 해석기로는 컴파일러인터프리터가 있다.

컴파일러

C, C++, Go, Rust 와 같은 언어는 컴파일러를 사용한다. 컴파일러는 우리가 만든 소스코드를 해석하고 일련의 과정을 거쳐 실행가능한 바이너리를 생성한다. 바이너리는 이진 파일이며 윈도우에서 .exe 같은 것이라고 생각하면 된다. 이렇게 직접 CPU 가 해석할 수 있는 형태로 바뀌기 때문에 중간 단계에 다른 걸 거칠 필요가 없기 때문에 빠르며, 이러한 언어들은 언어 차원에서 메모리를 직접 관리하거나 관여할 수 있는 기능을 제공하기도 한다.

 

컴파일러는 프로그램을 실행하기 전에 미리 컴파일을 해야한다. 프로그램이 거대해지면 컴파일만 몇십분이 소모되기도 한다.

인터프리터

PHP, Python, Javascript 와 같은 언어는 인터프리터를 사용한다. 컴파일러는 코드를 한 번에 해석하는 반면 인터프리터는 런타임 중 각 라인별로 해석한다. 이러한 기본적인 속성으로 인해 직접적인 컴파일 과정없이도 코드를 즉각 실행해볼 수 있는 장점이 있다. 내부적으로는 컴파일을 해서 기계어로 바꾼 뒤 실행해주어야겠지만, 그것을 개발자가 명시적으로 해주지는 않고 다른 것이 해주므로(브라우저 라든가) 눈에 안 보일 뿐이다.

 

인터프리터는 성능상으로 일반적으로 컴파일러를 사용하는 언어보다 느리다. 런타임 중에 메모리를 관리하거나 최적화 작업을 실행해주어야 하는 등 다양한 작업을 처리해주는 것을 같이 해야하기 때문이다.

어떤 과정을 거쳐 실행될까?

먼저, PHP 가 아주 간략히 대략적으로 어떻게 실행되는지 살펴보도록 하자. PHP 라는 언어는 인터프리터를 사용하며 자바처럼 가상머신 위에서 돌아간다. 자바가 JVM 에서 동작한다면, PHP 의 경우 Zend VM 에서 동작한다.

 

 

 

우리가 작성한 코드를 바로 실행할 수는 없으며, OPCode 라 불리는 중간 형태로 바꾸어 주어야한다. 우리의 코드가 인터프리터를 거치면 OPCode 가 짠! 하고 등장하여 Zend VM 이 어련히 알아서 실행해줄 것이다.

 

 

 

OPCode 가 생성되기 이전의 단계로 가보자. 이 단계에서 우리가 작성한 PHP 의 코드는 토큰화(Tokenize) 되며, 파싱(Parsing)된다. 토큰화라는 것은 코드를 생으로 파싱하지 않고 파서가 더 알아먹기 쉽도록 코드를 바꾸어주는 것을 말한다. 그러한 토큰들은 PHP Core Extension 에서도 일부 확인할 수 있다.

 

https://www.php.net/manual/en/tokens.php

 

PHP: List of Parser Tokens - Manual

T_ENCAPSED_AND_WHITESPACED is returned when parsing strings with evaluated content, like "some $value" or this example from the Strings reference page: foo.Now, I am printing some {$foo->bar[1]}.This should print a capital 'A': \x41EOT;?>This last example

www.php.net

이제 토큰화된 친구를 파서가 파싱할 것이고, 파서의 결과로는 AST(Abstrat Syntex Tree)를 던져줄 것이다. 물론 어떻게 생겨먹었는지는 잘 모르겠지만, 적어도 문법을 해석하기 위해 트리 형태로 표현한 것이라는 사실을 알 것이다.

OPCache

OPCache 는 인터프리트 결과로 나온 OPCode 를 캐시하기 위한 장치이다. 위의 과정을 보면 알겠지만, PHP 로 작성한 것을 해석할 때마다 토큰화하고 파싱해야 한다. 이 과정은 어플리케이션이 방대해질 수록 서버에 부담을 줄 것이다. 따라서 이를 해석하기 위한 대책이 OPCache 다.

 

 

 

OPCache 는 OPCode 를 캐시한다. 어디에? 메모리에 캐시한다. 따라서 캐시된 OPCode 가 OPCache 에 존재할 경우 그대로 Zend VM 을 통해 실행되지만, 없으면 파싱을 한 뒤 OPCache 에 저장할 것이다. 이것이 PHP 가 가진 OPCache 기능의 기본적인 동작이며 php.ini 에서 opcache 항목을 활성화하면 된다.

[opcache]
; Determines if Zend OPCache is enabled
opcache.enable=1

JIT(Just In Time)

JIT 컴파일러PHP 8 에 추가된 새로운 기능 중 하나다. 런타임 중 코드를 캐시하여 반복되는 작업에 대해 기계어로 바꿔주며 재해석에 대한 부하를 줄여주는 큰 역할을 한다. 런타임 중에 이 일을 처리한다는 것은 매우 중요한 특징이다. 그래서 Just In Time 이다. 먼저 아래의 영상을 봐보자. 

We use DynAsm (developed for LuaJIT project) for generation of native code.

이미지 프로세싱을 처리할 때 확실한 성능의 차이가 있음을 보여준다. 물론, 일반적인 어플리케이션에서 비슷한 계산을 엄청 많이하지는 않기 때문에 JIT 컴파일러를 사용한다고 해서 확실하게 성능이 향상된다고 말할 수는 없다. 그러나, 이것을 활용하면 PHP 7 보다도 두 배 이상의 성능차이를 벌릴 수도 있을 것이다. JIT 컴파일러는 우리의 코드를 식별하고 핫(hot) 한 부분을 찾아 최적화를 하려고 들 것이다.

 

 

 

JIT 컴파일러는 어찌되었든 컴파일러이기 때문에 기계어로 바꾸며, 또한 이것에 의해 생성된 PHP 파일의 네이티브 코드는 OPCache 공유 메모리의 추가된다.

PHP JIT doesn't introduce any additional IR (Intermediate Representation) form. It generates native code directly from PHP byte-code. ... When enabled, native code of PHP files is stored in an additional region of the OPcache shared memory

따라서 이미 런타임 중 컴파일된 코드 캐시가 이미 존재한다면, Zend VM 을 거쳐서 실행 될 필요도 없이 그냥 바로 직행할 수 있다. 따라서 JIT 는 반복되는 작업이나 연산에 대해 탁월한 성능상의 이점을 가져다준다. 이러한 JIT 을 활성화하는 방법에 대해서는 RFC 에서 친절하게 설명해주고 있다. php.ini Defaults 를 참고하자.

 

https://wiki.php.net/rfc/jit

 

PHP: rfc:jit

PHP RFC: JIT Introduction It's no secret that the performance jump of PHP 7 was originally initiated by attempts to implement JIT for PHP. We started these efforts at Zend (mostly by Dmitry) back in 2011 and since that time tried 3 different implementation

wiki.php.net

마무리

이 부분은 PHP 코드를 작성하고 실행하는 데에는 큰 도움이 되진 않겠지만, 서비스를 최적화하고 PHP 를 조금 더 깊게 알아보기 위해서는 기본적으로 알아두어야 하는 사항이다. 물론 변태들은 C언어로 작성된 Zend VM 까지 분석해보는 사람도 있기야 하겠지만, 딱히 거기까지 하고 싶지는 않다. 나는 지금 Go 언어를 하고 있지만, 컴파일러를 까보고 싶은 마음은 들지 않는다. 해야 할 공부가 너무나도 많으니까.

'프로그래밍 언어 > PHP' 카테고리의 다른 글

PHP 카테고리 목차 및 문서 정리  (2) 2020.11.13
PHP: declare  (0) 2020.11.13
PHP: Interpreter, OPCache, ― JIT  (0) 2020.10.29
PHP: Thread Safe vs Non Thread Safe  (0) 2020.10.28
PHP: PHP 8 기능 정리 및 요약  (1) 2020.09.23
PHP: 게시판  (0) 2020.09.11