2012-04-05 11 views
9

Recientemente he estado ejecutando pruebas de rendimiento en Java vs C# para 1000 tareas que se programarán en un threadpool. El servidor tiene 4 procesadores físicos, cada uno con 8 núcleos. El SO es Server 2008, tiene 32 GB de memoria y cada CPU es Xeon x7550 Westmere/Nehalem-C.Java vs C# Rendimiento de subprocesamiento múltiple, ¿por qué Java se está volviendo más lento? (gráficos y código completo incluidos)

En resumen, la implementación de Java es mucho más rápida que C# en 4 hilos, pero mucho más lenta a medida que aumenta el número de subprocesos. También parece que C# se ha vuelto más rápido por iteración, cuando el número de subprocesos ha aumentado. Los gráficos se incluyen en este post:

Java vs C# with a threadpool size of 4 threads Java vs C# with a threadpool size of 32 threads Peter's Java answer (see below) vs C#, for 32 threads

La implementación de Java fue escrito en un Hotspot JVM de 64 bits, con Java 7 y el uso de un servicio Ejecutor de subprocesos que encontré en línea (véase más adelante). También configuré la JVM en GC concurrente.

C# fue escrito en .NET 3.5 y el vino de subprocesos CodeProject: http://www.codeproject.com/Articles/7933/Smart-Thread-Pool

(he incluido el código de abajo).

Mis preguntas:

1) ¿Por qué es cada vez más lento, pero Java C# es cada vez más rápido?

2) ¿Por qué los tiempos de ejecución de C# fluctúan mucho? (Esta es nuestra pregunta principal)

nos preguntamos si la fluctuación de C# fue causado por el bus de memoria trabajando al límite ....

Código (Por favor, no poner de relieve los errores con bloqueo, esto es irrelevante con mi AIMS):

Java

import java.io.DataOutputStream; 
import java.io.FileNotFoundException; 
import java.io.FileOutputStream; 
import java.io.PrintStream; 
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 
import java.util.concurrent.TimeUnit; 

public class PoolDemo { 

    static long FastestMemory = 2000000000; 
    static long SlowestMemory = 0; 
    static long TotalTime; 
    static long[] FileArray; 
    static DataOutputStream outs; 
    static FileOutputStream fout; 

    public static void main(String[] args) throws InterruptedException, FileNotFoundException { 

     int Iterations = Integer.parseInt(args[0]); 
     int ThreadSize = Integer.parseInt(args[1]); 

     FileArray = new long[Iterations]; 
     fout = new FileOutputStream("server_testing.csv"); 

     // fixed pool, unlimited queue 
     ExecutorService service = Executors.newFixedThreadPool(ThreadSize); 
     //ThreadPoolExecutor executor = (ThreadPoolExecutor) service; 

     for(int i = 0; i<Iterations; i++) { 
      Task t = new Task(i); 
      service.execute(t); 
     } 

     service.shutdown(); 
     service.awaitTermination(90, TimeUnit.SECONDS); 

     System.out.println("Fastest: " + FastestMemory); 
     System.out.println("Average: " + TotalTime/Iterations); 

     for(int j=0; j<FileArray.length; j++){ 
      new PrintStream(fout).println(FileArray[j] + ","); 
     } 
     } 

    private static class Task implements Runnable { 

     private int ID; 

     static Byte myByte = 0; 

     public Task(int index) { 
      this.ID = index; 
     } 

     @Override 
     public void run() { 
      long Start = System.nanoTime(); 

      int Size1 = 10000000; 
      int Size2 = 2 * Size1; 
      int Size3 = Size1; 

      byte[] list1 = new byte[Size1]; 
      byte[] list2 = new byte[Size2]; 
      byte[] list3 = new byte[Size3]; 

      for(int i=0; i<Size1; i++){ 
       list1[i] = myByte; 
      } 

      for (int i = 0; i < Size2; i=i+2) 
      { 
       list2[i] = myByte; 
      } 

      for (int i = 0; i < Size3; i++) 
      { 
       byte temp = list1[i]; 
       byte temp2 = list2[i]; 
       list3[i] = temp; 
       list2[i] = temp; 
       list1[i] = temp2; 
      } 

      long Finish = System.nanoTime(); 
      long Duration = Finish - Start; 
      FileArray[this.ID] = Duration; 
      TotalTime += Duration; 
      System.out.println("Individual Time " + this.ID + " \t: " + (Duration) + " nanoseconds"); 


      if(Duration < FastestMemory){ 
       FastestMemory = Duration; 
      } 
      if (Duration > SlowestMemory) 
      { 
       SlowestMemory = Duration; 
      } 
     } 
     } 
} 

