2009-03-04 21 views
9

estoy utilizando LINQ paralelo, y yo estoy tratando de descargar muchas direcciones URL al mismo tiempo utilizando essentily código como este:Parallel LINQ - uso más hilos que los procesadores (CPU para tareas no encuadernadas)

int threads = 10; 
Dictionary<string, string> results = urls.AsParallel(threads).ToDictionary(url => url, url => GetPage(url); 

Desde descargar páginas web está vinculado a la red en lugar de a la CPU, usar más hilos que mi número de procesadores/núcleos es muy beneficioso, ya que la mayor parte del tiempo en cada hilo se gasta esperando a que la red se ponga al día. Sin embargo, a juzgar por el hecho de que ejecutar lo anterior con subprocesos = 2 tiene el mismo rendimiento que los subprocesos = 10 en mi máquina de doble núcleo, estoy pensando que las pisadas enviadas a AsParallel están limitadas a la cantidad de núcleos.

¿Hay alguna manera de anular este comportamiento? ¿Hay una biblioteca similar disponible que no tenga esta limitación?

(he encontrado tal biblioteca para Python, pero necesita algo que funciona en .Net)

Respuesta

12

¿Las direcciones URL se refieren al mismo servidor? Si es así, es posible que esté presionando el límite de conexión HTTP en lugar del límite de subprocesamiento. Hay una forma fácil de hacerlo: cambie su código a:

int threads = 10; 
Dictionary<string, string> results = urls.AsParallel(threads) 
    .ToDictionary(url => url, 
        url => { 
         Console.WriteLine("On thread {0}", 
             Thread.CurrentThread.ManagedThreadId); 
         return GetPage(url); 
        }); 

EDIT: Hmm. No puedo obtener ToDictionary() para poner en paralelo en todo con un poco de código de muestra. Funciona bien para Select(url => GetPage(url)) pero no para ToDictionary. Buscará un poco.

EDITAR: Bien, todavía no puedo obtener ToDictionary para hacer paralelo, pero puede solucionarlo. Aquí hay un programa corto pero completo:

using System; 
using System.Collections.Generic; 
using System.Threading; 
using System.Linq; 
using System.Linq.Parallel; 

public class Test 
{ 

    static void Main() 
    { 
     var urls = Enumerable.Range(0, 100).Select(i => i.ToString()); 

     int threads = 10; 
     Dictionary<string, string> results = urls.AsParallel(threads) 
      .Select(url => new { Url=url, Page=GetPage(url) }) 
      .ToDictionary(x => x.Url, x => x.Page); 
    } 

    static string GetPage(string x) 
    { 
     Console.WriteLine("On thread {0} getting {1}", 
          Thread.CurrentThread.ManagedThreadId, x); 
     Thread.Sleep(2000); 
     return x; 
    } 
} 

Entonces, ¿cuántos hilos utiliza esto? 5. ¿Por qué? Dios sabe. Tengo 2 procesadores, así que no es así, y hemos especificado 10 hilos, así que eso no es todo. Todavía usa 5 incluso si cambio GetPage para martillar la CPU.

Si solo necesita utilizar esto para una tarea en particular, y no le molesta el código levemente maloliente, es mejor que lo implemente usted mismo, para ser honesto.

+0

estoy recibiendo el mismo síntoma. Ejecuté su análisis y obtuve solo 1 hilo ... supongo que el rendimiento aumentó de 1 a 2 hilos que vi en mi cabeza –

+0

@DrFredEdison: ¿Qué ocurre si utiliza el formulario Select/ToDictionary como en la muestra? –

+0

Veo más o menos el mismo resultado que tú. Tengo aproximadamente 5 hilos usados ​​para cada ejecución de prueba ahora .. Gracias por llevarme hasta aquí ... Creo que conseguirá trabajo para lo que necesito en este momento. –

0

Controle el tráfico de su red. Si las URL provienen del mismo dominio, puede estar limitando el ancho de banda. Es posible que más conexiones no proporcionen ninguna aceleración.

6

De forma predeterminada, .Net tiene un límite de 2 conexiones simultáneas a un punto de servicio final (IP: puerto). Es por eso que no verías una diferencia si todas las URL son para el mismo servidor.

Se puede controlar utilizando la propiedad ServicePointManager.DefaultPersistentConnectionLimit.

1

Creo que ya hay buenas respuestas a la pregunta, pero me gustaría señalar un punto importante. Usar PLINQ para tareas que no están vinculadas a la CPU es, en principio, un diseño incorrecto. No quiere decir que no funcionará, lo hará, pero usar múltiples hilos cuando no es necesario puede causar problemas.

Desafortunadamente, no hay una buena manera de resolver este problema en C#. En F # puede usar flujos de trabajo asimétricos que se ejecutan en paralelo, pero no bloquean el hilo cuando se realizan llamadas asíncronas (debajo de la cubierta, usa los métodos BeginOperation y EndOperation).Puede encontrar más información aquí:

La misma idea se puede en cierta medida ser utilizado en C#, pero parece un poco raro (pero es más eficiente). Escribí un artículo sobre eso y también hay una biblioteca que debe ser un poco más evolucionado que mi idea original:

Cuestiones relacionadas