[{"data":1,"prerenderedAt":41},["ShallowReactive",2],{"project-plugin-hostd-placement":3},{"page":4,"others":25},{"slug":5,"src":6,"title":6,"source":7,"toc":8,"html":24},"placement","docs\u002Fplacement.md","https:\u002F\u002Fgithub.com\u002FFreeMixer\u002Fplugin-hostd\u002Fblob\u002Fb4ea6a9\u002Fdocs\u002Fplacement.md",[9,12,15,18,21],{"id":10,"title":11},"the-policy","The policy",{"id":13,"title":14},"the-configuration","The configuration",{"id":16,"title":17},"what-reports-the-core","What reports the core",{"id":19,"title":20},"the-clap-thread-pool","The CLAP thread pool",{"id":22,"title":23},"measured","Measured","\u003Ch1 id=\"core-placement-one-worker-per-chain-pinned-across-the-audio-cores\">Core placement: one worker per chain, pinned across the audio cores\u003C\u002Fh1>\n\u003Cp>plugin-hostd runs each chain (and each plugin that may not share) in a worker process of its own. On a small machine,\na Raspberry Pi 5 with four cores, the kernel and the JACK server decide where those processes run unless someone pins\nthem. A plugin's own threads do not help: Surge XT 1.4.0 and the other plugins measured on the Pi 5 ignore CLAP's\nthread-pool extension, so one instrument is one audio thread. Several instruments use several cores only when each\nruns in a worker of its own and those workers are spread over the cores, with the daemon, the UI and the system kept\noff them.\u003C\u002Fp>\n\u003Cp>The setting \u003Ccode>placement\u003C\u002Fcode> does that.\u003C\u002Fp>\n\u003Ch2 id=\"the-policy\">The policy\u003C\u002Fh2>\n\u003Cul>\n\u003Cli>\u003Cstrong>Audio cores.\u003C\u002Fstrong> \u003Ccode>audio_cores\u003C\u002Fcode> is a cpu list (\u003Ccode>1-3\u003C\u002Fcode>, \u003Ccode>2,3\u003C\u002Fcode>). Empty, the default, is every cpu the daemon may run on\nwhen it starts (its affinity, so a \u003Ccode>taskset\u003C\u002Fcode> or a cgroup is honoured) except the lowest one. A list that names a cpu\noutside that set refuses the start.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>The reserved cores\u003C\u002Fstrong> are the daemon's cpus that are not audio cores: the lowest one by default. The daemon pins\nitself and all its threads there at start. The processes it starts that are not workers, the descriptor scans and\nprobes, are forked from it and inherit that mask. The UI is the system's to pin there (\u003Ccode>taskset -c 0\u003C\u002Fcode>, or\n\u003Ccode>CPUAffinity=0\u003C\u002Fcode> in its unit). With no reserved core left (\u003Ccode>audio_cores\u003C\u002Fcode> names every cpu) the daemon is not pinned, and stderr says so.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Each new worker\u003C\u002Fstrong> is pinned (\u003Ccode>sched_setaffinity\u003C\u002Fcode>, in the child before it runs the worker program, so every thread\nit creates, its JACK or PipeWire thread included, inherits the mask) to one audio core: the one with the least load.\nThe load of a core is the sum of the estimated costs of the workers already placed on it, when the cost of every\nplaced worker is known. A worker's cost is the sum of its members' \u003Ccode>coreFractionP95\u003C\u002Fcode> from their qualification records\nat \u003Ccode>group_rate\u003C\u002Fcode> and \u003Ccode>group_quantum\u003C\u002Fcode>, as the planner reads it. When any placed worker has a member without a record,\nthe load is the number of workers. Ties go to the lowest core. So four workers on four cpus land on cores 1, 2, 3 and\n1, with the daemon on 0.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>A worker keeps its core\u003C\u002Fstrong> when it is respawned. A worker that is made anew (a chain formed, a group split by a\ndeath, a quarantined member moved out) is placed again.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Chains and isolated plugins.\u003C\u002Fstrong> A chain, whether declared by \u003Ccode>chain\u003C\u002Fcode> or a segment of a \u003Ccode>plan\u003C\u002Fcode>, is one worker, so it\ntakes one core. A plugin the planner leaves alone, an \u003Ccode>isolated\u003C\u002Fcode> verdict among them, gets a worker\nof its own, placed by the same rule. Zynthian still sees one chain.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>\u003Ccode>worker_env\u003C\u002Fcode>.\u003C\u002Fstrong> A format whose workers were given a cpu list by \u003Ccode>worker_env\u003C\u002Fcode> keeps that list: the policy does not\nmove those workers, and they report no core.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>\u003Ccode>placement none\u003C\u002Fcode>\u003C\u002Fstrong> is the behaviour before this setting. Nothing is pinned, the workers inherit the daemon's mask,\nand the reports say \u003Ccode>-\u003C\u002Fcode>.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"the-configuration\">The configuration\u003C\u002Fh2>\n\u003Cp>In the settings file (\u003Ccode>-c\u003C\u002Fcode>, or \u003Ccode>$XDG_CONFIG_HOME\u002Fplugin-hostd.conf\u003C\u002Fcode>):\u003C\u002Fp>\n\u003Cpre>\u003Ccode>placement spread        # or none\naudio_cores 1-3         # empty: every cpu the daemon may use but the lowest\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>and on the command line, over the file: \u003Ccode>plugin-hostd --placement=none\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch2 id=\"what-reports-the-core\">What reports the core\u003C\u002Fh2>\n\u003Cul>\n\u003Cli>\u003Ccode>worker_list\u003C\u002Fcode>: each record is \u003Ccode>&lt;worker&gt;:&lt;pid&gt;:&lt;format&gt;:&lt;state&gt;:&lt;core&gt;:&lt;pool&gt;:&lt;place&gt;:&lt;instances&gt;\u003C\u002Fcode>, \u003Ccode>&lt;core&gt;\u003C\u002Fcode> the audio\ncore the policy pinned it to, \u003Ccode>-\u003C\u002Fcode> for none, \u003Ccode>&lt;pool&gt;\u003C\u002Fcode> \u003Ccode>on\u003C\u002Fcode> or \u003Ccode>qualified\u003C\u002Fcode> (below). The place still holds colons, so a\nrecord is read from both ends: six fields on the left, the instances on the right.\u003C\u002Fli>\n\u003Cli>\u003Ccode>plan\u003C\u002Fcode> answers \u003Ccode>resp 0 &lt;segments&gt; &lt;cores&gt;\u003C\u002Fcode> and \u003Ccode>plan_get\u003C\u002Fcode> \u003Ccode>resp 0 &lt;segments&gt; &lt;reasons&gt; &lt;cores&gt;\u003C\u002Fcode>: \u003Ccode>&lt;cores&gt;\u003C\u002Fcode> has the\nshape of \u003Ccode>&lt;segments&gt;\u003C\u002Fcode>, each member replaced by the core of the worker that runs it now (\u003Ccode>-\u003C\u002Fcode> for none), so \u003Ccode>1,2;3\u003C\u002Fcode>\nplaced on cores 2 and 1 reads \u003Ccode>2,2;1\u003C\u002Fcode>.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"the-clap-thread-pool\">The CLAP thread pool\u003C\u002Fh2>\n\u003Cp>omx-clap-host's pool of helper threads (\u003Ccode>--thread-pool=qualified|off|on\u003C\u002Fcode>) sizes itself from the\nworker's affinity mask: N cores, N - 1 helpers. The pool is the plugin's qualification's to give, never a global switch,\nand it travels in the plugin's pin as the word \u003Ccode>pool=cores\u003C\u002Fcode> (or \u003Ccode>pool=off\u003C\u002Fcode>) after the verdict:\u003C\u002Fp>\n\u003Cpre>\u003Ccode>pin_set &lt;uri&gt; &lt;files&gt; &lt;layout&gt; verdict=isolated pool=cores\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>A CLAP or lv2-clap worker of a plugin whose pin says \u003Ccode>pool=cores\u003C\u002Fcode> (and is not a refusal) is started with\n\u003Ccode>--thread-pool=on\u003C\u002Fcode>, after the cpus above are set, so the mask it sizes the pool from is the one the policy gave it. The\nplugin runs in a worker of its own: the planner leaves it alone (\u003Ccode>verdict-isolated\u003C\u002Fcode>), a \u003Ccode>pool:\u003C\u002Fcode> placement and a chain\nare refused for it, and a respawn starts the worker with the option again. Every other worker is started with no\noption, the host's \u003Ccode>qualified\u003C\u002Fcode>, which grants the pool to no plugin. The pool's waits past the block are in the worker's\n\u003Ccode>xruns\u003C\u002Fcode> line, so they are attributed as any xrun is. Pins are read while \u003Ccode>require_pins\u003C\u002Fcode> is on, so the pool is too.\u003C\u002Fp>\n\u003Ch2 id=\"measured\">Measured\u003C\u002Fh2>\n\u003Cp>On a Pi 5, with two and three LV2 instruments in workers of their own, \u003Ccode>spread\u003C\u002Fcode> put the workers on cores 1-3 and the\ndaemon on 0, and core 0 fell from 7-9 % busy to under 2 %. Neither mode had an xrun at that load, so no xrun\ngain is shown yet: the instruments use a fraction of one core. A run that fills the audio cores is still owed.\u003C\u002Fp>\n",[26,29,32,35,38],{"slug":27,"title":28},"","README.md",{"slug":30,"title":31},"formats","docs\u002Fformats.md",{"slug":33,"title":34},"own-host","the highest SCHED_FIFO priority, which the probe asks for, and a lock as large as the probe's memory limit",{"slug":36,"title":37},"panel","docs\u002Fpanel.md",{"slug":39,"title":40},"presets","Presets in the host",1793954996714]