이 블로그 검색

2011년 3월 25일 금요일

Intro to Grand Central Dispatch, Part II: Multi-Core Performance 요약



Grand Central Dispatch, Part II: Multi-Core Performance 요약
dispatch 큐는 worker 쓰레드풀을 추상화 한다

GCD를 통해서 멀티코어 퍼포먼스를 뽑아낼수있는 2가지 방법

1.하나의 태스크를 병행처리

2.연관된 여러 작업들을 하나의 글로벌큐에, 관련없거나
느슨하게 관련된 작업들은 여러개의 커스텀 큐를 이용해 병행처리.

1
2
for(id obj in array)
 [self doSomethingIntensiveWith:obj];
Pastie #2500401 linked directly from Pastie.

doSomethingIntensiveWith 가 병행처리 가능한 상황이면 GCD를 이용해서
다음처럼 변경 할수있다.

1
2
3
4
5
6
dispatch_queue_t queue
 =  dispatch_get_global_queue
  (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

for(id obj in array)
    dispatch_async(queue, ^{[self doSomethingIntensiveWith:obj]; });
Pastie #2500404 linked directly from Pastie.

그런데 만약 하나의 처리결과를 다른것이 참조하는
다음과 같은 상황이면

1
2
3
4
5
for(id obj in array){
 [self doSomethingIntensiveWith:obj];
}

[self doSomethingWith:array];
Pastie #2500408 linked directly from Pastie.
위에서의 dispatch_async 은 사용할수없다.
이문제를 해결하기 위한 방법중 하나는 dispatch groups을 사용하는것이다.
dispatch groups은 여러개의 block들을 하나의 그룹으로 만들고,
block들이 완료 되기를 기다리거나 완료시 통보를 받기 위한 방법이다.

사용법
1. dispatch_group_create 를 이용 생성
2. dispatch_group_async 를 이용, block을 dispatch하고 동시에 그룹에 추가한다.

그룹을 이용해서 위 코드를 변경하면 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
dispatch_queue_t queue
  = dispatch_get_global_qeueue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 
dispatch_group_t group = dispatch_group_create();

for(id obj in array)
  dispatch_group_async(group, queue, ^{[self doSomethingIntensiveWith:obj];});
    
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(group);
    
[self doSomethingWith:array];
Pastie #2500414 linked directly from Pastie.

만약 이 작업이 비동기적으로 수행되어도 된다면 dispatch_group_async 를 이용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
dispatch_queue_t queue
  = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_group_t group = dispatch_group_create();

for(id obj in array)
{
  dispatch_group_async(group, queue,
    ^{ [self doSomethingIntensiveWith:obj]; });
}

//그룹이 완료되면 호출될 block을 지정
dispatch_group_notify(group, queue, ^{ [self doSomethingWith:array]; });

dispatch_release(group);
Pastie #2500419 linked directly from Pastie.
최종 결과를 가지고 처리해야 할 작업(doSomethingWith)도 백그라운로 처리된다.

만약 doSomethingWith이 main thread에서 처리되어야 한다면 (GUI변경등),
dispatch_group_notify에 global queue가 아닌 main queue 를 전달하면 된다.

GCD에서는 동기적() 경우를 편리하게 하기위한 dispatch_apply 함수를 제공한다.
이함수는 하나의 block을 병행적으로 여러번 호출하면서 완료되기를 기다린다.

1
2
3
4
5
6
7
8
9
dispatch_queue_t queue
  = dispatch_get_global_qeueue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_apply([array count], queue,
 ^(size_t index){ [self doSomethingIntensiveWith:
          [array objectAtIndex:index]];
});
//완료되었다.
[self doSomethingWith:array];
Pastie #2500421 linked directly from Pastie.

그렇다면 비동기적인 처리는 어떠한가?
dispatch_apply 의 비동기 버전은 존재하지 않는다. 하지만
dispatch_async 함수를 이용하여, 모든 작업을 백그라운드 작업(비동기)으로
만들 수 있다.

1
2
3
4
5
6
7
8
9
10
11
dispatch_queue_t queue
  = dispatch_get_global_qeueue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue,
 ^{
        dispatch_apply([array count], queue,
        ^(size_t index){ [self doSomethingIntensiveWith:
             [array objectAtIndex:index]];
        });
        [self doSomethingWith:array];
    });
Pastie #2500424 linked directly from Pastie.

이러한 작업에서 중요한점은, 동시에 여러개의 데이터들에 대해서
동일한 작업을 하는 코드를 구분해내고, 또한 그작업이 thread safe하게
병행처리 될수 있는지 확인 하는 것이다.

이조건이 만족되면 GCD를 이용, 병행 처리가 가능하다. 성능 관련해서 GCD가
쓰레드보다 경량, 부하가 작다고는 하지만, block이 복사되어 큐에 들어가고,
적합한 작업 쓰레드가 처리하는 과정등.. 큐에 block을 제공 하는것은 여전히 비용이
드는 일이다.

예를 들어 어떤 이미지의 모든 픽셀처리를 위한 block을 큐에 제출 하는 것 은
별 도움이 되지 않을 것 이다. 하지만 여러 이미지들을 전환하기 위해 하나의
이미지 처리를 위한 block을 큐로 제출 하는 것 은 좋은 활용 방안이다.

GCD사용이 이득인지 아닌지 의심스러우면, 실험이 필요하다.
어플리케이션 병행처리란 즉 최적화란 의미이다.
실험 전후를 측정해서 병행 처리의 이득을 보고 있는지 꼭 확인해야 한다.

온전한 내용을 원하신다면 원문을 참조하기 바랍니다...

댓글 없음:

댓글 쓰기