• Uladzislau Rezki (Sony)'s avatar
    rcu: Reduce synchronize_rcu() latency · 988f569a
    Uladzislau Rezki (Sony) authored
    A call to a synchronize_rcu() can be optimized from a latency
    point of view. Workloads which depend on this can benefit of it.
    
    The delay of wakeme_after_rcu() callback, which unblocks a waiter,
    depends on several factors:
    
    - how fast a process of offloading is started. Combination of:
        - !CONFIG_RCU_NOCB_CPU/CONFIG_RCU_NOCB_CPU;
        - !CONFIG_RCU_LAZY/CONFIG_RCU_LAZY;
        - other.
    - when started, invoking path is interrupted due to:
        - time limit;
        - need_resched();
        - if limit is reached.
    - where in a nocb list it is located;
    - how fast previous callbacks completed;
    
    Example:
    
    1. On our embedded devices i can easily trigger the scenario when
    it is a last in the list out of ~3600 callbacks:
    
    <snip>
      <...>-29      [001] d..1. 21950.145313: rcu_batch_start: rcu_preempt CBs=3613 bl=28
    ...
      <...>-29      [001] ..... 21950.152578: rcu_invoke_callback: rcu_preempt rhp=00000000b2d6dee8 func=__free_vm_area_struct.cfi_jt
      <...>-29      [001] ..... 21950.152579: rcu_invoke_callback: rcu_preempt rhp=00000000a446f607 func=__free_vm_area_struct.cfi_jt
      <...>-29      [001] ..... 21950.152580: rcu_invoke_callback: rcu_preempt rhp=00000000a5cab03b func=__free_vm_area_struct.cfi_jt
      <...>-29      [001] ..... 21950.152581: rcu_invoke_callback: rcu_preempt rhp=0000000013b7e5ee func=__free_vm_area_struct.cfi_jt
      <...>-29      [001] ..... 21950.152582: rcu_invoke_callback: rcu_preempt rhp=000000000a8ca6f9 func=__free_vm_area_struct.cfi_jt
      <...>-29      [001] ..... 21950.152583: rcu_invoke_callback: rcu_preempt rhp=000000008f162ca8 func=wakeme_after_rcu.cfi_jt
      <...>-29      [001] d..1. 21950.152625: rcu_batch_end: rcu_preempt CBs-invoked=3612 idle=....
    <snip>
    
    2. We use cpuset/cgroup to classify tasks and assign them into
    different cgroups. For example "backgrond" group which binds tasks
    only to little CPUs or "foreground" which makes use of all CPUs.
    Tasks can be migrated between groups by a request if an acceleration
    is needed.
    
    See below an example how "surfaceflinger" task gets migrated.
    Initially it is located in the "system-background" cgroup which
    allows to run only on little cores. In order to speed it up it
    can be temporary moved into "foreground" cgroup which allows
    to use big/all CPUs:
    
    cgroup_attach_task():
     -> cgroup_migrate_execute()
       -> cpuset_can_attach()
         -> percpu_down_write()
           -> rcu_sync_enter()
             -> synchronize_rcu()
       -> now move tasks to the new cgroup.
     -> cgroup_migrate_finish()
    
    <snip>
             rcuop/1-29      [000] .....  7030.528570: rcu_invoke_callback: rcu_preempt rhp=00000000461605e0 func=wakeme_after_rcu.cfi_jt
        PERFD-SERVER-1855    [000] d..1.  7030.530293: cgroup_attach_task: dst_root=3 dst_id=22 dst_level=1 dst_path=/foreground pid=1900 comm=surfaceflinger
       TimerDispatch-2768    [002] d..5.  7030.537542: sched_migrate_task: comm=surfaceflinger pid=1900 prio=98 orig_cpu=0 dest_cpu=4
    <snip>
    
    "Boosting a task" depends on synchronize_rcu() latency:
    
    - first trace shows a completion of synchronize_rcu();
    - second shows attaching a task to a new group;
    - last shows a final step when migration occurs.
    
    3. To address this drawback, maintain a separate track that consists
    of synchronize_rcu() callers only. After completion of a grace period
    users are deferred to a dedicated worker to process requests.
    
    4. This patch reduces the latency of synchronize_rcu() approximately
    by ~30-40% on synthetic tests. The real test case, camera launch time,
    shows(time is in milliseconds):
    
    1-run 542 vs 489 improvement 9%
    2-run 540 vs 466 improvement 13%
    3-run 518 vs 468 improvement 9%
    4-run 531 vs 457 improvement 13%
    5-run 548 vs 475 improvement 13%
    6-run 509 vs 484 improvement 4%
    
    Synthetic test(no "noise" from other callbacks):
    Hardware: x86_64 64 CPUs, 64GB of memory
    Linux-6.6
    
    - 10K tasks(simultaneous);
    - each task does(1000 loops)
         synchronize_rcu();
         kfree(p);
    
    default: CONFIG_RCU_NOCB_CPU: takes 54 seconds to complete all users;
    patch: CONFIG_RCU_NOCB_CPU: takes 35 seconds to complete all users.
    
    Running 60K gives approximately same results on my setup. Please note
    it is without any interaction with another type of callbacks, otherwise
    it will impact a lot a default case.
    
    5. By default it is disabled. To enable this perform one of the
    below sequence:
    
    echo 1 > /sys/module/rcutree/parameters/rcu_normal_wake_from_gp
    or pass a boot parameter "rcutree.rcu_normal_wake_from_gp=1"
    Reviewed-by: default avatarPaul E. McKenney <paulmck@kernel.org>
    Reviewed-by: default avatarFrederic Weisbecker <frederic@kernel.org>
    Co-developed-by: default avatarNeeraj Upadhyay (AMD) <neeraj.iitr10@gmail.com>
    Signed-off-by: default avatarNeeraj Upadhyay (AMD) <neeraj.iitr10@gmail.com>
    Signed-off-by: default avatarUladzislau Rezki (Sony) <urezki@gmail.com>
    988f569a
tree_exp.h 33.6 KB