12 Replies Latest reply on Mar 22, 2018 7:56 PM by rborges

    Best Practices related to Garbage Collection

    vwitzel

      Hi all,

      We recently built two applications leveraging the PI SDK, and I am curious to learn more about best practices related to garbage collection to optimize memory usage in the contexts of the applications we built.

       

      The first application is a command line VB.NET application using the PI SDK to retrieve either the current value or multiple historical values for a number of tags (the data retrieval method is dependent on how an associated XML configuration file is configured). The application runs periodically using the Windows Task Scheduler. Each time the application runs, it retrieves the aforementioned data for tags specified in the config file and posts that data to a web service. To do so, we are leveraging the .GetPoints method and .Data.Snapshot or .Data.RecordedValues methods. We are wondering if garbage collection (i.e., GC.Collect) and setting the retrieved value/values variable to "Nothing" (e.g., val = Nothing, where val is a PIValue variable) would be best practice/helpful during code execution, considering we are looping through and retrieving data for a potentially large number of tags. On the other hand, we are reusing the same variable during the code execution and from what I've read, .NET handles garbage collection automatically, so I am curious to hear what peoples' thoughts are on this.

       

      The second application is a VB.NET ACE application which retrieves instantaneous values for the input PI tags/aliases, passes them to a third-party DLL which returns some values, and outputs the values returned by the third-party DLL to the output PI tags/aliases. With this application, we have observed that when we initially start the calculation using the PI ACE Manager, the associated PIACEClassLibraryHost.exe process consumes ~16 MB of memory, but within just a few hours, that number grows to several hundred MBs and eventually up to ~1.5 GB. After some indeterminable and inconsistent time, the calculation stops working (that is, no more outputs are generated) and a generic error message ("External component has thrown an exception”) appears in the PI message log. Meanwhile, other ACE calculations continue working. We are wondering if the reason the ACE process stops working is because no garbage collection/"unloading" of the third-party DLL is being performed, and if so, how this could best be handled.

       

      I appreciate any insights people can share

        • Re: Best Practices related to Garbage Collection
          John Messinger

          Hi Vincent,

           

          My initial feeling about this is that because you are using PI SDK (which is unmanaged code wrapped up through interop assemblies) then you won't be able to implicitly rely on the garbage collector to completely clean up unused objects. I may be be slightly off the mark there, but that has been my past experience.  You may have to force GC on the PISDK objects such as PointList:

           

          System.Runtime.InteropServices.Marshal.ReleaseComObject(pointVals) // or whatever you have called the objects
          

           

          The issues you indicated in your ACE calculation may be related to this as well.

           

          Out of curiosity, for your command line application, why not choose to do new development using the AFSDK? It's pure .NET, and so things like garbage collection shouldn't be so much of an issue as you are not using unmanaged code through interop, and you can use the OSIsoft.PI namespace to access the PI Data Archive and PIPoints directly. I would even consider this for new code wrapped up in PI-ACE.

           

          John

          1 of 1 people found this helpful
          • Re: Best Practices related to Garbage Collection
            vkaufmann

            The general recommendation is that you should never explicitly call garbage collection and let the runtime handle it for you. This article speaks well to the topic of garbage collection on COM objects used in .NET clients.

             

            Managing COM Object Lifetime in a Garbage-Collected Environment | Managing COM Object Lifetime in a Garbage-Collected En…

             

            I think the main takeaways from this are that the wrapped COM object do participate in garbage collection, but can be manually cleaned-up with the methods mentioned in the article which are identical to what John describes.

             

            In regards to your ACE code, you may want to do a bit of debugging to see what objects are causing your memory to grow. It sounds like your memory grows until you hit the 2GB threshold for a 32-bit process and then hit an OutOfMemory exception or something similar that can't properly bubble up to the PI message logs. You can use Windbg with the correct version of the SOS .net debugger and run some commands like !dumpheap -stat to see what objects are taking up the majority of your memory. Commands like !GCRoot can also help diagnose what objects are being prevented from being garbage collected. Windows debugging is quite the pit of information but I find it really helpful when trying to diagnose memory issues in an application.

             

            --Vince

            2 of 2 people found this helpful
            • Re: Best Practices related to Garbage Collection
              vwitzel

              John & Vince,

               

              Thank you both for the input, I appreciate it. Looking at the article Vince linked to, it states:

              ReleaseComObject is only meant to be used if absolutely necessary. In many cases, you should be able to let the garbage collector take care of everything rather than complicating your code with explicit releasing.

              This goes back to my main reason for posting this thread, which is: how do I know if it is absolutely necessary with my particular code to do the explicit garbage collection?

               

              John,

               

              The main reason I used PI-SDK code is because I already had some PI-SDK code written, which was easy to repurpose, especially for such a small application. I was not aware of there being any downside to using it per se, since I did not need to access any AF objects. Are there any specific downsides to be aware of (other than the topic of garbage collection)? As it relates to using the AF-SDK in conjunction with ACE, my understanding was that this would require the use of a wrapper, which seemed unnecessarily complex for my application. Am I mistaken?

               

              Vince,

               

              I had a similar suspicion as you with respect to a memory threshold getting hit, but when I researched memory limits for 32-bit applications, I found that the limit is in fact higher than 2 GB (specifically, 2,800 MB for a 32-bit .NET application running on a 64-bit OS, as described in the second table of this article). Where did you see that the limit is 2 GB?

               

              I will give the Windbg approach a try to pinpoint what is causing the high memory usage - thanks for the suggestion!

              1 of 1 people found this helpful
                • Re: Best Practices related to Garbage Collection
                  vkaufmann

                  Some docs on memory limits:

                  Memory Limits for Windows and Windows Server Releases (Windows)

                  windows - How much memory can a 32 bit process access on a 64 bit operating system? - Stack Overflow

                   

                  Garbage Collection Guidance:

                  This was a good discussion I found on when to call things like GC.collect. This is not quite the same as handling native objects through .NET interops but it gets to the core of when you should be calling garbage collection or not.

                  c# - When is it acceptable to call GC.Collect? - Stack Overflow

                   

                  Another idea is to implement a dispose pattern using the IDisposable in a wrapper class for any of the unmanaged PISDK stuff. Microsoft has phenomenal documentation about this with a lot of good examples. I think if you click all the right buttons in Visual Studio it will implement it for you.

                   

                  Dispose Pattern | Microsoft Docs

                   

                  In regards of using PI SDK vs AF SDK:

                  Code reuse is nice, but their are some headaches with sticking with the PI SDK as well as some particular advantages of moving to the AF SDK. This list is not exhaustive and its late here so I am probably missing some important topics but:

                  1.      COM is confusing and is quickly dying. Because of this the code becomes hard to maintain for any developers that might come after you that have to make changes to your code to get 1 or 2 years more life out of it because the OS your app is installed on is no longer being patched by Microsoft and WannaCry 3.0 just took down half of all fortune 500 companies.
                  2.      The AF SDK has some considerable performance improvements over the PI SDK
                  3.      It gives you full access to all of the tools of the .NET framework
                  4.      OSIsoft is still actively developing it and adding new features with every release to make your applications better. In turn, you will get much better support on it as well because there is heavy investment in its success
                  5.      Rick Davin won't yell at you for using the AF SDK. John Messinger is a fan as well https://pisquare.osisoft.com/message/108669-re-pi-api-performance-vs-pi-sdk#comment-108669

                   

                  --Vince

                  5 of 5 people found this helpful
                  • Re: Best Practices related to Garbage Collection
                    John Messinger

                    Hi Vincent,

                     

                    Vincent Kaufmann has referenced some great resources to look at - I read some of those myself prior to my original response.

                     

                    I think with regards to the whole PI SDK vs AFSDK thing that there are a couple of important points to consider, and again Vince has touched on some of these, and raised a few other good points. The PI SDK is marked for deprecation, and most of the development effort is now going into the AF SDK. Secondly, you will get better performance from the AF SDK. I find it very performant for what I need it to do, and can scale it out fairly well.

                     

                    When using AF SDK in PI-ACE, you don't necessarily need to use a wrapper - I never have, and I've done a few projects combining these two technologies. Having said that, I primarily used ACE for it's scheduling capability, and none of the data access used objects like PIACEPoint - it was all pure AF SDK generally against an AF hierarchy to implement complex calculations that couldn't be done with Asset Analytics. If your ACE application is dependent upon using a PI Module Database structure and utilising PIACEPoint objects then introducing a wrapper to use AF SDK probably wouldn't be worth the effort.

                     

                    Regards,

                    John

                    2 of 2 people found this helpful
                      • Re: Best Practices related to Garbage Collection
                        KenjiHashimoto

                        I want to vote to John Messinger 's comment. PI SDK is quite old technology.

                        Even if you already has PI SDK code, rewrite to AF SDK is a good experience to learn new technology and it will be a good skill for the future.

                        I remember that I also called  Marshal.ReleaseComObject for PI SDK object from PI ACE.

                        1 of 1 people found this helpful
                        • Re: Best Practices related to Garbage Collection
                          Rick Davin

                          In my former life as a customer, we too used ACE for its (redundant) scheduling but only to trigger a lot of AF SDK code.  Whenever a significant change was needed to the code base, we would then either convert from ACE to a Windows Service or a Console App to be setup to run as a scheduled task.  The deciding factor was whether it needed to monitor and run almost constantly (Windows Service) or whether it could be run every 15 minutes to 24 hours (Console App).

                           

                          There were many advantages to converting, and Vince has done a quite job of enumerating many of them.  For us it was:

                          • Move away from legacy PI SDK with heavy, clunky ComponentObjectModel to managed AF SDK code
                          • Get faster AF SDK performance over PI SDK
                          • Have 64 bit application to get past some memory limitations of 32 bit apps
                          • Be better positioned for future maintainability, i.e. not stuck on VS 2008 which Microsoft doesn't support anymore
                          2 of 2 people found this helpful
                            • Re: Best Practices related to Garbage Collection
                              vwitzel

                              Thanks everyone for your inputs, much appreciated!

                               

                              Vincent Kaufmann,

                               

                              Thanks for the links regarding the memory usage limitations. I reviewed them, but admittedly, I am still unclear why the Microsoft article I linked to seemed to suggest a different memory limitation for a 32-bit application running on a 64-bit OS than the Microsoft article you linked to.

                               

                              All,

                               

                              As it relates to garbage collection, all of the potential reasons that people shared in the Stack Overflow post that Vincent Kaufmann linked to which might justify doing garbage collection manually don't seem to apply to my first application. It is run intermittently using the Task Scheduler, not as a service. Even though I am processing a large number of tags, and potentially, values, I am reusing the same variables to store these PIPoints and PIValues, so I should not have a significant amount of variables/objects that need garbage collection during and at the end of the code execution. So, that leaves the following questions in my mind:

                              1. Considering what John Messinger explained about the PI-SDK code being unmanaged code wrapped up through interop assemblies, when my PI-SDK application terminates, will it automatically release any memory it had allocated in the memory heap?
                              2. Since the PIACEClassLibraryHost.exe is a continuously running process, at the end of each calculation cycle, will it automatically release any memory the calculation itself had allocated and any third-party DLLs that are referenced by the ACE DLL from the memory heap?
                              1 of 1 people found this helpful
                                • Re: Best Practices related to Garbage Collection
                                  Steve Boyko

                                  One of the advantages of using task scheduler or other schedulers to run your program periodically is that you don't have to be so concerned about memory leaks or garbage collection. Once your application is done, Windows frees it all up. Of course, you should practice good coding practice and close your handles, dereference your objects, etc. but in the end it should get cleaned up anyway.

                                   

                                  For Windows services or PI-ACE, you need to be more rigorous to free everything up at the end of each cycle to prevent a slow memory leak.

                                  1 of 1 people found this helpful
                                  • Re: Best Practices related to Garbage Collection
                                    vkaufmann

                                    I would generally trust the MSDN article over the Microsoft blog post with the poorly formatted table

                                      • Re: Best Practices related to Garbage Collection
                                        vwitzel

                                        Hi all,

                                         

                                        In case someone else runs into the same issue down the road, I am writing to provide an update on our findings related to garbage collection for the second application I described in my original post. We found that the garbage collection as we were doing it (i.e., setting the third-party object in VB.NET to NULL and using System.GC.Collect and System.GC.WaitForPendingFinalizers) was not properly freeing up memory after each calculation cycle/did not prevent our application from crashing due to excessive memory. The solution was to use the .Dispose() method on the third-party object. From what I could read online, this method is actually unrelated to garbage collection, but is related to file handles (see this StackOverflow article: c# - Setting an object to null vs Dispose() - Stack Overflow ). In our case, we are having the third-party DLL load a file, so we're thinking that may be why the .Dispose() method is necessary. All actual garbage collection steps seem to be handled automatically.

                                         

                                        Thanks again for everyone's help!

                                          • Re: Best Practices related to Garbage Collection
                                            rborges

                                            Vicent,

                                             

                                            Just to give you a little more information on this topic.

                                             

                                            1) GC does not look the value of a variable before disposing it. It evaluates the current scope and the current generation of the object. More info in this nice article.

                                             

                                            2) GC.Collect() does not free the memory. Actually, it just mark the objects as disposable for a later GC evaluation. This evaluation usually happens fast, so it may seem that .Collect() is actually doing it, but it isn't. More info here.

                                             

                                            3) Set a variable to null, does nothing. Lets consider this code here:

                                            string s1 = "hello";
                                            string s2 = "world";
                                            Console.WriteLine(s1 + s2);
                                            s2 = null;
                                            Console.WriteLine(s1 + s2);
                                            

                                             

                                            Now let's see what Roslyn's IL output:

                                            IL_0000: ldstr "hello"
                                            IL_0005: ldstr "world"
                                            IL_000a: stloc.0
                                            IL_000b: dup
                                            IL_000c: ldloc.0
                                            IL_000d: call string [mscorlib]System.String::Concat(string, string)
                                            IL_0012: call void [mscorlib]System.Console::WriteLine(string)
                                            IL_0017: ldnull
                                            IL_0018: stloc.0
                                            IL_0019: ldloc.0
                                            IL_001a: call string [mscorlib]System.String::Concat(string, string)
                                            IL_001f: call void [mscorlib]System.Console::WriteLine(string)
                                            IL_0024: ret
                                            

                                             

                                            See what's going on? IL is popping null from the stack to the local variable 0! The variable still exists and the memory handler is still there! So, from a compiler perspective, null is just a value like all others. It's treated differently for obvious reasons, but it's just a value.

                                             

                                            I hope this helps you understand a little more about GC!

                                            1 of 1 people found this helpful