WALT算法简介

WALT(Windows-Assist Load Tracing)算法是由Qcom开发, 通过把时间划分为窗口,对

task运行时间和CPU负载进行跟踪计算的方法。为任务调度、迁移、负载均衡及CPU调频

提供输入。

WALT相对PELT算法,更能及时反映负载变化, 更适用于移动设备的交互场景。

1. 不同cpu不同频点的相对runtime     

        task在不同cpu的不同频点上运行,运行时长是不同的。那么怎么度量task的大小,体现task对CPU的能力需求呢?我们知道task在某个cpu某个频点的绝对runtime时间,此runtime期间这个cpu的能力,和最大capacity cpu在最高频点运行的能力,如果能有对应关系, 就可以把这个绝对runtime时间,归一化到相对最大cpu能力的runtime时间。

       

        WALT中,使用wrq->task_exec_scale表征cpu当前频点的能力。把capacity最大的cpu在最高频点运行的能力定义为1024。task_exec_scale的计算方法如下:      

赋值调用路径:walt_update_task_ravg->update_task_rq_cpu_cycles

如果不使用cpu cycles计算负载:

task_exec_scale=

{cpu_cur_freq(cpu)/(wrq->cluster->max_possible_freq)}

*arch_scale_cpu_capacity(cpu)

如果使用cpu cycles计算负载:

task_exec_scale=

{(cycles_delta /time_delta)/(wrq->cluster->max_possible_freq)}

*arch_scale_cpu_capacity(cpu)

wrq->task_exec_scale会在cpu频点发生变化时从新计算更新,此时就可以通过这个归一化后的cpu能力,计算task的相对runtime了。

WALT是通过scale_exec_time函数实现的:

delta * (wrq->task_exec_scale) / 1024

例如,此时task 在cpu0 1G频点对应的task_exec_scale=200运行了10ms,那么task的相对运行时间即为:

10*200/1024=1.953125(ms)

2. scaled runtime

        在实际项目中,WALT定义了5个历史窗口,每个窗口长16ms(可根据帧率动态设置为8ms)。在每次对task runtime delta进行更新时,都要转换为相对runtime。

        但对调度器默认的单位util,还要做进一步的归一化。WALT中把task 在capacity最大cpu最高频点连续运行一个窗口(16ms)时间,定义为最大uti值1024。所以把相对runtime按一个窗口归一化到1024的util值为:

runtime_scaled = (delta/sched_ravg_window)*1024

实现函数为:

scale_time_to_util(runtime)

一、关键数据结构

1. 变量

sched_ravg_window(EXPORT_SYMBOL())

walt负载统计窗口大小

定义变量时初始化:20000000

walt初始化时:walt_init->walt_tunables->DEFAULT_SCHED_RAVG_WINDOW:

300HZ时对齐刷新率:(3333333*5);其它刷新率16000000

单位:ns

NUM_LOAD_INDICES

#define NUM_LOAD_INDICES 1000

把window size(现在是16ms,不会根据动态window size调整)分为1000份

sched_load_granule

unsigned int __read_mostly sched_load_granule

sched_load_granule = DEFAULT_SCHED_RAVG_WINDOW / NUM_LOAD_INDICES

#define DEFAULT_SCHED_RAVG_WINDOW 16000000

计算得知window size分为1000份之后的单位粒度是16000ns=16us

2. 结构体

walt_task_struct

struct walt_task_struct {u32             flags;u64             mark_start;u64             window_start;u32             sum, demand;u32             coloc_demand;u32             sum_history[RAVG_HIST_SIZE];u16             sum_history_util[RAVG_HIST_SIZE];u32             curr_window_cpu[WALT_NR_CPUS];u32             prev_window_cpu[WALT_NR_CPUS];u32             curr_window, prev_window;u8              busy_buckets[NUM_BUSY_BUCKETS];u16             bucket_bitmask;u16             demand_scaled;u16             pred_demand_scaled;u64             active_time;u64             last_win_size;int             boost;bool                wake_up_idle;bool                misfit;bool                rtg_high_prio;u8              low_latency;u64             boost_period;u64             boost_expires;u64             last_sleep_ts;u32             init_load_pct;u32             unfilter;u64             last_wake_ts;u64             last_enqueued_ts;struct walt_related_thread_group __rcu  *grp;struct list_head        grp_list;u64             cpu_cycles;bool                iowaited;int             prev_on_rq;int             prev_on_rq_cpu;struct list_head        mvp_list;u64             sum_exec_snapshot_for_slice;u64             sum_exec_snapshot_for_total;u64             total_exec;int             mvp_prio;int             cidx;int             load_boost;int64_t             boosted_task_load;int             prev_cpu;int             new_cpu;u8              enqueue_after_migration;u8              hung_detect_status;int             pipeline_cpu;
};

3.mark_start:同步wrq->mark_start

记录task某些event的开始时间, 这些event在walt.h task_event中定义。

4.window_start

标识了per task window。同步于wrq->window_start:

walt_update_task_ravg->update_cpu_busy_time:

    /** Handle per-task window rollover. We don't care about the* idle task.*/if (new_window) {if (!is_idle_task(p))rollover_task_window(p, full_window);wts->window_start = window_start;}

新task wts->window_start初始化为0, 在第一次调用walt_update_task_ravg时同步为wrq->window_start

5.wts->sum += scale_exec_time(delta, rq, wts)

计算当前窗口task运行时间总和, 这些时间是实际运行时间delta按cpu当前频率和cpu capacity归一化到最大capacity cpu最高频率的运行时长。比如在最大capacity cpu上以最高频点运行的task,实际运行时间delta和统计到sum的时间是1:1的。

更新路径:

walt_update_task_ravg->update_task_demand->add_to_task_demand->(wts->sum += scale_exec_time(delta, rq, wts))

walt_update_task_ravg->update_task_demand->update_history->(wts->sum = 0), 在event跨窗口时,才会复位为0。

5.wts->demand

历史窗口中统计的相对runtime,通过不同的策略计算出当前采用的相对runtime值

#define WINDOW_STATS_RECENT     0

#define WINDOW_STATS_MAX        1

#define WINDOW_STATS_MAX_RECENT_AVG 2

#define WINDOW_STATS_AVG        3

6.wts->coloc_demand

5个历史窗口task 相对runtime的平均值

wts->coloc_demand = div64_u64((sum += hist[i]), RAVG_HIST_SIZE)

7.wts->sum_history[5]

记录了task 5个历史窗口的相对runtime。相对 runtime,即为task实际running时间归一化到最大capacity cpu最高频点后的相对运行时间, 最大值为16ms。

walt_update_task_ravg()->update_task_demand()->update_history()->

hist[wts->cidx] = runtime

8.wts->sum_history_util[5]

