Perl zu langsam gleichzeitiger Download mit HTTP :: Async & Net :: Async :: HTTP

8

Ich versuche, ungefähr 7 Dutzende von URLs parallel zu Skripten zu erhalten: der erste ist unten, mit HTTP :: Async, und der zweite ist auf Pastebin mit Net :: Async :: HTTP. Das Problem ist, dass ich ziemlich gleiche Timing-Ergebnisse bekomme - ungefähr 8,14 Sekunden für alle URLs. Es ist inakzeptabel langsam im Vergleich zu Curl + Xargs begann von Shell, die alle in weniger als 3 Sekunden mit 10-20 "Threads" bekommt. Zum Beispiel zeigt Devel :: Timer im ersten Skript, dass die maximale Warteschlangenlänge sogar kleiner als 6 ist ( $queue->in_progress_count & lt; = 5, $queue->to_send_count = 0 immer). Also, es sieht aus wie foreach mit $ queue- & gt; add ist zu langsam ausgeführt, und ich weiß nicht warum. Ziemlich gleiche Situation habe ich mit Net :: Async :: HTTP (zweites Skript auf Pastebin), die sogar langsamer als die erste ist.

Also, bitte, weiß jemand, was ich falsch mache? Wie kann ich eine gleichlaufende Download-Geschwindigkeit erhalten, verglichen mit curl + xargs, die von Shell gestartet wurden?

%Vor%     
berrymorr 26.03.2017, 03:37
quelle

4 Antworten

6

Meine besten Ergebnisse mit HTTP :: Async sind weit über 4 und bis zu mehr als 5 Sekunden. Wie ich verstehe, ist dieser Ansatz nicht erforderlich, und hier ist ein einfaches Forking-Beispiel, das ein wenig über 2 und höchstens unter 3 Sekunden dauert.

Es verwendet Parallel :: ForkManager und LWP::UserAgent für Downloads.

%Vor%

Die Dateien werden mit Path :: Tiny geschrieben. Sein path erstellt ein Objekt und spew Routinen schreiben die Datei.

Als Referenz benötigen die sequentiellen Downloads ungefähr 26 Sekunden.

Wenn die maximale Anzahl an Prozessen auf 30 eingestellt ist, dauert dies mehr als 4 Sekunden, und mit 60 ist es etwas über 2 Sekunden, ungefähr so ​​wie mit (bis zu) 90. In diesem Test gibt es 70 URLs.

Getestet an einem 4-Core-Laptop mit einer ordentlichen Netzwerkverbindung. (Hier ist die CPU nicht so wichtig.) Die Tests wurden wiederholt ausgeführt, mehrmals und an mehreren Tagen.

Ein Vergleich mit dem Ansatz aus der Frage

Die besten HTTP::Async Ergebnisse sind um den Faktor zwei langsamer als die obigen. Sie sind mit 30-40 "Slots" da für höhere Zahlen die Zeit steigt, was Rätsel (mich). Das Modul verwendet select zum Multiplexen über Net :: HTTP: : NB (eine nicht blockierende Version von Net :: HTTP ). Während select "nicht gut skaliert", betrachtet dies Hunderte von Sockets und ich würde erwarten, mehr als 40 für dieses Netzwerk-gebundene Problem verwenden zu können. Der einfache gegabelte Ansatz tut dies.

Außerdem wird select als eine langsame Methode zur Überwachung von Sockets betrachtet, während die Forks dies nicht einmal benötigen, da jeder Prozess seine eigene URL hat. (Dies kann den Overhead des Moduls mit vielen Verbindungen zur Folge haben.) Der inhärente Overhead von Fork ist durch den Netzwerkzugriff begrenzt und in den Schatten gestellt. Wenn wir nach (vielen) Hunderten von Downloads sind, kann das System durch Prozesse belastet werden, aber select würde auch nicht gut laufen.

Schließlich laden select basierte Methoden strikt eine Datei nach der anderen herunter, und der Effekt wird durch Drucken angezeigt, da die Anfragen add ed sind - wir können die Verzögerung sehen . Die gegabelten Downloads gehen parallel (in diesem Fall alle 70 gleichzeitig ohne Probleme). Dann wird es einen Netzwerk- oder Disketten-Engpass geben, aber das ist winzig im Vergleich zum Gewinn.

