2012-11-30 3 views
0

Java에서 Executor 서비스를 시험해보고 피보나치를 실행할 다음 코드를 작성했습니다 (예, 대용량의 재귀 버전 만 실행 프로그램 서비스를 강조합니다).Java Executor 서비스의 피보나치가 병렬보다 더 빠르게 순차적으로 실행됩니다.

놀랍게도 nThreads를 1로 설정하면 더 빨리 실행됩니다. 이는 Executor 서비스에 제출 된 각 "태스크"의 크기가 실제로 작음과 관련이 있습니다. 그러나 nThreads를 1로 설정 한 경우에도 여전히 동일한 숫자 여야합니다.

공유 된 Atomic 변수에 대한 액세스로 인해이 문제가 발생할 수 있는지 확인하려면 "텍스트 참조"라는 주석이있는 세 줄을 주석으로 처리하고 시스템 모니터를보고 실행이 얼마나 오래 걸리는 지 확인합니다. 그러나 결과는 같습니다.

왜 이런 일이 발생하는지 알고 싶습니다.

나는 BT와 비슷한 구현 방식과 비교하고 싶었다. F/J 구현보다 느린 것으로 나타났습니다.

public class MainSimpler { 
    static int N=35; 
    static AtomicInteger result = new AtomicInteger(0), pendingTasks = new AtomicInteger(1); 
    static ExecutorService executor; 

    public static void main(String[] args) { 
     int nThreads=2; 
     System.out.println("Number of threads = "+nThreads); 
     executor = Executors.newFixedThreadPool(nThreads); 
     Executable.inQueue = new AtomicInteger(nThreads); 
     long before = System.currentTimeMillis(); 
     System.out.println("Fibonacci "+N+" is ... "); 
     executor.submit(new FibSimpler(N)); 
     waitToFinish(); 
     System.out.println(result.get()); 
     long after = System.currentTimeMillis();   
     System.out.println("Duration: " + (after - before) + " milliseconds\n"); 
    } 

    private static void waitToFinish() { 
     while (0 < pendingTasks.get()){ 
      try { 
       Thread.sleep(1000); 
      } catch (InterruptedException e) { 
       e.printStackTrace(); 
      } 
     } 
     executor.shutdown(); 
    } 
} 



class FibSimpler implements Runnable { 
    int N; 
    FibSimpler (int n) { N=n; } 

    @Override 
    public void run() { 
     compute(); 
     MainSimpler.pendingTasks.decrementAndGet(); // see text 
    } 

    void compute() { 
     int n = N; 
     if (n <= 1) { 
      MainSimpler.result.addAndGet(n); // see text 
      return; 
     } 
     MainSimpler.executor.submit(new FibSimpler(n-1)); 
     MainSimpler.pendingTasks.incrementAndGet(); // see text 
     N = n-2; 
     compute(); // similar to the F/J counterpart 
    } 
} 

런타임 (약) :

  • 1 실 : 11초
  • 2 스레드 : 19초
  • 4 개 스레드 : 19초

업데이트 : I 통지 집행 서비스 내에서 하나의 스레드를 사용하더라도 전체 프로그램은 내 컴퓨터의 4 개 코어를 모두 사용합니다 (각 코어는 평균 약 80 % 사용량). 이것은 Executor 서비스 내에서 더 많은 쓰레드를 사용하면 전체 프로세스가 느려지는 이유를 설명 할 수 있습니다.하지만 이제 Executor 서비스 내에서 단 하나의 쓰레드가 활성화되어 있다면이 프로그램은 4 코어를 사용합니까?

+0

[관련 질문] [1]에서 말했듯이 가비지 수집과 관련이 있다고 생각합니다. 는 [1] : http://stackoverflow.com/questions/13645428/why-does-a-singlethreaded-executor-service-use-4-cores – Mahdi

답변

2

이 실행자 서비스에 제출 한 각 "태스크"의 크기가 실제로 작음과 관련이있을 수 있습니다.

확실히 그렇습니다. 따라서 결과적으로 컨텍스트 스위칭의 오버 헤드를 주로 측정하게됩니다. n == 1 일 때 컨텍스트 스위칭이 없으므로 성능이 향상됩니다.

하지만 난 당신이 여기에 '이상 1'을 의미 같은데요 1.

에 nThreads을 설정하면 여전히 또한 같은 수 있어야합니다.

과중한 잠금 경합 문제가 발생합니다. 스레드가 여러 개인 경우 result의 잠금이 항상 경쟁합니다. 쓰레드는 result을 업데이트하기 전에 서로를 기다려야하고 속도가 느려집니다. 단일 스레드 만있는 경우 JVM은이를 감지하고 실제로 잠금을 수행하지 않는 잠금 제거를 수행합니다.

당신은 N 작업으로 문제를 분할하지 않을 경우 더 나은 성능을 얻을 수 있지만, 오히려 스레드를 동시에 처리 할 수 ​​ N/nThreads 작업으로 나눌 수 있습니다

(당신은 신체의 대부분의 수에있을 nThreads을 선택합니다 가정 코어/스레드 가능). 그런 다음 각 스레드는 자체 작업을 수행하여 자체 총계를 계산하고 스레드가 완료 될 때이를 총합계에 추가합니다.그렇더라도 fib(35)의 경우 스레드 관리 비용이 이점보다 클 것으로 예상됩니다. 아마도 fib(1000)을 시도해보십시오.

+0

지금 본문에 언급, 나는 테스트 공유 변수에 대한 액세스를 제거하여 이론. 런타임은 변경되지 않습니다 (이제 런타임을보기 위해 시스템 모니터를 봅니다). – Mahdi

+0

N/nThreads 작업 사용에 관해서, 나는 당신이 말하는 것에 동의합니다. 그러나 일반적으로 그러한 해결책은 쉽지 않을 수 있습니다. 그래서 실제로 많은 작은 작업이 왜 불리한 결과를 초래하는지 더 잘 이해하고 싶습니다! – Mahdi

관련 문제