CRT 并行运行时:资源管理器

Visual C++ 2010 带有有趣的新特性和功能增强以简化更为本地化的编程。并行运行时是一个新增框架,用以简化并行编程并帮助您编写稳健、可扩展且具备良好响应能力的并行应用。

并行运行时提高了抽象化的水平,这样您无需考虑与并行相关的基础设施细节。并行运行时还支持您指定符合您的应用服务质量要求的调度策略。

以下是并行运行时框架的架构:



如欲获得关于本框架的更多信息,请参见以下简介内容。

在本文中,我们将就资源管理器 (Resource Manager) 层进行讨论,并试图了解其内部工作原理。为此,CppDepend 用于对 CRT 并行运行时代码源进行分析。

阅读本文之前,您可以参阅以下关于 ResourceManager 的文章:



ResourceManager 设计

ResourceManager 使用哪些类实现其责任?

SELECT TYPES WHERE IsDirectlyUsedBy "Concurrency.details.ResourceManager"



T对该设计的第一评论就是它加强了:

- 高度内聚性:许多类和结构被用于资源管理器,每个类和结构都有其具体责任,这样,设计变得十分清晰,而单一责任原则主张更改一个类应存在一个以上的理由。这种类被认为有内聚性。LCOM 值较高通常说明类别的内聚性较差。存在数种 LCOM 指标。LCOM 的取值范围是 [0-1]。LCOMHS(HS 代表 Henderson-Sellers)的取值范围是 [0-2]。请注意,LCOMHS 常常被看做是检测非内聚性类的更有效的方法。

LCOMHS 值大于 1 时,应视作告警。

WARN IF Count > 0 IN SELECT TYPES WHERE LCOMHS > 1 AND NbFields > 10 AND NbMethods >10 AND !IsGlobal ORDER BY LCOMHS DESC



因此,在 ResourceManager 使用的类中,仅 UMS 被视为内聚性较差的类。

- 低耦合:许多界面和代理被用于隔离 ResourceManager 与其他组件。调度程序与 ResourceManager进行通信,但这两个组件之间没有直接的链接,那么调度程序是如何与 ResourceManager 之间进行通信的呢?

为回答该问题,让我们了解一下哪些类使用了ResourceManager:

SELECT TYPES WHERE IsDirectlyUsing "Concurrency.details.ResourceManager"



仅这些类使用 ResourceManager,且调度程序不会直接识别 ResourceManager,那么这二者是如何交互的呢?让我们在相关性图形中添加调度程序及其使用的调度界面。



正如相关性图形中所示,调度程序使用界面 IResourceManager、ISchedulere 和 ISchedulerProxy 与 ResourceManager 通信。

ResourceManager 在哪里创建?
SELECT METHODS WHERE DepthOfCreateA "Concurrency.details.ResourceManager" == 1



仅 ResourceManager 创建一个实例,且为单例模式,因此仅一个 ResourceManager 面向整个流程存在。

资源分配的工作流程:

第 1 步:从系统中获取资源信息
资源管理器需要处理器和内核相关的所有的物理信息,用于为调度程序分配资源,让我们来了解一下由该组件操作的全部数据。

SELECT FIELDS WHERE IsDirectlyUsedBy "Concurrency.details.ResourceManager"



上述结构代表了 ResourceManager 所需的全部数据,最基本的数据为 ProcessorCore 和 ProcessorNode,它们代表了物理资源的相关信息。

其他的结构包括某些分配算法所需的调度程序数据。

好消息是 ResourceManager 不会直接访问其使用的其他逻辑类的字段。

让我们搜索 Resourcemanager 获得物理资源基本信息的位置,为此,我们可以搜索使用 GetLogicalProcessorInformation 的方法。

SELECT METHODS WHERE IsDirectlyUsing "WindowsAPIGlobalMembers.GetLogicalProcessorInformation(PSYSTEM_LOGICAL_PROCESSOR_INFORMATION,PDWORD)"



