인텔 Parallel Advisor Lite by object

이전 글: 인텔 Parallel Studio + Parallel Advisor

2. Parallel Advisor Lite

Parallel Studio에는 Parallel Advisor Lite라는 애드 온이 제공된다. Parallel Studio가 병렬 코드 작성, 버그 찾기, 성능 튜닝이라면 Advisor는 병렬 코드 작성 자체를 도와주는 것이 그 궁극적인 목표가 되겠다. 그러나… 역시 Parallel Studio가 기존의 인텔 컴파일러, Thread Checker, VTune의 재탕이듯 Advisor 역시 Thread Checker의 완벽한 재탕이어서 아쉬운 면이 많긴 하다. 그럼에도 불구 역시 Visual Studio와 굉장히 잘 연동을 시켜 사용하기가 좋다는 점은 매우 훌륭하다. 이상하게 Advisor 제품은 ‘Lite’라는 접미사가 붙는데, 그래서 나는 그냥 Advisor나 Advisor Professional이라도 있는 줄 알았다. 사실, Parallel Advisor의 원래 목표는 더 큰 것이었는데 그게 잘 안되어서 일단 라이트라는 이름을 달고 베타적인 상태로 출시된 것 같다. 그래서 애드 온으로 제공되기도 하고.

Parallel Advisor를 깔면 간단한 툴바 하나가 생기고 4가지 작업을 할 수 있다. 그냥 고르고 Advise 버튼만 누르면 되니까 사용하기는 무척 편리하다. Parallel Advisor의 궁극적인 목표는 분명 병렬 코드를 쉽게 작성할 수 있도록 여러 측면에서 돕는 것인데, 일단 지금 나와있는 버전의 기능을 한줄로 요약하면 “직렬 코드를 병렬 코드임으로 가장하고 실행시켜 발생하는 문제를 알려주어 병렬화 과정을 돕는다”로 볼 수 있다. 그런데 이 기능은 이미 Thread Checker의 projection 모드의 그것과 사실 완전히 동일하다. 대신 Parallel Advisor에서는 쓰기가 굉장히 편해졌다. 일단 위 툴바에서 볼 수 있는 4가지 기능을 하나 하나 살펴보자.

 

1) Open Workflow: 어떻게 내 프로그램을 병렬화 할까요?

이 항목을 고르고 Advise를 뿅 누르면… 아마 정말로 이상적인 그림은 프로그램을 샥샥 분석해서 무언가 척 하고 알려주는 것이다. 그런데… 이건 아직까지 꿈속에서 나올 법한 이야기고 이 버튼을 누르면…

그냥 MSDN이 뜨지 말입니다…… 그러니까 문서 읽고 니가 잘 해보라는 소리.

웃기기도 하지만 반대로 그만큼 무언가 코드를 분석해 병렬 프로그래밍을 돕는 것 자체가 굉장히 어렵다는 사실의 방증이기도 하다. 그런데 여기에 뜨는 문서가 상당히 잘 작성되어 있어 병렬 프로그래밍을 공부하는 자료로서는 아주 좋다. 핵심 부분을 따로 캡처해서 올려 본다. 병렬화 하는 일반적인 순서도와 Advisor가 어떻게 도와주는지를 보여준다.

일반적으로 프로그램의 병렬화는 (1) 병렬화 할 대상을 찾기, (2) 어떻게 병렬화할지 머리 굴리기, (3) 병렬 코드 작성 삽질하기, (4) 제대로 돌아가는지, 버그는 있는지 살펴보기, (5) 성능 튜닝하기로 요약할 수 있다. 그런데 위 그림은 조금 이상한 스텝이 보인다: “Annotate tasks”, “Model correctness”가 있는데 좀 있다 설명한다. 이 과정은 사실 바로 앞에서 말한 병렬화 5단계 중, (2), (3)에 해당하는 내용이다.

 

2)  뜨거운 지점(…) 찾기: 도대체 어디를 병렬화 해야 할까요?

두 번째 기능. 과연 이 기능도 그냥 MSDN 띄어주고 배 쨀 것인가? 사실 내가 Parallel Advisor를 보게 된 것은 바로 이 기능이 어떻게 만들어졌는지 보기 위해서였다. 왜냐면 내가 근 2년 간 캐삽질 하고 있는 문제가 바로 이 문제, 즉 병렬화 가능한 부분을 어떻게 찾는가, 이기 때문이다.

