2011-02-03 24 views
10

He diseñado una clase que llena una matriz con números enteros usando un número variado de subprocesos, con el fin de ver el poder de multi-threading. Pero de acuerdo con mi resultado, no hay ninguno ...¿Por qué mi multihilo no es eficiente?

La idea: La idea era llenar una matriz de 100000000 enteros con el valor "1". Comenzando con 1 hilo (uno hilos llena toda la matriz) e incrementando hasta 100 hilos (cada hilo se llena una matriz de sub de tamaño 100000000/nbThreads)

Ejemplo: Con 10 hilos, creo 10 hilos y cada uno es llenando una matriz de 10000000 enteros.

Aquí está mi código:

public class ThreadedArrayFilling extends Thread{ 
    private int start; 
    private int partitionSize; 
    public static int[] data; 
    public static final int SIZE = 100000000; 
    public static final int NB_THREADS_MAX = 100; 


    public static void main(String[] args){ 
     data = new int[SIZE]; 
     long startTime, endTime; 
     int partition, startIndex, j; 
     ThreadedArrayLookup[] threads; 

     for(int i = 1; i <= NB_THREADS_MAX; i++){  
      startTime = System.currentTimeMillis(); 
      partition = SIZE/i; 
      startIndex = 0; 
       threads = new ThreadedArrayLookup[i]; 
      for(j = 0; j < i; j++){   
       threads[j] = new ThreadedArrayLookup(startIndex, partition); 
       startIndex += partition; 
      } 
      for(j = 0; j < i; j++){ 
       try { 
        threads[j].join(); 
       } catch (InterruptedException e) { 
        // TODO Auto-generated catch block 
        e.printStackTrace(); 
       } 
      } 
      endTime = System.currentTimeMillis();  
      System.out.println(i + " THREADS: " + (endTime - startTime) + "ms"); 
     } 
    } 

    public ThreadedArrayFilling(int start, int size){ 
     this.start = start; 
     this.partitionSize = size; 
     this.start(); 
    } 

    public void run(){ 
     for(int i = 0; i < this.partitionSize; i++){ 
      data[this.start + i] = 1; 
     } 
    } 

    public static String display(int[] d){ 
     String s = "["; 

     for(int i = 0; i < d.length; i++){ 
      s += d[i] + ", "; 
     } 

     s += "]"; 
     return s; 
    } 

} 

Y aquí están mis resultados:

