【问题标题】:Optimize C# Code Fragment优化 C# 代码片段
【发布时间】:2011-11-12 01:39:36
【问题描述】:

我正在分析一些 C# 代码。下面的方法是最昂贵的方法之一。出于这个问题的目的,假设微优化是正确的做法。有没有办法提高这种方法的性能?

将输入参数从p 更改为ulong[] 会导致宏效率低下。

static ulong Fetch64(byte[] p, int ofs = 0)
{
    unchecked
    {
        ulong result = p[0 + ofs] + 
            ((ulong) p[1 + ofs] <<  8) + 
            ((ulong) p[2 + ofs] << 16) + 
            ((ulong) p[3 + ofs] << 24) + 
            ((ulong) p[4 + ofs] << 32) + 
            ((ulong) p[5 + ofs] << 40) + 
            ((ulong) p[6 + ofs] << 48) + 
            ((ulong) p[7 + ofs] << 56);
        return result;
    }
}

【问题讨论】:

  • 看起来像 BitConverter.ToInt64 - msdn.microsoft.com/en-us/library/… ?
  • 读取并移动几个字节——这真的很贵吗?我敢肯定你说的很多,但如果编译器可能会出错,我会感到惊讶
  • @Alexei ToUInt64,但是是的。如果您的意思是使用它,请将其作为答案发布? (或者 Eric 甚至想优化 BitConverter?)
  • @Rup:BitConverter 比我的代码快很多

标签: c# micro-optimization unchecked


【解决方案1】:

为什么不使用 BitConverter?我必须相信微软已经花了一些时间调整该代码。此外,它还处理字节序问题。

BitConverter 如何将 byte[] 转换为 long/ulong(ulong 将其转换为有符号,然后将其转换为无符号):

[SecuritySafeCritical]
public static unsafe long ToInt64(byte[] value, int startIndex)
{
  if (value == null)
  {
    ThrowHelper.ThrowArgumentNullException(ExceptionArgument.value);
  }
  if (((ulong) startIndex) >= value.Length)
  {
    ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.startIndex, ExceptionResource.ArgumentOutOfRange_Index);
  }
  if (startIndex > (value.Length - 8))
  {
    ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
  }
  fixed (byte* numRef = &(value[startIndex]))
  {
    if ((startIndex % 8) == 0)
    {
      return *(((long*) numRef));
    }
    if (IsLittleEndian)
    {
      int num  = ((numRef[0] | (numRef[1] << 8)) | (numRef[2] << 0x10)) | (numRef[3] << 0x18);
      int num2 = ((numRef[4] | (numRef[5] << 8)) | (numRef[6] << 0x10)) | (numRef[7] << 0x18);
      return (((long) ((ulong) num)) | (num2 << 0x20));
    }
    int num3 = (((numRef[0] << 0x18) | (numRef[1] << 0x10)) | (numRef[2] << 8)) | numRef[3];
    int num4 = (((numRef[4] << 0x18) | (numRef[5] << 0x10)) | (numRef[6] << 8)) | numRef[7];
    return (((long) ((ulong) num4)) | (num3 << 0x20));
  }
}

我怀疑一次转换一个 32 位字是为了提高 32 位效率。 32 位 CPU 上没有 64 位寄存器意味着处理 64 位整数的成本要高得多。

如果您确定您的目标是 64 位硬件,那么一口气完成转换可能会更快。

【讨论】:

  • 另外值得注意的是,这是使用不安全的代码来提高性能;似乎 BCL 方法是最好的选择。
  • 天啊!今天在不同的环境中使用了 BitConverter,并没有想到这一点。顺便说一句,这种优化将我的 CityHash C# 端口的整体性能提高了 30%(现在比我移植的 C++ 版本快 28%)。
【解决方案2】:

尝试使用for 而不是展开循环。您也许可以节省边界检查的时间。

试试 BitConverter.ToUInt64 - http://msdn.microsoft.com/en-us/library/system.bitconverter.touint64.aspx,如果它是你想要的。

【讨论】:

    【解决方案3】:

    作为参考,Microsoft 的 .NET 4.0 BitConverter.ToInt64(共享源计划http://referencesource.microsoft.com/netframework.aspx):

        // Converts an array of bytes into a long.
        [System.Security.SecuritySafeCritical]  // auto-generated 
        public static unsafe long ToInt64 (byte[] value, int startIndex) {
            if( value == null)  {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.value);
            } 
    
            if ((uint) startIndex >= value.Length) { 
                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.startIndex, ExceptionResource.ArgumentOutOfRange_Index); 
            }
    
            if (startIndex > value.Length -8) {
                ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
            }
    
            fixed( byte * pbyte = &value[startIndex]) {
                if( startIndex % 8 == 0) { // data is aligned 
                    return *((long *) pbyte); 
                }
                else { 
                    if( IsLittleEndian) {
                        int i1 = (*pbyte) | (*(pbyte + 1) << 8)  | (*(pbyte + 2) << 16) | (*(pbyte + 3) << 24);
                        int i2  = (*(pbyte+4)) | (*(pbyte + 5) << 8)  | (*(pbyte + 6) << 16) | (*(pbyte + 7) << 24);
                        return (uint)i1 | ((long)i2 << 32); 
                    }
                    else { 
                        int i1 = (*pbyte << 24) | (*(pbyte + 1) << 16)  | (*(pbyte + 2) << 8) | (*(pbyte + 3)); 
                        int i2  = (*(pbyte+4) << 24) | (*(pbyte + 5) << 16)  | (*(pbyte + 6) << 8) | (*(pbyte + 7));
                        return (uint)i2 | ((long)i1 << 32); 
                    }
                }
            }
        } 
    

    【讨论】:

      【解决方案4】:

      为什么不变得不安全?

      unsafe static ulong Fetch64(byte[] p, int ofs = 0)
      {
        fixed (byte* bp = p)
        {
          return *((ulong*)(bp + ofs));
        }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-11-13
        • 1970-01-01
        • 2013-01-17
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多