2011-08-23 15 views
5

Basit bir hesaplama yapmaya çalışıyorum (Math.random() 10000000 kez çağırır). Şaşırtıcı bir şekilde basit bir yöntemle çalıştırmak, ExecutorService'i kullanmanın çok daha hızlı bir şekilde gerçekleştirilmesini sağlar.ExecutorService yavaş çoklu iş parçacığı performansı

Ben ExecutorService's surprising performance break-even point --- rules of thumb? başka konuyu okumuş ve Callable kullanarak toplu yürüterek cevap takip etmeye çalıştı, ancak performans benim şimdiki koduna göre hala

nasıl performansını artırmak do kötü mü?

import java.util.*; 
import java.util.concurrent.*; 

public class MainTest { 
    public static void main(String[]args) throws Exception { 
     new MainTest().start();; 
    } 

    final List<Worker> workermulti = new ArrayList<Worker>(); 
    final List<Worker> workersingle = new ArrayList<Worker>(); 
    final int count=10000000; 

    public void start() throws Exception { 
     int n=2; 

     workersingle.add(new Worker(1)); 
     for (int i=0;i<n;i++) { 
      // worker will only do count/n job 
      workermulti.add(new Worker(n)); 
     } 

     ExecutorService serviceSingle = Executors.newSingleThreadExecutor(); 
     ExecutorService serviceMulti = Executors.newFixedThreadPool(n); 
     long s,e; 
     int tests=10; 
     List<Long> simple = new ArrayList<Long>(); 
     List<Long> single = new ArrayList<Long>(); 
     List<Long> multi = new ArrayList<Long>(); 

     for (int i=0;i<tests;i++) { 
      // simple 
      s = System.currentTimeMillis(); 
      simple(); 
      e = System.currentTimeMillis(); 
      simple.add(e-s); 

      // single thread 
      s = System.currentTimeMillis(); 
       serviceSingle.invokeAll(workersingle); // single thread 
      e = System.currentTimeMillis(); 
      single.add(e-s); 

      // multi thread 
      s = System.currentTimeMillis(); 
       serviceMulti.invokeAll(workermulti); 
      e = System.currentTimeMillis(); 
      multi.add(e-s); 
     } 
     long avgSimple=sum(simple)/tests; 
     long avgSingle=sum(single)/tests; 
     long avgMulti=sum(multi)/tests; 
     System.out.println("Average simple: "+avgSimple+" ms"); 
     System.out.println("Average single thread: "+avgSingle+" ms"); 
     System.out.println("Average multi thread: "+avgMulti+" ms"); 

     serviceSingle.shutdown(); 
     serviceMulti.shutdown(); 
    } 

    long sum(List<Long> list) { 
     long sum=0; 
     for (long l : list) { 
      sum+=l; 
     } 
     return sum; 
    } 

    private void simple() { 
     for (int i=0;i<count;i++){ 
      Math.random(); 
     } 
    } 

    class Worker implements Callable<Void> { 
     int n; 

     public Worker(int n) { 
      this.n=n; 
     } 

     @Override 
     public Void call() throws Exception { 
      // divide count with n to perform batch execution 
      for (int i=0;i<(count/n);i++) { 
       Math.random(); 
      } 
      return null; 
     } 
    } 
} 

Bu kod

Average simple: 920 ms 
Average single thread: 1034 ms 
Average multi thread: 1393 ms 

düzenleme için çıkış: Her bir iplik için yeni rasgele nesne ile Math.random() değiştirdikten sonra performans nedeniyle Math.random (yaşamaya) olan senkronize bir yöntem .. performans (her iplik için rasgele ile Math.random() yerleştirildikten sonra)

yeni kodu için çıkış

geliştirilmiş

Average simple: 928 ms 
Average single thread: 1046 ms 
Average multi thread: 642 ms 

cevap

12

Math.random() senkronize edildi. Senkronize edilen bütün noktaların bir şeyleri yavaşlatmak, böylece çarpışmamaktır. Yeni bir Random gibi, senkronize edilmeyen bir şey kullanın ve/veya her bir iş parçacığının kendi nesnesiyle birlikte çalışmasını sağlayın.

+0

Ah Haklısınız! Math.random() 'ın senkronize olduğunu farketmedim. Her bir İşçi için yeni bir Rastgele nesne koyduğumda, performans büyük ölçüde iyileşti – GantengX

+0

Sadece hızlı bir soru, eğer Rastgele nesneyi paylaşmaya çalışırsam, performans hala devam ediyor. Bunun neden böyle olduğunu biliyor musun? Random.nextDouble senkronize edilmez ve AtomicLong.compareAndSet olarak adlandırılan Random.next (int) işlevini çağırır.Bunun neden performansın – GantengX

+4

performansını etkileyeceğini anlamıyorum, çünkü aynı kaynağa tekrar birden çok ileti dizisine sahip olmanız gerekiyor: AtomicLong bu durumda. Tek bir iş parçacığı değerini her seferinde güncelleyebilir ve her çağrı için nextDouble() öğesine iki kez güncellenir. –

3

Diğer iş parçacığının içeriğini okumak için iyi yaparsınız. Orada çok iyi ipuçları var.

Belki de sizin ölçütünüzle ilgili en önemli konu, Math.random() sözleşmesine göre, "Bu yöntem, birden fazla iş parçacığı tarafından doğru şekilde kullanılmasına izin vermek için düzgün şekilde eşitlenir. Ancak, çok sayıda iş parçacığının sözde sayıları oluşturması gerekiyorsa büyük bir oranda, her bir iş parçacığının kendi pseudorandom-sayı üretecine sahip olması için çekişmeyi azaltabilir "

Bunu şu şekilde okuyun: yöntem senkronize edilir, bu nedenle yalnızca bir iş parçacığının aynı anda yararlı bir şekilde kullanması olasıdır zaman. Öyleyse, görevleri dağıtmak için bir seri yük yapıyorsunuz, sadece seri olarak çalışmaya zorlamak için.

1

Birden çok iş parçacığı kullandığınızda, ek iş parçacığı kullanmanın ek yükünün farkında olmanız gerekir. Ayrıca, algoritmanızın paralel olarak önceden oluşturulup oluşturulmadığı işlerin olup olmadığını belirlemeniz gerekir. Bu nedenle, eşzamanlı olarak çalıştırılabilen ve çok sayıda iş parçacığı kullanmanın yükünü aşacak kadar büyük bir çalışma yapmanız gerekir.

Bu durumda, en basit çözüm, her iş parçacığında ayrı bir Rastgele kullanmaktır. Sahip olduğunuz problem, mikro ölçüt olarak, döngüsünüzün aslında hiçbir şey yapmamasıdır ve JIT, hiçbir şey yapmayan kod atmada çok iyidir. Bunun için bir geçici çözüm rastgele sonuçları toplayıp call()'dan döndürerek, JIT'in kodu atmasını önlemek için yeterlidir.

Son olarak, çok sayıda rakamı toplamak istiyorsanız, bunları kaydetmeniz ve daha sonra özetlemeniz gerekmez. Onları gittiğin gibi toplayabilirsin.

İlgili konular