簡體   English   中英

具有超過65535 ^ 2個元素的2d陣列 - >陣列尺寸超出支持的范圍

[英]2d-Array with more than 65535^2 elements --> Array dimensions exceeded supported range

我有一台帶有128 GB RAM的64位PC,我正在使用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個元素? 我看到以下答案https://stackoverflow.com/a/2338797/7556646

我檢查了gcAllowVeryLargeObjects的文檔,我看到了以下注釋。

數組中的最大元素數是UInt32.MaxValue (4294967295)。

我不明白為什么有這個限制? 有解決方法嗎? 是否計划在即將推出的.net版本中刪除此限制?

我需要在存儲器中的元素,因為我想計算例如使用英特爾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不支持托管堆上的大型數組有多種原因。

其中一些是技術性的,其中一些可能是“范式的”。

這篇博文介紹了為什么存在限制的一些原因。 基本上,由於內存碎片,決定限制(大寫O)對象的最大大小。 實現處理較大對象的成本與這樣一個事實進行權衡,即在大多數情況下,由於程序員的設計謬誤,需要這么大的對象和那些需要大量對象的用例。 因為,對於CLR, 一切都是對象,這個限制也適用於數組。 為了強制實施此限制,數組索引器采用帶符號整數設計。

但是一旦你確定,你的程序設計要求你有這么大的陣列,你將需要一個解決方法。

上面提到的博客文章還表明,您可以實現大型數組而無需進入非托管區域。

但正如Evk在評論中指出的那樣,你想通過PInvoke將數組作為一個整體傳遞給外部函數。 這意味着您將需要非托管堆上的數組,或者它必須在調用期間進行封送處理。 整個事情的編組是一個壞主意,這個數組很大。

解決方法

因此,由於托管堆是不可能的,因此您需要在非托管堆上分配空間並將該空間用於您的陣列。

假設您需要8 GB的空間:

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

大! 現在,您在虛擬內存中有一個區域,您可以在其中存儲最多8 GB的數據。

如何將其轉換為數組?

那么C#有兩種方法

“不安全”的方法

這將讓你使用指針。 指針可以轉換為數組。 (在香草C中它們通常是同一個)

如果您對如何通過指針實現2D陣列有一個好主意,那么這將是您的最佳選擇。

這是一個指針

“元帥”方法

您不需要不安全的上下文,而是必須將您的數據從托管堆“編組”到非托管堆。 你仍然需要理解指針算術。

您要使用的兩個主要功能是PtrToStructure和反向StructureToPtr 使用其中一個,您將從非托管堆上的指定位置獲取值類型(例如double)的副本。 使用另一個,您將在非托管堆上放置值類型的副本。

從某種意義上說,這兩種方法都是“不安全的”。 你需要知道你的指針

常見陷阱包括但不限於:

  • 忘記嚴格檢查邊界
  • 混合我的元素的大小
  • 弄清楚對齊方式
  • 混合你想要的2D陣列
  • 忘記使用2D數組填充
  • 忘記釋放記憶
  • 忘記釋放內存並使用它無論如何

您可能希望將2D陣列設計轉換為一維陣列設計


在任何情況下,您都希望將其全部包含在具有相應檢查和destsructors的類中。

靈感的基本例子

接下來是基於非托管堆的“類似”數組的泛型類。

特色包括:

  • 它有一個索引訪問器,可以接受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));
    }
}

我從MrPaulch的這個答案中使用了“Marshal”方法的基本例子,創建了下面一個名為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我傳遞了HugeMatrix<T>類的Pointer屬性。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM