C# Heap(ing) vs Stack(ing) in .NET (PartIV)

2023-11-22 13:30

本文主要是介绍C# Heap(ing) vs Stack(ing) in .NET (PartIV),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

以下文章来自:http://www.c-sharpcorner.com/UploadFile/rmcochran/csharp_memory_401282006141834PM/csharp_memory_4.aspx

 

Graphing

Let's look at this from the GC's point of view. If we are responsible for "taking out the trash" we need a plan to do this effectively. Obviously, we need to determine what is garbage and what is not (this might be a bit painful for the pack-rats out there). 

In order to determine what needs to be kept, we'll first make the assumption that everything not being used is trash (those piles of old papers in the corner, the box of junk in the attic, everything in the closets, etc.)  Imagine we live with our two good friends: Joseph Ivan Thomas (JIT) and Cindy Lorraine Richmond (CLR). Joe and Cindy keep track of what they are using and give us a list of things they need to keep. We'll call the initial list our "root" list because we are using it as a starting point.  We'll be keeping a master list to graph where everything is in the house that we want to keep. Anything that is needed to make things on our list work will be added to the graph (if we're keeping the TV we don't throw out the remote control for the TV, so it will be added to the list. If we're keeping the computer the keyboard and monitor will be added to the "keep" list).

This is how the GC determines what to keep as well. It receives a list of "root" object references to keep from just-in-time (JIT) compiler and common language runtime (CLR) (Remember Joe and Claire?) and then recursively searches object references to build a graph of what should be kept. 

Roots consist of:

  • Global/Static pointers. One way to make sure our objects are not garbage collected by keeping a reference to them in a static variable.
  • Pointers on the stack. We don't want to throw away what our application's threads still need in order to execute.
  • CPU register pointers. Anything in the managed heap that is pointed to by a memory address in the CPU should be preserved (don't throw it out).

 Stacking_Heaping1.gif

In the above diagram, objects 1, 3, and 5 in our managed heap are referenced from a root 1 and 5 are directly referenced and 3 is found during the recursive search.  If we go back to our analogy and object 1 is our television, object 3 could be our remote control. After all objects are graphed we are ready to move on to the next step, compacting.

Compacting

Now that we have graphed what objects we will keep, we can just move the "keeper objects" around to pack things up.

 Stacking_Heaping2.gif

Fortunately, in our house we don't need to clean out the space before we put something else there. Since Object 2 is not needed, as the GC we'll move Object 3 down and fix the pointer in Object 1.

 Stacking_Heaping3.gif

Next, as the GC, we'll copy Object 5 down

 Stacking_Heaping4.gif

Now that everything is cleaned up we just need to write a sticky note and put it on the top of our compacted heap to let Claire know where to put new objects.

 Stacking_Heaping5.gif

Knowing the nitty-gritty of CG helps in understanding that moving objects around can be very taxing. As you can see, it makes sense that if we can reduce the size of what we have to move, we'll improve the whole GC process because there will be less to copy.

What about things outside the managed heap?

As the person responsible for garbage collection, one problem we run into in cleaning house is how to handle objects in the car. When cleaning, we need to clean everything up. What if the laptop is in the house and the batteries are in the car?

There are situations where the GC needs to execute code to clean up non-managed resources such as files, database connections, network connections, etc. One possible way to handle this is through a finalizer.

class Sample

{

          ~Sample()

          {

                    // FINALIZER: CLEAN UP HERE

          }

}

During object creation, all objects with a finalizer are added to a finalization queue. Let's say objects 1, 4, and 5 have finalizers and are on the finalization queue.  Let's look at what happens when objects 2 and 4 are no longer referenced by the application and ready for garbage collection.
 Stacking_Heaping6.gif 
 

Object 2 is treated in the usual fashion. However, when we get to object 4, the GC sees that it is on the finalization queue and instead of reclaiming the memory object 4 owns, object 4 is moved and it's finalizer is added to a special queue named freachable. 
 Stacking_Heaping7.gif  

There is a dedicated thread for executing freachable queue items. Once the finalizer is executed by this thread on Object 4, it is removed from the freachable queue. Then and only then is Objet 4 ready for collection.

 Stacking_Heaping8.gif

So Object 4 lives on until the next round of GC.

Because adding a finalizer to our classes creates additional work for GC it can be very expensive and adversely affect the performance garbage collection and thus our program. Only use finalizers when you are absolutely sure you need them.

A better practice is to be sure to clean up non-managed resources. As you can imagine, it is preferable to explicitly close connections and use the IDisposable interface for cleaning up instead of a finalizer where possible.

IDisposaible

Classes that implement IDisposable perform clean-up in the Dispose() method (which is the only signature of the interface). So if we have a ResouceUser class instead of using a finalizer as follows:

public class ResourceUser

{

          ~ResourceUser() // THIS IS A FINALIZER

          {

                    // DO CLEANUP HERE

          }

}

We can use IDisposable as a better way to implement the same functionality:

public class ResourceUser : IDisposable

{

          #region IDisposable Members

 

          public void Dispose()

          {

                    // CLEAN UP HERE!!!

          }

 

          #endregion

}

 

IDisposable in integrated with the using keyword. At the end of the using block Dispose() is called on the object declared in using(). The object should not be referenced after the using block because it should be essentially considered "gone" and ready to be cleaned up by the GC.

public static void DoSomething()

{

ResourceUser rec = new ResourceUser();

 

using (rec)

{

                // DO SOMETHING

 

} // DISPOSE CALLED HERE

 

            // DON'T ACCESS rec HERE

 

}

 

I like putting the declaration for the object in the using block because it makes more sense visabally and rec is no longer available outside of the scope of the using block. Whis this pattern is more in line with the intention of the IDisposible interface, it is not required.

public static void DoSomething()

{

using (ResourceUser rec = new ResourceUser())

{

                // DO SOMETHING

 

} // DISPOSE CALLED HERE

}

 

By using using() with classes that implement IDisposible we can perform our cleanup without putting additional overhead on the GC by forcing it to finalize our objects.

Static Variables: Watch Out!

class Counter

{

          private static int s_Number = 0;

 

          public static int GetNextNumber()

          {

                    int newNumber = s_Number;

 

                    // DO SOME STUFF

           

                    s_Number = newNumber + 1;

 

                    return newNumber;

          }

}

 

If two threads call GetNextNumber() at the same time and both are assigned the same value for newNumber before s_Num}