仅 ResourceManager::InitializeSystemInformation 使用该方法从系统中获得信息,但该方法是否负责填充 ProcessorCore 和 ProcessorNode?

为此,我们可以搜索为 ProcessorCore 分配字段的方法:SELECT METHODS WHERE IsDirectlyWritingField "Concurrency.details.ProcessorCore.m_processorNumber"



因此,ResourceManager 调用 DetermineTopology 方法向结构中填充数据。
以下相关性图形所示为获得节点和内核信息的有关方法。



步骤 2:调度程序请求资源分配
ResourceManager 获得分配算法所需的全部数据后,调度程序就可以请求它分配咨询。

调度程序创建后,将会请求 ResourceManager 并调用 CreateResourceManager 获得一个单例,之后,它调动 IResourceManager::RegisterScheduler 在资源管理器上注册并获得指向 ISchedulerProxy 的指针, 借助这一界面,调度程序将与资源管理器进行交互操作。
此后,调度程序准备好通过调用 RequestInitialVirtualProcessors 来请求资源。



如上所述,调度程序使用 IscheduleProxy界面与资源管理器进行通讯以执行低耦合,且该界面的具体实施调用 ResourceManager::RequestInitialVirtualprocessors。



第 3 步:资源分配
调度程序被创建之后,资源管理器需要将资源分配给调度程序,它将依据政策,并考虑流程中的其他调度程序对资源进行分配。最终,将按需向调度程序分配资源。

资源分配的切入点是 RequestInitialVirtualProcessors,以下依赖关系图所示为用于实现分配的方法。



PerformAllocation 方法实施分配逻辑,它向 SchedulerProxy 请求调度程序政策,并调用 AllocateCores 方法运行分配。

以下是 PerformAllocation 和 AllocateCores 的部分特征:



这些方法描述清晰,且循环复杂度不高,这很好地表明了初步分配算法并非十分复杂。

第 4 步:为调度程序提供资源
ResourceManager 分配资源后,调度程序得到资源已分配的通知,为此,资源管理器调用了GrantAllocation 方法。



ResourceManager 需要为调度程序提供哪些资源?
下述有关资源管理器的 msdn 文章中提到,资源是与具体线程有关,并在特定内核中运行的虚拟处理器。
但正如相关性图形所示,资源管理器并未向 SchedulerProxy 提供 VirtualProcessors,而是提供了一列 SchedulerNodes,SchedulerProxy,调度程序创建 VirtualProcessorRoot 类,VirtualProcessorRoot 表示可在其上执行线程代理的硬件线程的抽象。

由何人创建 VirtualProcessor?
在回答该问题之前,先让我们搜索代表虚拟处理器的类,因此,让我们搜索从这一点继承下来的类。



这样,VirtualProcessor 可涉及到一个简单线程或一个 UMS 线程。
用户模式调度 (UMS)是 Windows7 和 Windows 服务器 2008 R2 推出的轻量级机制,该应用可用于调度其自身的线程。更多信息,请参见用户模式调度

让我们一起搜索创建 ThreadVirtualProcessor 的方法。



因此,调度程序负责创建 VirtualProcessors,其创建所需全部信息均由管理器提供。

步骤5:动态管理资源
初次分配资源后,资源管理器的第二大主要责任就是定期监控调度程序对资源的利用情况,并在各调度程序间动态切换资源。

并且如下图所示,资源管理器创建 DynamicRM 线程,该工作线程按固定的时间间隔苏醒,并对多个调度程序间的资源加载进行平衡。



以下是 DynamicResourceManager 的相关性图形:



ResourceManager 能否自定义?
ResourceManager 经精心设计,通过使用界面与其他并行运行时框架去耦合,但可以自定义资源管理器的分配行为。

为此,我们来搜索一下 ResourceManager 虚拟方法:

SELECT METHODS FROM TYPES "Concurrency.details.ResourceManager" WHERE IsVirtual



仅这些方法为虚拟方法,且实施 IResourceManager 方法,因此,不可能超载分配资源管理器逻辑,并进行自定义。

有关编译器优化的更完整信息,请参阅优化通知