프로세스에 대한 메모리 할당이 느리고 더 빠를 수 있는 이유는 무엇입니까?
저는 가상 메모리가 어떻게 작동하는지 비교적 잘 알고 있습니다.모든 프로세스 메모리는 페이지로 분할되며 가상 메모리의 모든 페이지는 실제 메모리의 페이지 또는 스왑 파일의 페이지에 매핑되거나 물리적 페이지가 여전히 할당되지 않은 새 페이지가 될 수 있습니다.OS는 애플리케이션이 메모리를 요구할 때가 아니라 새로운 페이지를 요구할 때 실제 메모리에 매핑합니다.malloc, 애플리케이션이 할당된 메모리에서 모든 페이지에 실제로 액세스할 때만 가능합니다.그래도 궁금한 게 있어요.
리눅스로 앱을 프로파일링할 때 이 사실을 알게 되었습니다.perf도구.
커널 기능에 걸리는 시간의 약 20%가 있습니다.clear_page_orig,__do_page_fault그리고.get_page_from_free_list. 이것은 제가 이 일에 대해 예상했던 것보다 훨씬 많은 것이고 저는 몇 가지 조사를 했습니다.
몇 가지 작은 예로 시작하겠습니다.
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define SIZE 1 * 1024 * 1024
int main(int argc, char *argv[]) {
int i;
int sum = 0;
int *p = (int *) malloc(SIZE);
for (i = 0; i < 10000; i ++) {
memset(p, 0, SIZE);
sum += p[512];
}
free(p);
printf("sum %d\n", sum);
return 0;
}
예를 들어,memset메모리 바인딩된 처리일 뿐입니다.이 경우, 우리는 작은 메모리 덩어리를 한 번 할당하고 그것을 반복합니다.이 프로그램을 다음과 같이 실행합니다.
$ gcc -O1 ./mem.c && time ./a.out
-O1필요한 이유는clang와 함께-O2루프를 완전히 제거하고 값을 즉시 계산합니다.
결과는 사용자: 0.520s, sys: 0.008s입니다.에 따르면perf, 이 시간의 99%는memset부터libc. 따라서 이 경우 쓰기 성능은 약 20기가바이트/s로 제 메모리의 이론적 성능인 12.5Gb/s보다 높습니다.이것은 L3 CPU 캐시 때문인 것 같습니다.
테스트를 변경하고 루프에서 메모리 할당을 시작합니다(코드의 동일한 부분은 반복하지 않습니다).
#define SIZE 1 * 1024 * 1024
for (i = 0; i < 10000; i ++) {
int *p = (int *) malloc(SIZE);
memset(p, 0, SIZE);
free(p);
}
결과는 완전히 같습니다.나는 그렇게 믿어요.free실제로 OS에 메모리를 사용할 수 있는 것은 아니고, 단지 그 과정 내에서 메모리를 사용할 수 있는 목록에 넣었을 뿐입니다. 그리고.malloc다음 반복에서 정확히 동일한 메모리 블록을 얻을 수 있습니다.그것이 눈에 띄는 차이가 없는 이유입니다.
1 메가바이트에서 크기를 늘려야 합니다.실행 시간은 조금씩 증가하여 10메가바이트 가까이 포화될 것입니다(저는 10메가바이트와 20메가바이트 사이에 차이가 없습니다).
#define SIZE 10 * 1024 * 1024
for (i = 0; i < 1000; i ++) {
int *p = (int *) malloc(SIZE);
memset(p, 0, SIZE);
free(p);
}
시간 표시: 사용자: 1.184s, sys: 0.004s.perf여전히 99% 이상의 시간이 사용되고 있음을 보고합니다.memset, 그러나 처리량은 약 8.3Gb/s입니다.그 시점에서, 무슨 일이 일어나고 있는지 이해합니다.
메모리 블록 크기를 계속 늘리면 사용자: 0.724s, sys: 3.300s 중 어느 시점(35Mb의 경우) 실행 시간이 크게 증가합니다.
#define SIZE 40 * 1024 * 1024
for (i = 0; i < 250; i ++) {
int *p = (int *) malloc(SIZE);
memset(p, 0, SIZE);
free(p);
}
에 따르면perf,memset시간의 18%만 소비합니다.
분명히 메모리는 OS에서 할당되고 각 단계에서 해제됩니다.앞서 말씀드린 대로 OS는 할당된 각 페이지를 삭제한 후 사용해야 합니다.그래서 27.3%의clear_page_orig특별해 보이지 않습니다. 클리어 멤의 경우 4초 * 0.273 ≈ 1.1초입니다. 세 번째 예에서도 마찬가지입니다.memset17.9%가 소요되었으며, 이로 인해 700msec의 ≈이 발생합니다. 이는 메모리가 L3 캐시에 이미 저장되어 있기 때문에 정상입니다.clear_page_orig(첫 번째 및 두 번째 예제).
이해할 수 없는 것 - 마지막 사례가 단순한 경우보다 2배 느린 이유memset +력 +memsetL3 캐시? 이거 뭐 좀 해도 요?이거 가지고 뭐 좀 해도 돼요?
결과는 기본 Mac OS, VMware 산하 Ubuntu 및 Amazon c4.large 인스턴스에서 재현할 수 있습니다(작은 차이가 있음).
또한 두 가지 수준에서 최적화할 여지가 있다고 생각합니다.
- OS 차원에서OS가 페이지를 이전에 속해 있던 것과 동일한 응용 프로그램으로 반환하는 것을 알고 있다면 해당 페이지를 지울 수 없습니다.
- CPU 레벨에 있습니다.CPU가 페이지가 비어 있었다는 것을 알고 있다면 메모리의 페이지를 지울 수 없습니다.캐시에서 어느 정도 처리한 후에야 캐시에서 지우고 메모리로 옮길 수 있습니다.
여기서 일어나고 있는 일은 몇 가지 다른 시스템을 포함하기 때문에 약간 복잡합니다. 그러나 컨텍스트 전환 비용과는 전혀 관련이 없습니다. 프로그램에서 시스템 호출을 거의 하지 않습니다(Strace를 사용하여 이를 확인하십시오.
첫째, 방법에 대한 몇 가지 기본 원칙을 이해하는 것이 중요합니다.malloc구현은 일반적으로 다음과 같이 작동합니다.
- 대부분의.
malloc구현은 OS에서 호출을 통해 많은 메모리를 얻습니다.sbrk아니면mmap초기화 중에획득된 메모리의 양은 일부에서 조정될 수 있습니다.malloc실행.일단 메모리가 획득되면, 그것은 일반적으로 프로그램이 예를 들어, 메모리를 요청할 때, 예를 들어, 그것이 다른 크기 클래스들로 절단되고 데이터 구조로 배열된다,malloc(123),malloc구현을 통해 이러한 요구사항에 맞는 메모리를 신속하게 찾을 수 있습니다. - 전화할때
free, 메모리는 무료 목록으로 반환되며 다음 호출 시 다시 사용할 수 있습니다.malloc.malloc구현을 통해 이 기능의 작동 방식을 정확하게 조정할 수 있습니다. - 대용량 메모리 청크를 할당할 경우 대부분
malloc구현들은 단순히 거대한 양의 메모리에 대한 요구를 곧바로 전달할 것입니다.mmap시스템 호출, 즉 한 번에 "페이지"의 메모리를 할당합니다.대부분의 시스템에서 메모리 1페이지는 4096바이트입니다. - 이와 관련하여 대부분의 OS는 메모리 페이지를 삭제한 후 다음을 통해 메모리를 요청한 프로세스에 배포합니다.
mmap아니면sbrk. 이것이 당신이 전화를 받는 이유입니다.clear_page_orig성능 출력에서는 메모리 하려고 시도하고 .이 함수는 메모리 페이지에 0을 기록하려고 시도하고 있습니다.
이러한 원칙들은 많은 이름을 가지고 있지만 흔히 "수요 페이징"이라고 불리는 또 다른 개념과 교차합니다."요구 페이징"이 의미하는 것은 사용자 프로그램이 OS에 메모리 덩어리를 요청할 때(예: 호출)mmap), 메모리는 프로세스의 가상 주소 공간에 할당되지만 해당 메모리를 지원하는 물리적 RAM은 아직 없습니다.
다음은 수요 페이징 프로세스의 개요입니다.
- 라는 프로그램.
mmap500MB의 RAM합니다를 합니다. - 커널은 요청된 500MB의 RAM에 대해 프로세스의 주소 공간에 있는 주소 영역을 매핑합니다.가상 주소를 지원하기 위해 물리적 RAM을 "소수"(OS 종속) 페이지(각각 4096바이트)에 매핑합니다.
- 사용자 프로그램이 메모리에 쓰기를 시작합니다.
- 결국 사용자 프로그램은 유효하지만 이를 지원하는 물리적 RAM이 없는 주소에 액세스합니다.
- 이로 인해 CPU에 페이지 장애가 발생합니다.
- 커널은 프로세스가 유효한 주소에 액세스하지만 물리적 RAM이 지원되지 않는 주소에 액세스하는 것을 보고 페이지 오류에 응답합니다.
- 그런 다음 커널은 해당 영역에 할당할 RAM을 찾습니다.먼저 다른 프로세스의 메모리를 디스크에 기록해야 하는 경우("스왑 아웃") 이 작업이 느려질 수 있습니다.
마지막 경우에 성능 저하가 발생한 가장 가능성이 높은 이유는 다음과 같습니다.
- 커널은 40MB의 요청을 이행하기 위해 배포할 수 있는 메모리의 0'd 페이지가 부족하여 성능 출력에 따라 계속해서 메모리를 0으로 만들고 있습니다.
- 아직 매핑되지 않은 메모리에 액세스할 때 페이지 장애가 발생합니다.10mb가 아닌 40mb에 액세스하기 때문에 매핑해야 하는 메모리 페이지가 많아지므로 페이지 오류가 더 많이 발생합니다.
- 또 다른 대답이 지적했듯이,
memsetO(n)은 메모리를 더 많이 쓸수록 더 오래 걸린다는 뜻입니다. - 요즘은 40mb가 RAM이 많지 않기 때문에 가능성은 낮지만, 충분한 RAM이 있는지 확인하기 위해 시스템의 여유 메모리 양을 확인합니다.
애플리케이션이 성능에 극도로 민감한 경우 대신 전화를 걸 수 있습니다.mmap및:
- 을 통과합니다
MAP_POPULATE플래그를 지정하면 모든 페이지 오류가 발생하고 모든 물리적 메모리를 에 매핑할 수 있습니다. 그러면 액세스 시 페이지 오류에 대한 비용을 지불하지 않게 됩니다. - 을 통과합니다
MAP_UNINITIALIZED프로세스에 배포하기 전에 메모리 페이지를 영점화하지 않도록 시도하는 플래그입니다.이 플래그를 사용하는 것은 보안 문제이므로 이 옵션을 사용할 때의 의미를 완전히 이해하지 못하는 한 사용해서는 안 됩니다.프로세스가 민감한 정보를 저장하기 위해 관련이 없는 다른 프로세스에 의해 사용된 메모리 페이지를 발행할 수 있습니다.이 옵션을 허용하려면 커널을 컴파일해야 합니다.AWS 리눅스 커널과 같은 대부분의 커널에는 이 옵션이 기본적으로 활성화되어 있지 않습니다.이 옵션을 사용하면 안 됩니다.
이러한 최적화 수준은 거의 항상 실수입니다. 대부분의 응용 프로그램은 페이지 장애 비용을 최적화하지 않는 최적화를 위한 훨씬 낮은 지연 효과를 가지고 있습니다.실제 애플리케이션에서는 다음을 권장합니다.
- 사용을 피함
memset그것이 정말로 필요하지 않은 한, 큰 메모리 블록에.대부분의 경우 동일한 프로세스에 의해 재사용되기 전에 메모리를 영점화할 필요가 없습니다. - 동일한 메모리 덩어리를 반복적으로 할당하고 빈 공간을 확보하는 것을 방지합니다. 큰 블록을 앞에 할당하고 나중에 필요에 따라 다시 사용할 수도 있습니다.
- 으로.
MAP_POPULATE접근 시 페이지 장애의 비용이 성능에 정말로 해로울 경우 위의 플래그(unlikely).
궁금한 점이 있으시면 댓글로 남겨주시면 필요하다면 이 게시물을 조금 더 확대해서 편집하겠습니다.
확실하지는 않지만, 사용자 모드에서 커널로 컨텍스트를 전환하는 비용에 베팅할 용의가 있습니다. 그리고 다시 말해서, 다른 모든 것을 지배하고 있습니다.memset또한 상당한 시간이 걸립니다. O(n)이 될 것이라는 것을 기억하세요.
갱신하다
저는 무료가 실제로 OS의 메모리를 무료로 제공하는 것이 아니라, 단지 그 과정 내에서 무료 목록에 넣었다고 생각합니다.그리고 malloc은 다음 반복에서 정확히 같은 메모리 블록을 얻습니다.그것이 눈에 띄는 차이가 없는 이유입니다.
이것은 원칙적으로 맞는 말입니다.더클래식malloc구현은 단일 링크 리스트에 메모리를 할당합니다.free는 단순히 할당이 더 이상 사용되지 않는다는 플래그를 설정합니다.시간이 흐를수록,malloc충분히 큰 자유 블록을 찾을 수 있을 때 처음으로 재할당합니다.이것은 충분히 잘 작동하지만 파편화로 이어질 수 있습니다.
현재 여러 가지 슬라이서 구현이 있습니다. 이 위키피디아 기사를 참조하십시오.
언급URL : https://stackoverflow.com/questions/39947921/why-is-memory-allocation-for-processes-slow-and-can-it-be-faster
'programing' 카테고리의 다른 글
| SQL: 여러 테이블에서 조합 선택 개수(*) (0) | 2023.10.31 |
|---|---|
| mysql의 테이블에서 N개의 레코드를 선택하는 방법 (0) | 2023.10.31 |
| jQuery scroll() 사용자가 스크롤을 중지할 때 탐지 (0) | 2023.10.26 |
| 디렉터리에 있는 모든 파일 목록? (0) | 2023.10.26 |
| mysql 열의 '적절한 경우' 형식을 수행하는 방법은? (0) | 2023.10.26 |

