C/C++ 언어 프로그래밍에서 까다로운 부분 중의 하나는 획득했던(할당받았던) 메모리를 여하히 해제하느냐이다. 프로그램밍의 사소한 오류로 메모리 부족 현상이 쉽게 일어나기 때문이다.

프로그램밍의 사소한 오류로 메모리 부족 현상이 쉽게 일어나기 때문이다.

자바 언어, 파이썬 언어, C# 언어 등은 자동으로 쓰레기 수집(garbage collection)이 이루어지므로 거의 신경쓰지 않아도 되지만, C 언어는 소스 작성하는 프로그래머가 직접해야 한다.

오래 전(10여년 전)에 Hans Boehm 가 만들었던 C/C++ 언어용 쓰레기 수집기(Boehm-Demers-Weiser conservative garbage collector, 줄여사 BoehmGC 러고 부름)를 이용하여 컴파일되고 실행되는 소스이다. 이 쓰레기 수집기를 쓰면, 메모리 해제(free)를 프로그래머가 신경쓰지 않아도 된다. 그렇게 하기 위해서는 헤더 파일 추가 구문

                    #include "gc.h"     또는      #include "gc/gc.h"
        또는      #include "gc_cpp.h"     또는      #include "gc/gc_cpp.h"

가 있어야 하고,  함수 malloc(크기) 대신에 매크로 GC_MALLOC(크기) 를, 함수 realloc(포인터, 크기) 대신에 매크로 GC_REALLOC(포인터, 크기) 를 사용해야 한다. 좀 더 좋은 성능을 위해서는 GC_MALLOC_ATOMIC(크기) 과 GC_enable_incremental() 을 사용하기도 한다. GC_MALLOC_ATOMIC 은 포인터를 전혀 갖지 않은 객체에 대하여만 적용된다. 


MinGW 의 gcc 나 Visual C++ 2010 용으로 동작하는지는 아직 확인 되지는 않았으나, Cygwin에는 Hans Boehm 의 쓰레기 수집기가 이미 설치되어 있어서, Cygwin 의  g++ 4.5.3 으로 컴파일할 수 있었다.

아래의 두 소스는 워낙 오래된 것이라서 #include 부분과 using namespace 부분을 수정 또는 추가하여 현재의 컴파일러로 컴파일되게 고쳤다.

* C++ 언어용 쓰레기 수집  벤치마크 테스트 (파일명: GCBench_OGC.cpp)

// This is adapted from a benchmark written by John Ellis and Pete Kovac
// of Post Communications.
// It was modified by Hans Boehm of Silicon Graphics.
// Translated to C++ 30 May 1997 by William D Clinger of Northeastern Univ.
//
//      This is no substitute for real applications.  No actual application
//      is likely to behave in exactly this way.  However, this benchmark was
//      designed to be more representative of real applications than other
//      Java GC benchmarks of which we are aware.
//      It attempts to model those properties of allocation requests that
//      are important to current GC techniques.
//      It is designed to be used either to obtain a single overall performance
//      number, or to give a more detailed estimate of how collector
//      performance varies with object lifetimes.  It prints the time
//      required to allocate and collect balanced binary trees of various
//      sizes.  Smaller trees result in shorter object lifetimes.  Each cycle
//      allocates roughly the same amount of memory.
//      Two data structures are kept around during the entire process, so
//      that the measured performance is representative of applications
//      that maintain some live in-memory data.  One of these is a tree
//      containing many pointers.  The other is a large array containing
//      double precision floating point numbers.  Both should be of comparable
//      size.
//
//      The results are only really meaningful together with a specification
//      of how much memory was used.  It is possible to trade memory for
//      better time performance.  This benchmark should be run in a 32 MB
//      heap, though we don't currently know how to enforce that uniformly.
//
//      Unlike the original Ellis and Kovac benchmark, we do not attempt
//      measure pause times.  This facility should eventually be added back
//      in.  There are several reasons for omitting it for now.  The original
//      implementation depended on assumptions about the thread scheduler
//      that don't hold uniformly.  The results really measure both the
//      scheduler and GC.  Pause time measurements tend to not fit well with
//      current benchmark suites.  As far as we know, none of the current
//      commercial Java implementations seriously attempt to minimize GC pause
//      times.

