纯C#方法
所以,有几个选择。最简单的方法是在不安全的上下文中使用new/delete来处理结构体。其次是使用内置的Marshalling服务来处理非托管内存(此代码可见于下面)。但是,这两种方法都处理结构体(尽管我认为后一种方法非常接近您想要的方法)。我的代码有一个限制,即必须始终坚持使用结构体,并使用IntPtrs作为引用(使用ChunkAllocator.ConvertPointerToStructure获取数据和ChunkAllocator.StoreStructure存储更改后的数据)。显然,这很麻烦,因此如果您使用我的方法,则最好非常需要性能。但是,如果您只处理值类型,这种方法就足够了。
插曲:CLR中的类
类在分配内存时有8个字节的“前缀”。其中四个字节用于多线程同步索引,另外四个字节用于标识它们的类型(基本上是虚拟方法表和运行时反射)。这使得处理非托管内存变得困难,因为这些是CLR特定的,并且由于同步索引可以在运行时更改。有关运行时对象创建的详细信息,请参见此处,有关引用类型的内存布局概述,请参见此处。还可以查看CLR via C#以获得更深入的解释。
注意事项
通常情况下,事情很少像是简单的“是”或“否”那样简单。引用类型的真正复杂性与垃圾收集器在垃圾收集期间如何压缩已分配内存有关。如果您可以以某种方式确保垃圾收集不会发生或不会影响相关数据(请参见
fixed keyword),则可以将任意指针转换为对象引用(只需将指针偏移8个字节,然后将该数据解释为具有相同字段和内存布局的结构体;可能要使用
StructLayoutAttribute以确保)。我建议尝试非虚拟方法,看看它们是否有效;它们应该有效(特别是如果将它们放在结构体上),但由于必须丢弃虚拟方法表,因此虚拟方法无法使用。
一个人不可能简单地走进魔多
简单来说,这意味着托管引用类型(类)不能在非托管内存中分配。你可以在C++中使用托管引用类型,但那些会受到垃圾回收的影响...而且过程和代码比基于
struct
的方法更加繁琐。那我们现在怎么办呢?当然是回到起点了。
有一个秘密方法
我们可以自己处理内存分配,就像勇闯
蜘蛛的巢穴一样。不幸的是,这就是我们分道扬镳的地方,因为我对此并不是很了解。我会给你提供一个
链接或
链接 - 也许实际上是
链接或
链接。这相当复杂,令人质疑:是否有其他优化方法可尝试?缓存一致性和优越算法是一种方法,谨慎应用P/Invoke来进行性能关键代码也是一种方法。您还可以为关键方法/类应用前面提到的仅结构内存分配。
祝好运,并告诉我们如果您找到更好的替代方案。
附录:源代码
ChunkAllocator.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace MemAllocLib
{
public sealed class ChunkAllocator : IDisposable
{
IntPtr m_chunkStart;
int m_offset;
readonly int m_size;
public ChunkAllocator(int memorySize = 1024)
{
if (memorySize < 1)
throw new ArgumentOutOfRangeException("memorySize must be positive");
m_size = memorySize;
m_chunkStart = Marshal.AllocHGlobal(memorySize);
}
~ChunkAllocator()
{
Dispose();
}
public IntPtr Allocate<T>() where T : struct
{
int reqBytes = Marshal.SizeOf(typeof(T));
return Allocate<T>(reqBytes);
}
public IntPtr Allocate<T>(int reqBytes) where T : struct
{
if (m_chunkStart == IntPtr.Zero)
throw new ObjectDisposedException("ChunkAllocator");
if (m_offset + reqBytes > m_size)
throw new OutOfMemoryException("Too many bytes allocated: " + reqBytes + " needed, but only " + (m_size - m_offset) + " bytes available");
T created = default(T);
Marshal.StructureToPtr(created, m_chunkStart + m_offset, false);
m_offset += reqBytes;
return m_chunkStart + (m_offset - reqBytes);
}
public void Dispose()
{
if (m_chunkStart != IntPtr.Zero)
{
Marshal.FreeHGlobal(m_chunkStart);
m_offset = 0;
m_chunkStart = IntPtr.Zero;
}
}
public void ReleaseAllMemory()
{
m_offset = 0;
}
public int AllocatedMemory
{
get { return m_offset; }
}
public int AvailableMemory
{
get { return m_size - m_offset; }
}
public int TotalMemory
{
get { return m_size; }
}
public static T ConvertPointerToStruct<T>(IntPtr ptr) where T : struct
{
return (T)Marshal.PtrToStructure(ptr, typeof(T));
}
public static void StoreStructure<T>(IntPtr ptr, T data) where T : struct
{
Marshal.StructureToPtr(data, ptr, false);
}
}
}
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MemoryAllocation
{
class Program
{
static void Main(string[] args)
{
using (MemAllocLib.ChunkAllocator chunk = new MemAllocLib.ChunkAllocator())
{
Console.WriteLine(">> Simple data test");
SimpleDataTest(chunk);
Console.WriteLine();
Console.WriteLine(">> Complex data test");
ComplexDataTest(chunk);
}
Console.ReadLine();
}
private static void SimpleDataTest(MemAllocLib.ChunkAllocator chunk)
{
IntPtr ptr = chunk.Allocate<System.Int32>();
Console.WriteLine(MemAllocLib.ChunkAllocator.ConvertPointerToStruct<Int32>(ptr));
System.Diagnostics.Debug.Assert(MemAllocLib.ChunkAllocator.ConvertPointerToStruct<Int32>(ptr) == 0, "Data not initialized properly");
System.Diagnostics.Debug.Assert(chunk.AllocatedMemory == sizeof(Int32), "Data not allocated properly");
int data = MemAllocLib.ChunkAllocator.ConvertPointerToStruct<Int32>(ptr);
data = 10;
MemAllocLib.ChunkAllocator.StoreStructure(ptr, data);
Console.WriteLine(MemAllocLib.ChunkAllocator.ConvertPointerToStruct<Int32>(ptr));
System.Diagnostics.Debug.Assert(MemAllocLib.ChunkAllocator.ConvertPointerToStruct<Int32>(ptr) == 10, "Data not set properly");
Console.WriteLine("All tests passed");
}
private static void ComplexDataTest(MemAllocLib.ChunkAllocator chunk)
{
IntPtr ptr = chunk.Allocate<Person>();
Console.WriteLine(MemAllocLib.ChunkAllocator.ConvertPointerToStruct<Person>(ptr));
System.Diagnostics.Debug.Assert(MemAllocLib.ChunkAllocator.ConvertPointerToStruct<Person>(ptr).Age == 0, "Data age not initialized properly");
System.Diagnostics.Debug.Assert(MemAllocLib.ChunkAllocator.ConvertPointerToStruct<Person>(ptr).Name == null, "Data name not initialized properly");
System.Diagnostics.Debug.Assert(chunk.AllocatedMemory == System.Runtime.InteropServices.Marshal.SizeOf(typeof(Person)) + sizeof(Int32), "Data not allocated properly");
Person data = MemAllocLib.ChunkAllocator.ConvertPointerToStruct<Person>(ptr);
data.Name = "Bob";
data.Age = 20;
MemAllocLib.ChunkAllocator.StoreStructure(ptr, data);
Console.WriteLine(MemAllocLib.ChunkAllocator.ConvertPointerToStruct<Person>(ptr));
System.Diagnostics.Debug.Assert(MemAllocLib.ChunkAllocator.ConvertPointerToStruct<Person>(ptr).Age == 20, "Data age not set properly");
System.Diagnostics.Debug.Assert(MemAllocLib.ChunkAllocator.ConvertPointerToStruct<Person>(ptr).Name == "Bob", "Data name not set properly");
Console.WriteLine("All tests passed");
}
struct Person
{
public string Name;
public int Age;
public Person(string name, int age)
{
Name = name;
Age = age;
}
public override string ToString()
{
if (string.IsNullOrWhiteSpace(Name))
return "Age is " + Age;
return Name + " is " + Age + " years old";
}
}
}
}