정보실

웹학교

정보실

php 프로그래밍에서 PHP FFI를 사용하는 방법

본문

외부 함수 인터페이스를 사용하여 PHP가 Go, Rust 및 C ++에서 작동하도록 하는 팁.


https://spiralscout.com/blog/how-to-use-php-ffi-in-programming 


PHP 7.4의 2020 릴리스는 개발자들에게 이전에는 해 보지 못한 작업을 수행 할 수 있는 기능을 제공했습니다. 

데이터 구조에 액세스하고 순수 PHP 코드를 사용하여 다른 언어로 작성된 함수를 호출하고 확장 기능이 없으며 외부 라이브러리에 바인딩 할 필요가 없습니다. 

이것이 어떻게 가능한지? PHP FFI (Foreign Function Interface) 사용 이 기사에서는 PHP FFI의 장점, 장점 및 기능에 대해 논의하고 플러그인을 만들지 않고도 PHP가 Go, Rust 및 C ++와 같은 언어와 작동하는 방식을 비교합니다. 

또한 이 기능을 구현하는 데 사용한 실험을 공유하고 가장 유용한 부분과 문제가 없는 부분을 강조합니다.


Programming with PHP FFI 


PHP FFI 란 무엇입니까? 


일반적으로 FFI는 개발자가 한 언어를 사용하여 다른 언어로 작성된 라이브러리 함수를 호출 할 수 있는 인터페이스입니다. 예를 들어, FFI를 사용하면 Rust, C ++ 또는 Go from pure PHP로 작성된 함수를 호출 할 수 있습니다. 해석 된 언어를 컴파일 된 언어와 연결하기 위해 libffi [GitHub] 라이브러리 인 Wiki가 사용됩니다.


해석 된 언어는 호출 된 함수의 매개 변수 (즉, 레지스터)를 구체적으로 검색 할 위치 또는 호출 후 함수의 결과를 얻을 수있는 위치를 알지 못하므로 해당 작업을 수행하기 위해 Libffi에 의존합니다. 따라서 이 라이브러리는 시스템 라이브러리 (Linux)의 일부이므로 설치해야 합니다.


PHP FFI의 장점 


지금은 완전히 실험적이지만, PHP FFI의 초기 테스트는 번거로운 PHP 확장을 제거하고 궁극적으로 흥미로운 새로운 개발 시대를 열 수 있는 많은 이점을 보여줍니다.


  • C 프로그램 / 라이브러리와 인터페이스하기 위해 PHP 전용 확장 / 모듈을 작성할 필요가 없는 시간과 에너지 절약
  • 이미지 및 비디오 처리와 같은 무거운 컴퓨팅 작업에서 더 빠르게 실행
  • 고가의 VM 및 컨테이너를 시작하는 것보다 일반적인 클라우드 플랫폼에서 PHP를 시작하는 비용을 절약하십시오.


PHP FFI 실험 


참고 : 모든 PHP FFI 실험은 ArchLinux (5.6.1 커널), Libffi 3.2.1에서 수행되었습니다.


우리는 실험을 통해 매우 엄격한 방법으로 과학적 방법을 고수하지는 않았지만 목적을 세웠습니다. 새로운 언어 기능을 살펴 보는 것은 매우 흥미로운 일이지만, 우리가 묻는 질문은 소프트웨어 개발에 실질적인 의미가 있습니까?


결과는 다음과 같습니다.


PHP FFI로 피보나치 시퀀스 계산 


첫 번째 실험에서 피보나치 수열 계산과 같은 문제는 간단하지만 흥미로웠다고 생각했습니다. 물론 우리는 가장 효율적인 방법으로 그 일을 시작하지 않았습니다. 오히려, 우리는 가능한 많은 프로세서를 사용하기 위해 재귀의 도움을 원했습니다. 이렇게 하면 컴파일 된 언어가 이 기능을 최적화 하지 못하게 됩니다 (예 : 루프 해제 기술 적용).


실험 1 : Rust 사용 