C#:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading; 
using Amib.Threading; 
using System.Diagnostics; 
using System.IO; 
using System.Runtime; 


namespace ServerTesting 
{ 
    class Program 
    { 
     static long FastestMemory = 2000000000; 
     static long SlowestMemory = 0; 
     static long TotalTime = 0; 
     static int[] FileOutput; 
     static byte myByte = 56; 

     static System.IO.StreamWriter timeFile; 
     static System.IO.StreamWriter memoryFile; 

     static void Main(string[] args) 
     { 
      Console.WriteLine("Concurrent GC enabled: " + GCSettings.IsServerGC); 
      int Threads = Int32.Parse(args[1]); 
      int Iterations = Int32.Parse(args[0]); 

      timeFile = new System.IO.StreamWriter(Threads + "_" + Iterations + "_" + "time.csv"); 

      FileOutput = new int[Iterations]; 
      TestMemory(Threads, Iterations); 

      for (int j = 0; j < Iterations; j++) 
      { 
       timeFile.WriteLine(FileOutput[j] + ","); 
      } 

      timeFile.Close(); 
      Console.ReadLine(); 
     } 

     private static void TestMemory(int threads, int iterations) 
     { 
      SmartThreadPool pool = new SmartThreadPool(); 
      pool.MaxThreads = threads; 
      Console.WriteLine("Launching " + iterations + " calculators with " + pool.MaxThreads + " threads"); 
      for (int i = 0; i < iterations; i++) 
      { 
       pool.QueueWorkItem(new WorkItemCallback(MemoryIntensiveTask), i); 
      } 
      pool.WaitForIdle(); 
      double avg = TotalTime/iterations; 
      Console.WriteLine("Avg Memory Time : " + avg); 
      Console.WriteLine("Fastest: " + FastestMemory + " ms"); 
      Console.WriteLine("Slowest: " + SlowestMemory + " ms"); 
     } 



     private static object MemoryIntensiveTask(object args) 
     { 

      DateTime start = DateTime.Now; 
      int Size1 = 10000000; 
      int Size2 = 2 * Size1; 
      int Size3 = Size1; 

      byte[] list1 = new byte[Size1]; 
      byte[] list2 = new byte[Size2]; 
      byte[] list3 = new byte[Size3]; 

      for (int i = 0; i < Size1; i++) 
      { 
       list1[i] = myByte; 
      } 

      for (int i = 0; i < Size2; i = i + 2) 
      { 
       list2[i] = myByte; 
      } 

      for (int i = 0; i < Size3; i++) 
      { 
       byte temp = list1[i]; 
       byte temp2 = list2[i]; 
       list3[i] = temp; 
       list2[i] = temp; 
       list1[i] = temp2; 
      } 

      DateTime finish = DateTime.Now; 
      TimeSpan ts = finish - start; 
      long duration = ts.Milliseconds; 

      Console.WriteLine("Individual Time " + args + " \t: " + duration); 

      FileOutput[(int)args] = (int)duration; 
      TotalTime += duration; 

      if (duration < FastestMemory) 
      { 
       FastestMemory = duration; 
      } 
      if (duration > SlowestMemory) 
      { 
       SlowestMemory = duration; 
      } 
      return null; 
     } 
    } 
} 
+7

¿Se dio cuenta de que en Java su bucle crea un 'PrintStream' en cada iteración mientras que en' C# 'se abre la 'StreamWriter' solo una vez? No creo que explique el fenómeno, pero su prueba no es 100% precisa en el nivel básico. – RonK

+0

¿Has probado la clase ThreadPoolExecutor? Se supone que ThreadPoolExecutor proporciona un rendimiento mejorado para una gran cantidad de tareas asincrónicas. – ChadNC

+0

@ ChanNC- gracias lo intentaré. – mezamorphic

Respuesta

5

Y Parece que no está probando el marco de subprocesamiento tanto como está probando cómo el lenguaje optimiza el código no optimizado.

Java es especialmente bueno para optimizar código sin sentido, lo que creo que explicaría la diferencia en los idiomas. A medida que crece el número de hilos, sospecho que el cuello de la botella se mueve a la forma en que se realiza el GC o algo más incidental a su prueba.

Java también podría estar desacelerando, ya que no es compatible con NUMA de forma predeterminada. Intente ejecutar -XX:+UseNUMA Sin embargo, sugiero que para obtener el máximo rendimiento, intente mantener cada proceso en una sola región numa para evitar la sobrecarga de numa cruzada.

También puede probar este optimizar ligeramente código que fue del 40% rápido en mi máquina

import java.io.DataOutputStream; 
import java.io.FileNotFoundException; 
import java.io.FileOutputStream; 
import java.io.PrintStream; 
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 
import java.util.concurrent.TimeUnit; 