#include <new>          // #include <new.h>
#include <iostream>     // #include <iostream.h>
#include <sys/time.h>

#ifdef GC
#  include "gc.h"
#endif

using namespace std;

//  These macros were a quick hack for the Macintosh.
//
//  #define currentTime() clock()
//  #define elapsedTime(x) ((1000*(x))/CLOCKS_PER_SEC)

#define currentTime() stats_rtclock()
#define elapsedTime(x) (x)

/* Get the current time in milliseconds */

unsigned
stats_rtclock( void )
{
  struct timeval t;
  struct timezone tz;

  if (gettimeofday( &t, &tz ) == -1)
    return 0;
  return (t.tv_sec * 1000 + t.tv_usec / 1000);
}

static const int kStretchTreeDepth    = 18;      // about 16Mb
static const int kLongLivedTreeDepth  = 16;  // about 4Mb
static const int kArraySize  = 500000;  // about 4Mb
static const int kMinTreeDepth = 4;
static const int kMaxTreeDepth = 16;

typedef struct Node0 *Node;

struct Node0 {
        Node left;
        Node right;
        int i, j;
        Node0(Node l, Node r) { left = l; right = r; }
        Node0() { left = 0; right = 0; }
#       ifndef GC
          ~Node0() { if (left) delete left; if (right) delete right; }
# endif
};

struct GCBench {

        // Nodes used by a tree of a given size
        static int TreeSize(int i) {
                return ((1 << (i + 1)) - 1);
        }

        // Number of iterations to use for a given tree depth
        static int NumIters(int i) {
                return 2 * TreeSize(kStretchTreeDepth) / TreeSize(i);
        }

        // Build tree top down, assigning to older objects.
        static void Populate(int iDepth, Node thisNode) {
                if (iDepth<=0) {
                        return;
                } else {
                        iDepth--;
#   ifndef GC
                          thisNode->left  = new Node0();
                          thisNode->right = new Node0();
#   else
                          thisNode->left  = new (GC_NEW(Node0)) Node0();
                          thisNode->right = new (GC_NEW(Node0)) Node0();
#   endif
                        Populate (iDepth, thisNode->left);
                        Populate (iDepth, thisNode->right);
                }
        }

        // Build tree bottom-up
        static Node MakeTree(int iDepth) {
                if (iDepth<=0) {
#       ifndef GC
                        return new Node0();
#       else
                        return new (GC_NEW(Node0)) Node0();
#       endif
                } else {
#       ifndef GC
                        return new Node0(MakeTree(iDepth-1),
                                         MakeTree(iDepth-1));
#       else
                        return new (GC_NEW(Node0)) Node0(MakeTree(iDepth-1),
                                            MakeTree(iDepth-1));
#       endif
                }
        }

        static void PrintDiagnostics() {
#if 0
                long lFreeMemory = Runtime.getRuntime().freeMemory();
                long lTotalMemory = Runtime.getRuntime().totalMemory();

                System.out.print(" Total memory available="
                                 + lTotalMemory + " bytes");
                System.out.println("  Free memory=" + lFreeMemory + " bytes");
#endif
        }

        static void TimeConstruction(int depth) {
                long    tStart, tFinish;
                int     iNumIters = NumIters(depth);
                Node    tempTree;

                cout << "Creating " << iNumIters
                     << " trees of depth " << depth << endl;
               
                tStart = currentTime();
                for (int i = 0; i < iNumIters; ++i) {
#   ifndef GC
                          tempTree = new Node0();
#   else
                          tempTree = new (GC_NEW(Node0)) Node0();
#   endif
                        Populate(depth, tempTree);
#          ifndef GC
                          delete tempTree;
#   endif
                        tempTree = 0;
                }
                tFinish = currentTime();
                cout << "\tTop down construction took "
                     << elapsedTime(tFinish - tStart) << " msec" << endl;
                    
                tStart = currentTime();
                for (int i = 0; i < iNumIters; ++i) {
                        tempTree = MakeTree(depth);
#   ifndef GC
                          delete tempTree;
#   endif
                        tempTree = 0;
                }
                tFinish = currentTime();
                cout << "\tBottom up construction took "
                     << elapsedTime(tFinish - tStart) << " msec" << endl;

        }

