أسرع طريقة مبادلة endianness في C# مع 16 بت الكلمات

StackOverflow https://stackoverflow.com/questions/1610868

  •  05-07-2019
  •  | 
  •  

سؤال

هناك يجب أن يكون أسرع و أفضل طريقة تبديل بايت من 16bit الكلمات ثم هذا.:

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

هل من أحد لديه فكرة ؟

هل كانت مفيدة؟

المحلول

في محاولة مني لتقديم طلب للحصول على جائزة Uberhacker، أقدم فيما يلي. لاختبار بلدي، وأنا استخدم مجموعة مصدر 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 مرات أسرع من التعليمات البرمجية المقدمة في السؤال الأصلي.

نصائح أخرى

وبهذه الطريقة يبدو أن أسرع قليلا من الأسلوب في السؤال الأصلي:

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 خارج حلقة).

وأنا دائما أحب هذا:

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 -.

الطريقة التالية في اختبار ما يقرب من 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

حسنا، يمكنك استخدام 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];
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top