提高BitConverter.ToInt16的性能

6
我正在从USB设备收集数据,并且这些数据必须发送到音频输出组件。目前,我没有及时传递数据以避免输出信号中的杂音。所以每一毫秒都很重要。
目前,我正在收集以65536字节的字节数组形式提供的数据。前两个字节以小端格式表示16位数据。这两个字节必须放置在双精度数组的第一个元素中。接下来的两个字节必须放置在不同双精度数组的第一个元素中。然后对65536缓冲区中的所有字节重复此操作,以便您最终得到大小为16384的2个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);
}

我该如何改进这个?有没有可能使用不安全的代码来复制值?我对此毫无经验。谢谢。


2
看一下BitConverter.ToInt16的源代码,然后移除你不需要的所有检查,并将fixed语句从循环中提取出来。 - Michael Liu
@MichaelLiu,我不知道C#现在已经成为开源了。 - Chetan Mehra
1
简短的历史课:.NET Framework 的参考源自 2007 年以来一直以某种形式可用。Michael 链接的参考源网站在 2014 年进行了重大改进。.NET Core 是 .NET 的完全开源版本,于 2016 年首次发布。C# 语言本身自 2001 年起就是一个开放标准(ECMA-334)。现在你知道了。 :) - Matt Johnson-Pint
2个回答

4
这让我在发布时的速度提高了三倍,使用指针unsafe。可能还有其他微小的优化,但这些细节留给大众去决定。

更新

我的原始算法存在一个错误,并且可以进行改进。

修改后的代码

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

@KookieMonster 不行,块复制不起作用,因为他需要在两个数组之间交错项目。 - TheGeneral
嗨,现在我尝试了一下,它不起作用。从中得出的频谱根本不是应该的样子。也许我弄错了字节序部分。我还输入了 *pA = (*pS++ << 8) + *pS++ ; *pB = (*pS++ << 8) + *pS++ ;但它也没有起作用。目标数组是双精度的。我看不出有任何逻辑? - Tom
@Tom 好的,我会在上班时看一下这个问题,说实话我还没有测试过它。 - TheGeneral
工作得很好,非常感谢。不幸的是,代码的其他部分仍然存在瓶颈问题,我无法追踪到。 - Tom
由于这并没有帮助我的应用程序实时性能,我认为我可能已经找到了我的问题所在。我在这里发布了这个问题:https://dev59.com/iq_la4cB1Zd3GeqPqknE - Tom
显示剩余10条评论

-3
"所以每个毫秒都很重要。" 如果是这样,你正在处理 实时编程。而且尽管它功能强大,.NET运行时不太适合实时编程。
仅仅是垃圾回收内存管理通常就已经成为不适合实时编程的理由
现在你可以将.NET从GC内存管理更改为直接管理。并通过转向不安全代码和使用裸指针来挤出一些性能。但那几乎是你去除.NET所有卖点的地方了。最好还是一开始就用本地C++编写整个程序/部分程序。"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接