함수 호출이 현대 플랫폼에 효과적인 메모리 장벽입니까?
내가 검토한 코드베이스에서 나는 다음과 같은 숙어를 발견했다.
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;
...
}
"잠깐만요"라고 나는 팀의 선배인 저자에게 말했다. "여기에는 기억의 장벽이 없어요!장담은 못 하겠지만global.data
캐시에서 메인 메모리로 플래시 됩니다.스레드 A와 스레드B가 2개의 다른 프로세서에서 동작하는 경우 - 이 스킴은 실패할 수 있습니다.
시니어 프로그래머는 5살짜리 아들에게 신발끈 묶는 법을 설명하듯 빙긋이 웃으며 천천히 설명했다. "얘야, 우리는 여기서 많은 실과 관련된 벌레를 봤어." "고부하 테스트에서, 그리고 실제 고객에서." 그는 그의 긴 수염을 긁어보려고 멈췄다. "하지만 우리는 이 사자성어를 가진 벌레를 가져본 적이 없어."
"하지만 책에는..."."
「조용히 해!」라고 즉석에서 나를 쉬게 했다.이론적으로는 장담할 수 없지만, 실제로는 함수 호출을 사용했다는 것은 사실상 기억의 장벽이다.컴파일러는 명령 순서를 변경하지 않습니다.global.data = data
함수 호출에서 사용하는 사용자가 있는지 여부를 알 수 없기 때문에 x86 아키텍처는 스레드 B가 파이프에서 명령어를 읽을 때까지 다른 CPU가 이 글로벌 데이터를 볼 수 있도록 합니다.안심하세요, 우리는 걱정해야 할 현실적인 문제들이 많습니다.우리는 가짜 이론 문제에 추가적인 노력을 투자할 필요가 없다.
"안심하세요. 머지않아 진짜 문제와 박사학위가 아닌 문제를 분리할 수 있게 될 것입니다."
그가 맞습니까?실제로 문제가 되지 않는 것(x86, x64, ARM 등)입니까?
내가 배운 모든 것에 반하는 것이지만, 그는 긴 턱수염을 가지고 있고 정말 똑똑해 보여요!
그가 틀렸다는 걸 증명하는 코드를 보여주면 가산점!
메모리 장벽은 명령어 재배열을 막는 것만이 아닙니다.명령의 순서를 바꾸지 않더라도 캐시 일관성에 문제가 발생할 수 있습니다.순서 변경에 대해서는, 사용의 컴파일러와 설정에 따라 다릅니다.ICC는 재정렬에 특히 적극적이다.전체 프로그램 최적화를 포함한 MSVC도 가능합니다.
공유 데이터 변수가 다음과 같이 선언된 경우volatile
사양에 포함되지 않더라도 대부분의 컴파일러는 변수의 읽기 및 쓰기를 중심으로 메모리 변수를 생성하여 순서를 변경할 수 없습니다.이것은 올바른 사용법도 아니고, 목적도 아닙니다.
(남은 표가 있으면 내레이션에 +1로 질문하겠습니다.)
실제로 함수 호출은 컴파일러의 장벽입니다.즉, 컴파일러는 콜을 지나 글로벌메모리 액세스를 이동시키지 않습니다.이에 대한 경고는 컴파일러가 알고 있는 함수, 예를 들어 임베디드 함수(IPO에 유의하십시오!) 등입니다.
따라서 이를 실현하기 위해서는 이론적으로 프로세서 메모리 장벽(컴파일러 장벽과 더불어)이 필요합니다.단, 글로벌 상태를 바꾸는 시스템인 읽기 및 쓰기를 호출하고 있기 때문에 커널이 메모리 장벽을 발생시키고 있는 것은 확실합니다.하지만 그런 보장은 없기 때문에 이론적으로는 장벽이 필요합니다.
기본 규칙은 컴파일러는 글로벌 상태를 코드화한 그대로 표시해야 하지만 특정 함수가 글로벌 변수를 사용하지 않는다는 것을 증명할 수 있다면 원하는 방식으로 알고리즘을 구현할 수 있다는 것입니다.
결과적으로 기존 컴파일러는 다른 컴파일 유닛의 함수를 항상 메모리 장벽으로 취급했습니다.왜냐하면 이러한 함수의 내부를 볼 수 없었기 때문입니다.최신 컴파일러는 이러한 장벽을 허물고 오랫동안 정상적으로 동작하고 있음에도 불구하고 제대로 작성되지 않은 코드가 실패하는 "전체 프로그램" 또는 "링크 시간" 최적화 전략을 점점 더 발전시키고 있습니다.
문제의 함수가 공유 라이브러리에 있는 경우, 그 내부를 볼 수 없지만, 함수가 C 표준으로 정의되어 있는 함수라면, 그 함수의 기능을 이미 알고 있기 때문에, 그 기능도 주의할 필요가 있습니다.컴파일러는 커널 호출이 무엇인지 인식하지 않지만 컴파일러가 인식할 수 없는 것(인라인 어셈블러 또는 어셈블러 파일에 대한 함수 호출)을 삽입하는 것은 그 자체로 메모리 장벽을 만들 것입니다.
당신의 경우,notify
컴파일러가 내부에서 볼 수 없는 블랙박스(라이브러리 기능)가 되거나 인식 가능한 메모리 장벽이 포함되어 있기 때문에 안전합니다.
실제로, 당신은 이것에 걸려 넘어지기 위해 매우 나쁜 코드를 써야 합니다.
실제로, 그가 옳고, 이 특정한 사건에 기억의 장벽이 내포되어 있습니다.
그러나 요점은 만약 그 존재가 "논의할 수 있는" 것이라면, 그 코드는 이미 너무 복잡하고 불분명하다는 것이다.
정말로, 뮤텍스나 다른 적절한 구조물을 사용하세요.스레드를 처리하고 유지보수가 가능한 코드를 작성할 수 있는 유일한 안전한 방법입니다.
또한 send()가 여러 번 호출되면 코드가 예측 불가능하다는 등의 오류가 나타날 수 있습니다.
언급URL : https://stackoverflow.com/questions/10698253/is-function-call-an-effective-memory-barrier-for-modern-platforms
'programing' 카테고리의 다른 글
pthread_cond_wait에 스플리어스 웨이크업이 발생하는 이유는 무엇입니까? (0) | 2022.08.07 |
---|---|
Java/Spring에서 Scala/Lift를 사용하는 이유는 무엇입니까? (0) | 2022.08.07 |
Foreach(Vuex)의 상태로부터 요소를 참조하려면 어떻게 해야 합니까? (0) | 2022.08.02 |
Java의 불변 배열 (0) | 2022.07.19 |
JSON 데이터를 Java 개체로 변환하는 중 (0) | 2022.07.19 |