<p>solicitações tem baixo desempenho transmitindo grandes respostas binárias</p>

Criado em 5 dez. 2014  ·  40Comentários  ·  Fonte: psf/requests

https://github.com/alex/http-client-bench contém os benchmarks que usei.

Os resultados são algo como:

| | solicitações / http | socket |
| --- | --- | --- |
| CPython | 12MB / s | 200 MB / s |
| PyPy | 80 MB / s | 300 MB / s |
| Go | 150 MB / s | n / a |

as solicitações impõem uma sobrecarga considerável em comparação com um soquete, particularmente no CPython.

Propose Close

Todos 40 comentários

Essa sobrecarga é inesperadamente grande. No entanto, evitá-lo pode ser complicado.

O grande problema é que fazemos muito processamento por bloco. Isso é todo o caminho para baixo na pilha: solicitações, urllib3 e httplib. Seria extremamente interessante ver onde o tempo está sendo gasto para descobrir quem está causando a ineficiência.

acho que a próxima etapa seria tentar criar o perfil de httplib / urllib3 para ver o
desempenho lá?

Kevin burke
telefone: 925.271.7005 | twentymilliseconds.com

Na quinta-feira, 4 de dezembro de 2014 às 17h01, Cory Benfield [email protected]
escreveu:

Essa sobrecarga é inesperadamente grande. No entanto, evitá-lo pode ser complicado.

O grande problema é que fazemos muito processamento por bloco. Isso é
todo o caminho para baixo na pilha: solicitações, urllib3 e httplib. Seria
extremamente interessante ver onde o tempo está sendo gasto para descobrir quem
está causando a ineficiência.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65732050
.

Acabei de executar benchmarks com urllib3:

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

E eu executei novamente as solicitações CPython +: 35 MB / s

(Minha máquina parece estar experimentando um pouco de ruído nos benchmarks, se alguém tiver um sistema mais silencioso que possa ligá-los, seria incrível)

Tentei executá-los na minha máquina depois de desligar todos os outros
janela do aplicativo e do terminal e também gerou bastante ruído - o
benchmark de soquete estava em qualquer lugar de 30 MB / s a ​​460 MB / s.

Kevin burke
telefone: 925.271.7005 | twentymilliseconds.com

Na quinta-feira, 4 de dezembro de 2014 às 21h24, Alex Gaynor [email protected]
escreveu:

Acabei de executar benchmarks com urllib3:

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

E eu executei novamente as solicitações CPython +: 35 MB / s

(Minha máquina parece estar experimentando um pouco de ruído nos benchmarks, se
qualquer um tem um sistema mais silencioso para poder usá-los, isso seria incrível)

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65748982
.

Tornei os benchmarks mais fáceis de executar agora, para que outras pessoas possam verificar meus 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 ... esses números são estranhos. Ohttplib de CPython é mais lento do que as solicitações ou urllib3, embora ambas as bibliotecas usem httplib? Isso simplesmente não pode estar certo.

Eles se reproduzem de forma consistente para mim - você pode experimentar os benchmarks e ver se
você pode reproduzir? Supondo que você possa, você vê algo de errado com o
benchmarks?

Na sexta-feira, 05 de dezembro de 2014 às 11:16:45 Cory Benfield [email protected]
escreveu:

Uh ... esses números são estranhos. Httplib do CPython é mais lento do que as solicitações ou
urllib3, mesmo que ambas as bibliotecas usem httplib? Isso simplesmente não pode estar certo.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65821989
.

