<p>las solicitudes tienen un rendimiento deficiente en la transmisión de respuestas binarias grandes</p>

Creado en 5 dic. 2014  ·  40Comentarios  ·  Fuente: psf/requests

https://github.com/alex/http-client-bench contiene los puntos de referencia que utilicé.

Los resultados son algo como:

| | peticiones / http | enchufe |
| --- | --- | --- |
| CPython | 12 MB / s | 200 MB / s |
| PyPy | 80 MB / s | 300 MB / s |
| Ir | 150 MB / s | n / a |

Las solicitudes imponen una sobrecarga considerable en comparación con un socket, particularmente en CPython.

Propose Close

Todos 40 comentarios

Esa sobrecarga es inesperadamente grande. Sin embargo, evitarlo puede resultar complicado.

El gran problema es que hacemos bastante procesamiento por fragmento. Eso es todo el camino hacia abajo: solicitudes, urllib3 y httplib. Sería muy interesante ver dónde se está gastando el tiempo para averiguar quién está causando la ineficiencia.

Supongo que el siguiente paso sería intentar crear un perfil de httplib / urllib3 para ver el
actuación allí?

Kevin Burke
teléfono: 925.271.7005 | twentymilliseconds.com

El jueves 4 de diciembre de 2014 a las 5:01 p.m., Cory Benfield [email protected]
escribió:

Esa sobrecarga es inesperadamente grande. Sin embargo, evitarlo puede resultar complicado.

El gran problema es que hacemos bastante procesamiento por fragmento. Esa es
hasta el final de la pila: solicitudes, urllib3 y httplib. Podría ser
extremadamente interesante ver dónde se está gastando el tiempo para averiguar quién
está causando la ineficiencia.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65732050
.

Acabo de ejecutar puntos de referencia con urllib3:

PyPy: 120 MB / s
CPython: 70 MB / s

Y volví a ejecutar las solicitudes de CPython +: 35 MB / s

(Mi máquina parece estar experimentando un poco de ruido en los puntos de referencia, si alguien tiene un sistema más silencioso en el que pueda hacerlo, sería increíble)

Intenté ejecutarlos en mi máquina después de apagar todos los demás
aplicación y ventana de terminal y también obtuvo una buena cantidad de ruido: el
el punto de referencia de socket estuvo entre 30mb / sa 460mb / s.

Kevin Burke
teléfono: 925.271.7005 | twentymilliseconds.com

El jueves 4 de diciembre de 2014 a las 9:24 p.m., Alex Gaynor [email protected]
escribió:

Acabo de ejecutar puntos de referencia con urllib3:

PyPy: 120 MB / s
CPython: 70 MB / s

Y volví a ejecutar las solicitudes de CPython +: 35 MB / s

(Mi máquina parece estar experimentando un poco de ruido en los puntos de referencia, si
cualquiera tiene un sistema más silencioso en el que puedan hacerlo, eso sería increíble)

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65748982
.

Hice que los puntos de referencia sean más fáciles de ejecutar ahora, por lo que otras personas pueden verificar mis números:

CPython:

BENCH SOCKET:
   8GiB 0:00:22 [ 360MiB/s] [======================================================>] 100%
BENCH HTTPLIB:
   8GiB 0:02:34 [53.1MiB/s] [======================================================>] 100%
BENCH URLLIB3:
   8GiB 0:01:30 [90.2MiB/s] [======================================================>] 100%
BENCH REQUESTS
   8GiB 0:01:30 [90.7MiB/s] [======================================================>] 100%
BENCH GO HTTP
   8GiB 0:00:26 [ 305MiB/s] [======================================================>] 100%

PyPy:

BENCH SOCKET:
   8GiB 0:00:22 [ 357MiB/s] [======================================================>] 100%
BENCH HTTPLIB:
   8GiB 0:00:43 [ 189MiB/s] [======================================================>] 100%
BENCH URLLIB3:
   8GiB 0:01:07 [ 121MiB/s] [======================================================>] 100%
BENCH REQUESTS
   8GiB 0:01:09 [ 117MiB/s] [======================================================>] 100%
BENCH GO HTTP
   8GiB 0:00:26 [ 307MiB/s] [======================================================>] 100%

Uh ... esos números son raros. Http: //cpython es más lento que las solicitudes o urllib3, a pesar de que ambas bibliotecas usan Eso simplemente no puede ser correcto.

