Slide 1

Slide 1 text

Антон Шабовта Когда производительности PHP не хватает. Переносим код в С

Slide 2

Slide 2 text

Антон Шабовта PHP Developer в Onliner.by ● Более 10 лет опыта; ● Проекты на PHP, Python, C#, Java; ● Энтузиаст асинхронного PHP; ● Рассказываю странные штуки на конференциях :)

Slide 3

Slide 3 text

Один из крупнейших порталов Беларуси > 8 000 000 просмотров страниц в день > 1 500 000 посещений > 30 000 RPS NGinx > 50 000 RPS Redis > 100 000 000 записей в Cassandra

Slide 4

Slide 4 text

PHP. Производительность

Slide 5

Slide 5 text

PHP. Производительность

Slide 6

Slide 6 text

PHP создан умирать Инициализация + Роутинг + Контроллер + Отправка ответа = Время ответа

Slide 7

Slide 7 text

Плюсы: ● простая обработка ошибок; ● memory leaks не страшны. Минусы: ● производительность :( PHP создан умирать

Slide 8

Slide 8 text

Высокопроизводительный сервер приложений для PHP написанный на Go. RoadRunner

Slide 9

Slide 9 text

RoadRunner Инициализация + Роутинг + Контроллер + Отправка ответа = Время ответа

Slide 10

Slide 10 text

$relay = new \Spiral\Goridge\StreamRelay(STDIN, STDOUT); $worker = new \Spiral\RoadRunner\Worker($relay); $client = new \Spiral\RoadRunner\PSR7Client($worker); $app = new \My\Framework\Kernel(); $app->boot(); while ($request = $client->acceptRequest()) { try { $client->respond($app->handle($request)); } catch (\Throwable $e) { $worker->error((string) $e); } } RoadRunner. Обработчик

Slide 11

Slide 11 text

$relay = new \Spiral\Goridge\StreamRelay(STDIN, STDOUT); $worker = new \Spiral\RoadRunner\Worker($relay); $client = new \Spiral\RoadRunner\PSR7Client($worker); $app = new \My\Framework\Kernel(); $app->boot(); while ($request = $client->acceptRequest()) { try { $client->respond($app->handle($request)); } catch (\Throwable $e) { $worker->error((string) $e); } } RoadRunner. Обработчик

Slide 12

Slide 12 text

$relay = new \Spiral\Goridge\StreamRelay(STDIN, STDOUT); $worker = new \Spiral\RoadRunner\Worker($relay); $client = new \Spiral\RoadRunner\PSR7Client($worker); $app = new \My\Framework\Kernel(); $app->boot(); while ($request = $client->acceptRequest()) { try { $client->respond($app->handle($request)); } catch (\Throwable $e) { $worker->error((string) $e); } } RoadRunner. Обработчик

Slide 13

Slide 13 text

$relay = new \Spiral\Goridge\StreamRelay(STDIN, STDOUT); $worker = new \Spiral\RoadRunner\Worker($relay); $client = new \Spiral\RoadRunner\PSR7Client($worker); $app = new \My\Framework\Kernel(); $app->boot(); while ($request = $client->acceptRequest()) { try { $client->respond($app->handle($request)); } catch (\Throwable $e) { $worker->error((string) $e); } } RoadRunner. Обработчик

Slide 14

Slide 14 text

RoadRunner STDIN STDOUT

Slide 15

Slide 15 text

RoadRunner TCP Socket

Slide 16

Slide 16 text

RoadRunner UNIX Socket

Slide 17

Slide 17 text

flags 1 8 data size (LE) JSON encoded data data size (BE) 8 GoRidge. Протокол

Slide 18

Slide 18 text

0 1 2 3 3 2 1 0 От старшего к младшему (Big Endian) От младшего к старшему (Little Endian) Порядок байт

Slide 19

Slide 19 text

Бенчмарк Результат (ns/op) Pipe Spawn Worker 14087964 Pipe Exec Echo 15194 TCP Spawn Worker 13987739 TCP Exec Echo 44920789 UNIX Spawn Worker 14521818 UNIX Exec Echo 27014 go test -bench=. RoadRunner. Производительность

Slide 20

Slide 20 text

Бенчмарк Результат (ns/op) Pipe Spawn Worker 14087964 Pipe Exec Echo 15194 TCP Spawn Worker 13987739 TCP Exec Echo 44920789 UNIX Spawn Worker 14521818 UNIX Exec Echo 27014 go test -bench=. ??? RoadRunner. Производительность

Slide 21

Slide 21 text

strace -eT php examples/client.php socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 3 <0.000014> connect(3, {}, 16) = 0 <0.000077> sendto(3, {data}, 31, 0, NULL, 0) = 31 <0.000027> sendto(3, {data}, 25, 0, NULL, 0) = 25 <0.000020> recvfrom(3, {data}, 17, MSG_WAITALL, NULL, NULL) = 17 <0.000023> recvfrom(3, {data}, 14, MSG_WAITALL, NULL, NULL) = 14 <0.000022> recvfrom(3, {data}, 17, MSG_WAITALL, NULL, NULL) = 17 <0.000009> recvfrom(3, {data}, 16, MSG_WAITALL, NULL, NULL) = 16 <0.000006> Цена одного syscall

Slide 22

Slide 22 text

strace -eT php examples/client.php socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 3 <0.000014> connect(3, {}, 16) = 0 <0.000077> sendto(3, {data}, 31, 0, NULL, 0) = 31 <0.000027> sendto(3, {data}, 25, 0, NULL, 0) = 25 <0.000020> recvfrom(3, {data}, 17, MSG_WAITALL, NULL, NULL) = 17 <0.000023> recvfrom(3, {data}, 14, MSG_WAITALL, NULL, NULL) = 14 <0.000022> recvfrom(3, {data}, 17, MSG_WAITALL, NULL, NULL) = 17 <0.000009> recvfrom(3, {data}, 16, MSG_WAITALL, NULL, NULL) = 16 <0.000006> Цена одного syscall

Slide 23

Slide 23 text

sendto sendto recvfrom sendto sendto recvfrom Цена одного syscall. Алгоритм Нейгла

Slide 24

Slide 24 text

sendto sendto recvfrom sendto recvfrom recvfrom Цена одного syscall. Алгоритм Нейгла

Slide 25

Slide 25 text

// https://github.com/spiral/roadrunner/src/Worker.php:83 public function send(string $payload = null, string $header = null) { if (is_null($header)) { $this->relay->send($header, PAYLOAD_CONTROL | PAYLOAD_NONE); } else { $this->relay->send($header, PAYLOAD_CONTROL | PAYLOAD_RAW); } $this->relay->send($payload, Relay::PAYLOAD_RAW); } Цена одного syscall

Slide 26

Slide 26 text

// https://github.com/spiral/roadrunner/src/Worker.php:83 public function send(string $payload = null, string $header = null) { if (is_null($header)) { $this->relay->send($header, PAYLOAD_CONTROL | PAYLOAD_NONE); } else { $this->relay->send($header, PAYLOAD_CONTROL | PAYLOAD_RAW); } $this->relay->send($payload, Relay::PAYLOAD_RAW); } Цена одного syscall

Slide 27

Slide 27 text

// https://github.com/zloyuser/roadrunner/src/Worker.php:118 public function send(string $payload = '', string $header = '') { $flag = PAYLOAD_CONTROL; $flag = $flag | (empty($header) ? PAYLOAD_NONE : PAYLOAD_RAW); $head = Goridge\pack($header, $flag); $body = Goridge\pack($payload, PAYLOAD_RAW); $this->relay->send($head . $body); } Цена одного syscall. Рефакторинг

Slide 28

Slide 28 text

// https://github.com/zloyuser/roadrunner/src/Worker.php:118 public function send(string $payload = '', string $header = '') { $flag = PAYLOAD_CONTROL; $flag = $flag | (empty($header) ? PAYLOAD_NONE : PAYLOAD_RAW); $head = Goridge\pack($header, $flag); $body = Goridge\pack($payload, PAYLOAD_RAW); $this->relay->send($head . $body); } Цена одного syscall. Рефакторинг

Slide 29

Slide 29 text

// https://github.com/zloyuser/roadrunner/src/Worker.php:118 public function send(string $payload = '', string $header = '') { $flag = PAYLOAD_CONTROL; $flag = $flag | (empty($header) ? PAYLOAD_NONE : PAYLOAD_RAW); $head = Goridge\pack($header, $flag); $body = Goridge\pack($payload, PAYLOAD_RAW); $this->relay->send($head . $body); } Цена одного syscall. Рефакторинг

Slide 30

Slide 30 text

strace -eT php examples/client.php socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 3 <0.000014> connect(3, {options}, 16) = -1 <0.000072> sendto(3, {data}, 56, 0, NULL, 0) = 56 <0.000031> recvfrom(3, {data}, 8192, MSG_WAITALL, NULL, NULL) = 63 <0.000025> Цена одного syscall

Slide 31

Slide 31 text

Бенчмарк Результат (ns/op) Pipe Spawn Worker 13757261 Pipe Exec Echo 15635 TCP Spawn Worker 13652737 TCP Exec Echo 25165 UNIX Spawn Worker 14332569 UNIX Exec Echo 17805 go test -bench=. Цена одного syscall. Производительность

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

PHP расширение. Документация https://www.php.net/manual/ru/internals2.php

Slide 34

Slide 34 text

PHP расширение. Документация http://www.phpinternalsbook.com

Slide 35

Slide 35 text

PHP расширение. Документация Блоги: ● https://nikic.github.io ● https://blog.krakjoe.ninja ● https://blog.ircmaxell.com Примеры расширений: ● https://github.com/php/php-src ● https://github.com/php-ds/ext-ds ● https://github.com/datastax/php-driver

Slide 36

Slide 36 text

./php-src/ext/ext_skel.php --ext myext --dir . config.m4 - конфигурационный файл для Linux config.w32 - конфигурационный файл для Windows myext.c - пример расширения с парой функций php_myext.h - заголовочный файл расширения tests/001.phpt - пример тестов для расширения PHP расширение. Подготовка

Slide 37

Slide 37 text

PHP_METHOD(MyClass, anyMethod) { char* buffer = (char*)malloc(42); // This may fail my_wonderfull_extension_function(buffer); // Never be called if we fail before free(buffer); } PHP расширение. Работа с памятью

Slide 38

Slide 38 text

PHP_METHOD(MyClass, anyMethod) { char* buffer = emalloc(42); // This may fail my_wonderfull_extension_function(buffer); // Zend MM will clear automatically **after request** efree(buffer); } PHP расширение. Работа с памятью

Slide 39

Slide 39 text

No content

Slide 40

Slide 40 text

// config.m4 if test "$PHP_MYEXT" != "no"; then PHP_REQUIRE_CXX() \ PHP_SUBST(MYEXT_SHARED_LIBADD) \ PHP_NEW_EXTENSION(myext, \ src/MyCoolClass.cpp \ myext.c \ , $ext_shared, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1) PHP_ADD_BUILD_DIR($ext_builddir/src, 1) fi PHP расширение. Интеграция С++

Slide 41

Slide 41 text

// Relay.php class Relay { public function send(string $data): void; public function receive(): ?Frame; } // Relay.hpp class Relay { public: void send(std::string& data); Frame * receive(); } PHP расширение. Проектирование классов С++

Slide 42

Slide 42 text

// Relay.php class Relay { public function send(string $data): void; public function receive(): ?Frame; } // Relay.hpp class Relay { public: void send(std::string& data); void send(const char* data, size_t size); Frame * receive(); } PHP расширение. Проектирование классов С++

Slide 43

Slide 43 text

PHP_METHOD(Relay, send) { zend_string* payload; if (ZEND_PARSE_THROW("S", &payload) == FAILURE) { return; } relay_object * relay = relay_from_zend(Z_OBJ_P(getThis())); relay->obj->send(ZSTR_VAL(payload), ZSTR_LEN(payload)); zend_string_release(payload); RETURN_ZVAL(getThis(), 1, 0); } PHP расширение. Реализация

Slide 44

Slide 44 text

PHP_METHOD(Relay, send) { zend_string* payload; if (ZEND_PARSE_THROW("S", &payload) == FAILURE) { return; } relay_object * relay = relay_from_zend(Z_OBJ_P(getThis())); relay->obj->send(ZSTR_VAL(payload), ZSTR_LEN(payload)); zend_string_release(payload); RETURN_ZVAL(getThis(), 1, 0); } PHP расширение. Реализация

Slide 45

Slide 45 text

PHP_METHOD(Relay, send) { zend_string* payload; if (ZEND_PARSE_THROW("S", &payload) == FAILURE) { return; } relay_object * relay = relay_from_zend(Z_OBJ_P(getThis())); relay->obj->send(ZSTR_VAL(payload), ZSTR_LEN(payload)); zend_string_release(payload); RETURN_ZVAL(getThis(), 1, 0); } PHP расширение. Реализация typedef struct { Relay * obj; zend_object std; } relay_object;

Slide 46

Slide 46 text

PHP_METHOD(Relay, send) { zend_string* payload; if (ZEND_PARSE_THROW("S", &payload) == FAILURE) { return; } relay_object * relay = relay_from_zend(Z_OBJ_P(getThis())); relay->obj->send(ZSTR_VAL(payload), ZSTR_LEN(payload)); zend_string_release(payload); RETURN_ZVAL(getThis(), 1, 0); } PHP расширение. Реализация

Slide 47

Slide 47 text

PHP_METHOD(Relay, send) { zend_string* payload; if (ZEND_PARSE_THROW("S", &payload) == FAILURE) { return; } relay_object * relay = relay_from_zend(Z_OBJ_P(getThis())); relay->obj->send(ZSTR_VAL(payload), ZSTR_LEN(payload)); zend_string_release(payload); RETURN_ZVAL(getThis(), 1, 0); } PHP расширение. Реализация

Slide 48

Slide 48 text

PHP_METHOD(MyClass, anyMethod) { char* buffer = new char[42]; try { my_wonderfull_extension_function(buffer); } catch (const std::exception& e) { zend_throw_exception(zend_ce_exception, e.what(), 0); } // This will be called always delete[] buffer; } PHP расширение. Обработка исключений

Slide 49

Slide 49 text

Zend MM

Slide 50

Slide 50 text

PHP_METHOD(MyClass, anyMethod) { std::string * buffer = new std::string("PHP will never die!"); // This may fail my_wonderfull_extension_function(buffer); // Never be called if we fail before delete buffer; } PHP расширение. Интеграция Zend MM и C++

Slide 51

Slide 51 text

https://wildwolf.name/php-extensions-and-cpp class ZendAllocator { // @param cnt Number of objects to allocate // @return Pointer to the allocated storage T* allocate(std::size_t cnt) { return static_cast(safe_emalloc(cnt, sizeof(T), 0)); } // @param p Storage to deallocate // @param cnt Number of the allocated objects void deallocate(T* p, std::size_t cnt) { static_cast(cnt); efree(p); } } PHP расширение. Интеграция Zend MM и C++

Slide 52

Slide 52 text

using zstring = std::basic_string< char, std::char_traits, ZendAllocator >; PHP_METHOD(MyClass, anyMethod) { zstring* buffer = new zstring("PHP will never die!"); try { my_wonderfull_extension_function(buffer); } catch (const std::exception& e) { zend_throw_exception(zend_ce_exception, e.what(), 0); } } PHP расширение. Интеграция Zend MM и C++

Slide 53

Slide 53 text

PHP расширение. Бенчмарки

Slide 54

Slide 54 text

PHP расширение. Бенчмарки

Slide 55

Slide 55 text

flags 1 2 sequence Binary data 4 opcode data size (BE) 1 GoRidge V3. Работа над ошибками

Slide 56

Slide 56 text

flags 1 2 sequence Binary data 4 opcode data size (BE) 1 no control frames GoRidge V3. Работа над ошибками

Slide 57

Slide 57 text

flags 1 2 sequence Binary data 4 opcode data size (BE) 1 async ready GoRidge V3. Работа над ошибками

Slide 58

Slide 58 text

flags 1 2 sequence Binary data 4 opcode data size (BE) 1 one size GoRidge V3. Работа над ошибками

Slide 59

Slide 59 text

flags 1 2 sequence Binary data 4 opcode data size (BE) 1 GoRidge V3. Работа над ошибками JSON

Slide 60

Slide 60 text

flags 1 2 sequence Binary data 4 opcode data size (BE) 1 GoRidge V3. Работа над ошибками Google Protobuf

Slide 61

Slide 61 text

No content

Slide 62

Slide 62 text

Антон Шабовта СПАСИБО! Вопросы?