1 THREADS: 196ms 
2 THREADS: 208ms 
3 THREADS: 222ms 
4 THREADS: 213ms 
5 THREADS: 198ms 
6 THREADS: 198ms 
7 THREADS: 198ms 
8 THREADS: 198ms 
9 THREADS: 198ms 
10 THREADS: 206ms 
11 THREADS: 201ms 
12 THREADS: 197ms 
13 THREADS: 198ms 
14 THREADS: 204ms 
15 THREADS: 199ms 
16 THREADS: 203ms 
17 THREADS: 234ms 
18 THREADS: 225ms 
19 THREADS: 235ms 
20 THREADS: 235ms 
21 THREADS: 234ms 
22 THREADS: 221ms 
23 THREADS: 211ms 
24 THREADS: 203ms 
25 THREADS: 206ms 
26 THREADS: 200ms 
27 THREADS: 202ms 
28 THREADS: 204ms 
29 THREADS: 202ms 
30 THREADS: 200ms 
31 THREADS: 206ms 
32 THREADS: 200ms 
33 THREADS: 205ms 
34 THREADS: 203ms 
35 THREADS: 200ms 
36 THREADS: 206ms 
37 THREADS: 200ms 
38 THREADS: 204ms 
39 THREADS: 205ms 
40 THREADS: 201ms 
41 THREADS: 206ms 
42 THREADS: 200ms 
43 THREADS: 204ms 
44 THREADS: 204ms 
45 THREADS: 206ms 
46 THREADS: 203ms 
47 THREADS: 204ms 
48 THREADS: 204ms 
49 THREADS: 201ms 
50 THREADS: 205ms 
51 THREADS: 204ms 
52 THREADS: 207ms 
53 THREADS: 202ms 
54 THREADS: 207ms 
55 THREADS: 207ms 
56 THREADS: 203ms 
57 THREADS: 203ms 
58 THREADS: 201ms 
59 THREADS: 206ms 
60 THREADS: 206ms 
61 THREADS: 204ms 
62 THREADS: 201ms 
63 THREADS: 206ms 
64 THREADS: 202ms 
65 THREADS: 206ms 
66 THREADS: 205ms 
67 THREADS: 207ms 
68 THREADS: 210ms 
69 THREADS: 207ms 
70 THREADS: 203ms 
71 THREADS: 207ms 
72 THREADS: 205ms 
73 THREADS: 203ms 
74 THREADS: 211ms 
75 THREADS: 202ms 
76 THREADS: 207ms 
77 THREADS: 204ms 
78 THREADS: 212ms 
79 THREADS: 203ms 
80 THREADS: 210ms 
81 THREADS: 206ms 
82 THREADS: 205ms 
83 THREADS: 203ms 
84 THREADS: 203ms 
85 THREADS: 209ms 
86 THREADS: 204ms 
87 THREADS: 206ms 
88 THREADS: 208ms 
89 THREADS: 263ms 
90 THREADS: 216ms 
91 THREADS: 230ms 
92 THREADS: 216ms 
93 THREADS: 230ms 
94 THREADS: 234ms 
95 THREADS: 234ms 
96 THREADS: 217ms 
97 THREADS: 229ms 
98 THREADS: 228ms 
99 THREADS: 215ms 
100 THREADS: 232ms 

Qué me he perdido?

EDITAR: Informaciones adicionales:

Mi máquina está en funcionamiento un doble núcleo.

expectativas:

  • me esperaba ver un enorme aumento de actuaciones entre 1 y 2 hilos (para hacer uso de la doble núcleo)
  • Yo también estaba esperando a ver una desaceleración después eso para una gran cantidad de hilos.

Pero esto no cumple ninguna de mis expectativas. ¿Mis expectativas son falsas o es un problema con mi algo?

+1

@nbarraille, ¿cuántos núcleos tiene en su máquina? – dsolimano

+0

"Ejemplo: con 10 hilos, creo 10 hilos y cada uno llena una matriz de 10000000 enteros". - Supongo que quieres decir que cada hilo está llenando 1/10 de la matriz. – Grammin

+0

dsolimano: 2cores en esta máquina – nbarraille

Respuesta

18

Con dos núcleos, el mejor rendimiento que podría esperar es 2 hilos que llevan la mitad del tiempo como un solo hilo.Cualquier subproceso adicional solo genera una sobrecarga inútil después de eso, suponiendo que estás completamente vinculado a la CPU, pero en realidad no lo estás.

La pregunta es por qué no se ve una mejora al pasar de 1 a 2 hilos. Y la razón probablemente sea que su programa no está vinculado a la CPU, sino que está atado a la memoria. Su cuello de botella es el acceso a la memoria principal, y los 2 hilos están por turnos escribiendo en la memoria principal. Los núcleos de CPU reales no hacen nada la mayor parte del tiempo. Verá la diferencia esperada si en vez de hacer poco trabajo real en una gran área de memoria hace mucho trabajo intensivo de CPU en una pequeña cantidad de memoria. Porque entonces cada núcleo de CPU puede funcionar completamente dentro de su caché.

+0

Gracias. Entendí por qué no veo ningún aumento de rendimiento entre 1 y 2 hilos. Pero, ¿cómo se puede explicar que las actuaciones no son degradantes para 100 hilos? – nbarraille

+0

Pregunta adicional: me han dado a entender que, en algunas aplicaciones, podría ser más eficiente usar muchos más subprocesos que la cantidad de CPU, cuando el factor limitante no es la CPU o la memoria (como el disco o el acceso a la red). ¿Estás de acuerdo con esto? – nbarraille