        void main() {
                Node    root;
                Node    longLivedTree;
                Node    tempTree;
                long    tStart, tFinish;
                long    tElapsed;

#ifdef GC
// GC_full_freq = 30;
GC_enable_incremental();
#endif
                cout << "Garbage Collector Test" << endl;
                cout << " Live storage will peak at "
                     << 2 * sizeof(Node0) * TreeSize(kLongLivedTreeDepth) +
                        sizeof(double) * kArraySize
                     << " bytes." << endl << endl;
                cout << " Stretching memory with a binary tree of depth "
                     << kStretchTreeDepth << endl;
                PrintDiagnostics();
              
                tStart = currentTime();
               
                // Stretch the memory space quickly
                tempTree = MakeTree(kStretchTreeDepth);
#  ifndef GC
                  delete tempTree;
#  endif
                tempTree = 0;

                // Create a long lived object
                cout << " Creating a long-lived binary tree of depth "
                     << kLongLivedTreeDepth << endl;
#  ifndef GC
                  longLivedTree = new Node0();
#  else
                  longLivedTree = new (GC_NEW(Node0)) Node0();
#  endif
                Populate(kLongLivedTreeDepth, longLivedTree);

                // Create long-lived array, filling half of it
                cout << " Creating a long-lived array of "
                     << kArraySize << " doubles" << endl;
#  ifndef GC
                  double *array = new double[kArraySize];
#  else
                  double *array = (double *)
    GC_MALLOC_ATOMIC(sizeof(double) * kArraySize);
#  endif
                for (int i = 0; i < kArraySize/2; ++i) {
                        array[i] = 1.0/i;
                }
                PrintDiagnostics();

                for (int d = kMinTreeDepth; d <= kMaxTreeDepth; d += 2)
{
                        TimeConstruction(d);
                }

                if (longLivedTree == 0 || array[1000] != 1.0/1000)
                        cout << "Failed" << endl;
                                        // fake reference to LongLivedTree
                                        // and array
                                        // to keep them from being optimized away

                tFinish = currentTime();
                tElapsed = elapsedTime(tFinish-tStart);
                PrintDiagnostics();
                cout << "Completed in " << tElapsed << " msec" << endl;
#  ifdef GC
    cout << "Completed " << GC_gc_no << " collections" <<endl;
    cout << "Heap size is " << GC_get_heap_size() << endl;
#  endif
        }
};

main () {
    GCBench x;
    x.main();
}

 

* 쓰레기 수집기가 동작하지 않도록 컴파일하기
$ g++ -o GCBench GCBench.cpp -lgc

* 실행하기
$ ./GCBench
Garbage Collector Test
 Live storage will peak at 8194272 bytes.

 Stretching memory with a binary tree of depth 18
 Creating a long-lived binary tree of depth 16
 Creating a long-lived array of 500000 doubles
Creating 33824 trees of depth 4
        Top down construction took 351 msec
        Bottom up construction took 348 msec
Creating 8256 trees of depth 6
        Top down construction took 352 msec
        Bottom up construction took 350 msec
Creating 2052 trees of depth 8
        Top down construction took 350 msec
        Bottom up construction took 350 msec
Creating 512 trees of depth 10
        Top down construction took 358 msec
        Bottom up construction took 354 msec
Creating 128 trees of depth 12
        Top down construction took 357 msec
        Bottom up construction took 355 msec
Creating 32 trees of depth 14
        Top down construction took 355 msec
        Bottom up construction took 358 msec
Creating 8 trees of depth 16
        Top down construction took 371 msec
        Bottom up construction took 373 msec
Completed in 5239 msec

 

* 쓰레기 수집기가 동작하도록 컴파일하기
g++ -DGC -o GCBench GCBench.cpp -lgc

