programing

프로세스에 대한 메모리 할당이 느리고 더 빠를 수 있는 이유는 무엇입니까?

stoneblock 2023. 10. 26. 20:37

프로세스에 대한 메모리 할당이 느리고 더 빠를 수 있는 이유는 무엇입니까?

저는 가상 메모리가 어떻게 작동하는지 비교적 잘 알고 있습니다.모든 프로세스 메모리는 페이지로 분할되며 가상 메모리의 모든 페이지는 실제 메모리의 페이지 또는 스왑 파일의 페이지에 매핑되거나 물리적 페이지가 여전히 할당되지 않은 새 페이지가 될 수 있습니다.OS는 애플리케이션이 메모리를 요구할 때가 아니라 새로운 페이지를 요구할 때 실제 메모리에 매핑합니다.malloc, 애플리케이션이 할당된 메모리에서 모든 페이지에 실제로 액세스할 때만 가능합니다.그래도 궁금한 게 있어요.

리눅스로 앱을 프로파일링할 때 이 사실을 알게 되었습니다.perf도구.

enter image description here

커널 기능에 걸리는 시간의 약 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%만 소비합니다.

enter image description here

분명히 메모리는 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구현은 일반적으로 다음과 같이 작동합니다.

  1. 대부분의.malloc구현은 OS에서 호출을 통해 많은 메모리를 얻습니다.sbrk아니면mmap초기화 중에획득된 메모리의 양은 일부에서 조정될 수 있습니다.malloc실행.일단 메모리가 획득되면, 그것은 일반적으로 프로그램이 예를 들어, 메모리를 요청할 때, 예를 들어, 그것이 다른 크기 클래스들로 절단되고 데이터 구조로 배열된다,malloc(123),malloc구현을 통해 이러한 요구사항에 맞는 메모리를 신속하게 찾을 수 있습니다.
  2. 전화할때free, 메모리는 무료 목록으로 반환되며 다음 호출 시 다시 사용할 수 있습니다.malloc.malloc구현을 통해 이 기능의 작동 방식을 정확하게 조정할 수 있습니다.
  3. 대용량 메모리 청크를 할당할 경우 대부분malloc구현들은 단순히 거대한 양의 메모리에 대한 요구를 곧바로 전달할 것입니다.mmap시스템 호출, 즉 한 번에 "페이지"의 메모리를 할당합니다.대부분의 시스템에서 메모리 1페이지는 4096바이트입니다.
  4. 이와 관련하여 대부분의 OS는 메모리 페이지를 삭제한 후 다음을 통해 메모리를 요청한 프로세스에 배포합니다.mmap아니면sbrk. 이것이 당신이 전화를 받는 이유입니다.clear_page_orig성능 출력에서는 메모리 하려고 시도하고 .이 함수는 메모리 페이지에 0을 기록하려고 시도하고 있습니다.

이러한 원칙들은 많은 이름을 가지고 있지만 흔히 "수요 페이징"이라고 불리는 또 다른 개념과 교차합니다."요구 페이징"이 의미하는 것은 사용자 프로그램이 OS에 메모리 덩어리를 요청할 때(예: 호출)mmap), 메모리는 프로세스의 가상 주소 공간에 할당되지만 해당 메모리를 지원하는 물리적 RAM은 아직 없습니다.

다음은 수요 페이징 프로세스의 개요입니다.

  1. 라는 프로그램.mmap500MB의 RAM합니다를 합니다.
  2. 커널은 요청된 500MB의 RAM에 대해 프로세스의 주소 공간에 있는 주소 영역을 매핑합니다.가상 주소를 지원하기 위해 물리적 RAM을 "소수"(OS 종속) 페이지(각각 4096바이트)에 매핑합니다.
  3. 사용자 프로그램이 메모리에 쓰기를 시작합니다.
  4. 결국 사용자 프로그램은 유효하지만 이를 지원하는 물리적 RAM이 없는 주소에 액세스합니다.
  5. 이로 인해 CPU에 페이지 장애가 발생합니다.
  6. 커널은 프로세스가 유효한 주소에 액세스하지만 물리적 RAM이 지원되지 않는 주소에 액세스하는 것을 보고 페이지 오류에 응답합니다.
  7. 그런 다음 커널은 해당 영역에 할당할 RAM을 찾습니다.먼저 다른 프로세스의 메모리를 디스크에 기록해야 하는 경우("스왑 아웃") 이 작업이 느려질 수 있습니다.

마지막 경우에 성능 저하가 발생한 가장 가능성이 높은 이유는 다음과 같습니다.

  1. 커널은 40MB의 요청을 이행하기 위해 배포할 수 있는 메모리의 0'd 페이지가 부족하여 성능 출력에 따라 계속해서 메모리를 0으로 만들고 있습니다.
  2. 아직 매핑되지 않은 메모리에 액세스할 때 페이지 장애가 발생합니다.10mb가 아닌 40mb에 액세스하기 때문에 매핑해야 하는 메모리 페이지가 많아지므로 페이지 오류가 더 많이 발생합니다.
  3. 또 다른 대답이 지적했듯이,memsetO(n)은 메모리를 더 많이 쓸수록 더 오래 걸린다는 뜻입니다.
  4. 요즘은 40mb가 RAM이 많지 않기 때문에 가능성은 낮지만, 충분한 RAM이 있는지 확인하기 위해 시스템의 여유 메모리 양을 확인합니다.

애플리케이션이 성능에 극도로 민감한 경우 대신 전화를 걸 수 있습니다.mmap및:

  1. 을 통과합니다MAP_POPULATE플래그를 지정하면 모든 페이지 오류가 발생하고 모든 물리적 메모리를 에 매핑할 수 있습니다. 그러면 액세스 시 페이지 오류에 대한 비용을 지불하지 않게 됩니다.
  2. 을 통과합니다MAP_UNINITIALIZED프로세스에 배포하기 전에 메모리 페이지를 영점화하지 않도록 시도하는 플래그입니다.이 플래그를 사용하는 것은 보안 문제이므로 이 옵션을 사용할 때의 의미를 완전히 이해하지 못하는 한 사용해서는 안 됩니다.프로세스가 민감한 정보를 저장하기 위해 관련이 없는 다른 프로세스에 의해 사용된 메모리 페이지를 발행할 수 있습니다.이 옵션을 허용하려면 커널을 컴파일해야 합니다.AWS 리눅스 커널과 같은 대부분의 커널에는 이 옵션이 기본적으로 활성화되어 있지 않습니다.이 옵션을 사용하면 안 됩니다.

이러한 최적화 수준은 거의 항상 실수입니다. 대부분의 응용 프로그램은 페이지 장애 비용을 최적화하지 않는 최적화를 위한 훨씬 낮은 지연 효과를 가지고 있습니다.실제 애플리케이션에서는 다음을 권장합니다.

  1. 사용을 피함memset그것이 정말로 필요하지 않은 한, 큰 메모리 블록에.대부분의 경우 동일한 프로세스에 의해 재사용되기 전에 메모리를 영점화할 필요가 없습니다.
  2. 동일한 메모리 덩어리를 반복적으로 할당하고 빈 공간을 확보하는 것을 방지합니다. 큰 블록을 앞에 할당하고 나중에 필요에 따라 다시 사용할 수도 있습니다.
  3. 으로.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