<p>Anfragen hat eine schlechte Leistung beim Streamen großer binärer Antworten</p>

Erstellt am 5. Dez. 2014  ·  40Kommentare  ·  Quelle: psf/requests

https://github.com/alex/http-client-bench enthält die von mir verwendeten Benchmarks.

Die Ergebnisse sind etwa:

| | Anfragen/http | Steckdose |
| --- | --- | --- |
| CPython | 12 MB/s | 200 MB/s |
| PyPy | 80 MB/s | 300 MB/s |
| Los | 150 MB/s | n/a |

Requests verursacht einen erheblichen Overhead im Vergleich zu einem Socket, insbesondere bei CPython.

Propose Close

Alle 40 Kommentare

Dieser Overhead ist unerwartet groß. Es kann jedoch schwierig sein, dies zu vermeiden.

Das große Problem ist, dass wir ziemlich viel Verarbeitung pro Stück machen. Das ist ganz unten im Stack: Requests, urllib3 und httplib. Es wäre äußerst interessant zu sehen, wo die Zeit aufgewendet wird, um herauszufinden, wer die Ineffizienz verursacht.

Ich schätze, ein nächster Schritt wäre, zu versuchen, httplib / urllib3 zu profilieren, um das zu sehen
Leistung dort?

Kevin Burke
Telefon: 925.271.7005 | zwanzigmillisekunden.com

Am Donnerstag, 4. Dezember 2014 um 17:01 Uhr, Cory Benfield [email protected]
schrieb:

Dieser Overhead ist unerwartet groß. Es kann jedoch schwierig sein, dies zu vermeiden.

Das große Problem ist, dass wir ziemlich viel Verarbeitung pro Stück machen. Das ist
den ganzen Stack hinunter: Requests, urllib3 und httplib. Es wäre
sehr interessant zu sehen, wo die Zeit aufgewendet wird, um herauszufinden, wer
verursacht die Ineffizienz.


Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHub an
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65732050
.

Habe gerade Benchmarks mit urllib3 ausgeführt:

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

Und ich habe CPython + Requests erneut ausgeführt: 35 MB/s

(Meine Maschine scheint in Benchmarks ein bisschen Lärm zu machen, wenn jemand ein leiseres System hat, auf dem er diese verwenden kann, wäre das großartig)

Ich habe versucht, diese auf meinem Computer auszuführen, nachdem ich alle anderen heruntergefahren habe
Anwendungs- und Terminalfenster und bekam auch eine Menge Rauschen - die
Socket-Benchmark lag zwischen 30 MB/s und 460 MB/s.

Kevin Burke
Telefon: 925.271.7005 | zwanzigmillisekunden.com

Am Donnerstag, 4. Dezember 2014 um 21:24 Uhr, Alex Gaynor [email protected]
schrieb:

Habe gerade Benchmarks mit urllib3 ausgeführt:

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

Und ich habe CPython + Requests erneut ausgeführt: 35 MB/s

(Meine Maschine scheint in Benchmarks ziemlich laut zu sein, wenn
jemand ein leiseres System hat, auf dem er diese verwenden kann, das wäre großartig)


Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHub an
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65748982
.

Ich habe es jetzt einfacher gemacht, die Benchmarks auszuführen, damit andere Leute meine Zahlen hoffentlich überprüfen können:

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%

Äh... diese Zahlen sind seltsam. CPythons httplib ist langsamer als Anfragen oder urllib3, obwohl beide Bibliotheken httplib verwenden? Das kann einfach nicht stimmen.

Sie reproduzieren sich für mich konsistent - können Sie die Benchmarks ausprobieren und sehen, ob?
kannst du reproduzieren? Vorausgesetzt, Sie können, sehen Sie etwas falsch mit dem?
Benchmarks?

Am Freitag, den 05. Dezember 2014 um 11:16:45 Uhr Cory Benfield [email protected]
schrieb:

Äh... diese Zahlen sind seltsam. CPythons httplib ist langsamer als Anfragen oder
urllib3, obwohl beide Bibliotheken httplib verwenden? Das kann einfach nicht stimmen.


Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHub an
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65821989
.