Update : Ich schob dies, um die Anzahl der Sites und Prozesse zu verdoppeln, sah keine Anzeichen von OS / CPU-Belastung und behielt die durchschnittliche Geschwindigkeit bei.

Also würde ich sagen, wenn Sie jede zweite Rasur abrasieren müssen. Aber wenn das nicht kritisch ist und es andere Vorteile von HTTP::Async (oder so) gibt, dann sei zufrieden mit (nur ein bisschen) längeren Downloads.

Der HTTP::Async Code, der gute Ergebnisse liefert, war einfach

%Vor%

Ich habe auch versucht, Header und Timings zu optimieren. (Dies beinhaltete das Ablegen von keep-alive wie vorgeschlagen, von $request->header(Connection => 'close') , ohne Wirkung.)

    
zdim 31.03.2017, 09:36
quelle
4

Um meinen Kommentar zu erklären. Ich war neugierig, weil ich das Net::Async::HTTP noch nie benutzt habe, wollte dein Skript lokal ausprobieren. Also, erstellt diese minimalistische Plack app.psgi :

%Vor%

Der Server versteht URLs in der Form GET /sleep_time/reqID , wo

  • die Ruhezeit ist in Mikrosekunden für die usleep - und der Server schläft die angegebene Zeit bevor reagiert. Z.B. es täuscht etwas "Bearbeitungszeit" vor.
  • id - eine beliebige Nummer zur Identifizierung von ...

z. Wenn GET /1000000/1 angefordert wird, wird der Server 1 Sekunde vor dem Antworten schlafen. Als Antwort ist die PID des antwortenden Prozesses enthalten.

Führen Sie in einem Terminalfenster den obigen Befehl mit Starman preforkimg server mit dem Standard 20 workers aus.

%Vor%

Und in dem anderen Fenster die Ergebnisse mit xargs :

%Vor%

also, 20 Anfragen senden, wobei jede Antwort 1s Bearbeitungszeit ist.

%Vor%

Also, 20 Anfragen = 4 Sekunden. Es ist sichtbar, dass die entsprechenden PID unterschiedlich sind - z. B. wird die Antwort von einem anderen Arbeiter gesendet.

Verwenden Sie nun Ihr Skript async.pl (leicht verkürzt / geändert):

%Vor%

Befehl

%Vor%

Ergebnis

%Vor%

Gleich 20 Anfragen = 20 Sekunden, und jede Anfrage wird von demselben PID bedient. Wie die reine sequenzielle Verarbeitung. : (

Dies wahrscheinlich , weil die Anforderungen die Verbindung wiederverwenden (z. B. Keep-Alive).

Endlich - leider, wie ich sagte - habe ich keine Erfahrung mit dem Modul, also habe keine Ahnung, wie man das Modul dazu zwingt, die geöffnete Verbindung nicht wiederzuverwenden.

    
jm666 28.03.2017 06:14
quelle
4

So, endlich funktioniert Probe ( vollständiges Skript ). Es verwendet Furl und fork_call von AnyEvent::Util . Dieses Beispiel kehrt in ~ 3 Sekunden zurück, was gut genug ist. Wenn Sie eine einfache HTTP-Authentifizierung benötigen, verwenden Sie einfach URI mit folgenden Werten: username:[email protected]/path?param1=val1&param2=val2 . Sie fügen besser use EV; hinzu, bevor Sie AnyEvent verwenden, weil EV am schnellsten ist.

%Vor%     
berrymorr 02.04.2017 13:36
quelle
2

Async ist langsamer als das parallele Herunterladen: Der asynchrone Code wird nur während einer Antwort auf andere Anrufe übertragen, aber das Herunterladen geschieht sequenziell in einem einzigen Vorgang, während die curl + xargs 100% (gut, fast 100%) funktionieren. und solange Sie die Kerne nicht sättigen) parallel, wie bei der Verwendung von Gabelarbeitern.

Bitte googeln Sie nach "Nebenläufigkeit ist keine Parallelität"

    
Emil Perhinschi 03.04.2017 08:42
quelle