컴파일러가 힙 메모리 할당을 최적화 할 수 있습니까?
다음을 사용하는 간단한 코드를 고려 하십시오 new
(없음을 알고이 delete[]
있지만이 질문과 관련이 없습니다).
int main()
{
int* mem = new int[100];
return 0;
}
컴파일러가 new
호출 을 최적화 할 수 있습니까?
내 연구에서 g ++ (5.2.0) 및 Visual Studio 2015는 new
호출을 최적화하지 않는 반면 clang (3.0+)은 . 모든 테스트는 전체 최적화가 활성화 된 상태에서 수행되었습니다 (g ++ 및 clang의 경우 -O3, Visual Studio의 경우 릴리스 모드).
불가능한 new
(불법) 컴파일러가 출력을 최적화 할 수있게 후드 시스템 호출을?
편집 : 이제 프로그램에서 정의되지 않은 동작을 제외했습니다.
#include <new>
int main()
{
int* mem = new (std::nothrow) int[100];
return 0;
}
clang 3.0은 더 이상 최적화하지 않지만 이후 버전에서는 .
EDIT2 :
#include <new>
int main()
{
int* mem = new (std::nothrow) int[1000];
if (mem != 0)
return 1;
return 0;
}
역사는 clang이 N3664 : 메모리 할당 에 대한 명확한 규칙을 따르는 것이 좋습니다.이 규칙 을 사용하면 컴파일러가 메모리 할당을 할 수 있습니다. Nick Lewycky가 지적합니다 .
Shafik은 그것이 인과 관계를 위반하는 것처럼 보이지만 N3664는 N3433으로 시작하고, 저는 우리가 최적화를 먼저 작성하고 나중에 논문을 확신합니다.
그래서 clang은 최적화를 구현하고 나중에 C ++ 14의 일부로 구현 된 제안이 있습니다.
기본 질문은 이것이 이전에 유효한 최적화인지 여부 N3664
이며 어려운 질문입니다. 우리가 가야 할 것 같은-경우 규칙 표준 섹션 ++ 초안 C에 승인 1.9
프로그램 실행이 (라고하는 명령 광산 ) :
이 국제 표준의 의미 설명은 매개 변수화 된 비 결정적 추상 기계를 정의합니다. 이 국제 표준은 준수 구현의 구조에 대한 요구 사항을 지정하지 않습니다. 특히 추상적 인 기계의 구조를 복사하거나 에뮬레이트 할 필요가 없습니다. 추상, 아래 에 설명 된대로 기계의 관찰 가능한 동작을 에뮬레이트하기 관찰 준수 구현이 합니다. 5
가 메모 5
말하는 곳 :
이 조항은 관찰 가능한 행동으로부터 결정될 수있는 한, 결과가 마치 요구 사항을 준수한 것처럼 결과가 국제 표준의 요구 사항을 무시할 수 있기 때문에 "as-if"규칙 이라고도합니다. 프로그램의. 예를 들어, 실제 구현은 해당 값이 사용되지 않고 프로그램의 관찰 가능 동작에 영향을 미치지 않고 부작용이 발생하지 않고 추론 할 수있는 경우에는 일부를 평가해야합니다.
new
프로그램의 반환 값을 변경하기 때문에 관찰 가능한 동작을 발생시킬 수있는 예외를 throw 할 수 있기 때문에 as-if 규칙에 의해 허용되는 것에 반대하는 것처럼 보이게 됩니다.
예외를 던질 때 구현 세부 사항이라고 주장 할 수 있으므로 clang 은이 시나리오에서 예외가 발생하지 않을 경우 new
호출을 제거해도 as-if 규칙을 위반하지 않을 수 있습니다 .
또한 던지지 않는 버전에 대한 호출을 최적화 하는 것은 규칙 에 따라 유효 합니다.
그러나 우리는 이것이 관찰 가능한 동작에 영향을 미칠 수있는 다른 번역 단위에 새로운 대체 전역 연산자를 사용할 수 있습니다. 따라서 컴파일러는 사실이 아님을 증명해야합니다. 정렬이 최적화를 수행 할 수 없습니다. 위반하는없이 같은-경우 규칙을 . 이전 버전의 clang은 실제로이 경우에 최적화되었습니다. 이 godbolt 예제는여기 에서 Casey 를 통해 코드 를 보여줍니다 .
#include <cstddef>
extern void* operator new(std::size_t n);
template<typename T>
T* create() { return new T(); }
int main() {
auto result = 0;
for (auto i = 0; i < 1000000; ++i) {
result += (create<int>() != nullptr);
}
return result;
}
다음과 같이 최적화합니다.
main: # @main
movl $1000000, %eax # imm = 0xF4240
ret
이것은 실제로 너무 공격적으로 보이지만 이후 버전은 수행하지 않는 것입니다.
이 N3664 에서 허용 됩니다.
구현은 대체 가능한 전역 할당 함수 (18.6.1.1, 18.6.1.2)에 대한 호출을 생략 할 수 있습니다. 그렇게 할 때 스토리지는 대신 구현에 의해 제공되거나 다른 new-expression의 할당을 확장하여 제공됩니다.
이 제안은 C ++ 14 표준의 일부이므로 C ++ 14에서는 컴파일러 가new
표현식 을 최적화 할 수 있습니다 (예 : 던질 수 있음).
당신이 한 번 봐 걸릴 경우 연타 구현 상태를 명확 그들이 N3664을 구현 할 것을 주장한다.
C ++ 11 또는 C ++ 03에서 컴파일하는 동안이 동작을 관찰하면 버그를 채워야합니다.
C ++ 14 이전 에는 동적 메모리 할당 이 프로그램 의 관찰 가능한 상태 의 일부 이므로 (현재로서는 이에 대한 참조를 찾을 수는 없지만) 준수 구현은 여기에서 as-if 규칙 을 적용 할 수 없습니다. 케이스.
C ++ 표준은 올바른 프로그램이 어떻게해야하는지가 아니라 무엇을해야하는지 알려줍니다. 표준이 작성된 후에 새로운 아키텍처가 발생할 수 있고 발생할 수 있고 표준이 사용되어야하기 때문에 나중에는 전혀 말할 수 없습니다.
new
시스템 호출이 아니어도됩니다. 운영 체제없이 시스템 호출 개념없이 사용할 수있는 컴퓨터가 있습니다.
따라서 최종 동작이 변경되지 않는 한 컴파일러는 모든 것을 최적화 할 수 있습니다. 그것을 포함하여new
한 가지주의 사항이 있습니다.
대체 글로벌 연산자 new는 다른 번역 단위에서 정의되었을 수 있습니다
.이 경우 new의 부작용은 최적화 할 수 없을 정도일 수 있습니다. 그러나 게시 된 코드가 전체 코드 인 경우처럼 컴파일러가 new 연산자에 부작용이 없음을 보장 할 수 있다면 최적화가 유효합니다.
새로운 std :: bad_alloc을 던질 수 있다는 것은 요구 사항이 아닙니다. 이 경우 new가 최적화되면 컴파일러는 예외가 발생하지 않고 부작용이 발생하지 않음을 보장 할 수 있습니다.
컴파일러가 원래 예제에서 할당을 최적화하는 것은 완벽하게 허용되지만 필수 는 아닙니다. 일반적으로 as-if 규칙 이라고하는 표준 §1.9에 따른 EDIT1 예제에서는 더욱 그렇습니다 .
아래에 설명 된대로 추상 기계의 관찰 가능한 동작을 에뮬레이션하려면 준수하는 구현이 필요합니다.
[조건 3 페이지]
더 사람이 읽을 수있는 표현은 cppreference.com 에서 사용할 수 있습니다 .
관련 포인트는 다음과 같습니다.
- 휘발성이 없으므로 1) 및 2) 적용하지 마십시오.
- 데이터를 출력 / 쓰기하지 않거나 사용자에게 프롬프트하지 않으므로 3) 및 4) 적용되지 않습니다. 그러나 그렇게하더라도 EDIT1에서 분명히 만족할 것입니다 ( 원래의 예 에서도 분명하게 만족할 것 입니다. 순전히 이론적 인 관점에서는 프로그램 흐름과 출력이 이론적으로 다르기 때문에 불법이지만 두 단락 참조) 이하).
잡히지 않은 예외라도 잘 정의 된 (정의되지 않은!) 동작입니다. 그러나 엄밀히 말하면 new
(발생하지 않을 것입니다. 다음 단락을 참조하십시오), 관찰 가능한 동작은 프로그램의 종료 코드와 프로그램의 후반부에 나올 수있는 출력에 따라 다를 것입니다.
이제 단일 작은 할당의 특정 경우에 할당이 실패하지 않는다는 것을 보장 할 수있는 "의심의 이점"을 컴파일러에 제공 할 수 있습니다 . 메모리 부족이 매우 심한 시스템에서도 사용 가능한 최소 할당 세분성보다 적을 때 프로세스를 시작할 수 없으며을 호출하기 전에 힙도 설정됩니다 . 따라서이 할당이 실패하면 프로그램이 시작되지 않거나 호출 되기 전에 이미 비정상적인 종료에 도달했을 것 입니다. 컴파일러가 이것을 알고 있다고 가정하면, 이론적으로 할당 이 던질 수 있지만 컴파일러가 할 수 있기 때문에 원래 예제를 최적화하는 것도 합법적입니다.main
main
실제로 발생하지 않을 것임을 보장합니다.
<약간 미정>
반면에 EDIT2 예제에서 할당을 최적화하는 것은 허용 되지 않습니다 (그리고 관찰 할 수 있듯이 컴파일러 버그). 값은 외부에서 관찰 가능한 효과 (반환 코드)를 생성하는 데 사용됩니다.
당신이 대체 할 경우주의 new (std::nothrow) int[1000]
와 new (std::nothrow) int[1024*1024*1024*1024ll]
오늘날의 컴퓨터에 - -입니다 (! 그가 4TiB 할당의) 실패 보장은 여전히 호출을 최적화합니다. 즉, 0을 출력해야하는 코드를 작성했지만 1을 반환합니다.
@Yakk는 이것에 대해 좋은 주장을 내놓았습니다. 메모리를 건드리지 않는 한 포인터가 반환 될 수 있으며 실제 RAM이 필요하지 않습니다. EDIT2에서 할당을 최적화하는 것이 합법적입니다. 나는 여기서 누가 옳고 누가 그른지 확신 할 수 없다.
4TiB 할당을 수행하는 것은 OS가 페이지 테이블을 생성해야하기 때문에 최소한 2 자리 기가 바이트 양의 RAM과 같은 것이없는 시스템에서 실패 할 가능성이 거의 없습니다. 물론 C ++ 표준은 페이지 테이블이나 OS가 메모리를 제공하기 위해 수행하는 작업에 대해 신경 쓰지 않습니다.
그러나 반면에 "메모리가 건드리지 않으면 작동한다"는 가정은 정확히 그러한 세부 사항과 OS가 제공하는 것에 의존 합니다. 만지지 않은 RAM이 실제로 필요하지 않다는 가정 은 OS가 가상 메모리를 제공 하기 때문에 사실 입니다. 이는 OS가 페이지 테이블을 생성해야 함을 의미합니다 (모르는 척 할 수는 있지만 어쨌든 의존한다는 사실은 바뀌지 않습니다).
따라서 먼저 하나를 가정하고 "하지만 다른 하나는 신경 쓰지 않는다"라고 말하는 것은 100 % 옳지 않다고 생각합니다.
따라서 컴파일러 는 메모리가 건드리지 않는 한 일반적으로 4TiB 할당이 완벽하게 가능 하다고 가정 할 수 있으며 일반적으로 성공할 수 있다고 가정 할 수 있습니다 . 성공할 가능성이 있다고 가정 할 수도 있습니다 (그렇지 않은 경우에도). 그러나 어떤 경우에도 실패 가능성이있을 때 무언가 작동 해야 한다고 가정 해서는 안된다고 생각 합니다 . 그리고 실패의 가능성이있을뿐만 아니라,이 예에서 실패는 가능성 이 더 높습니다 .
</ 약간 미정>
스 니펫에서 발생할 수있는 최악의 상황 은 처리되지 않은 new
throws std::bad_alloc
입니다. 그런 다음 발생하는 일은 구현에 따라 정의됩니다.
최상의 경우는 no-op이고 최악의 경우는 정의되지 않은 상태에서 컴파일러는이를 존재하지 않는 것으로 간주 할 수 있습니다. 이제 실제로 가능한 예외를 포착하려고 시도하면 다음과 같습니다.
int main() try {
int* mem = new int[100];
return 0;
} catch(...) {
return 1;
}
... 그러면에 대한 호출 operator new
이 유지됩니다 .
'ProgramingTip' 카테고리의 다른 글
Django에서 'max_length'의 최대 크기는 얼마입니까? (0) | 2020.11.15 |
---|---|
하나의 함수 호출로 R data.table에 여러 열을 추가 하시겠습니까? (0) | 2020.11.15 |
Gradle 로그는 어디에 있습니까? (0) | 2020.11.15 |
Elisp에서 기호를 공유로 변환 (0) | 2020.11.15 |
Qt Quick 대 Qt 위젯 (0) | 2020.11.15 |