把task在每个历史窗口累加的相对runtime(0ms~16ms), 归一化到(0~1024)的util值, 这里称之为scaled runtime:

walt_update_task_ravg()->update_task_demand()->update_history()->

scale_time_to_util(runtime)=(wts->sum/sched_ravg_window)*1024

9.curr_window_cpu[WALT_NR_CPUS]

统计task在每个cpu上当前窗口中相对runtime

10.prev_window_cpu[WALT_NR_CPUS]

统计task在每个cpu上前一窗口中相对runtime

11.curr_window, prev_window

walt_update_task_ravg()->update_cpu_busy_time()

统计task在当前窗和前一窗中,相对runtime。包含task运行期间的irqtime。

12.参考如何更新wts->bucket_bitmask及busy_buckets[bidx]

13.参考如何更新wts->bucket_bitmask及busy_buckets[bidx]

14.demand_scaled

参考5),把计算出当前的相对runtime值归一化到scaled runtime(0~1024).

walt_update_task_ravg()->update_task_demand()->update_history()->

scale_time_to_util(demand)

16.active_time

is_new_task中判断此值小于100ms就认为是新task,rollover_task_window是唯一调用路径。

28.last_sleep_ts:

记录task被开始sleep的时间, 在__schedule->android_rvh_schedule中, 当prev!=next且!prev->on_rq时, 更新prev task的last_sleep_ts。prev task不再被调度,且不在rq的wait list时, 即表示prev task被schedule out后,进入非running状态。

41.wts->cidx

current idx, 索引hist和hist_util数组中当前窗口, 存储task相对running time和util。在window更新时,在update_history中更新索引值,达到滚动更新历史窗口信息的目的。

task_event

task event标识了task的一组行为,这些行为发生的时间点,是对task demand变化更新的关键时间点。理解walt算法,首先要理解这些event的含义及如何通过这些event更新task demand和cpu 负载。

enum task_event {PUT_PREV_TASK   = 0,PICK_NEXT_TASK  = 1,TASK_WAKE   = 2,TASK_MIGRATE    = 3,TASK_UPDATE = 4,IRQ_UPDATE  = 5,
};

PUT_PREV_TASK/PICK_NEXT_TASK:

调用路径__schedule->android_rvh_schedule()->

walt_update_task_ravg(prev, rq, PUT_PREV_TASK/PICK_NEXT_TASK, wallclock, 0). CPU上发生任务调度,通过pick_next_task选出cpu上将要运行的next task后,通过hook调用核心函数对prev task和next task的util及cpu的负载进行更新。PUT_PREV_TASK标识prev task从rq上被调度走时对prev task的util和cpu负载进行更新。

TASK_WAKE:

调用路径try_to_wake_up->android_rvh_try_to_wake_up(*p)->

walt_update_task_ravg(p, rq, TASK_WAKE, wallclock, 0)

当wakeup一个task, 对非current(当前cpu)&&非runnalbe(不在run queue wait list中) &&非running(其它cpu)的task p,在select_task_rq选择target cpu之前,通过hook调用核心函数。此时的task_cpu(p)为task最后一次running的cpu。

walt_rq

struct walt_rq {struct task_struct  *push_task;struct walt_sched_cluster *cluster;struct cpumask      freq_domain_cpumask;struct walt_sched_stats walt_stats;u64         window_start;u32         prev_window_size;unsigned long       walt_flags;u64         avg_irqload;u64         last_irq_window;u64         prev_irq_time;struct task_struct  *ed_task;u64         task_exec_scale;u64         old_busy_time;u64         old_estimated_time;u64         curr_runnable_sum;u64         prev_runnable_sum;u64         nt_curr_runnable_sum;u64         nt_prev_runnable_sum;struct group_cpu_time   grp_time;struct load_subtractions load_subs[NUM_TRACKED_WINDOWS];DECLARE_BITMAP_ARRAY(top_tasks_bitmap,NUM_TRACKED_WINDOWS, NUM_LOAD_INDICES);u8          *top_tasks[NUM_TRACKED_WINDOWS];u8          curr_table;int         prev_top;int         curr_top;bool            notif_pending;bool            high_irqload;u64         last_cc_update;u64         cycles;u64         util;struct list_head    mvp_tasks;int                     num_mvp_tasks;u64         latest_clock;u32         enqueue_counter;
};

15.task_exec_scale: task所在cpu的当前频点归一化到cpu最大能力1024的值,体现当前频点cpu的能力.

例如在最大cluster中cpu在最高频点运行, task_exec_scale=1024.

赋值调用路径:walt_update_task_ravg->update_task_rq_cpu_cycles

!use_cycle_counter:如果不使用cpu cycles计算负载:task_exec_scale=

{cpu_cur_freq(cpu)/(wrq->cluster->max_possible_freq)}

*arch_scale_cpu_capacity(cpu)

use_cycle_counter:如果使用cpu cycles计算负载:task_exec_scale=

{(cycles_delta /time_delta)/(wrq->cluster->max_possible_freq)}

*arch_scale_cpu_capacity(cpu)

(https://www.kernel.org/doc/Documentation/devicetree/bindings/arm/cpu-capacity.txt)

18.curr_runnable_sum

当前窗口cpu 负载信息

19.prev_runnable_sum

前一窗cpu负载信息

20.nt_curr_runnable_sum

cpu当前窗new task负载信息, wts->active_time小于100ms属于new task

21.nt_prev_runnable_sum

cpu前一窗new task负载信息

24.unsigned long top_tasks_bitmap[2][BITS_TO_LONGS(1000)],

跟踪curr和prev两个窗口。BITS_TO_LONGS(1000)=16, 即为大小为16*64=1024bit

26.u8 *top_tasks[2]

两个指针元素的指针数组, 分别指向大小1000 Byte(u8)类型的mem。记录当前cpu上最近两个窗口curr和prev上, 翻转?

27.curr_table 0?1

使用两个window进行跟踪,标识哪个是curr的,curr和prev构成一个环形数组,不停翻转.

在update_window_start->rollover_top_tasks(rq, full_window)翻转更新:

wrq->curr_table

wrq->prev_top = curr_top

wrq->curr_top = 0

wrq->top_tasks[prev/curr]

wrq->top_tasks_bitmap[prev/curr]

28.prev_top

前窗最大task runtime落在1000个bucket中的index

29.curr_top

是index值,当前窗口的max load_to_index(wts->curr_window), 记录在当前窗口最大task runtime落在的bucket id。如当前窗口运行过task和相对runtime分别为:

A(2ms) B(6ms) C(15ms),

则:wrq->curr_top = load_to_index(15ms)=15*1000/16

34.util

walt gov调频获取cpu util:

waltgov_update_freq->waltgov_get_util(wg_cpu)->cpu_util_freq_walt()->__cpu_util_freq_walt()

wrq->util = scale_time_to_util(freq_policy_load(rq, reason))

37.记录最近一次调用update_window_start的时间戳,但未必满足更新条件(delta \geq sched_ravg_window)对wrq->window_start进行更新

二、核心函数

1. 函数调用关系
   

walt_update_task_ravg是WALT算法的入口函数,

主要函数调用关系如下图。主要涉及:

1. 随着时间推移会产生新的window,更新window的

start time

update_window_start

2.计算 wrq->task_exec_scale, task_exec_scale反

映了在当前频点下cpu的能力,参见:

《二、task_runtime 1. 不同cpu不同频点的相对runtim》

update_task_rq_cpu_cycles

3. 对task runtime和历史记录的更新,实现函数为:

update_task_demand

4. 对task 和 rq curr和prev两个window统计信息的更新

update_cpu_busy_time

5. 更新task预测需求

update_task_pred_demand

2. 函数说明

a) update_window_start(rq, wallclock, event)

      • 更新wrq->window_start += (u64)nr_windows * (u64)sched_ravg_window
      • 更新wrq->prev_window_size = sched_ravg_window
      • 更新wrq的历史窗口统计信息, 初始化新窗口信息:
        update_window_start()->rollover_cpu_window(rq, nr_windows > 1)