* 실행하기
$ ./GCBench
Garbage Collector Test
 Live storage will peak at 8194272 bytes.

 Stretching memory with a binary tree of depth 18
 Creating a long-lived binary tree of depth 16
 Creating a long-lived array of 500000 doubles
Creating 33824 trees of depth 4
        Top down construction took 132 msec
        Bottom up construction took 110 msec
Creating 8256 trees of depth 6
        Top down construction took 114 msec
        Bottom up construction took 109 msec
Creating 2052 trees of depth 8
        Top down construction took 115 msec
        Bottom up construction took 112 msec
Creating 512 trees of depth 10
        Top down construction took 116 msec
        Bottom up construction took 114 msec
Creating 128 trees of depth 12
        Top down construction took 122 msec
        Bottom up construction took 116 msec
Creating 32 trees of depth 14
        Top down construction took 122 msec
        Bottom up construction took 123 msec
Creating 8 trees of depth 16
        Top down construction took 137 msec
        Bottom up construction took 114 msec
Completed in 1759 msec
Completed 229 collections
Heap size is 27652096

 

* C++ 언어용 쓰레기 수집  벤치마크 테스트 (파일명: GCBench_OGC.cpp)

// This is adapted from a benchmark written by John Ellis and Pete Kovac
// of Post Communications.
// It was modified by Hans Boehm of Silicon Graphics.
// Translated to C++ 30 May 1997 by William D Clinger of Northeastern Univ.
//
//      This is no substitute for real applications.  No actual application
//      is likely to behave in exactly this way.  However, this benchmark was
//      designed to be more representative of real applications than other
//      Java GC benchmarks of which we are aware.
//      It attempts to model those properties of allocation requests that
//      are important to current GC techniques.
//      It is designed to be used either to obtain a single overall performance
//      number, or to give a more detailed estimate of how collector
//      performance varies with object lifetimes.  It prints the time
//      required to allocate and collect balanced binary trees of various
//      sizes.  Smaller trees result in shorter object lifetimes.  Each cycle
//      allocates roughly the same amount of memory.
//      Two data structures are kept around during the entire process, so
//      that the measured performance is representative of applications
//      that maintain some live in-memory data.  One of these is a tree
//      containing many pointers.  The other is a large array containing
//      double precision floating point numbers.  Both should be of comparable
//      size.
//
//      The results are only really meaningful together with a specification
//      of how much memory was used.  It is possible to trade memory for
//      better time performance.  This benchmark should be run in a 32 MB
//      heap, though we don't currently know how to enforce that uniformly.
//
//      Unlike the original Ellis and Kovac benchmark, we do not attempt
//      measure pause times.  This facility should eventually be added back
//      in.  There are several reasons for omitting it for now.  The original
//      implementation depended on assumptions about the thread scheduler
//      that don't hold uniformly.  The results really measure both the
//      scheduler and GC.  Pause time measurements tend to not fit well with
//      current benchmark suites.  As far as we know, none of the current
//      commercial Java implementations seriously attempt to minimize GC pause
//      times.

#include <new>         // #include <new.h>
#include <iostream>    // #include <iostream.h>
#include <sys/time.h>

#ifdef GC
#include "gc_cpp.h"    // #  include "gc.h"
#endif
#ifdef OGC
#  include "ogc.h"
#endif

using namespace std;

//  These macros were a quick hack for the Macintosh.
//
//  #define currentTime() clock()
//  #define elapsedTime(x) ((1000*(x))/CLOCKS_PER_SEC)

#define currentTime() stats_rtclock()
#define elapsedTime(x) (x)

/* Get the current time in milliseconds */

unsigned
stats_rtclock( void )
{
  struct timeval t;
  struct timezone tz;

  if (gettimeofday( &t, &tz ) == -1)
    return 0;
  return (t.tv_sec * 1000 + t.tv_usec / 1000);
}

static const int kStretchTreeDepth    = 18;      // about 16Mb
static const int kLongLivedTreeDepth  = 16;  // about 4Mb
static const int kArraySize  = 500000;  // about 4Mb
static const int kMinTreeDepth = 4;
static const int kMaxTreeDepth = 16;

