Upgrade to Pro — share decks privately, control downloads, hide ads and more …

«Когда производительности PHP-кода не хватает: пишем С драйвер для PHP + RoadRunner» — Антон Шабовта (Onliner)

Badoo Tech
September 21, 2019

«Когда производительности PHP-кода не хватает: пишем С драйвер для PHP + RoadRunner» — Антон Шабовта (Onliner)

Badoo PHP Meetup #3

Основные тезисы:

RoadRunner: протокол и особенности реализации
PHP + Go — это быстро, надо ли еще оптимизировать?
Реализация клиентского кода на С: с чего начать и когда вообще стоит переносить что-то в PHP-extension?
Сравнение производительности PHP-FPM, RoadRunner и RoadRunner + C.
Бонус: а если асинхронно? :)

Badoo Tech

September 21, 2019
Tweet

More Decks by Badoo Tech

Other Decks in Technology

Transcript

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

    С
  2. Антон Шабовта PHP Developer в Onliner.by • Более 10 лет

    опыта; • Проекты на PHP, Python, C#, Java; • Энтузиаст асинхронного PHP; • Рассказываю странные штуки на конференциях :)
  3. Один из крупнейших порталов Беларуси > 8 000 000 просмотров

    страниц в день > 1 500 000 посещений > 30 000 RPS NGinx > 50 000 RPS Redis > 100 000 000 записей в Cassandra
  4. PHP. Производительность

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

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

    ответа = Время ответа
  7. Плюсы: • простая обработка ошибок; • memory leaks не страшны.

    Минусы: • производительность :( PHP создан умирать
  8. Высокопроизводительный сервер приложений для PHP написанный на Go. RoadRunner

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

    Время ответа
  10. $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. Обработчик
  11. $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. Обработчик
  12. $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. Обработчик
  13. $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. Обработчик
  14. RoadRunner STDIN STDOUT

  15. RoadRunner TCP Socket

  16. RoadRunner UNIX Socket

  17. flags 1 8 data size (LE) JSON encoded data data

    size (BE) 8 GoRidge. Протокол
  18. 0 1 2 3 3 2 1 0 От старшего

    к младшему (Big Endian) От младшего к старшему (Little Endian) Порядок байт
  19. Бенчмарк Результат (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. Производительность
  20. Бенчмарк Результат (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. Производительность
  21. 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
  22. 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
  23. sendto sendto recvfrom sendto sendto recvfrom Цена одного syscall. Алгоритм

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

    Нейгла
  25. // 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
  26. // 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
  27. // 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. Рефакторинг
  28. // 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. Рефакторинг
  29. // 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. Рефакторинг
  30. 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
  31. Бенчмарк Результат (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. Производительность
  32. None
  33. PHP расширение. Документация https://www.php.net/manual/ru/internals2.php

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

  35. 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
  36. ./php-src/ext/ext_skel.php --ext myext --dir . config.m4 - конфигурационный файл для

    Linux config.w32 - конфигурационный файл для Windows myext.c - пример расширения с парой функций php_myext.h - заголовочный файл расширения tests/001.phpt - пример тестов для расширения PHP расширение. Подготовка
  37. 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 расширение. Работа с памятью
  38. 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 расширение. Работа с памятью
  39. None
  40. // 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 расширение. Интеграция С++
  41. // 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 расширение. Проектирование классов С++
  42. // 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 расширение. Проектирование классов С++
  43. 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 расширение. Реализация
  44. 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 расширение. Реализация
  45. 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;
  46. 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 расширение. Реализация
  47. 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 расширение. Реализация
  48. 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 расширение. Обработка исключений
  49. Zend MM

  50. 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++
  51. 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<T*>(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<void>(cnt); efree(p); } } PHP расширение. Интеграция Zend MM и C++
  52. using zstring = std::basic_string< char, std::char_traits<char>, ZendAllocator<char> >; 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++
  53. PHP расширение. Бенчмарки

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

  55. flags 1 2 sequence Binary data 4 opcode data size

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

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

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

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

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

    (BE) 1 GoRidge V3. Работа над ошибками Google Protobuf
  61. None
  62. Антон Шабовта СПАСИБО! Вопросы?