2014-12-12 5 views
3

다음 코드는 내 데비안 시스템에서 이상한 메모리 동작을 유발합니다. 지도가 지워진 후에도 htop은 프로그램이 여전히 많은 메모리를 사용하고 있음을 보여 주므로 메모리 누수가 있다고 생각합니다. 이상한 사실은 그것이 일부 상황에서만 나타납니다.std map 및 shared_ptr을 사용한 이상한 메모리 동작

#include <map> 
#include <iostream> 
#include <memory> 


int main(int argc, char** argv) 
{ 
    if (argc != 2) 
    { 
     std::cout << "Usage: " << argv[0] << " <1|0> " << std::endl; 
     std::cout << "1 to insert in the second map and see the problem " 
       "and 0 to not insert" << std::endl; 
     return 0; 
    } 

    bool insertion = atoi(argv[1]); 
    std::map<uint64_t, std::shared_ptr<std::string> > mapStd; 
    std::map<uint64_t, size_t> counterToSize; 
    size_t dataSize = 1024*1024; 
    uint64_t counter = 0; 

    while(counter < 10000) 
    { 
     std::shared_ptr<std::string> stringPtr = 
       std::make_shared<std::string>(dataSize, 'a'); 
     mapStd[counter] = stringPtr; 

     if (insertion) 
     { 
      counterToSize[counter] = dataSize; 
     } 
     if (counter > 500) 
     { 
      mapStd.erase(mapStd.begin()); 
     } 
     std::cout << "\rInserted chunk " << counter << std::flush; 

     counter++; 
    } 

    std::cout << std::endl << "Press ENTER to delete the maps" << std::endl; 
    char a; 
    std::cin.get(a); // wait for ENTER to be pressed 

    mapStd.clear(); // clear both maps 
    counterToSize.clear(); 

    std::cout << "Press ENTER to exit the program" << std::endl; 

    std::cin.get(a); // wait for ENTER to be pressed 
    return 0; 
} 

설명 :

