I love having test projects included in my solutions. Software is alive. I’m constantly making improvements/changes/fixes. When I have customers asking for various features in my code, or for code improvements, being agile and able to publish a changed build with utmost confidence relies largely on a great set of tests that can be run very quickly and exercise my code. These tests very also make it easy to run my code, allowing Test Driven Development techniques, and much greater productivity.

 

I wrote a tool called MemSpect which consists of:

1.      A portion that’s injected into a target process (very early) that intercepts all memory use, such as

a.      Heap operations like creation and allocation

b.      Virtual memory allocations like VirtualAlloc

c.      Managed object creation

2.      For each intercepted allocation, it creates an associated tag which contains

a.      The Sequence number (1,2,3…)

b.      The ThreadId

c.      The Size of the allocation

d.      The callstack (who allocated it and why)

3.      If the object is freed (or garbage collected) the memory is freed and the tag is discarded.

4.      A UI executable shows UI to examine all memory allocations

5.      It can take a “snapshot” of the entire process for later offline analysis even after the target process has exited

 

We discovered and fixed hundreds of memory issues which effect performance. (Reading disk is 1000 times slower than memory, like the difference between 1 second and 17 minutes! So inefficient memory use leads to more slow disk accesses)

 

MemSpect is further described here: http://blogs.msdn.com/b/visualstudio/archive/2012/03/05/visual-studio-11-beta-performance-part-1.aspx

 

I rely heavily on the test infrastructure I’ve created for MemSpect. However, sometimes a test or two throws an Out of Memory exception, especially those that load a full offline snapshot of Visual Studio memory.

 

I know that the production code, when run on a 64 bit OS, can use more memory, but the test infrastructure in VS 2010 does not take advantage of more memory.

 

So I modified the test execution engine to allow much larger memory to be used, if available. ( see Out of memory? Easy ways to increase the memory available to your program)

 

Without the fix, the code below used 164Meg before it failed. With the fix, 655 Meg!  That’s 4 times more memory!

 

 

 

On a 64 bit OS with at least 8 Gigs memory (that’s the same amount of  memory on my Windows Phone!),

Start Visual Studio 2010

File->New->Project->C#->Test

Call it csTest.

 

Paste in the code sample below.

 

You might have to choose Project->Properties->Build->Allow Unsafe Code.

 

Hit Ctrl+R,T to run the test (Don’t hit Ctrl+R, Ctrl+T, which will debug the test!)

 

3:15:52 PM Size = 40000

3:15:52 PM      0 Priv=      55,214,080  Mgd:               0

3:15:52 PM   1000 Priv=      94,519,296  Mgd:      42,323,344

3:15:53 PM   2000 Priv=     135,544,832  Mgd:      83,416,952

3:15:53 PM   3000 Priv=     219,201,536  Mgd:     164,495,376

3:15:53 PM   4000 Priv=     219,459,584  Mgd:     164,495,376

3:15:53 PM Exception SizeofBig = 40,000 Inst #=4,096 TotSize = 163,840,000

System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown.

   at System.Collections.Generic.List`1.set_Capacity(Int32 value)

   at System.Collections.Generic.List`1.EnsureCapacity(Int32 min)

   at System.Collections.Generic.List`1.Add(T item)

   at csTest.MyTests.MyTest() in C:\Users\calvinh\Documents\Visual Studio 2010\Projects\csTest\csTest\UnitTest1.cs:line 110

 

 

 

 

Now make the change: Start a Visual Studio Command Prompt

 

Make a backup copy of QTAgent32.exe before you start. Also Exit VS so the file is not in use.

 

C:\>editbin "Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\QTAgent32.exe" /LargeAddressAware

 

To revert the change:

C:\>editbin "Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\QTAgent32.exe" /LargeAddressAware:no

 

To verify the change:

link /dump /headers "Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\QTAgent32.exe" | more

             122 characteristics

                   Executable

                   Application can handle large (>2GB) addresses

                   32 bit word machine

 

 

 

3:14:45 PM Size = 40000

3:14:45 PM      0 Priv=      55,070,720  Mgd:               0

3:14:46 PM   1000 Priv=     115,257,344  Mgd:      21,889,544

3:14:46 PM   2000 Priv=     197,410,816  Mgd:      21,889,544

3:14:46 PM   3000 Priv=     219,459,584  Mgd:     164,997,440

3:14:46 PM   4000 Priv=     219,844,608  Mgd:     164,997,440

3:14:46 PM   5000 Priv=     386,777,088  Mgd:     328,591,840

3:14:46 PM   6000 Priv=     387,035,136  Mgd:     328,591,840

3:14:46 PM   7000 Priv=     387,424,256  Mgd:     328,591,840

3:14:46 PM   8000 Priv=     387,813,376  Mgd:     328,591,840

3:14:47 PM   9000 Priv=     715,374,592  Mgd:     655,855,360

3:14:47 PM  10000 Priv=     715,632,640  Mgd:     655,855,360

3:14:47 PM  11000 Priv=     715,632,640  Mgd:     655,855,360

3:14:47 PM  12000 Priv=     716,021,760  Mgd:     655,855,360

3:14:47 PM  13000 Priv=     716,410,880  Mgd:     655,855,360

3:14:47 PM  14000 Priv=     716,800,000  Mgd:     655,855,360

3:14:47 PM  15000 Priv=     717,189,120  Mgd:     655,855,360

3:14:47 PM  16000 Priv=     717,189,120  Mgd:     655,855,360

3:14:47 PM Exception SizeofBig = 40,000 Inst #=16,384 TotSize = 655,360,000

System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown.

   at System.Collections.Generic.List`1.set_Capacity(Int32 value)

   at System.Collections.Generic.List`1.EnsureCapacity(Int32 min)

   at System.Collections.Generic.List`1.Add(T item)

   at csTest.MyTests.MyTest() in C:\Users\calvinh\Documents\Visual Studio 2010\Projects\csTest\csTest\UnitTest1.cs:line 110

 

 

 

 