rollover_cpu_window(struct rq *rq, bool full_window)
{struct walt_rq *wrq = &per_cpu(walt_rq, cpu_of(rq));u64 curr_sum = wrq->curr_runnable_sum;u64 nt_curr_sum = wrq->nt_curr_runnable_sum;u64 grp_curr_sum = wrq->grp_time.curr_runnable_sum;u64 grp_nt_curr_sum = wrq->grp_time.nt_curr_runnable_sum;if (unlikely(full_window)) {curr_sum = 0;nt_curr_sum = 0;grp_curr_sum = 0;grp_nt_curr_sum = 0;}wrq->prev_runnable_sum = curr_sum;wrq->nt_prev_runnable_sum = nt_curr_sum;wrq->grp_time.prev_runnable_sum = grp_curr_sum;wrq->grp_time.nt_prev_runnable_sum = grp_nt_curr_sum;wrq->curr_runnable_sum = 0;wrq->nt_curr_runnable_sum = 0;wrq->grp_time.curr_runnable_sum = 0;wrq->grp_time.nt_curr_runnable_sum = 0;
}

9)~21):

scheduler_tick()->trace_android_rvh_tick_entry(rq)->update_window_start在每个tick(250HZ 4ms)中断处理函数中触发更新wrq->window_start,所以超过一个window_size(8ms/16ms)满足更新条件(delta >=sched_ravg_window)后,在第二个window_size内还没更新wrq->window_start的情况(full_window=2)极少发生:(异常task长时间关中断运行,或tickless的idle cpu(https://docs.kernel.org/timers/no_hz.html))

当这种情况发生时,因为前窗更新window start异常,prev值只参考前窗, 所以(nr_windows > 1)后更新的prev值设置为0.

  • 更新
    • update_window_start()->rollover_top_tasks(struct rq *rq, bool full_window)
static void rollover_top_tasks(struct rq *rq, bool full_window)
{struct walt_rq *wrq = &per_cpu(walt_rq, cpu_of(rq));u8 curr_table = wrq->curr_table;u8 prev_table = 1 - curr_table;int curr_top = wrq->curr_top;clear_top_tasks_table(wrq->top_tasks[prev_table]);clear_top_tasks_bitmap(wrq->top_tasks_bitmap[prev_table]);if (full_window) {curr_top = 0;clear_top_tasks_table(wrq->top_tasks[curr_table]);clear_top_tasks_bitmap(wrq->top_tasks_bitmap[curr_table]);}wrq->curr_table = prev_table;wrq->prev_top = curr_top;wrq->curr_top = 0;
}

b)update_task_rq_cpu_cycles(p, rq, event, wallclock, irqtime)

主要计算 wrq->task_exec_scale, task_exec_scale反映了在当前频点下cpu的能力,参见walt_rq.task_exec_scale说明。

c)u64 update_task_demand(p, rq, event, wallclock)

此函数是WALT算法计算task demand核心函数,把此次event发生时需要更新的task busy time delta,归一化到最大capacity、最高频点的相对时间,即相对runtime,并累加到wts->sum, 并前滚更新task的历史窗口信息。根据5个历史窗口得到最终表示task大小的demand。详细参见《三、算法 1. 如何更新task demnd》

d)update_cpu_busy_time(p, rq, event, wallclock, irqtime)

详细参考《三、算法 2. 如何更新cpu负载信息》

e)update_task_pred_demand(rq, p, event)

static void update_task_pred_demand(struct rq *rq, struct task_struct *p, int event)
{u16 new_pred_demand_scaled;struct walt_task_struct *wts = (struct walt_task_struct *) p->android_vendor_data1;u16 curr_window_scaled;if (is_idle_task(p))return;if (event != PUT_PREV_TASK && event != TASK_UPDATE &&(!SCHED_FREQ_ACCOUNT_WAIT_TIME ||(event != TASK_MIGRATE &&event != PICK_NEXT_TASK)))return;/** TASK_UPDATE can be called on sleeping task, when its moved between* related groups*/if (event == TASK_UPDATE) {if (!p->on_rq && !SCHED_FREQ_ACCOUNT_WAIT_TIME)return;}curr_window_scaled = scale_time_to_util(wts->curr_window);if (wts->pred_demand_scaled >= curr_window_scaled)return;new_pred_demand_scaled = get_pred_busy(p, busy_to_bucket(curr_window_scaled),curr_window_scaled, wts->bucket_bitmask);if (task_on_rq_queued(p) && (!task_has_dl_policy(p) ||!p->dl.dl_throttled))fixup_walt_sched_stats_common(rq, p,wts->demand_scaled,new_pred_demand_scaled);wts->pred_demand_scaled = new_pred_demand_scaled;
}

10~14.只处理PUT_PREV_TASK,TASK_UPDATE,TASK_MIGRATE,PICK_NEXT_TASK四个event,SCHED_FREQ_ACCOUNT_WAIT_TIME=0 (default)

20~22.当sleep task在不同group间迁移时,TASK_UPDATE会被调用。SCHED_FREQ_ACCOUNT_WAIT_TIME=0(default),不对sleep task 更新。

25.(wts->curr_window/sched_ravg_window)*1024, 把wts->curr_window以ns为单位的数值归一化到1024,归一化后的值为util。

29.参考《三、算法 1.如何更新task runtime》

f)run_walt_irq_work_rollover(old_window_start, rq)

三、算法