Ich schnappe mir gerade eine bekannt leise Maschine. Es sollte ein paar Minuten dauern, bis es verfügbar ist, da es sich um eine physische Box handelt, die installiert werden muss (Gott, ich liebe 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%

Für was es wert ist:

Dieser 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%

Sie sollten in der Lage sein, den gleichen Effekt auf Python2 zu erzielen mit
env PYTHONUNBUFFERED= oder das -u Flag.

Am Freitag, den 05. Dezember 2014 um 11:42:36 Uhr Corey Farwell [email protected]
schrieb:

Für was es wert ist:

Dieser Patch https://gist.github.com/frewsxcv/1c0f3c81cda508e1bca9 , CPython
3.4.2:

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


Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHub an
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65826239
.

@alex Interessanterweise hat weder env PYTHONUNBUFFERED= noch -u den gleichen Effekt auf Python 2. Ergebnisse von meinem eingehenden Computer.

In Ordnung, die folgenden Daten stammen von einem Computer, der nichts anderes tut, als diese Tests auszuführen. Der letzte Test wurde mit gesetztem Python-Flag -u , und wie Sie sehen, hat dieses Flag keine Auswirkung.

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%

Diese Zahlen sind äußerst stabil und weisen die folgenden Merkmale auf:

  1. Raw Socket-Lesevorgänge sind schnell (duh).
  2. Go ist etwa 80 % der Geschwindigkeit eines Raw-Socket-Lesens.
  3. urllib3 ist etwa 20 % der Geschwindigkeit eines Raw-Socket-Lesevorgangs.
  4. request ist etwas langsamer als urllib3, was sinnvoll ist, da wir ein paar Stack-Frames hinzufügen, damit die Daten passieren.
  5. httplib ist langsamer als request/urllib3. Das ist einfach unmöglich, und ich vermute, dass wir httplib oder die Sockets-Bibliothek auf eine Weise konfigurieren müssen, die httplib nicht ist.

FWIW, ich habe gerade buffering=True von @kevinburke hinzugefügt , mach deine Läufe
das mit einbeziehen?

Am Freitag, den 05. Dezember 2014 um 12:04:40 Uhr Cory Benfield [email protected]
schrieb:

Okay, die folgenden Daten stammen von einer Maschine, die nichts anderes tut
aber diese Tests ausführen. Der letzte Test wurde mit dem Python -u Flag ausgeführt
gesetzt, und wie Sie sehen, hat dieses Flag keine Wirkung.

Python 2.7.6
go version go1.2.1 linux/amd64
BANKBUCHSE:
8GiB 0:00:16 [ 500MiB/s] [================================>] 100%
BENCH-HTTPLIB:
8GiB 0:01:32 [88,6 MiB/s] [================================>] 100%
BANK URLLIB3:
8GiB 0:01:20 [ 101MiB/s] [================================>] 100%
BANKANFRAGEN
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
BANKBUCHSE:
8GiB 0:00:16 [ 503MiB/s] [================================>] 100%
BENCH-HTTPLIB:
8GiB 0:01:33 [87,8MiB/s] [================================>] 100%
BANK URLLIB3:
8GiB 0:01:20 [ 101MiB/s] [================================>] 100%
BANKANFRAGEN
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
BANKBUCHSE:
8GiB 0:00:16 [ 506MiB/s] [================================>] 100%
BENCH-HTTPLIB:
8GiB 0:01:31 [89,1MiB/s] [================================>] 100%
BANK URLLIB3:
8GiB 0:01:20 [ 101MiB/s] [================================>] 100%
BANKANFRAGEN
8GiB 0:01:20 [ 101MiB/s] [================================>] 100%
BENCH GO HTTP
8GiB 0:00:21 [ 389MiB/s] [================================>] 100%

Diese Zahlen sind äußerst stabil und weisen die folgenden Merkmale auf:

  1. Raw Socket-Lesevorgänge sind schnell (duh).
  2. Go ist etwa 80 % der Geschwindigkeit eines Raw-Socket-Lesens.
  3. urllib3 ist etwa 20 % der Geschwindigkeit eines Raw-Socket-Lesevorgangs.
  4. Anfragen ist etwas langsamer als urllib3, was sinnvoll ist, da wir
    Fügen Sie ein paar Stapelrahmen hinzu, damit die Daten passieren können.
  5. httplib ist langsamer als request/urllib3. Das ist einfach unmöglich,
    und ich vermute, dass wir httplib oder die Sockets-Bibliothek in konfigurieren müssen
    eine Möglichkeit, die httplib nicht ist.


Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHub an
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65829335
.

Cory - Sehen Sie sich die neueste Version des Bench-Clients an, der die
buffering=True in httplib (wie bei Anfragen/urllib3)

Kevin Burke
Telefon: 925.271.7005 | zwanzigmillisekunden.com

Am Freitag, 5. Dezember 2014 um 10:04 Uhr, Cory Benfield [email protected]
schrieb:

Okay, die folgenden Daten stammen von einer Maschine, die nichts anderes tut
aber diese Tests ausführen. Der letzte Test wurde mit dem Python -u Flag ausgeführt
gesetzt, und wie Sie sehen, hat dieses Flag keine Wirkung.

Python 2.7.6
go version go1.2.1 linux/amd64
BANKBUCHSE:
8GiB 0:00:16 [ 500MiB/s] [================================>] 100%
BENCH-HTTPLIB:
8GiB 0:01:32 [88,6 MiB/s] [================================>] 100%
BANK URLLIB3:
8GiB 0:01:20 [ 101MiB/s] [================================>] 100%
BANKANFRAGEN
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
BANKBUCHSE:
8GiB 0:00:16 [ 503MiB/s] [================================>] 100%
BENCH-HTTPLIB:
8GiB 0:01:33 [87,8MiB/s] [================================>] 100%
BANK URLLIB3:
8GiB 0:01:20 [ 101MiB/s] [================================>] 100%
BANKANFRAGEN
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
BANKBUCHSE:
8GiB 0:00:16 [ 506MiB/s] [================================>] 100%
BENCH-HTTPLIB:
8GiB 0:01:31 [89,1MiB/s] [================================>] 100%
BANK URLLIB3:
8GiB 0:01:20 [ 101MiB/s] [================================>] 100%
BANKANFRAGEN
8GiB 0:01:20 [ 101MiB/s] [================================>] 100%
BENCH GO HTTP
8GiB 0:00:21 [ 389MiB/s] [================================>] 100%

Diese Zahlen sind äußerst stabil und weisen die folgenden Merkmale auf:

  1. Raw Socket-Lesevorgänge sind schnell (duh).
  2. Go ist etwa 80 % der Geschwindigkeit eines Raw-Socket-Lesens.
  3. urllib3 ist etwa 20 % der Geschwindigkeit eines Raw-Socket-Lesevorgangs.
  4. Anfragen ist etwas langsamer als urllib3, was sinnvoll ist, da wir
    Fügen Sie ein paar Stapelrahmen hinzu, damit die Daten passieren können.
  5. httplib ist langsamer als request/urllib3. Das ist einfach unmöglich,
    und ich vermute, dass wir httplib oder die Sockets-Bibliothek in konfigurieren müssen
    eine Möglichkeit, die httplib nicht ist.


Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHub an
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65829335
.

Ja, das behebt das Leistungsverhalten von httplib, um viel mehr Sinn zu machen.

Neue Ergebnisse und Schlussfolgerungen:

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. Raw Socket-Lesevorgänge sind schnell (duh).
  2. Go ist etwa 80 % der Geschwindigkeit eines Raw-Socket-Lesens.
  3. httplib ist knapp 25 % der Geschwindigkeit eines Raw-Socket-Lesevorgangs.
  4. urllib3 ist etwa 20 % der Geschwindigkeit eines Raw-Socket-Lesevorgangs, was httplib einen kleinen Overhead hinzufügt.
  5. request ist etwas langsamer als urllib3, was sinnvoll ist, da wir ein paar Stack-Frames hinzufügen, damit die Daten passieren.

Die wirklichen Kosten hier sind also wohl httplib. Um dies zu beschleunigen, muss httplib aus dem Weg geräumt werden.

Ich bin daran interessiert, herauszufinden, welcher Teil von httplib uns kostet. Ich denke, das Profiling von bench_httplib.py ist ein guter nächster Schritt.

Ich habe die Konvertierung des Sockets in ein Dateiobjekt durch socket.makefile indem ich diese Zeile zum bench_socket.py Test hinzugefügt habe, was ihn überhaupt nicht verlangsamt. Seltsamerweise scheint es schneller zu werden.

Die Antwort ist mit ziemlicher Sicherheit die Übertragungscodierung: Chunked-Handling.
Siehe: https://github.com/alex/http-client-bench/pull/6 , Wechsel zu
Content-Length auf dem Server führt zu unerwarteten Ergebnissen.

Am Freitag, den 05. Dezember 2014 um 12:24:53 Uhr Cory Benfield [email protected]
schrieb:

Die wirklichen Kosten hier sind also wohl httplib. Dies zu beschleunigen erfordert
httplib aus dem Weg räumen.

Ich bin daran interessiert, herauszufinden, welcher Teil von httplib uns kostet. ich
denke, dass Profiling bench_httplib.py ein guter nächster Schritt ist.


Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHub an
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65831653
.

Interessant.

Das Chunked-Handling ist mit ziemlicher Sicherheit das Problem, und ich bin nicht wirklich überrascht, dass go das besser handhabt, zumal chunked der Standard-HTTP-Modus für go ist.

Es ist jedoch unerwartet, dass Anfragen schneller sind als ein Raw-Socket!

Eine erwähnenswerte Sache: Wenn der Socket die Chunked-Codierung in den vorherigen Tests nicht dekodierte, hatte er einen unfairen Vorteil, da er tatsächlich weniger Daten las als die anderen Methoden! Sie alle lasen die aufgeteilten Header sowie die 8 GB Daten.

Dies führt zu einer Folgefrage: Glauben wir immer noch, dass alle diese Methoden tatsächlich die gleiche Datenmenge lesen?

Ja, der Socket-Layer hat geschummelt, er hat die aufgeteilten Metadaten nicht dekodiert.
und technisch etwas weniger lesen. Es war da als Basis für "wie schnell"
können wir lesen", um nichts zu beweisen.

Am Freitag, den 05. Dezember 2014 um 12:33:10 Uhr Cory Benfield [email protected]
schrieb:

Interessant.

Das stückige Handling ist mit ziemlicher Sicherheit das Problem, und ich bin es nicht wirklich
überrascht, dass go das besser handhabt, zumal chunked die Standardeinstellung ist
HTTP-Modus für unterwegs.

Es ist jedoch unerwartet, dass Anfragen schneller sind als ein Raw-Socket!

Eine erwähnenswerte Sache: Wenn der Socket die Chunk-Codierung nicht dekodierte
in den vorherigen Tests hat es dann einen unfairen Vorteil bekommen, wie es eigentlich war
weniger Daten lesen als die anderen Methoden! Sie haben alle gelesen
Chunked-Header sowie die 8 GB Daten.

Daraus ergibt sich eine Folgefrage: Denken wir alle diese Methoden noch?
lesen Sie tatsächlich die gleiche Datenmenge?


Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHub an
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65833299
.

Es würde mich nicht wundern, wenn dies mit der Chunk-Größe zusammenhängt, die wir jeweils aus dem Socket ablesen.

Kuchen für @alex, weil er super hilfsbereit ist :cake:

@nelhage hat die verschiedenen Beispiele (in der Übertragung
Kodierung: chunked case) https://gist.github.com/nelhage/dd6490fbc5cfb815f762
sind die Ergebnisse. Es sieht so aus, als ob es einen Fehler in httplib gibt, der dazu führt
nicht immer einen vollen Brocken aus der Steckdose lesen.

Am Mo 08. Dez. 2014 um 09:05:14 Kenneth Reitz [email protected]
schrieb:

Kuchen für @alex https://github.com/alex dafür, dass er super hilfsbereit ist [Bild:
:Kuchen:]


Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHub an
https://github.com/kennethreitz/requests/issues/2371#issuecomment -66147998
.

