조리법:IProgressMonitor 퍼포먼스 개선하기

위클립스
이동: 둘러보기, 찾기

Job을 이용하여 작업을 처리할 때, IProgressMonitor를 이용하면, 사용자에게 작업의 진척도를 알려주고, 취소 요청을 받을 수 있다. 그러나, 작업 진행에 영향을 미치는 단위가 아주 작고 개수가 많은 경우, IProgressMonitor에 접근할 때 마다, UI동기화가 일어나, 작업의 속도가 크게 저하 될 수 있다.

목차

[편집] 예시

monitor.beginTask("테스트", 1000000);
for(int i=0; i<1000000; i++){
    monitor.subTask(i + "번째 작업 중...");
    monitor.worked(1);
}
monitor.done();

위의 코드를 모니터 없이 수행하면 0.1초도 걸리지 않고 끝나지만(아무일도 하지 않으니까), 작업 진척도 표시기가 켜진 상태에서는, Progress UI를 100만번이나 업데이트하고 동기화 하기 때문에 굉장히 느려진다.

[편집] 기초 전략

사람의 눈은 IProgressMonitor에 표시되는 정보가 1초에 30번 이상 변경되더라도, 인간은 그 정보를 볼 수 없다. 따라서, 실제 IProgressMonitor를 초당 30번만 업데이트하고, 그 외에는 단순히 정보를 보존만 해두는 델리게이트 IProgressMonitor를 고안하여, 성능향상을 얻을 수 있다.

[편집] 델리게이트 Progress Monitor 소스 코드

  1. /**
  2.  * @author Jeeeyul 2011. 7. 11. 오후 3:17:12
  3.  * @since 1.8
  4.  */
  5. public class DeferredProgressMonitor implements IProgressMonitor, Runnable {
  6.     private IProgressMonitor actualMonitor;
  7.     private ILock lock = Job.getJobManager().newLock();
  8.     private boolean isDisposed = false;
  9.     private int worked = 0;
  10.  
  11.     public DeferredProgressMonitor(IProgressMonitor actualMonitor) {
  12.         super();
  13.         this.actualMonitor = actualMonitor;
  14.         new Thread(this).start();
  15.     }
  16.  
  17.     // 다른 위임 메서드들은 생략 됨.
  18.  
  19.     @Override
  20.     public void worked(int work) {
  21.         this.lock.acquire();
  22.         this.worked += work;
  23.         this.lock.release();
  24.     }
  25.  
  26.     @Override
  27.     public void run() {
  28.         while (!this.isDisposed) {
  29.             try {
  30.                 Thread.sleep(50);
  31.                 update();
  32.             } catch (InterruptedException e) {
  33.                 e.printStackTrace();
  34.             }
  35.         }
  36.     }
  37.  
  38.     private void update() {
  39.         this.lock.acquire();
  40.  
  41.         if (this.worked > 0) {
  42.             this.actualMonitor.worked(this.worked);
  43.             this.worked = 0;
  44.         }
  45.  
  46.         // 다른 업데이트 코드는 생략됨...
  47.  
  48.         this.lock.release();
  49.     }
  50.  
  51.     public void dispose() {
  52.         this.isDisposed = true;
  53.     }
  54. }

13에서는 랩핑(Wrapping)할 실제 IProgressMonitor 를 인자로 받는 생성자를 선언하였다. 이 생성자는 14특정 간격으로 실제 모니터를 업데이트하는 스레드를 시작시킨다. 이 스레드는 주기적으로 38의 update()메소드를 호출한다.

이 클래스의 핵심전략은 worked(int)등과 같이 20 원래의 IProgressMonitor의 상태를 변형시키는 코드를 즉시 반영하지 않고, 22 단순히 값을 보존만 해 두었다가, 30 정해진 시간 간격으로 실제로 업데이트 하는 것이다.

다음은 이 모니터를 사용하는 Job의 코드이다:

@Override
protected IStatus run(IProgressMonitor monitor) {
    // 주어진 monitor를 DeferredProgressMonitor로 감싼다.
    DeferredProgressMonitor dMonitor = new DeferredProgressMonitor(monitor);
    try{
       // dMonitor를 이용해 작업을 수행하는 코드... 
    }finally{
        dMonitor.dispose();
    }
    return Status.OK_STATUS;
}

[편집] 결론

이렇게, 주어진 요청들을 모아두었다가, 적절한 시점에 한 번씩만 처리하는 기법을 Deferred Update 매커니즘[1]이라고 한다. 이 모니터를 이용하는 클라이언트는 worked()를 필요한 만큼 아무리 많이 호출하더라도, 모니터의 진행상태를 표시중인 UI와 동기화하기 위해 불필요하게 느려지지 않게 된다.

[편집] 참조

  1. 특집기사:Deferred Update 매커니즘을 통한 성능향상

이 기사에 대한 의견은 토론 페이지를 통해 나눌 수 있습니다.

개인 도구
이름공간
변수
행위
포탈
탐색
도움
도구모음