PHP의 경우, 가장 먼저 php.ini (아키 리눅스의 /etc/php/php.ini)에서 확장자 ffi를 주석 해제했습니다. 다음으로 조건부 인터페이스를 선언해야 했습니다. 현재 PHP FFI에 존재하는 몇 가지 제한 사항이 있습니다. 특히 일부 특수한 경우를 제외하고 C 프리 프로세서 (#include, #define 등)를 사용할 수 없습니다. PHP에서 :

$ffi = FFI::cdef(
     "int Fib(int n);",
    "/PATH/TO/SO/lib.so");
  1. FFI :: cdef –이 작업으로 상호 작용 인터페이스를 정의합니다.
  2. int Fib (int n) – IT는 컴파일 된 언어의 내 보낸 메소드 이름입니다. 조금 나중에 어떻게 해야 하는지 이야기하겠습니다.
  3. /PATH/TO/SO/lib.so – 위 함수가 있는 동적 라이브러리의 경로입니다.

우리가 사용한 전체 PHP 스크립트 : PHP FFI 


function fib($n)
{
    if ($n === 1 || $n === 2) {
        return 1;
    }
    return fib($n - 1) + fib($n - 2);
}

$start = microtime(true);
$p = 0;
for ($i = 0; $i < 1000000; $i++) {
    $p = fib(12);
}

echo '[PHP] execution time: '.(microtime(true) - $start).' Result: '.$p.PHP_EOL;

RUST FFI 


$rust_ffi = FFI::cdef(
    "int Fib(int n);",
    "lib/libphp_rust_ffi.so");

$start = microtime(true);
$r = 0;
for ($i=0; $i < 1000000; $i++) { $r = $rust_ffi->Fib(12);
}

echo '[RUST] execution time: '.(microtime(true) - $start).' Result: '.$r.PHP_EOL;

CPP FFI 


$cpp_ffi = FFI::cdef(
    "int Fib(int n);",
    "lib/libphp_cpp_ffi.so");

$start = microtime(true);
$c = 0;
for ($i=0; $i < 1000000; $i++) { $c = $cpp_ffi->Fib(12);
}

echo '[CPP] execution time: '.(microtime(true) - $start).' Result: '.$c.PHP_EOL;


GOLANG FFI 


$golang_ffi = FFI::cdef(
    "int Fib(int n);",
    "lib/libphp_go_ffi.so");

$start = microtime(true);

for ($i=0; $i < 1000000; $i++) { $golang_ffi->Fib(12);
}

echo '[GOLANG] execution time: '.(microtime(true) - $start).' Result: '.$c.PHP_EOL;

첫 번째 단계는 Rust 언어로 동적 라이브러리를 만드는 것입니다. 이를 위해서는 몇 가지 준비가 필요했습니다. 1. 모든 플랫폼에서 설치를 위해 여기에서 하나의 지침 만 필요했습니다. 2. 그 후, 우리는 cargo new rust_php_ffi 명령으로 어디에서나 프로젝트를 만들 수 있습니다. 그게 다야! 우리가 사용한 기능은 다음과 같습니다. RUST :


//src/lib.rs

#[no_mangle]
extern "C" fn Fib(n: i32) -> i32 {
    if (n == 0) || (n == 1) {
        return 1;
    }

    Fib(n - 1) + Fib(n - 2)
}

참고 : 필요한 함수에 # [no_mangle] 속성을 추가하는 것이 중요합니다. 그렇지 않으면 컴파일러가 함수 이름을 _аgs @ fs34와 같은 이름으로 바꿉니다. 그리고 PHP로 내보낼 때 libffi는 단순히 동적 라이브러리에서 Fib라는 함수를 찾을 수 없습니다. 이 문제에 대한 자세한 내용은 여기를 참조하십시오. Cargo.toml에서 다음 속성을 추가했습니다.


[lib]
crate-type = ["cdylib"]

Cargo.toml의 속성을 통해 동적 라이브러리에 대한 세 가지 옵션이 있다는 사실에 주의를 기울이고 싶습니다. 1. dylib – Rust는 불안정한 ABI와 이 라이브러리를 공유합니다. 내부 ABI로 이동). 2. cdylib는 C / C ++에서 사용하기 위한 동적 라이브러리입니다. 이것이 우리의 최고의 선택입니다. 3. rlib – rlib extestion (.rlib)이 있는 Rust 정적 라이브러리. 또한 Rust로 작성된 다양한 rlib를 연결하는 데 사용되는 메타 데이터도 포함하고 있으며, 카고 빌드 --release를 사용하여 컴파일했습니다. 그리고 target / release 폴더에서 .so 파일을 보았습니다. 이것이 우리의 역동적 인 도서관입니다. C ++ 다음은 C ++입니다. CPP :


// in php_cpp_ffi.cpp

int main() {
    
}

extern "C" int Fib(int n) {
    if ((n==1) || (n==2)) {
        return 1;
    }

    return Fib(n-1) + Fib(n-2);
}


우리는 extern 함수를 선언하여 PHP에서 가져올 수 있도록해야했습니다. 우리는 컴파일했다 :


g++ -fPIC -O3 -shared src/php_cpp_ffi.cpp -o ../lib/ libphp_cpp_ffi.so


컴파일에 대한 몇 가지 의견 : 1. -fPIC 위치 독립적 인 코드. 동적 라이브러리의 경우 메모리에 로드 되는 주소와 독립적이어야합니다. 2. -O3 – 최대 최적화


실험 2 : Golang 사용 


