이 블로그 검색

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

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

2011년 3월 24일 목요일

Intro to Grand Central Dispatch, Part I: Basics and Dispatch Queues 정리


참고 : www.mikeash.com

향후 아이패드2나 아이폰5 등 멀티코어 환경에서 개발을 하려는 사람들에게는
꼭 알아두어야 할 기술이 바로 이 GCD 가 아닐까 한다.

* 2011-10-20에 추가됨
이번에 IOS5 가 나오면서 GCD 에 변경된 부분이 있다.
간추려보면
1. 새로운 global queue 추가(DISPATCH_QUEUE_PRIORITY_BACKGROUND)
2. custom concurrent queue
3. Dispatch IO
자세한 사항은 여기를 참조

1. GCD (Grand Central Dispatch)

concurrent programming을 위한 저수준 C API.
작업을 분리하고 work queues에 저장후, 동시에 혹은 하나씩 작업을 처리.
(NSOperationQueue와 비슷하나 좀더 저수준이고 높은 성능을 보인다고 한다).

dispatch object =  GCD 는 객체지향 개념으로 만들어졌다. GCD 객체를 일컬음.

메모리 관리 필요 : dispatch_retain, dispatch_release

Dispatch Queues: 처리할 작업을 받아들이기 위한 객체. concurrent / serial 이 존재.
동시에 작업을 처리(시스템 부하를 고려하여)하거나 하나씩 작업을 처리한다.

3가지 큐 존재

Main queue: serial queue.
메인 쓰레드가 처리하게 된다.
dispatch_get_main_queue() 호출로 얻을수있다. 

Global queues: concurrent queues
전체 프로세스가 공유하며, 3종류 존재
(high,default,low priority queue).
동시에 작업을 수행한다.
dispatch_get_global_queue() 호출로 접근가능 (priority 지정필요).

Custom queues: serial queue.
한번에 하나씩 작업을 처리한다.
dispatch_queue_create()로 생성된다.

2. 사용법
  1) 큐를 생성 (위의 함수 호출 이용)
custom queue 경우 다음처럼 호출.

1
dispatch_queue_create("com.yourcompany.subsystem.task", NULL);
Pastie #2500485 linked directly from Pastie.
 
  2) 처리할 작업을 전달한다.
       
1
2
3
4
5
6
dispatch_async(dispatch_get_global_queue
  (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
 ^{
  [self goDoSomethingLongAndInvolved];
  NSLog(@"Done doing something long and involved");
 });
Pastie #2500430 linked directly from Pastie.
dispatch_async 함수는 즉시 리턴되며, 작업은 비동기적으로
백그라운드에서 수행된다.

* 중첩된 dispatche.
시간이 걸리는 작업 종료후 UI변경등이 필요한 경우
UI처리는 main thread 에서 수행되어야 하므로 중첩된 호출을 통해 가능함.
       
1
2
3
4
5
6
7
8
9
dispatch_async(dispatch_get_global_queue
  (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
 [self goDoSomethingLongAndInvolved];
     dispatch_async(dispatch_get_main_queue(),
     ^{
            [textField setStringValue:@"Done doing something long and involved"];
        });
});
Pastie #2500436 linked directly from Pastie.
처리할 작업(block)이 종료될때까지 기다리는 경우 dispatch_sync 사용.
__block 변수 : 살행중인 block으로부터 결과를 받을수 있음. 만약 백그라운드
작업중인 쓰레드에서 GUI 컴트롤 값을 필요로 하는 경우 다음처럼 사용가능.
       
1
2
3
4
5
6
7
8
9
10
11
12
13
//백그라운드 쓰레드내에서의 소스.
//현재 이쓰레드가 백그라운드로 작업중이라고 가정.
__block NSString *stringValue;
  
dispatch_sync(dispatch_get_main_queue(),
^{
        // __block 변수는 자동으로 retain 되지 않음.
        // reference 를 확실하게 만들어서 관리한다.
        stringValue = [[textField stringValue] copy];
});
  
[stringValue autorelease];  
// 그리고 stringValue를 백그라운드 쓰레드에서 사용한다.
Pastie #2500450 linked directly from Pastie.

쓰레드등을 이용한 백그라운드 프로세싱을 중첩된 block을 이용하여 아래처럼
교체가능하다.

1
2
3
4
5
6
7
8
9
dispatch_queue_t bgQueue = myQueue; //global 혹은 custom 큐.

dispatch_async(dispatch_get_main_queue(), ^{
    NSString *stringValue = [[[textField stringValue] copy] autorelease];
    dispatch_async(bgQueue, ^{
            // 이부분에 들어가는 소스는 백그라운드로 수행되며
            // stringValue 를 사용할수 있다.
    });
});
Pastie #2500459 linked directly from Pastie.
 3) Lock을 대체하기 위해서 이렇게도 사용 가능하다.
      일반적인 경우가 다음과 같다면,
   
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
NSLock *lock;  
//getter
- (id)something
{
    id localSomething;
    [lock lock];
    localSomething = [[something retain] autorelease];
    [lock unlock];
    return localSomething;
}
//setter
- (void)setSomething:(id)newSomething
{
    [lock lock];
    if(newSomething != something)
    {
        [something release];
        something = [newSomething retain];
        [self updateSomethingCaches];
    }
    [lock unlock];
}
Pastie #2500472 linked directly from Pastie.
   
     다음처럼 바꿀수 있다.
     이때 큐는 custom (즉 serial)큐만 가능하다. 동기 /비동기 처리를
