Program Club

함수 호출이 최신 플랫폼에 효과적인 메모리 장벽입니까?

proclub 2020. 11. 15. 11:55
반응형

함수 호출이 최신 플랫폼에 효과적인 메모리 장벽입니까?


내가 검토 한 코드베이스에서 다음 관용구를 발견했습니다.

void notify(struct actor_t act) {
    write(act.pipe, "M", 1);
}
// thread A sending data to thread B
void send(byte *data) {
    global.data = data;
    notify(threadB);
}
// in thread B event loop
read(this.sock, &cmd, 1);
switch (cmd) {
    case 'M': use_data(global.data);break;
    ...
}

"Hold it", 저는 우리 팀의 선임 멤버 인 저자에게 "여기에는 메모리 장벽이 없습니다! global.data캐시에서 메인 메모리로 플러시 될 것이라는 보장은 없습니다 . 스레드 A와 스레드 B가 실행되면 두 개의 다른 프로세서-이 체계는 실패 할 수 있습니다. "

선임 프로그래머는 5 살짜리 소년이 신발 끈을 묶는 방법을 설명하는 것처럼 천천히 설명했습니다. "어린 소년 이여, 여기에서 많은 스레드 관련 버그, 고부하 테스트 및 실제 클라이언트를 보았습니다."라고 그는 길쭉한 수염을 긁기 위해 잠시 멈췄다. "하지만 우리는이 관용구에 버그가 없었습니다."

"하지만 책에는 ..."

"조용!"그는 아마도 이론적으로,이 보장되지 것 "즉시 나를 잠잠하지만, 실제로는, 사실 당신은 함수 호출 효과적으로 메모리 장벽 사용했다. 컴파일러는 명령의 순서를 변경하지 않습니다 global.data = data, 때문에 그것을이 경우 알 수 없습니다 함수 호출에서이를 사용하는 모든 사람과 x86 아키텍처는 스레드 B가 파이프에서 명령을 읽을 때까지 다른 CPU가이 전역 데이터를 볼 수 있도록 보장합니다. 안심할 수 있습니다. 우리는 걱정해야 할 실제 문제가 많습니다. 우리는 가짜 이론적 문제에 더 많은 노력을 기울일 필요가 없습니다.

"안심하세요, 시간이 지나면 박사님이 박사 학위를 취득해야하는 비 문제와 실제 문제를 분리하는 것을 이해할 수있을 것입니다."

그가 맞습니까? 실제로 실제로 문제가되지 않습니까 (예 : x86, x64 및 ARM)?

내가 배운 모든 것에 위배되지만 그는 긴 수염과 정말 똑똑한 외모를 가지고 있습니다!

그가 틀렸다는 것을 증명하는 코드를 보여줄 수 있다면 추가 점수!


메모리 장벽은 명령 재정렬을 막기위한 것이 아닙니다. 명령이 다시 정렬되지 않더라도 캐시 일관성에 문제가 발생할 수 있습니다. 재정렬은 컴파일러와 설정에 따라 다릅니다. ICC는 재정렬에 특히 공격적입니다. 전체 프로그램 최적화가 포함 된 MSVC도 가능합니다.

공유 데이터 변수로 선언 된 경우 volatile, 이 사양에없는 경우에도 대부분의 컴파일러는 메모리 변수 주위 읽고 변수에서 쓰기 및 재정렬 방지 생성합니다. 이것은을 (를) 사용하는 올바른 방법이 volatile아니며 그 용도가 아닙니다.

(남은 투표가 있으면 해설에 대한 질문을 +1하겠습니다.)


실제로 함수 호출은 컴파일러 장벽입니다. 즉, 컴파일러는 호출 이후 전역 메모리 액세스를 이동하지 않습니다. 이에 대한주의 사항은 컴파일러가 내장 함수, 인라인 함수 (IPO를 염두에 두십시오!) 등과 같이 무언가에 대해 알고있는 함수입니다.

따라서이 작업을 수행하려면 이론적으로 프로세서 메모리 장벽 (컴파일러 장벽 외에)이 필요합니다. 그러나 전역 상태를 변경하는 syscall 인 읽기 및 쓰기를 호출하기 때문에 커널이 해당 구현의 어딘가에 메모리 장벽을 발행한다고 확신합니다. 그러나 그러한 보장은 없으므로 이론적으로 장벽이 필요합니다.


기본 규칙은 다음과 같습니다. 컴파일러는 전역 상태 를 코딩 한 것과 똑같이 보이 도록해야하지만 주어진 함수가 전역 변수를 사용하지 않는다는 것을 증명할 수 있다면 알고리즘을 선택하는 방식으로 구현할 수 있습니다.

결론은 전통적인 컴파일러가 항상 다른 컴파일 단위의 함수 메모리 장벽으로 취급했다는 것입니다. 이러한 함수 내부를 볼 수 없기 때문입니다. 점점, 현대 컴파일러는 "전체 프로그램"또는 휴식이 장벽 아래 "링크시"최적화 전략 성장하는 것입니다 그것은 년 동안 잘 작동하고, 비록 실패 잘못 작성된 코드의 원인을.

문제의 함수가 공유 라이브러리에 있으면 내부를 볼 수 없지만 함수가 C 표준에 의해 정의 된 함수이면 그럴 필요가 없습니다. 함수가 무엇을하는지 이미 알고 있습니다. -그래서 그들도 조심해야합니다. 컴파일러는 커널 호출을 인식 하지 못하지만 컴파일러가 인식 할 수없는 것을 삽입하는 행위 (인라인 어셈블러 또는 어셈블러 파일에 대한 함수 호출)는 자체적으로 메모리 장벽을 생성합니다.

귀하의 경우 notify에는 컴파일러가 내부에서 볼 수없는 블랙 박스 (라이브러리 함수)이거나 인식 가능한 메모리 장벽이 포함되어 있으므로 대부분 안전합니다.

실제로이 문제를 해결하려면 매우 나쁜 코드를 작성해야합니다 .


실제로, 그는 옳고이 특정한 경우에 기억 장벽이 내포되어 있습니다.

그러나 요점은 그 존재가 "논쟁의 여지가있는"경우 코드가 이미 너무 복잡하고 명확하지 않다는 것입니다.

정말 여러분, 뮤텍스 나 다른 적절한 구조를 사용하세요. 스레드를 처리하고 유지 보수 가능한 코드를 작성하는 유일한 안전한 방법입니다.

그리고 send ()가 두 번 이상 호출되면 코드를 예측할 수없는 것과 같은 다른 오류가 표시 될 수 있습니다.

참고 URL : https://stackoverflow.com/questions/10698253/is-function-call-an-effective-memory-barrier-for-modern-platforms

반응형