views:

405

answers:

7

How do I initialize a multi-dimensional array of a primitive type as fast as possible?

I am stuck with using multi-dimensional arrays. My problem is performance. The following routine initializes a 100x100 array in approx. 500 ticks. Removing the int.MaxValue initialization results in approx. 180 ticks just for the looping. Approximately 100 ticks to create the array without looping and without initializing to int.MaxValue.

  • Routines similiar to this are called a few hundred-thousand to several million times during a "run".
  • The array size will not change during a run and arrays are created one-at-a-time, used, then discarded, and a new array created.
  • A "run" which may last from one minute (using 10x10 arrays) to forty-five minutes (100x100).
  • The application creates arrays of int, bool, and struct.
  • There can be multiple "runs" executing at same time, but are not because performance degrades terribly.
  • I am using 100x100 as a base-line.

I am open to suggestions on how to optimize this non-default initialization of an array. One idea I had is to use a smaller primitive type when available. For instance, using byte instead of int, saves 100 ticks. I would be happy with this, but I am hoping that I don't have to change the primitive data type.

    public int[,] CreateArray(Size size) {
        int[,] array = new int[size.Width, size.Height];
        for (int x = 0; x < size.Width; x++) {
            for (int y = 0; y < size.Height; y++) {
                array[x, y] = int.MaxValue;
            }
        }
        return array;
    }

Down to 450 ticks with the following:

    public int[,] CreateArray1(Size size) {
        int iX = size.Width;
        int iY = size.Height;
        int[,] array = new int[iX, iY];
        for (int x = 0; x < iX; x++) {
            for (int y = 0; y < iY; y++) {
                array[x, y] = int.MaxValue;
            }
        }
        return array;
    }

CreateArray5; Accepted Implementation: Limitation: Unable to Resize, can be changed

Down to approximately 165 ticks after a one-time initialization of 2800 ticks. (See my answer below.) If I can get stackalloc to work with multi-dimensional arrays, I should be able to get the same performance without having to intialize the private static array.

    private static bool _arrayInitialized5;
    private static int[,] _array5;

    public static int[,] CreateArray5(Size size) {
        if (!_arrayInitialized5) {
            int iX = size.Width;
            int iY = size.Height;
            _array5 = new int[iX, iY];
            for (int x = 0; x < iX; x++) {
                for (int y = 0; y < iY; y++) {
                    _array5[x, y] = int.MaxValue;
                }
            }
            _arrayInitialized5 = true;
        }
        return (int[,])_array5.Clone();
    }

CreateArray8; Accepted Implemention; Limitation: Requires Full Trust

Down to approximately 165 ticks without using the "clone technique" above. (See my answer below.) I am sure I can get the ticks lower, if I can just figure out the return of CreateArray9.

    public unsafe static int[,] CreateArray8(Size size) {
        int iX = size.Width;
        int iY = size.Height;
        int[,] array = new int[iX, iY];
        fixed (int* pfixed = array) {
            int count = array.Length;
            for (int* p = pfixed; count-- > 0; p++)
                *p = int.MaxValue;
        }
        return array;
    }

Notes

I am providing all code and notes regarding this question as answers. Hopefully, it will save someone's time in the future.

Arrays allocated on the Large Object Heap (LOH) are not part of this discussion. The performance improvements noted hear are only for arrays allocated on the heap.

Stackalloc

My idea of using stackalloc to eliminate initializing array to default values did not work out because the allocated stack memory must be copied out of the method. Meaning, I would have to create another array to hold the results. This array would be initialized defeating the whole purpose of using stackalloc.

CreateArray8; unsafe/fixed method

The CLR will only execute unsafe code if it is in a fully trusted assembly.

CreateArray5; clone method

Requires variables to determine if array is initialized and to store the initialized array. Performance is the same as the unsafe/fixed method after initialization. See Dan Tao's answer for possible solution.

300% Performance Increase?

I suck at percentages, but 300% is what I figured (500 to 165 ticks).


Final Implementation for Application

For this application, I settled on using the "clone" method. Following is a "lean" Generic implementation used in the application with performance samples.

Initialization:

  • Grid<int>; generic clone class initalize: 4348, 4336, 4339, 4654
  • Grid<bool>; generic clone class initalize: 2692, 2684, 3916, 2680
  • Grid<Color>; generic clone class initalize: 3747, 4630, 2702, 2708

