2012-04-05 5 views
9

Niedawno przeprowadziłem testy porównawcze dla języka Java w wersji C#, aby zaplanować 1000 zadań nad pulpitem. Serwer ma 4 fizyczne procesory, każdy z 8 rdzeniami. System operacyjny to Server 2008, ma 32 GB pamięci, a każdy procesor to Xeon x7550 Westmere/Nehalem-C.Java vs C# Wielowątkowość wydajność, dlaczego Java się wolniej? (Zawiera wykresy i pełny kod)

Krótko mówiąc, implementacja Java jest znacznie szybsza niż C# w 4 wątkach, ale znacznie wolniejsza w miarę wzrostu liczby wątków. Wydaje się również, że C# stał się szybszy w iteracji, gdy liczba wątków wzrosła. Wykresy są zawarte w tym poście:

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

Wdrożenie Java zostało napisane na 64bit Hotspot JVM, Java 7 i korzystania z usługi Executor pula wątków znalazłem w Internecie (patrz niżej). Ustawiłem również JVM na współbieżną GC.

C# został napisany na .NET 3.5 i pula wątków pochodzi codeproject: http://www.codeproject.com/Articles/7933/Smart-Thread-Pool

(mam włączone poniższy kod).

Moje pytania:

1) Dlaczego Java coraz wolniej, ale C# jest coraz szybsze?

2) Dlaczego czasy wykonania C# zmieniają się znacznie? (To jest nasze główne pytanie)

Zrobiliśmy zastanawiam czy C# fluktuacja została spowodowana przez magistralę pamięci jest maxed ....

Code (Proszę nie podkreślają błędy z blokadą, to nie ma znaczenia z moim ma):

Javy

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

nie można zauważyć, że w Javie pętla tworzy 'PrintStream' w każdej iteracji natomiast w' C# 'otwarciu "StreamWriter" tylko raz? Nie sądzę, że wyjaśnia to zjawisko, ale twój test nie jest w 100% dokładny na poziomie podstawowym. – RonK

+0

Czy wypróbowałeś klasę ThreadPoolExecutor? ThreadPoolExecutor ma zapewnić lepszą wydajność dla dużej liczby zadań asynchronicznych. – ChadNC

+0

@ ChanNC- dzięki. Spróbuję tego. – mezamorphic

Odpowiedz

5

Y Wydaje się, że nie testujesz pracy ramki wątków tak samo, jak testujesz, jak język optymalizuje niezoptymalizowany kod.

Java jest szczególnie dobra w optymalizacji kodu bezcelowego, co, jak sądzę, wyjaśniłoby różnicę w językach. Ponieważ liczba nitek rośnie, podejrzewam, że szyjka butelek przesuwa się do tego, co robi GC, lub jakaś inna przyczyna towarzysząca twojemu testowi.

Java może również spowalniać, ponieważ domyślnie nie jest NUMA. Spróbuj uruchomić: -XX:+UseNUMA Jednak proponuję maksymalną wydajność, dlatego powinieneś starać się, aby każdy proces był w jednym regionie numa, aby uniknąć narzutu z powodu przekroczenia wartości numa.

Można również spróbować nieco zoptymalizować kod, który wynosił 40% szybko na moim komputerze

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 Właśnie wypróbowałem twoje kodowane rzeczy! Czy mógłbyś wyjaśnić, dlaczego zmiany wprowadziły taką różnicę? Przepraszam, że cię obciążam ... Czy wiesz też, dlaczego kod C# tak bardzo się waha? Jest to jeden z naszych największych zmartwień, ponieważ cała nasza biblioteka jest napisana w języku C#. – mezamorphic

+2

Podczas korzystania z większej liczby wątków, zasoby z pojedynczym wątkiem stają się coraz bardziej krytyczne. W tym przypadku GC jest udostępnionym zasobem. W powyższym przykładzie przetwarzam moje bufory, które nie tylko powodują, że jest szybszy, ale zmniejsza jitter (zatrzymuje się na czyszczenie) Nigdy nie dotknąłem C# (w jakimkolwiek stopniu się do tego przyznaję), ale zakładam, że to samo podejście, które działa w Javie, jest do cpu i pamięci profilować swoją aplikację. Upewnij się, że masz narzędzie, któremu możesz zaufać. VisualVM nie jest świetnym przykładem (ale jest darmowy;) –

+0

Hej Peter, wykreśliłem 40% przyspieszenia i dla pierwszych 25 iteracji java zajmuje około 2450 ms, ale potem dla reszty 975 iteracji potrzeba około 450ms- czy to jest spowodowane GC i czy są jakieś optymalizacje, których mogłabym użyć, aby uniknąć tego początkowego wąskiego gardła? Dziękuję - twoje odpowiedzi są bardzo doceniane. – mezamorphic