Se reproducen de forma coherente para mí. ¿Puedes probar los puntos de referencia y ver si
se puede reproducir? Suponiendo que pueda, ¿ve algo malo en el
puntos de referencia?

El viernes 05 de diciembre de 2014 a las 11:16:45 a. M. Cory Benfield [email protected]
escribió:

Uh ... esos números son raros. El httplib de CPython es más lento que las solicitudes o
urllib3, a pesar de que ambas bibliotecas usan httplib? Eso simplemente no puede ser correcto.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65821989
.

Ahora solo estoy agarrando una máquina silenciosa conocida. Debería tardar unos minutos en estar disponible porque es una caja física que tiene que instalarse (Dios, me encanta MAAS).

CPython 2.7.8

BENCH SOCKET:
   8GiB 0:00:26 [ 309MiB/s] [================================>] 100%
BENCH HTTPLIB:
   8GiB 0:02:24 [56.5MiB/s] [================================>] 100%
BENCH URLLIB3:
   8GiB 0:01:42 [79.7MiB/s] [================================>] 100%
BENCH REQUESTS
   8GiB 0:01:45 [77.9MiB/s] [================================>] 100%
BENCH GO HTTP
   8GiB 0:00:27 [ 297MiB/s] [================================>] 100%

Por lo que vale:

Este parche , CPython 3.4.2 :

BENCH SOCKET:
   8GiB 0:00:27 [ 302MiB/s] [================================>] 100%
BENCH HTTPLIB:
   8GiB 0:00:53 [ 151MiB/s] [================================>] 100%
BENCH URLLIB3:
   8GiB 0:00:54 [ 149MiB/s] [================================>] 100%
BENCH REQUESTS
   8GiB 0:00:56 [ 144MiB/s] [================================>] 100%
BENCH GO HTTP
   8GiB 0:00:31 [ 256MiB/s] [================================>] 100%

Debería poder obtener el mismo efecto en Python2 con
env PYTHONUNBUFFERED= o la bandera -u .

El viernes 05 de diciembre de 2014 a las 11:42:36 a. M. Corey Farwell [email protected]
escribió:

Por lo que vale:

Este parche https://gist.github.com/frewsxcv/1c0f3c81cda508e1bca9 , CPython
3.4.2:

ENCHUFE DE BANCO:
8GiB 0:00:27 [302MiB / s] [================================>] 100%
BANCO HTTPLIB:
8GiB 0:00:53 [151MiB / s] [================================>] 100%
BENCH URLLIB3:
8GiB 0:00:54 [149MiB / s] [================================>] 100%
SOLICITUDES DE BANCO
8GiB 0:00:56 [144MiB / s] [================================>] 100%
BENCH GO HTTP
8GiB 0:00:31 [256MiB / s] [================================>] 100%

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65826239
.

@alex Curiosamente, ni env PYTHONUNBUFFERED= o -u tienen el mismo efecto en Python 2. Resultados de mi máquina entrante.

Muy bien, los datos a continuación provienen de una máquina que no hace nada más que ejecutar estas pruebas. La última prueba se ejecutó con la bandera Python -u establecida y, como puede ver, esa bandera no tiene ningún efecto.

Python 2.7.6
go version go1.2.1 linux/amd64
BENCH SOCKET:
   8GiB 0:00:16 [ 500MiB/s] [================================>] 100%
BENCH HTTPLIB:
   8GiB 0:01:32 [88.6MiB/s] [================================>] 100%
BENCH URLLIB3:
   8GiB 0:01:20 [ 101MiB/s] [================================>] 100%
BENCH REQUESTS
   8GiB 0:01:21 [ 100MiB/s] [================================>] 100%
BENCH GO HTTP
   8GiB 0:00:21 [ 385MiB/s] [================================>] 100%
Python 2.7.6
go version go1.2.1 linux/amd64
BENCH SOCKET:
   8GiB 0:00:16 [ 503MiB/s] [================================>] 100%
BENCH HTTPLIB:
   8GiB 0:01:33 [87.8MiB/s] [================================>] 100%
BENCH URLLIB3:
   8GiB 0:01:20 [ 101MiB/s] [================================>] 100%
BENCH REQUESTS
   8GiB 0:01:22 [99.3MiB/s] [================================>] 100%