Estou apenas pegando uma máquina silenciosa conhecida agora. Deve demorar alguns minutos para ficar disponível porque é uma caixa física que tem que ser instalada (deus eu amo o 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 que vale a pena:

Este patch , 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%

Você deve ser capaz de obter o mesmo efeito no Python2 com
env PYTHONUNBUFFERED= ou a bandeira -u .

Na sexta-feira, 5 de dezembro de 2014 às 11h42min36s, Corey Farwell [email protected]
escreveu:

Por que vale a pena:

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

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

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65826239
.

@alex Curiosamente, nem env PYTHONUNBUFFERED= nem -u tem o mesmo efeito no Python 2. Resultados da entrada da minha máquina.

Tudo bem, os dados abaixo vêm de uma máquina que não está fazendo nada além de executar esses testes. O último teste foi executado com o sinalizador Python -u definido e, como você pode ver, esse sinalizador não tem efeito.

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%

Esses números são extremamente estáveis ​​e mostram os seguintes recursos:

  1. Leituras brutas de soquete são rápidas (duh).
  2. Go tem cerca de 80% da velocidade de uma leitura de soquete bruto.
  3. urllib3 tem cerca de 20% da velocidade de uma leitura de soquete bruto.
  4. solicitações é um pouco mais lento do que urllib3, o que faz sentido, pois adicionamos alguns quadros de pilha para os dados passarem.
  5. httpplib é mais lento do que as solicitações / urllib3. Isso é simplesmente impossível, e eu suspeito que devemos configurar o httplib ou a biblioteca de sockets de uma maneira que o httplib não está.

FWIW, acabei de adicionar buffering=True de @kevinburke , faça suas corridas
incluir isso?

Na sexta-feira, 05 de dezembro de 2014 às 12h04h40, Cory Benfield [email protected]
escreveu:

Tudo bem, os dados abaixo vêm de uma máquina que não está fazendo mais nada
mas executando esses testes. O último teste foi executado com a sinalização -u do Python
definido, e como você pode ver, esse sinalizador não tem efeito.

Python 2.7.6
go versão go1.2.1 linux / amd64
TOMADA DE BANCADA:
8GiB 0:00:16 [500MiB / s] [====================================>] 100%
BENCH HTTPLIB:
8GiB 0:01:32 [88,6 MiB / s] [====================================>] 100%
BENCH URLLIB3:
8GiB 0:01:20 [101 MiB / s] [====================================>] 100%
PEDIDOS DE BANCO
8GiB 0:01:21 [100MiB / s] [====================================>] 100%
BENCH GO HTTP
8GiB 0:00:21 [385MiB / s] [====================================>] 100%

Python 2.7.6
go versão go1.2.1 linux / amd64
TOMADA DE BANCADA:
8GiB 0:00:16 [503MiB / s] [====================================>] 100%
BENCH HTTPLIB:
8GiB 0:01:33 [87,8 MiB / s] [====================================>] 100%
BENCH URLLIB3:
8GiB 0:01:20 [101 MiB / s] [====================================>] 100%
PEDIDOS DE BANCO
8GiB 0:01:22 [99,3 MiB / s] [====================================>] 100%
BENCH GO HTTP
8GiB 0:00:20 [391 MiB / s] [====================================>] 100%

Python 2.7.6
go versão go1.2.1 linux / amd64
TOMADA DE BANCADA:
8GiB 0:00:16 [506MiB / s] [====================================>] 100%
BENCH HTTPLIB:
8GiB 0:01:31 [89,1 MiB / s] [====================================>] 100%
BENCH URLLIB3:
8GiB 0:01:20 [101 MiB / s] [====================================>] 100%
PEDIDOS DE BANCO
8GiB 0:01:20 [101 MiB / s] [====================================>] 100%
BENCH GO HTTP
8GiB 0:00:21 [389MiB / s] [====================================>] 100%

Esses números são extremamente estáveis ​​e mostram os seguintes recursos:

  1. Leituras brutas de soquete são rápidas (duh).
  2. Go tem cerca de 80% da velocidade de uma leitura de soquete bruto.
  3. urllib3 tem cerca de 20% da velocidade de uma leitura de soquete bruto.
  4. solicitações é um pouco mais lento do que urllib3, o que faz sentido, pois
    adicione alguns frames de pilha para os dados passarem.
  5. httpplib é mais lento do que as solicitações / urllib3. Isso é simplesmente impossível,
    e eu suspeito que devemos configurar o httplib ou a biblioteca de sockets em
    uma maneira que o httplib não é.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65829335
.

Cory - veja a última versão do cliente de bancada que liga o
buffering = True in httplib (como as solicitações / urllib3 fazem)

Kevin burke
telefone: 925.271.7005 | twentymilliseconds.com

Na sexta-feira, 5 de dezembro de 2014 às 10h04, Cory Benfield [email protected]
escreveu:

Tudo bem, os dados abaixo vêm de uma máquina que não está fazendo mais nada
mas executando esses testes. O último teste foi executado com a sinalização -u do Python
definido, e como você pode ver, esse sinalizador não tem efeito.

Python 2.7.6
go versão go1.2.1 linux / amd64
TOMADA DE BANCADA:
8GiB 0:00:16 [500MiB / s] [====================================>] 100%
BENCH HTTPLIB:
8GiB 0:01:32 [88,6 MiB / s] [====================================>] 100%
BENCH URLLIB3:
8GiB 0:01:20 [101 MiB / s] [====================================>] 100%
PEDIDOS DE BANCO
8GiB 0:01:21 [100MiB / s] [====================================>] 100%
BENCH GO HTTP
8GiB 0:00:21 [385MiB / s] [====================================>] 100%

Python 2.7.6
go versão go1.2.1 linux / amd64
TOMADA DE BANCADA:
8GiB 0:00:16 [503MiB / s] [====================================>] 100%
BENCH HTTPLIB:
8GiB 0:01:33 [87,8 MiB / s] [====================================>] 100%
BENCH URLLIB3:
8GiB 0:01:20 [101 MiB / s] [====================================>] 100%
PEDIDOS DE BANCO
8GiB 0:01:22 [99,3 MiB / s] [====================================>] 100%
BENCH GO HTTP
8GiB 0:00:20 [391 MiB / s] [====================================>] 100%

Python 2.7.6
go versão go1.2.1 linux / amd64
TOMADA DE BANCADA:
8GiB 0:00:16 [506MiB / s] [====================================>] 100%
BENCH HTTPLIB:
8GiB 0:01:31 [89,1 MiB / s] [====================================>] 100%
BENCH URLLIB3:
8GiB 0:01:20 [101 MiB / s] [====================================>] 100%
PEDIDOS DE BANCO
8GiB 0:01:20 [101 MiB / s] [====================================>] 100%
BENCH GO HTTP
8GiB 0:00:21 [389MiB / s] [====================================>] 100%

Esses números são extremamente estáveis ​​e mostram os seguintes recursos:

  1. Leituras brutas de soquete são rápidas (duh).
  2. Go tem cerca de 80% da velocidade de uma leitura de soquete bruto.
  3. urllib3 tem cerca de 20% da velocidade de uma leitura de soquete bruto.
  4. solicitações é um pouco mais lento do que urllib3, o que faz sentido, pois
    adicione alguns frames de pilha para os dados passarem.
  5. httpplib é mais lento do que as solicitações / urllib3. Isso é simplesmente impossível,
    e eu suspeito que devemos configurar o httplib ou a biblioteca de sockets em
    uma maneira que o httplib não é.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65829335
.

Sim, isso corrige o comportamento de desempenho do httplib para fazer muito mais sentido.

Novos resultados e conclusões:

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. Leituras brutas de soquete são rápidas (duh).
  2. Go tem cerca de 80% da velocidade de uma leitura de soquete bruto.
  3. httpplib é um pouco menos de 25% da velocidade de uma leitura de soquete bruto.
  4. urllib3 tem cerca de 20% da velocidade de uma leitura de soquete bruto, adicionando uma pequena sobrecarga ao httplib.
  5. solicitações é um pouco mais lento do que urllib3, o que faz sentido, pois adicionamos alguns quadros de pilha para os dados passarem.

Portanto, indiscutivelmente o custo real aqui é httplib. Acelerar isso exige que o httplib saia do caminho.

Estou interessado em descobrir que parte do httplib está nos custando. Eu acho que o perfil de bench_httplib.py é um bom próximo passo.

Eu descartei a conversão do soquete em um objeto de arquivo por meio de socket.makefile adicionando essa linha ao teste bench_socket.py , que não diminui a velocidade. Estranhamente, parece torná-lo mais rápido.

A resposta é quase certamente a codificação de transferência: manipulação em partes.
Veja: https://github.com/alex/http-client-bench/pull/6 , mudando para
O comprimento do conteúdo no servidor produz alguns resultados inesperados.

Na sexta-feira, 05 de dezembro de 2014 às 12h24min53, Cory Benfield [email protected]
escreveu:

Portanto, indiscutivelmente o custo real aqui é httplib. Acelerar isso requer
tirando o httplib do caminho.

Estou interessado em descobrir que parte do httplib está nos custando. eu
acho que a criação de perfil bench_httplib.py é uma boa próxima etapa.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65831653
.

Interessante.

O manuseio em partes é quase com certeza o problema, e não estou realmente surpreso que go lide melhor com isso, especialmente porque em partes é o modo HTTP padrão para go.

No entanto, as solicitações sendo mais rápidas do que um soquete bruto é ... inesperado!

Uma coisa que vale a pena notar: se o soquete não estava decodificando a codificação fragmentada nos testes anteriores, ele obteve uma vantagem injusta, pois estava lendo menos dados do que os outros métodos! Todos eles liam os cabeçalhos em partes, bem como os 8 GB de dados.

Isso leva a uma pergunta que se segue: ainda achamos que todos esses métodos estão realmente lendo a mesma quantidade de dados?

Sim, a camada de soquete estava trapaceando, ela não decodificou os metadados fragmentados,
e tecnicamente leia um pouco menos. Estava lá como uma linha de base para "quão rápido
podemos ler ", para não provar nada.

Na sexta-feira, 05 de dezembro de 2014 às 12h33min10s, Cory Benfield [email protected]
escreveu:

Interessante.

O manuseio em pedaços é quase com certeza o problema, e eu realmente não
surpreso que go lida com isso melhor, especialmente porque chunked é o padrão
Modo HTTP para ir.

No entanto, as solicitações sendo mais rápidas do que um soquete bruto é ... inesperado!

Uma coisa que vale a pena notar: se o soquete não estava decodificando a codificação em partes
nos testes anteriores, ele obteve uma vantagem injusta, pois na verdade era
lendo menos dados do que os outros métodos! Todos eles estavam lendo o
cabeçalhos fragmentados, bem como 8 GB de dados.

Isso leva a uma pergunta que se segue: ainda achamos que todos esses métodos
estão realmente lendo a mesma quantidade de dados?

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65833299
.

Eu não ficaria surpreso se isso estiver relacionado ao tamanho do bloco que estamos lendo no soquete de cada vez.

Bolo para @alex por ser super útil: cake:

@nelhage fez alguns stracing dos vários exemplos (na transferência
codificação: caso fragmentado) https://gist.github.com/nelhage/dd6490fbc5cfb815f762
são os resultados. Parece que há um bug no httplib que resulta nele
nem sempre lendo um pedaço completo do soquete.

Na segunda-feira, 8 de dezembro de 2014 às 9h05min14s Kenneth Reitz [email protected]
escreveu:

Bolo para @alex https://github.com/alex por ser super útil [imagem:
:bolo:]

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/kennethreitz/requests/issues/2371#issuecomment -66147998
.

