4

I am attempting to collect data from a USB port using D3XX.NET from FTDI. The data is collected and then sent to a fast fourier transform for plotting a spectrum. This works fine, even if you miss some data. You can't tell. However, if you then want to send this data to an audio output component, you will notice data missing. This is where my problem appears to be. The data is collected and then sent to the audio device. All packets are making it within the time span needed. However, the audio is dropping data it appears. Here is a picture of what a sine wave looks like at the output of the audio:

enter image description here

You can see that some data is missing at the beginning and it seems a whole cycle is missing near the end. This is just one example, it changes all the time. Sometimes it appears that the data is just not there. I have gone through the whole processing chain and i'm pretty sure the data packets for the sound are making it. I have since used JetBrains performance profiler. What I have found is the following: The ReadPipe method takes 8.5ms which is exactly what you expect the read to take. So far so good. Once the ReadPipe command is finished, you have 0.5ms to do another ReadPipe or you will loose some data. Looking at the profiler output I see this:

enter image description here

The ReadPipe takes 8.5ms and then there is this entry for garbage collection which on average takes 1.6ms. If this is indeed occurring even occasionally, then I have lost some data.

So here is the code: It is a backgroundworker:

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

The waithandle signifies to the other thread that data is available. So is the GC the cause of the lost data? And if so, how can I avoid this? Thanks!

Tom
  • 527
  • 1
  • 8
  • 28

2 Answers2

4

If you can confirm that you aren't running out of memory, you could try setting GCSettings.LatencyMode to GCLatencyMode.SustainedLowLatency. This will prevent certain blocking garbage collections from occurring, unless you're low on memory. Check out the docs on latency modes for more details and restrictions.

If garbage collection is still too disruptive for your use case and you're using .NET 4.6 or later, you may be able to try calling GC.TryStartNoGCRegion. This method will attempt to reserve enough memory to allocate up to the amount specified, and block GC until you've exhausted the reservation. If your memory usage is fairly consistent, you might be able to get away with passing in a large enough value to accommodate your application's usage, but there's no guarantee that the call will succeed.

If you're on an older version of .NET that doesn't support either of these, you're probably out of luck. If this is a GUI application (which it looks like, judging by the event handler), you don't have enough control over allocations.

Another thing to consider is that C# isn't really the right tool for applications that can't tolerate disruptions. If you're familiar with writing native code, you could perform your time sensitive work on an un-managed thread; as far as I'm aware, this is the only reliable solution, especially if your application is going to run on end-user machines.

Collin Dauphinee
  • 13,664
  • 1
  • 40
  • 71
  • Hi Thanks for this. I am using .Net 4.0. I tried changing the GC to lowLatency and also tried the ReadPipeAsync method the library provides and it did not make any difference. Now, the D3XX.NET library is just a C# wrapper for a bunch of unmanaged code. So is the idea to create my thread as au unmanaged thread and use the code in there? – Tom Oct 24 '18 at 00:20
  • Yes, you'll have to create your own unmanaged library with a function that spawns a background thread to do whatever it is you need, then P/Invoke into it. Unmanaged threads won't be paused during garbage collection. This is fairly complicated, though. – Collin Dauphinee Oct 24 '18 at 22:34
  • I posted a follow up on how to do this – Tom Oct 31 '18 at 01:32
2

You need to be friendlier to your garbage collector and not allocate so much.

In short, if your GC is stalling your threads, you have a garbage problem. The GC will pause all threads to do a clean up and there is nothing you can really do apart form better management of what garbage you create.

If you have arrays, don't keep creating them constantly, instead reuse them (so on and so forth). Use lighter weight structures, use tools which allow you to reduce allocations like Span<T> and Memory<T>. Consider using less awaits if your code is heavily async, and don't put them in loops. Pass by ref and use ref locals and such, also stay away from large unmanaged data blocks if you can.

Also, it might be beneficial to call GC.Collect in any down time when it wont matter, though better design will likely be more beneficial.

TheGeneral
  • 79,002
  • 9
  • 103
  • 141
  • There are a 10's of thousands of lines in the code. Not really possible for this specific case. So, does the profiler indicate that this thread is blocking during the GC? And if so, how to prevent it? – Tom Oct 23 '18 at 05:06
  • @tom indeed, it does seem to be saying that garbage collection is blocking for 150 ms, which is a long time in audio land. there maybe a couple of tricks you can do to gives hints to the garbage collection, maybe someone else can chime in here and give some advice. – TheGeneral Oct 23 '18 at 05:08
  • That is the total time 149ms for 89 calls. It is called 89 times during the 4207 times that the ReadPipe has occured. It amounts to 1.4ms each time it is called. This must be the source of the glitches. – Tom Oct 23 '18 at 05:13
  • @Tom check out this link https://stackoverflow.com/questions/9669963/monitoring-garbage-collector-in-c-sharp , without seeing your solution and the profiling results its hard to tell, but this might give oyu a better indication if its a problem – TheGeneral Oct 23 '18 at 05:16