具有超过 65535^2 个元素的二维数组 --> 数组维度超出了支持的范围
2d-Array with more than 65535^2 elements --> Array dimensions exceeded supported range
我有一台 64 位 PC,内存为 128 GB,我使用的是 C# 和 .NET 4.5。
我有以下代码:
double[,] m1 = new double[65535, 65535];
long l1 = m1.LongLength;
double[,] m2 = new double[65536, 65536]; // Array dimensions exceeded supported range
long l2 = m2.LongLength;
我知道 <gcAllowVeryLargeObjects enabled="true" />
并且我已将其设置为 true。
为什么一个多维数组的元素不能超过4294967295个?
我看到了下面的回答.
我也检查了 gcAllowVeryLargeObjects 的文档,我看到了以下评论。
The maximum number of elements in array is UInt32.MaxValue (4294967295).
我不明白为什么会有这个限制?有解决方法吗?是否计划在即将推出的 .net 版本中取消此限制?
我需要内存中的元素,因为我想使用 Intel MKL 计算例如对称特征值分解。
[DllImport("custom_mkl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true, SetLastError = false)]
internal static extern lapack_int LAPACKE_dsyevd(
int matrix_layout, char jobz, char uplo, lapack_int n, [In, Out] double[,] a, lapack_int lda, [In, Out] double[] w);
免责声明:结果比预期的要长
为什么 CLR 不支持大数组
CLR 不支持托管堆上的大型数组的原因有多种。
其中有些是技术性的,有些可能是“范式”。
此 blog post 探讨了存在限制的一些原因。本质上,由于内存碎片,决定限制(大写 O)对象的最大大小。实现处理较大对象的成本是根据这样一个事实进行权衡的,即不存在很多需要如此大对象的用例[ed],而那些需要如此大对象的用例——在大多数情况下——是由于程序员的设计谬误。
因为对于 CLR,everything 都是一个对象,这个限制也适用于数组。为了强制执行此限制,数组索引器设计为带符号整数。
但是一旦您确定您的程序设计要求您拥有如此大的数组,您将需要一个解决方法。
上面提到的博客 post 还演示了您可以在不进入非托管领域的情况下实现大数组。
但正如 Evk 在评论中指出的那样,您希望通过 PInvoke 将数组作为一个整体传递给外部函数。这意味着您将需要非托管堆上的数组,否则必须在调用期间对其进行封送处理。对于这么大的数组,编组整个事情是个坏主意。
解决方法
因此,由于托管堆不存在问题,因此您需要在非托管堆上分配 space 并将其 space 用于您的数组。
假设您需要 8 GB 的 space:
long size = (1L << 33);
IntPtr basePointer = System.Runtime.InteropServices.Marshal.AllocHGlobal((IntPtr)size);
太棒了!现在您在虚拟内存中有一个区域,您可以在其中存储多达 8 GB 的数据。
如何将其转换为数组?
嗯,在 C# 中有两种方法
“不安全”的方法
这将使您可以使用指针。指针可以转换为数组。 (在香草 C 中,它们通常是一样的)
如果您对如何通过指针实现二维数组有很好的了解,那么这将是您的最佳选择。
这是一个pointer
“元帅”方法
您不需要不安全的上下文,而是必须将数据从托管堆“封送”到非托管堆。您仍然需要了解指针运算。
您要使用的两个主要函数是 PtrToStructure and the reverse StructureToPtr。使用一个,您将从非托管堆上的指定位置获得值类型(例如双精度)的副本。对于另一个,您将在非托管堆上放置一个值类型的副本。
从某种意义上说,这两种方法都是“不安全的”。您需要了解您的 pointers
常见陷阱包括但不限于:
- 忘记严格检查边界
- 混合元素的大小
- 排列混乱
- 混合你想要什么样的二维数组
- 忘记使用二维数组进行填充
- 忘记释放内存
- 忘记释放内存并仍然使用它
您可能希望将二维阵列设计转变为一维阵列设计
在任何情况下,您都希望使用适当的检查和析构函数将其全部包装到 class 中。
灵感的基本示例
接下来是一个泛型 class,它“类似于”一个基于非托管堆的数组。
功能包括:
- 它有一个接受 64 位整数的索引访问器。
- 它将
T
可以变成的类型限制为值类型。
- 它有边界检查并且是一次性的。
如果你注意到了,我没有进行任何类型检查,所以如果 Marshal.SizeOf
未能 return 正确的数字,我们就落入了上述坑之一。
您必须自己实现的功能包括:
- 2D 访问器和 2D 数组算法(取决于其他库的期望,通常类似于
p = x * size + y
- 用于 PInvoke 目的的公开指针(或内部调用)
因此,如果有的话,仅将其用作灵感。
using static System.Runtime.InteropServices.Marshal;
public class LongArray<T> : IDisposable where T : struct {
private IntPtr _head;
private Int64 _capacity;
private UInt64 _bytes;
private Int32 _elementSize;
public LongArray(long capacity) {
if(_capacity < 0) throw new ArgumentException("The capacity can not be negative");
_elementSize = SizeOf(default(T));
_capacity = capacity;
_bytes = (ulong)capacity * (ulong)_elementSize;
_head = AllocHGlobal((IntPtr)_bytes);
}
public T this[long index] {
get {
IntPtr p = _getAddress(index);
T val = (T)System.Runtime.InteropServices.Marshal.PtrToStructure(p, typeof(T));
return val;
}
set {
IntPtr p = _getAddress(index);
StructureToPtr<T>(value, p, true);
}
}
protected bool disposed = false;
public void Dispose() {
if(!disposed) {
FreeHGlobal((IntPtr)_head);
disposed = true;
}
}
protected IntPtr _getAddress(long index) {
if(disposed) throw new ObjectDisposedException("Can't access the array once it has been disposed!");
if(index < 0) throw new IndexOutOfRangeException("Negative indices are not allowed");
if(!(index < _capacity)) throw new IndexOutOfRangeException("Index is out of bounds of this array");
return (IntPtr)((ulong)_head + (ulong)index * (ulong)(_elementSize));
}
}
我使用了 from MrPaulch 中 "Marshal" 方法的基本示例来创建以下 class 名为 HugeMatrix<T>
:
public class HugeMatrix<T> : IDisposable
where T : struct
{
public IntPtr Pointer
{
get { return pointer; }
}
private IntPtr pointer = IntPtr.Zero;
public int NRows
{
get { return Transposed ? _NColumns : _NRows; }
}
private int _NRows = 0;
public int NColumns
{
get { return Transposed ? _NRows : _NColumns; }
}
private int _NColumns = 0;
public bool Transposed
{
get { return _Transposed; }
set { _Transposed = value; }
}
private bool _Transposed = false;
private ulong b_element_size = 0;
private ulong b_row_size = 0;
private ulong b_size = 0;
private bool disposed = false;
public HugeMatrix()
: this(0, 0)
{
}
public HugeMatrix(int nrows, int ncols, bool transposed = false)
{
if (nrows < 0)
throw new ArgumentException("The number of rows can not be negative");
if (ncols < 0)
throw new ArgumentException("The number of columns can not be negative");
_NRows = transposed ? ncols : nrows;
_NColumns = transposed ? nrows : ncols;
_Transposed = transposed;
b_element_size = (ulong)(Marshal.SizeOf(typeof(T)));
b_row_size = (ulong)_NColumns * b_element_size;
b_size = (ulong)_NRows * b_row_size;
pointer = Marshal.AllocHGlobal((IntPtr)b_size);
disposed = false;
}
public HugeMatrix(T[,] matrix, bool transposed = false)
: this(matrix.GetLength(0), matrix.GetLength(1), transposed)
{
int nrows = matrix.GetLength(0);
int ncols = matrix.GetLength(1);
for (int i1 = 0; i1 < nrows; i1++)
for (int i2 = 0; i2 < ncols; i2++)
this[i1, i2] = matrix[i1, i2];
}
public void Dispose()
{
if (!disposed)
{
Marshal.FreeHGlobal(pointer);
_NRows = 0;
_NColumns = 0;
_Transposed = false;
b_element_size = 0;
b_row_size = 0;
b_size = 0;
pointer = IntPtr.Zero;
disposed = true;
}
}
public void Transpose()
{
_Transposed = !_Transposed;
}
public T this[int i_row, int i_col]
{
get
{
IntPtr p = getAddress(i_row, i_col);
return (T)Marshal.PtrToStructure(p, typeof(T));
}
set
{
IntPtr p = getAddress(i_row, i_col);
Marshal.StructureToPtr(value, p, true);
}
}
private IntPtr getAddress(int i_row, int i_col)
{
if (disposed)
throw new ObjectDisposedException("Can't access the matrix once it has been disposed");
if (i_row < 0)
throw new IndexOutOfRangeException("Negative row indices are not allowed");
if (i_row >= NRows)
throw new IndexOutOfRangeException("Row index is out of bounds of this matrix");
if (i_col < 0)
throw new IndexOutOfRangeException("Negative column indices are not allowed");
if (i_col >= NColumns)
throw new IndexOutOfRangeException("Column index is out of bounds of this matrix");
int i1 = Transposed ? i_col : i_row;
int i2 = Transposed ? i_row : i_col;
ulong p_row = (ulong)pointer + b_row_size * (ulong)i1;
IntPtr p = (IntPtr)(p_row + b_element_size * (ulong)i2);
return p;
}
}
我现在可以调用具有巨大矩阵的英特尔 MKL 库,例如:
[DllImport("custom_mkl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true, SetLastError = false)]
internal static extern lapack_int LAPACKE_dsyevd(
int matrix_layout, char jobz, char uplo, lapack_int n, [In, Out] IntPtr a, lapack_int lda, [In, Out] double[] w);
对于参数 IntPtr a
我传递 Pointer
属性 的 HugeMatrix<T>
class.
我有一台 64 位 PC,内存为 128 GB,我使用的是 C# 和 .NET 4.5。 我有以下代码:
double[,] m1 = new double[65535, 65535];
long l1 = m1.LongLength;
double[,] m2 = new double[65536, 65536]; // Array dimensions exceeded supported range
long l2 = m2.LongLength;
我知道 <gcAllowVeryLargeObjects enabled="true" />
并且我已将其设置为 true。
为什么一个多维数组的元素不能超过4294967295个? 我看到了下面的回答.
我也检查了 gcAllowVeryLargeObjects 的文档,我看到了以下评论。
The maximum number of elements in array is UInt32.MaxValue (4294967295).
我不明白为什么会有这个限制?有解决方法吗?是否计划在即将推出的 .net 版本中取消此限制?
我需要内存中的元素,因为我想使用 Intel MKL 计算例如对称特征值分解。
[DllImport("custom_mkl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true, SetLastError = false)]
internal static extern lapack_int LAPACKE_dsyevd(
int matrix_layout, char jobz, char uplo, lapack_int n, [In, Out] double[,] a, lapack_int lda, [In, Out] double[] w);
免责声明:结果比预期的要长
为什么 CLR 不支持大数组
CLR 不支持托管堆上的大型数组的原因有多种。
其中有些是技术性的,有些可能是“范式”。
此 blog post 探讨了存在限制的一些原因。本质上,由于内存碎片,决定限制(大写 O)对象的最大大小。实现处理较大对象的成本是根据这样一个事实进行权衡的,即不存在很多需要如此大对象的用例[ed],而那些需要如此大对象的用例——在大多数情况下——是由于程序员的设计谬误。 因为对于 CLR,everything 都是一个对象,这个限制也适用于数组。为了强制执行此限制,数组索引器设计为带符号整数。
但是一旦您确定您的程序设计要求您拥有如此大的数组,您将需要一个解决方法。
上面提到的博客 post 还演示了您可以在不进入非托管领域的情况下实现大数组。
但正如 Evk 在评论中指出的那样,您希望通过 PInvoke 将数组作为一个整体传递给外部函数。这意味着您将需要非托管堆上的数组,否则必须在调用期间对其进行封送处理。对于这么大的数组,编组整个事情是个坏主意。
解决方法
因此,由于托管堆不存在问题,因此您需要在非托管堆上分配 space 并将其 space 用于您的数组。
假设您需要 8 GB 的 space:
long size = (1L << 33);
IntPtr basePointer = System.Runtime.InteropServices.Marshal.AllocHGlobal((IntPtr)size);
太棒了!现在您在虚拟内存中有一个区域,您可以在其中存储多达 8 GB 的数据。
如何将其转换为数组?
嗯,在 C# 中有两种方法
“不安全”的方法
这将使您可以使用指针。指针可以转换为数组。 (在香草 C 中,它们通常是一样的)
如果您对如何通过指针实现二维数组有很好的了解,那么这将是您的最佳选择。
这是一个pointer
“元帅”方法
您不需要不安全的上下文,而是必须将数据从托管堆“封送”到非托管堆。您仍然需要了解指针运算。
您要使用的两个主要函数是 PtrToStructure and the reverse StructureToPtr。使用一个,您将从非托管堆上的指定位置获得值类型(例如双精度)的副本。对于另一个,您将在非托管堆上放置一个值类型的副本。
从某种意义上说,这两种方法都是“不安全的”。您需要了解您的 pointers
常见陷阱包括但不限于:
- 忘记严格检查边界
- 混合元素的大小
- 排列混乱
- 混合你想要什么样的二维数组
- 忘记使用二维数组进行填充
- 忘记释放内存
- 忘记释放内存并仍然使用它
您可能希望将二维阵列设计转变为一维阵列设计
在任何情况下,您都希望使用适当的检查和析构函数将其全部包装到 class 中。
灵感的基本示例
接下来是一个泛型 class,它“类似于”一个基于非托管堆的数组。
功能包括:
- 它有一个接受 64 位整数的索引访问器。
- 它将
T
可以变成的类型限制为值类型。 - 它有边界检查并且是一次性的。
如果你注意到了,我没有进行任何类型检查,所以如果 Marshal.SizeOf
未能 return 正确的数字,我们就落入了上述坑之一。
您必须自己实现的功能包括:
- 2D 访问器和 2D 数组算法(取决于其他库的期望,通常类似于
p = x * size + y
- 用于 PInvoke 目的的公开指针(或内部调用)
因此,如果有的话,仅将其用作灵感。
using static System.Runtime.InteropServices.Marshal;
public class LongArray<T> : IDisposable where T : struct {
private IntPtr _head;
private Int64 _capacity;
private UInt64 _bytes;
private Int32 _elementSize;
public LongArray(long capacity) {
if(_capacity < 0) throw new ArgumentException("The capacity can not be negative");
_elementSize = SizeOf(default(T));
_capacity = capacity;
_bytes = (ulong)capacity * (ulong)_elementSize;
_head = AllocHGlobal((IntPtr)_bytes);
}
public T this[long index] {
get {
IntPtr p = _getAddress(index);
T val = (T)System.Runtime.InteropServices.Marshal.PtrToStructure(p, typeof(T));
return val;
}
set {
IntPtr p = _getAddress(index);
StructureToPtr<T>(value, p, true);
}
}
protected bool disposed = false;
public void Dispose() {
if(!disposed) {
FreeHGlobal((IntPtr)_head);
disposed = true;
}
}
protected IntPtr _getAddress(long index) {
if(disposed) throw new ObjectDisposedException("Can't access the array once it has been disposed!");
if(index < 0) throw new IndexOutOfRangeException("Negative indices are not allowed");
if(!(index < _capacity)) throw new IndexOutOfRangeException("Index is out of bounds of this array");
return (IntPtr)((ulong)_head + (ulong)index * (ulong)(_elementSize));
}
}
我使用了 HugeMatrix<T>
:
public class HugeMatrix<T> : IDisposable
where T : struct
{
public IntPtr Pointer
{
get { return pointer; }
}
private IntPtr pointer = IntPtr.Zero;
public int NRows
{
get { return Transposed ? _NColumns : _NRows; }
}
private int _NRows = 0;
public int NColumns
{
get { return Transposed ? _NRows : _NColumns; }
}
private int _NColumns = 0;
public bool Transposed
{
get { return _Transposed; }
set { _Transposed = value; }
}
private bool _Transposed = false;
private ulong b_element_size = 0;
private ulong b_row_size = 0;
private ulong b_size = 0;
private bool disposed = false;
public HugeMatrix()
: this(0, 0)
{
}
public HugeMatrix(int nrows, int ncols, bool transposed = false)
{
if (nrows < 0)
throw new ArgumentException("The number of rows can not be negative");
if (ncols < 0)
throw new ArgumentException("The number of columns can not be negative");
_NRows = transposed ? ncols : nrows;
_NColumns = transposed ? nrows : ncols;
_Transposed = transposed;
b_element_size = (ulong)(Marshal.SizeOf(typeof(T)));
b_row_size = (ulong)_NColumns * b_element_size;
b_size = (ulong)_NRows * b_row_size;
pointer = Marshal.AllocHGlobal((IntPtr)b_size);
disposed = false;
}
public HugeMatrix(T[,] matrix, bool transposed = false)
: this(matrix.GetLength(0), matrix.GetLength(1), transposed)
{
int nrows = matrix.GetLength(0);
int ncols = matrix.GetLength(1);
for (int i1 = 0; i1 < nrows; i1++)
for (int i2 = 0; i2 < ncols; i2++)
this[i1, i2] = matrix[i1, i2];
}
public void Dispose()
{
if (!disposed)
{
Marshal.FreeHGlobal(pointer);
_NRows = 0;
_NColumns = 0;
_Transposed = false;
b_element_size = 0;
b_row_size = 0;
b_size = 0;
pointer = IntPtr.Zero;
disposed = true;
}
}
public void Transpose()
{
_Transposed = !_Transposed;
}
public T this[int i_row, int i_col]
{
get
{
IntPtr p = getAddress(i_row, i_col);
return (T)Marshal.PtrToStructure(p, typeof(T));
}
set
{
IntPtr p = getAddress(i_row, i_col);
Marshal.StructureToPtr(value, p, true);
}
}
private IntPtr getAddress(int i_row, int i_col)
{
if (disposed)
throw new ObjectDisposedException("Can't access the matrix once it has been disposed");
if (i_row < 0)
throw new IndexOutOfRangeException("Negative row indices are not allowed");
if (i_row >= NRows)
throw new IndexOutOfRangeException("Row index is out of bounds of this matrix");
if (i_col < 0)
throw new IndexOutOfRangeException("Negative column indices are not allowed");
if (i_col >= NColumns)
throw new IndexOutOfRangeException("Column index is out of bounds of this matrix");
int i1 = Transposed ? i_col : i_row;
int i2 = Transposed ? i_row : i_col;
ulong p_row = (ulong)pointer + b_row_size * (ulong)i1;
IntPtr p = (IntPtr)(p_row + b_element_size * (ulong)i2);
return p;
}
}
我现在可以调用具有巨大矩阵的英特尔 MKL 库,例如:
[DllImport("custom_mkl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true, SetLastError = false)]
internal static extern lapack_int LAPACKE_dsyevd(
int matrix_layout, char jobz, char uplo, lapack_int n, [In, Out] IntPtr a, lapack_int lda, [In, Out] double[] w);
对于参数 IntPtr a
我传递 Pointer
属性 的 HugeMatrix<T>
class.