public class PoolDemo { 

    static long FastestMemory = 2000000000; 
    static long SlowestMemory = 0; 
    static long TotalTime; 
    static long[] FileArray; 
    static FileOutputStream fout; 

    public static void main(String[] args) throws InterruptedException, FileNotFoundException { 

     int Iterations = Integer.parseInt(args[0]); 
     int ThreadSize = Integer.parseInt(args[1]); 

     FileArray = new long[Iterations]; 
     fout = new FileOutputStream("server_testing.csv"); 

     // fixed pool, unlimited queue 
     ExecutorService service = Executors.newFixedThreadPool(ThreadSize); 
     //ThreadPoolExecutor executor = (ThreadPoolExecutor) service; 

     for (int i = 0; i < Iterations; i++) { 
      Task t = new Task(i); 
      service.execute(t); 
     } 

     service.shutdown(); 
     service.awaitTermination(90, TimeUnit.SECONDS); 

     System.out.println("Fastest: " + FastestMemory); 
     System.out.println("Average: " + TotalTime/Iterations); 

     PrintStream ps = new PrintStream(fout); 
     for (long aFileArray : FileArray) { 
      ps.println(aFileArray + ","); 
     } 
    } 

    static class ThreadLocalBytes extends ThreadLocal<byte[]> { 
     private final int bytes; 

     ThreadLocalBytes(int bytes) { 
      this.bytes = bytes; 
     } 

     @Override 
     protected byte[] initialValue() { 
      return new byte[bytes]; 
     } 
    } 

    private static class Task implements Runnable { 

     static final int Size1 = 10000000; 
     static final int Size2 = 2 * Size1; 
     static final int Size3 = Size1; 

     private int ID; 
     private static final ThreadLocalBytes list1b = new ThreadLocalBytes(Size1); 
     private static final ThreadLocalBytes list2b = new ThreadLocalBytes(Size2); 
     private static final ThreadLocalBytes list3b = new ThreadLocalBytes(Size3); 

     static byte myByte = 0; 

     public Task(int index) { 
      this.ID = index; 
     } 

     @Override 
     public void run() { 
      long Start = System.nanoTime(); 


      byte[] list1 = list1b.get(); 
      byte[] list2 = list2b.get(); 
      byte[] list3 = list3b.get(); 

      for (int i = 0; i < Size1; i++) { 
       list1[i] = myByte; 
      } 

      for (int i = 0; i < Size2; i = i + 2) { 
       list2[i] = myByte; 
      } 

      for (int i = 0; i < Size3; i++) { 
       byte temp = list1[i]; 
       byte temp2 = list2[i]; 
       list3[i] = temp; 
       list2[i] = temp; 
       list1[i] = temp2; 
      } 

      long Finish = System.nanoTime(); 
      long Duration = Finish - Start; 
      FileArray[this.ID] = Duration; 
      TotalTime += Duration; 
      System.out.println("Individual Time " + this.ID + " \t: " + (Duration) + " nanoseconds"); 

      if (Duration < FastestMemory) { 
       FastestMemory = Duration; 
      } 
      if (Duration > SlowestMemory) { 
       SlowestMemory = Duration; 
      } 
     } 
    } 
} 
+1

Peter ¡Acabo de probar tu código, lo mejor! ¿Podrían explicar por qué sus cambios marcaron la diferencia? Lamento agobiarte ... ¿También sabes por qué el código C# fluctúa tanto? Esta es una de nuestras mayores preocupaciones ya que toda nuestra biblioteca está escrita en C#. – mezamorphic

+2

A medida que usa más subprocesos, los recursos de subproceso único o compartido se vuelven cada vez más críticos. En este caso, el GC es un recurso compartido. En el ejemplo anterior, reciclo mis búferes, lo cual no solo lo hace más rápido, sino que reduce la inestabilidad (hace una pausa para hacer limpiezas). Nunca he tocado C# (en ningún grado lo admitiré) pero supongo que el mismo enfoque que funciona en Java es a la CPU y perfil de memoria de su aplicación. Asegúrate de tener una herramienta en la que puedas confiar. VisualVM no es un gran ejemplo (pero es gratis;) –

+0

Hey peter, graficé el 40% de aceleración y durante las primeras 25 iteraciones el java toma alrededor de 2450 ms, pero luego para el resto de las 975 iteraciones toma alrededor de 450ms- ¿Esto se debe al GC y hay alguna optimización que pueda utilizar para evitar este cuello de botella inicial? Gracias, tus respuestas son muy apreciadas. – mezamorphic

Cuestiones relacionadas