Então, o que temos aqui é um bug em uma biblioteca padrão que ninguém está realmente mantendo? ( @Lukasa tem pelo menos 2 conjuntos de patches que estão abertos há> 1 ano.)

Alguém (posso chegar lá, não está claro) provavelmente precisa fazer uma busca detalhada com PDB
ou algo assim e descobrir qual código exato está gerando aqueles 20 bytes
lê para que possamos montar um bom relatório de bug.

Em Seg, 08 de dezembro de 2014 às 9:14:09 Ian Cordasco [email protected]
escreveu:

Então, o que temos aqui é um bug em uma biblioteca padrão que ninguém está realmente
mantendo? ( @Lukasa https://github.com/Lukasa tem pelo menos 2 patch
conjuntos que estão abertos há> 1 ano.) Talvez eu fale mal de uma lista
em algum lugar esta noite

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/kennethreitz/requests/issues/2371#issuecomment -66149522
.

Vou tentar encaixar isso hoje à noite ou amanhã, se ninguém mais fizer isso.

Então, alguma notícia sobre a causa raiz? O que está gerando essas leituras curtas e quanto a situação melhora sem elas?

@kislyuk Não que eu saiba. Espero ter algum tempo para persegui-lo neste feriado de Natal.

Obrigado @Lukasa. Estou lidando com um problema de desempenho em que a velocidade de download em uma resposta fragmentada usando urllib3 / requests é muito mais lenta do que com curl e outras bibliotecas e estou tentando entender se esse é o culpado.

