背景在SPDK中,每个CPU核都会对应一个reactor,创建的线程会在reactor上执行。图1核心-反应器-线程对应关系默认情况下,反应器以恒定轮询模式运行,以实现最高效的处理。但是当reactor空闲时,会继续轮询,造成资源浪费。与此同时,其他反应堆可能正在运行大量超过自身负荷的工作。如果把busyreactor上的一些线程移到idlereactor上,会大大提高效率。或者如果此时处于空闲状态的reactor使用中断方式,也可以大大降低资源消耗。于是,spdk中的scheduler模块应运而生。通常在空闲状态下,调度器可以将reactor切换到中断模式(interruptmode)。在Linux上,这是使用epoll实现的,但会降低CPU使用率,并且在事件发生时可能响应速度较慢。调度器大大提高了轻量级或大变化工作负载的效率。Schedulerdynamic旨在节省能源并提高CPU利用率,尤其是当工作负载随时间变化较大时,可以更好地动态管理reactor上的线程。SPDK_TOP在介绍调度器之前先介绍一下spdk用来查看cpu使用率的一个工具——spdk_top。spdk_top应用程序类似于标准top,通过SPDK轻量级线程和轮询器提供有关CPU核心使用情况的实时反馈。spdk_top应用程序使用RPC命令调用来收集性能指标并将其显示在报告中,以便您可以分析和确定您的代码是否有效运行,从而调整您的实现并从SPDK中获得更多收益。为什么经典的top实用程序不能与SPDK一起使用?SPDK采用轮询模式设计,通过运行在分配给SPDK应用程序的每个CPU核心上的反应器线程来调度SPDK轻量级线程和轮询器。因此,标准的Linuxtop实用程序对于分析轮询模式应用程序(例如SPDK)的CPU使用率是无效的,因为它只会报告它们正在使用分配给它们的100%的CPU资源。开发spdk_top实用程序是为了分析和报告花费在实际工作与仅轮询工作上的CPU周期。该实用程序依赖于添加到轮询器的工具来跟踪它们何时工作而不是轮询。spdk_top实用程序从轮询器获取细粒度指标,分析并报告每个轮询器、线程和核心的指标。启动spdk_top应用程序(仅当SPDK中有目标时才可执行):$SPDKDIR/build/bin/spdk_top基本操作spdk_top窗口底部的菜单显示了许多用于更改显示数据的选项。每个菜单项在方括号中都有一个与之关联的键。切换选项卡[1-3]-允许选择线程/轮询器/核心选项卡。排序[s]-允许在排序弹出窗口中按列对显示的数据进行排序。Total/Interval[t]-将所有选项卡中的显示值更改为Totaltime(自SPDK应用程序启动以来测量)或Intervaltime(自上次刷新以来测量)。选择线程后回车,会显示详细信息,然后可以按ESC键关闭弹窗:help[h]–显示更多帮助信息对应的reactor切换到中断模式(直到出现是当前核心上的活动线程,然后切换到轮询模式);当线程的繁忙时间间隔除以当前线程一个周期的百分比小于SCHEDULER_LOAD_LIMIT时,当前线程状态为空闲;当线程繁忙时间间隔除以当前线程一个周期的百分比大于SCHEDULER_LOAD_LIMIT时,当前线程状态为繁忙;2.线程调度原理当创建的线程的活跃时间百分比小于SCHEDULER_LOAD_LIMIT(代码中设置为20)时,线程处于空闲状态,线程将移动到主核。图2平衡原理示例:启动一个目标test/event/scheduler/scheduler及其rpc插件是测试例程,用于评估和观察调度器的行为。在实际应用中,只需使用需要启动的目标即可。$SPDKDIR/test/event/scheduler/scheduler-m0xF-p2—wait-for-rpc&scripts/rpc.pyframework_set_schedulerdynamicscripts/rpc.pyframework_start_init此时启动spdk_top,可以观察到只有app_thread(即,启动目标)一个线程,并在我们指定的主核心(core2)上。然后创建4个空闲线程(活跃时间比例为0)rpc_cmd--pluginscheduler_pluginscheduler_thread_create-nidle0-m0x1-a0rpc_cmd--pluginscheduler_pluginscheduler_thread_create-nidle1-m0x2-a0rpc_cmd--pluginscheduler_creater-nth_read_schedAfter0x4-a0rpc_cmd--pluginscheduler_pluginscheduler_thread_create-nidle3-m0x8-a0,我们可以观察到,虽然在创建时指定了这些线程绑定到不同的核上,但仍然通过调度器(主核)全部分配给了core2:主核上没有活动线程,这个CPU核的频率会随着负载的降低而降低。其他反应器对应的所有CPU核心都保持在最大频率。因此,当前reactor没有活跃线程,所以此时主核处于低功耗状态:2、当当前线程活跃比大于SCHEDULER_LOAD_LIMIT时,当前线程状态为busy,以及调度器会为当前线程找到最合适的反应堆,并将其移过去。主核尽量闲置。只有当线程的执行时间超过所有线程空闲时间的总和时,主核才能包含活跃线程。调度器调度的目标reactor只能从与target绑定的core上的reactor中选择(即启动target时设置的cpumask对应的core)。预定的目标反应堆必须有足够的空间来丢弃线程。调度的目标reactor应该是当前polling模式下最空闲的reactor(中断模式的reactor先被排除,除非没有合适的才会被选中)。当满足上述条件时,如果主核对应的reactor可以容纳当前线程,则应将当前线程移至主核。示例:创建一个活跃比为50的线程,根据调度原则,此时线程应该分配在core0上:rpc_cmd--pluginscheduler_pluginscheduler_thread_create-nbusy0-a50我们再创建一个活跃比为40的线程:rpc_cmd--pluginscheduler_pluginscheduler_thread_create-nbusy1-a40从spdk_top可以看出busy1是分配在core2(主核)上的。5)当主核无法容纳线程时,优先选择合适的核中核id最小的核。示例:在前面的基础上创建一个活跃比为40的线程:rpc_cmd--pluginscheduler_pluginscheduler_thread_create-nbusy2-a40此时core0、core1、core3都处于空闲状态,core0的id为最小的,所以它会首先在core0上分配。6)如果当前核心超过限制,任何其他核心都比当前核心更合适。对于超限内核,通过将线程放置在最不繁忙的内核上来平衡线程。约束条件:当前核心没有线程或者没有单个线程在运行,即忙碌时间为0且完成的工作小于限制(SCHEDULER_CORE_LIMIT95)示例:先清除所有之前的线程,然后创建线程busy0oncore1:rpc_cmd--pluginscheduler_pluginscheduler_thread_create-nbusy0-m0x2-a50然后在core0上创建busy1,活跃时间比为30:rpc_cmd--pluginscheduler_pluginscheduler_thread_create-nbusy1-m0x1-a30发现busy1被调度到core1,也就是因为core0上没有活动线程,此时应该是中断模式,所以core1比core0更适合容纳busy1。7)如果没有找到更合适的,则不发生移动,线程还在当前核心上。3、调度流程图当reactor没有调度到的spdk_threads时,切换到中断模式,停止主动轮询。在足够多的线程变为活动状态后,reactor将切换回轮询模式并再次为其分配线程。如果CPU支持变频,可以根据需要改变主核的频率。变频规则:1)如果线程不在主核上,则设置主核的默认频率。例子:我们先创建一个活跃比为10的线程,此时观察spdk_top,发现该线程分配在主核(core2)上,core2的频率为1000Mhz。然后我们再创建一个活跃比为50的线程,分配到core0上,主核(core2)增加到默认频率2300MHz。2)如果主核上的繁忙时间大于空闲时间,则对主核执行升频操作。3)其他情况,对主核进行降频操作(主核空闲,其他核上没有线程)。示例:销毁除app_thread之外的所有线程,然后观察spdk_top,发现主核(core2)的频率降低到1000MHz。至此,调度器动态平衡的原理和流程规则已经全部讲解完毕。结论Scheduler_dynamic是我们实现资源自动动态平衡的最终目标,在一定程度上可以极大地帮助我们合理利用资源,提高工作效率,延长设备寿命。同时spdk_top也是一个非常好的工具,可以让我们真实的看到资源的使用情况,有针对性的准确分析,合理的改变设备的运行状态。两者目前都处于实验状态,并在不断改进。
