托管到非托管开销 [英] Managed to unmanaged overhead

查看:63
本文介绍了托管到非托管开销的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在.NET中,当您必须离开托管代码并进入非托管又称为本机代码的领域时,有几个地方.仅举几例:

  • 外部dll函数
  • COM调用

总是有关于从一侧跳到另一侧的开销的评论,而我的问题是,是否有人可以衡量正在发生的确切开销,并且可以解释如何计算开销.例如,在.NET中,也许 byte [] 可以转换为 IntPtr 甚至甚至可以转换为 byte * ,从而帮助编组节省了一些CPU周期./p>

解决方案

[我看到我没有真正回答有关如何衡量的问题;最好的测量方法是使用某种检测工具,或者使用检测工具类(请参阅:使用数组字段而不是大量的对象.(请注意:最新版本中的引用计数存在一些问题,我还没有找到答案.)

此外,我还使用ATL COM和FFTW库进行了一些接口操作,以实现高性能DSP的良好效果,尽管该库尚未为黄金时段做好准备,但这是我为此创建的峰值解决方案的基础上面的链接,该峰值为我提供了我正在寻找的大多数信息,以完成更加成熟的非托管内存分配器和快速数组处理,从而支持外部分配以及非托管堆中的非托管分配,最终替换FFTW C#库中当前存在的处理.

因此,重点是,我相信性能损失会过分夸大,尤其是随着这些天我们拥有的处理能力.实际上,如果您仅使用C#来避免某些陷阱(例如,调用许多小的跨边界函数而不是传递缓冲区或多次分配字符串等),就可以得到很好的性能.但是,对于高速处理,C#仍然可以满足我提到的所有方案的要求.是不是有时候需要预先考虑一下.但是在开发速度,可维护性和可理解性方面所获得的优势以及我花时间弄清楚如何获得所需的性能的时间始终远远少于完全或完全使用C ++进行开发所花费的时间.

我的两位.(哦,一个警告,我之所以提到ATL COM,是因为您使用MFC时所造成的性能损失不值得.我记得,当通过MFC调用时,速度要慢两个数量级.MFC COM Object与ATL上的接口相对应,并且不能满足我们的需求;另一方面,ATL的速度比直接在C ++中直接调用等效函数要慢一些.即使我们正在收集和移动大量的超声数据,也没有发现瓶颈.


哦,我发现了: http://msdn.microsoft.com/en-us/library/ms973839.aspx ".NET应用程序中的性能提示和技巧".我发现这句话很有趣:

要加快过渡时间,请尝试尽可能使用P/Invoke.开销仅需31条指令,再加上成本如果需要数据封送,则为封送,否则为8.通讯互操作要昂贵得多,最多需要65条指令.

示例部分的标题:进行大量调用",用于字符串迭代的循环使用",正在寻找异步IO机会".


所引用的快速内存库中的一些摘要:

MemoryArray.cs

 公共MemoryArray(int parElementCount,int parElementSize_bytes){描述符=新的MemoryArrayDescriptor(元帅.AllocHGlobal(parElementCount * parElementSize_bytes),parElementSize_bytes,parElementCount);}受保护的重写void OnDispose(){如果(Descriptor.StartPointer!= IntPtr.Zero)Marshal.FreeHGlobal(Descriptor.StartPointer);base.OnDispose();}//如果您要顺序访问,则实际上仅应将其用于随机访问项目//使用枚举器,该枚举器通过数组描述符的TryMoveNext调用使用指针数学.////我还没有弄清楚它到底会去哪里,但是你也可以做类似的事情//具有一个在此处更新的MemoryArrayItem成员,而不是每个成员都创建一个新成员//时间;这会破坏试图保留对该项目的引用的任何内容,因为//它将不再是不变的.////可以通过类似调用的方法来补救,该调用可以返回该项目的新副本(如果有的话)//要保留.我肯定需要看到我需要性能提升和//在我与用户对此的期望相抵触之前,这已经足够有意义了.公共MemoryArrayItem此[int i]{得到{返回新的MemoryArrayItem(this,Descriptor.GetElementPointer(i),Descriptor.ElementSize_bytes);}}//您也可以进行多维索引;为此,您必须以某种方式传递尺寸//构造函数并存储它们.////为此您可以做各种事情;取各种切片,等等,在之间进行切换//倒数第一/倒数第一/自定义维度排序等,但在示例中我没有解决.////如果您不需要在此处进行错误检查,则始终可以执行以下操作:公共MemoryArrayItem this [int x,int y]{得到{如果(myDimensions == null)抛出新的ArrayTypeMismatchException(试图在不调用SetDimensions()的情况下索引二维数组");如果(myDimensions.Length!= 2)抛出新的ArrayTypeMismatchException(当前设置的尺寸不提供二维数组.[尺寸:" + myDimensions.Length +]"));int RowSize_bytes = myDimensions [0] * Descriptor.ElementSize_bytes;返回新的MemoryArrayItem(this,Descriptor.StartPointer +(y * RowSize_bytes)+ x * Descriptor.ElementSize_bytes,Descriptor.ElementSize_bytes);}}公共无效SetDimensions(int [] parDimensions){如果(parDimensions.Length< = 0)抛出新的Exception(无法将数组设置为零维.");为(int i = 0; i< parDimensions.Length; ++ i)如果(parDimensions [i]< = 0)抛出新的ArgumentOutOfRangeException(无法将索引" + i.ToString()+的维设置为" + parDimensions [i] +.");myDimensions = new int [parDimensions.Length];parDimensions.CopyTo(myDimensions,0);}private int [] myDimensions = null; 

来自 MemoryArrayEnumerator.cs

 公共类MemoryArrayEnumerator:IEnumerator< MemoryArrayItem>{//处理主数组的引用计数私有AutoReference< MemoryArray>myArray;私有MemoryArray数组{get {return myArray;}}私人IntPtr myCurrentPosition = IntPtr.Zero;公共MemoryArrayEnumerator(MemoryArray parArray){myArray = AutoReference< MemoryArray> .CreateFromExisting(parArray);}//---------------------------------------------------------------------------------------------------------------#region IEnumerator< MemoryArrayItem>执行//---------------------------------------------------------------------------------------------------------------公共MemoryArrayItem当前{得到{如果(Array.Descriptor.CheckPointer(myCurrentPosition))返回新的MemoryArrayItem(myArray,myCurrentPosition,Array.Descriptor.ElementSize_bytes);别的抛出新的IndexOutOfRangeException(枚举器错误:Current()超出范围");}}公共无效Dispose(){myArray.Dispose();}对象System.Collections.IEnumerator.Current{得到{抛出新的NotImplementedException();}}公共布尔MoveNext(){bool RetVal = true;如果(myCurrentPosition == IntPtr.Zero)myCurrentPosition = Array.Descriptor.StartPointer;别的RetVal = Array.Descriptor.TryMoveNext(ref myCurrentPosition);返回RetVal;}公共无效Reset(){myCurrentPosition = IntPtr.Zero;}//---------------------------------------------------------------------------------------------------------------#endregion IEnumerator< MemoryArrayItem>执行//--------------------------------------------------------------------------------------------------------------- 

In .NET there are several places when you must leave managed code and enter the realm of unmanaged a.k.a. native code. To name a few:

  • extern dll functions
  • COM invocation

There are always comments about overhead that jump from one side to another causes, and my question here is if anybody MEASURED exact overhead that is happening, and can explain how it can be calculated. For example, maybe byte[] can be converted to IntPtr or even to byte* in .NET and help marshaller save some CPU cycles.

解决方案

[I see I didn't really answer the question about how you would measure; the best way to measure is just with some instrumentation, either with the instrumentation classes (see: http://msdn.microsoft.com/en-us/library/aa645516(v=vs.71).aspx) or even with something as simple as placing in some timers around whatever calls you're interested in. So, in the crudest form, when we were trying to find the performance hit taken, for instance, in our call between C# and ATL COM, we would just place timers around an empty function call we would start a timer, run in a tight loop between C# and the empty ATL COM function, do enough loops that we were able to get reasonably consistent answers between runs, and then do the exact same thing in C++. Then, the difference between those two numbers is the overhead for making the call across that boundary.]

I don't really have any hard numbers, but I can answer from previous experience that as long as you use things in an efficient way, C# performs with very little, if any, overhead beyond what one might expect in C++, depending on the exact nature of what you are trying to do.

I worked on several applications that collected very large amounts ultrasonic data through very high frequency (100MHz-3GHz A/D boards) and by doing certain things, as you suggest, (things like byte[] arrays allocated in managed code and then locked down as pointers and passed as buffers for the data; transferring large amounts of data and processing it for imaging various parts).

Way back when, we communicated with C++ code to VB6, and we would wrap the C++ in ATL Simple COM objects, and pass pointers back and forth when needed for data and imaging. We practiced similar techniques much later with C# in VS.NET 2003. Also, I have written a library for a question here that allows for massive unmanaged data storage that can provide support for very large arrays and array operations, as well as even a lot of LINQ-type functionality! Using array fields instead of massive number of objects . (note: there are some issues with the reference counting that were in the latest version, and I have not yet tracked down.)

Also, I have done some interfacing using ATL COM with the FFTW library in order to perform high-performance DSP to good effect, although that library is not quite ready for primetime, it was the basis of the spike solution I created for the link above, and the spike gave me most of the information I was looking for to complete my much more full-blown unmanaged memory allocator and fast-array processing supporting both externally allocated as well as unmanaged allocations from the unmanaged heap, which will eventually replace the processing that currently exists in the FFTW C# library.

So, the point is, I believe the performance penalty is very overblown, especially with the processing power we have these days. In fact, you can get very good performance if you just take care to avoid some of the pitfalls (like calling lots of little cross-boundary functions instead of passing buffers, or multiple allocations of strings and such) using C# by itself. But when it comes to high-speed processing, C# still can fit the bill for all of the scenarios I've mentioned. Does it take a little forethought, yes, sometimes. But the advantages gained in development speed, maintainability, and understandability, the time I spent figuring out how to get the performance I need has always been far less than the amount of time it would have taken to develop primarily or completely in C++.

My two bits. (Oh, one caveat, I mention ATL COM specifically because the performance hit you took when using MFC was not worth it. As I recall it, it was about two orders of magnitude slower when calling out via an MFC COM Object versus the interface on the ATL one, and did not meet our needs. ATL on the other hand was only a smidge slower than calling the equivalent function directly in C++. Sorry, I do not recall any particular numbers offhand, other than that even with the large amounts of ultrasonic data we were collecting and moving around, we did not find it a bottleneck.)


Oh, I found this: http://msdn.microsoft.com/en-us/library/ms973839.aspx "Performance Tips and Tricks in .NET Applications". I found this quote very interesting:

To speed up transition time, try to make use of P/Invoke when you can. The overhead is as little as 31 instructions plus the cost of marshalling if data marshalling is required, and only 8 otherwise. COM interop is much more expensive, taking upwards of 65 instructions.

Sample section titles: "Make Chunky Calls", "Use For Loops for String Iteration", "Be on the Lookout for Asynchronous IO Opportunities".


Some snippets from the referenced Fast Memory Library:

in 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;

from 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
    //---------------------------------------------------------------------------------------------------------------

这篇关于托管到非托管开销的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