Reclaiming memory from erased AutoCAD entities using .NET

I was pleasantly surprised the other day to find that the "permanent object deletion" API I mentioned back in this post - and had marked as only being available in ObjectARX - was also exposed in the .NET API to AutoCAD 2009. What better way to celebrate the good news than to put together some test code and post it to my blog? 🙂

So, for a Thanksgiving/pre-AU treat, here's some information on making use of the Database.ReclaimMemoryFromErasedObjects() method to - surprisingly enough - reclaim memory from erased objects.

Firstly, why is this even needed? Well, when you erase an object inside AutoCAD, you only actually set its "erased" flag to true, which means it no longer participates in a number of operations such as regenerating its graphics for display/plotting and saving itself to file. Using a flag makes operations such as Undo more efficient, as objects don't need to be paged out and back into memory. The manual approach to reclaim this memory is to save and close the drawing and then reopen it: erased objects are not written to file and closing the document will cause its memory to be reclaimed.

But now - with AutoCAD 2009 - it's possible to reclaim some of that memory programmatically. The following C# code implements two commands: the CREATE command adds 200,000 lines to the modelspace, and the DESTROY command goes through the modelspace and erases all the lines it contains, requesting the memory used by them to be reclaimed. Note that to reclaim an entity's memory you do need to erase it first.

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.Runtime;

namespace PermanentDeletionTest

{

  public class Commands

  {

    [CommandMethod("create")]

    public static void CreateLotsOfObjects()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Database db = doc.Database;

      Transaction tr =

        db.TransactionManager.StartTransaction();

      using (tr)

      {

        BlockTable bt =

          (BlockTable)tr.GetObject(

            db.BlockTableId,

            OpenMode.ForRead

          );

        BlockTableRecord btr =

          (BlockTableRecord)tr.GetObject(

            bt[BlockTableRecord.ModelSpace],

            OpenMode.ForWrite

          );

        for (int i=0; i < 200000; i++)

        {

          Line ln = new Line();

          ln.StartPoint = new Point3d(0, i, 0);

          ln.EndPoint = new Point3d(i, 0, 0);

          btr.AppendEntity(ln);

          tr.AddNewlyCreatedDBObject(ln, true);

        }

        tr.Commit();

      }

    }

    [CommandMethod("destroy")]

    public static void PermanentlyDeleteContentsOfModelspace()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Database db = doc.Database;

      ObjectIdCollection erased =

        new ObjectIdCollection();

      Transaction tr =

        db.TransactionManager.StartTransaction();

      using (tr)

      {

        BlockTable bt =

          (BlockTable)tr.GetObject(

            db.BlockTableId,

            OpenMode.ForRead

          );

        BlockTableRecord btr =

          (BlockTableRecord)tr.GetObject(

            bt[BlockTableRecord.ModelSpace],

            OpenMode.ForRead

          );

        foreach (ObjectId id in btr)

        {

          DBObject obj = tr.GetObject(id, OpenMode.ForRead);

          Line ln = obj as Line;

          if (ln != null)

          {

            ln.UpgradeOpen();

            ln.Erase();

            erased.Add(id);

          }

        }

        tr.Commit();

      }

      db.ReclaimMemoryFromErasedObjects(erased);

    }

  }

}

To see how it all worked, I used the very useful Process Explorer tool from Microsoft's Sysinternals site. This may not be a perfect way to measure memory usage by a process, but at least it provided me with a pretty graph. 🙂

AutoCAD memory profile during mass creation and destruction

I've annotate the image with the following events:

  1. AutoCAD start-up complete (204 Mb)
  2. NETLOAD  launched (213 Mb)
  3. Application loaded (216 Mb)
  4. CREATE starts (216 Mb)
  5. CREATE completed (388 Mb)
  6. DESTROY starts (388 Mb)
  7. DESTROY completed (263 Mb)
  8. UNDO starts (263 Mb)
  9. UNDO completed (396 Mb)
  10. REDO starts (396 Mb)
  11. REDO completed (294 Mb)

