Подтвердить что ты не робот

Каковы накладные расходы фиксированного оператора С# на управляемой небезопасной структуре, содержащей фиксированные массивы?

Я пытался определить, какова истинная стоимость использования фиксированного оператора в С# для управляемых небезопасных структур, содержащих фиксированные массивы. Обратите внимание: я не имею в виду неуправляемые структуры.

В частности, есть ли какие-либо причины, чтобы избежать шаблона, показанного ниже в классе "MultipleFixed"? Является ли стоимость простой фиксации данных без нуля, около нуля (== стоимость аналогична настройке и очистке одного флага при входе/выходе из фиксированной области), или это достаточно важно, чтобы избежать, когда это возможно?

Очевидно, что эти классы придуманы, чтобы помочь объяснить вопрос. Это для высокопроизводительной структуры данных в XNA-игре, где производительность чтения и записи этих данных имеет решающее значение, поэтому, если мне нужно исправить массив и передать его повсюду, я сделаю это, но если вообще не будет никакой разницы 'd предпочитают держать fixed() локальным в методах, чтобы помочь сохранить сигнатуры функций немного более переносимыми на платформы, которые не поддерживают небезопасный код. (Да, его дополнительный код ворчания, но что бы он ни делал.)

 

    unsafe struct ByteArray
    {
       public fixed byte Data[1024];
    }

    class MultipleFixed
    {
       unsafe void SetValue(ref ByteArray bytes, int index, byte value)
       {
           fixed(byte* data = bytes.Data)
           {
               data[index] = value;
           }
       }

        unsafe bool Validate(ref ByteArray bytes, int index, byte expectedValue)
        {
           fixed(byte* data = bytes.Data)
           {
               return data[index] == expectedValue;
           }
        }

        void Test(ref ByteArray bytes)
        {
            SetValue(ref bytes, 0, 1);
            Validate(ref bytes, 0, 1);
        }
    }

    class SingleFixed
    {
       unsafe void SetValue(byte* data, int index, byte value)
       {
           data[index] = value;
       }

        unsafe bool Validate(byte* data, int index, byte expectedValue)
        {
           return data[index] == expectedValue;
        }

        unsafe void Test(ref ByteArray bytes)
        {
            fixed(byte* data = bytes.Data)
            {
                SetValue(data, 0, 1);
                Validate(data, 0, 1);
            }
        }
    }

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

Спасибо за любую информацию!

4b9b3361

Ответ 1

Эмпирически, накладные расходы, по-видимому, в лучшем случае составляют ~ 270% на 32-битных JIT и ~ 200% на 64 бит (и накладные расходы ухудшаются, чем больше вы "звоните" fixed). Поэтому я попытаюсь свести к минимуму блоки fixed, если производительность действительно важна.

Извините, я не достаточно знаком с фиксированным/небезопасным кодом, чтобы узнать, почему этот случай


Подробнее

Я также добавил несколько методов TestMore, которые называют ваши два метода тестирования 10 раз вместо 2 чтобы дать более реальный мировой сценарий нескольких методов, вызываемых в вашей структуре fixed.

Код, который я использовал:

class Program
{
    static void Main(string[] args)
    {
        var someData = new ByteArray();
        int iterations = 1000000000;
        var multiple = new MultipleFixed();
        var single = new SingleFixed();

        // Warmup.
        for (int i = 0; i < 100; i++)
        {
            multiple.Test(ref someData);
            single.Test(ref someData);
            multiple.TestMore(ref someData);
            single.TestMore(ref someData);
        }

        // Environment.
        if (Debugger.IsAttached)
            Console.WriteLine("Debugger is attached!!!!!!!!!! This run is invalid!");
        Console.WriteLine("CLR Version: " + Environment.Version);
        Console.WriteLine("Pointer size: {0} bytes", IntPtr.Size);
        Console.WriteLine("Iterations: " + iterations);

        Console.Write("Starting run for Single... ");
        var sw = Stopwatch.StartNew();
        for (int i = 0; i < iterations; i++)
        {
            single.Test(ref someData);
        }
        sw.Stop();
        Console.WriteLine("Completed in {0:N3}ms - {1:N2}/sec", sw.Elapsed.TotalMilliseconds, iterations / sw.Elapsed.TotalSeconds);

        Console.Write("Starting run for More Single... ");
        sw = Stopwatch.StartNew();
        for (int i = 0; i < iterations; i++)
        {
            single.Test(ref someData);
        }
        sw.Stop();
        Console.WriteLine("Completed in {0:N3}ms - {1:N2}/sec", sw.Elapsed.TotalMilliseconds, iterations / sw.Elapsed.TotalSeconds);


        Console.Write("Starting run for Multiple... ");
        sw = Stopwatch.StartNew();
        for (int i = 0; i < iterations; i++)
        {
            multiple.Test(ref someData);
        }
        sw.Stop();
        Console.WriteLine("Completed in {0:N3}ms - {1:N2}/sec", sw.Elapsed.TotalMilliseconds, iterations / sw.Elapsed.TotalSeconds);

        Console.Write("Starting run for More Multiple... ");
        sw = Stopwatch.StartNew();
        for (int i = 0; i < iterations; i++)
        {
            multiple.TestMore(ref someData);
        }
        sw.Stop();
        Console.WriteLine("Completed in {0:N3}ms - {1:N2}/sec", sw.Elapsed.TotalMilliseconds, iterations / sw.Elapsed.TotalSeconds);


        Console.ReadLine();
    }
}