BENCH GO HTTP
   8GiB 0:00:20 [ 391MiB/s] [================================>] 100%
Python 2.7.6
go version go1.2.1 linux/amd64
BENCH SOCKET:
   8GiB 0:00:16 [ 506MiB/s] [================================>] 100%
BENCH HTTPLIB:
   8GiB 0:01:31 [89.1MiB/s] [================================>] 100%
BENCH URLLIB3:
   8GiB 0:01:20 [ 101MiB/s] [================================>] 100%
BENCH REQUESTS
   8GiB 0:01:20 [ 101MiB/s] [================================>] 100%
BENCH GO HTTP
   8GiB 0:00:21 [ 389MiB/s] [================================>] 100%

Estos números son extremadamente estables y muestran las siguientes características:

  1. Las lecturas de sockets sin procesar son rápidas (duh).
  2. Go es aproximadamente un 80% de la velocidad de lectura de un socket sin formato.
  3. urllib3 tiene aproximadamente un 20% de la velocidad de lectura de un socket sin formato.
  4. Las solicitudes son un poco más lentas que urllib3, lo que tiene sentido ya que agregamos un par de marcos de pila para que pasen los datos.
  5. httplib es más lento que las solicitudes / urllib3. Eso es simplemente imposible, y sospecho que debemos estar configurando http: // www.

FWIW, me acabo de fusionar agregando buffering=True de @kevinburke , haz tus carreras
incluir eso?

El viernes 05 de diciembre de 2014 a las 12:04:40 p.m. Cory Benfield [email protected]
escribió:

Muy bien, los datos a continuación provienen de una máquina que no hace nada más
pero ejecutando estas pruebas. La última prueba se ejecutó con la bandera -u de Python
establecido, y como puede ver esa bandera no tiene ningún efecto.

Python 2.7.6
go versión go1.2.1 linux / amd64
ENCHUFE DE BANCO:
8GiB 0:00:16 [500MiB / s] [================================>] 100%
BANCO HTTPLIB:
8GiB 0:01:32 [88,6MiB / s] [================================>] 100%
BENCH URLLIB3:
8GiB 0:01:20 [101MiB / s] [================================>] 100%
SOLICITUDES DE BANCO
8GiB 0:01:21 [100MiB / s] [================================>] 100%
BENCH GO HTTP
8GiB 0:00:21 [385MiB / s] [================================>] 100%

Python 2.7.6
go versión go1.2.1 linux / amd64
ENCHUFE DE BANCO:
8GiB 0:00:16 [503MiB / s] [================================>] 100%
BANCO HTTPLIB:
8GiB 0:01:33 [87.8MiB / s] [================================>] 100%
BENCH URLLIB3:
8GiB 0:01:20 [101MiB / s] [================================>] 100%
SOLICITUDES DE BANCO
8GiB 0:01:22 [99,3MiB / s] [================================>] 100%
BENCH GO HTTP
8GiB 0:00:20 [391MiB / s] [================================>] 100%

Python 2.7.6
go versión go1.2.1 linux / amd64
ENCHUFE DE BANCO:
8GiB 0:00:16 [506MiB / s] [================================>] 100%
BANCO HTTPLIB:
8GiB 0:01:31 [89,1MiB / s] [================================>] 100%
BENCH URLLIB3:
8GiB 0:01:20 [101MiB / s] [================================>] 100%
SOLICITUDES DE BANCO
8GiB 0:01:20 [101MiB / s] [================================>] 100%
BENCH GO HTTP
8GiB 0:00:21 [389MiB / s] [================================>] 100%

Estos números son extremadamente estables y muestran las siguientes características:

  1. Las lecturas de sockets sin procesar son rápidas (duh).
  2. Go es aproximadamente un 80% de la velocidad de lectura de un socket sin formato.
  3. urllib3 tiene aproximadamente un 20% de la velocidad de lectura de un socket sin formato.
  4. solicitudes es un poco más lento que urllib3, lo que tiene sentido ya que
    agregue un par de marcos de pila para que pasen los datos.
  5. httplib es más lento que las solicitudes / urllib3. Eso es simplemente imposible
    y sospecho que debemos estar configurando httplib o la biblioteca de sockets en
    una forma en la que httplib no lo es.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65829335
.

Cory: vea la última versión del cliente de banco que enciende el
buffering = True en httplib (como hacen las solicitudes / urllib3)