만약 이 기능이 매우 이상적으로 돌아간다면 프로그램을 잘 분석해서(정적 분석이든 동적 분석이든)

  • (A) 병렬화 가능한 부분이 있다면 그 부분을 알려주고,
  • (B) 그러하지 않다면 왜 병렬화가 안 되는지 분석해 알려주고,
  • (C) 더 나가 이렇게 병렬화하라는 가이드까지 해줄 수 있는 것.

으로 요약할 수 있다. 지금까지 내가 (A), (B)를 좀 들여다 보았고 (C)도 좀 생각해봤는데 이거 쉽지 않은 문제이다. 일단 프로그램에서 병렬화 가능한 부분을 찾는 것은 굉장히 어려운 문제이다.

이 문제를 풀 수 있는 접근 방식은 여러 가지가 있는데, 일단 정적 분석으로 컴파일러가 자동으로 병렬화 해주는 기능(Automatic Parallelization)이 있긴 하다. 그런데 이거 해보면 포트란에서나 잘 돌아가고 포인터 있는 C/C++ 코드는 매우 쥐약이다. 포인터는 컴파일러 최적화의 아주 큰 장애물인데, 두 포인터가 가리키는 영역이 서로 겹치는지 아닌지를 모르면, 의존성을 파악할 수 없고, 컴파일러는 보수적으로 의존성을 가정해버려 최적화 기회를 많이 놓치게 된다. 자동 병렬화가 실패하는 가장 큰 이유도 포인터 때문이다. 그리고 복잡한 컨트롤 플로우나(재귀 호출, break, continue 등) 동적 정보의 부재(for 루프가 N번까지 도는데 이 N이 얼마인지 모름) 역시 실패의 주요 원인이다. 연구용 컴파일러는 더 잘 될지 모르겠지만, 자동 병렬화를 가장 잘 해주는 일반용 컴파일러인 인텔 컴파일러와 포틀랜드 컴파일러도 내가 실험해본 결과 거의 안 되었다. 최근에는 이러한 약점을 보완하고자 동적 프로파일링 정보를 이용해 자동 병렬화 기회를 더 찾자는 아이디어(슬라이드)도 있다.

Parallel Advisor는 동적 프로파일링(사실 프로파일링은 그 자체가 동적이기 때문에 굳이 동적, dynamic이라는 수식어가 필요 없다) 기반이므로 마치 프로파일러처럼 프로그램을 돌려서 (A), (B) 같은 문제를 푸는 것이다. 직관적으로 프로그램을 돌려서 데이터 의존성을 죄다 밝혀내면 병렬 가능한 부분을 찾을 수 있다. 그런데 아직까지 Parallel Advisor는 이것을 구현한 것은 아니다.

데이터 의존성 같은 가장 낮은 수준의 접근 방식 외에 다른 방법으로 병렬성을 동적으로 찾는 아이디어(슬라이드)도 있다. 대표적으로 교환성(commutativity)을 찾는 방법인데, 데이터 의존성보다 더 높은 수준의 병렬성을 찾을 수 있다. 교환성은 “a + b = b + a”를 말한다. 프로그램 코드에서 교환성이 있다는 이야기는…

1: hash_table.Insert(“김태희”, “010-123-4567”);
2: hash_table.Insert(“박경림”, “010-7654-321”);

위와 같은 코드에서 1, 2번 구문은 그 순서가 뒤 바뀌어도 거시적인 관점에서는 아무런 차이가 없다. 미시적인 메모리 레이아웃은 다소 변화가 있을 수도 있겠지만 자료구조의 관점에서 보면 1 - 2로 실행하나 2 - 1로 실행하나 차이가 없다. 구체적으로 말하면 이 해시 테이블을 쓰는 코드가 1, 2 코드의 순서에 무관하다는 것. 이 때 얘들을 교환성이 있는 코드라고 말하고 얘들을 병렬화 대상으로 보는 것이다.

