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

Когда производительности PHP не хватает. Перено...

Avatar for zloyuser zloyuser
December 14, 2019

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

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

Avatar for zloyuser

zloyuser

December 14, 2019
Tweet

More Decks by zloyuser

Other Decks in Programming

Transcript

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

    опыта; • Пишу на PHP, Python, C#, Java; • Энтузиаст асинхронного PHP; • Рассказываю странные штуки на PHP конференциях :)
  2. // Worker.php $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); } }
  3. // Worker.php $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); } }
  4. // Worker.php $relay = new \Spiral\Goridge\StreamRelay(STDIN, STDOUT); $worker = new

    \Spiral\RoadRunner\Worker($relay); $client = new \Spiral\RoadRunner\HTTPClient($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); } }
  5. // Worker.php $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); } }
  6. // Worker.php $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); } }
  7. 0 1 2 3 3 2 1 0 От старшего

    к младшему (Big Endian) От младшего к старшему (Little Endian)
  8. 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>
  9. 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>
  10. // 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); }
  11. // 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); }
  12. // 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); }
  13. // 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); }
  14. // 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); }
  15. 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}, 31, 0, NULL, 0) = 31 <0.000027> -sendto(3, {data}, 25, 0, NULL, 0) = 25 <0.000020> +sendto(3, {data}, 56, 0, NULL, 0) = 56 <0.000031> recvfrom(3, {data}, 8192, MSG_WAITALL, NULL, NULL) = 63 <0.000025>
  16. ./php-src/ext/ext_skel.php --ext myext --dir . config.m4 - конфигурационный файл для

    Linux config.w32 - конфигурационный файл для Windows myext.c - пример расширения с парой функций php_myext.h - заголовочный файл расширения tests/001.phpt - пример тестов для расширения tests/002.phpt tests/003.phpt
  17. 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); }
  18. PHP_METHOD(MyClass, anyMethod) { char* buffer = (char*) emalloc(42); // This

    may fail my_wonderfull_extension_function(buffer); // Zend MM will clear automatically **after request** efree(buffer); }
  19. // 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
  20. // 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(); }
  21. // 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(); }
  22. 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); }
  23. 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); }
  24. 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); } typedef struct { Relay * obj; zend_object std; } relay_object;
  25. 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); }
  26. 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); }
  27. 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; }
  28. 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; }
  29. https://wildwolf.name/php-extensions-and-cpp class ZendAllocator<T> { // @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); } }
  30. 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); } }
  31. • Неэффективный заголовок • Отсутствие информации о назначении пакета •

    Невозможно реализовать асинхронный режим работы