If two threads call GetNextNumber() at the same time and both are assigned the same value for newNumber before s_Number is incremented they will return the same result!word is one way to ensure only one thread can access a block of code at a time. As a best practice, you should lock as little code as possible because threads have to wait in a queue to execute the code in the lock()  block and it can be inefficient.

class Counter

{

          private static int s_Number = 0;

 

          public static int GetNextNumber()

          {

                    lock (typeof(Counter))

                    {

                             int newNumber = s_Number;

 

                             // DO SOME STUFF

 

                             newNumber += 1;

                             s_Number = newNumber;

 

                             return newNumber;

                    }

          }

}

Static Variables: Watch Out... Number 2!

The next thing we have to watch out for objects referenced by static variables.  Remember, how anything that is referenced by a "root" is not cleaned up. Here's one of the ugliest examples I can come up with:

class Olympics

{

          public static Collection<Runner> TryoutRunners;

}

 

class Runner

{

          private string _fileName;

          private FileStream _fStream;

 

          public void GetStats()

          {

                    FileInfo fInfo = new FileInfo(_fileName);

                    _fStream = _fileName.OpenRead();

          }

}

 

Because the Runner Collection is static for the Olympics class, not only will objects in the collection will not be released for garbage collection (they are all indirectly referenced through a root), but as you probably noticed, every time we  run GetStats() the stream is opened to the file. Because it is not closed and never released by GC this code is effectively a disaster waiting to happen. Imagine we have 100,000 runners trying out for the Olympics.  We would end up with that many non-collectable objects each with an open resource.  Ouch! Talk about poor performance!

Singleton

One trick to keep things light is to keep only one instance of a class in memory at all times. To do this we can use the GOF Singleton Pattern.

One trick to keep things light is to keep only one instance of a utility class in memory at all times. One easy way to  do this we can use the GOF Singleton Pattern. Singletons should be used with caution because they are really "global variables" and cause us much headached and "strange" behavior in multi-threaded applications where different threads could be altering the state of the object.  If we are using the singleton pattern (or any global variable) we should be able to justify it (in other words... don't do it without a good reason).

public">{

          private static Earth _instance = new Earth();

 

          private Earth() { }

 

          public static Earth GetInstance() { return _instance; }

}

 

We have a private constructor so only Earth can execute it's constructor and make an Earth. We have a static instance of Earth and a static method to get the instance. This particular implementation is thread safe because the CLR ensures thread safe creation of static variables. This is the most elegant way I have found to implement the singleton pattern in C#.

In Conclusion...