Kevin Burke
teléfono: 925.271.7005 | twentymilliseconds.com

El viernes 5 de diciembre de 2014 a las 10:04 a. M., Cory Benfield [email protected]
escribió:

Muy bien, los datos a continuación provienen de una máquina que no hace nada más
pero ejecutando estas pruebas. La última prueba se ejecutó con la bandera -u de Python
establecido, y como puede ver esa bandera no tiene ningún efecto.

Python 2.7.6
go versión go1.2.1 linux / amd64
ENCHUFE DE BANCO:
8GiB 0:00:16 [500MiB / s] [================================>] 100%
BANCO HTTPLIB:
8GiB 0:01:32 [88,6MiB / s] [================================>] 100%
BENCH URLLIB3:
8GiB 0:01:20 [101MiB / s] [================================>] 100%
SOLICITUDES DE BANCO
8GiB 0:01:21 [100MiB / s] [================================>] 100%
BENCH GO HTTP
8GiB 0:00:21 [385MiB / s] [================================>] 100%

Python 2.7.6
go versión go1.2.1 linux / amd64
ENCHUFE DE BANCO:
8GiB 0:00:16 [503MiB / s] [================================>] 100%
BANCO HTTPLIB:
8GiB 0:01:33 [87.8MiB / s] [================================>] 100%
BENCH URLLIB3:
8GiB 0:01:20 [101MiB / s] [================================>] 100%
SOLICITUDES DE BANCO
8GiB 0:01:22 [99,3MiB / s] [================================>] 100%
BENCH GO HTTP
8GiB 0:00:20 [391MiB / s] [================================>] 100%

Python 2.7.6
go versión go1.2.1 linux / amd64
ENCHUFE DE BANCO:
8GiB 0:00:16 [506MiB / s] [================================>] 100%
BANCO HTTPLIB:
8GiB 0:01:31 [89,1MiB / s] [================================>] 100%
BENCH URLLIB3:
8GiB 0:01:20 [101MiB / s] [================================>] 100%
SOLICITUDES DE BANCO
8GiB 0:01:20 [101MiB / s] [================================>] 100%
BENCH GO HTTP
8GiB 0:00:21 [389MiB / s] [================================>] 100%

Estos números son extremadamente estables y muestran las siguientes características:

  1. Las lecturas de sockets sin procesar son rápidas (duh).
  2. Go es aproximadamente un 80% de la velocidad de lectura de un socket sin formato.
  3. urllib3 tiene aproximadamente un 20% de la velocidad de lectura de un socket sin formato.
  4. solicitudes es un poco más lento que urllib3, lo que tiene sentido ya que
    agregue un par de marcos de pila para que pasen los datos.
  5. httplib es más lento que las solicitudes / urllib3. Eso es simplemente imposible
    y sospecho que debemos estar configurando httplib o la biblioteca de sockets en
    una forma en la que httplib no lo es.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65829335
.

Sí, eso corrige el comportamiento de rendimiento de httplib para que tenga mucho más sentido.

Nuevos resultados y conclusiones:

Python 2.7.6
go version go1.2.1 linux/amd64
BENCH SOCKET:
   8GiB 0:00:16 [ 499MiB/s] [================================>] 100%
BENCH HTTPLIB:
   8GiB 0:01:12 [ 113MiB/s] [================================>] 100%
BENCH URLLIB3:
   8GiB 0:01:21 [ 100MiB/s] [================================>] 100%
BENCH REQUESTS
   8GiB 0:01:20 [ 101MiB/s] [================================>] 100%
BENCH GO HTTP
   8GiB 0:00:20 [ 391MiB/s] [================================>] 100%
  1. Las lecturas de sockets sin procesar son rápidas (duh).
  2. Go es aproximadamente un 80% de la velocidad de lectura de un socket sin formato.
  3. httplib es un poco menos del 25% de la velocidad de lectura de un socket sin formato.
  4. urllib3 tiene aproximadamente un 20% de la velocidad de una lectura de socket sin formato, lo que agrega un poco de sobrecarga a httplib.
  5. Las solicitudes son un poco más lentas que urllib3, lo que tiene sentido ya que agregamos un par de marcos de pila para que pasen los datos.

Entonces, podría decirse que el costo real aquí es httplib. Para acelerar esto, es necesario quitar httplib.