1. 如何更新task runtime

  • WALT(Windows-Assist Load Tracing)算法是由Qcom开发, 通过把时间划分为窗口,对task运行时间和CPU负载进行跟踪计算的方法。为任务调度、迁移、负载均衡及CPU调频提供输入。

  • WALT相对PELT算法,更能及时反映负载变化, 更适用于移动设备的交互场景。

不同cpu不同频点的相对runtime     

        task在不同cpu的不同频点上运行,运行时长是不同的。那么怎么度量task的大小,体现task对CPU的能力需求呢?我们知道task在某个cpu某个频点的绝对runtime时间,此runtime期间这个cpu的能力,和最大capacity cpu在最高频点运行的能力,如果能有对应关系, 就可以把这个绝对runtime时间,归一化到相对最大cpu能力的runtime时间。

       

        WALT中,使用wrq->task_exec_scale表征cpu当前频点的能力。把capacity最大的cpu在最高频点运行一个window定义为1024。task_exec_scale的计算方法如下:      

赋值调用路径:walt_update_task_ravg->update_task_rq_cpu_cycles

如果不使用cpu cycles计算负载:

task_exec_scale=

{cpu_cur_freq(cpu)/(wrq->cluster->max_possible_freq)}

*arch_scale_cpu_capacity(cpu)

如果使用cpu cycles计算负载:

task_exec_scale=

{(cycles_delta /time_delta)/(wrq->cluster->max_possible_freq)}

*arch_scale_cpu_capacity(cpu)

wrq->task_exec_scale会在cpu频点发生变化时从新计算更新,此时就可以通过这个归一化后的cpu能力,计算task的相对runtime了。

WALT是通过scale_exec_time函数实现的:

delta * (wrq->task_exec_scale) / 1024

例如,此时task 在cpu0 1G频点对应的task_exec_scale=200运行了10ms,那么task的相对运行时间即为:

10*200/1024=1.953125(ms)

scaled runtime

        在实际项目中,WALT定义了5个历史窗口,每个窗口长16ms(可根据帧率动态设置为8ms)。在每次对task runtime delta进行更新时,都要转换为相对runtime。

        但对调度器默认的单位util,还要做进一步的归一化。WALT中把task 在capacity最大cpu最高频点连续运行一个窗口(16ms)时间,定义为最大uti值1024。所以把相对runtime按一个窗口归一化到1024的util值为:

runtime_scaled = (delta/sched_ravg_window)*1024

实现函数为:

scale_time_to_util(runtime)


ms: wts->mark_start: event的开始时间

ws: wrq->window_start

wc: wallclock: 当前event时间戳,walt_sched_clock()->sched_clock()


在计算task的demand时分三种情况:

    • event未跨越窗口
    • event跨两个窗口
    • event跨多个窗口

update_task_demand
()是WALT算法的核心函数,计算task 相对runtime并更新历史窗口(默认配置记录5个history window)。涉及三个主要函数 :

1) account_busy_for_task_demand(struct rq *rq, struct task_struct *p, int event)
判断task的busy time, 对应event是否应该累加task running time。

static int
account_busy_for_task_demand(struct rq *rq, struct task_struct *p, int event)
{/** No need to bother updating task demand for the idle task.*/if (is_idle_task(p))return 0;/** When a task is waking up it is completing a segment of non-busy* time. Likewise, if wait time is not treated as busy time, then* when a task begins to run or is migrated, it is not running and* is completing a segment of non-busy time.*/if (event == TASK_WAKE || (!SCHED_ACCOUNT_WAIT_TIME &&(event == PICK_NEXT_TASK || event == TASK_MIGRATE)))return 0;/** The idle exit time is not accounted for the first task _picked_ up to* run on the idle CPU.*/if (event == PICK_NEXT_TASK && rq->curr == rq->idle)return 0;/** TASK_UPDATE can be called on sleeping task, when its moved between* related groups*/if (event == TASK_UPDATE) {if (rq->curr == p)return 1;return p->on_rq ? SCHED_ACCOUNT_WAIT_TIME : 0;}return 1;
}

如何确定event发生时, task在rq上是busy状态, 并应该被累加到task running time中呢?

7.不统计idle task的demand。

16.TASK_WAKE视为非busytime。SCHED_ACCOUNT_WAIT_TIME = flase, PICK_NEXT_TASK, TASK_MIGRATE event视为非busy time,不统计task demand。

24.如果设置了SCHED_ACCOUNT_WAIT_TIME,并且event=PICK_NEXT_TASK,当task被调度到idle cpu运行时,cpu退出idle的时间不能视为task的busy time,不统计。

31.event == TASK_UPDATE 且

        • task p正在running,属于busy time。
        • task p在等待队列中,可以通过配置 SCHED_ACCOUNT_WAIT_TIME 决定是否把task在等待队列中的时间视为busy time;
          非running非runnable不统计。

38.排除以上情况,默认是需要更新的。

2) update_history(rq, p, runtime, samples, event)

WALT记录了task 5个历史窗口运行时间。在event跨窗口时统计task busy time,因为产生了新的历史窗口, 需要对task历史窗口runtime进行滚动更新。使用wts->cidx作为hist[]数组中current window的索引,通过更新索引值达到滚动更新历史信息。这里的runtime,是task在被更新窗口内的runtime,即wts->sum。当跨越并更新full window时,为window_size(16ms)的相对runtime(大小取决于当前频点计算出的wrq->task_exec_scale)。

