mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-07 08:36:02 -10:00
video_filter: size the Automatic worker count from spare cores
RARCH_SOFTFILTER_THREADS_AUTO gave a filter one worker per logical CPU. That is wrong twice over: the pool competes with the emulation, video, audio and task threads for the cores those need, and the light filters get slower as workers are added (EPX at 256x224 measured ~26% slower on eight workers than on one; LQ2x and 2xSaI lose their four-worker gain at eight). A filter sits on the frame's critical path - every worker must finish before the frame presents - so a few unhindered workers beat many contended ones. Automatic now resolves as: - physical cores the process may use (sthread_get_core_topology, or cpu_features_get_core_amount_physical where there is no topology), - minus the reserved frame-critical threads: the emulation thread, plus the video, audio and task threads where each runs on its own thread (video_driver_init_filter tells the filter the count), - minus one of headroom for the GPU driver and the OS once above two, - capped at RARCH_SOFTFILTER_AUTO_MAX (8), - then capped by the plugin's workload: light filters (epx, lq2x, crop_borders, darken) run alone, medium ones (2xsai, super2xsai, supereagle, phosphor2x) stop at four, heavy ones (ntsc, ntsc_crt, 2xbr, blargg_ntsc_snes, and any plugin not in the table) take the budget. On a 4-core with everything threaded that is one worker; on a 16-core X3D with everything threaded, 8 for the heavy filters, 4 for the medium, 1 for the light. An explicit Video Filter Threads value is passed through untouched, as before. The arithmetic is rarch_softfilter_auto_budget(); filter_lifecycle checks it across eleven machine/reserve/plugin combinations and that the live answer stays within bounds.
This commit is contained in:
1 parent
050d0a92c2
commit
df96c5f5d6
5 files changed
+157
-3
No files matched your search
@@ -2369,6 +2369,22 @@ void video_driver_init_filter(enum retro_pixel_format colfmt_int,
|
||||
return;
|
||||
}
|
||||
|
||||
/* Cores the frame-critical threads hold, for the Automatic worker
|
||||
* count: the emulation thread, plus each pipeline that runs on a
|
||||
* thread of its own. */
|
||||
{
|
||||
unsigned reserved = 1;
|
||||
#ifdef HAVE_THREADS
|
||||
if (VIDEO_DRIVER_IS_THREADED_INTERNAL(video_st))
|
||||
reserved++;
|
||||
if (settings->bools.audio_threaded_pipeline)
|
||||
reserved++;
|
||||
if (settings->bools.threaded_data_runloop_enable)
|
||||
reserved++;
|
||||
#endif
|
||||
rarch_softfilter_set_auto_reserved(reserved);
|
||||
}
|
||||
|
||||
if (!(video_st->state_filter = rarch_softfilter_new(
|
||||
settings->paths.path_softfilter_plugin,
|
||||
settings->uints.video_filter_threads, colfmt, dims)))
|
||||
|
||||
+78
-2
@@ -154,6 +154,82 @@ static const struct softfilter_config softfilter_config = {
|
||||
config_userdata_free,
|
||||
};
|
||||
|
||||
/* RARCH_SOFTFILTER_THREADS_AUTO: how many workers a filter gets when
|
||||
* the user has not said.
|
||||
*
|
||||
* The old answer, one per logical CPU, is wrong twice over: the pool
|
||||
* competes with the emulation, video, audio and task threads for the
|
||||
* cores those need, and the light filters get slower as workers are
|
||||
* added (EPX at 256x224 was ~26% slower on eight workers than one).
|
||||
* A filter is on the frame's critical path - every worker has to
|
||||
* finish before the frame can present - so fewer, unhindered workers
|
||||
* beat many contended ones.
|
||||
*
|
||||
* Budget: physical cores the process may use, minus the reserved
|
||||
* frame-critical threads, minus one for the GPU driver and the OS
|
||||
* once there is room to spare, capped at RARCH_SOFTFILTER_AUTO_MAX
|
||||
* and never below one. Then a per-filter ceiling by workload: the
|
||||
* light filters run best alone, the medium ones stop gaining at four,
|
||||
* only the heavy ones (NTSC, NTSC/CRT, 2xBR) use the whole budget. A
|
||||
* plugin this table does not know counts as heavy. */
|
||||
static unsigned softfilter_auto_reserved = 1;
|
||||
|
||||
void rarch_softfilter_set_auto_reserved(unsigned reserved_cores)
|
||||
{
|
||||
softfilter_auto_reserved = reserved_cores;
|
||||
}
|
||||
|
||||
static unsigned softfilter_workload_cap(const char *short_ident)
|
||||
{
|
||||
/* Measured on the repository benchmark: 4 vs 8 workers. */
|
||||
static const char *light[] = { "epx", "lq2x", "crop_borders", "darken", NULL };
|
||||
static const char *medium[] = { "2xsai", "super2xsai", "supereagle", "phosphor2x", NULL };
|
||||
unsigned i;
|
||||
if (short_ident)
|
||||
{
|
||||
for (i = 0; light[i]; i++)
|
||||
if (string_is_equal(short_ident, light[i]))
|
||||
return 1;
|
||||
for (i = 0; medium[i]; i++)
|
||||
if (string_is_equal(short_ident, medium[i]))
|
||||
return 4;
|
||||
}
|
||||
return RARCH_SOFTFILTER_AUTO_MAX;
|
||||
}
|
||||
|
||||
unsigned rarch_softfilter_auto_budget(unsigned cores, unsigned reserved,
|
||||
const char *short_ident)
|
||||
{
|
||||
unsigned cap = softfilter_workload_cap(short_ident);
|
||||
unsigned budget;
|
||||
if (cores <= reserved)
|
||||
return 1;
|
||||
budget = cores - reserved;
|
||||
/* Headroom for the GPU driver and the OS, once there is any. */
|
||||
if (budget > 2)
|
||||
budget--;
|
||||
if (budget > RARCH_SOFTFILTER_AUTO_MAX)
|
||||
budget = RARCH_SOFTFILTER_AUTO_MAX;
|
||||
if (budget > cap)
|
||||
budget = cap;
|
||||
return budget < 1 ? 1 : budget;
|
||||
}
|
||||
|
||||
unsigned rarch_softfilter_auto_threads(const char *short_ident)
|
||||
{
|
||||
unsigned cores = 0;
|
||||
unsigned fast = 0, slow = 0;
|
||||
|
||||
#ifdef HAVE_THREADS
|
||||
if (sthread_get_core_topology(&fast, &slow))
|
||||
cores = fast + slow;
|
||||
#endif
|
||||
if (!cores)
|
||||
cores = cpu_features_get_core_amount_physical();
|
||||
return rarch_softfilter_auto_budget(cores, softfilter_auto_reserved,
|
||||
short_ident);
|
||||
}
|
||||
|
||||
static bool create_softfilter_graph(rarch_softfilter_t *filt,
|
||||
enum retro_pixel_format in_pixel_format,
|
||||
unsigned max_dims,
|
||||
@@ -231,8 +307,8 @@ static bool create_softfilter_graph(rarch_softfilter_t *filt,
|
||||
&softfilter_config, input_fmt, input_fmt,
|
||||
VIDEO_SCALE_W(max_dims), VIDEO_SCALE_H(max_dims),
|
||||
threads != RARCH_SOFTFILTER_THREADS_AUTO ? threads :
|
||||
cpu_features_get_core_amount(), cpu_features,
|
||||
&userdata);
|
||||
rarch_softfilter_auto_threads(filt->impl->short_ident),
|
||||
cpu_features, &userdata);
|
||||
if (!filt->impl_data)
|
||||
{
|
||||
RARCH_ERR("[SoftFilter] Failed to create softfilter state.\n");
|
||||
|
||||
@@ -26,10 +26,36 @@
|
||||
|
||||
#define RARCH_SOFTFILTER_THREADS_AUTO 0
|
||||
|
||||
/* Upper bound of workers RARCH_SOFTFILTER_THREADS_AUTO ever picks;
|
||||
* an explicit count from the user may go higher. Past this the heavy
|
||||
* filters gain little per extra worker and the pool is only taking
|
||||
* cores from the GPU driver and the OS. */
|
||||
#define RARCH_SOFTFILTER_AUTO_MAX 8
|
||||
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
typedef struct rarch_softfilter rarch_softfilter_t;
|
||||
|
||||
/* Tells RARCH_SOFTFILTER_THREADS_AUTO how many physical cores the
|
||||
* frontend's own frame-critical threads occupy (the emulation thread,
|
||||
* plus the video, audio and task threads where each runs on its own
|
||||
* thread), so the pool is sized from what is left. Default 1: the
|
||||
* emulation thread. Read at rarch_softfilter_new(). */
|
||||
void rarch_softfilter_set_auto_reserved(unsigned reserved_cores);
|
||||
|
||||
/* The worker count RARCH_SOFTFILTER_THREADS_AUTO resolves to for the
|
||||
* plugin named by short_ident on this machine, given the reserve
|
||||
* above; what rarch_softfilter_new() passes to the plugin. Exposed
|
||||
* for tests. */
|
||||
unsigned rarch_softfilter_auto_threads(const char *short_ident);
|
||||
|
||||
/* The arithmetic behind it, for a given physical core count and
|
||||
* reserve: cores minus reserve, minus one of headroom once above two,
|
||||
* capped at RARCH_SOFTFILTER_AUTO_MAX and at the plugin's workload
|
||||
* ceiling (light filters 1, medium 4, heavy the cap); never below 1. */
|
||||
unsigned rarch_softfilter_auto_budget(unsigned cores, unsigned reserved,
|
||||
const char *short_ident);
|
||||
|
||||
rarch_softfilter_t *rarch_softfilter_new(
|
||||
const char *filter_path,
|
||||
unsigned threads,
|
||||
|
||||
@@ -116,6 +116,42 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
|
||||
/* Error path: free() of a partially built filter must be clean. */
|
||||
/* The Automatic worker count: cores left after the reserve, one
|
||||
* of headroom once above two, capped at RARCH_SOFTFILTER_AUTO_MAX
|
||||
* and at the plugin's workload ceiling. */
|
||||
{
|
||||
static const struct { unsigned cores, reserved, want; const char *plug; } cases[] = {
|
||||
{ 4, 4, 1, "ntsc_crt" }, /* 4-core, everything threaded: nothing spare */
|
||||
{ 4, 2, 2, "ntsc_crt" }, /* 4-core, only main + tasks: two spare, no headroom yet */
|
||||
{ 4, 2, 1, "epx" }, /* light filter runs alone whatever is spare */
|
||||
{ 3, 1, 2, "ntsc" },
|
||||
{ 6, 1, 4, "no_such_plugin" }, /* unknown counts as heavy: 5, minus headroom */
|
||||
{ 16, 4, 8, "ntsc_crt" }, /* 9950X3D, everything threaded: 11, capped at 8 */
|
||||
{ 16, 4, 4, "2xsai" }, /* medium stops at four */
|
||||
{ 16, 4, 1, "lq2x" },
|
||||
{ 24, 4, 8, "2xbr" }, /* 8P+16E: still 8 */
|
||||
{ 1, 1, 1, "ntsc" },
|
||||
{ 2, 4, 1, "ntsc" }, /* reserve larger than the machine */
|
||||
};
|
||||
unsigned i;
|
||||
for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++)
|
||||
{
|
||||
unsigned got = rarch_softfilter_auto_budget(cases[i].cores,
|
||||
cases[i].reserved, cases[i].plug);
|
||||
CHECK(got == cases[i].want,
|
||||
"auto budget: %u cores, %u reserved, %s -> %u, want %u",
|
||||
cases[i].cores, cases[i].reserved, cases[i].plug, got,
|
||||
cases[i].want);
|
||||
}
|
||||
/* The live answer is within the same bounds. */
|
||||
{
|
||||
unsigned live = rarch_softfilter_auto_threads("ntsc_crt");
|
||||
CHECK(live >= 1 && live <= RARCH_SOFTFILTER_AUTO_MAX,
|
||||
"live auto count %u out of range", live);
|
||||
printf("auto budget on this machine for a heavy filter: %u\n", live);
|
||||
}
|
||||
}
|
||||
|
||||
CHECK(rarch_softfilter_new("/nonexistent/no_such.filt", 4,
|
||||
RETRO_PIXEL_FORMAT_XRGB8888, VIDEO_SCALE_PACK(SRC_W, SRC_H)) == NULL,
|
||||
"bogus path: expected NULL");
|
||||
|
||||
@@ -30,5 +30,5 @@ S_UINT(video_filter_threads, VIDEO_FILTER_THREADS,
|
||||
"video_filter_threads",
|
||||
DEFAULT_VIDEO_FILTER_THREADS, SD_FLAG_ADVANCED, SDESC_RANGE_MINMAX, CMD_EVENT_NONE, 0, MAXIMUM_VIDEO_FILTER_THREADS, 1, 0, setting_action_ok_uint, setting_get_string_representation_uint_video_filter_threads,
|
||||
"Video Filter Threads",
|
||||
"How many threads a CPU video filter may run on. 'Automatic' uses one per CPU core. Light filters can run slower when spread over many threads, so a lower count may be faster for them; heavy filters such as NTSC gain the most from more threads. A running filter picks up the change straight away.")
|
||||
"How many threads a CPU video filter may run on. 'Automatic' uses the cores left after the emulation, video, audio and task threads have theirs, at most 8, and fewer for light filters that run slower when spread out. Heavy filters such as NTSC gain the most from more threads. A running filter picks up the change straight away.")
|
||||
#endif
|
||||
Reference in new issue
Block a user