<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<atom:link href="http://gentoo-zh.org/extern.php?action=feed&amp;tid=846&amp;type=rss" rel="self" type="application/rss+xml" />
		<title><![CDATA[Gentoo中文社区 / Gentoo 之 WALT 负载计算]]></title>
		<link>http://www.gentoo-zh.org/viewtopic.php?id=846</link>
		<description><![CDATA[Gentoo 之 WALT 负载计算 最近发表的帖子。]]></description>
		<lastBuildDate>Wed, 27 Mar 2024 14:33:34 +0000</lastBuildDate>
		<generator>FluxBB</generator>
		<item>
			<title><![CDATA[Gentoo 之 WALT 负载计算]]></title>
			<link>http://www.gentoo-zh.org/viewtopic.php?pid=965#p965</link>
			<description><![CDATA[<p>WALT（Window Assisted Load Tracking窗口辅助负载跟踪算法）的核心算法思想是：以时间窗口长度window为单位，跟踪CPU使用情况，用前面N个窗口的CPU使用情况预测当前窗口的CPU需求。窗口默认长度是20ms，walt_ravg_window = (20000000 / TICK_NSEC) * TICK_NSEC，进程负载计算默认是5个历史窗口(#define RAVG_HIST_SIZE_MAX&#160; 5)，CPU负载计算只用一个历史窗口。要理解WALT算法需要理解几个概念：WALT时间、cpu_scale、freq_scale。</p><p><span class="postimg"><img src="https://www.batsom.net/usr/uploads/2024/03/1611292403.png" alt="FluxBB bbcode 测试" /></span> </p><p>&#160; &#160; &#160; &#160; WALT时间是什么呢，是进程占用CPU时间吗？想象一下，有两个进程，在一个时间窗口内，A进程在2.6G频率上运行了5ms，B进程在小核500M频率上运行了5ms。如果仅考虑CPU占用时间，那么它们的负载是相同的，这显然与实际情况不符。所以WALT时间，不仅要考虑CPU占用时间，还要考虑所在CPU的运算能力、运行频率。函数scale_exec_time()用于计算WALT时间，参数delta是CPU运行时间，rq是进程所在运行队列（与CPU对应），SCHED_CAPACITY_SHIFT是10。 </p><div class="codebox"><pre><code>static u64 scale_exec_time(u64 delta, struct rq *rq)
{
	unsigned long capcurr = capacity_curr_of(cpu_of(rq));
 
	return (delta * capcurr) &gt;&gt; SCHED_CAPACITY_SHIFT;
}
 
unsigned long capacity_curr_of(int cpu)
{
	return cpu_rq(cpu)-&gt;cpu_capacity_orig *
	       arch_scale_freq_capacity(NULL, cpu)
	       &gt;&gt; SCHED_CAPACITY_SHIFT;
} </code></pre></div><p>capacity_curr_of()中cpu_capacity_orig的值最终来源于per_cpu变量cpu_scale。</p><div class="codebox"><pre><code>#define arch_scale_cpu_capacity scale_cpu_capacity
static void update_cpu_capacity(struct sched_domain *sd, int cpu)
{
	unsigned long capacity = arch_scale_cpu_capacity(sd, cpu);
	struct sched_group *sdg = sd-&gt;groups;
	struct max_cpu_capacity *mcc;
	unsigned long max_capacity;
	int max_cap_cpu;
	unsigned long flags;
 
	cpu_rq(cpu)-&gt;cpu_capacity_orig = capacity;
    ...............................................................
}
unsigned long scale_cpu_capacity(struct sched_domain *sd, int cpu)
{
	return per_cpu(cpu_scale, cpu);
}</code></pre></div><p>capacity_curr_of()中arch_scale_freq_capacity()最终是获取的per_cpu变量freq_scale。</p><div class="codebox"><pre><code>#define arch_scale_freq_capacity cpufreq_scale_freq_capacity
unsigned long cpufreq_scale_freq_capacity(struct sched_domain *sd, int cpu)
{
	return per_cpu(freq_scale, cpu);
}</code></pre></div><p>&#160; &#160; &#160; &#160; 上面计算WALT时间的代码，可以简化为如下的运算公式：</p><p><span class="postimg"><img src="https://www.batsom.net/usr/uploads/2024/03/2462733025.png" alt="FluxBB bbcode 测试" /></span> </p><p>&#160; &#160; &#160; &#160; &#160;cpu_scale是当前CPU运算能力尺度化。市面上有各种各样的CPU，它们的运算能力各不相同，同一系列的CPU也会迭代升级运算能力，如果以CPU实际运算能力为参数，很难做到算法的统一。内核用CPU运算能力尺度化来解决该问题，定义系统中最高运算能力核的cpu_scale为1024，其它核的cpu_scale为该(CPU运算能力/最大核运算能力)*1024。CPU各个核的cpu_scale都由SOC厂商给出，MTK平台可以cat各cpu目录下cpu_capacity节点查看cpu_scale。</p><p><span class="postimg"><img src="https://www.batsom.net/usr/uploads/2024/03/173174480.png" alt="FluxBB bbcode 测试" /></span> </p><p><span class="postimg"><img src="https://www.batsom.net/usr/uploads/2024/03/2704819799.png" alt="FluxBB bbcode 测试" /></span> </p><p>&#160; &#160; &#160; &#160; freq_scale是某CPU当前频率运算能力的瓷都化，系统定义当前核最高频率运行时的freq_scale为1024，其它频率的freq_scale为当前频率运算能力/最高频率运算能力*1024。为什么用频率的运算能力比，而不是频率比呢？是因为有些厂商的CPU是CONFIG_NONLINER_FREQ_CTL类型的，它的运算能力与频率不是正比关系。例如MTK的某些SOC就是这样的芯片，它的运算能力与频率不成正比，只能查表来看某频点运算能力。 </p><p><span class="postimg"><img src="https://www.batsom.net/usr/uploads/2024/03/433249682.png" alt="FluxBB bbcode 测试" /></span> </p><p>综上，我们可以把WALT时间的运算公式做如下表示：<br /><span class="postimg"><img src="https://www.batsom.net/usr/uploads/2024/03/2691871479.png" alt="FluxBB bbcode 测试" /></span> </p><p>&#160; &#160; &#160; &#160; 从上面的公式可以看出，进程只有在最大核的最高频点上运行时，其CPU占用时间才会等于WALT时间，其它情况WALT时间都是CPU占用时间按一定比例缩短的结果。</p><p>1.进程负载计算</p><p>&#160; &#160; &#160; &#160; 进程的task_struct中有一个ravg类型的变量用于保存WALT相关的信息。其中mark_start是上一次统计的时间，sum指进程在当前窗口已经运行的WALT时间，数组sum_history[RAVG_HIST_SIZE_MAX]保存进程在前5个历史窗口期内的WALT时间，demand是由sum_history[]历史数据推算出的值。</p><div class="codebox"><pre><code>struct ravg {
	u64 mark_start;
	u32 sum, demand;
	u32 sum_history[RAVG_HIST_SIZE_MAX];
	u32 curr_window, prev_window;
	u16 active_windows;
};</code></pre></div><p>&#160; &#160; &#160; &#160; &#160;在进程切换等情况下，会统计WALT时间，有三种情况。情况一，如果当前时间(wallclock)与上次统计时间(mark_start)在一个时间窗口内，只需要将wallclock - mark_start转换为WALT时间后累加到ravg.sum即可。情况二，如果当前时间(wallclock)与上次统计时间(mark_start)跨了一个窗口，首先计算mark_start到当前窗口起始位置这部分WALT时间，并累加到ravg.sum后调用update_history()，将ravg.sum存入历史窗口。然后计算当前窗口起始时间到现在(wallclock)的这部分WALT时间，并赋值到ravg.sum。情况三，如果当前时间(wallclock)与上次统计时间(mark_start)跨了多个窗口，首先计算mark_start到它下一个窗口起始位置这部分WALT时间，并累加到ravg.sum后调用update_history()，将ravg.sum存入历史窗口。然后计算中间跨过窗口的WALT时间并更新到历史窗口中，最后计算当前窗口起始时间到现在(wallclock)的这部分WALT时间，并赋值到ravg.sum。</p><p><span class="postimg"><img src="https://www.batsom.net/usr/uploads/2024/03/193969208.png" alt="FluxBB bbcode 测试" /></span> </p><div class="codebox"><pre class="vscroll"><code>//进程切换时更新负载
static void __sched notrace __schedule(bool preempt)
{
	struct task_struct *prev, *next;
	unsigned long *switch_count;
	struct pin_cookie cookie;
	struct rq *rq;
	int cpu;
	u64 wallclock;
    ................................................................
	next = pick_next_task(rq, prev, cookie);
	wallclock = walt_ktime_clock();
	walt_update_task_ravg(prev, rq, PUT_PREV_TASK, wallclock, 0);
	walt_update_task_ravg(next, rq, PICK_NEXT_TASK, wallclock, 0);
	clear_tsk_need_resched(prev);
	clear_preempt_need_resched();
    ................................................................
}
 
void walt_update_task_ravg(struct task_struct *p, struct rq *rq,
	     int event, u64 wallclock, u64 irqtime)
{
	if (walt_disabled || !rq-&gt;window_start)
		return;
 
	lockdep_assert_held(&amp;rq-&gt;lock);
    //更新窗口起始位置
	update_window_start(rq, wallclock);
 
	if (!p-&gt;ravg.mark_start)
		goto done;
    //更新进程负载
	update_task_demand(p, rq, event, wallclock);
	update_cpu_busy_time(p, rq, event, wallclock, irqtime);
 
done:
	trace_walt_update_task_ravg(p, rq, event, wallclock, irqtime);
 
	p-&gt;ravg.mark_start = wallclock;
}
 
//更新窗口起始位置
static void update_window_start(struct rq *rq, u64 wallclock)
{
	s64 delta;
	int nr_windows;
 
	delta = wallclock - rq-&gt;window_start;
    ..............................................................
	if (delta &lt; walt_ravg_window)
		return;
 
	nr_windows = div64_u64(delta, walt_ravg_window);
	rq-&gt;window_start += (u64)nr_windows * (u64)walt_ravg_window;
 
	rq-&gt;cum_window_demand = rq-&gt;cumulative_runnable_avg;
}
 
static void update_task_demand(struct task_struct *p, struct rq *rq,
	     int event, u64 wallclock)
{
	u64 mark_start = p-&gt;ravg.mark_start;
	u64 delta, window_start = rq-&gt;window_start;
	int new_window, nr_full_windows;
	u32 window_size = walt_ravg_window;
 
	new_window = mark_start &lt; window_start;
    ................................................................
	//情况一
	if (!new_window) {
		/* The simple case - busy time contained within the existing
		 * window. */
		add_to_task_demand(rq, p, wallclock - mark_start);
		return;
	}
    //情况二、三代码相同，只是情况二nr_full_windows为0
	delta = window_start - mark_start;
	nr_full_windows = div64_u64(delta, window_size);
	window_start -= (u64)nr_full_windows * (u64)window_size;
    //计算mark_start到它下一个窗口起始位置之间的WALT时间
	add_to_task_demand(rq, p, window_start - mark_start);
 
	/* Push new sample(s) into task&#039;s demand history */
	update_history(rq, p, p-&gt;ravg.sum, 1, event);
	//计算中间跨越窗口的WALT时间
	if (nr_full_windows)
		update_history(rq, p, scale_exec_time(window_size, rq),
			       nr_full_windows, event);
 
	/* Roll window_start back to current to process any remainder
	 * in current window. */
	window_start += (u64)nr_full_windows * (u64)window_size;
 
	/* 计算当前窗口起始时间到现在之间的WALT时间 */
	mark_start = window_start;
	add_to_task_demand(rq, p, wallclock - mark_start);
}</code></pre></div><p>&#160; &#160; &#160; &#160; &#160;demand值是在update_history()中更新的，有四种策略可选：WINDOW_STATS_RECENT，用本次更新进来的值；WINDOW_STATS_MAX，用所有历史记录中的最大值；WINDOW_STATS_AVG，用历史记录中的平均值；WINDOW_STATS_MAX_RECENT_AVG，本次更新进来的值与历史平均值中较大的那个。</p><div class="codebox"><pre class="vscroll"><code>#define WINDOW_STATS_RECENT		0
#define WINDOW_STATS_MAX		1
#define WINDOW_STATS_MAX_RECENT_AVG	2
#define WINDOW_STATS_AVG		3
#define WINDOW_STATS_INVALID_POLICY	4
 
static void update_history(struct rq *rq, struct task_struct *p,
			 u32 runtime, int samples, int event)
{
	u32 *hist = &amp;p-&gt;ravg.sum_history[0];
	int ridx, widx;
	u32 max = 0, avg, demand;
	u64 sum = 0;
 
	/* Ignore windows where task had no activity */
	if (!runtime || is_idle_task(p) || exiting_task(p) || !samples)
			goto done;
 
	/* Push new &#039;runtime&#039; value onto stack */
	widx = walt_ravg_hist_size - 1;
	ridx = widx - samples;
	for (; ridx &gt;= 0; --widx, --ridx) {
		hist[widx] = hist[ridx];
		sum += hist[widx];
		if (hist[widx] &gt; max)
			max = hist[widx];
	}
 
	for (widx = 0; widx &lt; samples &amp;&amp; widx &lt; walt_ravg_hist_size; widx++) {
		hist[widx] = runtime;
		sum += hist[widx];
		if (hist[widx] &gt; max)
			max = hist[widx];
	}
 
	p-&gt;ravg.sum = 0;
 
	if (walt_window_stats_policy == WINDOW_STATS_RECENT) {
		demand = runtime;
	} else if (walt_window_stats_policy == WINDOW_STATS_MAX) {
		demand = max;
	} else {
		avg = div64_u64(sum, walt_ravg_hist_size);
		if (walt_window_stats_policy == WINDOW_STATS_AVG)
			demand = avg;
		else
			demand = max(avg, runtime);
	}
    ....................................................................
	if (!task_has_dl_policy(p) || !p-&gt;dl.dl_throttled) {
		if (task_on_rq_queued(p))
			fixup_cumulative_runnable_avg(rq, p, demand);
		else if (rq-&gt;curr == p)
			fixup_cum_window_demand(rq, demand);
	}
 
	p-&gt;ravg.demand = demand;
    ....................................................................
}</code></pre></div><p>&#160; &#160; &#160; &#160; 进程负载的计算公式如下，可以看出1024就是满负载时的值，进程满负载必须满足：在整个时间窗口内都处于运行状态，并且所在核是大核，运行频率是大核最高频率。</p><p><span class="postimg"><img src="https://www.batsom.net/usr/uploads/2024/03/2165974261.png" alt="FluxBB bbcode 测试" /></span> </p><div class="codebox"><pre><code>static inline unsigned long task_util(struct task_struct *p)
{
#ifdef CONFIG_SCHED_WALT
	if (!walt_disabled &amp;&amp; (sysctl_sched_use_walt_task_util ||
				p-&gt;prio &lt; sched_use_walt_nice)) {
		unsigned long demand = p-&gt;ravg.demand;
		return (demand &lt;&lt; SCHED_CAPACITY_SHIFT) / walt_ravg_window;
	}
#endif
	return p-&gt;se.avg.util_avg;
}</code></pre></div><p>2.CPU负载计算</p><p>&#160; &#160; &#160; &#160; CPU负载计算是在update_cpu_busy_time()中完成的，计算方法与进程负载类似。不同的是，CPU负载计算只用了一个历史窗口，就是运行队列中的prev_runnable_sum。</p><div class="codebox"><pre><code>static inline unsigned long cpu_util_freq(int cpu)
{
	unsigned long util = cpu_rq(cpu)-&gt;cfs.avg.util_avg;
	unsigned long capacity = capacity_orig_of(cpu);
 
#ifdef CONFIG_SCHED_WALT
	if (!walt_disabled &amp;&amp; sysctl_sched_use_walt_cpu_util)
		util = div64_u64(cpu_rq(cpu)-&gt;prev_runnable_sum,
				walt_ravg_window &gt;&gt; SCHED_CAPACITY_SHIFT);
#endif
	return (util &gt;= capacity) ? capacity : util;
}</code></pre></div>]]></description>
			<author><![CDATA[dummy@example.com (batsom)]]></author>
			<pubDate>Wed, 27 Mar 2024 14:33:34 +0000</pubDate>
			<guid>http://www.gentoo-zh.org/viewtopic.php?pid=965#p965</guid>
		</item>
	</channel>
</rss>
