2009-09-17 4 views
0

보드 게임을하는 아주 오래되고 매우 큰, 완전히 작동하는 C 프로그램이 있습니다. 멀티 코어 프로세서를 활용할 수 있도록 여러 스레드에서 작동하도록 변환하고 싶습니다. 이전 프로그램에는 board []라고하는 전역 UBYTE 배열이 있습니다. 보드 []의 내용을 조작하는 많은 (매우 최적화 된, 매우 중요한) 기능이 있습니다.오래된 C 코드를 쓰레드로 변환하기

1 단계는 싱글 보드 []의 많은 조작을 수행하는 단일 스레드 에 작업의 큰 숫자를 수행 : 지금 다음과 같이 처리 작업을 만들고 싶어. 이들은 여러 코어에서 수행하기에는 너무 복잡합니다.

2 단계 농장 밖으로 "보드는 []"각 스레드 스레드의 컬렉션에와있는 자신의 자신의 개인 "보드 []" 's의 그들의 별도의 조작을하고 시간을 보낼의 여러 사본 .

단계 3. 스레드가 작업을 끝내고 스레드가 스레드에 대한 답을 반환합니다.

인수로는 32 개의 하위 스레드가 있다고 가정합니다.

이제는 하나의 글로벌 보드 []와 sub_board [32] []와 같은 다른 이름을 가진 32 개의 서브 보드를 만든 다음 새 보드에서 작동하는 새로운 보드 조작 함수 모음을 작성하십시오. 2 차원 sub_board [] [],하지만 이것은 게임 보드에 대한 모든 액세스에 대해 추가 곱셈과 덧셈이 필요하기 때문에 내 최적화를 망칠 것입니다. 또한 오래된 보드 조작 함수의 새로운 버전은 약간 복잡 할 것입니다.

지금까지는 C++ 프로그래머가 아니었지만 (가능한 한 빨리 배우는 중입니다.) 누군가 C++와 관련된 다음 트릭을 제안했습니다 (모든 세부 사항이 정확하지는 않습니다.) : 나는 기존 이사회를 그대로 둔다. 기존 보드 조작 기능을 그대로 둡니다. 보드 (board) []와 보드 조작 함수의 새로운 세트를 포함하는 새로운 클래스 (thread_type이라고 부른다)를 만든다. 이런 식으로 뭔가 :

class thread_type 
{ 
    UBYTE board[]; // boards for slave threads to work with 
    void board_manipulation_A(void); 
    void board_manipulation_B(void); 
} 

보드 조작 함수는 "thread_type ::"는 시작에 선언하는 것 외에는 기존의 것들 (그래서 잘라 붙여 넣을 수 있습니다)와 동일하다. 그런 다음에 주() 내가 가진 :

class thread_type slave[32]; 

가 지금은 기본 스레드에서 모든 내 예전의 코드로 하나의 세계 보드 []를 조작 할 수 있습니다. 그럼 난 .board [] 다음 32 개 스레드의 각 내부 지금

For (i = 0; i < 32;i++) 
{ 
    // there will have to be some extra thread/mutex 
    // related code around here but I'm not showing it for simplicity 

    slave[n].do_your_stuff(); 
} 

가 [N] 슬레이브 메인 보드 []를 복사 할 수 있습니다, 각각은 그들의 자신의 다른 "보드 []"에 작업 할 코드는 이전의 원본 (완전히 디버깅되고 최적화 된) 코드와 거의 동일합니다. #define 트릭을 수행하여 이전 코드를 자르거나 지나치는 것을 방지 할 수도 있습니다.함수 선언이

void THREAD_OR_BASE board_manipulation_A(void); 

처럼 작성하고 내가 언제 내가 만드는 것이 매우 확신 할 수

#define THREAD_OR_BASE thread_type:: 

이 방법을 한 번

#define THREAD_OR_BASE // zilch 

과 한 번이를 통해 실행 한 board_manipulation_A()에 대한 수정은 기본 스레드 버전과 하위 스레드 버전 모두에 나타납니다.

내 질문은 다음과 같습니다. A) 모두 작동합니까? B) 몇 가지 중요한 단계를 놓쳤습니까? C) 나는 더 간단한 방법으로 같은 것을 얻을 수 있었습니까?

편집 : 대신 32 개 스레드, 나는 당신이 같은 일 다시 스레드 결과 보드를 병합 할 필요가없는 경우

+0

오래된 게임이라면 왜 모든 것을 최적화해야합니까? 너의 (잘만되면 그렇게 오래되지 않은) 기계에서 잘 돌아 가야하지 않을까? –

+0

게임은 낡고 코드는 오래되었지만 지속적으로 업데이트되고 새로운 라이벌과 경쟁해야합니다. 속도가 중요합니다. – Mick

+0

C 코드로 작업하는 경우 왜이 태그가 C++입니까? – jalf

답변

2

이하는 것처럼 나에게 보인다 "코어 수만큼 스레드"고 말했다해야 좋은 전략, 각 스레드가 자신의 보드 복사본을 가지고 작업하고 있기 때문에 왜 작동하지 않아야하는지 알 수 없습니다.

그러나 스레드가 많은 CPU - 바운드 작업을 수행하는 것처럼 보입니다. 사실이라면 많은 스레드가 없어야하며, CPU가 가진 코어와 동일한 스레드를 갖는 것이 좋습니다. , 더 많은 경우, 그들은 CPU 리소스를 위해 경쟁 할 것이고, 당신의 성능은 저하 될 것입니다.

+0

감사합니다. 귀하의 의견은 나에게 자신감을 부여하고 계획을 구현할 수 있습니다. – Mick

1