+1

@nbarraille: sí, eso es algo cierto. La idea es que algunos subprocesos pueden usar la CPU mientras que otros esperan por IO. Sin embargo, es mejor usar hilos separados (o grupos de hilos) para estas tareas. Normalmente no desea tener más de un subproceso que acceda al disco, y ese subproceso puede entregar tareas a un grupo de subprocesos de cálculo. En cuanto a no ver la degradación con muchos hilos, no estoy seguro. Tal vez cada subproceso básicamente hace su parte en secuencia, por lo que no hay sobrecarga de contención y la sobrecarga de creación de subprocesos se oculta al tener dos núcleos que se alternan o que son pequeños. –

9

El subprocesamiento múltiple es super eficiente cuando su software está vinculado a la CPU: hay muchas aplicaciones que son de subprocesamiento único y puede verlas infrautilizando CPU modernas maximizando solo el uso de un núcleo (esto aparece muy claramente en monitores de CPU)

Sin embargo, no tiene sentido lanzar muchos más hilos que la cantidad de CPU (virtuales) disponibles.

Las aplicaciones de múltiples subprocesos correctos que hacen, por ejemplo, el crujido de números, crean varios subprocesos de trabajo que están relacionados con la cantidad de CPU (virtuales) disponibles para la JVM.

+0

Sí, eso es lo que esperaba: como mi computadora tiene 2 CPUs, esperaba ver un aumento real de rendimientos entre 1 y 2 núcleos, y luego una ralentización para una gran cantidad de hilos. Pero ninguna de mis suposiciones fue verificada ... – nbarraille

+4

@nbarraille - Su código probablemente no está vinculado a la CPU, pero está limitado más por la velocidad de acceso a la memoria. – Justin

+0

Justin: ¿Puedo cambiar esto cambiando la cantidad de memoria asignada a la JVM? Si es así, ¿cómo puedo hacerlo en Eclipse? – nbarraille

4

La tarea que realiza dentro del hilo es muy pequeña, el tiempo utilizado para eso se ve superado por la sobrecarga de su configuración.

Realice algunos cálculos pesados ​​(por ejemplo, ejecute una aproximación de PI para poner en la matriz) verá una ventaja de varios hilos, pero solo hasta aproximadamente el número de núcleos que tiene su máquina.

O haga algo que espere algo externo (lectura de una base de datos, raspando datos de un sitio web) esto podría ser más eficaz siempre que otros hilos hagan algo útil mientras otros están esperando.

0

Es posible que dos hilos, cada uno con su propia CPU o núcleo, trabajen al unísono, para completar una tarea más lenta que si solo un hilo hiciera todo el trabajo. Ambos núcleos quieren que sus cachés L1 + L2 escriban datos en la memoria, lo que está bien. Sin embargo, pronto saturan el caché L3 común de tal forma que detiene escrituras adicionales hasta que ha logrado escribir una línea de caché actualizada en la RAM, lo que le permite aceptar nuevas escrituras.

Dicho de otra manera, el propósito de sus hilos no es realizar ningún procesamiento para hablar sino llenar la RAM del sistema. La memoria RAM del sistema es lenta y, como puede ver al comparar su resultado de un hilo con el de dos hilos, la capacidad de escritura en RAM se agota con un hilo y, por lo tanto, no puede ser más rápida con dos hilos.

Sus hilos son tan pequeños que con toda probabilidad residirán en la caché L1 y, por lo tanto, no requerirán recuperaciones de la RAM del sistema, lo que obstaculizaría su capacidad para escribir RAM. Su capacidad para escribir en RAM es la misma ya sea que tenga 1 o 100 hilos tratando de hacerlo. Cuantos más hilos tenga, más gastos de administración de subprocesos tendrá. Esto es insignificante para algunos hilos pero aumenta para cada hilo adicional y eventualmente se volverá perceptible.

Cuestiones relacionadas