Author Topic: Interesting performance FYI  (Read 1411 times)

0 Members and 1 Guest are viewing this topic.

WILL HATCH

  • Bull Frog
  • Posts: 448
Interesting performance FYI
« on: June 25, 2013, 03:04:15 AM »
I've been playing around with some pretty large models lately and seeing how to squeeze every last microsecond out of each iteration.  I learned something interesting here.  By explicitly disposing of objects which are alive for anything outside of my tightest loops instead of letting the garbage collector handle them I'm seeing a 10 - 15% speed increase.

I've been learning more about the collector and how managed code is really working behind the scenes, thought I'd share this little piece.

Jeff H

  • Needs a day job
  • Posts: 6054
Re: Interesting performance FYI
« Reply #1 on: June 25, 2013, 02:38:23 PM »
Is this AutoCAD objects, and are they opened through a transaction?
 

WILL HATCH

  • Bull Frog
  • Posts: 448
Re: Interesting performance FYI
« Reply #2 on: June 25, 2013, 04:19:29 PM »
Awww... I eat my words.   Yesterday I was seeing roughly 300k ticks running TESTGETOBJECTS1 and 250k running TESTGETOBJECTS2 and today I'm seeing about 190k for both.  Only with the addition of explicitly disposing the Entitys I've opened inside the transaction (lines 82 and 98) am I seeing the TESTGETOBJECTS2 running any faster (180k ticks) today.

Just when I though I'd learned something I'm back to being confused...  The logic behind explicit disposal (I know the transaction will do this for me) is that on a relatively large database there will be several calls to the GC during this operation and by explicitly disposing we are minimizing the number of objects that need to be handled during the collection.