Was wir hier also haben, ist ein Fehler in einer Standardbibliothek, den niemand wirklich pflegt? ( @Lukasa hat mindestens 2 Patch-Sets, die seit >1 Jahr offen sind.) Vielleicht werde ich heute Abend irgendwo auf einer Liste stinken

Jemand (ich komme vielleicht dazu, unklar) muss wahrscheinlich mit pdb aufschlüsseln
oder so und finde heraus, welcher Code genau diese 20 Byte generiert
liest, damit wir einen guten Fehlerbericht zusammenstellen können.

Am Montag, 08. Dezember 2014 um 9:14:09 Uhr Ian Cordasco [email protected]
schrieb:

Was wir hier also haben, ist ein Fehler in einer Standardbibliothek, der niemand wirklich ist
Aufrechterhaltung? ( @Lukasa https://github.com/Lukasa hat mindestens 2 Patches
Sets, die seit >1 Jahr geöffnet sind.) Vielleicht mache ich einen Gestank auf eine Liste
irgendwo heute Nacht


Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHub an
https://github.com/kennethreitz/requests/issues/2371#issuecomment -66149522
.

Ich werde versuchen, das heute Abend oder morgen einzubauen, falls sonst niemand dazu kommt.

Gibt es Neuigkeiten zur Ursache? Was führt zu diesen kurzen Lesevorgängen und wie sehr verbessert sich die Situation ohne sie?

