2012-05-09 2 views
3

다음 직렬 함수를 고려하십시오. 코드를 병렬 처리하면 모든 스레드가 병렬 영역 (이 기능은 표시되지 않음)에서이 함수를 호출합니다. 나는이 threadsafe를 효율적으로 (빠른) 으로 만들려고 노력하고있다.C++ OpenMP critical : "단방향"잠금?

float get_stored_value__or__calculate_if_does_not_yet_exist(int A) 
{  
    static std::map<int, float> my_map; 

    std::map::iterator it_find = my_map.find(A); //many threads do this often. 

    bool found_A = it_find != my_map.end(); 

    if (found_A) 
    { 
     return it_find->second; 
    } 
    else 
    { 
     float result_for_A = calculate_value(A); //should only be done once, really. 
     my_map[A] = result_for_A; 
     return result_for_A; 
    }  
} 

이 기능은 스레드가 성공적으로 (그것이 무엇이든) 자신의 "A"에 대한 저장된 값을 "발견"것이라고 거의 매번. 새 A가 호출 될 때마다 값을 계산하고 저장해야합니다.

그럼 #pragma omp critical은 어디에 넣어야합니까? 각 스레드는 지속적으로이 일을하기 때문에

이 용이하지만, 그것은이 모든 주위에 #pragma omp critical을 넣어 매우 비효율적 이며 종종 읽기 전용 케이스가 될 것입니다.

"편도"critical 또는 "편도"lock 루틴을 구현할 수있는 방법이 있습니까? 즉, 반복자와 관련된 위의 연산은 else 문에서 my_map에 쓸 때만 "잠겨"있어야합니다. 그러나 다중 스레드는 .find 호출을 동시에 실행할 수 있어야합니다.

나는 이해할 수 있기를 바랍니다. 감사합니다.

답변

1

@ ChrisA의 답변으로 문제가 해결 될 수 있지만 앞으로의 검색 사용자가 유용하다고 생각하는 경우 내 답변을 남겨 두겠습니다.

#pragma omp critical 섹션에 name을 부여 할 수 있습니다. 그런 다음 해당 이름을 가진 섹션은 동일한 중요 섹션으로 간주됩니다. 이것이 당신이하고자하는 일이라면, 당신은 당신의 방법의 작은 부분을 쉽게 비판적으로 만들 수 있습니다.

#pragma omp critical map_protect 
{ 
    std::map::iterator it_find = my_map.find(A); //many threads do this often. 

    bool found_A = it_find != my_map.end(); 
} 

...

#pragma omp critical map_protect 
{ 
    float result_for_A = calculate_value(A); //should only be done once, really. 
    my_map[A] = result_for_A; 
} 

#pragma omp atomic#pragma omp flush 지침이 유용 할 수 있습니다.

atomic은 메모리 위치 (지시문 앞에 나오는 표현식의 lvalue)에 항상 원자 단위로 쓰도록 만듭니다.

flush 모든 스레드가 사용할 것으로 예상되는 메모리는 실제로 모든 스레드에 쓰여지고 프로세서 캐시에 저장되지 않고 사용할 수 없도록 보장됩니다.

+0

나보다 낫다 그러나'critical'의 사용은 내가 걱정했던 비 효율성이다 - 때문에'critical'에 여러 개의 스레드가 simlutaneously지도를 읽을 수 없습니다. – cmo

+0

'원자'는 좋은 생각입니다. 그렇다면 필자는 쓰기 영역 주위에'atomic'과'flush'를 넣기 만하면됩니까? - 읽기 영역은 지시가 필요합니까? – cmo

+0

@CycoMatto 알아요. 나는 내가 원자와 플러시로 영리한 것을했고, 실수를 깨달았으며, 나의 대답을 편집했다고 생각했다. 문제를 일으키는 다른 사람에게 유용 할 수있는 정보를 제공하지만, 원하는 경우 잠긴 경우 쓰기 동작을 제공하지 않습니다. –

2

this link on Stack Overflow에 따르면 std::map에 삽입하면 반복기가 무효화되지 않습니다. 동일한 것이 반복자 end()에도 해당됩니다. Here's a supporting link.

불행히도 중요한 섹션을 사용하지 않으면 삽입이 여러 번 발생할 수 있습니다. 또한 calculate_value 루틴이 계산 상 비쌉니다. 따라서 A의 동일한 값으로 두 번 연산 한 다음 두 번 삽입하면이 else 절이 잠기지 않도록 잠 가야합니다.

void testFunc(std::map<int,float> &theMap, int i) 
{ 
    std::map<int,float>::iterator ite = theMap.find(i); 

    if(ite == theMap.end()) 
    { 
     theMap[i] = 3.14 * i * i; 
    } 
} 

는 다음과 같이 전화 :

std::map<int,float> myMap; 

int i; 
#pragma omp parallel for 
for(i=1;i<=100000;++i) 
{ 
    testFunc(myMap,i % 100); 
} 

if(myMap.size() != 100) 
{ 
    std::cout << "Problem!" << std::endl; 
} 

편집 : earler 버전에서 오류를 수정 편집

다음은이 잘못된 여러 삽입을 복제 할 수있는 샘플 기능입니다.

+0

'.insert'가 "mid-find"로 나타나더라도 true입니까? (즉,이 쓰레드가'.find' 호출 안에있는 동안). – cmo

+0

삽입 작업이 여러 번 호출 될 수 있지만 * 실제로 사용하는 삽입 연산자에 따라 실제로 한 번 또는 두 번 이상 발생할 수 있습니다. '연산자 []'사용. 'myMap [i] = 3.14 * i * i'는 다중 쓰기가됩니다. 그러나 myMap.insert (std :: pair (i, 3.14 * i * i))'는 실제로 한 번만 쓸 것입니다. – cmo

1

OpenMP는 스레드 통신 또는 동기화 라이브러리가 아닌 자동 루프 병렬화를위한 컴파일러 "도구"입니다. 따라서 읽기/쓰기 뮤텍스와 같이 정교한 뮤텍스가 없습니다. 쓰기에서는 잠금을 가져 오지만 읽기에서는 락을 얻지 마십시오.

여기에 implementation example이 있습니다.

어쨌든 크리스 A.의 대답은 :)하지만