다음 실험에서는 런타임 언어 인 Golang이 있었습니다. Go를 위해 CGO라는 동적 라이브러리와 상호 작용하기 위한 특수 메커니즘이 개발되었습니다. 이 메커니즘의 작동 방식에 대해 자세히 알아보십시오. CGO가 C에서 생성 된 오류를 해석한다는 사실 때문에 C ++ 링크 및 링크에서와 같이 최적화를 사용할 수 있는 방법이 없었습니다. 드럼 롤 부탁합니다. . . 그리고 여기 코드가 있습니다! 골랑 :


package main

import (
        "C"
)

// we needed to have empty main in package main :)
// because -buildmode=c-shared requires exactly one main package
func main() {

}

//export Fib
func Fib(n C.int) C.int {
        if n == 1 || n == 2 {
                return 1
        }

        return Fib(n-1) + Fib(n-2)
}


따라서 이 모든 함수는 동일한 Fib 함수이지만 이 함수를 동적 라이브러리로 내보내려면 위의 주석 (일종의 GO 특성) // export Fib를 추가해야 합니다. 그런 다음 go build -o ../lib/libphp_go_ffi.so -buildmode = c-shared를 컴파일했습니다. 동적 라이브러리를 얻기 위해 -buildmode = c-shared를 추가 한 방법에 주의하십시오. 출력시 2 개의 파일을 받았습니다. 헤더가 .h 및 .so 인 파일은 동적 라이브러리입니다. 함수 이름을 알고 있기 때문에 헤더가 있는 파일은 실제로 필요하지 않으며 FFI PHP는 C 전 처리기 작업에 제한이 있습니다.


로켓 발사 


모든 것을 작성한 후 (소스 코드가 제공됨) 작은 Makefile을 만들어 모든 것을 수집했습니다 (저장소에도 위치). lib 폴더에서 make build를 호출 한 후 4 개의 파일이 나타납니다. GO (.h / .so)의 경우 2 개, Rust 및 C ++의 경우 2 개. 메이크 파일 :


build_cpp:
        echo 'Building cpp...'
        cd cpp && g++ -fPIC -O3 -shared src/php_cpp_ffi.cpp -o libphp_cpp_ffi.so

build_go:
        echo 'Building golang...'
        cd golang && go build -o libphp_go_ffi.so -buildmode=c-shared

build_rust:
        echo 'Building Rust...'
        cargo build --release && mv rust/target/release/libphp_ffi.so libphp_rust_ffi.so

build: build_cpp build_go build_rust


run:
        php php/php_fib.php

그런 다음 php 폴더로 이동하여 스크립트를 실행했습니다 (또는 Makefile – make run). 참고 : FFI :: cdef의 php 스크립트에서 .so 파일의 경로는 하드 코드 되어 있으므로 모든 것이 작동하려면 make run을 실행하십시오. 작업 결과는 다음과 같습니다.


1. [PHP] execution time: 8.6763260364532 Result: 144

2. [RUST] execution time: 0.32162690162659 Result: 144

3. [CPP] execution time: 0.3515248298645 Result: 144

4. [GOLANG] execution time: 5.0730509757996 Result: 144


예상대로 PHP는 수학 연산으로 로드 된 CPU에서 가장 낮은 결과를 보였지만 그럼에도 불구하고 전체적으로 백만 번의 호출에 대해 매우 느꼈습니다. 우리는 CGO의 실행 시간이 PHP보다 약간 짧은 것에 놀랐습니다. 이는 불안정한 ABI로 인해 소집 된 소집으로 인한 것입니다. CGO는 Go-types에서 C (GO 동적 라이브러리를 빌드 한 후 얻은 h 파일에서 볼 수 있음) 유형으로 유형 변환 작업을 수행해야 했으며 C와 C의 수신 및 반환 값을 복사해야 했습니다. GO 호환성. Rust와 C ++는 실험에서 가장 좋은 결과를 보여주었습니다. 솔직히 말해서 안정적인 ABI (Rust의 extern)를 가지고 PHP와 이러한 언어 사이의 유일한 계층은 libffi이기 때문에 우리가 기대했던 것입니다.


결론 


다양한 함정을 겪을 가능성이 높기 때문에 PHP FFI 접근 방식은 아직 생산 준비가 되지 않았지만 유망합니다. PHP 개발 커뮤니티는 PHP.net에서 기능을 소개하는 자체 경고를 발행했습니다.


PHP FFI warning 

결론 : 전처리기를 사용하는 일반적인 방법은 없습니다.


이 기사는 단순히 새로운 언어 기능의 기능을 강조하기 위한 것입니다. 그러나 PHP의 이 기능이 안정되면 코드에서 핫스팟을 최적화 할 수 있는 가능성을 상상해보십시오. 게임 체인저일 수 있습니다.



페이지 정보

조회 22회 ]  작성일20-06-26 15:18

웹학교