在.NET中,有几个地方必须离开托管代码并进入非托管a.k.a的领域。本地代码。举几个例子:
总有一些关于开销的评论,从一边跳到另一边,我在这里的问题是,是否有人精确测量正在发生的开销,并解释如何计算它。例如,也许可以将byte[]转换为IntPtr,甚至可以在.NET中转换为byte*,从而帮助编组程序节省一些CPU周期。
发布于 2011-08-16 06:16:04
[我知道我并没有真正回答关于如何测量的问题;最好的测量方法是使用一些工具,或者使用插装类(参见:http://msdn.microsoft.com/en-us/library/aa645516(v=vs.71).aspx,甚至是一些简单的东西,比如在您感兴趣的任何调用中添加计时器)。因此,在最简单的形式中,当我们试图找到性能受到影响时,例如,在C#和ATL之间的调用中,我们只需在一个空函数调用周围放置计时器,在C#和空ATL函数之间运行一个紧循环,执行足够多的循环,我们能够在运行过程中得到合理一致的答案,然后在C++中做同样的事情。然后,这两个数字之间的区别是跨越边界进行调用的开销。]
我实际上没有任何硬数字,但我可以从以前的经验中回答,只要您以一种高效的方式使用事物,C#的执行开销将非常小(如果有的话),超出人们在C++中可能预期的开销,这取决于您所要做的事情的确切性质。
我研究了几个应用程序,这些应用程序通过非常高的频率(100 3GHz 3 3GHz/D板)收集大量超声波数据,并按您的建议做某些事情(如在托管代码中分配的byte[]数组,然后作为指针锁定并作为数据缓冲区传递;传输大量数据并对其进行成像处理)。
回到过去,我们使用C++代码与VB6通信,并将C++封装在ATL对象中,并在需要数据和图像时来回传递指针。我们在C# 2003的VS.NET中使用了类似的技术。此外,我在这里为一个问题编写了一个库,它允许大规模的非托管数据存储,可以提供对非常大的数组和数组操作的支持,甚至支持许多LINQ类型的功能!使用数组字段而不是大量的对象。(注:最新版本中的参考计数存在一些问题,我还没有找到。)
此外,我还使用ATL COM与FFTW库进行了一些接口,以实现高性能DSP的良好效果,尽管该库还没有为黄金时段做好准备,但它是我为上面的链接创建的尖峰解决方案的基础,并且尖峰给了我所寻找的大部分信息,以便完成我的更全面的非托管内存分配器和快速数组处理,这些处理既支持外部分配,也支持来自非托管堆的非托管分配,这将最终取代FFTW C#库中目前存在的处理。
所以,重点是,我相信表现上的惩罚被夸大了,特别是我们这些天的处理能力。实际上,如果您自己使用C#来避免一些陷阱(比如调用大量的小越界函数而不是传递缓冲区或多个字符串分配等等),就可以获得很好的性能。但是当涉及到高速处理时,C#仍然可以满足我提到的所有场景。是的,有时候需要事先考虑一下。但是,在开发速度、可维护性和可理解性方面,我花在如何获得我所需要的性能上的时间总是比在C++中主要或完全开发所需的时间少得多。
我的两块。(哦,请注意,我特别提到ATL,因为在使用MFC时受到的性能影响不值得。我记得,通过MFC对象调用时,它比ATL上的接口慢了大约两个数量级,不能满足我们的需要。另一方面,ATL只比在C++中直接调用等效函数慢一点点。对不起,我不记得任何具体的数字,除了我们收集和移动的大量超声波数据之外,我们没有发现它是一个瓶颈。)
哦,我发现了这个:http://msdn.microsoft.com/en-us/library/ms973839.aspx“.NET应用程序中的性能提示和技巧”。我觉得这句话很有趣:
若要加快转换时间,请尽可能使用P/Invoke。如果需要数据编组,则开销仅为31条指令加上编组的成本,否则仅需8条。COM互操作要贵得多,需要65条以上的指令。
示例部分的标题是:"Make Chunky Call“、"Use For Loops for String迭代”、"Be on the Lookout for A同步IO section“。
引用的快速内存库中的一些片段:
在MemoryArray.cs中
public MemoryArray(int parElementCount, int parElementSize_bytes)
{
Descriptor =
new MemoryArrayDescriptor
(
Marshal.AllocHGlobal(parElementCount * parElementSize_bytes),
parElementSize_bytes,
parElementCount
);
}
protected override void OnDispose()
{
if (Descriptor.StartPointer != IntPtr.Zero)
Marshal.FreeHGlobal(Descriptor.StartPointer);
base.OnDispose();
}
// this really should only be used for random access to the items, if you want sequential access
// use the enumerator which uses pointer math via the array descriptor's TryMoveNext call.
//
// i haven't figured out exactly where it would go, but you could also do something like
// having a member MemoryArrayItem that gets updated here rather than creating a new one each
// time; that would break anything that was trying to hold on to a reference to the item because
// it will no longer be immutable.
//
// that could be remedied by something like a call that would return a new copy of the item if it
// was to be held onto. i would definitely need to see that i needed the performance boost and
// that it was significant enough before i would contradict the users expectations on that one.
public MemoryArrayItem this[int i]
{
get
{
return new MemoryArrayItem(this, Descriptor.GetElementPointer(i), Descriptor.ElementSize_bytes);
}
}
// you could also do multiple dimension indexing; to do so you would have to pass in dimensions somehow in
// the constructor and store them.
//
// there's all sorts of stuff you could do with this; take various slices, etc, do switching between
// last-to-first/first-to-last/custom dimension ordering, etc, but i didn't tackle that for the example.
//
// if you don't need to error check here then just you could always do something like:
public MemoryArrayItem this[int x, int y]
{
get
{
if (myDimensions == null)
throw new ArrayTypeMismatchException("attempted to index two dimensional array without calling SetDimensions()");
if (myDimensions.Length != 2)
throw new ArrayTypeMismatchException("currently set dimensions do not provide a two dimensional array. [dimension: " + myDimensions.Length + "]");
int RowSize_bytes = myDimensions[0] * Descriptor.ElementSize_bytes;
return new MemoryArrayItem(this, Descriptor.StartPointer + (y * RowSize_bytes) + x * Descriptor.ElementSize_bytes, Descriptor.ElementSize_bytes);
}
}
public void SetDimensions(int[] parDimensions)
{
if (parDimensions.Length <= 0)
throw new Exception("unable to set array to dimension of zero.");
for (int i = 0; i < parDimensions.Length; ++i)
if (parDimensions[i] <= 0)
throw new ArgumentOutOfRangeException("unable to set dimension at index " + i.ToString() + " to " + parDimensions[i] + ".");
myDimensions = new int[parDimensions.Length];
parDimensions.CopyTo(myDimensions, 0);
}
private int[] myDimensions = null;来自MemoryArrayEnumerator.cs
public class MemoryArrayEnumerator :
IEnumerator<MemoryArrayItem>
{
// handles reference counting for the main array
private AutoReference<MemoryArray> myArray;
private MemoryArray Array { get { return myArray; } }
private IntPtr myCurrentPosition = IntPtr.Zero;
public MemoryArrayEnumerator(MemoryArray parArray)
{
myArray = AutoReference<MemoryArray>.CreateFromExisting(parArray);
}
//---------------------------------------------------------------------------------------------------------------
#region IEnumerator<MemoryArrayItem> implementation
//---------------------------------------------------------------------------------------------------------------
public MemoryArrayItem Current
{
get
{
if (Array.Descriptor.CheckPointer(myCurrentPosition))
return new MemoryArrayItem(myArray, myCurrentPosition, Array.Descriptor.ElementSize_bytes);
else
throw new IndexOutOfRangeException("Enumerator Error: Current() was out of range");
}
}
public void Dispose()
{
myArray.Dispose();
}
object System.Collections.IEnumerator.Current
{
get { throw new NotImplementedException(); }
}
public bool MoveNext()
{
bool RetVal = true;
if (myCurrentPosition == IntPtr.Zero)
myCurrentPosition = Array.Descriptor.StartPointer;
else
RetVal = Array.Descriptor.TryMoveNext(ref myCurrentPosition);
return RetVal;
}
public void Reset()
{
myCurrentPosition = IntPtr.Zero;
}
//---------------------------------------------------------------------------------------------------------------
#endregion IEnumerator<MemoryArrayItem> implementation
//---------------------------------------------------------------------------------------------------------------发布于 2011-08-16 06:22:11
获取托管数组的地址确实是可能的。
首先,您必须使用System.Runtime.InteropServices.GCHandle插入数组,这样垃圾收集器就不会移动数组。只要非托管代码能够访问托管数组,就必须保持此句柄的分配。
byte[] the_array = ... ;
GCHandle pin = GCHandle.Alloc(the_array, GCHandleType.Pinned);然后,您应该能够使用System.Runtime.InteropServices.Marshal.UnsafeAddrOfPinnedArrayElement为数组中的任何元素获取一个IntPtr。
IntPtr p = Marshal.UnsafeAddrOfPinnedArrayElement(the_array,0);重要:固定对象严重干扰GC操作。能够在堆中移动对象是现代GC能够(在某种程度上)跟上手动内存管理的原因之一。通过将对象固定在托管堆中,GC失去了它相对于手动内存管理的一个性能优势:相对不碎片的堆。
因此,如果您计划将这些数组“放在非托管端”一段时间,请考虑创建数组的副本。复制内存的速度惊人。使用Marshal.Copy(*)方法将托管内存复制到非托管内存,反之亦然。
https://stackoverflow.com/questions/7074054
复制相似问题