static void update_history(struct rq *rq, struct task_struct *p,u32 runtime, int samples, int event)
{  runtime_scaled = scale_time_to_util(runtime);/* Push new 'runtime' value onto stack */for (; samples > 0; samples--) {hist[wts->cidx] = runtime;hist_util[wts->cidx] = runtime_scaled;wts->cidx = ++(wts->cidx) % RAVG_HIST_SIZE;}

wts->demand的计算策略

WINDOW_STATS_RECENT

取上一窗的wts->sum作为demand

WINDOW_STATS_MAX

取5个历史窗口中最大值作为task demand

WINDOW_STATS_MAX_RECENT_AVG

取 max{5个历史窗口的平均值,上一窗的wts->sum},作为task demand

WINDOW_STATS_AVG

取5个窗口的平均值作为task demnad

如何计算predict task demand?

static inline u16 predict_and_update_buckets(struct task_struct *p, u16 runtime_scaled) {int bidx;u32 pred_demand_scaled;struct walt_task_struct *wts = (struct walt_task_struct *) p->android_vendor_data1;bidx = busy_to_bucket(runtime_scaled);pred_demand_scaled = get_pred_busy(p, bidx, runtime_scaled, wts->bucket_bitmask);bucket_increase(wts->busy_buckets, &wts->bucket_bitmask, bidx);return pred_demand_scaled;
}

在调用update_history更新task的历史runtime后,会调用get_pred_busy()预测task的runtime,即未来task对cpu能力的需求。把0~1024的scaled runtime划分为16个buckets,每个buckets对应64 util,把要更新的runtime_scaled通过busy_to_bucket映射到对应的bucket id,作为起始start bucket id,根据wts->bucket_bitmask, 向上寻找第一个置位的bit n, 表示在之前的窗口, task的runtime落到这个bit所对应的busy_buckets[n],这是一个busy bucket。如果hist_util[5]记录的5个最近历史window的task runtime有落在这个busy_buckets[n]范围内的(例如hist_util[m]), 则这个hist_util[m]作为预测的task predict demand返回。

如何更新wts->bucket_bitmask及busy_buckets[bidx]?

#define INC_STEP 8
#define DEC_STEP 2
#define CONSISTENT_THRES 16
#define INC_STEP_BIG 16
static inline void bucket_increase(u8 *buckets, u16 *bucket_bitmask, int idx)
{int i, step;for (i = 0; i < NUM_BUSY_BUCKETS; i++) {if (idx != i) {if (buckets[i] > DEC_STEP)buckets[i] -= DEC_STEP;else {buckets[i] = 0;*bucket_bitmask &= ~BIT_MASK(i);}} else {step = buckets[i] >= CONSISTENT_THRES ?INC_STEP_BIG : INC_STEP;if (buckets[i] > U8_MAX - step)buckets[i] = U8_MAX;elsebuckets[i] += step;*bucket_bitmask |= BIT_MASK(i);}}
}

wts->bucket_bitmask的16个bit位对应16个busy_buckets,表示对应的bucket是否还处于busy状态。处于busy状态的bucket会在计算task的predict demand时被选中。当调用update_history更新task的历史负载时,需要更新进历史窗口的task scaled runtime通过busy_to_bucket(runtime_scaled)获取到runtime_scaled所属的bucket id(参数idx),并将bucket_bitmask中的对应的bit置位,表示task在历史窗口的runtime曾经落在这个bucket,表征了task在历史窗口中运行的大小特性。每次调用update_history更新的runtime,runtime落在的bucket,对应的busy_buckets[bidx]值会步进增加,其它busy_buckets则会衰减,当衰减到小于DEC_STEP时,busy_buckets[i]被清零,对应的bit mask被清除。这样busy_buckets[bidx]就反映了近期task runtime特征,值越大表明task在历史窗口的负载越多的落在这个bucket。bucket_bitmask[bit_n]=0表示task的runtime已经很久没有落在这个bucket区间了。目前bucket_bitmask[bidx]的值除了步进或衰减以设置或清除bucket_bitmask ,没有其它用途。

3) add_to_task_demand(struct rq *rq, struct task_struct *p, u64 delta)

static u64 add_to_task_demand(struct rq *rq, struct task_struct *p, u64 delta)
{struct walt_task_struct *wts = (struct walt_task_struct *) p->android_vendor_data1;delta = scale_exec_time(delta, rq, wts);wts->sum += delta;if (unlikely(wts->sum > sched_ravg_window))wts->sum = sched_ravg_window;return delta;
}

当前窗口task busy time统计,把实际运行时间delta按cpu当前频率和capacity归一化到最大capacity cpu上最高频率运行的时长。累加到到wts->sum。wts->sum最大值为sched_ravg_window,即16ms。在event跨窗口时调用update_history归0,从新开始统计新窗口task的相对运行时间。参考wts.sum。

2. 如何更新cpu负载信息

update_cpu_busy_time是cpu负载信息更新核心函数, 将task runningtime或irqtime更新到:

wrq->grp_time->curr/prev_runnable_sum

wrq->grp_time->nt_curr/prev_runnable_sum

wts->curr/prev_window

wts->curr/prev_window_cpu[cpu]

task属于grp时才更grp_time对应的 sum,irq只更新到wrq对应的sum。

关键函数包括:

1) rollover_task_window(p, full_window)

如果非idle task跨窗口 ,需要将curr_window赋值给prev_window, rollover_task_window task的runtime统计:
wts->curr/prev_window
wts->curr/prev_window_cpu[i]

wts->active_time

static void rollover_task_window(struct task_struct *p, bool full_window)
{u32 *curr_cpu_windows = empty_windows;u32 curr_window;int i;struct walt_rq *wrq = &per_cpu(walt_rq, cpu_of(task_rq(p)));struct walt_task_struct *wts = (struct walt_task_struct *) p->android_vendor_data1;/* Rollover the sum */curr_window = 0;if (!full_window) {curr_window = wts->curr_window;curr_cpu_windows = wts->curr_window_cpu;}wts->prev_window = curr_window;wts->curr_window = 0;/* Roll over individual CPU contributions */for (i = 0; i < nr_cpu_ids; i++) {wts->prev_window_cpu[i] = curr_cpu_windows[i];wts->curr_window_cpu[i] = 0;}if (is_new_task(p))wts->active_time += wrq->prev_window_size;
}

3 10 17 22.当经历一个完整窗口,表示可能存在异常,前窗清零:

wts->prev_window=0,

wts->prev_window_cpu[i]=0

13 14 17 22. 当只是跨一个窗口,更新:

wts->prev_window = wts->curr_window

wts->prev_window_cpu[i] = wts->curr_window_cpu[i]

18 23.wts->curr_window和wts->curr_window_cpu[i]在跨窗口时会被重置为0

2) account_busy_for_cpu_time(rq, p, irqtime, event)

walt_update_task_ravg->update_cpu_busy_time->account_busy_for_cpu_time

static int account_busy_for_cpu_time(struct rq *rq, struct task_struct *p,u64 irqtime, int event)
{if (is_idle_task(p)) {/* TASK_WAKE && TASK_MIGRATE is not possible on idle task! */if (event == PICK_NEXT_TASK)return 0;/* PUT_PREV_TASK, TASK_UPDATE && IRQ_UPDATE are left */return irqtime || cpu_is_waiting_on_io(rq);}if (event == TASK_WAKE)return 0;if (event == PUT_PREV_TASK || event == IRQ_UPDATE)return 1;/** TASK_UPDATE can be called on sleeping task, when its moved between* related groups*/if (event == TASK_UPDATE) {if (rq->curr == p)return 1;return p->on_rq ? SCHED_FREQ_ACCOUNT_WAIT_TIME : 0;}/* TASK_MIGRATE, PICK_NEXT_TASK left */return SCHED_FREQ_ACCOUNT_WAIT_TIME;
}

4.先看task是否是idle task。对于非idle task,再根据event具体判断cpu的busy time

10.p是idle task, 此时只有更新irqtime和cpu在等待io(rq->nr_iowait >0), cpu才属于busytime

13.非idle task的TASK_WAKE event发生时,cpu不是busytime

16.非idle taks的PUT_PREV_TASK 和 IRQ_UPDATE属于busytime

