lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <CAJZ5v0iEKoozunNzeTHHqKCsvJ_ouUm+cFT_6mMohYCzAW39QQ@mail.gmail.com>
Date: Thu, 17 Jul 2025 20:23:18 +0200
From: "Rafael J. Wysocki" <rafael@...nel.org>
To: 朱恺乾 <zhukaiqian@...omi.com>
Cc: "rafael@...nel.org" <rafael@...nel.org>, Daniel Lezcano <daniel.lezcano@...aro.org>, 
	"christian.loehle@....com" <christian.loehle@....com>, 
	"quic_zhonhan@...cinc.com" <quic_zhonhan@...cinc.com>, 
	"linux-pm@...r.kernel.org" <linux-pm@...r.kernel.org>, 
	"linux-kernel@...r.kernel.org" <linux-kernel@...r.kernel.org>
Subject: Re: [PATCH] cpuidle: menu: find the typical interval by a heuristic
 classification method

On Thu, Jul 17, 2025 at 12:12 PM 朱恺乾 <zhukaiqian@...omi.com> wrote:
>
> The iterations of deviation calculation gives too less predictions on
> the idle interval by trying to find a single repeating pattern from the
> whole history. This is not always the case when the workload is flowing.
>
> This algorithm assumes there're multiple repeating patterns heuristically,
> and tries to determine which is the most promising one from the averages
> of different idle states. It also takes the occurrence sequence into
> consideration, and gives the prediction close to the recent idle.

Instead of saying "this algorithm", please describe it or give a
reference to a description of it that is not going to vanish from the
Web at one point.

> This increased the shallow idle states detected, but the difference in deep
> sleep time didn't change a lot. The performance on my platform, as
> expected, has improved.
>
> Before:
> Multi-Core Score              7279
> Overall    above   under
>   34107    0.00    2.75
>    8200   59.90    7.02
>   29881   57.06    0.00
>
> After:
> Multi-Core Score              7365
> Overall    above   under
>   49913    0.00    6.43
>    7881   44.51   18.08
>   23108   52.38    0.00

It is unclear what the columns above mean and what's represented by
the numbers, so qualifying the improvement is rather hard.

> There's another re-classification method, which, instead of looking for the
> repeating-interval, tends to find the repeating state. It gives a better result
> on performance gain, but may hurt the power consumption.
>
> if (best_state == drv->state_count - 1 || state_avg[best_state] == 0) {
> adj_weight[best_state] += weights[i];
> adj_avg[best_state] += value;
> adj_hit[best_state]++;
> } else if (best_diff < state_avg[best_state] * 2) {
> adj_weight[best_state] += weights[i];
> adj_avg[best_state] += value;
> adj_hit[best_state]++;
> } else {
> adj_weight[best_state + 1] += weights[i];
> adj_avg[best_state + 1] += value;
> adj_hit[best_state + 1]++;
> }
>
> Repeating State:
> Multi-Core Score              7421
> Overall    above   under
>   60857    0.00    8.30
>    3838   29.88   18.42
>   15318   39.05    0.00

I'm not sure what the above part of the changelog means at all.