지금 이야기 한 두 방법 (1) 데이터 의존성 파악으로 병렬성 찾기, (2) 교환성으로 병렬성 찾기는 코드를 직접 분석하는 것인데, 자료구조 수준에서, 즉 매우 높은 수준에서 병렬성을 찾는 방법도 많이 있다. 이 부분은 내가 어떻게 잘 이해하기는 벅찬 분야라 많이는 모른다. 최근의 관련 연구로는 이것 정도가 있다.

에.. 헛소리는 그만 하고, 그럼 도대체 Parallel Advisor가 하는 것은 뭐야?? 흠.. 이번에도 낚였다. Parallel Studio에 있는 그냥 Hot spots 찾는 기능과 같다. 직접적으로 병렬성을 자동으로 찾아주는 기능은 없다. (사실 이거 굉장히 시간이 오래 걸리고 메모리도 많이 먹는 작업이라 쉽지 않다)

 

3)  성능 모델: 잠재적인 병렬화로 인한 성능 향상은 얼마인가?

세 번째 기능이다. 아마 이런 기능이 있으면 정말로, 진짜로, 마술과도 같을 것이다.

어떤 직렬 코드가 있고 얘를 병렬화 했을 때 얼마나 성능이 향상될까? 다시 말해 예상 speedup은?

당연히 이런 마술은 아직까지 없다. 그래서… 이번에도 이 기능을 누르면 그냥 MSDN이 뜨고 만다. 그렇다 해서 결코 웃어서는 아니 된다. 오히려 “정말 이 일은 어렵구나” 라고 경외심을 가져야 한다. 농담이 아니라 이 일은 매우 중요한 문제인데 매우 어렵다. 멀티코어 구조는 복잡한 캐시가 있고, 병렬화된 코드 중 락이 들어 있으면 이건 점쟁이가 점 치는 수준으로 어려워진다. 가장 간단한 예측 법은 어떤 코드가 아무런 락도 없고 완벽히 병렬화 된다면 암달의 법칙 같은 걸 이용하면 된다. 그러나 이보다 복잡한 코드는 … 아직 요원하다.

 

4) 모델 검증: 데이터 공유 문제가 있는가?

병렬화 가능한 부분을 찾는 이상적인 방법은 앞서 이야기한 것처럼 코드를 던져주면 자동으로 찾아주는 기능이다. 그런데 현재 Parallel Advisor가 사용하고 있는 방법은 약간 다르다.

  • (A) 프로그래머가 병렬화하고 싶은 코드를 명시적으로 표기한다(annotation).
  • (B) Parallel Advisor는 이 표기된 직렬 코드를 마치 병렬화가 된 것처럼 돌려서 혹시 데이터 공유, 즉 충돌이 있는지 살펴본다.
  • (C) 만약 데이터 공유가 없으면 손쉽게 병렬화할 수 있고, 공유가 있으면 이 부분에 락 같은 것을 넣어야 한다. 락을 넣는다는 표기를 다시 하고 Parallel Advisor를 다시 돌려 재검증 한다.

현재로서는 프로그래머가 직접 표기를 하고 검증하는 과정을 반복함으로써 병렬 코드 작성을 돕고 있다. 실제 간단하게 예를 보도록 하자.

이 코드는 sudoku 예제 프로그램인데, 프로파일링을 해보니 위의 for 루프가 가장 많은 시간이 걸렸다. 그러니 이 부분을 병렬화 대상으로 삼는 것은 매우 합리적일 것이다. 그래서 프로그래머가 이 부분을 검사해보기로 한다. ANNOTATE_SITE_BEGIN/END로 Parallel Advisor가 검사할 부분을 정해준다. 그리고 ANNOTATE_TASK_BEGIN와 END로 병렬화를 한 것처럼 Advisor에게 알려준다. 이 annotation을 쓰려면 별도의 헤더 파일을 추가하면 된다.

하나 이 코드에서 재밌는 것은 for 루프의 상한 값이 4이다. 원래 이 프로그램은 100으로 되어있다. 그런데 100으로 하고 이 annotation과 함께 분석을 하면 시간이 오래…. 걸린다. 그래서 의도적으로 4로 크게 줄였다. 가정은 루프 4번만 돌려도 대충 병렬화시 문제점은 다 나올 것 같다라는 희망이다.

자, 이렇게 annotation을 하고 분석을 해보자.

