【问题标题】:How to alloc more than MaxInteger bytes of memory in C#如何在 C# 中分配超过 MaxInteger 字节的内存
【发布时间】:2011-04-08 20:24:14
【问题描述】:

我希望分配超过 MaxInteger 字节的内存。

Marshall.AllocHGlobal() 需要一个整数 - 所以我不能使用它。还有其他方法吗?

更新

我把平台改成x64,然后运行下面的代码。

myp 似乎有正确的长度:大约 3.0G。但顽固地“缓冲”最大为 2.1G。

知道为什么吗?

    var fileStream = new FileStream(
          "C:\\big.BC2",
          FileMode.Open,
          FileAccess.Read,
          FileShare.Read,
          16 * 1024,
          FileOptions.SequentialScan);
    Int64 length = fileStream.Length;
    Console.WriteLine(length);
    Console.WriteLine(Int64.MaxValue);
    IntPtr myp = new IntPtr(length);
    //IntPtr buffer = Marshal.AllocHGlobal(myp);
    IntPtr buffer = VirtualAllocEx(
        Process.GetCurrentProcess().Handle,
        IntPtr.Zero,
        new IntPtr(length),
        AllocationType.Commit | AllocationType.Reserve,
        MemoryProtection.ReadWrite);
    unsafe
    {
        byte* pBytes = (byte*)myp.ToPointer();
        var memoryStream = new UnmanagedMemoryStream(pBytes, (long)length, (long)length, FileAccess.ReadWrite);
        fileStream.CopyTo(memoryStream);

【问题讨论】:

  • 你为什么要这样做?
  • 它没有接收 IntPtr 的重载吗? IntPtr 是 64 位平台上的 64 位值,大于 Int32.MaxInteger。
  • Vitor,我的测试代码失败:IntPtr myp = new IntPtr(length); //length = 3 000 000 000 所以它在我的 64 位机器上似乎并不大...
  • @ManInMoon,您还需要将 IntPtr 定位为 64 位平台为 64 位。检查构建/平台目标选项。
  • 如果您无法将您的平台设置为 64 位版本,您将无法获得超过 2GB 的内存。

标签: c# memory-management


【解决方案1】:

这在当前的主流硬件上是不可能的。内存缓冲区限制为 2 GB,即使在 64 位机器上也是如此。缓冲区的索引寻址仍然使用 32 位有符号偏移量。从技术上讲,可以生成可以索引更多的机器代码,使用寄存器来存储偏移量,但这很昂贵并且会减慢 all 数组索引,即使对于不大于 2 GB 的数组索引也是如此.

此外,您无法从 32 位进程可用的地址空间中获得大于约 650MB 的缓冲区。没有足够的连续内存页可用,因为虚拟内存在不同的地址包含代码和数据。

IBM 和 Sun 等公司销售的硬件可以做到这一点。

【讨论】:

  • 你确定吗?目前我附近没有超过 2 GB 的机器,所以我无法对其进行测试,但我能找到的一切都表明 VirtualAlloc() 能够分配超过 2 GB 的空间。至于实际寻址缓冲区,这是一个单独的问题,但如果它可以用于内存映射文件,那么它肯定也可以用于普通内存。
  • 有趣...只要阅读 Intel 手册,实际上位移和立即操作数都具有最大 32 位,即使在 x64 上也是如此。
  • @Hans Passant:该链接实际上与您相矛盾。引用:“使用本机分配。您始终可以 P/Invoke 到 NT 的本机堆并分配内存,然后您可以使用不安全的代码访问这些内存。[...] 分配 8GB 块 [...]。”
  • @Rasmus:这不是关于分配它,这是关于在你得到它之后索引数组。我的回答中的“技术上可能”条款。
  • @Hans:所以你使用指针增量而不是索引增量来迭代它。大不了。无论如何,大多数优化编译器都会为您完成这种转换。
【解决方案2】:

我参与了您提出的其他问题之一,老实说,我认为您在这里打的是一场失败的战斗。除了将所有内容读入内存之外,您可能还需要探索处理这些数据的其他途径。

如果我理解正确,您有多个线程同时处理数据,这就是为什么您不想直接处理文件,因为我假设存在 I/O 争用。

您是否考虑过或是否有可能将数据块读入内存,让线程处理该块,然后读取下一个块或由线程处理?这样,在任何时候,您的内存中永远不会超过一个块,但所有线程都可以访问该块。这不是最佳的,但我把它作为一个起点。如果这是可行的,那么可以探索优化它的选项。

更新: 使用平台调用分配非托管内存并从 .NET 使用它的示例。

既然您非常确定需要将这么多数据加载到内存中,我想我会编写一个小型测试应用程序来验证这是否可行。为此,您将需要以下内容

  1. 使用/unsafe 编译选项进行编译
  2. 如果您想分配超过 2 GB 的空间,您还需要将目标平台切换到 x64

*上面的第 2 点稍微复杂一些,在 64 位操作系统上,您仍然可以针对 x86 平台并访问完整的 4 GB 内存。这需要您使用EDITBIN.EXE 之类的工具在PE 标头中设置LargeAddressAware 标志。

此代码使用VirtualAllocEx 分配非托管内存并使用UnmanagedMemoryStream 使用.NET 流隐喻访问非托管内存。请注意,此代码仅在具有 4 GB RAM 的目标 64 位环境中完成了一些非常基本的快速测试。最重要的是,该进程的内存利用率仅达到了大约 2.6 GB。

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.ComponentModel;

namespace MemoryMappedFileTests
{
  class Program
  {
    static void Main(string[] args)
    {
      IntPtr ptr = IntPtr.Zero;
      try
      {
        // Allocate and Commit the memory directly.
        ptr = VirtualAllocEx(
          Process.GetCurrentProcess().Handle, 
          IntPtr.Zero, 
          new IntPtr(0xD0000000L), 
          AllocationType.Commit | AllocationType.Reserve, 
          MemoryProtection.ReadWrite);
        if (ptr == IntPtr.Zero)
        {
          throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        // Query some information about the allocation, used for testing.
        MEMORY_BASIC_INFORMATION mbi = new MEMORY_BASIC_INFORMATION();
        IntPtr result = VirtualQueryEx(
          Process.GetCurrentProcess().Handle, 
          ptr, 
          out mbi, 
          new IntPtr(Marshal.SizeOf(mbi)));
        if (result == IntPtr.Zero)
        {
          throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        // Use unsafe code to get a pointer to the unmanaged memory. 
        // This requires compiling with /unsafe option.
        unsafe
        {
          // Pointer to the allocated memory
          byte* pBytes = (byte*)ptr.ToPointer();

          // Create Read/Write stream to access the memory.
          UnmanagedMemoryStream stm = new UnmanagedMemoryStream(
            pBytes, 
            mbi.RegionSize.ToInt64(), 
            mbi.RegionSize.ToInt64(), 
            FileAccess.ReadWrite);

          // Create a StreamWriter to write to the unmanaged memory.
          StreamWriter sw = new StreamWriter(stm);
          sw.Write("Everything seems to be working!\r\n");
          sw.Flush();

          // Reset the stream position and create a reader to check that the 
          // data was written correctly.
          stm.Position = 0;
          StreamReader rd = new StreamReader(stm);
          Console.WriteLine(rd.ReadLine());
        }
      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.ToString());
      }
      finally
      {
        if (ptr != IntPtr.Zero)
        {
          VirtualFreeEx(
            Process.GetCurrentProcess().Handle, 
            ptr, 
            IntPtr.Zero, 
            FreeType.Release);
        }
      }

      Console.ReadKey();
    }

    [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
    static extern IntPtr VirtualAllocEx(
      IntPtr hProcess, 
      IntPtr lpAddress,
      IntPtr dwSize, 
      AllocationType dwAllocationType, 
      MemoryProtection flProtect);

    [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
    static extern bool VirtualFreeEx(
      IntPtr hProcess, 
      IntPtr lpAddress, 
      IntPtr dwSize, 
      FreeType dwFreeType);

    [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
    static extern IntPtr VirtualQueryEx(
      IntPtr hProcess, 
      IntPtr lpAddress, 
      out MEMORY_BASIC_INFORMATION lpBuffer, 
      IntPtr dwLength);

    [StructLayout(LayoutKind.Sequential)]
    public struct MEMORY_BASIC_INFORMATION
    {
      public IntPtr BaseAddress;
      public IntPtr AllocationBase;
      public int AllocationProtect;
      public IntPtr RegionSize;
      public int State;
      public int Protect;
      public int Type;
    }

    [Flags]
    public enum AllocationType
    {
      Commit = 0x1000,
      Reserve = 0x2000,
      Decommit = 0x4000,
      Release = 0x8000,
      Reset = 0x80000,
      Physical = 0x400000,
      TopDown = 0x100000,
      WriteWatch = 0x200000,
      LargePages = 0x20000000
    }

    [Flags]
    public enum MemoryProtection
    {
      Execute = 0x10,
      ExecuteRead = 0x20,
      ExecuteReadWrite = 0x40,
      ExecuteWriteCopy = 0x80,
      NoAccess = 0x01,
      ReadOnly = 0x02,
      ReadWrite = 0x04,
      WriteCopy = 0x08,
      GuardModifierflag = 0x100,
      NoCacheModifierflag = 0x200,
      WriteCombineModifierflag = 0x400
    }

    [Flags]
    public enum FreeType
    {
      Decommit = 0x4000,
      Release = 0x8000
    }
  }
}

【讨论】:

  • 谢谢克里斯,但我实际上是通过读取二维字节数组来做到这一点的——因此它仍然在内存中。并分别处理每个字节数组。但这很麻烦,我不得不做一些马戏团的把戏才能让它发挥作用。鉴于我在这台服务器上有 32G - 我有点生气,我不能直接使用它......
  • 嗨,克里斯,感谢您的代码。我必须“回答”才能向您展示我的测试代码。你介意读一下吗?
【解决方案3】:

这在没有 pinvoke 调用的托管代码中是不可能的,这是有充分理由的。分配如此多的内存通常表明需要重新审视一个糟糕的解决方案。

你能告诉我们为什么你认为你需要这么多内存吗?

【讨论】:

  • 你仍然不应该这样做,文件应该分段读取。
  • 说来话长!!!如果您有兴趣-请查看我的其他问题-它们都在同一主题上。你会发现我在那里详细说明了原因。非常感谢
  • JaredPar,请您指点一下您想到的 Pinvoke 电话
【解决方案4】:

使用Marshal.AllocHGlobal(IntPtr)。此重载将IntPtr 的值视为要分配的内存量,并且 IntPtr 可以保存 64 位值。

【讨论】:

  • 感谢 Rasmus - 请参阅下面 Hans 的评论 - 可惜!
【解决方案5】:

来自评论:

如何创建第二个可以独立读取相同内存流的二进制读取器?

var fileStream = new FileStream("C:\\big.BC2",
      FileMode.Open,
      FileAccess.Read,
      FileShare.Read,
      16 * 1024,
      FileOptions.SequentialScan);
    Int64 length = fileStream.Length;
    IntPtr buffer = Marshal.AllocHGlobal(length);
    unsafe
    {
        byte* pBytes = (byte*)myp.ToPointer(); 
        var memoryStream = new UnmanagedMemoryStream(pBytes, (long)length, (long)length, FileAccess.ReadWrite);
        var binaryReader = new BinaryReader(memoryStream);
        fileStream.CopyTo(memoryStream);
        memoryStream.Seek(0, SeekOrigin.Begin);
        // Create a second UnmanagedMemoryStream on the _same_ memory buffer
        var memoryStream2 = new UnmanagedMemoryStream(pBytes, (long)length, (long)length, FileAccess.Read);
        var binaryReader2 = new BinaryReader(memoryStream);
     }

【讨论】:

    【解决方案6】:

    如果您无法使其直接按照您希望的方式工作,请创建一个类来提供您想要的行为类型。所以,要使用大数组:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.IO;
    
    namespace BigBuffer
    {
      class Storage
      {
        public Storage (string filename)
        {
          m_buffers = new SortedDictionary<int, byte []> ();
          m_file = new FileStream (filename, FileMode.Open, FileAccess.Read, FileShare.Read);
        }
    
        public byte [] GetBuffer (long address)
        {
          int
            key = GetPageIndex (address);
    
          byte []
            buffer;
    
          if (!m_buffers.TryGetValue (key, out buffer))
          {
            System.Diagnostics.Trace.WriteLine ("Allocating a new array at " + key);
            buffer = new byte [1 << 24];
            m_buffers [key] = buffer;
    
            m_file.Seek (address, SeekOrigin.Begin);
            m_file.Read (buffer, 0, buffer.Length);
          }
    
          return buffer;
        }
    
        public void FillBuffer (byte [] destination_buffer, int offset, int count, long position)
        {
          do
          {
            byte []
              source_buffer = GetBuffer (position);
    
            int
              start = GetPageOffset (position),
              length = Math.Min (count, (1 << 24) - start);
    
            Array.Copy (source_buffer, start, destination_buffer, offset, length);
    
            position += length;
            offset += length;
            count -= length;
          } while (count > 0);
        }
    
        public int GetPageIndex (long address)
        {
          return (int) (address >> 24);
        }
    
        public int GetPageOffset (long address)
        {
          return (int) (address & ((1 << 24) - 1));
        }
    
        public long Length
        {
          get { return m_file.Length; }
        }
    
        public int PageSize
        {
          get { return 1 << 24; }
        }
    
        FileStream
          m_file;
    
        SortedDictionary<int, byte []>
          m_buffers;
      }
    
      class BigStream : Stream
      {
        public BigStream (Storage source)
        {
          m_source = source;
          m_position = 0;
        }
    
        public override bool CanRead
        {
          get { return true; }
        }
    
        public override bool CanSeek
        {
          get { return true; }
        }
    
        public override bool CanTimeout
        {
          get { return false; }
        }
    
        public override bool CanWrite
        {
          get { return false; }
        }
    
        public override long Length
        {
          get { return m_source.Length; }
        }
    
        public override long Position
        {
          get { return m_position; }
          set { m_position = value; }
        }
    
        public override void Flush ()
        {
        }
    
        public override long Seek (long offset, SeekOrigin origin)
        {
          switch (origin)
          {
          case SeekOrigin.Begin:
            m_position = offset;
            break;
    
          case SeekOrigin.Current:
            m_position += offset;
            break;
    
          case SeekOrigin.End:
            m_position = Length + offset;
            break;
          }
    
          return m_position;
        }
    
        public override void SetLength (long value)
        {
        }
    
        public override int Read (byte [] buffer, int offset, int count)
        {
          int
            bytes_read = (int) (m_position + count > Length ? Length - m_position : count);
    
          m_source.FillBuffer (buffer, offset, bytes_read, m_position);
    
          m_position += bytes_read;
          return bytes_read;
        }
    
        public override void  Write(byte[] buffer, int offset, int count)
        {
        }
    
        Storage
          m_source;
    
        long
          m_position;
      }
    
      class IntBigArray
      {
        public IntBigArray (Storage storage)
        {
          m_storage = storage;
          m_current_page = -1;
        }
    
        public int this [long index]
        {
          get
          {
            int
              value = 0;
    
            index <<= 2;
    
            for (int offset = 0 ; offset < 32 ; offset += 8, ++index)
            {
              int
                page = m_storage.GetPageIndex (index);
    
              if (page != m_current_page)
              {
                m_current_page = page;
                m_array = m_storage.GetBuffer (m_current_page);
              }
    
              value |= (int) m_array [m_storage.GetPageOffset (index)] << offset;
            }
    
            return value;
          }
        }
    
        Storage
          m_storage;
    
        int
          m_current_page;
    
        byte []
          m_array;
      }
    
      class Program
      {
        static void Main (string [] args)
        {
          Storage
            storage = new Storage (@"<some file>");
    
          BigStream
            stream = new BigStream (storage);
    
          StreamReader
            reader = new StreamReader (stream);
    
          string
            line = reader.ReadLine ();
    
          IntBigArray
            array = new IntBigArray (storage);
    
          int
            value = array [0];
    
          BinaryReader
            binary = new BinaryReader (stream);
    
          binary.BaseStream.Seek (0, SeekOrigin.Begin);
    
          int
            another_value = binary.ReadInt32 ();
        }
      }
    }
    

    我将问题分为三类:

    • 存储 - 存储实际数据的位置,使用分页系统
    • BigStream - 使用 Storage 类作为其数据源的流类
    • IntBigArray - Storage 类型的包装器,提供一个 int 数组接口

    以上内容可以得到显着改进,但它应该为您提供有关如何解决问题的想法。

    【讨论】:

    • 感谢 Skizz - 感谢您分享此内容
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-08-20
    • 1970-01-01
    • 1970-01-01
    • 2018-10-24
    • 1970-01-01
    • 2023-03-31
    • 2018-10-22
    相关资源
    最近更新 更多