Sin embargo, estoy interesado en averiguar qué parte de httplib nos está costando. Creo que perfilar bench_httplib.py es un buen siguiente paso.

Descarté la conversión del socket a un objeto de archivo a través de socket.makefile agregando esa línea a la prueba bench_socket.py , eso no la ralentiza en absoluto. Extrañamente, parece hacerlo más rápido.

Es casi seguro que la respuesta sea la codificación de transferencia: manejo fragmentado.
Ver: https://github.com/alex/http-client-bench/pull/6 , cambiando a
Content-Length en el servidor produce algunos resultados inesperados.

El viernes 05 de diciembre de 2014 a las 12:24:53 p.m. Cory Benfield [email protected]
escribió:

Entonces, podría decirse que el costo real aquí es httplib. Acelerar esto requiere
conseguir que htplib no estorbe.

Sin embargo, estoy interesado en averiguar qué parte de httplib nos está costando. I
cree que la creación de perfiles bench_httplib.py es un buen paso siguiente.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65831653
.

Interesante.

El manejo fragmentado es casi con certeza el problema, y ​​no me sorprende mucho que go lo maneje mejor, especialmente porque fragmentado es el modo HTTP predeterminado para ir.

Sin embargo, las solicitudes son más rápidas que un socket sin formato es ... ¡inesperado!

Una cosa que vale la pena señalar: si el socket no estaba decodificando la codificación fragmentada en las pruebas anteriores, entonces obtuvo una ventaja injusta, ya que en realidad estaba leyendo menos datos que los otros métodos. Todos estaban leyendo los encabezados fragmentados, así como los 8 GB de datos.

Esto lleva a una pregunta de seguimiento: ¿todavía creemos que todos estos métodos están leyendo la misma cantidad de datos?

Sí, la capa de socket estaba haciendo trampa, no decodificó los metadatos fragmentados,
y técnicamente leer un poco menos. Estaba allí como una línea de base para "qué tan rápido
podemos leer ", para no probar nada.

El viernes 05 de diciembre de 2014 a las 12:33:10 p.m. Cory Benfield [email protected]
escribió:

Interesante.

El manejo fragmentado es casi con certeza el problema, y ​​realmente no estoy
sorprendido de que go lo maneje mejor, especialmente porque fragmentado es el predeterminado
Modo HTTP para ir.

Sin embargo, las solicitudes son más rápidas que un socket sin formato es ... ¡inesperado!

Una cosa que vale la pena señalar: si el socket no estaba decodificando la codificación fragmentada
en las pruebas anteriores, obtuvo una ventaja injusta, ya que en realidad
leer menos datos que los otros métodos! Todos estaban leyendo el
encabezados fragmentados, así como los 8 GB de datos.

Esto lleva a una pregunta de seguimiento: ¿todavía creemos que todos estos métodos
están leyendo la misma cantidad de datos?

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65833299
.

No me sorprendería que esto esté relacionado con el tamaño del fragmento que leemos del zócalo a la vez.

Pastel para @alex por ser de gran ayuda: pastel:

@nelhage hizo algunos stracing de varios ejemplos (en la transferencia
codificación: caso fragmentado) https://gist.github.com/nelhage/dd6490fbc5cfb815f762
son los resultados. Parece que hay un error en httplib que da como resultado
no siempre leyendo un fragmento completo del zócalo.

El lunes 08 de diciembre de 2014 a las 9:05:14 a. M. Kenneth Reitz [email protected]
escribió:

Pastel para @alex https://github.com/alex por ser muy útil [imagen:
:pastel:]

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/kennethreitz/requests/issues/2371#issuecomment -66147998
.

Entonces, ¿lo que tenemos aquí es un error en una biblioteca estándar que nadie está manteniendo realmente? ( @Lukasa tiene al menos 2 conjuntos de parches que han estado abiertos durante> 1 año). Tal vez levante un escándalo en una lista en algún lugar esta noche

Alguien (podría llegar a eso, no está claro) probablemente necesite profundizar con pdb
o algo y averigüe qué código exacto está generando esos 20 bytes
lee para que podamos armar un buen informe de errores.

El lunes 08 de diciembre de 2014 a las 9:14:09 a.m. Ian Cordasco [email protected]
escribió:

Entonces, lo que tenemos aquí es un error en una biblioteca estándar que nadie está realmente
manteniendo? ( @Lukasa https://github.com/Lukasa tiene al menos 2 parches
conjuntos que han estado abiertos durante> 1 año).
en algún lugar esta noche

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/kennethreitz/requests/issues/2371#issuecomment -66149522
.

Intentaré encajar eso esta noche o mañana si nadie más lo hace.

Entonces, ¿alguna noticia sobre la causa raíz? ¿Qué genera estas lecturas breves y cuánto mejora la situación sin ellas?

@kislyuk No que yo sepa. Con suerte, tendré algo de tiempo para perseguirlo en estas vacaciones de Navidad.

Gracias @Lukasa. Estoy lidiando con un problema de rendimiento en el que la velocidad de descarga en una respuesta fragmentada usando urllib3 / request es mucho más lenta que con curl y otras bibliotecas, y trato de comprender si este es el culpable.

Estaba hurgando un poco con esto. Las lecturas cortas provienen de la función _read_chunked en httplib

https://fossies.org/linux/misc/Python-2.7.9.tgz/Python-2.7.9/Lib/httplib.py#l_585

Las lecturas de 2 bytes parecen ser principalmente de la línea 622.

Obtuve un patrón de strace ligeramente diferente al publicado anteriormente:
recvfrom (3, "400 \ r \ n \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 "..., 8192, 0, NULL, NULL) = 8192
recvfrom (3, "\ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 "..., 54, 0, NULL, NULL) = 54
recvfrom (3, "\ r \ n", 2, 0, NULL, NULL) = 2

Este patrón se puede explicar de la siguiente manera:

  • self.fp.readline (línea 591) activa una lectura en búfer de 8192 bytes (en socket.readline)
  • cada fragmento que se consume es 1031 bytes (5 bytes de longitud del fragmento ("400 \ r \ n") + 1024 bytes de datos + 2 bytes de terminador)
  • podemos consumir 7 de esos fragmentos de los 8192 bytes almacenados en búfer, lo que nos deja con 975 bytes
  • luego leemos la siguiente longitud de fragmento (5 bytes) que sale con 970 bytes
  • ahora solo tenemos 970 bytes, lo cual es insuficiente para cumplir con el fragmento actual (1024), por lo que volvemos a la red para el déficit de 54 bytes
  • para lograr esto httplib hace un sock.read (54) en los bytes pendientes. socket.read en este caso (con una longitud explícita) optará por ir a la red por los 54 bytes especificados (en lugar de almacenar en búfer otros 8192)
  • luego llegamos a leer el terminador de fragmentos que es de 2 bytes y nuevamente ese es el mismo escenario que el anterior

A continuación, el patrón se repetirá (vuelva al paso 1).

FWIW, encontré que se podría hacer una modesta (20% más o menos) aceleración aquí enrollando el terminador de fragmentos de 2 bytes leído en el cuerpo del fragmento leído, es decir, en lugar de esto:

            value.append(self._safe_read(chunk_left)) 
            amt -= chunk_left

        self._safe_read(2)  # toss the CRLF at the end of the chunk

haz esto en su lugar:

            value.append(self._safe_read(chunk_left + 2)[:-2]) 
            amt -= chunk_left

Realmente, sin embargo, probablemente sería mejor si la lectura de los 54 bytes pudiera almacenar en búfer más bytes que 54 (es decir, 8192 bytes), lo que significaría que el socket almacenado en búfer no estaría vacío cuando se trata de la lectura de 2 bytes.

Además de esto. No estoy seguro de que las lecturas pequeñas sean el factor principal en la pérdida de rendimiento (o no en localhost). Jugué con el tamaño del búfer del socket de modo que fuera un múltiplo de 1031 bytes y, a pesar de que el strace ya no tenía lecturas pequeñas, no tuvo mucho impacto en el rendimiento.

Creo que la pérdida de rendimiento puede tener más que ver con la forma en que socket.py maneja las lecturas pequeñas. Aquí está el código relevante (de socket.read):

https://fossies.org/linux/misc/Python-2.7.9.tgz/Python-2.7.9/Lib/socket.py#l_336

Cuando pasa una longitud explícita a socket.read y se puede completar a partir de los datos almacenados en búfer existentes, esta es la ruta del código:

        buf = self._rbuf
        buf.seek(0, 2)  # seek end

        #.....

        # Read until size bytes or EOF seen, whichever comes first
        buf_len = buf.tell()
        if buf_len >= size:
            # Already have size bytes in our buffer?  Extract and return.
            buf.seek(0)
            rv = buf.read(size)
            self._rbuf = StringIO()
            self._rbuf.write(buf.read())
            return rv

