Use a large array in C#
In C#, sometimes I need to be able to apply for a large array, use it, and then immediately release the memory it consumes.
Sometimes I need to allocate a large array, use it and then release its memory space immediately.
Due to the int[] array = new int[1000000]; provided in C#, the memory release of the array is difficult to be fully controlled by the programmer, and the program may become very slow after applying for a large array.
If I use something like int[] array = new int[1000000]; , it will be difficult to release its memory space by programmer and the app probably runs slower and slower.
Especially in C# + OpenGL programming, I need to design an unmanaged array when using VAO/VBO. For example, when glBufferData, I hope to use the following glBufferData:
Specially in C#+OpenGL routines when I'm using VAO/VBO, I need an unmanaged array for glBufferData:
/// <summary> /// Set the data of the current VBO. /// </summary> /// <param name="target"></param> /// <param name="data"></param> /// <param name="usage"></param> public static void glBufferData(uint target, UnmanagedArrayBase data, uint usage) { GetDelegateFor<glBufferData>()((uint)target, , // Use unmanaged arrays , // Use unmanaged arrays (uint)usage); } // ... // glBufferData statement private delegate void glBufferData(uint target, int size, IntPtr data, uint usage);
When specifying VBO data, it may be float, vec3 and other types:
And the content in VBO can be float, vec3 and any other structs.
/// <summary> /// The position array of the pyramid. /// </summary> static vec3[] positions = new vec3[] { new vec3(0.0f, 1.0f, 0.0f), new vec3(-1.0f, -1.0f, 1.0f), // ... new vec3(-1.0f, -1.0f, 1.0f), }; // Create a vertex buffer for the vertex data. { uint[] ids = new uint[1]; (1, ids); (GL.GL_ARRAY_BUFFER, ids[0]); // Use vec3 as parameter of generic unmanaged array UnmanagedArray<vec3> positionArray = new UnmanagedArray<vec3>(); for (int i = 0; i < ; i++) { // Use this[i] to read and write elements of an unmanaged array positionArray[i] = positions[i]; } (, positionArray, ); (positionLocation, 3, GL.GL_FLOAT, false, 0, ); (positionLocation); }
UnmanagedArray<T>
So I designed such a non-managed array type: no unsafe, which can accept any struct type as a generic parameter, and can free memory at any time.
So I designed this UnmangedArray<T> : no 'unsafe' keyword, takes any struct as generic parameter, can be released anytime you want.
1 /// <summary> 2 /// Unmanaged arrays with elements of sbyte, byte, char, short, ushort, int, uint, long, ulong, float, double, decimal, bool or other struct. 3 /// <para> cannot use enum type as T. </para> 4 /// </summary> 5 /// <typeparam name="T">sbyte, byte, char, short, ushort, int, uint, long, ulong, float, double, decimal, bool or other struct, the enum type cannot be used as T. </typeparam> 6 public class UnmanagedArray<T> : UnmanagedArrayBase where T : struct 7 { 8 9 /// <summary> 10 ///Unmanaged arrays with element types sbyte, byte, char, short, ushort, int, uint, long, ulong, float, double, decimal, bool or other struct. 11 /// </summary> 12 /// <param name="count"></param> 13 [MethodImpl()] 14 public UnmanagedArray(int count) 15 : base(count, (typeof(T))) 16 { 17 } 18 19 /// <summary> 20 /// Get or set an element indexed to <paramref name="index"/>. 21 /// </summary> 22 /// <param name="index"></param> 23 /// <returns></returns> 24 public T this[int index] 25 { 26 get 27 { 28 if (index < 0 || index >= ) 29 throw new IndexOutOfRangeException("index of UnmanagedArray is out of range"); 30 31 var pItem = + (index * elementSize); 32 //var obj = (pItem, typeof(T)); 33 //T result = (T)obj; 34 T result = <T>(pItem);// works in .net 4.5.1 35 return result; 36 } 37 set 38 { 39 if (index < 0 || index >= ) 40 throw new IndexOutOfRangeException("index of UnmanagedArray is out of range"); 41 42 var pItem = + (index * elementSize); 43 //(value, pItem, true); 44 <T>(value, pItem, true);// works in .net 4.5.1 45 } 46 } 47 48 /// <summary> 49 /// Get each element in order in index order. 50 /// </summary> 51 /// <returns></returns> 52 public IEnumerable<T> GetElements() 53 { 54 if (!) 55 { 56 for (int i = 0; i < ; i++) 57 { 58 yield return this[i]; 59 } 60 } 61 } 62 } 63 64 /// <summary> 65 /// The base class of unmanaged arrays. 66 /// </summary> 67 public abstract class UnmanagedArrayBase : IDisposable 68 { 69 70 /// <summary> 71 /// Array pointer. 72 /// </summary> 73 public IntPtr Header { get; private set; } 74 75 /// <summary> 76 /// Number of elements. 77 /// </summary> 78 public int Count { get; private set; } 79 80 /// <summary> 81 /// The number of bytes of a single element. 82 /// </summary> 83 protected int elementSize; 84 85 /// <summary> 86 /// The number of bytes applied. (Number of elements * Number of bytes of a single element). 87 /// </summary> 88 public int ByteLength 89 { 90 get { return * ; } 91 } 92 93 94 /// <summary> 95 /// Unmanaged array. 96 /// </summary> 97 /// <param name="elementCount">Number of elements. </param> 98 /// <param name="elementSize">Number of bytes of a single element. </param> 99 [MethodImpl()] 100 protected UnmanagedArrayBase(int elementCount, int elementSize) 101 { 102 = elementCount; 103 = elementSize; 104 105 int memSize = elementCount * elementSize; 106 = (memSize); 107 108 (this); 109 } 110 111 private static readonly List<IDisposable> allocatedArrays = new List<IDisposable>(); 112 113 /// <summary> 114 /// Release all <see cref="UnmanagedArray"/> immediately.115 /// </summary> 116 [MethodImpl()] 117 public static void FreeAll() 118 { 119 foreach (var item in allocatedArrays) 120 { 121 (); 122 } 123 (); 124 } 125 126 ~UnmanagedArrayBase() 127 { 128 Dispose(); 129 } 130 131 #region IDisposable Members 132 133 /// <summary> 134 /// Internal variable which checks if Dispose has already been called 135 /// </summary> 136 protected Boolean disposed; 137 138 /// <summary> 139 /// Releases unmanaged and - optionally - managed resources 140 /// </summary> 141 /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> 142 protected void Dispose(Boolean disposing) 143 { 144 if (disposed) 145 { 146 return; 147 } 148 149 if (disposing) 150 { 151 //Managed cleanup code here, while managed refs still valid 152 } 153 //Unmanaged cleanup code here 154 IntPtr ptr = ; 155 156 if (ptr != ) 157 { 158 = 0; 159 = ; 160 (ptr); 161 } 162 163 disposed = true; 164 } 165 166 /// <summary> 167 /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. 168 /// </summary> 169 public void Dispose() 170 { 171 (true); 172 (this); 173 } 174 175 #endregion 176 177 } UnmanagedArray
How to use
UnmanagedArray<T> is very simple to use, just like a normal array:
Using UnamangedAray<T> is just like a normal array(int[], vec3[], etc.):
internal static void TypicalScene() { const int count = 100; // Test float type var floatArray = new UnmanagedArray<float>(count); for (int i = 0; i < count; i++) { floatArray[i] = i; } for (int i = 0; i < count; i++) { var item = floatArray[i]; if (item != i) { throw new Exception(); } } // Test the int type var intArray = new UnmanagedArray<int>(count); for (int i = 0; i < count; i++) { intArray[i] = i; } for (int i = 0; i < count; i++) { var item = intArray[i]; if (item != i) { throw new Exception(); } } // Test bool type var boolArray = new UnmanagedArray<bool>(count); for (int i = 0; i < count; i++) { boolArray[i] = i % 2 == 0; } for (int i = 0; i < count; i++) { var item = boolArray[i]; if (item != (i % 2 == 0)) { throw new Exception(); } } // Test vec3 type var vec3Array = new UnmanagedArray<vec3>(count); for (int i = 0; i < count; i++) { vec3Array[i] = new vec3(i * 3 + 0, i * 3 + 1, i * 3 + 2); } for (int i = 0; i < count; i++) { var item = vec3Array[i]; var old = new vec3(i * 3 + 0, i * 3 + 1, i * 3 + 2); if ( != || != || != ) { throw new Exception(); } } // Test foreach foreach (var item in ()) { (item); } // Free the memory occupied by this array, and after that, you can no longer use vec3Array. (); // Immediately release all memory occupied by unmanaged arrays, and after that, you can no longer use the arrays you requested above. (); }
Quick read and write UnmanagedArray<T>
UnmanagedArrayHelper
Since you often need to apply for and use a large UnmanagedArray<T>, using this[index] index method directly will be slower, so I added several auxiliary methods to specifically solve the problem of fast reading and writing UnmanagedArray<T>.
public static class UnmanagedArrayHelper { ///// <summary> ////// Error 1 Unable to get the address and size of the managed type ("T"), or the pointer to it cannot be declared ///// </summary> ///// <typeparam name="T"></typeparam> ///// <param name="array"></param> ///// <returns></returns> //public static unsafe T* FirstElement<T>(this UnmanagedArray<T> array) where T : struct //{ // var header = (void*); // return (T*)header; //} /// <summary> /// Get the address of the first element of the unmanaged array. /// </summary> /// <param name="array"></param> /// <returns></returns> public static unsafe void* FirstElement(this UnmanagedArrayBase array) { var header = (void*); return header; } public static unsafe void* LastElement(this UnmanagedArrayBase array) { var last = (void*)( + ( - / )); return last; } /// <summary> /// Get the address of the last element of the unmanaged array and then the address of the next unit. /// </summary> /// <param name="array"></param> /// <returns></returns> public static unsafe void* TailAddress(this UnmanagedArrayBase array) { var tail = (void*)( + ); return tail; } }
How to use
This type implements 3 extension methods, which can obtain the position of the first element of UnmanagedArray<T>, the position of the last element, and the position of the last element +1. This unsafe method can achieve the same reading and writing speed in C language.
Here is an example. Reading and writing UnmanagedArray<T> in unsafe is 10 to 70 times faster than this [index] method.
public static void TypicalScene() { int length = 1000000; UnmanagedArray<int> array = new UnmanagedArray<int>(length); UnmanagedArray<int> array2 = new UnmanagedArray<int>(length); long tick = ; for (int i = 0; i < length; i++) { array[i] = i; } long totalTicks = - tick; tick = ; unsafe { int* header = (int*)(); int* last = (int*)(); int* tailAddress = (int*)(); int value = 0; for (int* ptr = header; ptr <= last/*or: ptr < tailAddress*/; ptr++) { *ptr = value++; } } long totalTicks2 = - tick; ("ticks: {0}, {1}", totalTicks, totalTicks2);// unsafe method works faster. for (int i = 0; i < length; i++) { if (array[i] != i) { ("something wrong here"); } if (array2[i] != i) { ("something wrong here"); } } (); (); }
unsafe { vec3* header = (vec3*)(); vec3* last = (vec3*)(); vec3* tailAddress = (vec3*)(); int i = 0; for (vec3* ptr = header; ptr <= last/*or: ptr < tailAddress*/; ptr++) { *ptr = new vec3(i * 3 + 0, i * 3 + 1, i * 3 + 2); i++; } i = 0; for (vec3* ptr = header; ptr <= last/*or: ptr < tailAddress*/; ptr++, i++) { var item = *ptr; var old = new vec3(i * 3 + 0, i * 3 + 1, i * 3 + 2); if ( != || != || != ) { throw new Exception(); } } }
2015-08-25
Support complex structs with StructLayout and MarshalAs
In OpenGL I need to use UnmanagedArray<mat4>, where mat4 is defined as follows:
1 /// <summary> 2 /// Represents a 4x4 matrix. 3 /// </summary> 4 [StructLayout(, CharSet = , Size = 4 * 4 * 4)] 5 public struct mat4 6 { 7 /// <summary> 8 /// Gets or sets the <see cref="vec4"/> column at the specified index. 9 /// </summary> 10 /// <value> 11 /// The <see cref="vec4"/> column. 12 /// </value> 13 /// <param name="column">The column index.</param> 14 /// <returns>The column at index <paramref name="column"/>.</returns> 15 public vec4 this[int column] 16 { 17 get { return cols[column]; } 18 set { cols[column] = value; } 19 } 20 21 /// <summary> 22 /// Gets or sets the element at <paramref name="column"/> and <paramref name="row"/>. 23 /// </summary> 24 /// <value> 25 /// The element at <paramref name="column"/> and <paramref name="row"/>. 26 /// </value> 27 /// <param name="column">The column index.</param> 28 /// <param name="row">The row index.</param> 29 /// <returns> 30 /// The element at <paramref name="column"/> and <paramref name="row"/>. 31 /// </returns> 32 public float this[int column, int row] 33 { 34 get { return cols[column][row]; } 35 set { cols[column][row] = value; } 36 } 37 38 /// <summary> 39 /// The columms of the matrix. 40 /// </summary> 41 [MarshalAs(, SizeConst = 4)] 42 private vec4[] cols; 43 } 44 45 /// <summary> 46 /// Represents a four dimensional vector. 47 /// </summary> 48 [StructLayout(, CharSet = , Size = 4 * 4)] 49 public struct vec4 50 { 51 public float x; 52 public float y; 53 public float z; 54 public float w; 55 56 public float this[int index] 57 { 58 get 59 { 60 if (index == 0) return x; 61 else if (index == 1) return y; 62 else if (index == 2) return z; 63 else if (index == 3) return w; 64 else throw new Exception("Out of range."); 65 } 66 set 67 { 68 if (index == 0) x = value; 69 else if (index == 1) y = value; 70 else if (index == 2) z = value; 71 else if (index == 3) w = value; 72 else throw new Exception("Out of range."); 73 } 74 } 75 } mat4
Note: The size of T must be determined for UnmanagedArray<T>. So in mat4 we use [StructLayout(, CharSet = , Size = 4 * 4 * 4)] to specify that the size of mat4 is 4 vec4 * 4 float * 4 bytes (each float) = 64 bytes, and on private vec4[] cols; we use [MarshalAs(, SizeConst = 4)] to specify that the number of elements of cols must be 4. After that, it is OK to not write [StructLayout(, CharSet = , Size = 4 * 4)] on vec4, because vec4 only has 4 simple float fields and does not contain complex types.
Below are test cases.
mat4 matrix = ((), new vec3(2, 3, 4)); var size = (typeof(mat4)); size = (matrix); UnmanagedArray<mat4> array = new UnmanagedArray<mat4>(1); array[0] = matrix; mat4 newMatirx = array[0]; // newMatrix should be equal to matrix ();
If matrix and newMatrix are equal, it means that the above Attribute configuration is correct.
Summarize
This is the article about C# + unmanaged array in c# without 'unsafe' keyword. For more information about C# + unmanaged array content, please search for my previous articles or continue browsing the following related articles. I hope everyone will support me in the future!