struct Node0 {
#       ifdef OGC
          gc_ptr<Node0> left;
          gc_ptr<Node0> right;
          Node0(gc_ptr<Node0> l, gc_ptr<Node0> r) { left = l; right = r; }
# else
          Node0 * left;
          Node0 * right;
          Node0(Node0 *l, Node0 *r) { left = l; right = r; }
# endif
        int i, j;
        Node0() { left = 0; right = 0; }
#       if !defined(GC) && !defined(OGC)
          ~Node0() { if (left) delete left; if (right) delete right; }
# endif
};

#ifdef OGC
typedef gc_ptr<Node0> Node;
#else
typedef struct Node0 *Node;
#endif

struct GCBench {

        // Nodes used by a tree of a given size
        static int TreeSize(int i) {
                return ((1 << (i + 1)) - 1);
        }

        // Number of iterations to use for a given tree depth
        static int NumIters(int i) {
                return 2 * TreeSize(kStretchTreeDepth) / TreeSize(i);
        }

        // Build tree top down, assigning to older objects.
        static void Populate(int iDepth, Node thisNode) {
                if (iDepth<=0) {
                        return;
                } else {
                        iDepth--;
#   if defined(GC)
                          thisNode->left  = new (GC_NEW(Node0)) Node0();
                          thisNode->right = new (GC_NEW(Node0)) Node0();
#   elif defined(OGC)
                          thisNode->left  = gc_new Node0();
                          thisNode->right = gc_new Node0();
#   else
                          thisNode->left  = new Node0();
                          thisNode->right = new Node0();
#   endif
                        Populate (iDepth, thisNode->left);
                        Populate (iDepth, thisNode->right);
                }
        }

        // Build tree bottom-up
        static Node MakeTree(int iDepth) {
                if (iDepth<=0) {
#       if defined(GC)
                        return new (GC_NEW(Node0)) Node0();
#       elif defined(OGC)
                        return gc_new Node0();
#       else
                        return new Node0();
#       endif
                } else {
#       if defined(GC)
                        return new (GC_NEW(Node0)) Node0(MakeTree(iDepth-1),
                                            MakeTree(iDepth-1));
#       elif defined(OGC)
#   ifdef BROKEN_SMART_PTRS
     Node left = MakeTree(iDepth-1);
     Node right = MakeTree(iDepth-1);
                          return gc_new Node0(left, right);
#   else
                          return gc_new Node0(MakeTree(iDepth-1),
                                            MakeTree(iDepth-1));
#   endif
#       else
                        return new Node0(MakeTree(iDepth-1),
                                         MakeTree(iDepth-1));
#       endif
                }
        }

        static void PrintDiagnostics() {
#if 0
                long lFreeMemory = Runtime.getRuntime().freeMemory();
                long lTotalMemory = Runtime.getRuntime().totalMemory();

                System.out.print(" Total memory available="
                                 + lTotalMemory + " bytes");
                System.out.println("  Free memory=" + lFreeMemory + " bytes");
#endif
        }

        static void TimeConstruction(int depth) {
                long    tStart, tFinish;
                int     iNumIters = NumIters(depth);
                Node    tempTree;

                cout << "Creating " << iNumIters
                     << " trees of depth " << depth << endl;
               
                tStart = currentTime();
                for (int i = 0; i < iNumIters; ++i) {
#   if defined(GC)
                          tempTree = new (GC_NEW(Node0)) Node0();
#   elif defined(OGC)
                          tempTree = gc_new Node0();
#   else
                          tempTree = new Node0();
#   endif
                        Populate(depth, tempTree);
#          if !defined(GC) && !defined(OGC)
                          delete tempTree;
#   endif
                        tempTree = 0;
                }
                tFinish = currentTime();
                cout << "\tTop down construction took "
                     << elapsedTime(tFinish - tStart) << " msec" << endl;
                    
                tStart = currentTime();
                for (int i = 0; i < iNumIters; ++i) {
                        tempTree = MakeTree(depth);
#   if !defined(GC) && !defined(OGC)
                          delete tempTree;
#   endif
                        tempTree = 0;
                }
                tFinish = currentTime();
                cout << "\tBottom up construction took "
                     << elapsedTime(tFinish - tStart) << " msec" << endl;

        }

