并发数据结构-1.1.4 复杂度测量&1.1.5 正确性

简介:

原文链接译文链接,译者:张军,校对:周可人

1.1.4 复杂度测量

一个被广泛研究的方向是在理想化模型,如并行随机存取机上分析并发数据结构和算法的渐进复杂度[35, 122, 135]。然而,很少有将这些数据结构放在一个真实的多处理器上进行建模的。这里有多种原因,大部分原因跟系统硬件架构与线程异步执行的相互作用有关。想想组合树(combining tree)的例子,虽然我们能通过计算(指令数)得到O(P/logP)的加速比,但这无法反映在实证研究中[52, 129]。真实世界的行为是被上述其他因素支配的,如竞争开销,缓存行为,同步操作(例如CAS)开销,请求到达率,退避延时,数据结构在内存中的布局等等。这些因素很难用一个精确的涵盖目前所有架构的模型来量化。

采集这些方面的复杂度(的)测量方法已经由Dwork [28] ,Anderson,Yang [7]等人提出,虽然这些方法在算法设计上提供了有用的见解,但它们还是不能准确捕捉所有上述因素的影响。因此,并发数据结构的设计者通过在真实机器和模拟架构[9, 52, 97, 103]上运行微基准测试实证地比较他们的设计。在本章的剩余部分,我们普遍地根据这些数据结构基于经验观察到的行为来评价他们,并且用简单的复杂度变量来辅助直觉进行判断。

1.1.5 正确性

很容易发现,简单的基于锁的计数器,其行为跟那些顺序的实现是“相同”的。然而,很明显我们更难看到对合并树(combining tree)而言,同样如此。一般情况下,并发数据结构的实现往往是很精细的,不正确的实现并不少见。因此,声明并严格证明一个特定的设计正确地实现了所要求的并发数据结构是非常重要的。我们不希望达到这些而不精确地指明什么是“正确”。

顺序数据结构的数据结构规格描述通常更容易。例如,我们可以通过选择一组状态,并提供一个以状态,操作名,以及该操作的参数作为函数参数,将新的状态和操作返回值作为函数返回值的转换函数(trainsition function)来指明一个顺序数据结构的语义。加上指定的初始状态,转换函数(transition function)指定了该数据结构上所有可接受操作序列。计数器的顺序语义定义如下:计数器的状态是一组整数,初始状态是0。fetch-and-inc操作的转换函数在旧状态上加一来获取新状态,返回值是计数器的旧状态。

顺序数据结构上的操作是一次一个顺序执行的,我们只简单要求顺序操作的结果遵循如上讨论所指定的顺序语义。然而,对并发数据结构来说,操作不一定完全按照顺序。并发数据结构的正确性条件一般要求有些操作完全按照顺序以满足顺序语义。不同正确性条件的区别在于对总顺序的需求不同。

一个常见的正确性条件是Lamport的顺序一致性(sequential consistency)[81],这要求总顺序上保证每个线程执行操作的顺序。从软件工程的角度来看顺序一致性有个缺点:使用顺序一致性组件实现的数据结构本身可能不是顺序一致的。

一个自然,广泛使用且克服上述问题的正确性条件是Herlihy, Wing的可线性化(linearizability)[58],它是一个用于数据库事务的串行化条件的一个变种。可线性化需要(满足两个特性:(1)该数据结构是顺序一致的 ,(2)使其满足顺序一致性的总体顺序遵循操作执行的实时顺序。遵循实时顺序意味着如果操作O1在操作O2开始之前完成(操作之间不是并发的),那么O1必须排在O2之前。该正确性条件的另外一种理解方式是,它要求我们能够确定每个操作执行时间间隔中的不同点,称为可线性化点,这样,如果我们根据可线性化点的顺序来对操作排序,那么排序结果会遵循顺序一致性语义。

很容易看到,基于锁的计数器是可线性化的,计数器的状态始终存储在变量X中。我们将存储增加后的值到变量X定义为fetch-and-inc操作的可线性化点。基于CAS的无锁(lock-free)实现的计数器的可线性化点同样简单,除了使用CAS指令的语义而不是论证锁语义,我们能得出结论,计数器每被修改一次就加一。

对于合并树(combining tree)来说,很明显我们更难看到它是可线性化的,因为计数器的状态在同一时间不止加一,并且因为一个fetch-and-inc操作实际上可能由之前合并的另一个操作来执行。我们定义基于合并树(combining tree)的计数器上的fetch-and-inc操作的可线性化点如下:当一个获胜线程到达树的根节点并将其累积值加到计数器上时,我们将其之前合并的操作线性化。这些操作根据之后被分发到对应操作的返回值的顺序线性化。虽然详细的可线性化证明超出了我们讨论的范围,但我们应该清楚即使是简单并发数据结构的严格的正确性证明,也是相当具有挑战性的。

直观的吸引力和模块化使可线性化(linearizability)成为一个广受欢迎的正确性条件。在本章剩余部分我们讨论的大部分并发数据结构实现都是可线性化的。然而,在某些情况下,性能和可扩展性可通过满足较弱正确性得到提高。例如,静态一致性(quiescent consistency)条件[10]降低了对总操作顺序满足实时排序的要求,但要求所有静态状态(其中无任何操作执行)之后执行的操作必须排在该状态之前执行的操作之后。一个满足这种弱正确性条件的实现是否有用跟应用有关。相比之下,可线性化的实现始终是有用的,因为设计者可以将其视为具有原子性。 

目录
相关文章
|
9天前
|
存储 负载均衡 并行计算
实现优雅并行编程:确保正确性与提升性能的关键要素
在程序开发中,并行编程一种利用多个处理器或计算资源同时执行多个任务的编程方式,它能够提高计算效率和性能,是提高计算效率和性能的关键手段,但它也带来了一系列复杂的问题,涉及到任务分解、数据同步、资源分配等诸多复杂问题,稍有不慎就可能导致性能瓶颈、死锁甚至数据不一致等状况。编写优雅的并行程序需要在保证程序正确性的前提下,实现高效的并行计算。那么本文就来探讨一下如何在保证程序正确性的前提下,实现优雅的并行程序,以提升计算效率和性能,包括任务分解、数据同步和资源分配等方面的关键要素,希望能够为读者提供一些有用的指导和启示。
16 2
实现优雅并行编程:确保正确性与提升性能的关键要素
|
10月前
|
机器学习/深度学习 传感器 资源调度
【优化控制】基于策略迭代算法求解重构机械臂容错跟踪控制优化问题含Matlab代码
【优化控制】基于策略迭代算法求解重构机械臂容错跟踪控制优化问题含Matlab代码
|
11月前
|
程序员
【编程】程序的局部性原理对代码效率的影响
【编程】程序的局部性原理对代码效率的影响
82 0
|
机器学习/深度学习 存储 程序员
为什么LSTM看起来那么复杂,以及如何避免时序数据的处理差异和混乱(一)
为什么LSTM看起来那么复杂,以及如何避免时序数据的处理差异和混乱(一)
144 1
为什么LSTM看起来那么复杂,以及如何避免时序数据的处理差异和混乱(一)
|
机器学习/深度学习 存储 算法
为什么LSTM看起来那么复杂,以及如何避免时序数据的处理差异和混乱(二)
为什么LSTM看起来那么复杂,以及如何避免时序数据的处理差异和混乱(二)
77 0
为什么LSTM看起来那么复杂,以及如何避免时序数据的处理差异和混乱(二)
|
人工智能 算法
【数据结构】什么的图的关键路径?关键路径相关概念?关键路径算法实现?
【数据结构】什么的图的关键路径?关键路径相关概念?关键路径算法实现?
318 0
【数据结构】什么的图的关键路径?关键路径相关概念?关键路径算法实现?
|
编译器
指令流水线影响因素分类
指令流水线影响因素分类
194 0
指令流水线影响因素分类
|
算法
分析复杂度来判断算法效率
算法复杂度用于分析算法运行所需计算机资源的量,需要的时间资源为时间复杂度,需要的空间资源为空间复杂度。 在判断一个算法的优劣时,可以抛开软件和硬件因素,只考虑问题的规模。编写程序前预先估计算法优劣,可以改进并选择更高效的算法。
117 0
分析复杂度来判断算法效率
|
安全
细节是魔鬼——基于计数器的锁机制的实现准则
有点标题党了,本意是想把对内核锁机制的一些实现细节记录下来,但多少反映了锁机制实现时的一些准则。本文讨论的锁机制主要指基于计数器的锁机制例如 spinlock、mutex,不包括 RCU 这类锁机制。 ### parallesim 在讨论各种锁机制之前,有必要讨论系统的并行度,即有哪些潜在的竞争场景 1. 中断上下文与进程上下文对共享资源的访问,由于中断是异步进行的,因而中断与进程是并发执行
297 0
|
C# 测试技术 JavaScript
最大限度地降低多线程 C# 代码的复杂性
最大限度地降低多线程 C# 代码的复杂性分支或多线程编程是编程时最难最对的事情之一。这是由于它们的并行性质所致,即要求采用与使用单线程的线性编程完全不同的思维模式。对于这个问题,恰当类比就是抛接杂耍表演者,必须在空中抛接多个球,而不要让它们相互干扰。
1596 0

相关实验场景

更多