2012-04-05 12 views
9

Recentemente ho eseguito benchmark su Java vs C# per 1000 attività da pianificare su un threadpool. Il server ha 4 processori fisici, ciascuno con 8 core. Il sistema operativo è Server 2008, ha 32 GB di memoria e ogni CPU è una Xeon x7550 Westmere/Nehalem-C.Prestazioni di multithreading Java vs C#, perché Java si sta rallentando? (grafici e codice completo inclusi)

In breve, l'implementazione Java è molto più veloce di C# a 4 thread ma molto più lentamente con l'aumento del numero di thread. Sembra anche che C# sia diventato più veloce per iterazione, quando il numero di thread è aumentato. I grafici sono inclusi in questo 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

L'implementazione Java è stato scritto su un 64bit Hotspot JVM, con Java 7 e l'utilizzo di un servizio di esecutore di thread ho trovato in rete (vedi sotto). Ho anche impostato la JVM su GC simultaneo.

C# è stato scritto a .net 3.5 e il pool di thread è venuto da CodeProject: http://www.codeproject.com/Articles/7933/Smart-Thread-Pool

(ho incluso il codice qui sotto).

Le mie domande:

1) Perché Java sempre più lento, ma C# è sempre più veloce?

2) Perché i tempi di esecuzione di C# variano notevolmente? (Questa è la nostra domanda principale)

ci chiedemmo se il C# fluttuazione è stato causato dal bus di memoria che è maxed ....

Codice (Si prega di non mettere in evidenza gli errori con bloccaggio, questo è irrilevante con la mia mira):

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

Avete notato che in Java il ciclo crea un 'PrintStream' in ogni iterazione, mentre in' C# 'si apre il 'StreamWriter' solo una volta? Non penso che spieghi il fenomeno, ma il tuo test non è preciso al 100% a livello base. – RonK

+0

Hai provato la classe ThreadPoolExecutor? ThreadPoolExecutor dovrebbe fornire prestazioni migliorate per un numero elevato di attività asincrone. – ChadNC

+0

@ ChanNC- grazie Ci proverò. – mezamorphic

risposta

5

Y Non sembra che stiate testando il funzionamento del frame di thread tanto quanto state testando come la lingua ottimizzi il codice non ottimizzato.

Java è particolarmente adatto all'ottimizzazione del codice inutile, che credo possa spiegare la differenza nelle lingue. Man mano che il numero di thread aumenta, sospetto che il collo della bottiglia si sposti su come si comporta il GC o qualcos'altro che sia secondario al tuo test.

Java potrebbe anche rallentare poiché non è consapevole della NUMA per impostazione predefinita. Prova a eseguire -XX:+UseNUMA Tuttavia ti suggerisco di ottenere il massimo delle prestazioni dovresti cercare di mantenere ogni processo in una singola regione numa per evitare un sovraccarico incrociato.

Potete anche provare questo un po 'ottimizzare il codice che è stato il 40% in fretta sulla mia macchina

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 Ho appena provato la roba del tuo codice! Potresti approfondire il motivo per cui le tue modifiche hanno fatto la differenza? Mi dispiace caricarti ... Inoltre sai perché il codice C# fluttua così tanto? Questa è una delle nostre maggiori preoccupazioni poiché la nostra intera libreria è scritta in C#. – mezamorphic

+2

Mentre si utilizzano più thread, le singole risorse threaded/condivise diventano sempre più critiche. In questo caso il GC è una risorsa condivisa. Nell'esempio sopra ho riciclato i miei buffer che non solo rendono più veloce ma riduce il jitter (pause per fare clean up) Non ho mai toccato C# (in qualsiasi modo lo ammetto) ma presumo lo stesso approccio che funziona in Java, è per la cpu e il profilo di memoria della tua applicazione. Assicurati di avere uno strumento di cui ti puoi fidare. VisualVM non è un grande esempio (ma è gratuito;) –

+0

Hey peter, ho tracciato il grafico del 40% di accelerazione e per le prime 25 iterazioni il java impiega circa 2450 ms, ma poi per il resto delle 975 iterazioni ci vogliono circa 450ms- è dovuto al GC e ci sono ottimizzazioni che potrei usare per evitare questo collo di bottiglia iniziale? Grazie - le tue risposte sono molto apprezzate. – mezamorphic

Problemi correlati