Более быстрый способ поменять порядковый номер в C # с 16-битными словами - PullRequest
10 голосов
/ 23 октября 2009

Должен быть более быстрый и лучший способ поменять байты 16-битных слов, чем этот .:

public static void Swap(byte[] data)
{
    for (int i = 0; i < data.Length; i += 2)
    {
        byte b = data[i];
        data[i] = data[i + 1];
        data[i + 1] = b;
    }
}

У кого-нибудь есть идея?

Ответы [ 5 ]

7 голосов
/ 17 декабря 2011

Пытаясь подать заявку на награду Uberhacker, я сообщаю следующее. Для моего тестирования я использовал массив Source размером 8 192 байта и вызывал SwapX2 100 000 раз:

public static unsafe void SwapX2(Byte[] source)  
{  
    fixed (Byte* pSource = &source[0])  
    {  
        Byte* bp = pSource;  
        Byte* bp_stop = bp + source.Length;  

        while (bp < bp_stop)  
        {
            *(UInt16*)bp = (UInt16)(*bp << 8 | *(bp + 1));  
            bp += 2;  
        }  
    }  
}

Мой сравнительный анализ показывает, что эта версия более чем в 1,8 раза быстрее, чем код, представленный в исходном вопросе.

6 голосов
/ 23 октября 2009

Этот способ выглядит немного быстрее, чем метод из исходного вопроса:

private static byte[] _temp = new byte[0];
public static void Swap(byte[] data)
{
    if (data.Length > _temp.Length)
    {
        _temp = new byte[data.Length];
    }
    Buffer.BlockCopy(data, 1, _temp, 0, data.Length - 1);
    for (int i = 0; i < data.Length; i += 2)
    {
        _temp[i + 1] = data[i];
    }
    Buffer.BlockCopy(_temp, 0, data, 0, data.Length);
}

Мой бенчмаркинг предполагал, что метод вызывается неоднократно, поэтому изменение размера массива _temp не является фактором. Этот метод основан на том факте, что половину замены байтов можно выполнить с помощью начального вызова Buffer.BlockCopy(...) (со смещением позиции источника на 1).

Пожалуйста, сравните это сами, если я полностью сошел с ума. В моих тестах этот метод занимал примерно 70% до тех пор, пока исходный метод (который я изменил, чтобы объявить byte b вне цикла).

4 голосов
/ 16 декабря 2011

Мне всегда нравилось это:

public static Int64 SwapByteOrder(Int64 value) 
{
  var uvalue = (UInt64)value;
  UInt64 swapped = 
       ( (0x00000000000000FF) & (uvalue >> 56)
       | (0x000000000000FF00) & (uvalue >> 40)
       | (0x0000000000FF0000) & (uvalue >> 24)
       | (0x00000000FF000000) & (uvalue >> 8)
       | (0x000000FF00000000) & (uvalue << 8)
       | (0x0000FF0000000000) & (uvalue << 24)
       | (0x00FF000000000000) & (uvalue << 40)
       | (0xFF00000000000000) & (uvalue << 56));
  return (Int64)swapped;
}

Я полагаю, вы найдете, что это самый быстрый метод, а также достаточно читабелен и безопасен. Очевидно, что это относится к 64-битным значениям, но тот же метод может быть использован для 32- или 16-.

2 голосов
/ 12 марта 2018

Следующий метод, в моем тесте, почти в 3 раза быстрее, чем принятый ответ. (Всегда быстрее для более чем 3 символов или шести байтов, немного медленнее для менее или равного трех символов или шести байтов.) ( Обратите внимание, что принятый ответ может считываться / записываться за пределами массива. )

(Обновление При наличии указателя нет необходимости вызывать свойство для получения длины. Использование этого указателя немного быстрее, но требует либо проверки во время выполнения, либо, как в следующем примере, конфигурации проекта для построения для каждой платформы Определите X86 и X64 для каждой конфигурации.)

static unsafe void SwapV2(byte[] source)
{
    fixed (byte* psource = source)
    {
#if X86
        var length = *((uint*)(psource - 4)) & 0xFFFFFFFEU;
#elif X64
        var length = *((uint*)(psource - 8)) & 0xFFFFFFFEU;
#else
        var length = (source.Length & 0xFFFFFFFE);
#endif
        while (length > 7)
        {
            length -= 8;
            ulong* pulong = (ulong*)(psource + length);
            *pulong = ( ((*pulong >> 8) & 0x00FF00FF00FF00FFUL)
                      | ((*pulong << 8) & 0xFF00FF00FF00FF00UL));
        }
        if(length > 3)
        {
            length -= 4;
            uint* puint = (uint*)(psource + length);
            *puint = ( ((*puint >> 8) & 0x00FF00FFU)
                     | ((*puint << 8) & 0xFF00FF00U));
        }
        if(length > 1)
        {
            ushort* pushort = (ushort*)psource;
            *pushort = (ushort) ( (*pushort >> 8)
                                | (*pushort << 8));
        }
    }
}

Пять тестов с 300.000 раз 8192 байта

  • SwapV2: 1055, 1051, 1043, 1041, 1044
  • SwapX2: 2802, 2803, 2803, 2805, 2805

Пять тестов с 50.000.000 раз 6 байтов

  • SwapV2: 1092, 1085, 1086, 1087, 1086
  • SwapX2: 1018, 1019, 1015, 1017, 1018

Но если данные большие и производительность действительно имеет значение, вы можете использовать SSE или AVX. (В 13 раз быстрее.) https://pastebin.com/WaFk275U

Тест 5 раз, 100000 циклов с 8192 байтами или 4096 символами

  • SwapX2: 226, 223, 225, 226, 227, мин: 223
  • SwapV2: 113, 111, 112, 114, 112 Мин: 111
  • SwapA2: 17, 17, 17, 17, 16 мин: 16
1 голос
/ 23 октября 2009

Ну, вы могли бы использовать XOR-трюк подкачки , чтобы избежать промежуточного байта. Это не будет быстрее, и я не удивлюсь, если IL точно такой же.

for (int i = 0; i < data.Length; i += 2)
{
    data[i] ^= data[i + 1];
    data[i + 1] ^= data[i];
    data[i] ^= data[i + 1];
}
...