In relazione al design e al runtime - è meglio parallelizzare le sotto-attività più piccole o le attività in bundle?

2

Sto programmando un piccolo web scraper in python che voglio accelerare parallelizzando le cose. Il raschietto sta eseguendo la scansione degli URL in base al quale un singolo URL può rappresentare un "elemento" o un "indice". Un indice in questo caso è un elenco di URL di altri articoli e indici. L'analisi per un URL oggetto è diversa dall'analisi di un URL dell'indice.

Quindi per ogni URL succede quanto segue:

  1. recupera origine dall'URL
  2. controlla se l'URL è indice o elemento
  3. sorgente di analisi (in base all'indice o all'elemento)

Ora sto pensando a due diverse soluzioni per la parallelizzazione del processo:

a) usa una coda di URL, un processo di lavoro prende un URL e fa 1 - 3 sequenziale

b) utilizza una coda di attività (ad esempio "origine di recupero"), un processo di lavoro fa solo 1, 2 o 3

Penso che a) sia più logico e forse più leggibile nell'implementazione. Inoltre non so se una soluzione sia più veloce dell'altra.

Quale sarebbe il modo migliore per realizzare il mio progetto?

    
posta jervis 30.12.2016 - 20:30
fonte

2 risposte

1

Per aumentare il parallelismo, vogliamo esaminare diverse cose: (i) suddividere il lavoro e (ii) problemi di latenza.

Per aumentare le prestazioni (supponendo che ora abbiamo il parallelismo) vogliamo (iii) esaminare come vengono utilizzate le cache e (iv) evitare inutili sovraccarichi del sistema operativo associati al cambio di contesto (dall'avere troppi thread legati alla CPU, come non solo il cambio di contesto ha il suo overhead, ma tende anche a raffreddare le cache).

Suddividiamo il lavoro (i) in modo che possano essere applicate più CPU e identifichiamo operazioni a latenza elevata in modo da poter utilizzare un numero inferiore di thread complessivi.

Nei passaggi sequenziali, può esistere una significativa latenza di rete tra la richiesta dell'URL e la ricezione del documento a quell'URL.

Se dedichiamo il / i lavoratore / i a fare cose che implicano latenza, avremo spesso dei lavoratori bloccati in attesa di ricevere risposte dalla rete. A causa di ciò, avremo bisogno di avere molti lavoratori e contiamo sulla lingua e sull'ampiezza efficienza del sistema operativo delle implementazioni di threading per scala.

Questo non vuol dire che questo non possa funzionare, ma un'alternativa che utilizza oggetti semplici invece di thread probabilmente scalerebbe meglio.

Il punto principale che farei è che sembra che mentre il tuo passo (1) ha latenza, passi (2) e amp; (3) non sembrano avere latenza, il che significa che sono probabilmente entrambe le operazioni associate alla CPU.

Quindi, probabilmente avrei due gruppi di lavoratori e una coda di fronte a ciascuno. La prima coda dovrebbe contenere gli URL da visitare e avere un gruppo di lavoro come un semplice thread in background (ad esempio forse un solo worker) per attivare essenzialmente le richieste di rete in modo asincrono. Una cosa che questo operatore potrebbe fare è imporre qualsiasi limitazione necessaria (ad esempio, il recupero totale degli URL in sospeso o gli URL allo stesso server o monitorare la coda di output per essere troppo grande (il che significa che la seconda fase è in ritardo)).

La seconda coda sarà dedicata ai risultati di rete ricevuti e gli operatori manterranno questi risultati man mano che entrano in (2) & (3) insieme. Potresti volere tanti di questi lavoratori quanto tu hai le CPU; più non aiuterà molto e potrebbe anche ferire.

In questo approccio, utilizziamo oggetti più piccoli (rispetto ai thread) per ridimensionarli e mantenere il numero di thread totali più vicino al numero di CPU.

Quindi, non avrei gruppi separati di lavoratori per (2) e (3) connessi con una coda in mezzo, e invece suggerirei un solo gruppo di lavoro, dove questi lavoratori elaborano sequenzialmente (2) e amp; (3). Non vedo alcun punto per separare 2 & 3 (a meno che non ci sia un enorme vantaggio nella cache per l'esecuzione di molti 2 di fila e poi di molti 3 di fila, anche se questo non salta fuori come probabile per quello che stai descrivendo).

Se hai separato 2 & 3, dovresti capire quanti di loro avere, e qui corri il rischio di picchiare le cache se ti capita di passare troppo al cambio di contesto.

    
risposta data 30.12.2016 - 21:14
fonte
1

Tecnicamente, ha senso separare 1 da 2 e 3.

1 è vincolato dal throughput della rete. mentre 2 e 3 sono cpu limitati. Non vuoi un collo di bottiglia in uno che regge l'altro.

Architettonicamente ha più senso dividere 1, 2, 3-item 3-index.

Dato che hai percorso la coda / il lavoratore / i messaggi, potresti anche andare da capo e abbracciarlo. Ciò ti offre la flessibilità aggiuntiva di poter estrarre e sostituire un singolo passaggio senza influire sugli altri.

Ad esempio, decidi che esiste una terza categoria di pagina, indice, articolo e flusso. potresti sostituire 2 con la tua nuova logica e aggiungere un nuovo processo di parser dello stream senza influenzare 1 o 3

O forse decidi che l'elaborazione dell'indice è più importante dell'elemento. È possibile ridimensionare il numero di lavoratori per l'indice e lasciare l'elaborazione dell'elemento in esecuzione a una velocità inferiore.

    
risposta data 29.01.2017 - 22:04
fonte