unsafe struct ByteArray
{
    public fixed byte Data[1024];
}

class MultipleFixed
{
    unsafe void SetValue(ref ByteArray bytes, int index, byte value)
    {
        fixed (byte* data = bytes.Data)
        {
            data[index] = value;
        }
    }

    unsafe bool Validate(ref ByteArray bytes, int index, byte expectedValue)
    {
        fixed (byte* data = bytes.Data)
        {
            return data[index] == expectedValue;
        }
    }

    public void Test(ref ByteArray bytes)
    {
        SetValue(ref bytes, 0, 1);
        Validate(ref bytes, 0, 1);
    }
    public void TestMore(ref ByteArray bytes)
    {
        SetValue(ref bytes, 0, 1);
        Validate(ref bytes, 0, 1);
        SetValue(ref bytes, 0, 2);
        Validate(ref bytes, 0, 2);
        SetValue(ref bytes, 0, 3);
        Validate(ref bytes, 0, 3);
        SetValue(ref bytes, 0, 4);
        Validate(ref bytes, 0, 4);
        SetValue(ref bytes, 0, 5);
        Validate(ref bytes, 0, 5);
    }
}

class SingleFixed
{
    unsafe void SetValue(byte* data, int index, byte value)
    {
        data[index] = value;
    }

    unsafe bool Validate(byte* data, int index, byte expectedValue)
    {
        return data[index] == expectedValue;
    }

    public unsafe void Test(ref ByteArray bytes)
    {
        fixed (byte* data = bytes.Data)
        {
            SetValue(data, 0, 1);
            Validate(data, 0, 1);
        }
    }
    public unsafe void TestMore(ref ByteArray bytes)
    {
        fixed (byte* data = bytes.Data)
        {
            SetValue(data, 0, 1);
            Validate(data, 0, 1);
            SetValue(data, 0, 2);
            Validate(data, 0, 2);
            SetValue(data, 0, 3);
            Validate(data, 0, 3);
            SetValue(data, 0, 4);
            Validate(data, 0, 4);
            SetValue(data, 0, 5);
            Validate(data, 0, 5);
        }
    }
}

И результаты в .NET 4.0, 32 бит JIT:

CLR Version: 4.0.30319.239
Pointer size: 4 bytes
Iterations: 1000000000
Starting run for Single... Completed in 2,092.350ms - 477,931,580.94/sec
Starting run for More Single... Completed in 2,236.767ms - 447,073,934.63/sec
Starting run for Multiple... Completed in 5,775.922ms - 173,132,528.92/sec
Starting run for More Multiple... Completed in 26,637.862ms - 37,540,550.36/sec

И в .NET 4.0, 64 бит JIT:

CLR Version: 4.0.30319.239
Pointer size: 8 bytes
Iterations: 1000000000
Starting run for Single... Completed in 2,907.946ms - 343,885,316.72/sec
Starting run for More Single... Completed in 2,904.903ms - 344,245,585.63/sec
Starting run for Multiple... Completed in 5,754.893ms - 173,765,185.93/sec
Starting run for More Multiple... Completed in 18,679.593ms - 53,534,358.13/sec

Ответ 2

Это был действительно интересный вопрос, который у меня был.

Результаты, которые мне удалось получить, предлагают несколько разные причины потери производительности, чем сам "фиксированный".

Вы можете увидеть тесты, которые я запускаю, и результаты ниже, но есть следующие наблюдения, которые я рисую из них:

  • производительность использования "fixed" с чистыми указателями (x *) без IntPtr не хуже, чем в управляемом коде; в режиме деблокирования это даже способ лучше, если фиксированный не используется слишком часто - это самый удобный способ доступа к нескольким значениям массива.
  • в режиме отладки, используя "фиксированный" (внутри цикла), имеет большое отрицательное влияние на производительность, но в режиме выпуска он работает почти так же хорошо, как обычный доступ к массиву (метод FixedAccess);
  • с использованием 'ref' для значения параметра ссылочного типа (float []) был последовательно более или менее результативным (оба режима)
  • Режим отладки имеет значительное снижение производительности и режим выпуска при использовании арифметики IntPtr (IntPtrAccess), но для обоих режимов производительность хуже, чем доступ к нормальному массиву.
  • Если использовать смещение, не совпадающее со смещением значений массива, производительность ужасная, независимо от режима (для обоих режимов это занимает примерно одинаковое время). Это справедливо для "float", но не влияет на "int"

Выполняя тесты несколько раз, дает несколько иные, но широко согласованные результаты. Вероятно, я должен был запустить множество серий тестов и взять среднее время - но не успел на это:)

