Как выделить больше, чем MaxInteger байтов памяти в C # - PullRequest
1 голос
/ 06 сентября 2010

Я хочу выделить больше, чем 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);

Ответы [ 6 ]

7 голосов
/ 06 сентября 2010

Это невозможно на современном основном оборудовании.Буферы памяти ограничены 2 гигабайтами даже на 64-битных машинах.Индексная адресация буфера все еще выполняется с 32-битным смещением со знаком.Технически возможно генерировать машинный код, который может индексировать больше, используя регистр для хранения смещения, но это дорого и замедляет индексацию массива all , даже для тех, которые не превышают 2 ГБ.

Кроме того, вы не можете получить буфер размером более 650 МБ из адресного пространства, доступного для 32-битного процесса.Недостаточно доступных смежных страниц памяти, потому что виртуальная память содержит код и данные по разным адресам.

Такие компании, как IBM и Sun, продают оборудование, способное сделать это.

3 голосов
/ 06 сентября 2010

Я был вовлечен в один из других вопросов, которые вы задавали, и я искренне думаю, что вы ведете здесь проигранную битву. Возможно, вам нужно изучить другие способы обработки этих данных, кроме чтения всего в память.

Если я правильно понимаю, у вас есть несколько потоков, которые обрабатывают данные одновременно, и поэтому я не хочу работать с файлом напрямую из-за конфликта ввода-вывода.

Рассматривали ли вы или существует возможность чтения блока данных в память, обрабатывают ли потоки блок и затем читают следующий блок или обрабатывают потоки? Таким образом, в любой момент у вас никогда не будет больше, чем блок в памяти, но все потоки могут получить доступ к блоку. Это не оптимально, но я поставил это как отправную точку. Если это возможно, можно изучить варианты оптимизации.

Обновление: Пример использования вызова платформы для выделения неуправляемой памяти и ее использования из .NET.

Поскольку вы уверены, что вам нужно загрузить столько данных в память, я подумал, что напишу небольшое тестовое приложение, чтобы убедиться, что это будет работать. Для этого вам понадобится следующее

  1. Компиляция с опцией /unsafe
  2. Если вы хотите выделить более 2 ГБ, вам также необходимо переключить целевую платформу на x64

* Точка 2, приведенная выше, немного сложнее: в 64-битной ОС вы все равно можете использовать платформу x86 и получить доступ к полной памяти 4 GB. Это потребует от вас использования инструмента, такого как EDITBIN.EXE , чтобы установить флаг LargeAddressAware в заголовке PE.

В этом коде используется VirtualAllocEx для выделения неуправляемой памяти и UnmanagedMemoryStream для доступа к неуправляемой памяти с использованием метафоры потока .NET. Обратите внимание, что в этом коде было проведено только несколько очень простых быстрых тестов, и только в целевой 64-битной среде с 4 ГБ ОЗУ. И самое главное, я увеличил использование памяти до 2,6 ГБ.

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
    }
  }
}
1 голос
/ 06 сентября 2010

Это невозможно из управляемого кода без вызова pinvoke и по уважительной причине. Выделение такого большого объема памяти обычно является признаком плохого решения, которое необходимо пересмотреть.

Можете ли вы сказать нам, почему вы думаете, что вам нужно так много памяти?

0 голосов
/ 08 сентября 2010

Если вы не можете заставить его работать так, как вам нужно, создайте класс, чтобы обеспечить желаемый тип поведения.Итак, чтобы использовать большие массивы:

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

Выше можно улучшитьзначительно, но это должно дать вам идеи о том, как решить ваши проблемы.

0 голосов
/ 08 сентября 2010

Из комментария:

Как мне создать второй двоичный ридер, который может читать тот же поток памяти независимо?

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);
     }
0 голосов
/ 06 сентября 2010

Использование Marshal.AllocHGlobal(IntPtr).Эта перегрузка обрабатывает значение IntPtr как объем памяти для выделения, и IntPtr может содержать 64-битное значение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...