@kislyuk Soweit ich weiß nicht. Hoffentlich habe ich etwas Zeit, um es in den Weihnachtsferien zu jagen.

Danke @Lukasa. Ich habe es mit einem Leistungsproblem zu tun, bei dem die Download-Geschwindigkeit bei einer aufgeteilten Antwort mit urllib3/requests viel langsamer ist als bei curl und anderen Bibliotheken, und versuche zu verstehen, ob dies der Übeltäter ist.

Ich habe damit ein wenig herumgestöbert. Die kurzen Lesevorgänge stammen aus der Funktion _read_chunked in httplib

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

Die 2-Byte-Lesevorgänge scheinen hauptsächlich aus Zeile 622 zu stammen.

Ich habe ein etwas anderes Strace-Muster als das zuvor gepostete:
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

Dieses Muster lässt sich wie folgt erklären:

  • die self.fp.readline (Zeile 591) löst einen gepufferten Lesevorgang für 8192 Byte aus (in socket.readline)
  • jeder verbrauchte Chunk ist 1031 Byte (5 Byte Chunk-Länge ("400\r\n") + 1024 Byte Daten + 2 Byte Terminator)
  • Wir können 7 solcher Chunks von den gepufferten 8192 Bytes verbrauchen, was uns auf 975 Bytes zurücklässt
  • Wir lesen dann die nächste Chunk-Länge (5 Bytes), die mit 970 Bytes übrig bleibt
  • wir haben jetzt nur noch 970 Bytes, was nicht ausreicht, um den aktuellen Chunk (1024) zu erfüllen, also gehen wir für die fehlenden 54 Bytes zum Netzwerk zurück
  • Um dies zu erreichen, führt httplib ein sock.read(54) für die ausstehenden Bytes aus. socket.read wird in diesem Fall (mit einer expliziten Länge) entscheiden, für die angegebenen 54 Bytes zum Netzwerk zu gehen (anstatt weitere 8192 zu puffern)
  • Wir lesen dann den Chunk-Terminator, der 2 Byte groß ist, und das ist wieder das gleiche Szenario wie oben