> Signed-off-by: Kaiqian Zhu <zhukaiqian@...omi.com>
> ---
>  drivers/cpuidle/governors/menu.c | 174 ++++++++++++++++---------------
>  1 file changed, 89 insertions(+), 85 deletions(-)
>
> diff --git a/drivers/cpuidle/governors/menu.c b/drivers/cpuidle/governors/menu.c
> index 52d5d26fc7c6..52723ec1a0a6 100644
> --- a/drivers/cpuidle/governors/menu.c
> +++ b/drivers/cpuidle/governors/menu.c
> @@ -99,109 +99,113 @@ static DEFINE_PER_CPU(struct menu_device, menu_devices);
>
>  static void menu_update(struct cpuidle_driver *drv, struct cpuidle_device *dev);
>
> -/*
> - * Try detecting repeating patterns by keeping track of the last 8
> - * intervals, and checking if the standard deviation of that set
> - * of points is below a threshold. If it is... then use the
> - * average of these 8 points as the estimated value.
> - */
> -static unsigned int get_typical_interval(struct menu_device *data)
> +static int get_actual_state(struct cpuidle_driver *drv,
> +    struct cpuidle_device *dev,
> +    int duration_us)
>  {
> -s64 value, min_thresh = -1, max_thresh = UINT_MAX;
> -unsigned int max, min, divisor;
> -u64 avg, variance, avg_sq;
> -int i;
> +int actual;
>
> -again:
> -/* Compute the average and variance of past intervals. */
> -max = 0;
> -min = UINT_MAX;
> -avg = 0;
> -variance = 0;
> -divisor = 0;
> -for (i = 0; i < INTERVALS; i++) {
> -value = data->intervals[i];
> -/*
> - * Discard the samples outside the interval between the min and
> - * max thresholds.
> - */
> -if (value <= min_thresh || value >= max_thresh)
> -continue;
> +for (int i = 0; i < drv->state_count; i++) {
> +if (duration_us < drv->states[i].target_residency)
> +break;
> +
> +actual = i;
> +}
> +
> +return actual;
> +}
> +
> +static unsigned int get_typical_interval(struct cpuidle_driver *drv,
> + struct cpuidle_device *dev,
> + struct menu_device *data)
> +{
> +int cnt = 0;
> +
> +int state_hit[CPUIDLE_STATE_MAX];
> +int state_avg[CPUIDLE_STATE_MAX];
> +int adj_weight[CPUIDLE_STATE_MAX];
> +int adj_avg[CPUIDLE_STATE_MAX];
> +int adj_hit[CPUIDLE_STATE_MAX];
> +int hit_thres = max(2, INTERVALS / drv->state_count);
> +int weights[INTERVALS] = {5, 3, 2, 1};
> +int weight = 0;
> +int high_state = -1;
>
> -divisor++;
>
> -avg += value;
> -variance += value * value;
> +/* Going through the history, and divide them by the actual state */
> +for (int i = 0; i < INTERVALS; i++) {
> +int actual = get_actual_state(drv, dev, data->intervals[i]);
>
> -if (value > max)
> -max = value;
> +/* Count the idle states hit in the history */
> +state_avg[actual] += data->intervals[i];
> +state_hit[actual]++;
>
> -if (value < min)
> -min = value;
> +cnt++;
>  }
>
> -if (!max)
> +if (cnt < hit_thres)
>  return UINT_MAX;
>
> -if (divisor == INTERVALS) {
> -avg >>= INTERVAL_SHIFT;
> -variance >>= INTERVAL_SHIFT;
> -} else {
> -do_div(avg, divisor);
> -do_div(variance, divisor);
> +/* calculate the average of each state */
> +for (int i = 0; i < drv->state_count; i++) {
> +if (state_hit[i] > 1)
> +state_avg[i] /= state_hit[i];
>  }
>
> -avg_sq = avg * avg;
> -variance -= avg_sq;
> +/* try to re-assign the data points by the closeness */
> +for (int i = 0; i < INTERVALS; i++) {
> +/* Starting from the recent history */
> +int idx = ((data->interval_ptr - i - 1) + INTERVALS) % INTERVALS;
> +unsigned int diff;
> +unsigned int best_diff = UINT_MAX;
> +unsigned int best_state, next_state;
> +unsigned int value = data->intervals[idx];
> +
> +for (int state = 0; state < drv->state_count; state++) {
> +diff = abs(state_avg[state] - value);
> +if (diff < best_diff) {
> +best_diff = diff;
> +best_state = state;
> +}
> +}
>
> -/*
> - * The typical interval is obtained when standard deviation is
> - * small (stddev <= 20 us, variance <= 400 us^2) or standard
> - * deviation is small compared to the average interval (avg >
> - * 6*stddev, avg^2 > 36*variance). The average is smaller than
> - * UINT_MAX aka U32_MAX, so computing its square does not
> - * overflow a u64. We simply reject this candidate average if
> - * the standard deviation is greater than 715 s (which is
> - * rather unlikely).
> - *
> - * Use this result only if there is no timer to wake us up sooner.
> - */
> -if (likely(variance <= U64_MAX/36)) {
> -if ((avg_sq > variance * 36 && divisor * 4 >= INTERVALS * 3) ||
> -    variance <= 400)
> -return avg;
> +if (best_diff < (state_avg[best_state] >> 2)) {
> +adj_avg[best_state] += value;
> +adj_hit[best_state]++;
> +adj_weight[best_state] += weights[i];
> +} else if (best_state < drv->state_count - 1) {
> +next_state = best_state + 1;
> +diff = abs(state_avg[next_state] - value);
> +if (diff < (state_avg[next_state] >> 2)) {
> +adj_avg[next_state] += value;
> +adj_hit[next_state]++;
> +adj_weight[next_state] += weights[i];
> +}
> +}
>  }
>
> -/*
> - * If there are outliers, discard them by setting thresholds to exclude
> - * data points at a large enough distance from the average, then
> - * calculate the average and standard deviation again. Once we get
> - * down to the last 3/4 of our samples, stop excluding samples.
> - *
> - * This can deal with workloads that have long pauses interspersed
> - * with sporadic activity with a bunch of short pauses.
> +/* We've adjusted the hit status by the closeness, if one state is still
> + * hit more often and selected recently, we can assume that state is more
> + * likely to happen in the future
>   */
> -if (divisor * 4 <= INTERVALS * 3) {
> -/*
> - * If there are sufficiently many data points still under
> - * consideration after the outliers have been eliminated,
> - * returning without a prediction would be a mistake because it
> - * is likely that the next interval will not exceed the current
> - * maximum, so return the latter in that case.
> - */
> -if (divisor >= INTERVALS / 2)
> -return max;
> -
> -return UINT_MAX;
> +for (int state = 0; state < drv->state_count; state++) {
> +if (adj_weight[state] > 1 && adj_hit[state] >= hit_thres) {
> +adj_avg[state] /= adj_hit[state];
> +
> +if (adj_weight[state] > weight) {
> +weight = adj_weight[state];
> +high_state = state;
> +} else if (adj_weight[state] == weight) {
> +if (adj_hit[state] > adj_hit[high_state])
> +high_state = state;
> +}
> +}
>  }
>
> -/* Update the thresholds for the next round. */
> -if (avg - min > max - avg)
> -min_thresh = min;
> -else
> -max_thresh = max;
> +if (weight)
> +return adj_avg[high_state];
>
> -goto again;
> +return UINT_MAX;
>  }
>
>  /**
> @@ -225,7 +229,7 @@ static int menu_select(struct cpuidle_driver *drv, struct cpuidle_device *dev,
>  }
>
>  /* Find the shortest expected idle interval. */
> -predicted_ns = get_typical_interval(data) * NSEC_PER_USEC;
> +predicted_ns = get_typical_interval(drv, dev, data) * NSEC_PER_USEC;
>  if (predicted_ns > RESIDENCY_THRESHOLD_NS) {
>  unsigned int timer_us;
>
> --

If you want to change the get_typical_interval() algorithm, it needs
to be justified much better than in the changelog of this patch
because you are likely to affect at least some workloads adversely.

Moreover, there are obvious whitespace issues in the patch which need
to be addressed even before it can be reviewed.

Also this absolutely is not 6.17 material, so if you decide to pursue
it, please come back with it after 6.17-rc1 is out.

Thanks!

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