외국 코드는 종종 전역 상태와 설정을 가지고 있습니다 (어쩌면 보드 [] 옆에있는 경우). 이러한 변수는 메서드를 통해 스레드에서 사용하는 경우 클래스에 채워야합니다 (읽기 전용 인 경우 제외). 함수 내부의 정적 변수도 확인하십시오 (로컬 정적 변수는 실제로 응용 프로그램에 대해 한 번만 존재하므로 전역 변수이기도합니다). 스레드 안전도 아닙니다.

이미 이러한 사항을 확인한 것 같습니다. 이 대답이 다른 사람들이 기존 응용 프로그램을 비슷한 방식으로 수정하려고 할 때 도움이되기를 바랍니다.

+0

경고를 보내 주셔서 감사합니다. 예. 다른 전역 변수가 있습니다. 명확성을 위해 문제에 대한 설명을 단순화했습니다. 그러나 필자는 함수에서 선언 한 정적 변수에 대해서는 생각하지 않았다. – Mick

1

C++에서 상속을 설명하는 자습서로 작업하십시오. 그것은 매크로로 끝나지 않았습니다.

함수의 정의를 두 개 이상 가지고 있어야하며, 함수를 호출하는 사람 (기본 스레드의 프로 시저 또는 슬레이브 스레드의 프로 시저)을 기반으로 여러 정의를 가져야하는 경우가 있습니다. .

스레드의 동작을 설명하기 위해 클래스를 만드는 대신 모든 전역과 함께 보드에 대한 두 가지 조작과 데이터가있는 Board 클래스를 만듭니다. 한 스레드에서 호출되는지 다른 스레드에서 호출되는지 여부는 중요하지 않습니다. 보드 클래스에 UBYTE board[] 회원이있는 경우 기존 코드를 많이 변경하지 않아도되며 언어를 남용하지 않아야합니다.

다른 작업을 수행하는 동시 작업 소포를 갖고 싶다면 해당 작업자의 절차에서 다른 기능을 호출하십시오. 작업자를위한 행동을 포함하는 보드를 포장하는 것은 좋은 생각이지만, 작업자가 저레벨 표현을 직접 사용하는 대신 보드 객체에서 기능을 호출하게하십시오.

32 개 미만의 프로세서가 있다고 가정하면 개선의 여지가 거의 없을 수도 있습니다. 작업이 빠르면 OS 스레드를 생성하고 그 사이에서 전환하는 데 많은 시간을 할애해야합니다 유용한 일을하십시오.용감한 마음이라면 CPU 코어를 가진만큼의 스레드와 32 개의 작업 소포 큐를 생성하여 각 스레드가 다음 소포를 큐에서 꺼내 실행하고 결과를 저장하도록하십시오. 그렇게하면 작은 작업 항목에 대한 스레드를 생성하지 않습니다. 나는 내 자신을 굴리는 경향이 있지만이 threadpool의 요약보기는 '내가 병렬로 실행하고 싶은 32 개의 작업'과 '다른 코어에서 작업을 수행 할 수있게 해주는 OS 기능 사이의 추상화에 사용하기 쉽습니다. 내 컴퓨터 '.


OO 디자인의 기본 규칙 중 하나는 작동하는 데이터로 동작을 캡슐화하는 것입니다.

그래서 보드에 관한 데이터와 작업을 나타내는 클래스는 다음과 같이 보일 수 있습니다 :

class Board 
{ 
    private: 
     UBYTE board[ BOARD_SIZE ]; 

    public 
     // ... suitable constructors  

     void manipulation_A(); 
     void manipulation_B(); 
}; 

당신은 기능을 만들기 위해 (board 유일한 글로벌 데이터라고 가정) 기존의 코드로 변경 Board 클래스의 멤버 함수 :

:

void Board::manipulation_A() 
{ 
    ... 
} 

void Board::manipulation_B() 
{ 
    ... 
} 

귀하의 작업은 각각의 일을 자신의 보드를 가지고 객체

또는 원하는 조작 순서.

보드 개체의 기능을 어디에서나 호출 할 수 있으며 해당 개체에 속한 데이터에만 영향을줍니다. 로컬 보드와 글로벌 보드간에 전환하거나 동일한 코드를 두 번 컴파일하기 위해 매크로를 사용할 필요가 없습니다.

주 스레드는 조작을 수행 할 수 있으며 보드 데이터의 자체 복사본이있는 작업 항목을 작성하여 스레드를 분리/높은 수준의 동시 스케줄러로 큐에 넣을 수 있습니다.

+0

은 첫 번째 문장에서 오타가되었습니다. – Mick

+0

"C++에서 상속을 설명하는 자습서를 통해 작업"에서 "냄새"라는 단어가 없습니다. "디자인 냄새"또는 "코드 냄새"라는 문구는 매우 일반적입니다. 매크로를 사용하여 C 코드를 C++로 바꾸거나하지 않으면 호출하는 스레드에 따라 C++의 객체 지향 프로그래밍의 기본을 이해하지 못하는 사람 같은 냄새가납니다. –

+0

코드 길이는 6MB이며 그 중 일부는 25 년 전으로 거슬러 올라갑니다. 나는 무한한 시간 프로젝트가 아니라 너무 많이 바꾸어야한다고 꺼려한다. 제안 된 스키마에서 메인 스레드와 그 기능은 다음과 같은 라인으로 "board []"버전을 바꿀 수 있습니다 : board [i] = x; 원래 프로그램과 동일합니다. 슬레이브 스레드는 다음과 같은 라인으로 "board []"버전을 변경할 수 있습니다 : board [i] = x; 여러분은 이것이 방대한 양의 기존 (디버깅 및 최적화 된) 코드를 매우 쉽게 재사용 할 수 있다고 생각할 수 있습니다. 이 두 줄의 코드는 제안 된 시스템에서 어떤 모습을 보입니까? – Mick

관련 문제