Das Muster wird dann wiederholt (gehen Sie zurück zu Schritt 1)

FWIW, ich habe festgestellt, dass hier eine bescheidene (20% oder so) Beschleunigung erzielt werden könnte, indem der 2-Byte-Chunk-Terminator in den Chunk-Lesen gerollt wird, dh eher als dies:

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

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

mach das stattdessen:

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

Tatsächlich wäre es jedoch wahrscheinlich besser, wenn das Lesen für die 54 Bytes mehr Bytes als 54 (dh 8192 Bytes) puffern könnte, was bedeutet, dass der gepufferte Socket beim Lesen von 2 Byte nicht leer wäre.

Mehr dazu. Ich bin mir nicht sicher, ob die kleinen Lesevorgänge der Hauptfaktor für den Durchsatzverlust sind (oder nicht auf localhost). Ich spielte mit der Socket-Puffergröße herum, so dass sie ein Vielfaches von 1031 Bytes war und obwohl der strace keine kleinen Lesevorgänge mehr hatte, hatte dies keinen großen Einfluss auf den Durchsatz.

Ich denke, dass der Durchsatzverlust eher damit zu tun hat, wie socket.py mit kleinen Lesevorgängen umgeht. Hier ist der entsprechende Code (von socket.read):

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

Wenn Sie eine explizite Länge an socket.read übergeben und diese aus vorhandenen gepufferten Daten erfüllt werden kann, ist dies der Codepfad:

        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

