요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
Runqueue bucket 집계
sched-util-clamp.rst:184-263원문의 긴 ASCII bucket 배열을 동일한 구간과 count 관계가 보이도록 다시 구성했습니다.
모든 runnable task의 clamp를 매번 순회하면 enqueue/dequeue hot path 비용이 커집니다. 커널은 clamp 범위를 일정 수의 bucket으로 양자화하고 각 bucket의 active count를 유지하여 상한 집계를 일정한 비용으로 갱신합니다.
Max와 계층 집계
sched-util-clamp.rst:264-336task 값은 cgroup과 시스템 범위에 의해 제한된 뒤 effective 값이 되고, runqueue는 runnable task의 effective 값 중 필요한 최댓값을 유지합니다.
cgroup 계층에서는 자식이 부모가 허용한 범위를 벗어나지 못합니다. task가 요청한 min/max, cgroup 제한과 시스템 기본값을 결합해 effective clamp를 만든 뒤 runqueue aggregation에 반영합니다.
Task, cgroup, system 인터페이스
sched-util-clamp.rst:337-521| 범위 | 인터페이스 | 사용 목적 |
|---|---|---|
| Task | sched_setattr()의 sched_util_min/max | 개별 thread의 boost 또는 cap |
| cgroup | cpu.uclamp.min / cpu.uclamp.max | 서비스·앱 단위 정책 |
| System | sched_util_clamp_min/max 관련 설정 | 전체 허용 범위와 기본값 |
| RT default | sched_util_clamp_min_rt_default | RT task의 기본 최소 성능 요청 |
값은 capacity scale인 0..1024 범위로 해석합니다. min이 max보다 커지는 조합, cgroup parent 범위와 충돌하는 조합은 effective 값 계산에서 제한됩니다. 값을 쓴 뒤에는 task와 cgroup 양쪽에서 실제 적용값을 다시 읽어야 합니다.
사용 사례
sched-util-clamp.rst:522-593- UI, audio와 control thread의 uclamp_min을 올려 wake-up 직후 DVFS ramp 지연을 줄입니다.
- background task의 uclamp_max를 낮춰 big CPU 배치와 높은 OPP 선택을 억제합니다.
- powersave mode에서 app cgroup의 최대값을 일괄 제한합니다.
- 앱별 성능 정책을 cgroup 계층으로 적용하되 latency-critical helper thread는 별도 group으로 분리합니다.
알려진 한계와 검증 항목
sched-util-clamp.rst:594-742uclamp_max가 항상 물리 주파수의 완전한 상한이 되는 것은 아닙니다. 같은 performance domain의 다른 CPU가 높은 주파수를 요구하거나 RT/DL/IRQ 부하가 개입하면 domain frequency가 올라갈 수 있습니다.
낮은 uclamp_max로 실행이 지연되면 PELT util_avg 자체가 workload demand를 충분히 표현하지 못하는 feedback 문제가 생길 수 있습니다. schedutil rate limit과 driver transition latency도 반응 시간을 늘리므로 trace_cpu_frequency, sched_switch, sched_util_est 계열 trace를 함께 확인해야 합니다.
오랫동안 cap된 p0의 util_avg가 1024까지 누적된 상태에서 제한 없는 p1이 깨어나면 runqueue max가 1024로 풀리며 과도한 frequency 요청이 발생할 수 있습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
.. SPDX-License-Identifier: GPL-2.0
====================
Utilization Clamping
====================
1. Introduction
===============
Utilization clamping, also known as util clamp or uclamp, is a scheduler
feature that allows user space to help in managing the performance requirement
of tasks. It was introduced in v5.3 release. The CGroup support was merged in
v5.4.
Uclamp is a hinting mechanism that allows the scheduler to understand the
performance requirements and restrictions of the tasks, thus it helps the
scheduler to make a better decision. And when schedutil cpufreq governor is
used, util clamp will influence the CPU frequency selection as well.
Since the scheduler and schedutil are both driven by PELT (util_avg) signals,
util clamp acts on that to achieve its goal by clamping the signal to a certain
point; hence the name. That is, by clamping utilization we are making the
system run at a certain performance point.
The right way to view util clamp is as a mechanism to make request or hint on
performance constraints. It consists of two tunables:
* UCLAMP_MIN, which sets the lower bound.
* UCLAMP_MAX, which sets the upper bound.
These two bounds will ensure a task will operate within this performance range
of the system. UCLAMP_MIN implies boosting a task, while UCLAMP_MAX implies
capping a task.
One can tell the system (scheduler) that some tasks require a minimum
performance point to operate at to deliver the desired user experience. Or one
can tell the system that some tasks should be restricted from consuming too
much resources and should not go above a specific performance point. Viewing
the uclamp values as performance points rather than utilization is a better
abstraction from user space point of view.
As an example, a game can use util clamp to form a feedback loop with its
perceived Frames Per Second (FPS). It can dynamically increase the minimum
performance point required by its display pipeline to ensure no frame is
dropped. It can also dynamically 'prime' up these tasks if it knows in the
coming few hundred milliseconds a computationally intensive scene is about to
happen.
On mobile hardware where the capability of the devices varies a lot, this
dynamic feedback loop offers a great flexibility to ensure best user experience
given the capabilities of any system.
Of course a static configuration is possible too. The exact usage will depend
on the system, application and the desired outcome.
Another example is in Android where tasks are classified as background,
foreground, top-app, etc. Util clamp can be used to constrain how much
resources background tasks are consuming by capping the performance point they
can run at. This constraint helps reserve resources for important tasks, like
the ones belonging to the currently active app (top-app group). Beside this
helps in limiting how much power they consume. This can be more obvious in
heterogeneous systems (e.g. Arm big.LITTLE); the constraint will help bias the
background tasks to stay on the little cores which will ensure that:
1. The big cores are free to run top-app tasks immediately. top-app
tasks are the tasks the user is currently interacting with, hence
the most important tasks in the system.
2. They don't run on a power hungry core and drain battery even if they
are CPU intensive tasks.
.. note::
**little cores**:
CPUs with capacity < 1024
**big cores**:
CPUs with capacity = 1024
By making these uclamp performance requests, or rather hints, user space can
ensure system resources are used optimally to deliver the best possible user
experience.
Another use case is to help with **overcoming the ramp up latency inherit in
how scheduler utilization signal is calculated**.
On the other hand, a busy task for instance that requires to run at maximum
performance point will suffer a delay of ~200ms (PELT HALFIFE = 32ms) for the
scheduler to realize that. This is known to affect workloads like gaming on
mobile devices where frames will drop due to slow response time to select the
higher frequency required for the tasks to finish their work in time. Setting
UCLAMP_MIN=1024 will ensure such tasks will always see the highest performance
level when they start running.
The overall visible effect goes beyond better perceived user
experience/performance and stretches to help achieve a better overall
performance/watt if used effectively.
User space can form a feedback loop with the thermal subsystem too to ensure
the device doesn't heat up to the point where it will throttle.
Both SCHED_NORMAL/OTHER and SCHED_FIFO/RR honour uclamp requests/hints.
In the SCHED_FIFO/RR case, uclamp gives the option to run RT tasks at any
performance point rather than being tied to MAX frequency all the time. Which
can be useful on general purpose systems that run on battery powered devices.
Note that by design RT tasks don't have per-task PELT signal and must always
run at a constant frequency to combat undeterministic DVFS rampup delays.
Note that using schedutil always implies a single delay to modify the frequency
when an RT task wakes up. This cost is unchanged by using uclamp. Uclamp only
helps picking what frequency to request instead of schedutil always requesting
MAX for all RT tasks.
See :ref:`section 3.4 <uclamp-default-values>` for default values and
:ref:`3.4.1 <sched-util-clamp-min-rt-default>` on how to change RT tasks
default value.
2. Design
=========
Util clamp is a property of every task in the system. It sets the boundaries of
its utilization signal; acting as a bias mechanism that influences certain
decisions within the scheduler.
The actual utilization signal of a task is never clamped in reality. If you
inspect PELT signals at any point of time you should continue to see them as
they are intact. Clamping happens only when needed, e.g: when a task wakes up
and the scheduler needs to select a suitable CPU for it to run on.
Since the goal of util clamp is to allow requesting a minimum and maximum
performance point for a task to run on, it must be able to influence the
frequency selection as well as task placement to be most effective. Both of
which have implications on the utilization value at CPU runqueue (rq for short)
level, which brings us to the main design challenge.
When a task wakes up on an rq, the utilization signal of the rq will be
affected by the uclamp settings of all the tasks enqueued on it. For example if
a task requests to run at UTIL_MIN = 512, then the util signal of the rq needs
to respect to this request as well as all other requests from all of the
enqueued tasks.
To be able to aggregate the util clamp value of all the tasks attached to the
rq, uclamp must do some housekeeping at every enqueue/dequeue, which is the
scheduler hot path. Hence care must be taken since any slow down will have
significant impact on a lot of use cases and could hinder its usability in
practice.
The way this is handled is by dividing the utilization range into buckets
(struct uclamp_bucket) which allows us to reduce the search space from every
task on the rq to only a subset of tasks on the top-most bucket.
When a task is enqueued, the counter in the matching bucket is incremented,
and on dequeue it is decremented. This makes keeping track of the effective
uclamp value at rq level a lot easier.
As tasks are enqueued and dequeued, we keep track of the current effective
uclamp value of the rq. See :ref:`section 2.1 <uclamp-buckets>` for details on
how this works.
Later at any path that wants to identify the effective uclamp value of the rq,
it will simply need to read this effective uclamp value of the rq at that exact
moment of time it needs to take a decision.
For task placement case, only Energy Aware and Capacity Aware Scheduling
(EAS/CAS) make use of uclamp for now, which implies that it is applied on
heterogeneous systems only.
When a task wakes up, the scheduler will look at the current effective uclamp
value of every rq and compare it with the potential new value if the task were
to be enqueued there. Favoring the rq that will end up with the most energy
efficient combination.
Similarly in schedutil, when it needs to make a frequency update it will look
at the current effective uclamp value of the rq which is influenced by the set
of tasks currently enqueued there and select the appropriate frequency that
will satisfy constraints from requests.
Other paths like setting overutilization state (which effectively disables EAS)
make use of uclamp as well. Such cases are considered necessary housekeeping to
allow the 2 main use cases above and will not be covered in detail here as they
could change with implementation details.
.. _uclamp-buckets:
2.1. Buckets
------------
::
[struct rq]
(bottom) (top)
0 1024
| |
+-----------+-----------+-----------+---- ----+-----------+
| Bucket 0 | Bucket 1 | Bucket 2 | ... | Bucket N |
+-----------+-----------+-----------+---- ----+-----------+
: : :
+- p0 +- p3 +- p4
: :
+- p1 +- p5
:
+- p2
.. note::
The diagram above is an illustration rather than a true depiction of the
internal data structure.
To reduce the search space when trying to decide the effective uclamp value of
an rq as tasks are enqueued/dequeued, the whole utilization range is divided
into N buckets where N is configured at compile time by setting
CONFIG_UCLAMP_BUCKETS_COUNT. By default it is set to 5.
The rq has a bucket for each uclamp_id tunables: [UCLAMP_MIN, UCLAMP_MAX].
The range of each bucket is 1024/N. For example, for the default value of
5 there will be 5 buckets, each of which will cover the following range:
::
DELTA = round_closest(1024/5) = 204.8 = 205
Bucket 0: [0:204]
Bucket 1: [205:409]
Bucket 2: [410:614]
Bucket 3: [615:819]
Bucket 4: [820:1024]
When a task p with following tunable parameters
::
p->uclamp[UCLAMP_MIN] = 300
p->uclamp[UCLAMP_MAX] = 1024
is enqueued into the rq, bucket 1 will be incremented for UCLAMP_MIN and bucket
4 will be incremented for UCLAMP_MAX to reflect the fact the rq has a task in
this range.
The rq then keeps track of its current effective uclamp value for each
uclamp_id.
When a task p is enqueued, the rq value changes to:
::
// update bucket logic goes here
rq->uclamp[UCLAMP_MIN] = max(rq->uclamp[UCLAMP_MIN], p->uclamp[UCLAMP_MIN])
// repeat for UCLAMP_MAX
Similarly, when p is dequeued the rq value changes to:
::
// update bucket logic goes here
rq->uclamp[UCLAMP_MIN] = search_top_bucket_for_highest_value()
// repeat for UCLAMP_MAX
When all buckets are empty, the rq uclamp values are reset to system defaults.
See :ref:`section 3.4 <uclamp-default-values>` for details on default values.
2.2. Max aggregation
--------------------
Util clamp is tuned to honour the request for the task that requires the
highest performance point.
When multiple tasks are attached to the same rq, then util clamp must make sure
the task that needs the highest performance point gets it even if there's
another task that doesn't need it or is disallowed from reaching this point.
For example, if there are multiple tasks attached to an rq with the following
values:
::
p0->uclamp[UCLAMP_MIN] = 300
p0->uclamp[UCLAMP_MAX] = 900
p1->uclamp[UCLAMP_MIN] = 500
p1->uclamp[UCLAMP_MAX] = 500
then assuming both p0 and p1 are enqueued to the same rq, both UCLAMP_MIN
and UCLAMP_MAX become:
::
rq->uclamp[UCLAMP_MIN] = max(300, 500) = 500
rq->uclamp[UCLAMP_MAX] = max(900, 500) = 900
As we shall see in :ref:`section 5.1 <uclamp-capping-fail>`, this max
aggregation is the cause of one of limitations when using util clamp, in
particular for UCLAMP_MAX hint when user space would like to save power.
2.3. Hierarchical aggregation
-----------------------------
As stated earlier, util clamp is a property of every task in the system. But
the actual applied (effective) value can be influenced by more than just the
request made by the task or another actor on its behalf (middleware library).
The effective util clamp value of any task is restricted as follows:
1. By the uclamp settings defined by the cgroup CPU controller it is attached
to, if any.
2. The restricted value in (1) is then further restricted by the system wide
uclamp settings.
:ref:`Section 3 <uclamp-interfaces>` discusses the interfaces and will expand
further on that.
For now suffice to say that if a task makes a request, its actual effective
value will have to adhere to some restrictions imposed by cgroup and system
wide settings.
The system will still accept the request even if effectively will be beyond the
constraints, but as soon as the task moves to a different cgroup or a sysadmin
modifies the system settings, the request will be satisfied only if it is
within new constraints.
In other words, this aggregation will not cause an error when a task changes
its uclamp values, but rather the system may not be able to satisfy requests
based on those factors.
2.4. Range
----------
Uclamp performance request has the range of 0 to 1024 inclusive.
For cgroup interface percentage is used (that is 0 to 100 inclusive).
Just like other cgroup interfaces, you can use 'max' instead of 100.
.. _uclamp-interfaces:
3. Interfaces
=============
3.1. Per task interface
-----------------------
sched_setattr() syscall was extended to accept two new fields:
* sched_util_min: requests the minimum performance point the system should run
at when this task is running. Or lower performance bound.
* sched_util_max: requests the maximum performance point the system should run
at when this task is running. Or upper performance bound.
For example, the following scenario have 40% to 80% utilization constraints:
::
attr->sched_util_min = 40% * 1024;
attr->sched_util_max = 80% * 1024;
When task @p is running, **the scheduler should try its best to ensure it
starts at 40% performance level**. If the task runs for a long enough time so
that its actual utilization goes above 80%, the utilization, or performance
level, will be capped.
The special value -1 is used to reset the uclamp settings to the system
default.
Note that resetting the uclamp value to system default using -1 is not the same
as manually setting uclamp value to system default. This distinction is
important because as we shall see in system interfaces, the default value for
RT could be changed. SCHED_NORMAL/OTHER might gain similar knobs too in the
future.
3.2. cgroup interface
---------------------
There are two uclamp related values in the CPU cgroup controller:
* cpu.uclamp.min
* cpu.uclamp.max
When a task is attached to a CPU controller, its uclamp values will be impacted
as follows:
* cpu.uclamp.min is a protection as described in :ref:`section 3-3 of cgroup
v2 documentation <cgroupv2-protections-distributor>`.
If a task uclamp_min value is lower than cpu.uclamp.min, then the task will
inherit the cgroup cpu.uclamp.min value.
In a cgroup hierarchy, effective cpu.uclamp.min is the max of (child,
parent).
* cpu.uclamp.max is a limit as described in :ref:`section 3-2 of cgroup v2
documentation <cgroupv2-limits-distributor>`.
If a task uclamp_max value is higher than cpu.uclamp.max, then the task will
inherit the cgroup cpu.uclamp.max value.
In a cgroup hierarchy, effective cpu.uclamp.max is the min of (child,
parent).
For example, given following parameters:
::
p0->uclamp[UCLAMP_MIN] = // system default;
p0->uclamp[UCLAMP_MAX] = // system default;
p1->uclamp[UCLAMP_MIN] = 40% * 1024;
p1->uclamp[UCLAMP_MAX] = 50% * 1024;
cgroup0->cpu.uclamp.min = 20% * 1024;
cgroup0->cpu.uclamp.max = 60% * 1024;
cgroup1->cpu.uclamp.min = 60% * 1024;
cgroup1->cpu.uclamp.max = 100% * 1024;
when p0 and p1 are attached to cgroup0, the values become:
::
p0->uclamp[UCLAMP_MIN] = cgroup0->cpu.uclamp.min = 20% * 1024;
p0->uclamp[UCLAMP_MAX] = cgroup0->cpu.uclamp.max = 60% * 1024;
p1->uclamp[UCLAMP_MIN] = 40% * 1024; // intact
p1->uclamp[UCLAMP_MAX] = 50% * 1024; // intact
when p0 and p1 are attached to cgroup1, these instead become:
::
p0->uclamp[UCLAMP_MIN] = cgroup1->cpu.uclamp.min = 60% * 1024;
p0->uclamp[UCLAMP_MAX] = cgroup1->cpu.uclamp.max = 100% * 1024;
p1->uclamp[UCLAMP_MIN] = cgroup1->cpu.uclamp.min = 60% * 1024;
p1->uclamp[UCLAMP_MAX] = 50% * 1024; // intact
Note that cgroup interfaces allows cpu.uclamp.max value to be lower than
cpu.uclamp.min. Other interfaces don't allow that.
3.3. System interface
---------------------
3.3.1 sched_util_clamp_min
--------------------------
System wide limit of allowed UCLAMP_MIN range. By default it is set to 1024,
which means that permitted effective UCLAMP_MIN range for tasks is [0:1024].
By changing it to 512 for example the range reduces to [0:512]. This is useful
to restrict how much boosting tasks are allowed to acquire.
Requests from tasks to go above this knob value will still succeed, but
they won't be satisfied until it is more than p->uclamp[UCLAMP_MIN].
The value must be smaller than or equal to sched_util_clamp_max.
3.3.2 sched_util_clamp_max
--------------------------
System wide limit of allowed UCLAMP_MAX range. By default it is set to 1024,
which means that permitted effective UCLAMP_MAX range for tasks is [0:1024].
By changing it to 512 for example the effective allowed range reduces to
[0:512]. This means is that no task can run above 512, which implies that all
rqs are restricted too. IOW, the whole system is capped to half its performance
capacity.
This is useful to restrict the overall maximum performance point of the system.
For example, it can be handy to limit performance when running low on battery
or when the system wants to limit access to more energy hungry performance
levels when it's in idle state or screen is off.
Requests from tasks to go above this knob value will still succeed, but they
won't be satisfied until it is more than p->uclamp[UCLAMP_MAX].
The value must be greater than or equal to sched_util_clamp_min.
.. _uclamp-default-values:
3.4. Default values
-------------------
By default all SCHED_NORMAL/SCHED_OTHER tasks are initialized to:
::
p_fair->uclamp[UCLAMP_MIN] = 0
p_fair->uclamp[UCLAMP_MAX] = 1024
That is, by default they're boosted to run at the maximum performance point of
changed at boot or runtime. No argument was made yet as to why we should
provide this, but can be added in the future.
For SCHED_FIFO/SCHED_RR tasks:
::
p_rt->uclamp[UCLAMP_MIN] = 1024
p_rt->uclamp[UCLAMP_MAX] = 1024
That is by default they're boosted to run at the maximum performance point of
the system which retains the historical behavior of the RT tasks.
RT tasks default uclamp_min value can be modified at boot or runtime via
sysctl. See below section.
.. _sched-util-clamp-min-rt-default:
3.4.1 sched_util_clamp_min_rt_default
-------------------------------------
Running RT tasks at maximum performance point is expensive on battery powered
devices and not necessary. To allow system developer to offer good performance
guarantees for these tasks without pushing it all the way to maximum
performance point, this sysctl knob allows tuning the best boost value to
address the system requirement without burning power running at maximum
performance point all the time.
Application developer are encouraged to use the per task util clamp interface
to ensure they are performance and power aware. Ideally this knob should be set
to 0 by system designers and leave the task of managing performance
requirements to the apps.
4. How to use util clamp
========================
Util clamp promotes the concept of user space assisted power and performance
management. At the scheduler level there is no info required to make the best
decision. However, with util clamp user space can hint to the scheduler to make
better decision about task placement and frequency selection.
Best results are achieved by not making any assumptions about the system the
application is running on and to use it in conjunction with a feedback loop to
dynamically monitor and adjust. Ultimately this will allow for a better user
experience at a better perf/watt.
For some systems and use cases, static setup will help to achieve good results.
Portability will be a problem in this case. How much work one can do at 100,
200 or 1024 is different for each system. Unless there's a specific target
system, static setup should be avoided.
There are enough possibilities to create a whole framework based on util clamp
or self contained app that makes use of it directly.
4.1. Boost important and DVFS-latency-sensitive tasks
-----------------------------------------------------
A GUI task might not be busy to warrant driving the frequency high when it
wakes up. However, it requires to finish its work within a specific time window
to deliver the desired user experience. The right frequency it requires at
wakeup will be system dependent. On some underpowered systems it will be high,
on other overpowered ones it will be low or 0.
This task can increase its UCLAMP_MIN value every time it misses the deadline
to ensure on next wake up it runs at a higher performance point. It should try
to approach the lowest UCLAMP_MIN value that allows to meet its deadline on any
particular system to achieve the best possible perf/watt for that system.
On heterogeneous systems, it might be important for this task to run on
a faster CPU.
**Generally it is advised to perceive the input as performance level or point
which will imply both task placement and frequency selection**.
4.2. Cap background tasks
-------------------------
Like explained for Android case in the introduction. Any app can lower
UCLAMP_MAX for some background tasks that don't care about performance but
could end up being busy and consume unnecessary system resources on the system.
4.3. Powersave mode
-------------------
sched_util_clamp_max system wide interface can be used to limit all tasks from
operating at the higher performance points which are usually energy
inefficient.
This is not unique to uclamp as one can achieve the same by reducing max
frequency of the cpufreq governor. It can be considered a more convenient
alternative interface.
4.4. Per-app performance restriction
------------------------------------
Middleware/Utility can provide the user an option to set UCLAMP_MIN/MAX for an
app every time it is executed to guarantee a minimum performance point and/or
limit it from draining system power at the cost of reduced performance for
these apps.
If you want to prevent your laptop from heating up while on the go from
compiling the kernel and happy to sacrifice performance to save power, but
still would like to keep your browser performance intact, uclamp makes it
possible.
5. Limitations
==============
.. _uclamp-capping-fail:
5.1. Capping frequency with uclamp_max fails under certain conditions
---------------------------------------------------------------------
If task p0 is capped to run at 512:
::
p0->uclamp[UCLAMP_MAX] = 512
and it shares the rq with p1 which is free to run at any performance point:
::
p1->uclamp[UCLAMP_MAX] = 1024
then due to max aggregation the rq will be allowed to reach max performance
point:
::
rq->uclamp[UCLAMP_MAX] = max(512, 1024) = 1024
Assuming both p0 and p1 have UCLAMP_MIN = 0, then the frequency selection for
the rq will depend on the actual utilization value of the tasks.
If p1 is a small task but p0 is a CPU intensive task, then due to the fact that
both are running at the same rq, p1 will cause the frequency capping to be left
from the rq although p1, which is allowed to run at any performance point,
doesn't actually need to run at that frequency.
5.2. UCLAMP_MAX can break PELT (util_avg) signal
------------------------------------------------
PELT assumes that frequency will always increase as the signals grow to ensure
there's always some idle time on the CPU. But with UCLAMP_MAX, this frequency
increase will be prevented which can lead to no idle time in some
circumstances. When there's no idle time, a task will stuck in a busy loop,
which would result in util_avg being 1024.
Combing with issue described below, this can lead to unwanted frequency spikes
when severely capped tasks share the rq with a small non capped task.
As an example if task p, which have:
::
p0->util_avg = 300
p0->uclamp[UCLAMP_MAX] = 0
wakes up on an idle CPU, then it will run at min frequency (Fmin) this
CPU is capable of. The max CPU frequency (Fmax) matters here as well,
since it designates the shortest computational time to finish the task's
work on this CPU.
::
rq->uclamp[UCLAMP_MAX] = 0
If the ratio of Fmax/Fmin is 3, then maximum value will be:
::
300 * (Fmax/Fmin) = 900
which indicates the CPU will still see idle time since 900 is < 1024. The
_actual_ util_avg will not be 900 though, but somewhere between 300 and 900. As
long as there's idle time, p->util_avg updates will be off by a some margin,
but not proportional to Fmax/Fmin.
::
p0->util_avg = 300 + small_error
Now if the ratio of Fmax/Fmin is 4, the maximum value becomes:
::
300 * (Fmax/Fmin) = 1200
which is higher than 1024 and indicates that the CPU has no idle time. When
this happens, then the _actual_ util_avg will become:
::
p0->util_avg = 1024
If task p1 wakes up on this CPU, which have:
::
p1->util_avg = 200
p1->uclamp[UCLAMP_MAX] = 1024
then the effective UCLAMP_MAX for the CPU will be 1024 according to max
aggregation rule. But since the capped p0 task was running and throttled
severely, then the rq->util_avg will be:
::
p0->util_avg = 1024
p1->util_avg = 200
rq->util_avg = 1024
rq->uclamp[UCLAMP_MAX] = 1024
Hence lead to a frequency spike since if p0 wasn't throttled we should get:
::
p0->util_avg = 300
p1->util_avg = 200
rq->util_avg = 500
and run somewhere near mid performance point of that CPU, not the Fmax we get.
5.3. Schedutil response time issues
-----------------------------------
schedutil has three limitations:
1. Hardware takes non-zero time to respond to any frequency change
request. On some platforms can be in the order of few ms.
2. Non fast-switch systems require a worker deadline thread to wake up
and perform the frequency change, which adds measurable overhead.
3. schedutil rate_limit_us drops any requests during this rate_limit_us
window.
If a relatively small task is doing critical job and requires a certain
performance point when it wakes up and starts running, then all these
limitations will prevent it from getting what it wants in the time scale it
expects.
This limitation is not only impactful when using uclamp, but will be more
prevalent as we no longer gradually ramp up or down. We could easily be
jumping between frequencies depending on the order tasks wake up, and their
respective uclamp values.
We regard that as a limitation of the capabilities of the underlying system
itself.
There is room to improve the behavior of schedutil rate_limit_us, but not much
to be done for 1 or 2. They are considered hard limitations of the system.
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
1. 소개
1-117util clamp 또는 uclamp라고도 부르는 utilization clamping은 사용자 공간이 task의 성능 요구사항을 관리하는 데 관여할 수 있게 하는 scheduler 기능이다. Linux v5.3에서 처음 도입되었고 cgroup 지원은 v5.4에 병합되었다.
uclamp는 scheduler가 task의 성능 요구와 제한을 이해하도록 돕는 hint 방식이다. scheduler는 이 정보를 바탕으로 더 나은 결정을 내릴 수 있고, schedutil cpufreq governor를 사용할 때는 CPU frequency 선택에도 util clamp가 영향을 준다.
scheduler와 schedutil은 모두 PELT의 util_avg signal에 의해 구동된다. util clamp는 이 signal을 특정 지점으로 제한하는 방식으로 목적을 달성한다. utilization을 제한함으로써 system이 일정한 성능 지점에서 동작하도록 만드는 것이 이름의 유래다.
사용자 공간 관점에서는 util clamp를 utilization 수치라기보다 성능 제약에 대한 요청 또는 hint로 보는 편이 알맞다. 두 tunable이 이 범위를 정한다.
- UCLAMP_MIN은 하한을 정한다.
- UCLAMP_MAX는 상한을 정한다.
두 경계는 task가 system의 지정된 성능 범위 안에서 동작하도록 한다. UCLAMP_MIN은 task boost를, UCLAMP_MAX는 task cap을 뜻한다.
어떤 task가 원하는 사용자 경험을 제공하려면 최소한 어느 성능 지점에서 동작해야 한다고 scheduler에 알릴 수 있다. 반대로 특정 task가 너무 많은 자원을 쓰지 못하게 하여 정해진 성능 지점을 넘지 않도록 요청할 수도 있다. 사용자 공간에는 uclamp 값을 utilization보다 performance point로 추상화하는 편이 이해하기 쉽다.
예를 들어 game은 화면에서 관찰한 FPS와 util clamp 사이에 feedback loop를 만들 수 있다. frame이 떨어질 때 display pipeline task가 요구하는 최소 성능 지점을 동적으로 높일 수 있고, 수백 ms 뒤 계산량이 큰 장면이 시작될 것을 안다면 미리 이 task들을 높은 성능으로 준비시킬 수도 있다.
device 성능 편차가 큰 mobile hardware에서는 이런 동적 feedback loop가 각 system 능력에 맞는 사용자 경험을 만드는 데 큰 유연성을 준다.
정적 설정도 가능하다. 정확한 사용법은 system, application, 원하는 결과에 따라 달라진다.
Android에서는 task를 background, foreground, top-app 등으로 분류한다. background task가 사용할 수 있는 성능 지점에 상한을 두면 현재 사용자가 조작하는 top-app group처럼 중요한 task를 위해 자원을 남길 수 있고, background task의 전력 소비도 제한할 수 있다.
Arm big.LITTLE 같은 heterogeneous system에서는 이 제약의 효과가 더 분명하다. background task가 little core에 머물도록 유도하면 다음 두 결과를 얻는다.
- big core를 비워 두어 사용자가 현재 상호작용하는 가장 중요한 top-app task를 즉시 실행할 수 있다.
- CPU를 많이 쓰는 background task라도 전력 소모가 큰 core에서 실행되어 battery를 낭비하는 일을 줄인다.
이 문서에서 little core는 capacity가 1024보다 작은 CPU, big core는 capacity가 1024인 CPU를 뜻한다.
사용자 공간은 이런 uclamp 성능 요청 또는 hint를 통해 system resource가 가능한 한 효율적으로 사용되도록 만들고, 그 위에서 최선의 사용자 경험을 제공할 수 있다.
또 다른 용도는 scheduler utilization signal 계산 방식에 내재된 ramp-up latency를 극복하는 것이다.
최대 성능으로 실행되어야 하는 busy task라도 scheduler가 그 요구를 util_avg에서 파악하기까지 약 200 ms가 걸릴 수 있다. PELT half-life는 32 ms다. mobile game에서는 높은 frequency 선택이 늦어져 frame deadline을 놓칠 수 있다. UCLAMP_MIN=1024로 설정하면 이런 task는 실행을 시작할 때부터 가장 높은 성능 수준을 요청한다.
효과적으로 사용하면 눈에 보이는 사용자 경험과 성능 개선을 넘어 system 전체의 performance per watt도 높일 수 있다.
사용자 공간은 thermal subsystem과도 feedback loop를 구성하여 thermal throttling이 시작될 정도로 device가 뜨거워지지 않게 조절할 수 있다.
SCHED_NORMAL/SCHED_OTHER와 SCHED_FIFO/SCHED_RR는 모두 uclamp 요청과 hint를 적용한다.
SCHED_FIFO/SCHED_RR에서는 RT task를 항상 최대 frequency에 묶지 않고 원하는 성능 지점에서 실행할 수 있다. battery로 동작하는 범용 system에서 특히 유용하다.
설계상 RT task에는 task별 PELT signal이 없다. 예측하기 어려운 DVFS ramp-up delay에 대응하려면 RT task는 일정한 frequency에서 실행되어야 한다.
schedutil을 사용하면 RT task가 깨어날 때 frequency를 바꾸기 위한 delay가 한 번은 항상 생긴다. uclamp를 사용해도 이 비용은 달라지지 않는다. uclamp가 바꾸는 것은 schedutil이 모든 RT task에 무조건 MAX를 요청하는 대신 어떤 frequency를 요청할지 선택하는 부분이다. 기본값은 3.4절, RT 기본값 변경은 3.4.1절에서 다룬다.
2. 설계
118-180util clamp는 system의 모든 task가 갖는 속성이다. task utilization signal의 경계를 정하고 scheduler 내부의 일부 결정에 bias를 주는 방식으로 동작한다.
실제 task utilization signal 자체를 변경하지는 않는다. 어느 시점에 PELT signal을 검사해도 원래 값은 그대로다. clamp는 task가 깨어나 scheduler가 실행할 CPU를 고르는 순간처럼, 그 값이 필요한 의사결정 지점에서만 적용된다.
task가 실행될 최소·최대 성능 지점을 요청하려면 frequency 선택과 task placement 양쪽에 영향을 줄 수 있어야 한다. 두 결정 모두 CPU runqueue, 줄여서 rq 수준의 utilization 값과 관계가 있으며 이것이 uclamp 설계의 핵심 과제다.
task가 rq에서 깨어나면 그 rq의 utilization signal은 enqueue된 모든 task의 uclamp 설정에 영향을 받는다. 예를 들어 한 task가 UTIL_MIN=512를 요청했다면 rq signal은 그 요청과 rq에 있는 다른 모든 task 요청을 함께 만족시켜야 한다.
rq에 붙은 모든 task의 util clamp 값을 집계하려면 enqueue와 dequeue마다 상태를 관리해야 한다. 이 위치는 scheduler hot path이므로 느려지면 많은 workload에 큰 영향을 주고 기능 자체의 실용성을 떨어뜨린다.
이를 해결하기 위해 전체 utilization 범위를 struct uclamp_bucket 여러 개로 나눈다. 그러면 rq의 모든 task를 검색하지 않고 가장 높은 non-empty bucket의 일부 task만 살펴보면 된다.
task enqueue 시 대응 bucket counter를 증가시키고 dequeue 시 감소시킨다. 이 방식으로 rq 수준의 effective uclamp 값을 더 적은 비용으로 추적한다.
task가 enqueue·dequeue될 때마다 rq의 현재 effective uclamp 값을 갱신한다. 자세한 동작은 2.1절에서 설명한다.
이후 rq의 effective uclamp가 필요한 경로는 의사결정을 내리는 정확한 시점에 이미 집계된 값을 읽기만 하면 된다.
현재 task placement에서는 Energy Aware Scheduling과 Capacity Aware Scheduling, 즉 EAS/CAS만 uclamp를 사용한다. 따라서 placement 영향은 heterogeneous system에 적용된다.
task가 깨어나면 scheduler는 각 rq의 현재 effective uclamp와 이 task를 그 rq에 enqueue했을 때의 예상 값을 비교하고, 가장 energy-efficient한 조합이 되는 rq를 선호한다.
schedutil도 frequency를 갱신할 때 현재 enqueue된 task 집합의 영향을 받은 rq effective uclamp를 읽고, 모든 요청 제약을 만족하는 frequency를 선택한다.
EAS를 사실상 비활성화하는 overutilization 상태를 정하는 경로도 uclamp를 사용한다. 이런 경로는 위 두 주요 사용례를 지원하는 내부 관리에 가깝고 구현 세부에 따라 달라질 수 있어 이 문서에서는 자세히 다루지 않는다.
2.1. Bucket
182-2630부터 1024까지의 utilization 범위를 N개 bucket으로 나누고 task를 요청 값에 맞는 bucket에 집계한다. 이 그림은 개념도이며 실제 내부 자료구조를 그대로 나타낸 것은 아니다.
위 도식은 검색 범위를 줄이는 원리를 설명하기 위한 그림이며 struct rq와 struct uclamp_bucket의 실제 memory layout은 아니다.
enqueue·dequeue 때 rq effective uclamp를 결정하는 검색 범위를 줄이기 위해 전체 utilization 범위를 N개 bucket으로 나눈다. N은 compile time의 CONFIG_UCLAMP_BUCKETS_COUNT로 정하며 기본값은 5다.
rq에는 uclamp_id tunable인 UCLAMP_MIN과 UCLAMP_MAX 각각에 대한 bucket 집합이 있다.
각 bucket 범위는 1024/N이다. 기본값 5에서는 반올림한 DELTA가 205이고 다음 범위를 사용한다.
DELTA = round_closest(1024/5) = 204.8 = 205
Bucket 0: [0:204]
Bucket 1: [205:409]
Bucket 2: [410:614]
Bucket 3: [615:819]
Bucket 4: [820:1024]
다음 tunable을 가진 task p가 rq에 enqueue된다고 하자.
p->uclamp[UCLAMP_MIN] = 300
p->uclamp[UCLAMP_MAX] = 1024
UCLAMP_MIN에서는 bucket 1 counter를, UCLAMP_MAX에서는 bucket 4 counter를 증가시킨다. 이 rq에 각 범위의 task가 존재한다는 사실이 bucket에 반영된다.
rq는 uclamp_id마다 현재 effective uclamp 값을 별도로 추적한다.
task p를 enqueue할 때 rq 값은 다음 논리로 바뀐다.
// bucket 갱신 논리가 이 위치에서 실행된다
rq->uclamp[UCLAMP_MIN] = max(rq->uclamp[UCLAMP_MIN],
p->uclamp[UCLAMP_MIN])
// UCLAMP_MAX에도 같은 과정을 적용한다
task p를 dequeue할 때는 다음과 같이 남은 non-empty bucket 중 가장 높은 값을 찾는다.
// bucket 갱신 논리가 이 위치에서 실행된다
rq->uclamp[UCLAMP_MIN] = search_top_bucket_for_highest_value()
// UCLAMP_MAX에도 같은 과정을 적용한다
모든 bucket이 비면 rq uclamp 값은 system 기본값으로 돌아간다. 기본값은 3.4절에서 설명한다.
2.2-2.4. Max 집계, 계층 집계, 값의 범위
264-334util clamp는 가장 높은 성능 지점을 요구하는 task의 요청을 만족하도록 조정된다.
여러 task가 같은 rq에 있으면 다른 task가 높은 성능을 필요로 하지 않거나 그 지점에 도달하지 못하도록 제한되었더라도, 가장 높은 성능을 요구하는 task가 필요한 성능을 얻을 수 있어야 한다.
예를 들어 같은 rq에 다음 두 task가 있다고 하자.
p0->uclamp[UCLAMP_MIN] = 300
p0->uclamp[UCLAMP_MAX] = 900
p1->uclamp[UCLAMP_MIN] = 500
p1->uclamp[UCLAMP_MAX] = 500
p0와 p1이 모두 같은 rq에 enqueue되면 UCLAMP_MIN과 UCLAMP_MAX 모두 각 요청의 max로 집계된다.
rq->uclamp[UCLAMP_MIN] = max(300, 500) = 500
rq->uclamp[UCLAMP_MAX] = max(900, 500) = 900
MIN과 MAX는 서로 섞지 않고 uclamp_id별로 가장 큰 요청을 rq effective 값으로 선택한다.
5.1절에서 보듯 이 max aggregation은 특히 사용자 공간이 UCLAMP_MAX로 전력을 아끼려 할 때 나타나는 한계의 원인이다.
util clamp는 모든 task의 속성이지만 실제 적용되는 effective 값은 task 자신이나 middleware가 대신 요청한 값만으로 정해지지 않는다.
- task가 속한 cgroup CPU controller의 uclamp 설정이 먼저 task 요청을 제한한다.
- 그 결과는 다시 system-wide uclamp 설정의 제한을 받는다.
구체적인 interface는 3절에서 설명한다. task 요청은 cgroup과 system-wide 설정이 만든 경계를 지켜야 실제 effective 값이 된다.
요청이 현재 제약을 벗어나더라도 system은 값을 설정하는 작업 자체는 받아들인다. 이후 task가 다른 cgroup으로 이동하거나 system administrator가 전역 설정을 바꾸면 새 제약 안에 들어오는 범위에서 그 요청이 만족될 수 있다.
즉 task가 uclamp 값을 바꿀 때 계층 집계 때문에 error가 반환되는 방식이 아니라, 현재 cgroup과 system 설정에 따라 system이 그 요청을 그대로 충족하지 못할 수 있는 방식이다.
uclamp 성능 요청 범위는 0부터 1024까지이며 양 끝을 포함한다. cgroup interface에서는 0부터 100까지의 percentage를 사용하고, 다른 cgroup interface와 마찬가지로 100 대신 max를 쓸 수 있다.
3.1-3.2. Task와 cgroup interface
335-438sched_setattr() system call에는 두 field가 추가되었다.
- sched_util_min은 task가 실행될 때 system이 제공해야 할 최소 performance point, 즉 성능 하한을 요청한다.
- sched_util_max는 task가 실행될 때 system이 넘지 않아야 할 최대 performance point, 즉 성능 상한을 요청한다.
다음은 utilization을 40%에서 80% 사이로 제한하는 예다.
attr->sched_util_min = 40% * 1024;
attr->sched_util_max = 80% * 1024;
task p가 실행될 때 scheduler는 가능한 한 40% 성능 수준에서 시작하도록 해야 한다. task가 충분히 오래 실행되어 실제 utilization이 80%를 넘으면 scheduler 의사결정에 쓰는 utilization 또는 성능 수준을 상한에 맞춘다.
특수값 -1은 uclamp 설정을 system 기본값으로 reset한다.
-1로 기본값을 상속하도록 reset하는 것과 현재 system 기본값 숫자를 직접 설정하는 것은 다르다. RT task의 기본값은 system interface로 바뀔 수 있기 때문에 이 차이가 중요하다. 향후 SCHED_NORMAL/SCHED_OTHER에도 비슷한 knob가 생길 수 있다.
CPU cgroup controller에는 cpu.uclamp.min과 cpu.uclamp.max 두 값이 있다.
cpu.uclamp.min은 cgroup v2 문서 3-3절의 protection이다. task의 uclamp_min이 cpu.uclamp.min보다 낮으면 task는 cgroup의 cpu.uclamp.min을 상속한다. cgroup hierarchy에서 effective cpu.uclamp.min은 child와 parent 중 큰 값이다.
cpu.uclamp.max는 cgroup v2 문서 3-2절의 limit다. task의 uclamp_max가 cpu.uclamp.max보다 높으면 task는 cgroup의 cpu.uclamp.max를 상속한다. hierarchy에서 effective cpu.uclamp.max는 child와 parent 중 작은 값이다.
| 항목 | 계층 결합 | task에 주는 효과 |
|---|---|---|
| cpu.uclamp.min | max(child, parent) | task MIN이 낮으면 cgroup MIN으로 끌어올린다 |
| cpu.uclamp.max | min(child, parent) | task MAX가 높으면 cgroup MAX로 낮춘다 |
다음 task와 cgroup 설정을 예로 든다.
p0->uclamp[UCLAMP_MIN] = // system default;
p0->uclamp[UCLAMP_MAX] = // system default;
p1->uclamp[UCLAMP_MIN] = 40% * 1024;
p1->uclamp[UCLAMP_MAX] = 50% * 1024;
cgroup0->cpu.uclamp.min = 20% * 1024;
cgroup0->cpu.uclamp.max = 60% * 1024;
cgroup1->cpu.uclamp.min = 60% * 1024;
cgroup1->cpu.uclamp.max = 100% * 1024;
p0와 p1을 cgroup0에 붙이면 값은 다음과 같다.
p0->uclamp[UCLAMP_MIN] = cgroup0->cpu.uclamp.min = 20% * 1024;
p0->uclamp[UCLAMP_MAX] = cgroup0->cpu.uclamp.max = 60% * 1024;
p1->uclamp[UCLAMP_MIN] = 40% * 1024; // 그대로 유지
p1->uclamp[UCLAMP_MAX] = 50% * 1024; // 그대로 유지
p0와 p1을 cgroup1에 붙이면 값은 다음처럼 바뀐다.
p0->uclamp[UCLAMP_MIN] = cgroup1->cpu.uclamp.min = 60% * 1024;
p0->uclamp[UCLAMP_MAX] = cgroup1->cpu.uclamp.max = 100% * 1024;
p1->uclamp[UCLAMP_MIN] = cgroup1->cpu.uclamp.min = 60% * 1024;
p1->uclamp[UCLAMP_MAX] = 50% * 1024; // 그대로 유지
cgroup interface는 cpu.uclamp.max를 cpu.uclamp.min보다 낮게 설정하는 것을 허용한다. 다른 uclamp interface에서는 이 관계를 허용하지 않는다.
3.3-3.4. System interface와 기본값
439-5213.3.1 sched_util_clamp_min
sched_util_clamp_min은 허용할 UCLAMP_MIN 범위의 system-wide limit다. 기본값 1024에서는 task가 사용할 수 있는 effective UCLAMP_MIN 범위가 [0:1024]다. 이를 512로 바꾸면 [0:512]가 되어 task가 얻을 수 있는 boost의 최대치를 제한한다.
task가 이 knob보다 큰 값을 요청해도 설정 작업은 성공하지만, knob 값이 p->uclamp[UCLAMP_MIN]보다 커질 때까지 요청은 그대로 만족되지 않는다.
sched_util_clamp_min은 sched_util_clamp_max보다 작거나 같아야 한다.
3.3.2 sched_util_clamp_max
sched_util_clamp_max는 허용할 UCLAMP_MAX 범위의 system-wide limit다. 기본값 1024에서는 task의 effective UCLAMP_MAX 범위가 [0:1024]다.
이를 512로 바꾸면 허용 범위는 [0:512]가 된다. 어떤 task도 512보다 높은 성능에서 실행할 수 없고 모든 rq에도 같은 제한이 적용되므로, system 전체 성능이 절반으로 cap된다.
이 knob는 system 전체의 최대 성능 지점을 제한할 때 유용하다. battery가 부족하거나 idle·screen-off 상태에서 전력 소모가 큰 성능 수준에 접근하지 못하게 하는 용도로 쓸 수 있다.
task가 knob보다 높은 값을 요청해도 설정은 성공하지만, 전역 knob가 p->uclamp[UCLAMP_MAX]보다 커질 때까지 요청은 충족되지 않는다.
sched_util_clamp_max는 sched_util_clamp_min보다 크거나 같아야 한다.
3.4 기본값
기본적으로 모든 SCHED_NORMAL/SCHED_OTHER task는 다음 값으로 초기화된다.
p_fair->uclamp[UCLAMP_MIN] = 0
p_fair->uclamp[UCLAMP_MAX] = 1024
원문 488-490행의 문장은 중간 구절이 빠진 상태다. 값 자체가 뜻하는 바는 fair task가 기본적으로 boost도 cap도 받지 않아 [0:1024] 전체 범위를 사용할 수 있다는 것이다. 이 기본값을 boot 또는 runtime에 바꿀 별도 interface가 필요한지는 아직 근거가 정리되지 않았고 향후 추가될 수 있다.
SCHED_FIFO/SCHED_RR task의 기본값은 다음과 같다.
p_rt->uclamp[UCLAMP_MIN] = 1024
p_rt->uclamp[UCLAMP_MAX] = 1024
RT task는 기본적으로 system의 최대 성능 지점으로 boost된다. 이는 기존 RT task 동작을 유지하기 위한 값이다.
RT task의 기본 uclamp_min은 boot 또는 runtime에 sysctl로 바꿀 수 있다.
3.4.1 sched_util_clamp_min_rt_default
battery device에서 RT task를 항상 최대 성능으로 실행하는 것은 비싸고 반드시 필요하지도 않다. 이 sysctl은 system 요구사항을 만족할 만큼의 성능 보장은 유지하면서 항상 MAX에서 실행해 전력을 낭비하지 않도록 기본 boost 값을 조정한다.
application 개발자는 task별 util clamp interface로 성능과 전력을 직접 고려하는 것이 권장된다. 이상적으로 system designer는 이 knob를 0으로 두고 application이 자신의 성능 요구사항을 관리하게 해야 한다.
4. Util clamp 사용법
522-593util clamp는 사용자 공간이 보조하는 전력·성능 관리 개념을 제공한다. scheduler만으로는 최선의 결정을 내리는 데 필요한 application 의미를 알 수 없지만, 사용자 공간은 util clamp를 통해 task placement와 frequency 선택에 필요한 hint를 줄 수 있다.
application이 실행되는 system을 미리 가정하지 않고 feedback loop로 실행 결과를 관찰하며 값을 동적으로 조정할 때 가장 좋은 결과를 얻는다. 최종 목표는 더 좋은 사용자 경험과 더 나은 performance per watt다.
일부 system과 사용례에서는 정적 설정도 좋은 결과를 내지만 이식성이 문제가 된다. 100, 200, 1024라는 값으로 수행할 수 있는 일의 양은 system마다 다르므로 특정 target system이 없다면 정적 설정을 피하는 편이 좋다.
util clamp를 중심으로 완전한 관리 framework를 만들 수도 있고 application이 직접 interface를 사용할 수도 있다.
4.1 중요한 task와 DVFS latency에 민감한 task boost
GUI task는 wakeup 순간의 util_avg만 보면 frequency를 높일 만큼 busy하지 않을 수 있다. 하지만 사용자 경험을 위해 정해진 시간 안에 작업을 끝내야 하며, wakeup 때 필요한 frequency는 system 성능에 따라 달라진다. 느린 system에서는 높을 수 있고 충분히 빠른 system에서는 낮거나 0일 수도 있다.
task가 deadline을 놓칠 때마다 UCLAMP_MIN을 높이면 다음 wakeup에서는 더 높은 성능에서 시작한다. 각 system에서 deadline을 만족하는 가장 낮은 UCLAMP_MIN에 수렴하도록 조정해야 최적의 performance per watt를 얻는다.
heterogeneous system에서는 이 task를 더 빠른 CPU에 배치하는 것도 중요할 수 있다.
일반적으로 입력값은 단순 utilization이 아니라 task placement와 frequency 선택을 함께 뜻하는 performance level 또는 performance point로 해석해야 한다.
4.2 Background task cap
소개에서 설명한 Android 사례처럼 application은 성능이 중요하지 않지만 busy해져 불필요한 system resource를 쓸 수 있는 background task의 UCLAMP_MAX를 낮출 수 있다.
4.3 Powersave mode
system-wide sched_util_clamp_max로 모든 task가 일반적으로 energy efficiency가 낮은 고성능 지점에서 동작하지 못하게 제한할 수 있다.
같은 결과는 cpufreq governor의 maximum frequency를 낮춰도 얻을 수 있으므로 uclamp만의 고유 기능은 아니다. 더 편리한 대체 interface로 볼 수 있다.
4.4 Application별 성능 제한
middleware나 utility는 application을 실행할 때마다 UCLAMP_MIN/MAX를 설정하는 사용자 option을 제공할 수 있다. 최소 성능을 보장하거나, 성능 저하를 감수하는 대신 application이 system power를 지나치게 소비하지 못하도록 제한한다.
예를 들어 이동 중 laptop에서 kernel을 compile할 때 성능을 희생해 발열과 전력 소비를 낮추면서 browser 성능은 그대로 유지하도록 서로 다른 uclamp 제약을 적용할 수 있다.
5.1. Max aggregation 때문에 UCLAMP_MAX cap이 실패하는 경우
594-628task p0의 실행 성능을 512로 cap했다고 하자.
p0->uclamp[UCLAMP_MAX] = 512
p0가 모든 performance point를 사용할 수 있는 p1과 rq를 공유한다.
p1->uclamp[UCLAMP_MAX] = 1024
max aggregation 때문에 rq에는 최대 performance point가 허용된다.
rq->uclamp[UCLAMP_MAX] = max(512, 1024) = 1024
p0와 p1의 UCLAMP_MIN이 모두 0이라면 rq frequency는 두 task의 실제 utilization에 따라 정해진다.
p1이 작은 task이고 p0가 CPU-intensive task라면 둘이 같은 rq에서 실행된다는 이유만으로 p1이 rq의 frequency cap을 풀어 버린다. p1은 모든 성능 지점을 사용할 권한은 있지만 실제로 높은 frequency가 필요하지 않을 수 있는데도 이 결과가 생긴다.
5.2. UCLAMP_MAX가 PELT util_avg를 왜곡하는 경우
629-714PELT는 signal이 커지면 frequency도 올라 CPU에 일정한 idle 시간이 생긴다고 가정한다. UCLAMP_MAX가 frequency 상승을 막으면 어떤 상황에서는 idle 시간이 완전히 사라진다. task가 계속 busy loop에 머물면 util_avg는 1024까지 올라간다.
앞의 max aggregation 문제와 결합하면 심하게 cap된 task가 작은 non-capped task와 rq를 공유할 때 원하지 않는 frequency spike가 발생할 수 있다.
다음 값을 가진 task p0가 idle CPU에서 깨어난다고 하자.
p0->util_avg = 300
p0->uclamp[UCLAMP_MAX] = 0
rq->uclamp[UCLAMP_MAX] = 0
p0는 그 CPU가 지원하는 minimum frequency Fmin에서 실행된다. 이 작업을 가장 짧은 시간에 끝낼 때의 frequency가 Fmax이므로 Fmax/Fmin 비율이 결과에 중요하다.
Fmax/Fmin이 3이면 최대 환산 부하는 다음과 같다.
300 * (Fmax/Fmin) = 900
900은 1024보다 작으므로 CPU에는 여전히 idle 시간이 생긴다. 실제 util_avg가 정확히 900이 되는 것은 아니며 300과 900 사이에 있다. idle 시간이 남는 동안 p0->util_avg 갱신에는 작은 오차가 생기지만 Fmax/Fmin에 정확히 비례하지는 않는다.
p0->util_avg = 300 + small_error
Fmax/Fmin이 4이면 최대 환산 부하는 다음처럼 1024를 넘는다.
300 * (Fmax/Fmin) = 1200
p0->util_avg = 1024
CPU에 idle 시간이 사라지면 실제 util_avg가 1024에 도달한다. 이때 다음 값을 가진 p1이 같은 CPU에서 깨어난다고 하자.
p1->util_avg = 200
p1->uclamp[UCLAMP_MAX] = 1024
max aggregation에 따라 CPU의 effective UCLAMP_MAX는 1024가 된다. p0가 심하게 throttling된 채 계속 실행되었으므로 rq 상태는 다음과 같다.
p0->util_avg = 1024
p1->util_avg = 200
rq->util_avg = 1024
rq->uclamp[UCLAMP_MAX] = 1024
p0가 throttling되지 않았다면 p0 300과 p1 200을 합쳐 rq->util_avg=500 정도가 되어 CPU의 중간 성능 지점 부근에서 실행했을 것이다. 실제로는 누적된 1024와 풀린 UCLAMP_MAX 때문에 Fmax로 뛰어 frequency spike가 난다.
p0->util_avg = 300
p1->util_avg = 200
rq->util_avg = 500
낮은 frequency에서 idle이 사라져 p0의 util_avg가 포화된 뒤 작은 non-capped task가 UCLAMP_MAX를 풀면, 포화된 rq signal이 즉시 높은 frequency를 요구한다.
5.3. Schedutil 응답 시간의 한계
715-741schedutil에는 세 가지 한계가 있다.
- hardware가 frequency 변경 요청에 응답하는 데는 0이 아닌 시간이 들며, 일부 platform에서는 수 ms가 걸린다.
- fast-switch를 지원하지 않는 system은 worker deadline thread가 깨어나 frequency를 바꿔야 하므로 측정 가능한 overhead가 더해진다.
- schedutil의 rate_limit_us 구간 안에 들어온 새 요청은 버려진다.
작은 task가 시간 제약이 큰 작업을 수행하여 wakeup 직후 특정 performance point가 필요하더라도 이 한계 때문에 기대한 시간 안에 원하는 성능을 받지 못할 수 있다.
이 문제는 uclamp에서만 나타나는 것은 아니지만, uclamp를 사용하면 점진적인 frequency ramp-up·ramp-down 대신 task wakeup 순서와 각 uclamp 값에 따라 frequency 사이를 크게 뛰어다닐 수 있어 더 자주 드러날 수 있다.
문서는 이를 기반 system 능력의 한계로 본다.
schedutil rate_limit_us의 동작은 개선할 여지가 있지만 hardware 응답 시간과 non-fast-switch worker overhead는 해결하기 어렵다. 두 항목은 system의 hard limitation으로 간주한다.
utilization signal에 범위를 씌우는 이유
sched-util-clamp.rst:1-183PELT utilization은 최근 실행 이력을 반영하므로 갑자기 깨어난 latency-sensitive task의 필요 성능을 늦게 따라갈 수 있습니다. uclamp_min은 scheduler가 보는 최소 utilization을 올려 빠른 CPU 또는 높은 주파수를 요청하고, uclamp_max는 최대 utilization을 제한해 background workload의 성능과 전력을 억제합니다.
clamp는 task의 실제 실행 시간을 줄이거나 보장하지 않습니다. CPU selection과 schedutil frequency decision에 전달되는 utilization 표현을 제한합니다. 따라서 CPU가 이미 포화되었거나 thermal/power limit가 개입하면 요청한 성능이 실현되지 않을 수 있습니다.