Eu estava mexendo um pouco com isso. As leituras curtas vêm da função _read_chunked em httplib

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

As leituras de 2 bytes parecem vir principalmente da linha 622.

Obtive um padrão de strace ligeiramente diferente do postado 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 \ 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 \ 0 \ 0 "..., 54, 0, NULL, NULL) = 54
recvfrom (3, "\ r \ n", 2, 0, NULL, NULL) = 2

Esse padrão pode ser explicado da seguinte forma:

  • o self.fp.readline (linha 591) aciona uma leitura em buffer para 8192 bytes (em socket.readline)
  • cada pedaço consumido tem 1.031 bytes (comprimento do pedaço de 5 bytes ("400 \ r \ n") + 1.024 bytes de dados + 2 bytes de terminador)
  • podemos consumir 7 desses pedaços dos 8192 bytes armazenados, o que nos deixa com 975 bytes
  • então lemos o próximo comprimento do bloco (5 bytes) que sai com 970 bytes
  • agora temos apenas 970 bytes, o que é insuficiente para preencher a parte atual (1024), portanto, voltamos à rede para verificar o déficit de 54 bytes
  • para conseguir isso, httplib faz um sock.read (54) nos bytes pendentes. socket.read neste caso (com um comprimento explícito) irá optar por ir para a rede pelos 54 bytes especificados (em vez de armazenar em buffer outro 8192)
  • nós então começamos a ler o terminador de chunk que tem 2 bytes e novamente esse é o mesmo cenário acima

