事务存储结构的实现
扫描二维码
随时随地手机看文章
1 引言
随着多核处理器技术的不断更新和发展,传统的串行程序不论在效率上还是性能上都已经跟不上信息高速发展的脚步了,程序员不得不开发线程级并行以提高片上计算资源的使用效率,但也带来了新的挑战和问题。目前不同线程间的同步、对共享资源的访问等都是通过锁和信号量机制完成的。然而,这种传统的基于锁和信号量的并发系统存在明显的局限性。粗粒度的锁对大量的共享数据做了保护,但是可扩展性不好,因为即使在线程间不存在对共享数据的访问的情况下也可能会出现冲突阻塞现象;细粒度的锁虽然比粗粒度的锁扩展性能好,但由于算法设计的复杂性,普通程序员很难借助细力度的锁实现高效的应用。同时使用锁机制还会带来诸多问题,比如:死锁、优先级反转等,极大地影响了并行应用的效率和性能。
事务存储(Transactional Memory,TM)的使用是解决上述存在问题一个很好的办法[1]。通过将不同并行执行的线程事务化,用事务操作来代替锁机制能降低编程的复杂性。事务是被单线程执行的对内存进行读写的有序操作序列,其特性包括:原子性、隔离性、一致性和持久性。通常事务的执行过程为:调用事务入口函数(begin_transaction)开始执行事务,当事务执行完毕后调用提交函数(commit_transaction)开始提交工作,提交过程分为三个阶段(请求提交、开始提交和完成提交),执行完提交后此事务也就执行完毕,从而继续执行下面的事务。但如果事务在执行或提交过程中发生冲突或者错误,则通过其特有的回滚机制 (rollback)返回到此事务入口继续执行。事务的执行流程图如图 1所示。
图 1 事务执行流程
为了实现事务的这些特性,需有一个很好的TM系统来支持事务数据的版本管理(Version Management)和事务的冲突管理(Contention Management)。版本管理同时对新值(事务提交后可见)和原始的旧值(事务执行过程中发生了回滚的恢复数据)进行管理。根据数据存放方式的不同TM系统区分版本管理为:积极版本管理(Eager Version Management)和懒惰版本管理(Lazy Version Management)。积极版本管理是将新值置于目标存储区中,这样在提交时新值能够很快的得到执行,极大地降低了提交的时延;而懒惰版本管理是将原始的旧值置于目标存储区,虽然会增加提交的延时但是降低了当事务发生回滚后执行的延时。冲突管理是不同事务执行过程中对共享资源访问引发冲突而进行的冲突检测以及管理的机制。冲突管理有积极的(Eager)和懒惰的(Lazy)两种策略,如果冲突在读数据或写数据时立刻被发现而进行仲裁,这种冲突检测是积极的;如果冲突是在事务进行提交时才发现并仲裁的,这种冲突检测则是懒惰的。
目前,基于TM的硬件结构有很多种,图2中列出了目前几种流行的硬件结构根据版本管理和冲突管理而进行的分类。本文将介绍其中的一种结构——LogTM(日志事务存储),通过对其硬件结构(参见图3)、版本管理、冲突管理的实现,展现了此结构的优越性,并给后续研究提供参考和帮助。
图2 TM系统分类
2 LogTM的结构
LogTM是建立在多机系统中基于日志的TM实现,每个核都有独自的私有cache,并通过目录协议来维持数据的一致性。它采用的是积极的版本管理策略和积极的冲突管理策略。图3给出了LogTM的硬件结构,它通过增加一些寄存器和cache中的读/写位很好地完成了对事务的操作。
图3 LogTM的硬件结构 (图中黑框中为其特有结构)
2.1 版本管理(Version Management)
LogTM使用积极的版本管理,将新的执行数据存储在目标区域(目标地址)中,而将旧的数据存储在其它缓冲区。它通过在内存中开辟一块事务日志区域存储事务执行过程中被修改的数据(上文中提到的原始数据)和这些数据所对应的地址,新的执行数据则被保存在目标存储区(目标地址),当执行完成提交时,这些新数据将会立即生效;当事务执行过程中或提交时发生冲突或错误需要回滚时,则通过事务日志中记录的信息恢复出事务执行前的初始状态。
刚开始创建线程时,每一个线程对应着一个日志而且为日志分配一块虚拟存储区域,并将该日志基地址写入Log Base寄存器,当旧数据需要存储到日志时,LogTM通过Log Base寄存器中的值定位到日志的入口然后将旧数据的虚拟地址和值一同保存在日志中以便恢复时使用。为了减少冗余日志,LogTM为每一个cache块添加了对应的写(W)位,用此来标识该cache块在日志中的记录情况。当事务提交成功后,LogTM将对应cache块的写(W)标志位清0并将Log Pointer(日志指针)重新指向日志的入口处,以便处理后续事务。
LogTM也为每个cache块设置了一个读(R)标志位,就像上面我们讨论的写(W)标志位一样。在每个事务操作过程中LogTM同样将读标志位设置用于表示读操作的执行(如图4所示),而且在事务结束后,读标志位也清0。
这种积极的版本管理模式的缺点就是在事务发生冲突或错误需要回滚时执行比较慢,在回滚时,LogTM为了完成恢复必须将原始数据从日志中读到合适的目标地址中然后重置写(W)位,同时因为有很多数据记录在日志中,所以恢复过程必须按照由后向前(后进先出)的顺序进行。但在事务冲突比较少的情况下,这种模式能够带来高效的执行效率。
为了能更好的理解LogTM版本管理过程,图4通过一个例子进行了很好的分析。
(1)事务执行开始前的状态——cache数据块中存放着原始数据,每块的读(R)、写(W)位置0,LogBase(日志基址寄存器)指向日志入口地址,LogPtr(日志偏移指针)指向日志入口地址同时TMcount(事务计数器)置1代表正准备执行一个事务。
(2) 读00地址(十六进制地址)中的数据到寄存器r1中,00地址对应数据块的读(R)标志位置1表示此数据被读。
(3) 将寄存器r2中数据(这里假设为56)存入c0地址中,由于c0地址中存在原始数据34,将c0地址和该原始数据一起根据LogBase中的日志入口地址存入日志中,并将LogPtr指针后移,指向用于存放下个数据的地址位,同时将c0地址对应块的写(W)标志位置1,代表一次写操作的完成,其他的状态不变。
(4) 读取40地址中数据到寄存器r3中,然后r3中数据加1,并将执行后的r3数据存回40地址中,该操作对40地址对应块执行了一次读操作和一次写操作,将读(R)和写(W)标志位置1,然后将原始40地址对应块中数据存入日志中,存入LogPtr指向的地址中,同时将LogPtr指针后移。
(5) 事务提交后状态——将与本事务相关的各个数据块对应读写标志位清0,将LogPtr置位到LogBase,TMcount置0。(本例仅针对单事务执行,如果是嵌套事务的执行,LogTM结构会更加复杂,具体支持嵌套事务的LogTM实现,请参考[2])
(6) 事务回滚后状态——事务在执行或提交过程中如果出错需要回滚,则将日志中记录的原始数据按照地址映射关系重新加载到对应cache数据块中,同时将各个块对应读写标志位清0,LogPtr重置并且TMcount置0。
图 4 事务版本管理过程——成功提交和回滚
2.2 冲突管理(Contention Management)
LogTM采用积极的冲突管理模式,而冲突管理中的一个重要概念目录,就是在内存中开辟的一片用来记录共享数据索引和相关状态信息的区域,也称为目录表。此冲突管理以目录为桥梁,通过目录的分析和消息转发机制来完成多处理机间的冲突检测。具体的实现步骤概括起来为:①请求操作的处理机发出一致性请求到目录;②目录响应请求并可能将请求转发到其他一个或多个处理机上;③每个响应请求的处理机检查自身状态看是否发生冲突;④每个响应请求的处理机给出应答信号,包括冲突应答(nack)和非冲突应答(ack);⑤发出请求的处理机解决冲突。
事务发生冲突后的替换行为须依据目录中有效的MOESI状态(MOESI 状态:Modified(M),Owned(O), Exclusive(E),Shared(S) or Invalidate(I))而定。
下面结合图5中的冲突检测实例对冲突管理的具体行为进行说明。
图 5 LogTM冲突检测实例
(1)事务开始——处理机P开始执行事务,TMcount增1;此时仅目录中存放的cache块信息有效。
(2)处理机P向目录请求数据信息——步骤①:P在自身的cache中找不到某数据,马上发送独占请求(GETX)到目录。步骤②:目录收到请求后根据相应数据的索引找到“老”版本数据传给处理机P,当“老”版本数据达到P时,P将此数据更为“新”版本数据同时将本机此数据块对应读/写标志位置1。步骤③:P接受数据完毕后,发送应答信号给目录表示已经成功接受数据。与此同时目录中的状态信息为M@P(Modified by P),表示此数据正在被处理机P更改。
(3)检测到事务冲突——步骤①:处理机Q发出请求某共享数据的信号(GETS)给目录。步骤②:由于目录中此数据的状态为M@P,目录则根据请求转发给处理机P。步骤③:P接受到请求后检查自己的状态,由于P中相应数据块的写标志位已置,表明P正在修改此数据,不能满足Q的请求,发生冲突。这时处理机P直接发送冲突信号给Q,当Q接受到冲突信号后进行冲突处理。步骤④:处理机Q同时将冲突信号发送给目录,表明此次请求失败。
(4)事务溢出的处理——处理机P通知目录要将修改后的数据存到内存中(目前,内存中存在的是对应数据修改前的“脏”数据)。步骤①:P发出PUTX请求给目录。步骤②:目录认可后发送应答信号给P,通知P可以发送。步骤③:P接收到此信号后将数据写回内存(WB_XACT)同时将溢出位置1(表明此数据已经不在cache中)。这样在写回操作完成后,P中相关数据块信息已置为无效,但是目录中仍然保持着原先P持有数据时的状态,内存中对应区域已为修改后的“干净”数据,目录中该数据相应的状态也由“老”变成了“新”,表明内存中此数据已为更新后的数据。
(5)溢出数据的事务冲突检测——步骤①:处理机Q重新发出请求数据信号给目录,由于目录中的状态还没有改变。步骤②:目录根据当前状态再次将请求转发给处理机P,而此时Q请求的数据块已经写回内存中去了,并不在P的cache中,P收到请求信号后检查到自身的溢出位已经置位,它认为此数据可能由于某种原因不在cache中,但是仍然与它相关。比如:由于此数据块大小大于cache规定块大小而不能放下,但仍需操作。步骤③:P发出冲突信号(NACK)给Q,但是这个冲突并不是真正意义上的冲突,而是P假设的冲突。步骤④:Q收到冲突信号后处理冲突同时发送信号给目录,表明此次请求再次失败。
(6)目录中数据状态的懒惰(Lazy)更新——处理机P提交事务后将TMcount减1,将对应cache块的读/写标志位和溢出标志位清零,但此时目录中的状态仍然为M@P。步骤①:此时一旦处理机Q重新发出请求此数据信号。步骤②:该信号会再一次通过目录转发给处理机P,但此时P的溢出位已经被清空。步骤③:P通过发送清除信息(CLEAN)给目录通知目录不必再转发请求信息,目录中的数据信息有效可以直接发送给请求的处理机Q。步骤④:目录根据索引关系找到相关数据发送给处理机Q。步骤⑤:Q收到数据后进行处理同时将应答信号发送给目录,表明请求成功同时将目录对应数据项状态置E@Q,表示此时处理机Q独占此数据资源。
但在执行冲突检测的过程中也会存在错误的冲突,比如:当处理机Q请求访问一个数据块,该数据块在目录中的状态为M@P,而处理机P已经执行到后续事务,同时也置了溢出位。P发送冲突信号给Q,但这个冲突并不是因处理机Q请求的数据正被其他占有而产生的冲突,是一种无关的冲突。所以必须采取一种机制将目录状态及时更新。
2.3 操作系统对LogTM的支持
由于事务的引入,传统操作系统已经不能满足要求,必须更改或扩展操作系统使事务能稳定高效的执行。
首先,基于LogTM系统,操作系统负责对日志进行创建和维护。它为每一个执行线程开辟一片日志区域,并将该区域入口地址写到Log Base寄存器中,同时当某数据存入日志后使Log Pointer指针后移,用来存放新数据。当发生回滚,操作系统根据目前Log Pointer指针从后向前恢复数据直到Log Pointer与Log Base指向相同为止。
其次,当执行事务发生切换时,操作系统可以通过扩展目前的TCB(线程控制块)来对事务相关寄存器内容等信息进行保存。
再次,当发生事务级线程切换时,操作系统判断切换原因(包括其他线程抢占、时间片到达、事务之间冲突等而执行的切换),通知调度器采取不同的切换策略或冲突策略来完成切换。
最后,由于中断内新创建的事务和被中断事务冲突而导致活锁(被中断事务挂起得不到执行,中断内新创建事务由于冲突策略一直回滚——重新执行——回滚,也得不到执行),操作系统必须能够记录回滚次数并设定一个门限值,如果同一事务回滚数超过此门限,操作系统可以强行中止该事务而调度其他事务。
3 结论及展望
本文介绍TM的基本原理,并对当前主流TM系统LogTM进行分析实现,得出以下结论:
⑴要实现高效的事务处理必须要有一个很好的基于事务模型的硬件结构。比如:LogTM,硬件专门为TM添加了LogBase、LogPointer等寄存器并改变了cache的结构,在cache中加入了读(R)和写(W)标志位;这样对事务版本管理以及冲突管理都带来了前所未有的作用,这也是此TM结构优越性的体现。
⑵要高效的进行事务处理必须要有TM操作系统的支持,上文中提到了一些操作系统对LogTM的相关支持,但如果要完美的支持事务还需要不断更改和优化已有的操作系统,最终的目的是将操作系统事务化,并能很好的处理事务化的用户级应用。
⑶目前TM系统(包括LogTM)虽然通过一些特有的结构和机制解决了事务处理的一些问题,但是面对今后的发展,像多级嵌套事务的复杂应用、中断事务化(特别是外部设备的中断)、挂起事物与执行事务冲突问题、被切换事务的执行选择(重新调度后,被切换事务可能回滚也可能继续接着执行)等问题都需要我们不断的去研究,去寻找最优的解决办法一一攻克,所以对TM的研究任重而道远。
参考文献
[1]Yen, L. and J. Bobba, et al. LogTM-SE:Decoupling Hardware Transactional Memory from Caches. In Proc. of Thirteenth Annual International Symp. on High-Performance Computer Architecture.Feb.2007
[2]Moravan, M. J. and J. Bobba, et al. Supporting Nested Transactional Memory in LogTM. In Proc. of the Twelfth International Conference on Architectural Support for Programming Languages and Operating Systems, pages 359-370,Oct.2006
[3]Moore, K. E. and J. Bobba, et al. LogTM: Log_based Transactional Memory. In Proc. of the Twelfth IEEE Symp. on High-Performance Computer Architecture, pages 258–269, Feb. 2006
[4]Herlihy, M. and J. E. B. Moss . Transactional Memory:Architectural Support for Lock-Free Data Structures. In Proc. of the 20th Annual International Symp. on Computer Architecture, pages 289–300, May 1993
[5]Hammond, L. and V. Wong, et al. Transactional Memory Coherence and Consistency. In Proc. of the 31st Annual Intl. Symp. on Computer Architecture, June 2004
[6]Hammond, L. and B. D. Carlstrom, et al. Programming with Transactional Coherence and Consistency(TCC), ASPLOS.04, October 7–13, 2004
更多计算机与外设信息请关注:21ic计算机与外设频道