23.非idle taks,TASK_UPDATE时

24.p是curr task,cpu在busytime
27.p不是curr task,但是在runqueue的wait list中,取决于是否设置了SCHED_FREQ_ACCOUNT_WAIT_TIME
27.p不是curr task,也不是runnable状态,cpu不在busytime

31.非idle task,TASK_MIGRATE, PICK_NEXT_TASK对cpu来说,取决于是否设置了SCHED_FREQ_ACCOUNT_WAIT_TIME

3) update_top_tasks(p, rq, old_curr_window, new_window, full_window)

调用路径:

walt_update_task_ravg->update_cpu_busy_time->update_top_tasks

这里的old_curr_window是此次更新前的wts->curr_window。此函数作用是在更新

wrq->top_tasks[curr/prev],

wrq->top_tasks_bitmap[curr/prev]

wrq->top_tasks[2]是两个指针的指针数组,分别记录task在curr和prev window的相对runtime。分别指向1000个u8类型的mem, 即把0~16ms分成1000个buckets,每个bucket为16us。task在curr/prev window的相对runtime如果落在某个bucket,对应bucket的值+1,当bucket值等于1时,置位对应的top_tasks_bitmap位,表示此bucket为busy bucket。当bucket值从1变为0时,clear对应的top_tasks_bitmap位,表示此bucket为非busy bucket。

static void update_top_tasks(struct task_struct *p, struct rq *rq,u32 old_curr_window, int new_window, bool full_window)
{struct walt_rq *wrq = &per_cpu(walt_rq, cpu_of(rq));struct walt_task_struct *wts = (struct walt_task_struct *) p->android_vendor_data1;u8 curr = wrq->curr_table;u8 prev = 1 - curr;u8 *curr_table = wrq->top_tasks[curr];u8 *prev_table = wrq->top_tasks[prev];int old_index, new_index, update_index;u32 curr_window = wts->curr_window;u32 prev_window = wts->prev_window;bool zero_index_update;if (old_curr_window == curr_window && !new_window)return;old_index = load_to_index(old_curr_window);new_index = load_to_index(curr_window);if (!new_window) {zero_index_update = !old_curr_window && curr_window;if (old_index != new_index || zero_index_update) {if (old_curr_window)curr_table[old_index] -= 1;if (curr_window)curr_table[new_index] += 1;if (new_index > wrq->curr_top)wrq->curr_top = new_index;}if (!curr_table[old_index])__clear_bit(NUM_LOAD_INDICES - old_index - 1,wrq->top_tasks_bitmap[curr]);if (curr_table[new_index] == 1)__set_bit(NUM_LOAD_INDICES - new_index - 1,wrq->top_tasks_bitmap[curr]);return;}

没跨窗口

21.没跨窗口,更新前的wts->curr_window是0,当前非0,表示task在这个window里第一次被统计

23.如果此次更新task的runtime发生了变化,或者是第一次被统计busy time

25.curr_table指向1000个u8类型的mem, 更新前task runtime所属bucket id即为old_index,因为在同一window中发生了再次更新,原来task runtime落在的curr_table[old_index]减一,当前task runtime落在的curr_table[new_index]加一。

跨一个或多个窗口

    /** The window has rolled over for this task. By the time we get* here, curr/prev swaps would has already occurred. So we need* to use prev_window for the new index.*/update_index = load_to_index(prev_window);if (full_window) {/** Two cases here. Either 'p' ran for the entire window or* it didn't run at all. In either case there is no entry* in the prev table. If 'p' ran the entire window, we just* need to create a new entry in the prev table. In this case* update_index will be correspond to sched_ravg_window* so we can unconditionally update the top index.*/if (prev_window) {prev_table[update_index] += 1;wrq->prev_top = update_index;}if (prev_table[update_index] == 1)__set_bit(NUM_LOAD_INDICES - update_index - 1,wrq->top_tasks_bitmap[prev]);} else {zero_index_update = !old_curr_window && prev_window;if (old_index != update_index || zero_index_update) {if (old_curr_window)prev_table[old_index] -= 1;prev_table[update_index] += 1;if (update_index > wrq->prev_top)wrq->prev_top = update_index;if (!prev_table[old_index])__clear_bit(NUM_LOAD_INDICES - old_index - 1,wrq->top_tasks_bitmap[prev]);if (prev_table[update_index] == 1)__set_bit(NUM_LOAD_INDICES - update_index - 1,

8.跨多个窗口

25.跨一个窗口

top task的使用

waltgov_update_freq->waltgov_get_util(wg_cpu)->cpu_util_freq_walt()->__cpu_util_freq_walt()-> 

util = scale_time_to_util(freq_policy_load(rq, reason))

wrq->util = util->

freq_policy_load(rq, reason)->tt_load = top_task_load(rq)

WALT在调频获取cpu的util时, 会使用top task load:

freq_policy_load->tt_load = top_task_load(rq)

tt_load = (wrq->prev_top + 1) * sched_load_granule

这里的wrq->prev_top表示前窗中,最大相对runtime时长的task,落在1000个bucket中的index值。

 551 /*552  * Special case the last index and provide a fast path for index = 0.553  * Note that sched_load_granule can change underneath us if we are not554  * holding any runqueue locks while calling the two functions below.555  */556 static u32 top_task_load(struct rq *rq)557 {558     struct walt_rq *wrq = &per_cpu(walt_rq, cpu_of(rq));559     int index = wrq->prev_top;560     u8 prev = 1 - wrq->curr_table;561 562     if (!index) {563         int msb = NUM_LOAD_INDICES - 1;564 565         if (!test_bit(msb, wrq->top_tasks_bitmap[prev]))566             return 0;567         else568             return sched_load_granule;569     } else if (index == NUM_LOAD_INDICES - 1) {570         return sched_ravg_window;571     } else {572         return (index + 1) * sched_load_granule;573     }574 }

  • packing cpu

walt_find_and_choose_cluster_packing_cpu

1068 /* walt_find_and_choose_cluster_packing_cpu - Return a packing_cpu choice common for this cluster.                                                                                                                                     
1069  * @start_cpu:  The cpu from the cluster to choose from                                                                                                                                                                                
1070  *                                                                                                                                                                                                                                     
1071  * If the cluster has a 32bit capable cpu return it regardless                                                                                                                                                                         
1072  * of whether it is halted or not.                                                                                                                                                                                                     
1073  *                                                                                                                                                                                                                                     
1074  * If the cluster does not have a 32 bit capable cpu, find the                                                                                                                                                                         
1075  * first unhalted, active cpu in this cluster.                                                                                                                                                                                         
1076  *                                                                                                                                                                                                                                     
1077  * Returns -1 if packing_cpu if not found or is unsuitable to be packed on  to                                                                                                                                                         
1078  * Returns a valid cpu number if packing_cpu is found and is usable                                                                                                                                                                    
1079  */                                                                                                                                                                                                                                    
1080 static inline int walt_find_and_choose_cluster_packing_cpu(int start_cpu, struct task_struct *p)                                                                                                                                       
1081 {                                                                                                                                                                                                                                      
1082     struct walt_rq *wrq = &per_cpu(walt_rq, start_cpu);                                                                                                                                                                                
1083     struct walt_sched_cluster *cluster = wrq->cluster;                                                                                                                                                                                 
1084     cpumask_t unhalted_cpus;                                                                                                                                                                                                           
1085     int packing_cpu;                                                                                                                                                                                                                   
1086                                                                                                                                                                                                                                        
1087     /* if idle_enough feature is not enabled */                                                                                                                                                                                        
1088     if (!sysctl_sched_idle_enough)                                                                                                                                                                                                     
1089         return -1;                                                                                                                                                                                                                     
1090     if (!sysctl_sched_cluster_util_thres_pct)                                                                                                                                                                                          
1091         return -1;                                                                                                                                                                                                                     
1092                                                                                                                                                                                                                                        
1093     /* find all unhalted active cpus */                                                                                                                                                                                                
1094     cpumask_andnot(&unhalted_cpus, cpu_active_mask, cpu_halt_mask);                                                                                                                                                                    
1095                                                                                                                                                                                                                                        
1096     /* find all unhalted active cpus in this cluster */                                                                                                                                                                                
1097     cpumask_and(&unhalted_cpus, &unhalted_cpus, &cluster->cpus);                                                                                                                                                                       
1098                                                                                                                                                                                                                                        
1099     if (is_compat_thread(task_thread_info(p)))                                                                                                                                                                                         
1100         /* try to find a packing cpu within 32 bit subset */                                                                                                                                                                           
1101         cpumask_and(&unhalted_cpus, &unhalted_cpus, system_32bit_el0_cpumask());                                                                                                                                                       
1102                                                                                                                                                                                                                                        
1103     /* return the first found unhalted, active cpu, in this cluster */                                                                                                                                                                 
1104     packing_cpu = cpumask_first(&unhalted_cpus);                                                                                                                                                                                       
1105                                                                                                                                                                                                                                        
1106     /* packing cpu must be a valid cpu for runqueue lookup */                                                                                                                                                                          
1107     if (packing_cpu >= nr_cpu_ids)                                                                                                                                                                                                     
1108         return -1;                                                                                                                                                                                                                     
1109                                                                                                                                                                                                                                        
1110     /* if cpu is not allowed for this task */                                                                                                                                                                                          
1111     if (!cpumask_test_cpu(packing_cpu, p->cpus_ptr))                                                                                                                                                                                   
1112         return -1;
1113                                                                                                                                                                                                                                        
1114     /* if cluster util is high */                                                                                                                                                                                                      
1115     if (sched_get_cluster_util_pct(cluster) >= sysctl_sched_cluster_util_thres_pct)                                                                                                                                                    
1116         return -1;                                                                                                                                                                                                                     
1117                                                                                                                                                                                                                                        
1118     /* if cpu utilization is high */                                                                                                                                                                                                   
1119     if (cpu_util(packing_cpu) >= sysctl_sched_idle_enough)                                                                                                                                                                             
1120         return -1;                                                                                                                                                                                                                     
1121                                                                                                                                                                                                                                        
1122     /* don't pack big tasks */                                                                                                                                                                                                         
1123     if (task_util(p) >= sysctl_sched_idle_enough)                                                                                                                                                                                      
1124         return -1;                                                                                                                                                                                                                     
1125                                                                                                                                                                                                                                        
1126     if (task_reject_partialhalt_cpu(p, packing_cpu))                                                                                                                                                                                   
1127         return -1;                                                                                                                                                                                                                     
1128                                                                                                                                                                                                                                        
1129     /* don't pack if running at a freq higher than 43.9pct of its fmax */                                                                                                                                                              
1130     if (arch_scale_freq_capacity(packing_cpu) > 450)                                                                                                                                                                                   
1131         return -1;                                                                                                                                                                                                                     
1132                                                                                                                                                                                                                                        
1133     /* the packing cpu can be used, so pack! */                                                                                                                                                                                        
1134     return packing_cpu;                                                                                                                                                                                                                
1135 }

代码看起来是的,

1. 首先要设置sysctl_sched_idle_enough, 表示让cpu尽可能进入idle状态,packing才会生效;

2. 在start cpu对应的cluster里,考虑cpu_active_mask,cpu_halt_mask,如果是32位进程考虑支持32位进程的cpu mask system_32bit_el0_cpumask, 考虑task的cpu affinity p->cpus_ptr。在符合这些条件的cpu中找packing cpu。

3. 检查cluster的cpu使用率(前一个window)是否大于sysctl_sched_cluster_util_thres_pct(40), 只有cluster负载轻时才packing

4. 检查找到的packing cpu的cpu util是否大于sysctl_sched_idle_enough(30), 只有cpu的util小于30时才能被选座packing cpu。

5. 检查将要packing的task的util是否大于sysctl_sched_idle_enough(30),只有task util小于30的小task才能packing

6. 检查packing cpu是否能是partial halt cpu(加入uifirst和 frameboost检查)

7. 检查packing cpu当前频点的freq_scale是否小于450. 要求packing cpu的当前要处在较低频点。freq_scale=1024*(cpu_curr_freq/cpu_max_freq)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/848927.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

String类知识

目录 一、String存在意义 二、字符串为何不可变 三、String类常用方法 1、字符串构造 2、String对象的比较 3、字符串查找 4、转化 &#xff08;1&#xff09;数值和字符转化 &#xff08;2&#xff09;大小写转换 &#xff08;3&#xff09;字符串转数组 &#xff08;4&…

系统架构设计师【补充知识】: 应用数学 (核心总结)

24.1 图论之最小生成树 (1)定义: 在连通的带权图的所有生成树中&#xff0c;权值和最小的那棵生成树(包含图中所有顶点的树)&#xff0c;称作最小生成树。 (2)针对问题: 带权图的最短路径问题。 (3)最小生成树的解法有普里姆(Prim)算法和克鲁斯卡尔(Kruskal)算法&#xff0c;我…

重复文件怎么查找并清理,试试这5个文件去重方法(新)

重复文件怎么查找并清理&#xff1f;日常工作中&#xff0c;我们使用电脑的时间长了&#xff0c;都会累积大量好的文件&#xff0c;这其中难免会出现重复文件。这些重复文件不仅占用了电脑磁盘空间&#xff0c;还会降低电脑性能。因此&#xff0c;我们必须定期对重复文件查找出…

fastapi学习前置知识点

前置知识点 FastApi&#xff1a;一个用于构建API的现代、快速&#xff08;高性能&#xff09;的web框架。 FastApi是建立在Pydantic和Starlette基础上&#xff0c;Pydantic是一个基于Python类型提示来定义数据验证、序列化和文档的库。Starlette是一种轻量级的ASGI框架/工具包…

GIF录屏工具Gif123 v3.3.0单文件

软件介绍 GIF的优势是小、轻、快&#xff0c;适合时间短、画面小、需要嵌入其他页面&#xff0c;打开就自动循环播放的动画。Gif123可录制合成鼠标轨迹,可调整鼠标指针大小,可在设置中打开鼠标指针高亮光圈功能,高亮光圈可跟随鼠标移动以指示鼠标位置。软件极其简单&#xff0…

流水线建构apk、abb实战(二)

gradlew 命令生成apk、aab包 其实构建应用程序包就几个命令&#xff1a; ### 生成AAB&#xff1a; gradlew bundleRelease #输出到[project]/build/outputs/bundle/release/下 gradlew bundleDebug### 生成APK&#xff1a; gradlew assembleRelease gradlew assembleDebug###…

菜刀冰蝎哥斯拉流量通讯特征绕过检测反制感知

1.加密流程 工具名称requestsresponseAntSwordbase64等方式明文冰蝎2.0开启Openssl扩展-动态密钥aes加密aes加密base64未开启Openssl扩展-异或异或base64冰蝎3.0开启Openssl扩展-静态密钥aes加密aes加密base64未开启Openssl扩展-异或异或base64哥斯拉php的为base64异或base64异…

游戏《酒店业领袖》

为快餐连锁店麦当劳&#xff0c;我们创建了一款名为“好客领袖”的游戏。麦当劳的员工可以在网站上注册&#xff0c;并测试自己是否扮演酒店领导的角色&#xff0c;在餐厅可能出现的各种情况下快速做出决定。奖品等待着那些在比赛中表现最好的人。 对于该项目&#xff0c;我们&…

【Meetup】探索Apache SeaTunnel的二次开发与实战案例

在数据科技快速演进的今天&#xff0c;业务场景的复杂化和数据量的激增&#xff0c;推动了大数据技术的迅速发展&#xff0c;在众多开源大数据处理工具中&#xff0c;Apache SeaTunnel以其强大的数据集成能力&#xff0c;成为众多企业的首选。 但随着应用深入&#xff0c;企业面…

【三更新】多分式标注 脚本工具

这篇是 多分式标注 脚本工具的第三次更新文章。 原文章及历次更新请点击链接 【ArcGIS 脚本工具】生成多分式标注 【更新】多分式标注 脚本工具 【再更新】多分式标注&#xff08;船新版本&#xff09; 更新内容 1、组合字段标注 分子、分母、前分、后分&#xff0c;四个…

流水线建构apk、abb实战(一)

在构建机上需要下载的工具 流水线中的构建机无法使用Android Studio中自带的sdk工具下载&#xff0c;所以得下载commandlinetools命令行工具&#xff0c;下载后使用随附的 sdkmanager 下载其他 SDK 软件&#xff0c;解压后按照/cmdline-tools/latest/bin/sdkmanager目录结构整…

Camtasia Studio2024破解汉化版crack安装包下载地址

在当今数字化时代&#xff0c;视频内容已成为传播信息和吸引观众的重要方式。无论是企业宣传、在线教育还是个人创作&#xff0c;一款功能强大的视频编辑软件都是必不可少的工具。而Camtasia Studio2024作为业界领先的视频编辑软件&#xff0c;其永久免费版及最新版本的功能更是…

28 - 只出现一次的最大数字(高频 SQL 50 题基础版)

28 - 只出现一次的最大数字 select (selectnumfromMyNumbers group bynum havingcount(num)1order by num desc limit 1) as num;

二叉树的先序创建、复制、深度及结点个数

文章目录 前言一、二叉树的先序创建二、二叉树的复制三、二叉树的深度四、二叉树的结点个数总结 前言 T_T此专栏用于记录数据结构及算法的&#xff08;痛苦&#xff09;学习历程&#xff0c;便于日后复习&#xff08;这种事情不要啊&#xff09;。所用教材为《数据结构 C语言版…

体育器材管理系统(Java+MySQL)

技术栈 Java语言&#xff1a;作为主要编程语言&#xff0c;用于编写应用逻辑和界面交互。MySQL数据库&#xff1a;用于存储和管理体育器材的相关数据。Swing窗口视图&#xff1a;用于创建图形用户界面&#xff0c;使用户能够通过窗口进行操作&#xff08;GBK编码&#xff09;。…

线性模型-分类

一、线性判别分析LDA 线性判别分析是一种经典的线性学习方法&#xff0c;在二分类问题上最早是Fisher提出的&#xff0c;亦称为Fisher判别分析。 Fisher判别分析是一种用于降维和分类的统计方法&#xff0c;旨在找到可以最好区分不同类别的特征。它基于类内方差和类间方差的比…

Polar Web【简单】uploader

Polar Web【简单】uploader Contents Polar Web【简单】uploader思路EXP运行&总结 思路 本题的重点仍是文件上传&#xff0c;只是期间需要加上一步自主的文件上传。 打开环境&#xff0c;审查代码&#xff0c;发现在上传文件之后会自动生成一个以MD5散列值命名的目录&#…

COMPUTEX 2024 国际电脑展即将举行,英伟达宣布将Copilot+引入RTX系列设备,赋能游戏本AI助理

COMPUTEX 2024 国际电脑展即将于2024年6月4日至7日在台北南港展览馆1馆及2馆盛大举行。作为业界瞩目的盛会&#xff0c;本次展会不仅吸引了全球各地的科技爱好者&#xff0c;更迎来了AMD CEO苏姿丰博士和NVIDIA首席执行官黄仁勋的精彩演讲。 在展会的开幕之际&#xff0c;图形…

查看服务器端口是否打开,如何查看服务器端口是否打开

查看服务器端口是否打开&#xff0c;是确保服务器正常运行和网络通信畅通的关键步骤。以下是几个有力的方法&#xff0c;帮助你快速、准确地判断端口状态。 首先&#xff0c;你可以使用telnet命令来检测端口的连通性。telnet是一个网络协议&#xff0c;可以用于远程登录和管理网…

如何将 MySQL 数据库共享给他人?

文章目录 共享所有数据库给他人1. 连接到 MySQL 数据库2. 选择要使用的数据库3. 修改连接所需的 host4. 刷新权限 共享部分数据库给他人1. 创建用户2. 授权3. 刷新权限 结语 &#x1f389;欢迎来到Java学习路线专栏~探索Java中的静态变量与实例变量 ☆* o(≧▽≦)o *☆嗨~我是I…