O padrão então se repetirá (volte para a etapa 1)

FWIW, descobri que uma modesta (20% ou mais) aceleração poderia ser feita aqui rolando o terminador do bloco de 2 bytes lido no corpo do bloco lido, ou seja, em vez disso:

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

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

faça isso em vez disso:

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

Porém, provavelmente seria melhor se a leitura dos 54 bytes pudesse armazenar mais bytes do que 54 (ou seja, 8192 bytes), o que significaria que o soquete do buffer não estaria vazio quando se trata da leitura de 2 bytes.

Além disso. Não tenho certeza se as pequenas leituras são o principal fator na perda de taxa de transferência (ou não no localhost). Eu brinquei com o tamanho do buffer de soquete de forma que fosse um múltiplo de 1031 bytes e, apesar do strace não ter mais leituras pequenas, ele não teve muito impacto no rendimento.

Acho que a perda de taxa de transferência pode ter mais a ver com a forma como o socket.py lida com pequenas leituras. Aqui está o 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

Quando você passa um comprimento explícito para socket.read e pode ser preenchido a partir de dados existentes em buffer, este é o caminho do 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

O problema que percebo aqui é que mesmo uma leitura de 2 bytes significa copiar o restante não lido em um novo StringIO. Parece que ficará muito caro para muitas leituras pequenas. Se um determinado StringIO pudesse de alguma forma ser drenado em cada leitura, em vez do padrão atual de copiar o restante não lido em um novo StringIO, então espero que isso possa ajudar no rendimento

@gardenia Não tive a chance de absorver tudo isso, mas muito obrigado pelo seu esforço e trabalho aqui. @shazow talvez você ache a pesquisa de @gardenia interessante.

: +1: obrigado @gardenia. A propósito, minha própria pesquisa sobre desempenho em meu caso de uso descobriu que, no meu caso, as respostas não são fragmentadas, mas o urllib3 executa 20% mais rápido do que as solicitações, portanto, há alguma sobrecarga sendo introduzida que desejo caracterizar. Ainda de acordo com o título deste problema, mas a causa raiz diferente.

Fascinante, obrigado por compartilhar! :)

Parece ser um grande objetivo para o Hyper da @Lukasa abordar também.

@alex - Eu brinquei um pouco com o problema de desempenho não fragmentado urllib3 vs solicitações que você mencionou. Acho que vejo uma queda semelhante de 20% nas solicitações.

Em solicitações, tentei especulativamente substituir a chamada para self.raw.stream pela implementação embutida de stream () (de urllib3). Pareceu aproximar muito a taxa de transferência entre as solicitações e o urllib3, pelo menos na minha 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:

Talvez você possa tentar o mesmo em sua máquina para ver se faz diferença para você também.

(Observe que sim, eu sei que a chamada para is_fp_closed está acabando com o encapsulamento, não é um patch sério, apenas um ponto de dados)

@shazow É minha esperança que o BufferedSocket que o hyper usa deve resolver muito dessa ineficiência, essencialmente evitando pequenas leituras. Eu me pergunto se httplib em Py3 tem esse problema, porque usa io.BufferedReader extensivamente, o que deve fornecer aproximadamente o mesmo tipo de benefício que BufferedSocket .

Certamente, entretanto, quando hyper aumenta a funcionalidade HTTP / 1.1 suficiente para ser útil, devemos tentar compará-la ao lado dessas outras implementações e fazer esforços para tornar hyper mais rápido possível.

Inativo há quase um ano. Fechando.

Estou vendo problemas semelhantes, 10x menos taxa de transferência usando requests em comparação com urllib3 .

Acho que o problema reside na classe HTTPResponse do urllib3, quando ele é lido como um iterador, seu rendimento é muito ruim. Meu código está funcionando com um hack muito feio: eu retorno o objeto httplib.HTTPResponse sublinhado usado por urllib3 e isso parece corrigir meu problema de taxa de transferência.

Fato interessante: a superclasse HTTPResponse urllib3 é io.IOBase . A superclasse httplib.HTTPResponse Python3 é io.BufferedIOBase . Eu me pergunto se isso tem alguma coisa a ver com isso.

Esta página foi útil?
0 / 5 - 0 avaliações