So to wrap up, some things we can do to improve GC performance are:

  1. Clean up. Don't leave resources open!  Be sure to close all connections that are opened and clean up all non-managed objects as soon as possible. As a general rule when using non-managed objects, instantiate as late as possible and clean up as soon as possible.
  2. Don't overdo references.  Be reasonable when using references objects.  Remember, if our object is alive, all of it's referenced objects will not be collected (and so on, and so on). When we are done with something referenced by class, we can remove it by either setting the reference to null.  One trick I like to do is setting unused references to a custom light weight NullObject to avoid getting null reference exceptions. The fewer references laying about when the GC kicks off, the less pressure the mapping process will be. 
  3. Easy does it with finalizers. Finalizers are expensive during GC we should ONLY use them if we can justify it. If we can use IDisposible instead of a finalizer, it will be more efficient because our object can be cleaned up in one GC pass instead of two.
  4. Keep objects and their children together. It is easier on the GC to copy large chunks of memory together instead of having to essentially de-fragment the heap at each pass, so when we declare a object composed of many other objects, we should instantiate them as closely together as possible.

Next time we'll look even more closely at the GC process and look into ways to check under the hood as your program executes to discover problems that may need to be cleaned up.

转载于:https://www.cnblogs.com/luoyuonline/archive/2010/04/30/1725258.html

这篇关于C# Heap(ing) vs Stack(ing) in .NET (PartIV)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/410478

相关文章

C#高效实现在Word文档中自动化创建图表的可视化方案

《C#高效实现在Word文档中自动化创建图表的可视化方案》本文将深入探讨如何利用C#,结合一款功能强大的第三方库,实现在Word文档中自动化创建图表,为你的数据呈现和报告生成提供一套实用且高效的解决方... 目录Word文档图表自动化:为什么选择C#?从零开始:C#实现Word文档图表的基本步骤深度优化:C

在C#中分离饼图的某个区域的操作指南

《在C#中分离饼图的某个区域的操作指南》在处理Excel饼图时,我们可能需要将饼图的各个部分分离出来,以使它们更加醒目,Spire.XLS提供了Series.DataFormat.Percent属性,... 目录引言如何设置饼图各分片之间分离宽度的代码示例:从整个饼图中分离单个分片的代码示例:引言在处理

C#借助Spire.XLS for .NET实现在Excel中添加文档属性

《C#借助Spire.XLSfor.NET实现在Excel中添加文档属性》在日常的数据处理和项目管理中,Excel文档扮演着举足轻重的角色,本文将深入探讨如何在C#中借助强大的第三方库Spire.... 目录为什么需要程序化添加Excel文档属性使用Spire.XLS for .NET库实现文档属性管理Sp

C++,C#,Rust,Go,Java,Python,JavaScript的性能对比全面讲解

《C++,C#,Rust,Go,Java,Python,JavaScript的性能对比全面讲解》:本文主要介绍C++,C#,Rust,Go,Java,Python,JavaScript性能对比全面... 目录编程语言性能对比、核心优势与最佳使用场景性能对比表格C++C#RustGoJavapythonjav

C# 预处理指令(# 指令)的具体使用

《C#预处理指令(#指令)的具体使用》本文主要介绍了C#预处理指令(#指令)的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录1、预处理指令的本质2、条件编译指令2.1 #define 和 #undef2.2 #if, #el

C#实现将Excel工作表拆分为多个窗格

《C#实现将Excel工作表拆分为多个窗格》在日常工作中,我们经常需要处理包含大量数据的Excel文件,本文将深入探讨如何在C#中利用强大的Spire.XLSfor.NET自动化实现Excel工作表的... 目录为什么需要拆分 Excel 窗格借助 Spire.XLS for .NET 实现冻结窗格(Fro

C# Semaphore与SemaphoreSlim区别小结

《C#Semaphore与SemaphoreSlim区别小结》本文主要介绍了C#Semaphore与SemaphoreSlim区别小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的... 目录一、核心区别概览二、详细对比说明1.跨进程支持2.异步支持(关键区别!)3.性能差异4.API 差

C# List.Sort四种重载总结

《C#List.Sort四种重载总结》本文详细分析了C#中List.Sort()方法的四种重载形式及其实现原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友... 目录1. Sort方法的四种重载2. 具体使用- List.Sort();- IComparable

C#中Trace.Assert的使用小结

《C#中Trace.Assert的使用小结》Trace.Assert是.NET中的运行时断言检查工具,用于验证代码中的关键条件,下面就来详细的介绍一下Trace.Assert的使用,具有一定的参考价值... 目录1、 什么是 Trace.Assert?1.1 最简单的比喻1.2 基本语法2、⚡ 工作原理3

C#中DateTime的格式符的实现示例

《C#中DateTime的格式符的实现示例》本文介绍了C#中DateTime格式符的使用方法,分为预定义格式和自定义格式两类,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值... 目录DateTime的格式符1.核心概念2.预定义格式(快捷方案,直接复用)3.自定义格式(灵活可控