Первый класс тестирования:

class Test {
    public static void NormalAccess (float[] array, int index) {
        array[index] = array[index] + 2;
    }

    public static void NormalRefAccess (ref float[] array, int index) {
        array[index] = array[index] + 2;
    }

    public static void IntPtrAccess (IntPtr arrayPtr, int index) {
        unsafe {
            var array = (float*) IntPtr.Add (arrayPtr, index << 2);
            (*array) = (*array) + 2;
        }
    }

    public static void IntPtrMisalignedAccess (IntPtr arrayPtr, int index) {
        unsafe {
            var array = (float*) IntPtr.Add (arrayPtr, index); // getting bits of a float
            (*array) = (*array) + 2;
        }
    }

    public static void FixedAccess (float[] array, int index) {
        unsafe {
            fixed (float* ptr = &array[index]) 
                (*ptr) = (*ptr) + 2;
        }
    }

    public unsafe static void PtrAccess (float* ptr) {
        (*ptr) = (*ptr) + 2;
    }

}

И сами тесты:

    static int runs = 1000*1000*100;
    public static void Print (string name, Stopwatch sw) {
        Console.WriteLine ("{0}, items/sec = {1:N} \t {2}", sw.Elapsed, (runs / sw.ElapsedMilliseconds) * 1000, name);
    }

    static void Main (string[] args) {
        var buffer = new float[1024*1024*100];
        var len = buffer.Length;

        var sw = new Stopwatch();
        for (int i = 0; i < 1000; i++) { 
            Test.FixedAccess (buffer, 55);
            Test.NormalAccess (buffer, 66);
        }

        Console.WriteLine ("Starting {0:N0} items", runs);


        sw.Restart ();
        for (int i = 0; i < runs; i++) 
            Test.NormalAccess (buffer, i % len);
        sw.Stop ();

        Print ("Normal access", sw);

        sw.Restart ();
        for (int i = 0; i < runs; i++) 
            Test.NormalRefAccess (ref buffer, i % len);
        sw.Stop ();

        Print ("Normal Ref access", sw);

        sw.Restart ();
        unsafe {
            fixed (float* ptr = &buffer[0])
                for (int i = 0; i < runs; i++) { 
                    Test.IntPtrAccess ((IntPtr) ptr, i % len);
                }
        }
        sw.Stop ();

        Print ("IntPtr access (fixed outside loop)", sw);

        sw.Restart ();
        unsafe {
            fixed (float* ptr = &buffer[0])
                for (int i = 0; i < runs; i++) { 
                    Test.IntPtrMisalignedAccess ((IntPtr) ptr, i % len);
                }
        }
        sw.Stop ();

        Print ("IntPtr Misaligned access (fixed outside loop)", sw);

        sw.Restart ();
        for (int i = 0; i < runs; i++) 
            Test.FixedAccess (buffer, i % len);
        sw.Stop ();

        Print ("Fixed access (fixed inside loop)", sw);

        sw.Restart ();
        unsafe {
            fixed (float* ptr = &buffer[0]) { 
                for (int i = 0; i < runs; i++) { 
                    Test.PtrAccess (ptr + (i % len));
                }
            }
        }
        sw.Stop ();

        Print ("float* access (fixed outside loop)", sw);

        sw.Restart ();
        unsafe {
            for (int i = 0; i < runs; i++) { 
                fixed (float* ptr = &buffer[i % len]) { 
                    Test.PtrAccess (ptr);
                }
            }
        }
        sw.Stop ();

        Print ("float* access (fixed in loop)", sw);

и, наконец, результаты:

Режим отладки

Starting 100,000,000 items
00:00:01.0373583, items/sec = 96,432,000.00      Normal access
00:00:00.8582307, items/sec = 116,550,000.00     Normal Ref access
00:00:01.8822085, items/sec = 53,134,000.00      IntPtr access (fixed outside loop)
00:00:10.5356369, items/sec = 9,492,000.00       IntPtr Misaligned access (fixed outside loop)
00:00:01.6860701, items/sec = 59,311,000.00      Fixed access (fixed inside loop)
00:00:00.7577868, items/sec = 132,100,000.00     float* access (fixed outside loop)
00:00:01.0387792, items/sec = 96,339,000.00      float* access (fixed in loop)

Режим выпуска

Starting 100,000,000 items
00:00:00.7454832, items/sec = 134,228,000.00     Normal access
00:00:00.6619090, items/sec = 151,285,000.00     Normal Ref access
00:00:00.9859089, items/sec = 101,522,000.00     IntPtr access (fixed outside loop)
00:00:10.1289018, items/sec = 9,873,000.00       IntPtr Misaligned access (fixed outside loop)
00:00:00.7899355, items/sec = 126,742,000.00     Fixed access (fixed inside loop)
00:00:00.5718507, items/sec = 175,131,000.00     float* access (fixed outside loop)
00:00:00.6842333, items/sec = 146,198,000.00     float* access (fixed in loop)