Das Problem, das ich hier wahrnehme, ist, dass selbst ein 2-Byte-Lesen das Kopieren des ungelesenen Rests in ein neues StringIO bedeutet. Das sieht so aus, als ob es für viele kleine Lesevorgänge sehr teuer wird. Wenn ein gegebener StringIO bei jedem Lesevorgang irgendwie entwässert werden könnte, anstatt den aktuellen Muster des Kopierens des ungelesenen Rests in einen neuen StringIO, dann erwarte ich, dass dies dem Durchsatz helfen kann

@gardenia Ich hatte nicht die Gelegenheit, all das aufzunehmen, aber vielen Dank für Ihre Mühe und Arbeit hier. @shazow vielleicht finden Sie die Forschung von

:+1: danke @gardenia. Übrigens haben meine eigenen Recherchen zur Leistung in meinem Anwendungsfall ergeben, dass in meinem Fall die Antworten nicht unterteilt sind, aber urllib3 20+% schneller als Anfragen arbeitet, sodass ein gewisser Overhead eingeführt wird, den ich charakterisieren möchte. Immer noch im Einklang mit dem Titel dieser Ausgabe, aber andere Ursache.

Faszinierend, danke fürs Teilen! :)

Scheint auch ein großartiges Ziel für @Lukasas Hyper zu sein.

@alex - Ich habe ein wenig mit dem von Ihnen erwähnten Leistungsproblem urllib3 vs. Ich denke, ich sehe einen ähnlichen Rückgang der Anfragen um 20%.

In Anfragen habe ich spekulativ versucht, den Aufruf von self.raw.stream durch die Inline-Implementierung von stream() (von urllib3) zu ersetzen. Es schien den Durchsatz zwischen Anfragen und urllib3 viel näher zu bringen, zumindest auf meinem Computer:

--- 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:

Vielleicht können Sie dasselbe auf Ihrem Computer ausprobieren, um zu sehen, ob es auch für Sie einen Unterschied macht.

(Beachte ja, ich weiß, dass der Aufruf von is_fp_closed Encapsulation Busting ist, er ist nicht als ernsthafter Patch gedacht, sondern nur als Datenpunkt)

@shazow Ich hoffe, dass das BufferedSocket , das Hyper verwendet, einen Großteil dieser Ineffizienz beheben sollte, indem es im Wesentlichen kleine Lesevorgänge verhindert. Ich frage mich, ob httplib auf Py3 dieses Problem hat, da io.BufferedReader ausgiebig verwendet wird, was ungefähr die gleichen Vorteile wie BufferedSocket bieten sollte.

Wenn hyper genügend HTTP/1.1-Funktionalität entwickelt, um nützlich zu sein, sollten wir versuchen, es mit diesen anderen Implementierungen zu vergleichen und uns bemühen, hyper so schnell wie möglich zu machen.

Fast ein Jahr inaktiv. Schließen.

Ich sehe ähnliche Probleme, 10x weniger Durchsatz mit requests im Vergleich zu urllib3 .

Ich denke, das Problem liegt in der Klasse HTTPResponse urllib3. Wenn es als Iterator gelesen wird, ist der Durchsatz einfach wirklich schlecht. Ich habe meinen Code mit einem sehr hässlichen Hack zum Laufen gebracht: Ich gebe das unterstrichene httplib.HTTPResponse Objekt zurück, das von urllib3 verwendet wird, und das scheint mein Durchsatzproblem zu beheben.

Interessante Tatsache: Die Superklasse HTTPResponse urllib3 ist io.IOBase . Die Superklasse httplib.HTTPResponse Python3 ist io.BufferedIOBase . Ich frage mich, ob es damit zu tun hat.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen