DIY编程器网

 找回密码
 注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 1144|回复: 0
打印 上一主题 下一主题

[待整理] 利用并发操作实现可伸缩性(下)

[复制链接]
跳转到指定楼层
楼主
发表于 2014-10-12 15:38:45 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
共享状态
        无论何时采用并发操作,都需要考虑保护共享状态。这一点至关重要。要想了解锁定为何如此重要,建议您阅读 MSDN?Magazine 2005 年 8 月刊中 Vance Morrison 的文章(英文)(msdn.microsoft.com/msdnmag/issues/05/08/Concurrency)。正确性应始终优先于性能,如果您使用并发操作而不考虑锁定,则您的代码很有可能不正确。我不打算重申 Vance 已经表述得很明确的内容,而是想把重点集中于这种技术的性能上。
        最常见的同步技术是锁定和低位锁操作。锁定使用 Win32? Mutex 或 CRITICAL_SECTION 之类的原语,或 CLR Monitor、ReaderWriterLock,或相关的语言关键字(例如 C# 中的 lock 和 Visual Basic? 中的 SyncLock)来实现某种程度的相互排斥。要实现这种排斥,需对 API 进行调用;一些内部算法确保了两个使用同一个锁的代码段不可进入受保护的代码区。只要每个人都遵守此协议,代码就会一直保持安全。
        低位锁操作可使用联锁原语生成,后者通过对“加载-比较-存储”原子指令的硬件支持来实现。它们确保了内存的单个更新为原子级更新,并可用于生成使用优化并发操作的高度可伸缩代码。这样的代码更难以编写,但其往往不会阻塞。(如果您对此感到疑惑,可以告诉您,锁就是使用此类原语编写而成。)
        但进行这些调用会产生成本。图 5 说明了在不争用资源的情况下,获得各种类型锁的成本的微基准(以 CPU 周期为单位)。

图 5 比较各种锁的成本

        尽管这样的度量对于理解锁定的性能本质非常重要(特别是在要做出关于并行执行的动态决策时,在这种情况下,“需要就绪”的代码量要多于将实际并行运行的代码量),但确保以正确的粒度进行同步有助于保证代码的执行不会受到此类成本的制约。还有一些我没有提到的成本,例如联锁操作和内存层级之间的交互。遗憾的是,受空间所限,不允许发生这种情况。然而,更重要的部分是对可伸缩性的影响。可惜的是,您经常需要在可伸缩性和序列直线执行性能之间进行权衡。应通过度量来获知这些权衡结果。
        我们无法保证线程在持有锁时仍可运行,因此,如果其时间片到期,后续线程可能会运行并试图获取这同一个锁。此外,某一变为可运行状态的更高优先级的线程可以优先于在上锁情况下运行的线程。这会导致被称为优先级倒置的现象,如果某一被争用锁处的到达率特别高,则会导致锁保护。大多数锁通过在多 CPU 系统上以某种形式轻度旋转来回应争锁尝试,以期待持有锁的线程马上解锁。如果该方法失败(因为锁的持有方持锁时间超过预期时间,或也许因为在一次上下文切换后锁被交换出),则它会阻塞。对于高度并发的系统,阻塞量越多,则就需要越多的线程来保持 CPU 的占用状态,您的系统成功伸缩的可能性也就越低。
        这样,一个时刻需要保持思考的重要问题就是:如何在持有锁的同时执行最少量的工作,以将所需的锁定量减至最低?读取方/写入方锁功能对此会有帮助,它允许多个线程读取数据,同时又仍确保写操作互斥。对于大多数系统,读取方与写入方的比率非常高,因此赢得可伸缩性的成功率极大。要了解更多信息,Jeffrey Richter 在 MSDN Magazine 的 2006 年 6 月刊上发表的“并发事务”专栏是一个很好的起点(参见 msdn.microsoft.com/msdnmag/issues/06/06/ConcurrentAffairs)(英文)。
        就已提过的内容而言,如果能首先避免共享状态,则根本就不需要将访问同步化。提高操纵热数据结构(即多数线程都必须访问的数据)的算法的可伸缩性的一个常用技术是避免将各锁一起使用。这可采取三种实现难度逐级提高的重要形式:不变性、隔离性和锁自由性。
        不变性意味着某一实例一旦创建就不再改变,或者至少在一段固定的已知时间段内不再改变。例如,CLR 字符串具备不变性,因此不需要根据对其各字符的访问权限上锁。如果状态不变动,则不需要上锁。当有多个位置包含应在原子级观察的状态指针时,这就难以实现。
        隔离性通过维护各自的副本避免了对数据的任何并发访问。例如,malloc 操作和 free 操作的许多线程安全的 C 实现对每个线程都维护一个可用内存池,以在线程分配时不会争用该池(该池很可能是任一 C 程序中的一个热点)。同样,CLR 的服务器垃圾收集器 (GC) 使用每个线程一个分配上下文和每个 CPU 一个内存段的方式来提高内存分配的吞吐量。这通常需要与数据结构的中心副本定期会合,并且有时可能需要产生与复制和确保重要数据位不失时效所相关的成本。
        锁自由性是一种极复杂的技术,我将只对其一带而过。如果您真正理解目标计算机的内存模型并且乐于编写和维护大量代码,则可以创建在被并行访问时可成功伸缩的智能化数据结构。时常会出现这种情况,最终所得代码如此难以测试正确性和维护,以至于不值得为其花费这样的精力。对于程序中已被测量到与使用锁相关的伸缩性或性能问题的那些方面,这些技术值得探究。
用于剖析并行性的工具
        让我们看看如何可以测量和提高代码的可伸缩性。在这整个专栏中,我在技术、方法和成本方面一直不太明确。可惜的是,没有一个可适用于所有并行问题的魔法公式。同样,对于如何剖析问题和/或发现更好的方法以达到并行加速的问题,也没有一个简单的答案。完全可能发生这种情况,您将经历我在这里罗列的所有工作(以及其他一些工作 — 我还未讨论调试问题),但结果却不比坚持使用序列算法的结果好。还有一些所谓的令人困惑的并行问题,对于这些问题已编写了类似食谱的算法,可通过在线方式和在课本中获取这些算法。可惜的是,许多现实中的程序并不是这样简单易懂。
        以下是对于剖析并行算法的一些提示。所有这些提示利用的都是新版 Visual Studio? 2005 剖析器。它内置于普通的 Visual Studio 界面中(在工具|性能工具|新性能会话菜单项下),它还有一个命令行版本,位于 Team ToolsPerformanceTools Visual Studio 子目录中,名为 VSPerfCmd.exe(有关此工具的使用详细信息,请参见 msdn2.microsoft.com/ms182403.aspx)。此剖析器将创建 VSP 文件,这些 VSP 文件可通过 VSPerfReport.exe 命令进行管道输送以创建 CSV 或 XML 文件,以供进一步分析。以下是要查找的几个项目。
        确保 CPU 被占用。 如果处理器的利用率很低,则很可能是发生了以下两种情况之一。您没有使用足够的处理器来保持问题的占用状态,或者线程被备份以互相等待(这极有可能是因代码中热点处的过度同步引起)。通常任务管理器足以应付此问题,尽管也可使用处理器% 处理器时间性能计数器(通过 PerfMon.exe)。
        确保程序不大量出错。特别是对于数据密集型的应用程序,需要确保物理内存不定期溢出。在这种情况下,一个充满线程的系统可能会在其不断输入和输出页面数据时频繁磨损磁盘。(请回想一下,前面图表所示的磁盘访问的成本是多么昂贵?)与 PerfMon.exe 一样,任务管理器可以为您提供此数据(您需要将其选为一列)。VSPerfCmd 也可使用此命令通过 ETW 事件报告此数据:
  1. VSPerfCmd.exe
复制代码
  1. /events:on,Pagefault
复制代码
  1. /start:SAMPLE
复制代码
  1. /output:<reportFile name>
复制代码
  1. /launch:<exeFile name>
复制代码
然后当程序完成时使用下列命令。
  1. VSPerfCmd.exe /shutdown
复制代码
       您可能还想斟酌一下采样间隔。
        确定程序在哪里花费的 CPU 时间最多。 如果在持有锁时发生此 CPU 时间,则这一点特别重要。也可能出现这种情况,即创建线程、执行同步以及与这两件事相关的任何操作所需的附加代码在支配执行时间。
        检查系统上下文切换/秒和系统处理器队列长度性能计数器。 这有助于确定线程是否过多,以使时间浪费在上下文切换和线程迁移上。如果情况如此,则尝试调整用于确定使用多少任务的算法。
        查找内存层级和高速缓存问题。 如果上述建议没有一个有效,并且似乎应看到更大的加速,则可能存在内存层级和高速缓存问题。在高速缓存缺失和失效上花费的大量时间会极大限制加速程序的能力。对数据分区可更易于高速缓存线操作,并且使用上述一些方法(例如隔离)会有助于解决此问题。每个 CPU 都提供一组性能计数器,可在 Visual Studio 的剖析器中对其查询,包括已引退指令和缺失的高速缓存之类的信息。如果已引退指令的计数很低,则表明有更多时间花费在高滞后时间操作上(例如高速缓存缺失),并且可使用高速缓存特定的计数器来确定发生缺失的位置以及缺失频率。
        尽管确切的计数器特定于处理器,但 Visual Studio 界面为您提供了一个方便的选项来使用它们(参见图 6),也可以通过下列命令查询这些计数器:
  1. VSPerfCmd.exe /QueryCounters
复制代码
<div align="center">
复制代码
  1. 图 6 剖析器属性
复制代码
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友 微信微信
收藏收藏 分享分享 支持支持 反对反对
您需要登录后才可以回帖 登录 | 注册

本版积分规则

小黑屋|文字版|手机版|DIY编程器网 ( 桂ICP备14005565号-1 )

GMT+8, 2025-8-6 01:39 , 耗时 0.102248 秒, 22 个查询请求 , Gzip 开启.

各位嘉宾言论仅代表个人观点,非属DIY编程器网立场。

桂公网安备 45031202000115号

DIY编程器群(超员):41210778 DIY编程器

DIY编程器群1(满员):3044634 DIY编程器1

diy编程器群2:551025008 diy编程器群2

QQ:28000622;Email:libyoufer@sina.com

本站由桂林市临桂区技兴电子商务经营部独家赞助。旨在技术交流,请自觉遵守国家法律法规,一旦发现将做封号删号处理。

快速回复 返回顶部 返回列表