Additional notes:

·        The code uses a log file with a constant name. Normally, a test just passes or fails. But with a log file,  the test can output status. I open the file in VS, choose Tools->Options->Environment->Documents->Auto-load changes, if saved and the log refreshes automatically as the test progresses. TO refresh the log faster, use File.AppendAllText (as in the code comments)

·        Using a log file as test output, one can compare the log with a baseline log and assert any differences.

·        Performance counters are used to show memory use. Note how the managed memory grows in big chunks (doubling)

 

 

see also

Out of memory? Easy ways to increase the memory available to your program

Create your own Test Host using XAML to run your unit tests

http://blogs.msdn.com/b/visualstudio/archive/2012/03/05/visual-studio-11-beta-performance-part-1.aspx

 

 

<code>

 

using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using EnvDTE;
using System.IO;
using System.Diagnostics;
using System.Reflection;

namespace csTest
{
    /// <summary>
    /// Summary description for UnitTest1
    /// </summary>
    [TestClass]
    public class MyTests
    {

        private TestContext testContextInstance;

        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }
        StreamWriter _logStream;
        public const string LogFileName = @"d:\t.txt";
        private int _nLines;
        public void LogString(string logMsg, params object[] parms)
        {
            try
            {
                if (parms != null)
                {
                    logMsg = string.Format(logMsg, parms);
                }
                /* // just add a slash at the beginning of this line to switch
                File.AppendAllText(LogFileName, logMsg);  // slower, but updates every line
                /*/
                _logStream.WriteLine(
                    string.Format("{0:T} {1}",
                    DateTime.Now,
                    logMsg));
                if (_nLines++ == 100) // flush every 100 lines
                {
                    _logStream.Flush();
                }
                //*/
            }
            catch (Exception ex)
            {
                LogString(logMsg);

            }
        }

        [TestInitialize]
        public void Init()
        {
            File.WriteAllText(LogFileName, "");// create/clear log file
            _logStream = new StreamWriter(LogFileName, append: true);
        }
        [TestCleanup]
        public void cleanup()
        {
            _logStream.Close();
        }

        public struct BigClass
        {
            public unsafe fixed int _Array[10000];// = new int[1000000];
        }
        [TestMethod]
        public void MyTest()
        {
            int iInstance = 0;
            var sizeofBig = System.Runtime.InteropServices.Marshal.SizeOf(typeof(BigClass));
            try
            {
                //var thisAsm = System.IO.Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location);
                var procName = "QTAgent32";
                PerformanceCounter pcPrivBytes = new PerformanceCounter(
                    "Process",
                    "Private Bytes",
                    procName);


                PerformanceCounter pcManagedMem = new PerformanceCounter(
                    ".NET CLR Memory",
                    "# Bytes in all Heaps",
                    procName);

                var nInstances = 100000;
                LogString("Size = {0}", sizeofBig);
                var lstBig = new List<BigClass>();
                for (iInstance = 0; iInstance < nInstances; iInstance++)
                {
                    var newBig = new BigClass();
                    lstBig.Add(newBig);

                    if (iInstance % 1000 == 0)
                    {
                        LogString("{0,6} Priv={1,16:n0}  Mgd:{2,16:n0}", 
                            iInstance, 
                            (int)pcPrivBytes.NextValue(),
                            (int)pcManagedMem.NextValue()
                            );
                    }
                }
                LogString("Created {0} ", nInstances);

                System.Threading.Thread.Sleep(5000);

            }
            catch (Exception ex)
            {
                LogString("Exception SizeofBig = {0:n0} Inst #={1:n0} TotSize = {2:n0}\r\n{3}",
                    sizeofBig,
                    iInstance,
                    iInstance * sizeofBig,
                    ex
                    );
                throw;
            }

        }
    }
}

</code>