코드는 스택에 두 개의 맵을 생성한다 (그러나이 힙에 생성되는 경우 문제가 동일합니다). 그런 다음 문자열의 std :: shared_ptr을 첫 번째 맵에 삽입합니다. 각 문자열의 크기는 1MB입니다. 500 개의 문자열이 삽입되면 새로운 삽입마다 첫 번째 문자열을 삭제하므로지도에 사용 된 총 메모리는 항상 500MB와 같습니다. 총 10000 개의 문자열이 삽입되면 프로그램은 사용자가 Enter 키를 누를 때까지 기다립니다. 프로그램을 실행하고 첫 번째 인수로 1을 전달하면 첫 번째 맵에 삽입 될 때마다 두 번째 맵에도 다른 삽입이 수행됩니다. 첫 번째 인수가 0이면 두 번째 맵은 사용되지 않습니다. 일단 ENTER를 누르면 두 맵이 모두 지워집니다. 프로그램이 계속 실행되고 ENTER 키를 다시 기다린 다음 종료합니다. 후 (맵이 지워집니다 따라서 후)을 누르면 ENTER 내 64 비트 데비안 3.2.54-2에

  • 때, 그리고 프로그램이 함께 시작됩니다 : 여기

    는 사실이다 1을 첫 번째 인수로 (따라서 두 번째 맵에 삽입하여) htop은 프로그램이 여전히 500MB의 메모리를 사용함을 나타냅니다! 프로그램이 첫 번째 인수로 0으로 시작되면 메모리가 올바르게 해제됩니다.

  • 이 기계는 g ++ 4.7.2 및 libstdC++. so.6.0.17을 사용합니다. 나는 g ++ 4.8.2와 libstdC++, so.6.0.18, 같은 문제를 시도했다.

  • 저는 g ++ 4.9.2와 libstdC++. so.6.0.20, 같은 문제로 64 비트 페도라 21을 시험해 보았습니다.
  • 32 비트 g ++ 4.8.2 및 libstdC++. so.6.0.19의 우분투 14.04를 사용해 보았습니다. 문제가 나타나지 않습니다!
  • 32 비트 g ++ 4.7.2 및 libstdC++. so.6.0.17을 사용하는 Debian 3.2.54-2를 시도했지만 문제가 나타나지 않습니다!
  • 64 비트 Windows에서 시도했지만 문제가 나타나지 않습니다! 문제는 당신이지도 지워 줄을 (반전 경우 명확한 먼저 uint64_t는,이 size_t지도, 문제가 사라질 경우에 따라서!

누군가는 모든 설명이 있습니까, 존재하는 시스템에서

  • 이것은

  • +1

    메모리 할당자는 C++ 표준 라이브러리에 의해 구현되며 gcc는 libc의'malloc'을 사용합니다. 또한 각 테스트 머신에서 사용중인 libc 버전을 포함시켜야합니다. Windows는 컴파일러에 따라 자체적으로 완전히 다른 구현을 가지고 있습니다. –

    +4

    참조 http://unix.stackexchange.com/questions/53447/does-free-unmap-the-memory-of-a-process, http://stackoverflow.com/questions/14023815/memory-stability-of -ac-application-in-linux, http://stackoverflow.com/questions/13232119/memory-usage-doesnt-decrease-when-free-used, http://stackoverflow.com/questions/1119134/how-do -malloc-and-free-work – nos

    답변

    3

    here 다음에 libc malloc을 사용하는 것이 좋습니다. 기본적으로 libc malloc은 "큰"할당량 (> 128k)에 대해 mmap을 사용하고 작은 할당량에 대해서는 brk/freelists를 사용하여 시작합니다. free'd이면 malloc을 사용할 수있는 크기를 조정하려고 시도하지만 크기가 max (첫 번째 링크에서 정의 됨)보다 작은 경우에만 적용됩니다 .32 비트의 경우, 당신의 문자열은 max보다 길기 때문에 큰 할당을 위해 mmap/munmap을 계속 사용하고 더 작은 맵 노드 할당만을 sbrk를 사용하여 시스템에서 검색 한 메모리에 저장합니다. 그래서 32 비트 시스템에서 '문제'를 보지 못합니다.

    다른 비트는 조각화 중 하나이며 자유롭게 메모리를 병합하여 시스템에 반환하려고 할 때 사용됩니다.기본적으로 무료 회원은 무료 목록에 작은 블록을 붙이기 만하면 다음 요청을 할 준비가됩니다. 충분히 큰 블록이 힙의 맨 위에 free'd되면 시스템에 메모리를 반환하려고 시도합니다 see comment here. 임계 값은 64K입니다.

    1을 전달한 경우의 할당 패턴은 counterToSize 맵의 일부 요소가 힙의 맨 위에 오게되어 문자열 중 하나의 마지막 릴리스가 free'd하지 못하도록합니다. counterToSize 맵 내의 다양한 오브젝트의 릴리스는 임계 값을 트리거 할만큼 충분히 크지 않습니다.

    .clear() 호출 순서를 변경하면 예상했던 것처럼 메모리가 해제된다는 것을 알 수 있습니다. 또한 메모리의 큰 덩어리를 할당하고 클리어 한 후 즉시 해제하면 릴리스가 실행됩니다. (이 경우 큰 크기는 128 바이트 이상이어야합니다. 즉, 최대 크기는 빠른 크기의 저장소를 트리거하는 데 사용됩니다. (즉, 크기가 자유롭고 크기가 할당 된 크기보다 작 으면 목록에 추가됩니다.)

    기본적으로 문제는 아니며 매핑 된 일부 페이지가 있습니다. 사용자는 아무 것도 사용하지 않지만이를 해제 한 마지막 프리가 너무 작아 코드 경로를 트리거 할 수 없습니다. 다음에 시도 할 때 (메모리를 늘리지 않고 전체 루프를 다시 할 수 있습니다.)

    오, 그리고 malloc_trim()을 손으로 불러 와서 실제로 그 시점에서 페이지가 필요했습니다.

    +0

    malloc_trim이 작동합니다. 팁을 제공해 주셔서 감사합니다. 그러나 클리어 한 직후 청크를 할당한다고해서 릴리스가 실행되는 것은 아닙니다. 나는 64 바이트에서 10 메가 바이트까지 다른 크기로 시도했다 ... – Mike

    +1

    'int64_t * foo = new int64_t [16]; delete [] foo;'그것은 비어있게됩니다. 16보다 작은 크기는 할당이 fastbin으로 바로 들어가고 다른 모든 논리를 건너 뛰기 때문에 트리거되지 않습니다. 크기보다 크면 근처의 청크와 병합 된 다음 크기가 검사되어 빈 목록에 삽입되고 정리가 트리거됩니다. 방금 메모리를 할당 한 경우 릴리스 논리를 트리거하지 않았습니다. – Charlie

    관련 문제