        void main() {
                Node    root;
                Node    longLivedTree;
                Node    tempTree;
                long    tStart, tFinish;
                long    tElapsed;

#ifdef GC
// GC_full_freq = 30;
GC_enable_incremental();
#endif

#         ifdef OGC
    GC::SetPolicy(100);
#  endif
                cout << "Garbage Collector Test" << endl;
                cout << " Live storage will peak at "
                     << 2 * sizeof(Node0) * TreeSize(kLongLivedTreeDepth) +
                        sizeof(double) * kArraySize
                     << " bytes." << endl << endl;
                cout << " Stretching memory with a binary tree of depth "
                     << kStretchTreeDepth << endl;
                PrintDiagnostics();
              
                tStart = currentTime();
               
                // Stretch the memory space quickly
                tempTree = MakeTree(kStretchTreeDepth);
#  if !defined(GC) && !defined(OGC)
                  delete tempTree;
#  endif
                tempTree = 0;

                // Create a long lived object
                cout << " Creating a long-lived binary tree of depth "
                     << kLongLivedTreeDepth << endl;
#  if defined(GC)
                  longLivedTree = new (GC_NEW(Node0)) Node0();
#         elif defined(OGC)
                  longLivedTree = gc_new Node0();
#  else
                  longLivedTree = new Node0();
#  endif
                Populate(kLongLivedTreeDepth, longLivedTree);

                // Create long-lived array, filling half of it
                cout << " Creating a long-lived array of "
                     << kArraySize << " doubles" << endl;
#  if defined(GC)
                  double *array = (double *)
    GC_MALLOC(sizeof(double) * kArraySize);
#  else
                  double *array = new double[kArraySize];
#  endif
                for (int i = 0; i < kArraySize/2; ++i) {
                        array[i] = 1.0/i;
                }
                PrintDiagnostics();

                for (int d = kMinTreeDepth; d <= kMaxTreeDepth; d += 2)
{
                        TimeConstruction(d);
                }

                if (longLivedTree == 0 || array[1000] != 1.0/1000)
                        cout << "Failed" << endl;
                                        // fake reference to LongLivedTree
                                        // and array
                                        // to keep them from being optimized away

                tFinish = currentTime();
                tElapsed = elapsedTime(tFinish-tStart);
                PrintDiagnostics();
                cout << "Completed in " << tElapsed << " msec" << endl;
#  ifdef GC
    cout << "Completed " << GC_gc_no << " collections" <<endl;
    cout << "Heap size is " << GC_get_heap_size() << endl;
#  endif
        }
};

main () {
    GCBench x;
    x.main();
}

 

* 컴파일하기
$ g++ -o GCBench GCBench_OGC.cpp -lgc

* 실행하기
$ ./GCBench_OGC
Garbage Collector Test
 Live storage will peak at 8194272 bytes.

 Stretching memory with a binary tree of depth 18
 Creating a long-lived binary tree of depth 16
 Creating a long-lived array of 500000 doubles
Creating 33824 trees of depth 4
        Top down construction took 407 msec
        Bottom up construction took 348 msec
Creating 8256 trees of depth 6
        Top down construction took 351 msec
        Bottom up construction took 348 msec
Creating 2052 trees of depth 8
        Top down construction took 352 msec
        Bottom up construction took 347 msec
Creating 512 trees of depth 10
        Top down construction took 354 msec
        Bottom up construction took 354 msec
Creating 128 trees of depth 12
        Top down construction took 355 msec
        Bottom up construction took 356 msec
Creating 32 trees of depth 14
        Top down construction took 355 msec
        Bottom up construction took 359 msec
Creating 8 trees of depth 16
        Top down construction took 366 msec
        Bottom up construction took 374 msec
Completed in 5332 msec

 

 

 

 

Posted by Scripter
,