활용하는것임. 즉 , 비동기(dispatch_async)로 동기화가 필요한 함수를
호출하면  백그라운드에서는 dispatch_sync로 처리되어 serial하게
작업을 처리한다.
   
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (id)something
{
    __block id localSomething;
    dispatch_sync(queue, ^{
         localSomething = [something retain];
    });
    return [localSomething autorelease];
}
    
- (void)setSomething:(id)newSomething
{
    dispatch_async(queue, ^{
        if(newSomething != something)
        {
            [something release];
            something = [newSomething retain];
            [self updateSomethingCaches];
        }
    });
}
Pastie #2500480 linked directly from Pastie.
        
dispatch queue 사용이 매우 가벼워서 lock을 거는 빈도가 많아도 무방함. 


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

2010년 12월 1일 수요일

Proximity Pushup Counter

App store link












Welcome, please leave your comments and suggestions here.

2010년 8월 30일 월요일

UIImagePicker Memory Warnning viewDidUnload

UIImagePicker 를 이용해서 사진을 찍어서 일기장 어플에 삽입시키는 로직을 구현중에 골치아픈(그리고 구글에서도 해답을 속시원하게 찾을수없는) 현상을 겪게 되었다. 앱하나만을 띄워놓고 실행시에는 잘 나타나지 않지만 다른 앱이 여러개 떠있는 상태에서, UIImagePicker 로 사진을 촬영시엔 거의 100% 메모리 부족 경고를 받게되고, 모든 뷰들이 초기화 되는 현상이다. 구글에서 조언을 얻어, 사진 사이즈를 줄여도보고 했지만.. 이 메모리 부족 현상을 완전히 없앨수는 없었다. 심지어 누군가는 이미지피커 사용하면 viewDidUnload 호출이 먼저 발생 한다고 설명하는 내용도 봤다. 항상 메모리 부족경고를 받는 프로그램에서는 항상 viewDidUnload 가 호출되니 그렇게 판단한걸로 보여진다. 정상적인 경우라면 호출되지 않는다. 다른 앱도 분명히 이런 메모리 부족을 겪었을건데라고 의심하면서 비슷한 앱들을 실행해서 메모리 부족이 일어나게끔 환경을 맞춰주고 사진을 촬영했는데, 처리를 제대로 하는 앱도있고 그렇지 않은 것들도 있었다. 그렇다면 뭔가 해결할 방법은 존재하는것 같다.

삽질끝에 얻은 나름대로의 방안은 이런식이다.

Memory Warnning 발생시 사용중이던 데이터들을 백업하고 viewDidUnload 호출되고 이후 뷰들이 초기화 되는과정에서 flag처리와 임시로 보관중인 데이터를 복원해서 마치 아무일 없었던거처럼 처리하게 하는것.

물론 아주 성가시고 지저분한 방법이다... 다른방법은 뭐가있을련지..

결론:


메모리 부족 경고와 함께 viewDidUnload 가 발생하며,

이런 상황에서 해결방법은 데이터를 백업하고 뷰 초기화시 데이터를 복원하는것이다.
UIImagePicker 사용시 가끔 발생할수 있는 메모리 부족을 막을 방법은 없다.

2009년 10월 20일 화요일

Member Function Pointers 활용


어떤 변수의 값에 따라서 클래스 멤버함수를 각각 호출해야하는 

상황이라면, 아마 다음처럼 코딩을 하게 될것이다.

SomeClass::SomeMethod( int Somevalue  )
{
    ...
    if( 0 == Somevalue )
    {
        memFunc_01( SomeArg ) ; // 입력인자 SomeArg 로 멤버함수1 호출
    }
    else if( 1 == Somevalue )
    {
        memFunc_02( SomeArg ) ; // 입력인자 SomeArg 로 멤버함수2 호출
    }
    ... 생략
    else if( 9 == Somevalue )
    {
memFunc_10( SomeArg ) ; // 입력인자 SomeArg 로 멤버함수10 호출
    }
    ...
}

혹은 다른 방법(switch..case)을 이용할수 있다.

하지만 클래스 멤버 함수 포인터를 활용하면, 계속 반복되는 비교문을 없애고

효율적인 코드 작성이 가능하다.

SomeClass::SomeMethod(int Somevalue )
{    ...
    // fPAryMemFunc 라는 멤버함수 포인터 배열을 선언한다.
    // 사용시에 1번만 초기화 된다

    static BOOL( SomeClass::* fPAryMemFunc[] )( SomeArg ) =
    {
        & SomeClass:: memFunc_01,
        & SomeClass:: memFunc_02,
        & SomeClass:: memFunc_03,
        ...
        & SomeClass:: memFunc_10
    }

    //실제 멤버 함수 호출시에는 다음처럼 한다.

    (this->* fPAryMemFunc[ Somevalue ]) (SomeArg );    
}

이걸 채팅서버에 적용해봤는데 , 클라이언트의 요청이 들어오면

헤더의 사용목적을 보고 일일이 구분 처리하는게 아니고

헤더의 메시지 용도에 배열의 인덱스를 저장하게 하고

바로 해당 인덱스의 멤버 함수를 호출하게 하는것이다.