Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve performance, add internal Clock, reuse clock in same tick #457

Merged
merged 1 commit into from May 20, 2022

Conversation

clue
Copy link
Member

@clue clue commented May 19, 2022

This PR improves the HTTP server performance by adding an internal Clock instance that reuses the system clock within the same loop tick. This helps avoiding unneeded gettimeofday() syscalls that show a noticeable performance impact especially during benchmark runs (see also #455).

The new internal Clock class is mostly used as an internal optimization to avoid unneeded syscalls to get the current system time multiple times within the same loop tick. For the purpose of the HTTP server, the clock is assumed to not change to a significant degree within the same loop tick. If you need a high precision clock source, you may want to use \hrtime() instead (PHP 7.3+).

The API is modelled to resemble the PSR-20 ClockInterface (in draft at the time of writing this), but uses a float return value for performance reasons instead.

For a single HTTP request, this means the following syscalls can be eliminated:

pselect6(5, [4], [], [], NULL, NULL)    = 1 (in [4])
poll([{fd=4, events=POLLIN|POLLERR|POLLHUP}], 1, 0) = 1 ([{fd=4, revents=POLLIN}])
accept(4, {sa_family=AF_INET6, sin6_port=htons(54462), sin6_flowinfo=htonl(0), inet_pton(AF_INET6, "::ffff:127.0.0.1", &sin6_addr), sin6_scope_id=0}, [128 => 28]) = 3
fcntl(3, F_GETFL)                       = 0x2 (flags O_RDWR)
fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK)    = 0
fcntl(3, F_GETFL)                       = 0x802 (flags O_RDWR|O_NONBLOCK)
fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK)    = 0
pselect6(5, [3 4], [], [], NULL, NULL)  = 1 (in [3])
poll([{fd=3, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, 0) = 1 ([{fd=3, revents=POLLIN}])
recvfrom(3, "G", 1, MSG_PEEK, NULL, NULL) = 1
recvfrom(3, "GET / HTTP/1.1\r\nHost: localhost:"..., 65536, 0, NULL, NULL) = 78
poll([{fd=3, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, 0) = 0 (Timeout)
recvfrom(3, 0x7fceeb63d066, 65458, 0, NULL, NULL) = -1 EAGAIN (Resource temporarily unavailable)
getpeername(3, {sa_family=AF_INET6, sin6_port=htons(54462), sin6_flowinfo=htonl(0), inet_pton(AF_INET6, "::ffff:127.0.0.1", &sin6_addr), sin6_scope_id=0}, [128 => 28]) = 0
getsockname(3, {sa_family=AF_INET6, sin6_port=htons(8080), sin6_flowinfo=htonl(0), inet_pton(AF_INET6, "::ffff:127.0.0.1", &sin6_addr), sin6_scope_id=0}, [128 => 28]) = 0
gettimeofday({tv_sec=1652528350, tv_usec=915571}, NULL) = 0
- gettimeofday({tv_sec=1652528350, tv_usec=915621}, NULL) = 0
- gettimeofday({tv_sec=1652528350, tv_usec=915765}, NULL) = 0
pselect6(5, [3 4], [3], [], NULL, NULL) = 1 (out [3])
sendto(3, "HTTP/1.1 200 OK\r\nContent-Type: t"..., 150, 0, NULL, 0) = 150
pselect6(5, [3 4], [], [], NULL, NULL)  = 1 (in [3])
poll([{fd=3, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, 0) = 1 ([{fd=3, revents=POLLIN}])
recvfrom(3, "", 1, MSG_PEEK, NULL, NULL) = 0
recvfrom(3, "", 65536, 0, NULL, NULL)   = 0
shutdown(3, SHUT_RDWR)                  = 0
fcntl(3, F_GETFL)                       = 0x802 (flags O_RDWR|O_NONBLOCK)
fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK)    = 0
close(3)                                = 0

pselect6(5, [4], [], [], NULL, NULL)    = …

This is especially noticeable during a benchmark run. In particular, the HTTP server may handle multiple concurrent connections in a single loop tick, so even the first gettimeofday() call above will only be invoked once per tick. Running docker run -it --rm --net=host jordi/ab -n1000000 -c50 -k http://localhost:8080/ against the example HTTP server suggests results improved from 19188 req/s to 20318 req/s on my machine (best of 5 each).

This changeset only refactors some internal logic and does not affect the public API, so it should be safe to apply. The test suite confirms this has 100% code coverage.

Refs #455 and reactphp/event-loop#246

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants