Ответ 1
Ожидается.
Вы запускаете этот тест на виртуальной машине, на которой стоимость системных вызовов выше, чем на физическом оборудовании. Когда gevent активирован, он имеет тенденцию генерировать больше системных вызовов (для обработки устройства epoll), поэтому вы оказываетесь в меньшей производительности.
Вы можете легко проверить эту точку, используя strace на script.
Без gevent внутренний цикл генерирует:
recvfrom(3, ":931\r\n", 4096, 0, NULL, NULL) = 6
sendto(3, "*3\r\n$6\r\nINCRBY\r\n$10\r\ntestsocket\r"..., 41, 0, NULL, 0) = 41
recvfrom(3, ":941\r\n", 4096, 0, NULL, NULL) = 6
sendto(3, "*3\r\n$6\r\nINCRBY\r\n$10\r\ntestsocket\r"..., 41, 0, NULL, 0) = 41
С помощью gevent вы будете иметь вхождения:
recvfrom(3, ":221\r\n", 4096, 0, NULL, NULL) = 6
sendto(3, "*3\r\n$6\r\nINCRBY\r\n$10\r\ntestsocket\r"..., 41, 0, NULL, 0) = 41
recvfrom(3, 0x7b0f04, 4096, 0, 0, 0) = -1 EAGAIN (Resource temporarily unavailable)
epoll_ctl(5, EPOLL_CTL_ADD, 3, {EPOLLIN, {u32=3, u64=3}}) = 0
epoll_wait(5, {{EPOLLIN, {u32=3, u64=3}}}, 32, 4294967295) = 1
clock_gettime(CLOCK_MONOTONIC, {2469, 779710323}) = 0
epoll_ctl(5, EPOLL_CTL_DEL, 3, {EPOLLIN, {u32=3, u64=3}}) = 0
recvfrom(3, ":231\r\n", 4096, 0, NULL, NULL) = 6
sendto(3, "*3\r\n$6\r\nINCRBY\r\n$10\r\ntestsocket\r"..., 41, 0, NULL, 0) = 41
Когда вызов recvfrom блокируется (EAGAIN), gevent возвращается к циклу событий, поэтому для ожидания событий файловых дескрипторов (epoll_wait) выполняются дополнительные вызовы.
Обратите внимание, что этот тип тестов является наихудшим случаем для любой системы циклов событий, потому что у вас есть только один файловый дескриптор, поэтому операции ожидания не могут быть факторизованы на нескольких дескрипторах. Кроме того, асинхронные входы/выходы здесь ничего не могут улучшить, поскольку все синхронно.
Это также худший случай для Redis, потому что:
-
он генерирует много обращений к серверу
-
систематически подключается/отключается (1000 раз), потому что пул объявлен в функции UxDomainSocket.
На самом деле ваш тест не тестирует gevent, redis или redis-py: он использует способность виртуальной машины поддерживать игру в пинг-понг между двумя процессами.
Если вы хотите повысить производительность, вам необходимо:
-
используйте конвейерную линию для уменьшения количества обращений в оба конца
-
сделать пул постоянным по всему эталону
Например, рассмотрите следующие script:
#!/usr/bin/python
from gevent import monkey
monkey.patch_all()
import timeit
import redis
from redis.connection import UnixDomainSocketConnection
pool = redis.ConnectionPool(connection_class=UnixDomainSocketConnection, path = '/tmp/redis.sock')
def UxDomainSocket():
r = redis.Redis(connection_pool = pool)
p = r.pipeline(transaction=False)
p.set("testsocket", 1)
for i in range(100):
p.incr('testsocket', 10)
p.get('testsocket')
p.delete('testsocket')
p.execute()
print timeit.Timer(stmt='UxDomainSocket()', setup='from __main__ import UxDomainSocket').timeit(number=1000)
С этим script я получаю примерно 3-кратную производительность и почти не накладные расходы с gevent.