drowe67 / freedv-gui

GUI Application for FreeDV – open source digital voice for HF radio
https://freedv.org/
GNU Lesser General Public License v2.1
206 stars 52 forks source link

freedv thread priority #160

Closed Tyrbiter closed 3 years ago

Tyrbiter commented 3 years ago

freedv can be heavily affected by building rpms/compiling programs, voice decodes have extreme stuttering and window update rates slow considerably while a build is underway.

Could this be improved by boosting thread priorities of critical threads? I think rpmbuild does a make -j8 on my 8 Core machine but it should be possible to avoid starving important threads of CPU time.

drowe67 commented 3 years ago

@Tyrbiter I'm not sure this is an actual "issue". I don't feel a requirement of freedv-gui should be to perform flawlessly on a heavily loaded general purpose operating system. That feels out of scope to me.

Tyrbiter commented 3 years ago

That's a fair comment, I suppose I'm hoping for the holy grail where all running processes are fairly nice to each other.

I suppose I could look into reducing the compiler priority or at least leave a CPU or two free when building.

tmiw commented 3 years ago

Keep in mind as well that anything done in this regard would likely be platform dependent. I know for sure that there's a Apple-specific way to grant "real time" priority to macOS threads, for instance. Maybe some comparisons with other ham radio apps would be helpful here (for instance, try to run WSJT-X under the same scenario and see FT8, etc. have issues).

Tyrbiter commented 3 years ago

I think it will be other modes where continuous decoding is needed, so speech where adding latency is bad (like freedv) rather than WSJT-X where most (all?) of the modes store data in a buffer or file and then process it at the end of an rx cycle.

I don't notice any effect on WSPR when running a build on the same machine for instance.

I completely accept your comment about platform dependency, it's always more difficult to make anything run happily on a range of hardware/OS. where such constraints are required (or desired).

tmiw commented 3 years ago

Assuming we were to change thread priorities, though, I'm wondering what would need to be bumped up. Presumably PortAudio already uses RT priorities for its own stuff, which leaves the following possibilities:

Based on the number that seem to remain, I'm not sure if there'd be any difference bumping all of them up. Actually, does the following improve things at all for you?

renice -n 0 -p `pidof freedv`
Tyrbiter commented 3 years ago

I will give that a try when I get a moment.

I suppose that profiling the threads would help to decide what needs a priority boost, but I don't know how to do that myself.

Tyrbiter commented 3 years ago

Tried the renice suggestion, this is the result:

364026 (process ID) old priority 0, new priority 0

Looks like there is no way of improving this easily.

tmiw commented 3 years ago

@Tyrbiter, ah, figured it was worth a shot. (BTW, as root/for apps running as root I believe you can go negative on the nice value. That's not a sustainable solution, though.)

As for profiling, when I worked on https://github.com/drowe67/codec2/pull/200, the UI thread seemed to take the most CPU (in terms of percentage of the entire binary's CPU usage), followed by the decode thread for 700C.

Some more info on real-time scheduling:

+#if defined(__APPLE__)
+#include <mach/mach.h>
+#include <mach/mach_time.h>
+#include <mach/thread_policy.h>
+
+static void set_macos_rt_priority(mach_port_t mach_thread_id)
+{
+  kern_return_t result;
+
+  // Increase thread priority to real-time.
+
+  // Please note that the thread_policy_set() calls may fail in
+  // rare cases if the kernel decides the system is under heavy load
+  // and is unable to handle boosting the thread priority.
+  // In these cases we just return early and go on with life.
+
+  // Make thread fixed priority.
+  thread_extended_policy_data_t policy;
+  policy.timeshare = 0;  // Set to 1 for a non-fixed thread.
+  result = thread_policy_set(mach_thread_id,
+                             THREAD_EXTENDED_POLICY,
+                             (thread_policy_t)&policy,
+                             THREAD_EXTENDED_POLICY_COUNT);
+  if (result != KERN_SUCCESS) {
+    fprintf(stderr, "thread_policy_set() failure: %d", result);
+    return;
+  }
+
+  // Set to relatively high priority.
+  thread_precedence_policy_data_t precedence;
+  precedence.importance = 63;
+  result = thread_policy_set(mach_thread_id,
+                             THREAD_PRECEDENCE_POLICY,
+                             (thread_policy_t)&precedence,
+                             THREAD_PRECEDENCE_POLICY_COUNT);
+  if (result != KERN_SUCCESS) {
+    fprintf(stderr, "thread_policy_set() failure: %d", result);
+    return;
+  }
+
+  // Most important, set real-time constraints.
+
+  // Define the guaranteed and max fraction of time for the audio thread.
+  // These "duty cycle" values can range from 0 to 1.  A value of 0.5
+  // means the scheduler would give half the time to the thread.
+  // These values have empirically been found to yield good behavior.
+  // Good means that audio performance is high and other threads won't starve.
+  const double kGuaranteedAudioDutyCycle = 0.75;
+  const double kMaxAudioDutyCycle = 0.85;
+
+  // Define constants determining how much time the audio thread can
+  // use in a given time quantum.  All times are in milliseconds.
+
+  // About 128 frames @44.1KHz
+  const double kTimeQuantum = 2.9;
+
+  // Time guaranteed each quantum.
+  const double kAudioTimeNeeded = kGuaranteedAudioDutyCycle * kTimeQuantum;
+
+  // Maximum time each quantum.
+  const double kMaxTimeAllowed = kMaxAudioDutyCycle * kTimeQuantum;
+
+  // Get the conversion factor from milliseconds to absolute time
+  // which is what the time-constraints call needs.
+  mach_timebase_info_data_t tb_info;
+  mach_timebase_info(&tb_info);
+  double ms_to_abs_time =
+      ((double)tb_info.denom / (double)tb_info.numer) * 1000000;
+
+  thread_time_constraint_policy_data_t time_constraints;
+  time_constraints.period = kTimeQuantum * ms_to_abs_time;
+  time_constraints.computation = kAudioTimeNeeded * ms_to_abs_time;
+  time_constraints.constraint = kMaxTimeAllowed * ms_to_abs_time;
+  time_constraints.preemptible = 0;
+
+  result = thread_policy_set(mach_thread_id,
+                             THREAD_TIME_CONSTRAINT_POLICY,
+                             (thread_policy_t)&time_constraints,
+                             THREAD_TIME_CONSTRAINT_POLICY_COUNT);
+  if (result != KERN_SUCCESS)
+    fprintf(stderr, "thread_policy_set() failure: %d", result);
+
+  return;
+}
+#endif
+
drowe67 commented 3 years ago

I don't notice any effect on WSPR when running a build on the same machine for instance.

Interesting, but I still feel there is no requirement that freedv-gui needs to operate on a heavily loaded machine. Fun to work on for interest, and we might learn something, but probably belongs in Discussions rather than as an Issue.

Tyrbiter commented 3 years ago

Running on a general purpose PC is a long way from an optimised embedded task, so I agree that this is not worth expending any more effort on. Better to get codec2 integrated into real radios.

tmiw commented 3 years ago

@drowe67, we might want to add a Discussions tab to freedv-gui as well as with codec2. That way, we can add this discussion there and close this issue out.

drowe67 commented 3 years ago

This is a discussion rather than an issue so moved to https://github.com/drowe67/freedv-gui/discussions/161