【问题标题】:Improve performance of Bitconverter.ToInt16提高 Bitconverter.ToInt16 的性能
【发布时间】:2019-03-26 02:03:17
【问题描述】:

我正在从 USB 设备收集数据,这些数据必须发送到音频输出组件。目前我传递数据的速度不够快,无法避免输出信号中的咔嗒声。所以每一毫秒都很重要。

目前我正在收集以 65536 字节的字节数组传递的数据。前两个字节代表 little endian 格式的 16 位数据。这两个字节必须放在双精度数组的第一个元素中。后两个字节,必须放在不同双精度数组的第一个元素中。然后对 65536 缓冲区中的所有字节重复此操作,最终得到 2 个大小为 16384 的 double[] 数组。

我目前正在使用 BitConverter.ToInt16,如代码所示。运行此程序大约需要 0.3 毫秒,但必须执行 10 次才能将数据包发送到音频输出。所以开销是 3 毫秒,这足以让某些数据包最终无法按时交付。

代码

byte[] buffer = new byte[65536];
double[] bufferA = new double[16384];
double[] bufferB = new double[16384]

for(int i= 0; i < 65536; i +=4)
{
    bufferA[i/4] = BitConverter.ToInt16(buffer, i);
    bufferB[i/4] = BitConverter.ToInt16(buffer, i+2);
}

我该如何改进呢?是否可以使用不安全的代码复制值?我没有这方面的经验。 谢谢

【问题讨论】:

  • 查看BitConverter.ToInt16 源代码,然后删除所有不需要的检查并将fixed 语句退出循环。
  • @MichaelLiu 我不知道C#现在已经开源了。
  • 简短的历史教训:自 2007 年以来,.NET Framework 的参考源一直以一种或另一种形式提供。Michael 链接到的参考源网站在 2014 年进行了重大改进。.NET Core 是.NET 的完全开源版本,于 2016 年首次发布。自 2001 年以来,C# 语言本身就是一个开放标准 (ECMA-334)。所以现在你知道了。 :)

标签: c# unsafe-pointers bitconverter


【解决方案1】:

使用 Pointersunsafe,这让我的发布速度提高了三倍。可能还有其他的微优化,但是我会把这些细节留给大众

更新

我原来的算法有错误,本可以改进

修改后的代码

public unsafe (double[], double[]) Test2(byte[] input, int scale)
{
   var bufferA = new double[input.Length / 4];
   var bufferB = new double[input.Length / 4];

   fixed (byte* pSource = input)
      fixed (double* pBufferA = bufferA, pBufferB = bufferB)
      {
         var pLen = pSource + input.Length;
         double* pA = pBufferA, pB = pBufferB;

         for (var pS = pSource; pS < pLen; pS += 4, pA++, pB++)
         {
            *pA = *(short*)pS;
            *pB = *(short*)(pS + 2);
         }
      }

   return (bufferA, bufferB);
}

基准测试

每个测试运行 1000 次,每次运行前都会收集垃圾,并缩放到不同的数组长度。所有结果均对照原始 OP 版本进行检查

测试环境

----------------------------------------------------------------------------
Mode             : Release (64Bit)
Test Framework   : .NET Framework 4.7.1 (CLR 4.0.30319.42000)
----------------------------------------------------------------------------
Operating System : Microsoft Windows 10 Pro
Version          : 10.0.17134
----------------------------------------------------------------------------
CPU Name         : Intel(R) Core(TM) i7-3770K CPU @ 3.50GHz
Description      : Intel64 Family 6 Model 58 Stepping 9
Cores (Threads)  : 4 (8)      : Architecture  : x64
Clock Speed      : 3901 MHz   : Bus Speed     : 100 MHz
L2Cache          : 1 MB       : L3Cache       : 8 MB
----------------------------------------------------------------------------

结果

--- Random Set of byte ------------------------------------------------------
| Value    |    Average |    Fastest |    Cycles | Garbage | Test |    Gain |
--- Scale 16,384 -------------------------------------------- Time 13.727 ---
| Unsafe   |  19.487 µs |  14.029 µs |  71.479 K | 0.000 B | Pass | 59.02 % |
| Original |  47.556 µs |  34.781 µs | 169.580 K | 0.000 B | Base |  0.00 % |
--- Scale 32,768 -------------------------------------------- Time 14.809 ---
| Unsafe   |  40.398 µs |  31.274 µs | 145.024 K | 0.000 B | Pass | 56.62 % |
| Original |  93.127 µs |  79.501 µs | 329.320 K | 0.000 B | Base |  0.00 % |
--- Scale 65,536 -------------------------------------------- Time 18.984 ---
| Unsafe   |  68.318 µs |  43.550 µs | 245.083 K | 0.000 B | Pass | 68.34 % |
| Original | 215.758 µs | 160.171 µs | 758.955 K | 0.000 B | Base |  0.00 % |
--- Scale 131,072 ------------------------------------------- Time 22.620 ---
| Unsafe   | 120.764 µs |  79.208 µs | 428.626 K | 0.000 B | Pass | 71.24 % |
| Original | 419.889 µs | 322.388 µs |   1.461 M | 0.000 B | Base |  0.00 % |
-----------------------------------------------------------------------------

【讨论】:

  • 基准测试会很有趣。我不确定我对 StopWatch 类的使用在我的代码中是否有价值。
  • 优秀。非常感谢
  • @Tom 没问题,如果你需要字节序代码,它只是在第一个变量 ((*pbyte &lt;&lt; 8) | (*(pbyte + 1)) 上移动,你会从 or'ing 中获得一点好处,而不是加上 *pS | (*(pS+1) &lt;&lt; 8);
  • 另外我认为值得注意的是,可以在没有不安全的情况下实现类似的解决方案,首先将字节复制到new byte[16384 * sizeof(double)],然后使用Buffer.BlockCopy将这些字节复制到双数组中( s) 在最后。
  • @KookieMonster 没有块复制不起作用,因为他需要在 2 个数组之间错开项目。
【解决方案2】:

“所以每一毫秒都很重要。”如果是这种情况,您将在此处处理 Realtime Programming。尽管 .NET 运行时很强大,但它并不适合实时编程。

单独的垃圾回收内存管理通常是disqualifier for Realtime Programming

现在您可以将 .NET 从 GC 内存管理更改为直接管理。并通过使用不安全的代码和使用裸指针来挤出一点性能。但这几乎就是您删除 .NET 的所有卖点的地方。最好是先用原生 C++ 编写整个内容/那部分内容。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-02-19
    • 2012-03-19
    • 1970-01-01
    • 2014-04-18
    • 2014-01-04
    • 2021-10-31
    • 1970-01-01
    相关资源
    最近更新 更多