Code - C#: [Select]
  1.         [CommandMethod("TestGetObjects1")]
  2.         public void get1()
  3.         {
  4.             GC.Collect();
  5.             GC.Collect();
  6.             GC.Collect();
  7.             System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch();
  8.             timer.Start();
  9.             Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
  10.             Database db = Application.DocumentManager.MdiActiveDocument.Database;
  11.             List<PileTuple> piles = new List<PileTuple>();
  12.             RXClass rxc = RXClass.GetClass(typeof(Line));
  13.             using (Transaction tr = db.TransactionManager.StartTransaction())
  14.             {
  15.                 try
  16.                 {
  17.                     BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
  18.                     foreach (ObjectId BTRid in bt)
  19.                     {
  20.                         BlockTableRecord btr = (BlockTableRecord)tr.GetObject(BTRid, OpenMode.ForRead);
  21.                         foreach (var id in btr)
  22.                         {
  23.                             if (id.ObjectClass != rxc) continue;
  24.                             Entity ent = (Entity)tr.GetObject(id, OpenMode.ForRead);
  25.                             ResultBuffer rb = ent.GetXDataForApplication(AppName);
  26.                             if (rb == null) continue;
  27.                             TypedValue[] tvs = rb.AsArray();
  28.                             if (tvs[1].Value.ToString() != "508") continue;
  29.                             Point3d pt = (Point3d)tvs[60].Value;
  30.                             piles.Add(new PileTuple() { id = id, Location = new Point2d(pt.X, pt.Y), Xdata = tvs });
  31.                         }
  32.                     }
  33.                     int n = 100;
  34.                     foreach (var pile in piles.OrderBy(a=>a.Location.X).OrderBy(a=>a.Location.Y))
  35.                     {
  36.                         Entity ent = (Entity)tr.GetObject(pile.id, OpenMode.ForWrite);
  37.                         pile.Xdata[42] = new TypedValue(pile.Xdata[42].TypeCode, n++.ToString());
  38.                         ent.XData = new ResultBuffer(pile.Xdata);
  39.                     }
  40.                 }
  41.                 catch (System.Exception e)
  42.                 {
  43.                     ed.WriteMessage(e.ToString());
  44.                 }
  45.                 tr.Commit();
  46.             }
  47.             GC.Collect();
  48.             timer.Stop();
  49.             ed.WriteMessage("\n{0}", timer.ElapsedTicks);
  50.         }
  51.  
  52.         [CommandMethod("TestGetObjects2")]
  53.         public void get2()
  54.         {
  55.             GC.Collect();
  56.             GC.Collect();
  57.             GC.Collect();
  58.             System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch();
  59.             timer.Start();
  60.             Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
  61.  
  62.             using (Database db = Application.DocumentManager.MdiActiveDocument.Database)
  63.             {
  64.                 List<PileTuple> piles = new List<PileTuple>();
  65.                 using (RXClass rxc = RXClass.GetClass(typeof(Line)))
  66.                 {
  67.                     using (Transaction tr = db.TransactionManager.StartTransaction())
  68.                     {
  69.                         try
  70.                         {
  71.                             using (BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead))
  72.                             {
  73.                                 foreach (ObjectId BTRid in bt)
  74.                                 {
  75.                                     using (BlockTableRecord btr = (BlockTableRecord)tr.GetObject(BTRid, OpenMode.ForRead))
  76.                                     {
  77.                                         foreach (var id in btr)
  78.                                         {
  79.                                             if (id.ObjectClass != rxc) continue;
  80.                                             Entity ent = (Entity)tr.GetObject(id, OpenMode.ForRead);
  81.                                             ResultBuffer rb = ent.GetXDataForApplication(AppName);
  82.                                             ent.Dispose();
  83.                                             if (rb == null) continue;
  84.                                             TypedValue[] tvs = rb.AsArray();
  85.                                             if (tvs[1].Value.ToString() != "508") continue;
  86.                                             Point3d pt = (Point3d)tvs[60].Value;
  87.                                             piles.Add(new PileTuple() { id = id, Location = new Point2d(pt.X, pt.Y), Xdata = tvs });
  88.                                         }
  89.                                     }
  90.                                 }
  91.                             }
  92.                             int n = 100;
  93.                             foreach (var pile in piles.OrderBy(a => a.Location.X).OrderBy(a => a.Location.Y))
  94.                             {
  95.                                 Entity ent = (Entity)tr.GetObject(pile.id, OpenMode.ForWrite);
  96.                                 pile.Xdata[42] = new TypedValue(pile.Xdata[42].TypeCode, n++.ToString());
  97.                                 ent.XData = new ResultBuffer(pile.Xdata);
  98.                                 ent.Dispose();
  99.                             }
  100.                         }
  101.                         catch (System.Exception e)
  102.                         {
  103.                             ed.WriteMessage(e.ToString());
  104.                         }
  105.                         tr.Commit();
  106.                     }
  107.                 }
  108.             }
  109.             GC.Collect();
  110.             timer.Stop();
  111.             ed.WriteMessage("\n{0}", timer.ElapsedTicks);
  112.         }

Jeff H

  • Needs a day job
  • Posts: 6054
Re: Interesting performance FYI
« Reply #3 on: June 25, 2013, 04:37:10 PM »
Hey Will,
 
I think the only thing you can do is try minimize the number of managed wrappers that are created to help GC performance, and probably explicitly calling dispose on classes like RXClass where normally you let the finalizer clean it up, calling dispose would clean it up then and suppress the finializer which could help.

WILL HATCH

  • Bull Frog
  • Posts: 448
Re: Interesting performance FYI
« Reply #4 on: June 25, 2013, 06:06:35 PM »
You're right... after some more playing it seems to be nothing but quirks showing any real difference in the two times.

WILL HATCH

  • Bull Frog
  • Posts: 448
Re: Interesting performance FYI
« Reply #5 on: June 25, 2013, 06:45:34 PM »
Cheated!

Testing 615 piles:
Get 1: 662009
Get 2: 279272
Ratio (2/1): 0.421855291997541
Get 1: 590705
Get 2: 280776
Ratio (2/1): 0.475323554058286
Get 1: 572926
Get 2: 271165
Ratio (2/1): 0.473298471355812
Get 1: 575239
Get 2: 266402
Ratio (2/1): 0.463115331192774
Get 1: 576506
Get 2: 267534
Ratio (2/1): 0.464061085227214
Get 1: 579655
Get 2: 268355
Ratio (2/1): 0.462956413728856
Get 1: 594724
Get 2: 268463
Ratio (2/1): 0.451407711812538
Get 1: 580404
Get 2: 260945
Ratio (2/1): 0.449592008325236
Get 1: 574607
Get 2: 267318
Ratio (2/1): 0.46521883652653
Get 1: 582051
Get 2: 272280
Ratio (2/1): 0.467794059283465