El problema que percibo aquí es que incluso una lectura de 2 bytes significa copiar el resto no leído en un StringIO nuevo. Parece que será muy caro para muchas lecturas pequeñas. Si un StringIO dado podría de alguna manera drenarse en cada lectura en lugar del patrón actual de copiar el resto no leído en un StringIO nuevo, entonces espero que eso ayude al rendimiento

@gardenia No he tenido la oportunidad de asimilar todo esto, pero muchas gracias por su esfuerzo y trabajo aquí. @shazow quizás encuentre interesante la investigación de

: +1: gracias @gardenia. Por cierto, mi propia investigación sobre el rendimiento en mi caso de uso descubrió que, en mi caso, las respuestas no están fragmentadas, pero urllib3 funciona un 20% más rápido que las solicitudes, por lo que se están introduciendo algunos gastos generales que quiero caracterizar. Todavía en línea con el título de este problema, pero con una causa raíz diferente.

Fascinante, ¡gracias por compartir! :)

También parece un gran objetivo para el Hyper de

@alex :

En las solicitudes, intenté especulativamente reemplazar la llamada a self.raw.stream con la implementación en línea de stream () (de urllib3). Parecía acercar el rendimiento mucho más entre las solicitudes y urllib3, al menos en mi máquina:

--- requests.repo/requests/models.py    2015-03-06 16:05:52.072509869 +0000
+++ requests/models.py  2015-03-07 20:49:25.618007438 +0000
@@ -19,6 +19,7 @@
 from .packages.urllib3.fields import RequestField
 from .packages.urllib3.filepost import encode_multipart_formdata
 from .packages.urllib3.util import parse_url
+from .packages.urllib3.util.response import is_fp_closed
 from .packages.urllib3.exceptions import (
     DecodeError, ReadTimeoutError, ProtocolError, LocationParseError)
 from .exceptions import (
@@ -652,8 +654,12 @@
             try:
                 # Special case for urllib3.
                 try:
-                    for chunk in self.raw.stream(chunk_size, decode_content=True):
-                        yield chunk
+                    while not is_fp_closed(self.raw._fp):
+                        data = self.read(amt=chunk_size, decode_content=True)
+
+                        if data:
+                            yield data
+
                 except ProtocolError as e:
                     raise ChunkedEncodingError(e)
                 except DecodeError as e:

Tal vez podría intentar lo mismo en su máquina para ver si también hace una diferencia para usted.

(Tenga en cuenta que sí, sé que la llamada a is_fp_closed es una ruptura de encapsulación, no está destinada a ser un parche serio, solo un punto de datos)

@shazow Tengo la esperanza de que los BufferedSocket que hiper usa aborden gran parte de esa ineficiencia, esencialmente evitando lecturas pequeñas. Me pregunto si httplib en Py3 tiene este problema, porque usa io.BufferedReader extensamente, lo que debería proporcionar aproximadamente el mismo tipo de beneficio que BufferedSocket .

Ciertamente, sin embargo, cuando hyper crece la suficiente funcionalidad HTTP / 1.1 para ser útil, deberíamos intentar compararlo con estas otras implementaciones y hacer esfuerzos para hacer que hyper más rápido posible.

Inactivo durante casi un año. Clausura.

Veo problemas similares, 10 veces menos rendimiento usando requests en comparación con urllib3 .

Creo que el problema vive dentro de la clase HTTPResponse urllib3, cuando se lee como un iterador, su rendimiento es realmente malo. Obtuve mi código funcionando con un truco muy feo: devuelvo el objeto httplib.HTTPResponse subrayado utilizado por urllib3 y eso parece solucionar mi problema de rendimiento.

Dato interesante: la superclase HTTPResponse urllib3 es io.IOBase . La superclase httplib.HTTPResponse Python3 es io.BufferedIOBase . Me pregunto si tiene algo que ver con esto.

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

brainwane picture brainwane  ·  3Comentarios

remram44 picture remram44  ·  4Comentarios

avinassh picture avinassh  ·  4Comentarios

eromoe picture eromoe  ·  3Comentarios

jake491 picture jake491  ·  3Comentarios