요렇게 분석을 한다. 이런 시각화는 참 좋다. Cancel도 할 수 있고, 진행 상태도 보여준다! 결과가 끝나면 프로그래머가 병렬화하고자 한 부분에서 혹시 데이터 공유가 있었는지 알려준다.

이렇게 돌려보니까 main 함수에서 부르는 한 서브루틴에서 intialize라는 변수가 여러 스레드에 의해 공유됨을 알았다. 따라서 이 부분을 그대로 두면 데이터 레이스(data race)가 생기고 정확한 병렬화가 안 될 것이다. 이 문제를 해결하려면 일단 이 부분에 락을 넣으면 된다. 락을 넣는 것을 가정하여 또 새로운 annotation을 넣어보자.

ANNOTATE_CRITICAL_BEING/END로 임계 영역을 가정해보았다. 이런 뒤 다시 Advisor에게 분석을 의뢰한다. 해보면 이제 프로그래머가 병렬화하고자 하는 코드에 더 이상 데이터 공유가 없다. 안전한 병렬화가 가능하다는 이야기다. 그런데, 엄밀히 이야기하면 이것은 이 프로그램에 주어진 입력 값에 대해서만 참인 이야기이다. 따라서 정확히 이야기 하면 위에서 본 main 함수의 for 루프는 잠재적으로 병렬화 가능한(potentially parallelizable) 루프가 된다. 최악의 경우 어떤 다른 입력을 쓰면 다른 데이터 공유가 검출되어 첫 번째 병렬화 시도가 실패할 수 있다. 이 문제에 대한 근원적인 답은 없다. 프로파일이 가지는 근본적인 단점이다. 성능의 병목지점을 잡는 프로파일에서도 입력 값에 따라 결과 값은 크게 달라질 수 있다.

 

5) 결론

두 번의 글에 걸쳐 인텔 Parallel Studio와 애드온인 Parallel Advisor Lite에 대해 알아보았다. 기존의 인텔 툴에 비해 크게 새로운 기능이 추가된 것은 없었다. 그러나 Visual Studio와 훌륭한 연동으로 쓰기 편해졌다는 것만으로도 가치가 있다. 그리고 이 정도 도구라도 지금까지는 거의 없다시피 했다. Parallel Studio는 현재의 프로그램 분석 기법의 한계를 여실히 보여주기도 한다. 이상적인 그림과는 한참이나 멀지만 이렇게라도 한 걸음을 때는 것이 매우 중요하다. 그래서 언뜻 보면 Parallel Studio를 까는 것 같지만 결론은 “매우 높게 평가한다” 이다.

p.s. 마이크로소프트웨어에도 Parallel Studio에 관한 글이 올라온 적이 있음: Inspector, Amplifier


덧글

  • saiparan 2010/01/06 12:58 # 삭제 답글

    매우 유용한 글 감사합니다. 개인적으로는 네번째 모델검증 기능이 가장 쓸모가 있어 보입니다. 남이 작성한 코드를 병렬화 하고자 할때, 코드가 조금만 복잡해지면 병렬화할 부분안에서 사용하는 object 들이 mutation을 해서 race가 발생하는지를, 코드를 보면서 따라가는 건 사실상 불가능한데, 문자 그대로 적절한 "Advise"를 받을 수 있다면 참 좋겠다는 생각을 했었거든요. 하시는 연구에도 좋은 결과 있기를 기대하겠습니다.
  • object 2010/01/07 12:57 #

    "남이 작성한 코드를 병렬화 하고자 할 때" 바로 제가 주목하는 것도 이러한 때죠. 저는 바로 이 직렬 코드에서 data dependence를 찾아주는 것이 목적이고, Parallel Advisor는 마치 병렬화된 것을 가정해서 data race를 찾아주는 것으로 문제점을 짚어줍니다. 이 둘이 똑같은 것인지는 아직 생각 안해봤네요. 근데 똑같을 것 같군요. 즉, 직렬 코드에서 데이터 의존성이 발생하면 이를 무시하고 그냥 병렬화하면 반드시 race가 발생할 것이고, 그 역도 성립되겠죠.

    데이터 의존성 뒤져서 병렬성 찾는 삽질은 몇몇 연구자들이 하고 있습니다. 그런데 이게 쉽지가 않다는;;
댓글 입력 영역