Code - C#: [Select]
  1.         public int get2()
  2.         {
  3.             GC.Collect();
  4.             GC.Collect();
  5.             GC.Collect();
  6.             System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch();
  7.             timer.Start();
  8.             Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
  9.  
  10.             using (Database db = Application.DocumentManager.MdiActiveDocument.Database)
  11.             {
  12.                 List<PileTuple> piles = new List<PileTuple>();
  13.                 using (RXClass rxc = RXClass.GetClass(typeof(Line)))
  14.                 {
  15.                    
  16.                         try
  17.                         {
  18.                            
  19.                             using (BlockTable bt = (BlockTable)db.BlockTableId.Open(OpenMode.ForRead))
  20.                             {
  21.                                 foreach (ObjectId BTRid in bt)
  22.                                 {
  23.                                     using (BlockTableRecord btr = (BlockTableRecord)BTRid.Open(OpenMode.ForRead))
  24.                                     {
  25.                                         foreach (var id in btr)
  26.                                         {
  27.                                             if (id.ObjectClass != rxc) continue;
  28.                                             using (Entity ent = (Entity)id.Open(OpenMode.ForRead))
  29.                                             {
  30.                                                 ResultBuffer rb = ent.GetXDataForApplication(AppName);
  31.  
  32.                                                 if (rb == null) continue;
  33.                                                 TypedValue[] tvs = rb.AsArray();
  34.                                                 if (tvs[1].Value.ToString() != "508") continue;
  35.                                                 Point3d pt = (Point3d)tvs[60].Value;
  36.                                                 piles.Add(new PileTuple() { id = id, Location = new Point2d(pt.X, pt.Y), Xdata = tvs });
  37.                                             }
  38.                                         }
  39.                                     }
  40.                                 }
  41.                             }
  42.                             int n = 100;
  43.                             foreach (var pile in piles.OrderBy(a => a.Location.X).OrderBy(a => a.Location.Y))
  44.                             {
  45.                                 using (Entity ent = (Entity)pile.id.Open(OpenMode.ForWrite))
  46.                                 {
  47.                                     pile.Xdata[42] = new TypedValue(pile.Xdata[42].TypeCode, n++.ToString());
  48.                                     ent.XData = new ResultBuffer(pile.Xdata);
  49.                                 }
  50.                             }
  51.                         }
  52.                         catch (System.Exception e)
  53.                         {
  54.                             ed.WriteMessage(e.ToString());
  55.                         }
  56.                    
  57.                 }
  58.             }
  59.             GC.Collect();
  60.             timer.Stop();
  61.             return (int)timer.ElapsedTicks;
  62.         }

irneb

  • Water Moccasin
  • Posts: 1794
  • ACad R9-2016, Revit Arch 6-2016
Re: Interesting performance FYI
« Reply #6 on: July 15, 2013, 03:25:04 AM »
GC's performance is not easy to benchmark and/or debug. Many a gaming programmer's found to their detriment that GC is not a friend.

You might be interested to read this: http://sealedabstract.com/rants/why-mobile-web-apps-are-slow/

Note: It's a very long article. What I'm trying to show is the section entitled "GC on mobile is not the same animal as GC on the desktop". The graph shows quite a lot:

Thus if you have 6x more RAM than your code is actually using, then GC outperforms even direct memory allocation. If you've got less RAM available then the GC gets exponentially slower.

Usually on a Desktop you'd have a lot more RAM than you actually need at any one time. But (as we all know) 3d models throws that assumption out the window! Therefore it makes sense on larger models (i.e. once you've used up something like 1/6th of available RAM) to start forcing allocations to clear as soon as possible. This only being a "rule-of-thumb", there can obviously be other factors at play as well - e.g. some background process might be kicking in (both using up CPU cycles and eating more RAM).
Common sense - the curse in disguise. Because if you have it, you have to live with those that don't.