C# 禁用 USB ReadPipe 的垃圾回收
C# Disable garbage collection for USB ReadPipe
我正在尝试使用来自 FTDI 的 D3XX.NET 从 USB 端口收集数据。收集数据,然后将其发送到快速傅里叶变换以绘制频谱。这工作正常,即使你错过了一些数据。你不知道。但是,如果您随后想将此数据发送到音频输出组件,您会注意到数据丢失。这就是我的问题所在。
收集数据,然后将其发送到音频设备。所有数据包都在所需的时间跨度内完成。但是,音频正在丢弃它出现的数据。这是音频输出端正弦波的图片:
您可以看到开始时缺少一些数据,并且似乎在接近尾声时缺少整个循环。这只是一个例子,它一直在变化。有时似乎数据不存在。
我已经完成了整个处理链,我很确定声音的数据包正在生成。
从那以后,我使用了 JetBrains 性能分析器。我发现如下: ReadPipe 方法需要 8.5 毫秒,这正是您期望的读取时间。到目前为止,一切都很好。 ReadPipe 命令完成后,您有 0.5 毫秒的时间执行另一个 ReadPipe,否则您将丢失一些数据。查看探查器输出我看到了这个:
ReadPipe 需要 8.5 毫秒,然后有这个条目用于垃圾收集,平均需要 1.6 毫秒。如果这确实偶尔发生,那么我丢失了一些数据。
所以这是代码:它是一个后台工作者:
private void CollectData(object sender, DoWorkEventArgs e)
{
while (keepGoing)
{
ftStatus = d3xxDevice.ReadPipe(0x84, iqBuffer, 65536, ref bytesTransferred); //read IQ data - will get 1024 pairs - 2 bytes per value
_waitForData.Set();
}
}
等待句柄向另一个线程表明数据可用。
那么GC是数据丢失的原因吗?如果是这样,我该如何避免这种情况?
谢谢!
你需要对你的垃圾收集器友好一点,不要分配那么多。
简而言之,如果您的 GC
正在拖延您的线程,那么您遇到了垃圾问题。 GC 将暂停所有线程以进行清理,除了更好地管理您创建的垃圾之外,您无能为力。
如果您有数组,请不要不断创建它们,而是重复使用它们(依此类推)。使用更轻量级的结构,使用可以减少分配的工具,例如 Span<T>
和 Memory<T>
。如果您的代码有大量 async
,请考虑使用 less awaits
,并且不要将它们放入循环中。通过 ref
并使用 ref locals 等,如果可以的话,也要远离大型非托管数据块。
此外,在无关紧要的任何停机时间调用 GC.Collect
可能会有所帮助,但更好的设计可能会更有益。
如果您可以确认您没有 运行内存不足,您可以尝试将 GCSettings.LatencyMode
设置为 GCLatencyMode.SustainedLowLatency
。这将防止发生某些阻塞垃圾收集,除非您的内存不足。查看 docs on latency modes 了解更多详细信息和限制。
如果垃圾收集对于您的用例来说仍然太具有破坏性并且您使用的是 .NET 4.6 或更高版本,您可以尝试调用 GC.TryStartNoGCRegion。此方法将尝试保留足够的内存以分配指定的数量,并阻止 GC 直到您用完保留。如果您的内存使用情况相当一致,您或许可以通过传入足够大的值来满足您的应用程序的使用情况,但不能保证调用会成功。
如果您使用的是不支持其中任何一个的旧版 .NET,您可能就不走运了。如果这是一个 GUI 应用程序(从事件处理程序来看它看起来像),则您对分配没有足够的控制权。
另一件需要考虑的事情是,对于不能容忍中断的应用程序,C# 并不是真正合适的工具。如果您熟悉编写本机代码,则可以在非托管线程上执行时间敏感的工作;据我所知,这是唯一可靠的解决方案,尤其是当您的应用程序要 运行 在最终用户机器上时。
我正在尝试使用来自 FTDI 的 D3XX.NET 从 USB 端口收集数据。收集数据,然后将其发送到快速傅里叶变换以绘制频谱。这工作正常,即使你错过了一些数据。你不知道。但是,如果您随后想将此数据发送到音频输出组件,您会注意到数据丢失。这就是我的问题所在。 收集数据,然后将其发送到音频设备。所有数据包都在所需的时间跨度内完成。但是,音频正在丢弃它出现的数据。这是音频输出端正弦波的图片:
您可以看到开始时缺少一些数据,并且似乎在接近尾声时缺少整个循环。这只是一个例子,它一直在变化。有时似乎数据不存在。 我已经完成了整个处理链,我很确定声音的数据包正在生成。 从那以后,我使用了 JetBrains 性能分析器。我发现如下: ReadPipe 方法需要 8.5 毫秒,这正是您期望的读取时间。到目前为止,一切都很好。 ReadPipe 命令完成后,您有 0.5 毫秒的时间执行另一个 ReadPipe,否则您将丢失一些数据。查看探查器输出我看到了这个:
ReadPipe 需要 8.5 毫秒,然后有这个条目用于垃圾收集,平均需要 1.6 毫秒。如果这确实偶尔发生,那么我丢失了一些数据。
所以这是代码:它是一个后台工作者:
private void CollectData(object sender, DoWorkEventArgs e)
{
while (keepGoing)
{
ftStatus = d3xxDevice.ReadPipe(0x84, iqBuffer, 65536, ref bytesTransferred); //read IQ data - will get 1024 pairs - 2 bytes per value
_waitForData.Set();
}
}
等待句柄向另一个线程表明数据可用。 那么GC是数据丢失的原因吗?如果是这样,我该如何避免这种情况? 谢谢!
你需要对你的垃圾收集器友好一点,不要分配那么多。
简而言之,如果您的 GC
正在拖延您的线程,那么您遇到了垃圾问题。 GC 将暂停所有线程以进行清理,除了更好地管理您创建的垃圾之外,您无能为力。
如果您有数组,请不要不断创建它们,而是重复使用它们(依此类推)。使用更轻量级的结构,使用可以减少分配的工具,例如 Span<T>
和 Memory<T>
。如果您的代码有大量 async
,请考虑使用 less awaits
,并且不要将它们放入循环中。通过 ref
并使用 ref locals 等,如果可以的话,也要远离大型非托管数据块。
此外,在无关紧要的任何停机时间调用 GC.Collect
可能会有所帮助,但更好的设计可能会更有益。
如果您可以确认您没有 运行内存不足,您可以尝试将 GCSettings.LatencyMode
设置为 GCLatencyMode.SustainedLowLatency
。这将防止发生某些阻塞垃圾收集,除非您的内存不足。查看 docs on latency modes 了解更多详细信息和限制。
如果垃圾收集对于您的用例来说仍然太具有破坏性并且您使用的是 .NET 4.6 或更高版本,您可以尝试调用 GC.TryStartNoGCRegion。此方法将尝试保留足够的内存以分配指定的数量,并阻止 GC 直到您用完保留。如果您的内存使用情况相当一致,您或许可以通过传入足够大的值来满足您的应用程序的使用情况,但不能保证调用会成功。
如果您使用的是不支持其中任何一个的旧版 .NET,您可能就不走运了。如果这是一个 GUI 应用程序(从事件处理程序来看它看起来像),则您对分配没有足够的控制权。
另一件需要考虑的事情是,对于不能容忍中断的应用程序,C# 并不是真正合适的工具。如果您熟悉编写本机代码,则可以在非托管线程上执行时间敏感的工作;据我所知,这是唯一可靠的解决方案,尤其是当您的应用程序要 运行 在最终用户机器上时。