Управляется неуправляемыми накладными расходами - PullRequest
8 голосов
/ 16 августа 2011

В .NET есть несколько мест, где вы должны оставить управляемый код и ввести область неуправляемого собственного кода .k.a. Чтобы назвать несколько:

  • функции внешнего dll
  • COM-вызов

Всегда есть комментарии о накладных расходах, которые переходят с одной стороны на другую, и мой вопрос здесь заключается в том, измерял ли кто-нибудь точные накладные расходы и может объяснить, как их можно рассчитать. Например, возможно byte[] можно преобразовать в IntPtr или даже в byte* в .NET и помочь маршаллеру сэкономить некоторые циклы ЦП.

Ответы [ 2 ]

6 голосов
/ 16 августа 2011

Получение адреса управляемого массива действительно возможно.

Сначала необходимо закрепить массив с помощью 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 теряет одно преимущество в производительности по сравнению с ручным управлением памятью: относительно нефрагментированная куча.

Так что, если вы планируете хранить эти массивы "на неуправляемой стороне" в течение некоторого времени, вместо этого рассмотрите возможность создания копии массива. Копирование памяти происходит на удивление быстро. Используйте методы Marshal.Copy(*) для копирования из управляемой в неуправляемую память и наоборот.

5 голосов
/ 16 августа 2011

[Я вижу, я действительно не ответил на вопрос о том, как вы будете измерять; лучший способ измерить это просто с помощью некоторого инструментария, либо с помощью классов инструментария (см .: http://msdn.microsoft.com/en-us/library/aa645516(v=vs.71).aspx), или даже с помощью чего-то столь же простого, как размещение в некоторых таймерах любых нужных вам вызовов. Итак, самое грубое Например, когда мы пытались найти снижение производительности, полученное, например, при нашем вызове между C # и ATL COM, мы просто помещали таймеры вокруг пустого вызова функции, запускали таймер, работали в тесном цикле между C # и пустая функция ATL COM, сделать достаточное количество циклов, чтобы мы могли получить разумно согласованные ответы между прогонами, а затем сделать то же самое в C ++. Тогда разница между этими двумя числами - это издержки для выполнения вызова через эту границу.]

У меня нет точных цифр, но я могу ответить из предыдущего опыта, что до тех пор, пока вы используете вещи эффективно, C # работает с очень небольшими, если вообще таковыми, накладными расходами по сравнению с тем, что можно ожидать в C ++, в зависимости от того, что вы пытаетесь сделать.

Я работал над несколькими приложениями, которые собирали очень большие объемы ультразвуковых данных с очень высокой частотой (платы АЦП 100 МГц-3 ГГц) и, выполняя определенные действия, как вы предлагаете (например, массивы байтов [], выделенные в управляемом коде и затем блокируются как указатели и передаются как буферы для данных, передавая большие объемы данных и обрабатывая их для формирования различных частей).

Давным-давно, когда мы общались с кодом C ++ на VB6, мы оборачивали C ++ в простые COM-объекты ATL и передавали указатели назад и вперед, когда это необходимо для данных и обработки изображений. Мы практиковали аналогичные методы намного позже с C # в VS.NET 2003. Кроме того, я написал здесь библиотеку для вопроса, который учитывает массивное неуправляемое хранилище данных, которое может обеспечить поддержку очень больших массивов и операций с массивами, а также даже много функциональности типа LINQ! Использование полей массива вместо огромного количества объектов . (примечание: есть некоторые проблемы с подсчетом ссылок, которые были в последней версии, и я еще не выследил.)

Кроме того, я провел некоторый интерфейс с использованием ATL COM с библиотекой FFTW, чтобы обеспечить высокую производительность высокопроизводительного DSP, хотя эта библиотека еще не совсем готова для прайм-тайма, она была основой решения для всплеска, которое я создал для ссылка выше, и всплеск дал мне большую часть информации, которую я искал, чтобы завершить мой гораздо более полноценный неуправляемый распределитель памяти и обработку быстрого массива, поддерживающую как внешнее, так и неуправляемое распределение из неуправляемой кучи, которая в конечном итоге будет замените обработку, которая в настоящее время существует в библиотеке FFTW C #.

Итак, суть в том, что я считаю, что снижение производительности очень преувеличено, особенно с учетом вычислительной мощности, которую мы имеем в наши дни. Фактически, вы можете получить очень хорошую производительность, если просто позаботитесь о том, чтобы избежать некоторых ловушек (таких как вызов большого количества небольших трансграничных функций вместо передачи буферов или многократное распределение строк и тому подобное), используя сам C #. Но когда дело доходит до высокоскоростной обработки, C # все еще может отвечать всем сценариям, которые я упомянул. Требуется ли немного предусмотрительности, да, иногда. Но преимущества, получаемые в скорости разработки, удобстве обслуживания и понятности, время, потраченное на то, чтобы понять, как получить необходимую производительность, всегда было намного меньше, чем время, которое потребовалось бы для разработки в первую очередь или полностью в C ++.

Мои два бита. (О, одно предостережение, я упоминаю ATL COM специально, потому что удар по производительности, который вы получили при использовании MFC, стоил не . Насколько я помню, он был примерно на два порядка медленнее при вызове через MFC COM-объект по сравнению с интерфейсом на ATL и не удовлетворял нашим потребностям. С другой стороны, ATL был всего лишь чуть-чуть медленнее, чем прямой вызов эквивалентной функции в C ++. Извините, я не вспоминаю какие-либо конкретные числа, кроме этого. даже при большом количестве ультразвуковых данных, которые мы собирали и перемещали, мы не нашли это узким местом.)


О, я нашел это: http://msdn.microsoft.com/en-us/library/ms973839.aspx "Советы и рекомендации по производительности в приложениях .NET". Я нашел эту цитату очень интересной:

Чтобы ускорить время перехода, попробуйте использовать P / Invoke, когда можете. Накладные расходы составляют всего 31 инструкцию плюс стоимость маршаллинг, если маршаллинг данных требуется, и только 8 в противном случае. COM Взаимодействие намного дороже, занимает более 65 инструкций.

Примеры заголовков разделов: «Делать короткие вызовы», «Использовать циклы для итерации строк», «Следите за возможностями асинхронного ввода-вывода».


Некоторые фрагменты из указанной библиотеки быстрой памяти:

в 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
    //---------------------------------------------------------------------------------------------------------------
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...