Empresas
Empleos
  • Sobre nosotros
  • Soluciones
    • Publicación de vacantes
      Publica tu vacante y recibe candidatos calificados en 48h.
    • Evaluación de candidatos
      500+ pruebas técnicas y psicológicas, más anti-fraude.
    • Headhunting
      Búsqueda ejecutiva a la medida de principio a fin.
    • Nómina + EOR
      Dispersión de nómina y EOR en más de 15 países de LATAM.
  • Precios
  • Empleos

0

199
Vistas
¿Por qué Parallel.For no es rápido con operaciones intensivas en montón?

Para algunas operaciones, Parallel escala bien con el número de CPU, pero para otras operaciones no lo hace.

Considere el siguiente código, function1 obtiene una mejora de 10x mientras que function2 obtiene una mejora de 3x. ¿Esto se debe a la asignación de memoria, o quizás a GC?

 void function1(int v) { for (int i = 0; i < 100000000; i++) { var q = Math.Sqrt(v); } } void function2(int v) { Dictionary<int, int> dict = new Dictionary<int, int>(); for (int i = 0; i < 10000000; i++) { dict.Add(i, v); } } var sw = new System.Diagnostics.Stopwatch(); var iterations = 100; sw.Restart(); for (int v = 0; v < iterations; v++) function1(v); sw.Stop(); Console.WriteLine("function1 no parallel: " + sw.Elapsed.TotalMilliseconds.ToString("### ##0.0ms")); sw.Restart(); Parallel.For(0, iterations, function1); sw.Stop(); Console.WriteLine("function1 with parallel: " + sw.Elapsed.TotalMilliseconds.ToString("### ##0.0ms")); sw.Restart(); for (int v = 0; v < iterations; v++) function2(v); sw.Stop(); Console.WriteLine("function2 no parallel: " + sw.Elapsed.TotalMilliseconds.ToString("### ##0.0ms")); sw.Restart(); Parallel.For(0, iterations, function2); sw.Stop(); Console.WriteLine("function2 parallel: " + sw.Elapsed.TotalMilliseconds.ToString("### ##0.0ms"));

La salida en mi máquina:

 function1 no parallel: 2 059,4 ms function1 with parallel: 213,7 ms function2 no parallel: 14 192,8 ms function2 parallel: 4 491,1 ms

Ambiente:
Win 11, .Net 6.0, compilación de lanzamiento
i9 de 12.ª generación, 16 núcleos, 24 procesadores, DDR5 de 32 GB


Después de probar más, parece que la asignación de memoria no escala tan bien con múltiples subprocesos. Por ejemplo, si cambio la función 2 a:

 void function2(int v) { Dictionary<int, int> dict = new Dictionary<int, int>(10000000); }

El resultado es:

 function2 no parallell: 124,0 ms function2 parallell: 402,4 ms

¿La conclusión es que la asignación de memoria no escala bien con múltiples subprocesos?...

over 4 years ago · Santiago Trujillo
2 Respuestas
Responde la pregunta

0

Primera función trabaja en registros. Más núcleos = más registros.

La segunda función funciona en la memoria. Más núcleos = solo más caché L1 pero RAM compartida. El conjunto de datos de 10 millones de elementos ciertamente solo proviene de la RAM, ya que incluso L3 no es lo suficientemente grande. Esto supone que jit of language optimiza las asignaciones como búferes reutilizados. Si no, entonces también hay gastos generales de asignación. Por lo tanto, debe reutilizar el diccionario en cada nueva iteración en lugar de recrearlo.

También está guardando datos con un índice entero incremental. La matriz simple podría funcionar aquí, por supuesto, con la reutilización entre iteraciones. Debería tener menos consumo de memoria que un diccionario.

over 4 years ago · Santiago Trujillo Denunciar

0

tl;dr: contención de asignación de almacenamiento dinámico.

Tu primera función es vergonzosamente paralela . Cada subproceso puede hacer su cálculo con vergonzosamente poca interacción con otros subprocesos. Por lo tanto, se amplía muy bien a múltiples subprocesos. huseyin tugrul buyukisik señaló correctamente que su primer cálculo utiliza los registros del procesador no compartidos, por subproceso.

Su segunda función, cuando preasigna el diccionario, es algo menos vergonzosamente paralela. El cálculo de cada subproceso es independiente del de los demás, excepto por el hecho de que cada uno usa el subsistema RAM de su máquina. Por lo tanto, ve cierta contención de subproceso a subproceso a nivel de hardware a medida que los datos almacenados en caché a nivel de subproceso se escriben y se leen desde la RAM de nivel de máquina.

Su segunda función que no preasigna memoria no es vergonzosamente paralela. ¿Por qué no? Cada operación .Add() debe asignar algunos datos en el montón compartido. Eso no se puede hacer en paralelo, porque todos los subprocesos comparten el mismo montón. Más bien deben estar sincronizados. Las bibliotecas dotnet hacen un buen trabajo al paralelizar las operaciones del montón tanto como sea posible, pero no evitan al menos cierto bloqueo del subproceso B cuando el subproceso A asigna datos del montón. Entonces los hilos se ralentizan entre sí.

Los procesos separados, en lugar de los subprocesos separados, son una buena manera de escalar cargas de trabajo como su segunda función sin preasignación. Cada proceso tiene su propio montón.

over 4 years ago · Santiago Trujillo Denunciar
Responde la pregunta
Encuentra empleos remotos

¡Descubre la nueva forma de encontrar empleo!

Top de empleos
Top categorías de empleo
Empresas
Publicar vacante Precios Comercial
Legal
Términos y condiciones Política de privacidad
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomiéndame algunas ofertas
Necesito ayuda