Use:

  • Grid<int>; generic clone class: 185, 159, 152, 290
  • Grid<bool>; generic clone class: 39, 36, 44, 46
  • Grid<Color>; generic clone class: 2229, 2431, 2460, 2496

    public class Grid<T> {
        private T[,] _array;
        private T _value;
        private bool _initialized;
        private int _x;
        private int _y;
        public Grid(Size size, T value, bool initialize) {
            _x = size.Width;
            _y = size.Height;
            _value = value;
            if (initialize) {
                InitializeArray();
            }
        }
        private void InitializeArray() {
            int iX = _x;
            int iY = _y;
            _array = new T[iX, iY];
            for (int y = 0; y < iY; y++) {
                for (int x = 0; x < iX; x++) {
                    _array[x, y] = _value;
                }
            }
            _initialized = true;
        }
        public T[,] CreateArray() {
            if (!_initialized) {
                InitializeArray();
            }
            return (T[,])_array.Clone();
        }
    }
    
+4  A: 

A note on your Clone approach: I doubt you will be able to beat it, in terms of performance. However, it could be a breaking change considering that after the first initialization, it disregards the Size parameter and just returns an array of the same size on every call. Depending on whether that actually matters in your scenario, you could either:

  1. Stick with it, because it doesn't matter.
  2. Create a Dictionary<Size, int[,]> (I believe Size would behave properly as a key -- haven't tested) to pre-initialize an array every time a unique Size is requested. The overhead of this I am not sure of.
  3. Abandon the Clone idea.

In case you end up having to go with 3 above, here are a few borderline ridiculous suggestions:

1. Cache your Width and Height properties locally, rather than accessing them from the Size struct on each iteration.

static int[,] CreateArray(Size size) {
    int w = size.Width;
    int h = size.Height;

    int[,] array = new int[w, h];
    for (int x = 0; x < w; x++) {
        for (int y = 0; y < h; y++) {
            array[x, y] = int.MaxValue;
        }
    }

    return array;
}

To create a 1000x1000 array, on my machine, this resulted in an average execution time of about 120000 ticks versus about 140000 ticks.

2. Leverage multiple cores if you have them and initialize the array in parallel.

static int[,] CreateArray(Size size) {
    int w = size.Width;
    int h = size.Height;

    int[,] array = new int[w, h];
    Action<int[,], int, int> fillFirstHalf = FillArray;
    Action<int[,], int, int> fillSecondHalf = FillArray;

    var firstResult = fillFirstHalf.BeginInvoke(array, 0, h / 2, null, null);
    var secondResult = fillSecondHalf.BeginInvoke(array, h / 2, h, null, null);

    fillFirstHalf.EndInvoke(firstResult);
    fillSecondHalf.EndInvoke(secondResult);

    return array;
}

static void FillArray(int[,] array, int ystart, int yend) {
    int w = array.GetLength(0);

    for (int x = 0; x < w; ++x) {
        for (int y = ystart; y < yend; ++y) {
            array[x, y] = int.MaxValue;
        }
    }
}

This one probably isn't a very realistic suggestion in your scenario, since it seems that you're only creating 100x100 arrays, in which case the overhead of the parallelization exceeds the performance gain. However, for creating a 1000x1000 array, I found that this approach reduced my execution times down to about 70k ticks on average (compared to the ~120k ticks I got from the first optimization I suggested).

Also, if you are creating many arrays this way, I would highly recommend parallelizing that (i.e., if you need to create a thousand arrays, create 500 each from two threads), assuming you have multiple processors to do the work for you. Without multiple processors, forget it; adding threads will only hurt your performance.

3. Get enhanced performance by using an unsafe pointer.

Now here's an interesting discovery: it appears that a two-dimensional array in .NET is allocated in a predictable way*: basically as a one-dimensional block of memory, where each "row" is offset from the starting point by an amount equivalent to the length of all previous rows. In other words, a 10x2 array can be accessed using pointer just like a 20x1 array; a 10x10 array can be accessed like a 100x1 array, etc.

I have no idea if this is documented behavior or not. It may be an unspecified implementation detail that you don't want to depend on. Either way, it's worth looking into.

* It's possible that most other .NET developers already knew this and I'm just stating the obvious, in which case, I rescind my comment about this being "interesting".

In any case, knowledge of this allows you to exploit the fixed keyword in an unsafe context for a significant performance gain:

static int[,] CreateArray(Size size) {
    int w = size.Width;
    int h = size.Height;

    int[,] array = new int[w, h];
    unsafe {
        fixed (int* ptr = array) {
            for (int i = 0; i < w * h; ++i)
                ptr[i] = int.MaxValue;
        }
    }

    return array;
}

For initializing arrays of a signifcant size, I would even recommend combining the above approach (parallelization) with this one -- so, keep the same CreateArray from suggestion #2, and then rewrite FillArray as:

static void FillArray(int[,] array, int ystart, int yend) {
    int w = array.GetLength(0);

    unsafe {
        fixed (int* p = array) {
            for (int i = w * ystart; i < w * yend; ++i)
                p[i] = int.MaxValue;
        }
    } 
}

It actually seems that you already figured out this last part before I posted this, but I thought I'd include it anyway mainly for the point about combining unsafe with parallelization.


A note on stackalloc: I think you may be chasing after the leprechaun at the end of the rainbow with this one. According to the documentation on stackalloc:

A block of memory of sufficient size to contain expr elements of type type is allocated on the stack, not the heap; the address of the block is stored in pointer ptr. This memory is not subject to garbage collection and therefore does not have to be pinned (via fixed). The lifetime of the memory block is limited to the lifetime of the method in which it is defined. (emphasis mine)

This leads me to believe that you cannot return an object whose data is stored in memory allocated by stackalloc from a function, because that memory is only allocated for the lifetime of the function.

Dan Tao
I have written a blog about accumulating lots of micro optimizations and improving performance, thought you might like , http://akashkava.com/blog/18/c-optimizations-via-caching/
Akash Kava
Thanks, I should have caught this. Saves 50 Ticks for int[100,100]. Updated answer.
AMissico
The arrays get created in sequence during a "run". There can be multiple "runs" executing at same time. Array size does not change during a "run". I am using 100x100 as a base-line. At this size, the application takes 45 minutes, and 1 minute at 10x10.
AMissico
I used the static array and static flag because I could not get stackalloc to work. If I can get stackalloc to work then there would be no need for them.
AMissico
@AMissico: From my understanding of `stackalloc`, it simply isn't possible to use for a multidimensional array, for the simple reason that `stackalloc` works by allocating a block of memory and returning a *pointer* -- and as you know, accessing an item from within this block works by providing an offset from the pointer. Accessing an item from a *pointer* to a *multidimensional* array (if it were possible) would require *two* offsets. That said, you *could* use `stackalloc` for a jagged array, which I believe has better performance anyway. But that would certainly require some refactoring.
Dan Tao
@Dan Tao: Once I get that pointer from stackalloc, I can do nearly anything, right? The multi-dimensional array is still a single block of memory I should be able to manipulate the pointers at will (within the limits of C#). I am rusty on my pointer knowledge. Any help on CreateArray9?
AMissico
@AMissico: Yes, it seems that you're right about initializing the array in this way. As for your hypothetical `CreateArray9`, what are you aiming for? Figuring out a way to use `stackalloc`?
Dan Tao
@Dan Tao: Yes, I figured out, but flushing out your answer is excellent in my opinion. I believe these questions/answers/comments should be "live" and not static.
AMissico
@Dan Tao: At this point, I think stackalloc is the last and key optimization. With stackalloc, there is no need for fixed because the memory is already fixed. Since fixed is big performance boost then not initializing the array to default values should be another. See my answer for the CreateArray9 I currently have.
AMissico
@AMissico: I don't think using `stackalloc` in this way is possible. See the note at the end of my answer. If you think I've misunderstood, please let me know.
Dan Tao
@Dan Tao: Agreed. I just came to that conclusion also. I would have to copy the array off the stack using Buffer.BlockCopy or Array.Copy. Meaning, I would have to create an array, which would initialize to default values, which is what I am trying to avoid. Also, I may not be able to use "unsafe" due to permissions. (I have to check.) First choice is unsafe/fixed version, if not, then clone version.
AMissico
@Dan Tao: Thank you for your help. You are awesome!
AMissico
@Dan Tao: From 500 to 165 ticks for both possible solutions (unsafe/fixed and clone) is pretty good. I suck at percentages, but isn't that 300% increase in speed? Therefore, using the minimum call count of 200,000, the non-optimized way takes 10 seconds for all calls. The new optimized way will be only 3 seconds total.
AMissico
@Dan Tao: I integrated the Generic Clone method into my test application. Currently, I only integrated into one method commonly call and it has already reduced my "test run" from approx. 66 seconds to approx. 57 seconds.
AMissico
+3  A: 

Unmanaged Array for Granular Control

Create the array in C# unmanaged (unsafe) mode like shown here[code project] and initialize it manually.

In C# managed mode the array first initializes all elements to the default value before your loop starts to populate it. You can likely cut out the doubling up in unmanaged mode. This would save a lot of time.

John K
Good article. Yet, I do not think I will see any performance increase over the CreateArray8 I added to my question.
AMissico
"In C# managed mode the array first initializes all elements to the default value before your loop starts" Yes. Your statement leads me to believe I can stop this initialization using stackalloc.
AMissico
@AMisscio: Indeed `Marshal.AllocHGlobal` *causes the allocated memory to be locked in place. Also, the allocated memory is not zero-filled.* Ref: http://msdn.microsoft.com/en-us/library/s69bkh17.aspx However I got lost in the required implementation details and haven't yet update my answer with a viable unmanaged code solution based on this. Dropping the default CLR initialization on Arrays would definitely speed things up and leave the initialization values to you - which would be Int32.MaxValue in this case.
John K
... and there are likely other unmanaged code ways to drop the initialization phase. I am not very experienced working in that mode. However this info might lead to some real optimized solution.
John K
To supplement this answer, see "Handling of Large Byte Arrays" at http://www.codeproject.com/KB/dotnet/Large_Byte_Array_handling.aspx. It provides some performance measurements and other behaviors.
AMissico
A: 

You can use each row in parallel by using parallel library to initialize it, it will be faster.

However, I think there is limitation to this For, only 64 operations can be queued, but in that case you can queue 0 to 63, 64 to 127 etc.. in Parallel.For ..

public int[,] CreateArray(Size size) { 
    int[,] array = new int[size.Width, size.Height]; 
    System.Threading.Paralle.For (0,size.Width, 
      x=>{ 
        for (int y = 0; y < size.Height; y++) { 
            array[x, y] = int.MaxValue; 
        } 
      }
    ); 
    return array; 
} 

This is included in .NEt 4, however for .NET 3.51 you can get same library from codeplex.

Akash Kava
Do you have the name or link to the codeplex project?
AMissico
+2  A: 

Adding static and unsafe provide some reduction in Ticks. Following are some samples.

  • CreateArray; non-static non-unsafe: 521, 464, 453, 474
  • CreateArray; static: 430, 423, 418, 454
  • CreateArray; unsafe: 485, 464, 435, 414
  • CreateArray; static unsafe: 476, 450, 433, 405

I tried to use stackalloc. My idea was to allocate the array, which would not be initialized because it is unsafe code. I would then zip down the array, initalizing to int.MaxValue as I go, then Clone the array for the return result. But, I got stumped on the multi-dimensional declaration.

Then I remembered using Clone on arrays in another project. Each Array.Clone saved several seconds. Based on this idea, I created the following version of the CreateArray routine getting excellent results.

Now, all I need to do is get stackalloc to work with multi-dimensional arrays.

  • CreateArray5; pre-initialize: 2663, 3036
  • CreateArray5; clone: 157, 172

    private static bool _arrayInitialized5;
    private static int[,] _array5;
    
    
    public static int[,] CreateArray5(Size size) {
        if (!_arrayInitialized5) {
            int iX = size.Width;
            int iY = size.Height;
            _array5 = new int[iX, iY];
            for (int x = 0; x < iX; x++) {
                for (int y = 0; y < iY; y++) {
                    _array5[x, y] = int.MaxValue;
                }
            }
            _arrayInitialized5 = true;
        }
        return (int[,])_array5.Clone();
    }
    

    int[,] actual;

    int iHi = 10000 * 10 * 2; 
    //'absolute minimum times array will be created   (200,000)
    //'could be as high as 10000 * 10 * 20? * 50? (100,000,000?)

    Stopwatch o;

    //'pre-initialize
    o = Stopwatch.StartNew();
    actual = CreateArray5(new Size(100, 100));
    o.Stop();
    Trace.WriteLine(o.ElapsedTicks, "CreateArray5; pre-initialize");
    o = Stopwatch.StartNew();
    for (int i = 0; i < iHi; i++) { actual = CreateArray5(new Size(100, 100)); }
    o.Stop();
    Trace.WriteLine(o.ElapsedTicks / iHi, "CreateArray5; static unsafe clone");
AMissico
+1  A: 

This is untested in this scenario, but has worked in similar ones. Sometimes, swapping the order of the array traversal speeds things up due to memory locality.

In other words, instead of doing for(x) ... for(y) do instead for(y) ... for(x).

Erich Mirabal
In this case, it consistently saves approx. 20 ticks.
AMissico
Well, there you go. Every tick counts.
Erich Mirabal
The only previous time I worried about ticks like this was developing for Windows CE 2.0 due to limited processor ability. This is the first time due to the enormous number of iterations. :O)
AMissico
A: 

I was able to get the ticks down to approximately 165. See CreateArray8 below.

I got an idea from the "Range Check Elimination" section of the CodeProject article at http://www.codeproject.com/KB/dotnet/arrays.aspx provided by jdk. (@jdk, thank you very much.) The idea was to eliminate the range checking by using a pointer and initializing each element in one loop. I was able to get the ticks down to approximately 165. Just as good as cloning without the delay of pre-initialization and the supporting static variables. (See my other answer.)

I bet I can cut this in half, if I can just figure out the return of CreateArray9.

  • CreateArray3; static unsafe: 501, 462, 464, 462
  • CreateArray7; static unsafe for(y,x): 452, 451, 444, 429
  • CreateArray8; static unsafe pointer single_for: 187, 173, 156, 145

[TestClass]
public class CreateArrayTest {

    public static unsafe int[,] CreateArray3(Size size) {
        int iX = size.Width;
        int iY = size.Height;
        int[,] array = new int[iX, iY];
        for (int x = 0; x < iX; x++) {
            for (int y = 0; y < iY; y++) {
                array[x, y] = int.MaxValue;
            }
        }
        return array;
    }

    public unsafe static int[,] CreateArray7(Size size) {
        int iX = size.Width;
        int iY = size.Height;
        int[,] array = new int[iX, iY];
        for (int y = 0; y < iY; y++) {
            for (int x = 0; x < iX; x++) {
                array[x, y] = int.MaxValue;
            }
        }
        return array;
    }

    public unsafe static int[,] CreateArray8(Size size) {
        int iX = size.Width;
        int iY = size.Height;
        int[,] array = new int[iX, iY];
        fixed (int* pfixed = array) {
            int count = array.Length;
            for (int* p = pfixed; count-- > 0; p++)
                *p = int.MaxValue;
        }
        return array;
    }

    public unsafe static int[,] CreateArray9(Size size) {
        int iX = size.Width;
        int iY = size.Height;
        void* array = stackalloc int[iX * iY];
        int count = iX * iY;
        for (int* p = (int*)array; count-- > 0; p++)
            *p = int.MaxValue;

        //return (int[,])array; //how to return?
        return new int[1, 1];
    }

    [TestMethod()]
    public void CreateArray_Test() {

        int[,] actual;

        int iHi = 10000 * 10 * 2;
        //'absolute minimum times array will be created   (200,000)
        //'could be as high as 10000 * 10 * 20? * 50? (100,000,000?)

        Stopwatch o;

        o = Stopwatch.StartNew();
        for (int i = 0; i < iHi; i++) { actual = CreateArray3(new Size(100, 100)); }
        o.Stop();
        Trace.WriteLine(o.ElapsedTicks / iHi, "CreateArray3; static unsafe");

        o = Stopwatch.StartNew();
        for (int i = 0; i < iHi; i++) { actual = CreateArray7(new Size(100, 100)); }
        o.Stop();
        Trace.WriteLine(o.ElapsedTicks / iHi, "CreateArray7; static unsafe for(y,x)");

        o = Stopwatch.StartNew();
        for (int i = 0; i < iHi; i++) { actual = CreateArray8(new Size(100, 100)); }
        o.Stop();
        Trace.WriteLine(o.ElapsedTicks / iHi, "CreateArray8; static unsafe pointer single_for");

    }

}
AMissico
A: 

Class and Generic using "Clone" method.

  • MDArray; clone class initalize: 2444, 2587, 2421, 2406
  • MDArray; clone class: 440, 362, 198, 139
  • Grid<int>; generic clone class initalize: 5344, 5334, 5693, 5272
  • Grid<int>; generic clone class: 187, 204, 199, 288
  • Grid<bool>; generic clone class initalize: 3585, 3537, 3552, 3569
  • Grid<bool>; generic clone class: 37, 44, 36, 43
  • Grid<Color>; generic clone class initalize: 4139, 3536, 3503, 3533
  • Grid<Color>; generic clone class: 2737, 3137, 2414, 2171

[TestClass]
public class CreateArrayTest {

    public class MDArray {
        private bool _initialized;
        private int[,] _array;
        private int _x;
        private int _y;
        private int _value;
        public MDArray(Size size, int value, bool initialize) {
            _x = size.Width;
            _y = size.Height;
            _value = value;
            if (initialize) {
                InitializeArray();
            }
        }
        private void InitializeArray() {
            int iX = _x;
            int iY = _y;
            _array = new int[iX, iY];
            for (int y = 0; y < iY; y++) {
                for (int x = 0; x < iX; x++) {
                    _array[x, y] = _value;
                }
            }
            _initialized = true;
        }
        public int[,] CreateArray() {
            if (!_initialized) {
                InitializeArray();
            }
            return (int[,])_array.Clone();
        }
    }

    public class Grid<T> {
        private T[,] _array;
        private T _value;
        private bool _initialized;
        private int _x;
        private int _y;
        public Grid(Size size, T value, bool initialize) {
            _x = size.Width;
            _y = size.Height;
            _value = value;
            if (initialize) {
                InitializeArray();
            }
        }
        private void InitializeArray() {
            int iX = _x;
            int iY = _y;
            _array = new T[iX, iY];
            for (int y = 0; y < iY; y++) {
                for (int x = 0; x < iX; x++) {
                    _array[x, y] = _value;
                }
            }
            _initialized = true;
        }
        public T[,] CreateArray() {
            if (!_initialized) {
                InitializeArray();
            }
            return (T[,])_array.Clone();
        }
    }

    [TestMethod()]
    public void CreateArray_Test() {

        int[,] actual;

        int iHi = 10000 * 10 * 2; //'absolute minimum times array will be created   (200,000)
        //                          'could be as high as 10000 * 10 * 20? * 50? (100,000,000?)

        Stopwatch o;

        o = Stopwatch.StartNew();
        MDArray oMDArray = new MDArray(new Size(100, 100), int.MaxValue, true);
        o.Stop();
        Trace.WriteLine(o.ElapsedTicks, "     MDArray; clone class initalize");
        o = Stopwatch.StartNew();
        for (int i = 0; i < iHi; i++) { actual = oMDArray.CreateArray(); }
        o.Stop();
        Trace.WriteLine(o.ElapsedTicks / iHi, "     MDArray; clone class");

        o = Stopwatch.StartNew();
        Grid<int> oIntMap = new Grid<int>(new Size(100, 100), int.MaxValue, true);
        o.Stop();
        Trace.WriteLine(o.ElapsedTicks, "   Grid<int>; generic clone class initalize");
        o = Stopwatch.StartNew();
        for (int i = 0; i < iHi; i++) { actual = oIntMap.CreateArray(); }
        o.Stop();
        Trace.WriteLine(o.ElapsedTicks / iHi, "   Grid<int>; generic clone class");

        bool[,] fActual;
        o = Stopwatch.StartNew();
        Grid<bool> oBolMap = new Grid<bool>(new Size(100, 100), true, true);
        o.Stop();
        Trace.WriteLine(o.ElapsedTicks, "  Grid<bool>; generic clone class initalize");
        o = Stopwatch.StartNew();
        for (int i = 0; i < iHi; i++) { fActual = oBolMap.CreateArray(); }
        o.Stop();
        Trace.WriteLine(o.ElapsedTicks / iHi, "  Grid<bool>; generic clone class");

        Color[,] oActual;
        o = Stopwatch.StartNew();
        Grid<Color> oColMap = new Grid<Color>(new Size(100, 100), Color.AliceBlue, true);
        o.Stop();
        Trace.WriteLine(o.ElapsedTicks, " Grid<Color>; generic clone class initalize");
        o = Stopwatch.StartNew();
        for (int i = 0; i < iHi; i++) { oActual = oColMap.CreateArray(); }
        o.Stop();
        Trace.WriteLine(o.ElapsedTicks / iHi, " Grid<Color>; generic clone class");
    }
}
AMissico