代码之家  ›  专栏  ›  技术社区  ›  Wollmich

超过65535^2个元素的2d阵列-->阵列尺寸超出了支持的范围

  •  9
  • Wollmich  · 技术社区  · 7 年前

    我有一台64位PC,内存128 GB,使用C#和。净额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" /> 我把它设定为真的。

    为什么多维数组的元素不能超过4294967295个? 我看到了以下答案 https://stackoverflow.com/a/2338797/7556646 .

    我还检查了文档 gcAllowVeryLargeObjects 我看到了下面的评论。

    数组中的最大元素数为 UInt32.MaxValue (4294967295).

    我不明白为什么会有这个限制?是否有解决方法?是否计划在即将发布的版本中删除此限制。网

    我需要内存中的元素,因为我想使用英特尔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);
    
    2 回复  |  直到 7 年前
        1
  •  7
  •   Community CDub    4 年前

    免责声明:这个结果比预期的要长

    为什么CLR不支持大型阵列

    CLR不支持托管堆上的大型阵列有多种原因。

    其中有些是技术性的,有些可能是“范例性的”。

    blog post 探讨存在限制的一些原因。实际上,由于内存碎片,决定限制(大写O)对象的最大大小。实现处理较大对象的成本与这样一个事实进行了权衡,即不存在许多需要如此大对象的用例,而那些需要如此大对象的用例在大多数情况下都是由于程序员的设计谬误。 从那时起,对于CLR, 每件事 是对象,此限制也适用于数组。为了加强这一限制,数组索引器使用有符号整数设计。

    但是,一旦您确定了程序设计要求您拥有如此大的阵列,您将需要一种变通方法。

    上面提到的博文还表明,您可以实现大型阵列,而不必进入非托管领域。

    但正如Evk在评论中指出的那样,您希望通过PInvoke将数组作为一个整体传递给外部函数。这意味着您将需要非托管堆上的数组,否则必须在调用期间对其进行封送处理。对这么大的数组进行封送处理是个坏主意。

    解决方法

    因此,由于托管堆是不可能的,因此您需要在非托管堆上分配空间,并将该空间用于阵列。

    假设您需要8 GB的空间:

    long size = (1L << 33);
    IntPtr basePointer = System.Runtime.InteropServices.Marshal.AllocHGlobal((IntPtr)size);
    

    太棒了现在,虚拟内存中有了一个区域,您可以在其中存储多达8 GB的数据。

    如何将其转换为数组?

    在C语言中有两种方法#

    “不安全”方法

    这将允许您使用指针。指针可以转换为数组。(在香草C中,它们通常是同一个)

    如果您对如何通过指针实现2D数组有很好的想法,那么这将是您的最佳选择。

    这是一个 pointer

    “马歇尔”方法

    您不需要不安全的上下文,而是必须将数据从托管堆“封送”到非托管堆。您仍然需要理解指针算法。

    您要使用的两个主要功能是 PtrToStructure 反之亦然 StructureToPtr . 使用它,您将从非托管堆的指定位置获得一个值类型(例如double)的副本。对于另一个,您将在非托管堆上放置一个值类型的副本。

    这两种方法在某种意义上都是“不安全的”。你需要知道你的 pointers

    常见缺陷包括但不限于:

    • 忘记严格检查边界
    • 混合了我的元素的大小
    • 弄乱了路线
    • 混合您想要的2D阵列
    • 忘记使用2D数组填充
    • 忘记释放内存
    • 忘记释放内存并以任何方式使用它

    您可能希望将二维阵列设计转换为一维阵列设计


    在任何情况下,您都希望将其打包到一个具有适当检查和deststructor的类中。

    灵感的基本示例

    下面是一个基于非托管堆的“类似”数组的泛型类。

    功能包括:

    • 它有一个接受64位整数的索引访问器。
    • 它限制了 T 可以成为值类型。
    • 它具有边界检查功能,是一次性的。

    如果你注意到了,我没有做任何类型检查,所以如果 Marshal.SizeOf 未能返回正确的数字,我们正落入上述坑中。

    您必须自己实现的功能包括:

    • 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));
        }
    }
    
        2
  •  1
  •   Wollmich    6 年前

    我使用了下面的“封送”方法的基本示例 answer 从…起 MrPaulch 要创建以下类,请调用 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;
        }
    }
    

    现在我可以用巨大的矩阵调用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] IntPtr a, lapack_int lda, [In, Out] double[] w);
    

    对于参数 IntPtr a 我通过了 Pointer 的属性 HugeMatrix<T>