Be careful about nothing in managed code
Here’s a pattern of code use I’ve seen in a few places. There’s a function DeserializeList that returns an array of various sizes, depending on the input. This code can be called to deserialize (rehydrate) an object from a stream. For example, an XML deserializer might create an XML node from a binary stream. The stream might indicate the number of XML Attributes or XML Child nodes as 0 (reader.Readint32 returns 0). When DeserializeList is called to get a list of the Attributes or Child nodes for an XML node, often that list is empty. The returned value is thus an array of length 0.
From inspection, the code already can return a NULL, so callers might already be written to handle the NULL return value. A simple fix is to return NULL if the count is 0.
/// <summary>
/// Deserializes an array of items using the reader
/// </summary>
/// <typeparam name="T">the element type</typeparam>
/// <param name="reader">the reader</param>
/// <returns>an array of elements</returns>
public static T[] DeserializeList<T>(this IReader reader) where T : IBinarySerializable, new()
{
if (reader.ReadBoolean())
{
return null;
}
int count = reader.ReadInt32();
T[] array = new T[count];
for (int i = 0; i < count; i++)
{
array[i] = reader.Deserialize<T>();
}
return array;
}
One might think this is fine, and indeed, the code can be written so it functions correctly: creating an XML node with no attributes. However, the code is inefficient, and can slow down your application.
In particular, null length arrays still cause a CLR memory allocation of 16 bytes (in a 32 bit app)
Start Visual Studio, File->New->Project->C# Windows Console Application.
Paste in the code below. Hit Ctrl-F5 to Run the code.
The sample has 2 parts. The first part (g_fix = 0) creates a whole bunch of zero length arrays. The next part returns NULL, without creating the zero length array.
If you have Excel on your machine, it will automatically start.
When Excel starts, type these keystrokes exactly (we can automate Excel with a macro or use Automation, but that’s another story)
Alt->N->N->Enter
Alt N(to choose Insert)
N (to choose Line Graph)
Enter (to choose the first kind of line graph
See how easy it is to create a picture?
See also:
Native heap cost: The cost of using nothing
Use Perfmon to analyze your managed memory
See and hear the effects of Garbage Collection
Its easy to create a graph of memory use in Excel
<Code>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
static PerformanceCounter _PerformanceCounterGC;
static PerformanceCounter _PerformanceCounterGCPctTime;
static bool g_Fix;
static void Main(string[] args)
{
var thisasm = System.IO.Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location);
// "ConsoleApplication1"
_PerformanceCounterGC = new PerformanceCounter(".NET CLR Memory", "# Gen 0 Collections", thisasm);
_PerformanceCounterGCPctTime = new PerformanceCounter(".NET CLR Memory", "% Time in GC", thisasm);
var outFileName = Path.ChangeExtension(Path.GetTempFileName(), ".csv");
Action<string> outputIt = (string str) =>
{
Console.WriteLine(str);
System.IO.File.AppendAllText(outFileName, str + "\r\n");
};
var oldGCs = 0;
var totGCs = 0;
var nIters = 100;
outputIt.Invoke("Iteration,NumGCs, PctTimeGC");
for (int i = 1; i < nIters; i++)
{
if (i == nIters/2)
{
g_Fix = true;
}
for (int j = 0; j < 10000000; j++)
{
Func<int> getcnt = () => 0; // a func that returns a count to use
var theList = DeserializeList<string>(getcnt);
if (theList != null)
{
foreach (string str in theList)
{
var y = str;// put a bpt here if you like
}
}
}
var curGCs = (int)_PerformanceCounterGC.NextValue();
var NumGCs = curGCs - oldGCs;
totGCs += NumGCs;
oldGCs = curGCs;
var GCPctTime = _PerformanceCounterGCPctTime.NextValue();
outputIt.Invoke(string.Format("{0,-5}, {1,-5}, {2, -5:n3}", i, NumGCs, GCPctTime));
}
// start Excel or whatever's registered for .CSV
Process.Start(outFileName);
}
// defined in far away place
public static T[] DeserializeList<T>(Func<int> getNumItems)
{
var numItems = getNumItems.Invoke();
if (g_Fix && numItems == 0) // if Fixed, and no items, return NULL
{
return null;
}
// without the Fix, creates an empty array fr numItems==0
T[] result = new T[numItems]; // create array
return result;
}
/*
/// <summary>
/// Deserializes an array of items using the reader
/// </summary>
/// <typeparam name="T">the element type</typeparam>
/// <param name="reader">the reader</param>
/// <returns>an array of elements</returns>
public static T[] DeserializeList<T>(this IReader reader) where T : IBinarySerializable, new()
{
if (reader.ReadBoolean())
{
return null;
}
int count = reader.ReadInt32();
T[] array = new T[count];
for (int i = 0; i < count; i++)
{
array[i] = reader.Deserialize<T>();
}
return array;
}
*/
}
}
</Code>