I fully admit to not being an expert when it comes to memory profiling - so I'm sure people will step in and tell me why what I've done isn't accurate - but this should give you a feel for the capabilities of this particular function. You can see that there is still some increase in memory usage, even after "destroying" the entities, but there is benefit to be derived here. With hindsight I probably shouldn't have referred to it as the "permanent object deletion" API, as UNDO and REDO still work fine: it simply means AutoCAD has to page the objects back into memory (which explains some of the memory increase: UNDO doesn't come for free as we need a buffer containing the changes to the objects).

Here are some additional notes regarding this function from the ObjectARX Reference (the C++ version currently contains better information than the one for .NET):

For performance reasons, it is better to call this function as infrequently as possible with as many objects in the input array as possible, because there is a performance overhead that varies with the size of the overall database with each call, regardless of the number of objects whose memory is to be reclaimed.

If there are entities in the array, it is faster to group them by their owning block, although not required.

Also note that before being deleted, the object state is saved for Undo, of which some will remain in memory. It should be negligible for a huge drawing, but disabling Undo should eliminate this aspect of residual memory usage.

Note also that there is a residual amount of memory per object that cannot be reclaimed which can approach 10% of simple objects such as lines and circles that is associated with the object id and handle.

Finally its worth pointing out what happens when you comment out this function call. There is some reduction in memory usage, even when just erasing - probably as the graphics subsystem needs less memory for the geometry representation - but I saw the memory usage decrease to around 340 Mb, rather than 260 Mb.

OK, that's it from me until AU. Hopefully see some of you in Vegas!

8 responses to “Reclaiming memory from erased AutoCAD entities using .NET”

  1. Hi Kean...
    I don't understand the rows..

    if db is the current drawing database..
    I haven't never saw this method:

    db.ReclaimMemoryFromErasedObjects(erased);

  2. Hi Giuliano,

    It's new in AutoCAD 2009.

    Or did I misunderstand your question?

    Kean

  3. Hi Kean,
    If I don't want "destroying" the entities
    only update 50000 lines. If it possible
    reclaim the memory. GC.Collect don't work.
    Thank you.

  4. Hi Michael,

    There's nothing to reclaim: the entities are modified in-memory, and you can't control when they're paged out.

    Kean

  5. xinggangzhang Avatar

    I want to del the blank group,I do as :
    using (tr)
    {
    DBDictionary dict = (DBDictionary)tr.GetObject(db.GroupDictionaryId, OpenMode.ForRead, false);
    foreach (DictionaryEntry DBDE in dict)
    {
    ObjectId obj = dict.GetAt(DBDE.Key.ToString());
    Autodesk.AutoCAD.DatabaseServices.Group gp = tr.GetObject(obj, OpenMode.ForRead) as Autodesk.AutoCAD.DatabaseServices.Group;
    if (gp.NumEntities==0)
    {
    idlist.Add(obj);
    count++;
    }
    }
    }
    Transaction tr2 = db.TransactionManager.StartTransaction();
    using (tr2)
    {
    for (int id = 0; id < idlist.Count; id++)
    {
    Autodesk.AutoCAD.DatabaseServices.Group gp1 = tr.GetObject(idlist[id], OpenMode.ForWrite) as Autodesk.AutoCAD.DatabaseServices.Group;
    gp1.Erase();
    }
    }
    tr2.Commit();
    But it have error,Can you tell me how to correct it?(autocad2006)

  6. Kean Walmsley Avatar

    I'm afraid this is not a forum for support. Please submit your question to the ADN team, if you're a member, or otherwise the AutoCAD .NET Discussion Group.

    Kean

  7. i'm trying the same with layouts, because, seems i can't create new ones with old one's name but i don't want to save the file because it will hit the performance and also i'm using my procedure during save... it does not work, looks like this is only for element in the model space, right ? is there something similar for layouts ?

  8. That's a very different problem... you might try asking ADN or posting to the AutoCAD .NET Discussion Group.

    Kean

Leave a Reply to xinggangzhang Cancel reply

Your email address will not be published. Required fields are marked *