软件事务性存储器操作的优化的制作方法

文档序号:6569869阅读:291来源:国知局
专利名称:软件事务性存储器操作的优化的制作方法
软件事务性存储器操作的优化 相关申请的交叉引用
本申请要求2006年3月23日提交的美国专利申请第11/389,451号的优先 权,后者要求2005年12月7日提交的美国临时申请第60/748,386号的优先权。
背景
多线程进程的多个线程在并发执行期间共享公共存储器是常见的。因此, 多线程化进程的两个不同线程必须读取和更新可由程序访问的同一存储器位 置。然而,必须小心确保当一个线程正处于依赖于共享存储器位置的值的操纵 序列中间时另 一个线程不会修改该值。
例如,假定一程序正在访问两个不同软件对象的内容,其中每一对象表示 一不同银行帐户中的金钱数额。最初,第一帐户的数额是$10,其存储在存储 器地址A1处,而第二帐户的数额是$200,其存储在存储器地址A2处。银行 程序的第一线程被编码为将$100从A2转移到Al,而第二线程被编码为计算 两个帐户中的资金的总额。第一线程可通过向Al的内容增加$100而开始,从 而将其更新为$110,然后继续从A2的内容中减去S100,从而将其更新为$100。 然而,如果第二线程在这两个操作之间执行,则第二线程可对这两个帐户计算 出不正确的总额$310而非正确的总额$210。
软件事务性存储器提供了一编程抽象,通过该编程抽象,线程可安全地执 行一系列共享存储器访问,从而允许线程在没有来自另一线程的干扰的情况下 完成其事务。因此,事务性存储器可在软件中用于确保包括第一线程的示例性 加法和减法操作的事务关于存储器位置Al和A2是"原子的",且因此第二 线程将计算两个帐户的正确的总额。
然而,用于以软件来实现事务性存储器的现有方法遭受到性能问题。例如, 在一种现有方法中,当一线程在一事务内访问一系列存储器位置时,该线程维 护它希望在该事务期间读取和更新(即,写入)的存储器位置和值的单独列表,然后在该事务结束时,该线程更新实际共享存储器位置处的所有这些值。如果 在该事务期间,该线程希望重新读取或重新写入其列表中的任何存储器位置, 则该线程必须在列表中搜索该存储器位置的条目以访问该条目,这在程序上是 很慢的事情。因此,这一用软件实现事务性存储器的间接方法遭受较差的性能。 另外,用软件实现事务性存储器的现有方法引入了大量的开销,包括对事 务性存储器和记录保持指令的不必要的调用,导致程序的执行受损,尤其是在 这些指令以低效的方式执行的情况下。另外,某些事务性存储器方案中固有的 记录保持活动不能有效地限制它们所创建的记录的创建和维护,这会浪费存储 器以及盘空间和其它系统资源。
概述
描述了一种软件事务性存储器系统("STM")。此处所描述的系统和技 术对软件事务性存储器指令执行优化以达到高效的性能。描述了一种编译器, 它用软件事务性存储器指令来替换软件事务性存储器块,并且进一步将这些指 令分解成分解的软件事务性存储器指令。该编译器利用关于指令语义的知识来 执行在传统的软件事务性存储器系统上不可得的优化。该编译器另外对STM 代码执行高级优化。执行这些优化中的某一些以便利用较低级优化。这些高级 优化包括不必要的读一更新升级的移除、过程调用周围的STM操作的移动、 以及对新分配的对象的不必要的操作的移除。另外,优化STM代码以便为在 事务外部写的存储器访问提供强原子性。
在一个示例中,在包括处理单元和用关于软件事务性存储器操作的知识来 配置的编译器的计算机系统中描述了一种编译包括软件事务性存储器块的方 法。该方法包括优化程序以创建包含软件事务性存储器指令的经优化的程序, 以及编译该经优化的程序。
提供本概述以便用简化的形式介绍将在以下详细描述中进一步描述的一 些概念。本概述并不旨在确定所要求保护的主题的关键特征或必要特征,也不 旨在用于帮助确定所要求保护的主题的范围。
参考附图,从以下各实施例的详细描述中,将清楚其它特征和优点。附图简述


图1是用于编译构成原子存储器事务块的源代码的编译器的框图。 图2是图1的编译器的组件的框图。
图3是示出使用事务性存储器来编译和执行程序的示例进程的流程图。 图4是示出由图1的编译器执行的用于用事务性存储器来编译程序的示例 进程的流程图。
图5是示出由图1的编译器执行的用于执行高级软件事务性存储器优化的
示例进程的流程图。
图6是示出由图1的编译器执行的用于在编译期间优化分解的软件事务性
存储器指令的示例进程的流程图。
图7是示出由图1的编译器执行的用于引入实现强原子性的操作的示例进 程的流程图。
图8是示出由图1的编译器执行的用于移除读一更新升级的示例过程的流 程图。
图9是示出由图1的编译器执行的用于移除读一更新升级的另一示例进程 的流程图。
图IO是示出由图1的编译器执行的用于移动过程调用周围的操作的示例 进程的流程图。
图11是示出由图1的编译器执行的用于移除对新分配的对象的日志记录
操作的示例进程的流程图。
图12是示出由图1的编译器执行的用于移除对新分配的对象的日志记录
操作的另一示例进程的流程图。
图13是包括在软件事务性存储器系统的运行时环境中在运行时期间使用
的软件模块的框图。
图14a和14b是示出使用多用途首部字的示例性对象的框图。
图15a和15b是示出具有改变的快照的示例性对象的框图。
图16是示出图6的运行时环境的用于使用快照来确定对象的示例进程的
流程图。
图17是示出图6的运行时环境的用于使用扩大的首部字来修改对象的快照的示例进程的流程图。
图18a和18b是示出事务执行的示例的框图。 图19a-19c是示出事务执行的其它示例的框图。
图20是示出在图6的运行时环境中用于日志过滤的示例结合表的框图。
图21是示出图6的运行时环境的用于使用图13的结合表来过滤日志条目 的示例进程的流程图。
图22是示出图6的运行时环境的用于使用图13的结合表来过滤日志条目 的另一示例进程的流程图。
图23是示出图6的运行时环境所执行的用于在无用信息收集期间压縮日 志的示例进程的流程图。
图24是示出图6的运行时环境所执行的用于在无用信息收集期间压縮日 志的另一示例进程的流程图。
图25是示出图6的运行时环境所执行的用于在无用信息收集期间压缩日 志的又一示例进程的流程图。
图26是用于实现此处的技术的合适的计算环境的框图。
详细描述
此处所示的示例描述了基于软件和硬件的事务性存储器系统,以及对这些 系统的性能改进。具体地,以下的实现示例描述了分解的软件事务操作;使 用编译器中间表示("IR")中的STM原语来允许代码优化(其术语在以下 解释);用于改进这些原语的性能的编译器改进;使用结合表的运行时日志过 滤;以及高效的运行时按对象操作。尽管此处所提供的描述是作为对特定软件 事务性存储器操作实现的优化来提供的,但是可以认识到,此处所描述的技术 和系统可对各种实现操作,并且不一定暗示对此处所描述的技术的实现、性能 或要求的任何限制。
1.软件事务性存储器系统的示例
原子块提供了对编写并发程序的问题的有希望的简化。在此处所描述的系 统中, 一代码块被标记为原子的,并且编译器和运行时系统在该块内提供那些表现为原子的操作,包括函数调用。程序员不再需要担心手动锁定、低级竞争 条件或死锁。原子块也可提供异常恢复,由此如果异常终止一个块,则该块的 副作用被回退。这甚至在单线程化应用中也是有价值的出错处理代码通常难 以编写和测试。原子块的实现縮放到大型多处理器机器,因为它们是并行性保 留的原子块可并发地执行,只要一个块中正被更新的位置不在任何其它块中 被访问。这保留了在常规的数据高速缓存中所允许的共享的种类。
此处所描述的技术参考了与编译器和运行时系统紧密集成的STM实现。 该实现的一个特征是它是直接更新STM。这允许直接在堆中更新对象而不是
在对象的私有阴影副本(shadow copy)上工作,或经由对象引用和当前对象内 容之间的额外的间接层次来更新。这对于成功提交的事务而言更高效。
此处所描述的系统和技术利用了该实现的提供分解的STM接口这一特 征。例如,事务存储obj.field = 42被拆分成以下步骤(a)记录obj正被当前 线程更新,(b)将fidd所保持的旧的值记入日志,以及(c)将新值42储存到field 中。这一新设计允许向事务操作提供经典的优化。例如,本示例中的三个步骤 由编译器单独处理,并且(a)和(b)通常可以从循环中提升。在此处所描述的技术 中,通过使用具有关于STM接口和语义的知识并能够执行被配置为在该接口 上起特别的作用的编译器来使得分解的STM接口更高效。
在另一示例中,此处所描述的系统和技术示出了通过利用集成的事务性版 本化的高效的按对象操作而在所描述的STM实现中所获得的效率。这些实现 使用了事务性版本化与现有的对象首部字的集成。这不同于其它STM系统, 因为这些系统或者使用版本化记录的外部表、附加的首部字、或者使用对象引 用和当前对象内容之间的间接层次。这些方法导致不良的高速缓存局部性或增 加了空间使用。此处所描述的实现利用了扩大的首部字,以及允许在事务提交 期间对对象修改进行快速验证的高效的快照指令。
此外,描述了运行时日志过滤。该过滤是有用的,因为并非所有不必要的 STM操作都可在编译时被静态地标识。
在一个实现中,此处所描述的示例是在Bartok中实现的,Bartok是一种 用于公共中间语言(CIL)程序的优化提前研究编译器和运行时系统,其具有 可与Microsoft .Net平台相比的性能。该运行时系统可用包括无用信息收集器和该新STM的CIL来实现。 1.1语义
此处所描述的技术集中于原子块的执行。各种实现可在确切的语义上有所 不同,包括原子块与锁定代码的交互,以及在继续利用这些技术的同时将I/O 操作与原子块组合。
1.2设计假设
在此处所描述的示例中,关于如何使用原子块作出了一些假设。这些并不 一定表示关于此处所描述的实现的限制,而是用于便于描述。
一个假设是大多数事务都是成功提交的。这是一个合理的假设,因为首先, 对并行化保留STM的使用意味着事务不会"自发"地或由于程序员不能理解 的冲突而异常中止(在替换实现中,冲突是基于散列值来检测的,而散列值可 能会意外地冲突)。作为此一部分,假设由于高速缓存之间的过多数据移动的 成本程序员已经具有强烈的动机来避免竞争。诸如将高竞争操作移交给由单个 线程管理的工作队列等技术保持有价值。
第二个假设是在原子块中读取比更新多。该假设是通过对当前程序的观察
来证实的,并且试图形成其事务性版本。这强调了将事务性读取的开销保持得 特别低的好处读取仅涉及将正被读取的对象的地址以及其首部字的内容记入 日志。
最后一个假设是事务大小不应当被限定。这在建议STM实现需要随着事 务长度的增长而良好地縮放的同时保留了复合性。在这一设计中,空间开销随 着事务中访问的对象的数量而非随着所作出的访问数而增长。在此处所描述的 示例中,事务被非正式地称为"短"或"长"。短事务可能在不需要STM的 任何存储器分配的情况下运行。长事务是其执行可能会横跨GC周期的那些事 务(例如,评估已被转换成C弁的SEPC95基准xlisp版本的LSIP基准之一)。
1.3基于字的STM示例
用于基于字的STM的一种常规接口提供了以下两组操作void TMStartO
void TMAbortO
bool TMCommitO
bool TMisvalidO
word TMRead(addr addr)
void TMwHte(addr addr, word value)
第一组用于管理事务TMStart启动当前线程中的一个事务。TMAbort异 常中止当前线程的事务。TMCommit试图提交当前线程的事务。如果事务不能 提交(例如,在一个实现中,由于一并发事务己经更新了其所访问的位置之一), 则TMCommit返回假,并且丢弃当前事务。否则,TMCommit返回真,并且 在该事务期间所作出的任何更新被原子地传播到共享堆。TMIsValid当且仅当 当前线程的事务可在调用的时候提交时才返回真。第二组操作执行数据访问 TMRead返回所指定的位置的当前值,或由TWWrite在当前事务中写入的最新 近的值。
在此处所描述的技术的一个实现中,直接用STM来编程的过程通过使得 编译器重写原子块中的存储器访问来使用STM操作,并使其生成所调用方法 的专门版本来确保TMRead和TMWrite被用于对在原子块中作出的所有存储 器访问来自动化。
上述设计遭受限制其适用性的多个问题。以下代码示例说明了这一点。以 下示出的示例la迭代通过标记节点this.Head和this.Tail之间的链表的各元素。 它将节点的Value字段相加,并将结果存储在this.Sum中。示例lb示出了自 动发出对所有存储器访问的TMRead和TMWrite的调用的一个示例。
然而,对于这一基于字的系统会出现若干性能问题。首先,TMRead和 TMWrite的许多实现使用在每一 TMRead和TMWrtie操作上搜索的事务曰志。 TMRead必须看见同一事务的更早期的存储,因此它搜索保持试探更新的事务 日志。这一搜索可能无法縮放以支持大型事务。性能取决于事务日志的长度以 及辅助索引结构的有效性。其次,对STM库的不透明调用阻碍了优化(例如, 不再可能从循环中提升读取this.Tail,因为TMRead的行为对于编译器是未知 的)。最后,单块TM操作导致重复的工作。例如,在循环中访问一字段时的 重复搜索。1.4分解的直接访问STM
在此处提供的示例中使用的分解的直接访问STM实现解决了这些问题。 第一个问题是通过将系统设计成使得事务可直接对堆执行读和写操作来解决 的,从而使读自然地看见前一事务存储而无需任何搜索。仍需要日志来回退异 常中止的事务并跟踪关于所访问的位置的版本化信息。对于短事务,这些曰志 是只能被追加的。由此,不论事务的大小如何,都不需要搜索。
第二个问题是通过在编译的早期引入TM操作并扩展后续分析和优化阶 段以使其知晓其语义来解决的。最后,第三个问题是通过将单块TM操作分解 成分离的步骤以避免重复工作来解决的。例如,对事务日志的管理与实际数据
访问分开,这通常允许从循环中提升日志管理。
该接口将事务性存储器操作分解成四个组 tm一mgr DTMGetTMMgx ()
void DTMStaa:t (tm—mgr tx) void DTMAbort(tm一mgr tx) bool DTMCommit(tm一mgr tx) bool DTM工sValid(tm一mg2: tx》
void DTMOpeiaFor;Read(ttn一mg:r tx, object obj 〉 void DTMOpenForUpdate (tm—mgr tx, object ob j ) object DTMAddrToSurarogate (tm—mgr tx, addr addr)
void DTMLogFieldStore (tm—mgr tx,- object ob j , int offset) void DTM:L0gAdd2:St03:e(tmJSgr tx, acidr ob j )
前两个组是直接的,提供了获取当前线程的事务管理器的
DTMGetTMMgr,然后提供了普通的事务性管理操作。第三组提供了竞争检测 DTMOpenForRead和DTMOpenForUpdate指示所指定的对象将以只读模式访 问,或者它随后可被更新。对静态字段的访问由代表静态字段来保持版本化信 息的代理对象调解DTMAddrToSurrogate将地址映射到其代理。最后一组维 护一撤消日志,需要在异常中止时回退更新。DTMLogFieldStore处理对对象字 段的存储,而DTMLogAddrStore处理对任何地址的存储。
对这些操作的调用必须被正确定序以提供原子性。有三个规则(a)当读 取一位置时,该位置必须被打开以供读取,(b)当更新一位置或对该位置将存 储记入日志时必须打开该位置以供更新,(c)在更新一位置之前必须将该位置 的旧值记入日志。在实践中,这意味着对于一对象的字段的TMRead的调用被 拆分成DTMGetTMMgr、 DTMOpenForRead的序列,然后是字段读取。对TMWrite的调用则被拆分成DTMGetTMMge 、 DTMOpenForUpdate 、 DTMLogAddrStore的序列,然后是字段写入。对静态字段的TMRead的调用 被拆分成DTMGetTMMgr、 DTMAddrToSurrogate、 DTMOpenForRead的序歹U, 然后是静态字段读取。对TMWrite的调用则被拆分成DTMGetTMMgr、 DTMAddrToSurrogate、 DTMOpenForUpdate、 DTMLogAddrStore的序列,然后 是静态字段写入。
以下示例展示了使用分解的直接访问STM的一个示例。示例1中的代码 迭代通过标志节点this.Head和this.Tail之间的链表的各元素。它对节点的Value 字段求和,并将结果储存在this.Sum中。示例2示出了如何使用分解的直接访 问STM来实现Sum (求和)。
示例la
public int Sum() {
Node n = this.Head,, int t = 0; do {
t += n.Value;
if (n--this.Tail)
this. Sum = t,' return t,'
} while (true)
示例lb
public int Sum() {
Node ii - TMRead (&this. Head),. int t =_ 0 do {
t += TMRead(&n.Value)
if (n==TMRead(&this.Tail))
TMWrite (&this.Sum, t) return t;
rx - TMRead(&ruNext); } while (true》
示例2public int Sum() {
tm—mgr tx - DTMGetT刚gr () DTOOpenForRead(tx, this)j Node n - this.head,* int t = 0; do {
DTMOpenForRead(tx, n》; t += n .Value DTMOpenForRead(tx, this) if (n-=this.Tail) {
DTMOpenForUpciate (tx, this);
DTML)OgFieldStore(tx, this, of f setof (List Sum)); this.Sum -return t,-
DTMOpenForReaLd(tx, n〉,'
} while (true》
2.编译器优化
第2节描述了利用采用STM操作的知识来配置的编译器对分解的STM操 作的优化。应当注意,如在本申请中所使用的,术语"优化"、"经优化的" 等是本领域中一般指在不参考任何特定改进程度的改进的术语。由此,在各种 情形中,尽管"优化"可以改进系统或技术的性能的一个或多个方面,但是它 并不一定要求该系统或技术的每一方面都被改进。另外,在各种情形中,"优 化"不一定意味着对任一方面的达到任何特定的最小或最大程度的改进。此外, 尽管"经优化的"系统或技术可能在一个或多个领域中显示出性能改进,但是 它也可能在其它领域中显示出性能降低。最后,尽管"优化"在某些情形中可 改进系统或技术的性能,它也可能在其它情形中降低性能。在以下描述的特定 环境中,尽管优化将导致冗余的或多于的STM指令或日志写的移除,这可能 提供提高的性能,但是这些优化不应意味着将移除每一可能的冗余或多于的指 令。
图1是示出用于利用软件事务性存储器来创建经优化的程序120的编译器 100的一个示例的框图。在所示的示例中,编码器100取源代码110作为输入。 如图所示,源代码110包含一个或多个原子块115。如上所述,在一个实现中, 这些原子块的包括避免了希望利用STM的程序员的额外编程;这些块由编译 器修改以包括分解的STM指令,这些指令然后被优化。尽管图l示出了单一的一段源代码,但是应当认识到,这仅仅是出于简化说明的目的;此处所描述
的技术和系统也适用于一起编译的多个源代码文件,以及使用己经编译好的代
码的源代码。另外,在各种实现中,使用不同的代码语言,包括C++、 C#、 Java、 C以及其它语言;并且,在各种实现中,也可优化已解释的语言。在所示的示 例中,这一优化是由被集成在编译器中的STM优化150来提供的;该集成的 额外细节将在以下讨论。在编译和优化之后,产生利用软件事务性存储器的经 优化的程序120。这一经优化的程序的运行时操作的额外细节将在以下更详细 讨论。另外,尽管所示的实现示出在执行之前编译成可执行文件,但是此处所 描述的技术的替换实现可刚好在执行之前或与执行并发地编译和优化程序。
图2是示出图1的编译器100的示例组件的框图。图2示出了通过编译器 的示例操作路径。尽管图2单独示出了特定的模块,但是应当认识到,在各种 实现中,模块可用各种组合来合并或划分。该路径始于第一编译器模块220, 该模块接受源代码110并从中创建中间表示230。在一个实现中,该IR取控制 流图("CFG")的形式,这允许通过此处所描述的优化技术来容易地操纵它。
接着,优化模块240修改IR 230以创建经优化的IR 250。在优化模块240 的操作中,用低级和高级STM专用的优化来扩展传统的编译器优化。这种优 化的示例将在以下更详细描述。最后,该经优化的IR 250由第二编译器模块 260优化成可执行代码,诸如图1的经优化的程序120。
图3是用于使用STM来编译和执行程序的示例进程300的流程图。在各 种实现中,所示的进程框可被合并、划分成子框、或省略。该进程始于框320, 在那里接收包含事务性存储器块(诸如图1的原子块)的源代码。在一个替换 实现中,源代码可能不包含事务性存储器块,而是将包括单独的软件事务性存 储器指令,诸如上述基于字的或分解的指令。接着,在框340处,将该源代码 编译成可执行程序。编译的具体示例将在以下更详细描述。最后,在框360处, 执行该可执行程序。
图4是用于编译结合了事务性存储器块的源代码的示例进程400的流程 图。进程400对应于图3的框340。在各种实现中,所示的进程框可被合并、 划分成子框、或省略。该进程始于框420处,在那里编译器100将软件事务性 存储器指令插入到每一原子块中。在一个实现中,该插入是通过在该块内的读或写的每一实例周围插入正确的基于字的读和写STM指令来执行的。在另一
实现中,如果程序员决定插入其自己的STM指令,则框420的进程可被省略。 接着,在框440处,编译器100用分解的指令来替换基于字的STM指令。 在一个实现中,如果编译器接收到的源代码包含已经分解的指令,则省略框440 的进程。另外,在某些实现中,特别是框420和440的进程可被组合以响应于 接收到原子块来插入分解的STM指令。以上示例2示出了在框440的进程的 操作之后一段代码看上去会像什么。
在框440的进程的另一实现中,编译器还通过分解日志操作来减少日志管 理成本,从而允许将日志管理工作的成本分摊到多个操作上。特别地,在一个 实现中,DTMOpeW和DTMLog^^操作以对当前数组中存在空间的检查开始。 对于DTMOpenForRead,这是在该代码的快速路径版本中必须执行的唯一检 査。为分摊这些检查的成本,编译器利用了一个新的操作EnsureLogMemory, 该操作取指示在给定日志中保留多少槽的整数。DTMOpeW和DTMLog^^的专 门分解的版本因此可假设存在空间。为减少运行时的簿记,在一个实现中, EnsureLogMemory操作不是相加的两个连续的操作保留所请求的最大值而非 总数。为简明起见, 一个实现没有进行在调用或返回边沿之后需要保留的空间 的专门操作。在另一实现中,对每一基本块内的调用之间的所有操作组合保留。 在另一实现中,使用后向分析来尽可能早地急切地保留空间,从而被迫在所有 的调用和循环首部处停止。这具有组合更多保留的优点,但是可能会在不需要 保留操作的路径上引入保留操作。
在框460处,编译器执行高级STM优化,包括对强原子性的操作的引入、 不必要的STM操作的移动和移除、以及对新分配的对象的日志操作的移除。 该进程将在以下更详细描述。最后,在框480处,优化该程序,包括STM指 令。尽管图4的进程示出了高级优化之后的框460和480中的其它优化,并且 没有示出优化的重复,但是在某些实现中,框460和480的进程或其子进程可 按与所示的不同的次序来执行,并且可被重复。重复的一个原因是某些优化可 能展示出对于其它优化的机会。由此,可能希望重复地执行优化以便利用它们 可能引发的机会。
图5是用于对STM指令执行高级优化的示例进程500的流程图。进程500对应于图4的框460。在各种实现中,所示的进程框可被合并、划分成子框、
或省略。在一个实现中,进程500在以下描述的进程600的编译器优化之前执 行,以使高级优化所增加的操作可被编译器进一步优化。该进程在框520处开 始,其中编译器引入对强原子性的操作。接着,在框540处,用打开来更新
(open-for-update)操作替换打开对象以供读取的操作及其后的打开同一对象 以供更新的操作,以便允许稍后在后续优化期间移除打开操作。在一个实现中, 这些打开来读取操作及其后的打开来更新操作被称为读取一更新
(read-to-update)升级;框540的进程移除了这些升级。接着,在框560处, 移动过程调用周围的分解的STM操作,以提供图6的进程中的更大优化。最 后,在框580处,移除对于在将对象记入日志的事务中新分配的对象的日志记 录操作以防止不必要的日志操作调用。这些进程的每一个的具体示例将在以下 参考图7-12来更详细描述。
2.1对分解的代码的编译器优化
图6是用于对STM执行执行优化的示例进程600的流程图。进程600对 应于图4的框480。在各种实现中,所示的进程框可被合并、划分成子框、或 省略。另外,尽管所示的实现给出了其中每一动作被执行一次的示例,但是在 替换实现中,动作可被重复。由此,例如,以下描述的公共子表达式消除动作 可以在执行了代码运动优化之后被执行第二次。尽管图6未示出非STM指令 的优化,但是这是出于简化说明而这样做的,并非展示对此处所描述的进程的 任何限制。
该进程在框620处开始,在那里对STM指令的修改创建约束。在一个实 现中,这些约束至少是对原子性的约束,这是基于调用序列的。由此,有三个 规则(a)当读取一位置时该位置必须被打开以供读取,(b)当更新一位置或对 其将一存储记入日志时必须打开该位置以供更新,(c)在一位置被更新之前该 位置的旧值必须被记入日志。
这些规则可使用多种方法来实现。在一种方法中,编译器通过各种家务管 理(housekeeping)措施在编译期间跟踪约束。由于这会迅速使编译进程变复 杂,因此在另一实现中,可修改CFG以防止违反约束。 一种这样的方法是使用STM指令之间的哑变量来引入数据依赖性,该STM指令通过形成用于指令
的观输出变量(变为用于后续指令的输入变量)来强制实施调用次序。由此,
看上去像以下的IR (使用类属指令) open一for一update (loc >; log—f or一update (loc) write (locf val》;
变为
dununyl = open—for—update (loc); dummy2 -r.log—for—updater (loc, dummyl》 write (loc, val, dummy2)
接着,在框640处,对STM指令执行公共子表达式消除("CSE"), 之后在框660处对指令执行冗余加载一存储消除,并在框680处执行代码移动 优化。
在一个示例中,这些优化可对DTMGetTMMgr操作执行,因为它是恒定 的,且因此为CSE提供了机会。类似地,由于DTMOpenForRead 、 DTMOpenForUpdate 、 DTMAddrTo Surrogate和DTMLog承操作在事务内是幂等 的,因此它们符合CSE或代码运动的条件。对此优化的一个约束是在一个实 现中,代码运动不能扩展超过事务边界。在另一实现中,扩展CSE以提供对 在DTMOpenForUpdate之后发生的DTMOpenForRead的消除。该优化可被执 行是因为更新访问包含了读访问。
在其它实现中,可对嵌套事务之间的操作执行CSE。由此,在一个示例中, 嵌套事务中的DTMOpenForRead被外部事务内DTMOpenForRead或 DTMOpenForUpdate包含,且由此可被消除。在另一示例中,嵌套事务中的 DTMOpenForUpdate被外部事务中的DTMOpenForUpdate包含,且被消除。
在另一实现中,DTMGetTMMge操作可通过从每一线程的Thread对象中 取出用于线程的当前事务管理器(并在必要时创建事务管理器)来实现。Bartok 编译器因此可将GetCurrentThread指令作为服从代码运动的常数操作来处理。
作为一个示例,在上述进程执行之后,示例2的代码被简化为以下更高效 的代码
示例3public int Sum() {
tm一mgi: - DTMGetTMMgr () DTMOpenForRead(tx, this) 7 Node n - this,head; int t = do { -
DTMOpenForReaci(tx, n)
t += n. Value,'
if (n--this.Tail) {
DTMOpenForUpdate(tx DTMIiogFieldStore (t义 this.Sum = t/ return t,'
n = n, Next } while (true)
2.2高级STM优化
2.2.1实现强原子性
上述技术可用于构建"原子块",其中一个原子块中的存储器访问相对于 第二原子块中的访问不可分割地发生。然而,由一个线程执行的"原子"块在 第二个线程执行冲突的存储器访问而没有使用"原子"块时可能看上去并不是 不可分割地执行的。具有这一特征的设计可被认为是提供了 "弱原子性"。
此处所描述的技术的一个实现涉及如何提供"强原子性",其中原子块看 上去相对于所有存储器访问都是不可分割地执行的,而非仅仅是那些在其它原 子块中作出的存储器访问。
一个基本实现扩展了上述STM,其通过(a)标识发生在任何原子块外部的 对共享存储器的所有访问,(b)将这些访问重写为短原子块,而具有对强原子性 的支持。
例如,假定一程序从字段"ol.x"的内容中读取,并将结果储存在字段"02.x" 中。这最初由编译器中间表示(IR)的两个指令来表示
tl = getfield<x>(ol) 1j2:
putfield<x>(o2, tl)
基本实现将这些扩展为诸如以下的代码
,this);
,this, offsetof(List.Sum));Ij1:
DTMStart(tm) DTMOpenForReaci (tin, ol) tl = getfield<x>(ol) DTMCommit(tm》// CI L2:
DTMStart(tm)
DTMOpenForUpdate(tm, o2) logfield<x>(o2) putf ield cx>《o2, tl) DTMCommit(tm) 〉/ C2
(在某些实现中,所编写的实际代码更复杂,因为如果在提交操作ci或
C2期间有竞争,则它还必须包括从L1到L2的重新执行事务的代码路径。该 代码的确切细节将取决于如何在IR中表示STM操作而变化。)
该基本形式将提供强原子性,但是由于附加的事务启动、事务提交、打开 来读取、打开来更新、以及日志操作的成本超过了原始字段访问的成本,因此 其执行不良。
为提高效率同时仍提供强原子性实现,此处所描述的技术的一个实现使用 了专门的IR操作来加速仅访问单个存储器位置的短事务的执行。
有两种情况要考虑从单个位置读取的事务,以及更新单个位置的事务(包 括对单个位置执行读取一修改一写入操作的事务)。这两种情况都涉及对STM
字的检查,这将在以下更详细描述。第一种情况在扩展的IR中通过(a)为所涉 及的对象读取STM字,(b)读取字段,(c)重新读取STM字,并检査所读取 的值匹配(a)中的值并且该值不表明存在并发冲突访问来表示。第二种情况在扩 展的IR中通过(a)更新事务更新,(b)更新字段,(c)再次更新STM字,表明 它不再服从非事务性更新来表示。
由此, 一个示例IR看上去如下
LI: ,
sl = openoneobjforxead(ol)
tl = getfieldoc:> (ol)
if (!checkoneobj(ol, sl)) goto LI
L2 :
s2 - openoneobjforupdate(o2) putfield<x>(o2, tl) commitoneobj(o2, s2)
该实现涉及与上述STM实现的两个区别。第一个区别是,不像上述STM 实现,临时存储是在局部变量而非事务日志中找到的。这意味着变量可以在处 理器寄存器中分配以使其访问变得更快。第二个区别是在L2处开始的事务不 能异常中止,因此无需将在"o2.x"中盖写的值记入日志。 23此无需在其周围插入强原子性操作。
图7是用于引入实现强原子性的操作的示例进程700的流程图。进程700 对应于图5的框520。在各种实现中,所述进程框可被合并、划分成子框、或 省略。该进程在框720处开始,在那里执行类型分析以确定在原子块中可访问 的字段。如上所述,在一个实现中,执行这一步以避免针对不能引起冲突的存 储器访问的不必要的强原子性操作插入。接着,在框720处,使用在框710中 确定的字段,来定位程序中可访问包含在原子块中的字段的存储器访问。在一 个替换实现中,框710的进程可被省略,并且框720的进程可定位原子块外部 的每一存储器访问来插入强原子性操作。
接着,该进程继续到判定框725,在那里编译器确定在框720中定位的访 问是读取还是更新访问。如果该访问是读取,则该进程继续到框730,在那里 在该访问之前插入一打开来读取指令。在一个实现中,该指令被配置成阻断直 到它能够接收STM字,且因此确保该存储器访问可正确地读取所访问的字段。 在另一实现中,该操作不阻断,但是如果存储器访问没有检验出结束,则在存 储器访问之后创建一循环。接着,在框740处,在存储器访问之后插入一检验 指令,以确保在读访问的过程中STM字没有表明对所读取的字段的改变。在 以上提供的实现中,这是通过在框730处接收STM字并在框740处将该STM 字传递到检验操作来完成的;这还创建了数据依赖性,它防止代码优化对强原 子性操作的次序重新排序。
然而,如果框725确定访问是更新,则该进程继续到框750,在那里在该 访问之前插入打开来更新指令。在一个实现中,该指令被配置成修改来自所访 问的对象的STM字,以防止其它访问,由此提供强原子性。接着,在框760 处,在存储器访问之后插入提交指令以提交在存储器访问时执行的更新。在一 个实现中,改变所访问对象的版本号。在另一实现中,不改变版本号。接着, 在判定框765处,编译器确定是否有另外的非原子存储器访问。如果有,则该 进程重复。如果没有,则该进程结束。STM编译器的各种实现执行的另一高级优化是避免当DTMOpenForRead 操作之后有DTMOpenForUpdate操作时发生的不必要的日志记录。此处所描述 的技术固有的一个设计假设是读比写更常见,这就是这些技术使用分开的 DTMOpenForUpdate和DTMOpenForRead操作的原因;打开来读取指令能够更
快地完成。然而,有时候读取对象然后写入对象(规范的示例是"01^^1(1++")。
在这一情况中,带有打开操作的IR看上去如下
DTMOpenForRead (obj》 t = pbj,field; t: - t+l;
DTMOpenForUpdate (obj)
DTMLogFieldStore (obj , of fsetof (obj . field) ) obj , field = t,'
如果程序到达打开来读取点,则可以看到,它将到达打开来更新点,从而 忽略了此时的异常。由于对同一对象的打开来更新包含了打开来读取,因此打 开来读取操作被 浪费了。这在一个实现中被称为读取一更新升级。仅仅在较早
期执行打开来更新操作将是更高效的 DTMOpenForUpdate (obj),' t = obj.field; t = t+1,'
DTMIiogFieldStore (obj , off setof (obj . field) ) obj.field = t;
由此,在一个实现中,编译器在找到读取一更新升级时移除它们。 一般而 言,这可由基本块内的编译器通过直接的数据流分析来执行,从而如果
DTMOpenForRead操作后跟有DTMOpenForUpadate,则升级DTMOpenForRead
操作。在另一一般的情况下,DTMOpenForUpdate操作只需被插入到所有非异 常路径从中执行相同的DTMOpenForUpdate的所有基本块的开头处(而无需介 入对所涉及变量的存储)。CSE然后试图消除同一对象上的额外的 DTMOpenForUpdate操作以及任何后续DTMOpenForRead操作。
图8用于移除不必要的读取一更新升级的示例进程800的流程图。进程 800对应于图5的框540。在各种实现中,所示进程框可被合并、划分成子框、 或省略。该进程在框810处开始,在那里编译器标识后面总是跟有对同一引用 的打开来更新操作的打开来读取操作。注意,尽管此处的示例利用了对象指针, 但是所描述的用于消除不必要的读取一更新升级的技术还实现对内部指针和静态字段的移除。编译器需要确定打开操作是针对同一对象的(或在静态字段 的一个实现中,是针对代理对象的)。
在一个实现中,分析要求对象引用或内部指针是同一局部变量并且该变量 没有在操作之间更新。尽管该实现会遗漏对赋值上的升级的移除,但是其它实
现也分析赋值。在另一实现中,通过对代理对象的打开操作来控制静态字段(或 变量),这允许当单个代理对象控制所有静态字段时在两个不同静态字段之间 移除升级。框810的进程的一个示例进程将在以下参考图9来更详细描述。
接着,在框820处,用对同一引用的打开来更新操作替换在框810处标识 的打开来读取操作。然后,在框820处,移除冗余的打开来更新操作。在一个 实现中,这并不是紧接着框820的进程来执行的,而是由对图6描述的编译器 优化,诸如CSE来执行的。
读取一更新移除分析的第一示例性实现移除基本块内的升级。由此,编译 器査看整个程序中的每一基本块,并对每一扫描找出打开来读取操作。当找到 第一个操作时,编译器向前扫描以查找对指向被打开的对象的变量的打开来更 新操作或赋值。如果打开来更新操作首先出现,则编译器将打开来读取转换成 打开来更新操作,并删除原始的打开来更新。如果该变量被更新,则放弃搜索。 在一个替换实现中,编译器可从打开来更新操作开始向后扫描以搜索打开来读 取操作。
图9是用于移除所标识的总是被打开来更新操作包含的打开来读取操作 的第二示例进程900的流程图。进程900对应于图8的框810。在各种实现中, 所示进程框可被合并、划分成子框、或省略。
图9的进程利用了标准的后向数据流分析。在该分析中,编译器在每一程 序点处计算将来肯定会被打开来更新的对象集。在各种实现中,图9的进程是 对程序中的每一基本块执行的,或对基本块的子集执行。该进程在框910处开 始,在那里在基本块边界处创建包含肯定会被更新的对象的指示的集合。在框 920处,将基本块中的所有变量添加到该集合。然后,在框930处,对基本块 中的指令的分析通过检查该块中的最后一条指令开始。在判定框935处,编译 器考虑指令的形式。如果指令是赋值(例如,"x=..."),则在框940处,从该 集合中移除所赋值的变量。然而,如果指令是打开来更新指令,则在框950处,将由该指令打开的变量添加到该集合。
在任一情况下,或者如果指令是另一类型的,则编译器移至判定框955, 在那里它确定在基本块内是否存在另外的指令。如果有,则在框960处,编译 器在控制流图上向后移动并找到该控制流图中的下一指令且该进程重复。当编 译器在判定框955处确定不再有指令时,到达了基本块的开头。当编译器到达 该块的开头时,在框970处,它找出该块的前导块(即,可跳转到当前块的块) 并将该集合与储存在这些前导块的每一个的末端的集合相交。在一个实现中, 重复图9的进程,直到在给定每一块的末端的当前集合时不再有任何东西改变。 编译器可向后走查该块,以用相同的方式更新该集合来获得用于每一程序点的
隹A 朱PI o
此时,出于框810的目的,标识"必须在将来被打开来更新"集合中的变 量。然后,在一个实现中,对这些变量的每一个添加打开来更新操作,从而允 许CSE稍后移除额外的打开来更新操作。在另一实现中,使用局部冗余性 ("PRE")来代替打开来更新指令及其后的CSE优化的渐进相加。这是更一 般的解决方案,并且可产生在相同路径上具有更少打开指令的代码。
在一个实现中,上述分析假设不会引发异常,因此忽略了异常边,并在假 定没有抛出异常的情况下计算将来肯定会被打开来更新的对象的集合。这是因 为异常不是常见的情况。这一精度损失不会影响正确性。然而,可扩展替换实 现以考虑异常边,以便产生精确的结果。
另外,在替换实现中,以上分析可被修改成忽略其它代码段。这可通过利 用表明忽略的代码与被分析的代码相比相对较不频繁地执行的试探来完成。在 一个实现中,这些试探是静态地确定的;在另一实现中,它们是从简介信息确 定的。
作为一个示例,在执行了上述进程之后,示例3的代码被简化为以下更高 效的代码 示例3.1public int Sum() {
tm一mgr tx = DTMGetT醒gr () DTMOpenForUpdate(tx, this),' Node n = this.head,' int t 0, do {
DT化OpenFo;rRead(t:x:, n)
t += ii .Value;
if (n--this.Tail) { .
DTMLo^FieldStore(tx, this, offsetof(List,Sum));
ttiis.Sum = t
return t;
} while (true》 -
2.2.3在存在过程调用的情况下移动操作
许多现有的编译器优化只能比较、消除和移动函数内的代码,因为这些技 术一般太过昂贵以至于无法应用于整个程序图。然而,通过跨过程边界移动 STM操作的高级STM优化,这些优化可更高效地执行。
作为一个示例,给定代码
Foo(object obj) {
DTMOpenForU^date(obj ),
.} '.
Bar() {
obj = .
DTMOpenForUpdate (obj ),' Foo (obj 〉
}
很清楚,Foo总是打开由其参数引用的对象来更新。Foo的调用者还可打 开该对象(如上所述),或者它可在一循环(或多个其它东西)内调用Foo。 然而,该过程调用防止对具有调用者中的代码的Foo的动作的分析/优化。该 优化跨调用界限移动打开操作以便为其它优化创建更多机会。CSE是一个明显 的候选者,因为调用者可能已经完成了移动到它的操作。也可改进其它非事务 专用优化(例如,如果同一对象在一循环中被反复传递给一函数,则可将打开 从该循环中提升出来)。
在一个示例中,该优化是对DTMGetTMMgr以及DTMOpenFo浐操作实现 的。在替换实现中,该优化可对如果调用一方法则必须被打开的其它操作执行 的。另外,在替换实现中,该优化可对在调用一方法时通常发生的其它操作执 行,这牺牲了非常见的情况下的精度和性能以换取常见情况下的更好性能,而
28非虚拟(也称为"直接")调用执行 优化;这包括被"解除虚拟化"的虚拟调用(例如,确定仅单个调用目标存在 并用直接调用替换虚拟调用)。
图10是用于通过跨方法边界移动STM操作来优化STM操作的示例进程 1000的流程图。进程1000对应于图5的框560。在各种实现中,所示的进程 框可被合并、划分成子框、或省略。该进程在框1010处开始,在那里定位包 含可被移动到该方法之外的操作的方法。接着,在框1020处,克隆该方法以 创建该方法的允许该操作在该方法外部执行的版本。如果该操作给出结果,则 框1020的进程还向所克隆的方法添加自变量以使该结果可被传递到该方法。
接着,在框1030处,将该操作移动出所克隆的方法到用于该方法的一个 或多个调用地点。在替换实现中,在不移动操作的情况下创建所克隆的方法, 而非完全克隆该方法并移除操作。最后,在框1040处,用所克隆的方法来替 换对原始方法的调用。在所替换的调用的一个实现中,包括由所克隆的方法使 用的附加自变量。这些附加自变量的示例在以下示出。
在调用替换的另一实现中,编译器维护它克隆的一组方法以及从这些方法 到其克隆(专门的)版本的映射。编译器然后再次扫描程序中的所有方法来替 换调用。在某些情况下,该技术完全消除了原始版本的函数。然而,在某些情 况下,(例如,如果取该函数的地址),则仍会有对非专门版本的调用并且该 调用不能被移除。
不同操作将导致方法以不同的方式来克隆。在一个示例中,如果一方法包 含GetTxMgr,则编译器克隆该方法、添加一额外参数来接收事务管理器、并 用该参数来替换GetTxMgr的所有出现
FuncUsesMgr () {
m = GetTxMgr(》;
} ….
-=> FuncUsesMgr^copy (TxMgr mgr) {
} …
在此示例中,对该方法的调用被改为对克隆的方法的调用,并具有包含事务管
理器的附加自变量Call<^uncUsesMgr>()
-=> mgr = GetTxMgr (》
FuncUsesMgr—copy (mgr)
在另一示例中,代替令单个特性跟踪和创建基于(事务管理器)的专门克 隆,而是有许多(每一参数和每一静态代理)。例如,
Foo(object objl, object obj2, object obj3) { DTMOpenForRead,(objl) DTMOpenForUpdSLte (obj 3) /
} …
在此示例中,编译器可能希望创建期望调用者适当地打开objl和obj3(但 不必打开obj2)的专门版本。在一个实现中,这是通过执行上述作为框1010 的进程的一部分的"在将来某一点必须被打开来更新"分析来完成的。此处, 该分析仅跟踪参数和静态代理,但是还被扩展成完成"打开来读取"以及"打 开来更新"操作。编译器然后分析该函数的根处的集合。如果它们是非空的, 则编译器如上所述克隆该方法,除了改为来回移动适当的打开操作之外。编译 器在克隆的函数上储存期望打开哪些参数(以及是用于读取还是更新)以便使 其它优化能够看见。
2.2.4减少对新分配的对象的日志操作
最终的高级优化用于通过移除一事务中对在该事务内新分配的对象的日 志操作来减少日志操作数量。具体地,不必为从来不用escape命令取消从中创 建它们的事务的对象维护撤消日志信息。这是因为对于这一对象的撤消日志中 的信息仅在该事务被异常中止时才使用,此时该对象无论如何都被删除。
本质上,优化用于标识总是被绑定到自从事务开始以来分配的对象的变 量,然后删除这些对象上的日志操作。由此,图11示出了用于移除对新分配 的对象的日志操作的示例进程1100的流程图。进程1100对应于图5的框580。 在各种实现中,所示进程框可被合并、划分成子框、或省略。
该进程在框1110处开始,在那里编译器标识总是被绑定到在其事务中新 分配的对象的变量。在各种实现中,执行框1110的进程以接收关于所编译的 程序中的不同程序点集合处的变量的信息。由此,可执行框1110的分析以获 知关于一特定点处的引用、 一小范围的代码、或通过事务内的整个变量生存期 的信息。在此分析后,在框1120处,编译器移除通过这些变量操作的撤消日志操 作,并且该进程结束。在一个实现中,编译器通过用其分解不包括日志操作的 特殊的扩展版本的操作替换访问堆存储器的STM操作来执行框1120的进程。 在另一实现中,编译器在分解了 STM操作之后执行图11的进程以明确地移除 分解的日志操作。
框1110的进程的范围从简单到复杂,取决于所分析的代码。在一个示例 中,诸如以下的代码
atomic{
p - new _
} …
意味着P总是已知引用原子事务块中的新分配的对象。由此,移除通过p来行 动的日志操作是安全的。
然而,诸如以下的一段代码 atomic{
if (...)
p = new _
P = q'.
} …
并不能够容易地提供关于p是否总是引用新分配的对象的信息。由此,编译器 必须执行分析来标识变量是否符合日志移除的条件。
在一个实现中,编译器使用在每一程序点处利用表明每一变量是否已知肯 定引用新分配的对象的向量的位向量。尽管该实现将正确地标识出对其可移除 日志操作的引用,但是它一般较慢,并且涉及许多存储器使用。在另一实现中, 位向量可提供关于一大段代码,诸如基本块的概要信息。该实现对于过程间分 析仍是较慢的。
作为替换,在一个实现中,编译器使用了流敏感过程间分析来标识总是被
绑定到自从事务开始以来分配的对象的变量。图12示出了这一示例进程1200 的流程图。进程1200对应于图11的框1110。在各种实现中,所示进程框可被 合并、划分成子框、或省略。在所示的实现中,在事务中的每一基本块上执行 进程1200。
图12所示的进程是对整个程序的每一函数执行的,以便并发地构建并解析依赖性图。对于每一函数,该进程在框1210处开始,在那里创建从对象类
型化变量到依赖性图中的点阵元素或节点的映射。该映射表示可被分配给块中
的任一点处的变量的值的种类。在一个实现中,该点阵具有三个元素"旧",
表示引用可能不是新分配的对象的变量;"新",表示引用必须是新分配的对 象的变量;以及"未知",用于对其没有信息的变量。在框1220处,该映射 中的所有值被设为"未知"。接着,在框1230处,编译器前向移动通过基本 块以检查该块中的第一个操作。在判定框1235处,编译器确定它正在检查什 么类型的操作。如果该操作是对象分配,则在框1240处,编译器对于被分配 的变量向该映射添加"新"。如果该操作是赋值、强制类型转换或过程调用, 则在框1250处,编译器在变量之间传播点阵值。由此,赋值和强制类型转换 将其抽象值传播到所分配到的变量。调用将抽象值传播到调用形式并传播来自 返回值的抽象值。然而,如果操作是除了以上情况之外的任何操作,则在框1260 处,修改该点阵以对于向其分配操作的变量表示"旧"。在一个实现中,该分 析还考虑在要新分配的当前事务的提交的子事务内分配的对象。
编译器然后对从局部变量到点阵值或图节点的映射前向传播信息,并在函 数内迭代直到达到一固定点。由此,在判定框1265处,编译器确定是否到达 诸如if语句的结束等连接点。如果已到达连接点,贝U在框1270处,将来自前 导块的点阵值与用于当前块的现有映射进行点级相交。出于分析的目的,函数 的开始被认为是从所有其调用地点开始的连接点。在任一情况下,该过程前进 到判定框1275,在那里确定是否还有操作要检查。如果有,则该进程在判定框 1235处重复。如果没有,则该进程结束。该进程可导致传播通过图进入到来自 其它函数的变量。 一旦对事务中的每一基本块执行了该进程,则已被标为"新" 的那些变量可将其日志操作移除。在各种实现中,依赖性跟踪意味着函数可以 按不同的顺序来处理。它还意味着如果确定了一函数的新调用者或被调用者, 则无需第二次分析该函数。
3.运行时优化的示例
在本节中,描述分解的直接访问STM的实现。总体上,事务使用严格的 两阶段锁定来进行更新,并且记录它读取的对象的版本号以使其能检测冲突的更新。对冲突或死锁时的恢复使用回退日志。 一种优化涉及扩展对象格式以支 持提交操作使用的版本号,以及用于基于该扩展来确定对一对象的改变的快速 技术。还描述了对事务性存储器日志的条目的运行时过滤。
3.1原子提交操作
对象结构的扩展可以在此处描述的STM实现中的原子提交操作的上下文 中理解。在原子提交的一个示例中,调用DTMStart,打开对象用于读取和更 新,并且提交通过调用DTMCommit以试图原子地执行这些访问来结束。
内部地,提交操作通过试图确认已被打开来读取的对象来开始。这确保自 从这些对象被打开以来没有其它操作对其作出了更新。如果确认失败,则检测 到冲突事务的更新被回退,并且关闭其打开来更新的对象,此时这些对象可 被其它事务打开。如果确认成功,则事务已在没有冲突的情况下执行关闭它
打开来更新的对象,并保留更新。
确认进程检查在从DTMOpenForRead命令的调用到确认的时间跨度期间 没有对事务所读取的对象的冲突更新。保持对象打开来更新防止在从 DTMOpenForUpdate命令的调用到STM日志中对象的关闭的时间跨度期间的 冲突。结果,在这些时间跨度的交点没有对任何打开的对象的冲突访问;该事 务可被认为在确认开始之前是原子的。
3.2运行时环境
图13是示出了可用于在运行时环境1300中在运行时期间优化STM性能 的对象和软件模块的示例的框图。尽管图13分开示出了特定的模块,但是应 当认识到,在各种实现中,模块可按各种组合来合并或划分,或者可作为未示 出的其它运行时软件结构的部分来操作。图13示出了在运行时环境中操作的 对象1310,以及扩大的字首部1315。其字首部被扩大的对象的操作将在下一 节中描述。图13还示出了读确认模块1320和对象更新关闭模块1330,用于实 现如上所述的STM实现的确认和关闭过程。这些模块对于运行时环境中的对 象的特定方面在此描述。图13另外示出了过滤结合表1350,在某些实现中, 它过滤不必要的条目并防止其被记入到撤消日志1360、更新对象日志1370和读对象日志1380的各种组合中。这些过滤进程的具体实现将在以下更详细描
述。最后,图13示出了用于在对象在执行程序中不再可达到时解除其分配并 在无用信息收集期间压縮STM日志的无用信息收集模块1390。该无用信息收 集模块的具体实现在以下描述。
3.3对象结构
本节描述了用于支持只读对象的确认以及对更新的对象的打开和关闭操 作的结构的示例。在一个实现中,STM出于对于对象的操作的目的而对每一 对象利用两个抽象实体用于协调哪一事务已经打开了对象来更新的STM字, 以及在快速路径代码中用于检测对事务所读取的对象的冲突更新的STM快 照。使用这些数据结构的操作的示例如下
word GetSTMWor^(Object o)
:bool OpenSTMWord(Object o, word prev., word next) void CloseSTMWord(Object o, word next) snapshot GetSTMSnapshot(Object o) word SnapshotToWo:rd( snapshot s)
对象的STM字具有两个字段。 一个字段是指示该对象当前是否被任何事 务打开来更新的单个比特。如果该比特被设置,则该字的其余部分标识拥有的 事务。否则,该字的其余部分保持一版本号。OpenSTMWord对STM字执行 原子比较并交换(compare-and-swap)(从prev (前 一 项)至(J next (下 一 项))。 CloseSTMWord将该字更新到一指定值。
图14a和14b示出了实现对象中的STM字的示例。所示实现利用了Bartok 运行库在存储器中表示每一对象时将单个多用途首部字与该对象相关联的事 实,用此来将同步锁和散列码(两者都不是此处所描述的STM技术的组件) 与对象相关联。在图14a和14b中,该多用途首部字用一附加状态来扩展,以 保持在事务中曾经被打开来更新的对象的STM字。由此,在图14a中,对象 1400包括多用途首部字1410,它包括储存在其中的值的类型的指示符1413, 之后是实际的STM字1418。对指示符1413的使用允许通过使用不同指示符 值来将多用途字用于散列码和锁。在一个实现中,假设如果用于一对象的指示 符指示该字中储存了锁或散列码,则那时对于该对象尚没有STM字。如图14a 还示出的,STM字1418可以具有如上所述的两种类型的值。在示例1420中, STM字包括指示对象1400没有被打开来更新的比特,且因此该字的其余部分保持一版本号。在示例1430中,STM字包括指示该对象被打开来更新的比特, 因此STM字标识了打开该对象来更新的事务。
在另一实现中,如果对于这些用途中的一个以上用途(例如,对于散列码 和STM字)需要多用途字,则它被扩大,并且一外部结构保持该对象的锁字、 散列码和STM字。由此,在图14b中,对象1450被示为使用扩大的首部字。 对象的多用途字的指示符1465包含了指示该首部字被扩大的值,而该多用途 字的其余值1460包含被扩大的首部字结构的存储器地址。由此,在图14b中, 多用途字指向被扩大的首部字结构1470,这包括锁字、散列码和STM字。
与STM字形成对比,对象的STM快照提供了关于该对象的事务性状态的 提示。在一个实现中,运行时环境确保只要在对象上调用CloseSTMWord—即, 只要一线程释放对该对象的更新访问一就改变快照。这提供了足以检测冲突的
f曰息。
确保这一条件的一种方法是将STM快照实现为对象的多用途字的值。很 清楚,这一实现意味着当STM字被直接储存在多用途字中时快照将改变。然 而,当使用扩大的首部字时它不一定要改变。在一个实现中,对于使用扩大的 首部字的对象的快照可以跟踪并探査每一对象的扩大的首部字。然而,这是与 作出快速快照指令的目标不一致的低效的实现。由此,在另一实现中,如果多 用途字已被扩大,则CloseSTMWord创建新的扩大的结构,并将前一结构的内 容复制到该新的结构。这允许STM快照总是被实现为该对象的多用途字的值 同时保持快速。
图15a和15b示出了 CloseSTMWord的这一实现的效果。在图15a中,对 象1500被示为在CloseSTMWord的执行之前。对象1500使用扩大的首部字 1520,并将该扩大的首部字1520的地址储存在其多用途首部字1510中。图15b 示出了在执行CloseSTMWord之后对对象和运行时存储器的改变。在执行之 后,创建了新的扩大的首部字数据结构1540,并且改变了储存在多用途首部字 1510中的地址。这意味着包括多用途字1510的值的快照因关闭而改变。
图16是用于使用对象快照来执行确认的示例进程1600的流程图。在各种 实现中,所示的进程框可被合并、划分成子框、或省略。该进程在框1620处 开始,在那里对一对象记录快照数据。在一个实现中,该记录是在一对象被打开来读取时执行的。接着,在框1640处,读确认模块1320在提交操作的确认 时刻记录对该对象的第二快照。在判定框1660处,该模块比较这两个快照以 査看它们是否相同。如果它们匹配,则该进程继续到框1670,在那里允许事务 继续提交/异常中止过程,这利用了快照未被改变来执行快速路径测试的事实。 如果快照不匹配,则在框1680处,读确认模块1320执行不能利用匹配快照的 存在来确定事务是否能提交或异常中止的提交/异常中止过程,并且该进程结 束。在一个实现中,这两组不同的过程被称为快速路径和慢速路径过程。
框1670和1680的进程之间的关键差别是由于快照未被改变的知识,框 1670的进程可避免不必要的测试或存储器访问,并且可以比框1680的测试更 快地执行。在各种实现中,这些测试的确切特性可取决于底层的事务性存储器 实现的特性。例如,在一个实现中,以下在代码示例6中描述的执行两个快照 匹配的确认的代码只需检査单个STM字来确定它是否被一事务拥有,并且该 事务是否与当前正在确认的事务相同。相反,当本示例中快照不匹配时,则必 须查找第二STM字,以及在某些情况下必须査找更新条目。在其上执行的这 些附加存储器访问以及附加比较意味着框1680的这一实现一般要比框1670的 对应实现慢。
图17是用于修改使用扩大的首部字的对象的示例进程1700的流程图。在 各种实现中,所示进程框可被合并、划分成子框、或省略。该进程在框1720 处开始,在那里修改对象。在一个实现中,这可能是由于STM更新指令而引 起的。在另一实现中,可修改对象的扩大的首部字本身,这可以或者是在锁字 中,或者是在散列码中。接着,在框1740处,对象更新关闭模块1330响应于 关闭指令创建一新的扩大的首部字。该进程继续到框1760,在那里该模块将信 息从旧的首部字复制到新的首部字。然后,在框1780处,对象更新关闭模块 630修改该对象的多用途首部字以指向新的扩大的首部字。
最后,在框1790处,如果正发生无用信息收集,则将旧的扩大的首部字 留在原处,直到被无用信息收集器1390回收。对象更新关闭模块完成这一步 以防止在一不同线程中对该对象作出第二改变并且第三扩大首部字被写入到 从第一扩大首部字回收的存储器中的情形。如果当一读取该对象的事务打开时 发生这一情况,则该对象的快照可以看似为在提交时没有改变,即使它被改变了两次。这允许进行读的事务在其应当由于对象的两次修改而异常中止时能够 提交。在一个实现中,框1790的进程是通过将对象留在原处直到回收对象是 安全的时候来执行的,在一个示例中这是在没有事务打开了对象来读取时完成 的。
4. STM日志记录和提交的示例 4.1 STM日志结构的示例
每一线程具有带三个日志的单独的事务管理器。读对象日志和更新对象日 志跟踪该事务已打开来读取或打开来更新的对象。撤消日志跟踪在异常中止时 必须被撤消的更新。所有日志都是顺序地写入的,并且从不被搜索。使用单独 的日志是因为其中的条目具有不同的格式,并且因为在提交期间,系统需要轮 流在不同种类的条目上迭代。每一条目被组织成条目数组的列表,使得它们可 在不复制的情况下增长。
图18a、 18b和19a-c使用示例2a的列表示例示出了日志的结构。图18a 示出了保持具有值10的单个节点的列表的初始状态。假设对象的多用途字都 被用于保持STM字一在这一情况下对象在版本90和100下。在图18a、 18b 和19a-c所示的示例中,STM字的右手边的两位值对应于图14a、 14b、 15a和 15b的指示符。
示例3的一个操作打开this来更新,使用OpenSTMWord来用指向更新对 象日志中的新条目的指针原子地替换版本号。伪代码的一个示例如下示例4所 示
示例4
void DTMOpenPortJpdate (tm_mgr tx, object obj ) { word stm—word = GetSTMWord (obj )
if ( !IsOwnedSTMWord(stm—word) ) { entry - > obj = obj / entry -> stm—word = stin—word/ entry -> tx = tx,.
word new—stm—word = MakeOwnedSTMWord (entry) /
if (OpenSTMWord (obj , stm—word, new—stm一word) ) {
〃打开成功继续到日志中的卞一条目 —— entry ++, } else {
//打开失败使得事务无效Becomelnvalid (tx)
} else if (GetOwnerFromSTMWord(stm_word) == tx) {
//已经由此事务打开来更新无需做其它、情 } else {
//己经由另一事务打开来更新
//变为无效 Becomelnvalid (tx)
图18b示出了该结果。注意,在所示的实现中,"日志组块中的偏移量"字段 在无用信息收集期间用作将进入日志的内部指针(诸如来自图18b中的List节 点的指针)映射到对保持它的日志条目数组的引用的快速方法。
该列表相加示例继而打开每一列表节点来读取。DTM使得这一动作直接 对于每一对象,将对象引用及其当前STM快照记入日志。示例5以伪代码示 出了该动作的示例
示例5
void DTMOpenFo:rReaci(tm—mgr tx, object obj) { snapshot stm—snapshot = GetSTMSnapshot (obj ) entry -> obj - obj;
entry -> stm—snapshot = stm一snapshot ; entry ++;
}
图19a示出了它所创建的日志条目。在竞争较罕见的设计假设下,没有试 图检测冲突,因此尽早发现冲突的好处不如检査的成本重要。
在读取了列表节点之后,最后一步是更新Sum字段。DTMLogFieldStore 如图19b所示用撤消日志中的条目来记录盖写的值。对此的伪代码被省略一所 使用的具体记录受到在一个实现中使用的Bartok系统中的无用信息收集支持 的影响;其它设计在其它系统中将是适当的。撤消日志条目将被盖写的值的地 址记录为(对象,偏移量)对。这避免了使用在某些无用信息收集器中进行处 理的较为昂贵的内部指针。条目也在标量或引用类型的存储之间区分。这一类 型信息在某些无用信息收集器中是需要的。最后,它记录盖写的值。在另一实 现中,可使用仅保持地址和被盖写的字的较短的两字日志条目,这是以无用信 息收集期间更多的工作为代价的。4.2提交过程的示例
在此处所描述的实现中对于DTMCommit有两个阶段第一个阶段检查对
于被打开来读取的对象的冲突更新,第二个阶段关闭被打开来更新的对象。无 需明确地关闭被打开来读取的对象,因为该事实仅被记录在线程专用事务曰志 中。
如下的示例6示出了 ValidateReadObject的结构。在该伪代码中有大量情 况,但是如果被认为是按照DTM接口上的操作的条件情况的分离,则总体设 计更清楚。以下情况V1、 V2和V3指示没有发生冲突
Vl —该对象在事务持续期间的任一点处未被打开来更新。
V2 —该对象在整个持续期间由当前事务打开来更新。
V3 —该对象最初没有被打开来更新,并且当前事务是打开它来更新的
下一事务。
V4 —该对象在整个持续时间由另一事务打开来更新。
V5 —该对象最初没有打开来更新,并且另一事务是打开它来更新的下
一事务。
这些情况在示例伪代码中标出。 一些情况发生多次,因为在其中对由于实 际冲突而发生的STM快照失败进行测试,以及其中在没有冲突的情况下失败 (例如,由于当对象的多用途字变为扩大时STM快照改变)的场合之间进行 区分是有用的。
示例6
void ValidateReadObject {tm—mgr tx, object obj, read—entry *entry) { snapshot old—snapshot = entry -> stm—snapshot snapshot cur—snapshot = GetSTMSnapshot (obj ) / word cur—stm—word = SnapshotToWord (cur—snapshot)
if (old—snapshot == curAsnapshot) { // '|£照匹配没有事务关闭该对象
if (!IsOwnedSTMWord(cur—stm—word) ) {
// VI: OK:快照未改变,—没有>突 } else if (GetOwnerFromSTMWord(cur_stm—word) == tx) {
〃 V2: OK:在读取前由当前tx打开_ —
//来更新 } else {
〃 V4 :由另一tx打开来更新BecomeInvalid(tx);
} else {
//快照失配STM字上的慢速路径测试
word old—stm—word = SnapshotToWord (old—snapshot) if(!IsOwnedSTMWord (old—stm—word) ) { if (old—stin—word == cur—stm—word) {
〃 vl: ok! STM字在事务,月间一
//被扩大
} else if ( ! IsOwnedST丽ord(cur—stm—word) 〉 { 〃 V5 :另一tx的冲突更新—— Become Invalid (tx) } else if (GetOwnerFromSTMWorcl (cur—stm—word) == tx) { //当前tx打开该对象来更新... ——
update—entry *update—entry =
GetEntryFromSTMWord (cur—stm—word); if (update—entry -> stm—word !=
SnapshotToWord(old—snapshot) ) {
// V5 :...但是另一;x在当前tx打开该对象之前
//打开并关闭该对象来更新

Becomelnvalid (tx),. } else {
〃 V3 : OK:没有另一tx的介入的访问
} else {
// V5 :该对象由另一事务打开 Becomelnvalid (tx)
}
} else if (GetOwnerTromSTMWord(cur—stm—was) == tx) {
〃 V2 : OK:在读取之前由当前tx打开一来更薪 } else {
〃 V4 : STM字不变,但是先前由
//另一事务打开来更新 Becomelnvalid (tx)
示例7
-void CloseUpdatedObject (tm一mgr tx, object ob j , update—entry *entry) { 一 word old—stm—word - entry -> stm一word; word new—stm一word - GetNextVersion《old一stm—word) CloseST顺ord(ot)j , new—word),'
} 一
图19c示出了对该列表结构的所得的更新,其中新版本号91被放置在该 列表对象的首部。可以观察到,随着对版本号有29个比特可用,可以获得大约500M的不 同版本。所示的设计对于版本号溢出是安全的,只要在一运行的事务打开对象 来读取时一版本号在同一对象中不被重复使用一允许读取事务成功提交而无 需检测可能对该号码有大约500M的更新的A-B-A问题。
对于正确性,在一个实现中,这是通过(a)至少每500M事务执行无用信 息收集一次,以及(b)在每次无用信息收集时确认运行的事务。读取对象曰志 中的条目仅当所记入日志的版本号匹配当前版本号时才有效结果是每次无用 信息收集"复位"500M事务的"时钟"而无需访问每一对象来更新其版本号。
5.运行时日志过滤
本节描述了利用概率性散列方案来过滤来自读取对象日志和撤消日志的 重复以便过滤重复的运行时技术。日志过滤一般是有用的,这是因为a)日志 可能占据大量空间,从而耗尽系统资源,以及b) —旦将一特定存储器位置记 入曰志为已被写入或读取,则无需进一步在日志中记录它。这是因为,在确认 期间,所需的来自读取对象日志的唯一信息是事务之前该对象的STM快照, 而所需的来自撤消日志的唯一信息是在事务之前更新的存储器位置的值。由于 这不在事务内改变,因此对每一事务一给定存储器位置只需一个日志条目。
在第4节的实现中,不必过滤更新对象日志中的条目。这是因为 DTMOpenForUpdate不允许在同一事务内对同一更新的对象首部创建重复的日 志条目。在其它实现中,可能创建这种重复,并且因此可能过滤重复。
一般而言,过滤器支持两个操作。第一个操作是"过滤"操作,它在指定 的字必须在过滤器中存在时返回真。如果指定的字可能在过滤器中不存在则返 回假,并在该字的确不存在时将该字添加到过滤器。这一过滤器因而担当在搜 索时容许假否定的概率集(即,它可在字实际上在过滤器中存在时声明该字不 在过滤器中,但是不能在字实际上不在过滤器中时声明其在过滤器中)。第二 个操作是"清除"操作,它移除过滤器中所有的字。
在软件事务性存储器(STM)的上下文中,过滤器可用于减少相同字的内 容被写入STM保持的事务日志之一的次数。此处所描述的过滤方案使用结合表来概率性地检测对读取对象日志和撤 消日志的重复日志记录请求。尽管此处所描述的实现参考了散列表,但是可以 认识到,在替换实现中,过滤技术和系统可使用结合表的不同实现。 一种实现 使用了将地址的散列映射到与具有该散列的地址有关的最新近的日志记录操 作的细节的按线程表。
可以注意到,在一个实现中,只需一个结合表来同时过滤读取对象日志和 撤消日志。对读取对象日志的存储使用了该对象的首部字的地址,而对撤消日 志的存储使用了所记入日志的字的地址。由于这些地址集是不相交的,因此单 个表不会展示出读取对象和更新访问之间的冲突,且因此可用于两个日志。
图20示出了该表的设计。图20示出了被实现为散列表200的结合表。如 图20所示,散列表2000中的每一条目包括存储器地址2020和交易号2030。 条目是按照一系列槽号2010来组织的。
在一个实现中,标识用于一特定存储器地址的槽号的散列码通过将地址拆 分成散列索引和标签来获得。由此,在这一实现中,散列函数只需使用来自字 W的最低有效位中的几位来选择要在表中使用的槽S。字W中的位因此可被 认为被拆分成两个部分最低有效位是散列码,其用于标识要使用的槽,而其 余的位用作唯一地标识地址的标签。例如,字0x1000具有标签l,槽0,而字 0x1001具有标签1,槽1,字0x2000具有标签2,槽0,字0x2001具有标签2, 槽1等等。在替换实现中,使用不同的散列方案。
另外,尽管散列表2000将事务号示为与存储器地址分离,但是在各种实 现中,事务号诸如使用异或(XOR)运算与存储器地址组合。在一个实现中, 使用异或运算因为它是相对快速的运算,并且可以被连续的异或撤消。在替换 实现中,使用不同的记录事务号的方法,诸如用事务号来替换存储器地址中的 低序位,或使用加法运算而非异或运算。这些是有用的是因为它们各自共享了 这样的性质对于散列成相同的散列码的两个地址a,和a2,以及两个事务号^ 和t2,仅当ai=a2且t产t2时op(a!, t!)才等于op(a2, t2)。该性质提供了所插入的组 合值对于特定地址和从中创建它们的事务号是唯一的置信度。
使用对线程本地的事务号是要防止由较早的事务记录的条目与涉及当前事务的条目混淆。事务号的标识允许仅当用于事务号序列的比特溢出时该表才 被清除。在一个实现中,该表在每次事务号序列溢出时被清除一次,这通过防 止从不同事务生成的两个条目使用相同的事务号来避免了表中的冲突。在另一 实现中,对每一事务清除表中的一个槽;在某些实现中,对每一事务增加一小 的开销相比增加一偶然的大开销可能是较佳的。在其它实现中,较佳的是一次 执行所有的表清除。
图21是用于过滤日志条目的示例进程2100的流程图。在各种实现中,所 示进程框可被合并、划分成子框或省略。该进程在框2110处开始,在那里在 当前事务的开头更新事务计数。该计数提供了在散列表中使用的事务号。接着, 在判定框2115处,确定是否达到事务计数限制。在一个实现中,该限制是通 过分配给该计数的溢出比特数来确定的。在另一实现中,该限制可基于存储器 限制或者可被选择来细调散列表的性能。如果未达到限制,则在框2140处, 通过散列表来过滤要记入日志的地址。相反,如果达到了该限制,则在框2120 处复位计数,并且在框2130处清除该表。然后,在框2140处,通过散列表来 过滤要记入日志的地址。
图22是用于过滤日志条目的示例进程2200的流程图。在各种实现中,所 示的进程框可被合并、划分成子框、或省略。在各种实现中,进程2200对应 于进程2100的框2140的进程。进程2200在框2210处开始,在那里对地址散 列以找出正确的散列表条目。接着,在框2220处,将要过滤的地址与当前事 务号(从事务计数中接收)进行异或。在一个实现中,如上所述地通过将地址 拆分成散列码和标签值来执行散列。
该进程然后前进到判定框2225,在那里对照异或结果来检査散列条目的 值。如果两者匹配,则无需再次将存储器访问记入日志,并且在框230处不向 日志写入。然而,如果两者不匹配,则在框2240处将异或结果写入散列表条 目,并且在框2250处将条目写入日志。
5.3对于新分配的对象的运行时日志过滤
在一个实现中,此处所描述的STM系统和技术标识由当前事务分配的对 象来避免对其写入任何撤消日志条目。这在上述静态编译时分析遗漏或不能移除对于新分配的对象的特定日志操作的情况下提供了备份。该运行时技术是安 全的,因为如果当前事务异常中止,则对象将死去。在一个实现中,这是使用
为新分配的对象上工作的专门化的版本的DTMOpenForUpdate,并通过使得该 操作写入一指定的STM字值来将该对象标记为被事务性分配来完成的。
6.无用信息收集的示例
一般而言,无用信息收集("GC")提供了用于自动确定一存储器对象 何时可因其不再被程序中的任何线程所需而被安全地解除分配的机制。无用信
息收集被结合到许多现代编程语言中,并形成了 Microsoft .NET框架的一部分。 本节描述了将GC集成到上述STM技术中的各种实现。然后,这一集成 并不是简单的。为示出这一问题,考虑以下示例 stomic {
tl = new IjargeTemporaryObject () , 〃计算E1
t2 = new LargeTemporaryObject ()/ 〃计算E2
出于示例的目的,假设在El和E2处执行的计算都足够复杂以使必须对 其完成GC而不耗尽存储器。此外,假设绑定到tl的LargeTemporaryObject 仅在El中使用,类似地,绑定到t2的LargeTemporaryObject仅在E2中使用。 如果在没有"原子"块的情况下执行,则一旦E1完成,tl占据的空间将被回 收。
本示例不能用现有的事务性存储器系统和GC来执行。在这些系统中,将 发生以下两个问题之一
1. 某些不知道TM的GC在发生GC时迫使所有存储器事务异常中止。 在这些系统上,诸如El和E2等计算从不能在原子块中执行。
2. 其它不知道TM的GC迫使对象保留比此处的知道TM的GC的对象 所保留的时间更长。在这些系统上,示例可成功执行,但是tl和t2将被保留 到原子块的最后,即使GC在其间知道tl随后不需要的E2期间发生。
在一个实现中,这些问题由知道TM的GC解决,该GC(a)允许GC在线 程正出于执行原子块的中间的同时发生,以及(b)允许GC恢复可确保是程序不需要的对象,而不论原子块是成功完成还是被重新执行。
在各种实现中,无用信息收集技术包括在用于标识在当前原子块内分配的 对象的原子事务块的实现中使用的技术。各实现还包括用于标识STM的数据
结构引用的哪些对象确保是程序不需要的技术。最后,GC实现包括用于标识 TM的数据结构中的哪些条目对于程序的将来执行是不需要的技术。
尽管以下描述特别地依赖于以上所描述的系统,但是此处描述的实现不限 于该设置;它们可用于其它形式的事务性存储器,可能包括硬件事务性存储器。
此处所描述的实现是参考令世界惊叹的跟踪无用信息收集器,例如标记一 清扫无用信息收集器或复制无用信息收集器来描述的。然而,这是出于展示简 明的目的,并且各实现不限于该设置;可使用己知的方法来将STM与其它无 用信息收集技术,如世代无用信息收集、并发无用信息收集或并行无用信息收 集集成。在一个实现中,STM与世代无用信息收集集成。
在较高层次,在令世界惊叹的跟踪GC的操作可被概括为以下过程。首先, 停止应用程序中的所有应用程序线程(有时称为"增变线程(mutatorthread)")。 接着,访问增变线程最初用于访问对象的每一个"根",从而确保从这些根所 引用的对象在收集之后被保留。(根包括处理器的运行增变线程的保存寄存器 内容、线程的栈上的对象引用、以及这些线程可通过程序的静态字段来访问的 对象引用)。因此,被保留的对象通常被称为"灰色",而其余对象最初被称 为"白色"。然后,对每一灰色对象,访问它所包含的对象引用。这些引用所 标识的任何白色对象进而被标记为灰色,并且一旦访问了灰色对象中的所有引 用,该对象被标记为黑色。重复此步骤,直到再没有灰色对象。留下的任何白 色对象被认为是无用信息,并且可以使它们所占据的空间可被增变线程用于重 新分配。最后,重启增变线程。在以下示例中,灰色对象将被称为"己访问" 对象,而已知为白色的对象是"不可达的"。
在将STM与GC集成的一个实现中,当启动GC时所有事务被异常中止。 这具有明显的缺点。在另一实现中,GC将STM的数据结构认为是增变线程的 根的一部分,由此基于它们被日志中的条目引用来访问对象。在这一实现中, 从一些日志中对对象的引用被认为是"强引用",这需要GC保持存储器可通 过它们达到。尽管此实现允许STM系统和GC之间的某种程度的集成,但是在另一实 现中,有较大程度的集成。图23是示出由无用信息收集模块1390执行的用于 在STM系统中执行无用信息收集的示例进程2300的流程图。在各种实现中, 所示进程框可被合并、划分成子框,或省略。在以下所示的过程中,GC能够 使用STM的特殊知识来解除对象的分配并在不再可能使用它们时将条目记入 日志,并且能够通过移除冗余条目来压縮日志。在一个实现中,图23的进程 是代替以上访问已访问对象的每一对象引用的典型GC过程中的步骤来执行 的。在替换实现中,图23的进程可以被集成到其它一般的GC过程中。
在某些实现中,图23的进程识别STM系统中的日志的两种质量。第一种 质量是标识当前事务己在其上尝试访问的对象的日志。这种日志在各实现中包 括在PLDI论文中描述的实现中的对在读取对象、更新对象和撤消日志中访问 的对象的引用。在一种术语中,从这些日志对对象的某些引用被认为是"弱引 用",意味着除了这些弱引用之外,GC将回收由不可达的对象使用的存储器。 GC在执行该进程时识别的另一种质量是标识在提交或异常中止事务时将被恢 复到存储器的对象引用的日志。这种日志包括撤消日志中的旧值。从这些日志 的这些引用在某些术语中被称为"强引用"。如上所述,"强引用"要求GC 保持存储器通过它们可达。
该进程在框2310处开始,在那里GC模块1390访问由撤消日志1360中 的每一条目的"先前值"字段引用的对象,由此防止这些对象被认为是不可达 的,并且防止其在当前事务异常中止的情况下的回收。接着,在框2320处, 从日志中移除某些特殊情况的条目。这一移除进程的一个示例在以下参考图24 更详细描述。
该进程继续到框2325,在那里GC模块访问每一已经访问的对象包含的 对象引用,以访问每一可达对象并得到最终的不可达对象集。然后,在框2330 处,GC模块审阅读取对象日志13800中引用不可达对象的条目。在判定框2335 处,GC模块对每一条目确定是否有对被该条目引用的对象的冲突并发访问。 在一个实现中,GC通过对每一条目确定该条目中的版本号是否匹配对象的版 本号来完成这一步。如果是,则在框2350处只需从日志中简单地解除对该条 目的分配,因为该条目是最新的并且该对象是不可达的。然而,如果版本号不匹配,则当前事务无效。此时,GC模块本身在框2340处使事务异常中止,从 而删除关于该事务的所有日志条目。在替换实现中,可省略框2335、 2340和 2350的特定检査和处理,在不审阅的情况下从读取对象日志中解除己知为不可 达对象 的分配,并且依赖于STM的其它运行时系统来确定是否要使事务异常 中止。
接着,在框2360处,GC模块审阅更新对象日志1370中的条目,并解除 引用不可达对象的所有条目的分配。然后,在框2370处,对撤消日志1360中 的条目执行相同的进程。最后,在框2380处,GC模块进而解除所有剩余的不 可达对象的分配。
扩展实现利用了从STM日志中移除附加条目的特殊情况。图24是示出由 无用信息收集模块1390执行的用于移除特殊情况日志条目的示例进程2400的 流程图。图24的进程对应于图23的框2320。在各种实现中,所示的进程框可 被合并、划分成子框、或省略。尽管此处的描述将这些扩展描述为作为进程2400 和框2320的进程的一部分的连续的步骤,但是将认识到,在某些环境中,图 24的进程可彼此独立地使用,并且在某些情况中,可独立于基本实现(例如, 在除GC之外的其它时刻压縮日志)来使用,并且一快速实现可组合这些步骤 中的一个或多个的各部分来减少必须访问日志中的条目的次数。
进程2400在框2410处开始,在那里如果仅有一个事务是活动的,则GC 模块1390立即回退并从撤消日志1360中移除引用不可达对象的条目。在框 2420处,GC模块审阅读取对象日志1380和撤消日志1360,并且如果条目引 用了在当前事务块内创建的不可达对象则从这些日志中移除条目。GC模块 1390这样做是因为如果对象是在事务开始之后分配的且现在是不可达的,则不 论事务是否提交该对象都会丢失。在一个实现中,关于在当前事务的子事务内 分配的不可达对象的日志条目也被移除。
在框2430处,对于读取对象日志中的每一条目,检查该条目所引用的对 象,并且如果该对象已经在更新对象日志中,且对该对象读取对象和更新对象 日志的版本号匹配,则可移除读取对象日志条目。该进程可标识对象何时被首 次添加到读取对象日志中,以及对象何时被首次添加到更新对象日志中。在任 一情况下,GC都用于移除包含的读取对象日志条目。在框2440处,GC模块1390在允许重复条目的STM实现中从读取对象 日志移除重复条目。重复的读取对象日志条目移除的一个示例进程在以下参考 图25来描述。然后,在框2450处,GC模块1390审阅撤消日志中的条目,并 将该日志中的"先前值"与记入日志的存储器位置的当前值进行比较。如果这 些值匹配,则值未改变,并且没有原因来维持该撤消日志条目,因此GC模块 1390移除这些条目。
图25是示出由无用信息收集模块1390执行的用于移除重复的读取对象曰 志条目的一个这样的示例进程2500的流程图。图25的进程对应于图24的框 2440。在各种实现中,所示进程框可被合并、划分成子框、或省略。图25的 进程利用了读取对象日志条目仅记录该对象在当前事务内已被打开来读取的 这一事实。这使得对单个对象的多个条目是多余的,并且因此在GC期间移除 这些条目是有益的。
图25的进程利用了在无用信息收集期间为每一对象维护的单个读比特标 志。在一个实现中,该标志由运行时系统保持,这类似于如何保持STM字。 在另一实现中,GC模块1390在GC时维护每一对象的标志。该进程在框2510 处开始,在那里GC模块1390在日志中的第一个条目处开始压縮读取对象日 志。接着,在框2520处,审阅由当前审阅的条目引用的对象。在框2525处, GC模块1390确定该对象是否设置了其读比特。如果没有设置,则假设当前条 目是对该对象的第一个条目。由此,在框2530处,设置读比特,并且单独留 下该条目。然而,如果GC模块1390确定读比特先前已在框2540处设置,则 该模块移除当前条目,因为它对于对象的前一条目是多余的。在一个实现中, 该移除是通过将被保持的条目复制到被移除的条目的位置来原地完成的。在其 它实现中,不移动条目,而是仅仅在其所在之处解除其分配。该进程然后继续 到判定框2545,在那里该模块确定在读取对象日志中是否存在其它条目。如果 是,则该进程继续。如果不是,则该进程结束。
7.计算环境
以上软件事务性存储器技术可在各种计算设备的任一种上执行。该技术可 用硬件电路以及在如图26所示的计算机或其它计算环境中执行的软件来实现。图26示出了其中可实现所描述的实施例的合适的计算环境(2600)的一 般化的示例。计算环境(2600)并不对本发明的使用范围或功能提出任何局限, 因为本发明可在不同的通用或专用计算环境中实现。
参考图26,计算环境(2600)包括至少一个处理单元(2610)和存储器 (2620)。在图26中,这一最基本的配置(2630)被包括在虚线内。处理单 元(2610)执行计算机可执行指令,并且可以是真实或虚拟处理器。在多处理 系统中,多个处理单元执行计算机可执行指令以提高处理能力。存储器(2620) 可以是易失性存储器(例如,寄存器、高速缓存、RAM)、非易失性存储器(例 如,ROM、 EEPROM、闪存等)或两者的某种组合。存储器(2620)储存实 现此处所描述的技术的软件(2680)。
计算环境可具有附加特征。例如,计算环境(2600)包括存储(2640)、 一个或多个输入设备(2650)、 一个或多个输出设备(2660)以及一个或多个 通信连接(2670)。诸如总线、控制器或网络等互连机制(未示出)将计算环 境(2600)的各组件互连。通常,操作系统软件(未示出)为在计算环境(2600) 中执行的其它软件提供了操作环境,并协调计算环境(2600)的各组件的活动。
存储(2640)可以是可移动或不可移动的,并包括磁盘、磁带或磁带盒、 CD-ROM、 CD-RW、 DVD或可用于储存信息并可在计算环境(2600)内访问 的任何其它介质。存储(2640)储存用于实现此处所描述的技术的软件(2680) 的指令。
输入设备(2650)可以是诸如键盘、鼠标、笔或跟踪球等触摸输入设备、 语音输入设备、扫描设备或向计算环境(2600)提供输入的另一设备。对于音 频,输入设备(2650)可以是声卡或接受模拟或数字形式的音频输入的类似设 备,或向计算环境提供音频样本的CD-ROM读取器。输出设备(2660)可以 是显示器、打印机、CD刻录机或提供来自计算环境(2600)的输出的另一设 备。
通信连接(2670)允许在通信介质上与另一计算实体的通信。通信介质在 已调制数据信号中传输诸如计算机可执行指令、压縮音频或视频信息或其它数 据等信息。已调制数据信号是其一个或多个特性以对信号中的信息编码的方式 来设定或更改的信号。作为示例而非局限,通信介质包括用电、光、RF、红外、线或无线技术。
此处所描述的技术可在计算机可读介质的一般上下文中描述。计算机可读 介质可以是可在计算环境内访问的任何可用介质。作为示例而非局限,对于计
算环境(2600),计算机可读介质可包括存储器(2620)、存储(2640)、通 信介质和以上任一种的组合。
此处所描述的技术可在诸如程序模块中所包括的在真实或虚拟目标处理 器上的计算环境中执行的计算机可执行指令的一般上下文中描述。 一般而言, 程序模块包括执行特定任务或实现特定抽象数据类型的例程、程序、库、对象、 类、组件、数据结构等。程序模块的功能可如各种实施例中所需地被组合或在 程序模块之间拆分。用于程序模块的计算机可执行指令可在本地或分布式计算 环境中执行。
出于表示的目的,详细描述使用了如"确定"、"生成"、"比较"和"写 入"等术语来描述计算环境中的计算机操作。这些术语是对由计算机执行的操 作的高级抽象,并且不应与人类执行的动作混淆。对应于这些术语的实际计算 机操作可取决于实现而变化。
鉴于此处所描述的主题的许多可能的变型,要求保护落入所附权利要求书 及其等效技术方案的范围和精神之内的所有这样的实施例作为本发明。
权利要求
1.一种计算机系统中的方法,所述计算机系统包括处理单元和编译器,所述编译器是用关于软件事务性存储器操作的知识来配置的,所述方法被执行来编译程序,所述程序包括软件事务性存储器块,所述方法包括优化(460)所述程序以创建包含软件事务性存储器指令的经优化的程序;以及编译(340)所述经优化的程序。
2. 如权利要求1所述的方法,其特征在于,优化所述程序包括将直接访问软件事务性存储器指令在所述软件事务性存储器块处插入到所述程序中;以及对包括所述软件事务性存储器指令的所述程序执行优化,以创建一经优化的程序,使得所述优化的至少某一些被配置成特别地对所述直接访问软件事务性存储器指令操作。
3. 如权利要求2所述的方法,其特征在于,执行优化包括执行公共子表达式消除过程,该过程被修改为对分解的软件事务性存储器指令操作。
4. 如权利要求3所述的方法,其特征在于,所述公共子表达式消除过程被修改成使得打开一对象来读取的指令在所述同一事务内的打开一对象来更新的指令之后被消除。
5. 如权利要求3所述的方法,其特征在于,所述公共子表达式消除过程被修改成使得一事务内对从存储器地址的冗余读和写以及对存储器地址的冗余日志被移除。
6. 如权利要求5所述的方法,其特征在于,所述公共子表达式消除过程还被修改成使得如果第一事务中的读和写被第二事务中的读和写变得冗余,则消除所述第一事务中的读和写,其中所述第一事务嵌套在所述第二事务内。
7. 如权利要求2所述的方法,其特征在于,执行优化包括执行代码运动优化。
8. 如权利要求2所述的方法,其特征在于,执行优化包括用确保对一存储器事务外部的对象的事务性存储器访问与访问该对象的至少一个非事务性存储器操作之间的原子性的一个或多个事务性存储器操作来扩充所述至少一个非事务性存储器操作。
9. 如权利要求8所述的方法,其特征在于,扩充非事务性存储器操作包括在所述非事务性存储器操作之前插入一打开操作,所述打开操作打开所述对象以供所述非事务性存储器操作访问;以及在所述非事务性存储器操作之后插入一提交操作,所述提交操作确定在所述非事务性存储器操作的执行期间是否有对所述对象的冲突访问。
10. 如权利要求9所述的方法,其特征在于-所述非事务性存储器操作是读取操作;所述打开操作被配置成在所述非事务性存储器操作的执行之前检索所述对象的状态的指示;以及所述提交操作被配置成在所述非事务性存储器操作的执行之后检索所述对象的状态的指示;以及如果所述对象的状态指示冲突的访问,则使得所述打开、读取和提交操作循环,直到读取变为可能。
11. 如权利要求9所述的方法,其特征在于所述非事务性存储器操作是写入操作;所述打开操作被配置成获得对所述对象的写访问;以及所述提交操作被配置成提交对所述对象所作的写入。
12. 如权利要求9所述的方法,其特征在于,所述打开和提交命令利用了所述对象的同步来确保所述非事务性存储器操作原子地执行。
13. 如权利要求9所述的方法,其特征在于,所述打开命令阻断,直到它能够打开所述对象来访问。
14. 如权利要求8所述的方法,其特征在于,还包括标识访问存储器事务外部的对象的一个或多个存储器操作。
15. 如权利要求14所述的方法,其特征在于,标识一个或多个存储器操作包括分析事务性存储器访问以确定可在所述软件的执行期间被事务性存储器操作访问的字段;标识访问可被事务性存储器操作访问的字段的一个或多个非事务性存储器操作。
16. 如权利要求15所述的方法,其特征在于,对确保不被事务性存储器操作访问的任何字段,避免对访问该字段的非事务性存储器操作的扩充。
17. 如权利要求16所述的方法,其特征在于,用一个或多个事务性存储器操作来扩充访问存储器事务外部的对象的非事务性存储器操作包括在所述非事务性存储器操作周围插入原子存储器事务块。
18. 如权利要求2所述的方法,其特征在于,执行优化包括在一组程序点处标识对总是绑定到事务中新分配的对象的对象的引用;以及防止对象上的软件事务性存储器操作通过所标识的弓I用到达,其中所述操作在所述事务外部没有效果。
19. 如权利要求18所述的方法,其特征在于,在一组程序点处标识对总是绑定到新分配的对象的对象的引用跨过程调用而发生。
20. 如权利要求19所述的方法,其特征在于,在一组程序点处标识对总是绑定到新分配的对象的对象的引用包括对所述程序执行前向数据流分析。
21. 如权利要求20所述的方法,其特征在于,所述前向数据流分析包括对每一基本块维护一数据结构,所述数据结构表示,在所述基本块中的一位置处,对所述基本块中的每一变量,如果所述变量或者已知为总是被赋值给新分配的对象,则所述变量可能被赋值给在所述事务外部分配的对象,或者对所述变量信息是未知的。
22. 如权利要求21所述的方法,其特征在于,所述数据结构包括从每一引用到对每一引用保持的数据的映射,其中对每一引用保持的数据包括包含一点阵值和引用之间的依赖性的可更新单元。
23. 如权利要求22所述的方法,其特征在于,维护所述数据结构包括使用所述引用之间的依赖性,当所述数据流分析进行时通过所述点阵值的可更新单元来传播信息。
24. 如权利要求23所述的方法,其特征在于,传播信息包括在分析从变量到点阵值的可更新单元的映射中的未映射变量的程序语句之前将信息扩展到所述未映射变量。
25. 如权利要求18所述的方法,其特征在于,防止对象上的软件事务性存储器操作通过所标识的引用到达,其中所述操作在所述事务外部没有效果,包括,对每一引用,用当被分解成分解的软件事务性存储器操作时不包括更新曰志操作的更新操作来替换通过所述引用的更新操作。
26. 如权利要求18所述的方法,其特征在于,防止对象上的软件事务性存储器操作通过所标识的引用到达,其中所述操作在所述事务外部没有效果,包括,在软件事务性存储器操作被分解成分解的软件事务性存储器操作之后,移除通过所标识的引用来操作的分解的日志操作。
27. 如权利要求18所述的方法,其特征在于,还包括确定哪些所标识的引用是针对在所述事务之后不可访问的对象的;以及其中,防止对象上的软件事务性存储器操作通过所标识的引用到达,其中所述操作在所述事务外部没有效果,包括,移除软件事务性存储器操作以通过所确定的引用来打开对象来读取或更新。
28. 如权利要求2所述的方法,其特征在于,执行优化包括定位一打开引用来读取的软件事务性存储器指令,该指令已知在执行期间后面跟有打开引用来更新的第一软件事务性存储器指令;以及对于打开引用来读取的所述指令,替换为打开引用来更新的第二指令。
29. 如权利要求28所述的方法,其特征在于,所述方法还包括移除打开引用来更新的所述第一指令。
30. 如权利要求29所述的方法,其特征在于,移除所述第一指令包括使用公共子表达式消除来移除包括所述第一指令的冗余指令。
31. 如权利要求28所述的方法,其特征在于,所述软件是由一控制流图来表示的,并且所述方法是在所述控制流图上执行的。
32. 如权利要求31所述的方法,其特征在于,定位一打开引用来读取的指令包括,对所述控制流图中的基本块标识一打开弓I用来读取的指令;记录所述引用;从所标识的指令开始前向扫描通过所述基本块;当找到对所述引用的赋值时,停止对所标识的指令的扫描;以及当找到打开引用来更新的指令时,进而替换所标识的指令。
33. 如权利要求31所述的方法,其特征在于,定位一打开引用来读取的指令包括,对所述控制流图中的基本块-标识一打开弓I用来更新的指令;记录所述引用;从所标识的指令开始前向扫描通过所述基本块;当找到对所述引用的赋值时,停止对所标识的指令的扫描;以及当找到打开引用来读取的指令时,进而替换所找到的打开引用来读取的指令。
34. 如权利要求31所述的方法,其特征在于,定位一打开引用来读取的指令包括执行一数据流分析,所述数据流分析跨基本块边界搜索以找出因打开引用来更新的指令而变得冗余的打开引用来读取的指令。
35. 如权利要求34所述的方法,其特征在于,执行数据流分析包括在所述控制流图的每一基本块的边界处维护一变量集,所述变量集已知引用了被打开来更新的引用;对每一基本块后向扫描通过所述控制流图;当对一特定变量找到打开引用来更新的指令时,将该变量添加到对于所述基本块的变量集中;当对一特定变量找到定义时,将该变量从对于所述基本块的变量集中移除;当找到所述基本块的开头时,将对于所述基本块的变量集与储存在基本块的末端的直接导向所述基本块的变量集相交;重复所述进程,直到对每一基本块创建了一稳定的变量集;以及对每一基本块,将打开被对于所述基本块的变量集引用的引用来读取的指令认为是冗余指令。
36. 如权利要求2所述的方法,其特征在于,执行优化包括标识所述程序中的一个或多个过程,所述过程包括能够在所述过程外部优化的一个或多个软件事务性存储器操作;对所述一个或多个过程中的每一个,创建所述过程的克隆的版本,所述克隆的版本允许对能够在所述克隆的版本外部优化的软件事务性存储器指令的调用;移动能够在所述过程外部优化的一个或多个软件事务性存储器操作;以及用对其克隆的版本的调用来替换对所述一个或多个过程的每一个的调用。
37. 如权利要求36所述的方法,其特征在于,还包括通过移除能够被优化的所述一个或多个软件事务性存储器操作中的冗余来优化所述程序。
38. 如权利要求37所述的方法,其特征在于,优化所述程序包括对所述程序执行公共子表达式消除。
39. 如权利要求37所述的方法,其特征在于,能够被优化的所述一个或多个软件事务性存储器操作包括获取事务管理器的操作。
40. 如权利要求39所述的方法,其特征在于,包括获取事务管理器的操作的所述过程的克隆的版本包括取事务管理器的实例作为输入的过程。
41. 如权利要求37所述的方法,其特征在于,能够被优化的所述一个或多个软件事务性存储器操作包括打开引用来读取或更新的操作,所述引用作为输入被传递给所述过程。
42. 如权利要求41所述的方法,其特征在于,包括打开引用来读取或更新的操作的所述过程的克隆的版本包括依赖于在过程被调用之前被打开的引用的过程。
43. 如权利要求42所述的方法,其特征在于,创建包括打开引用来读取或更新的操作的所述过程的克隆的版本包括执行一后向数据流分析来确定哪些过程包括对于已知在所述过程的开头的引用打开引用来读取或更新的操作。
全文摘要
描述了对软件事务性存储器指令执行优化以实现高效性能的软件事务性存储器系统。软件事务性存储器块由软件事务性存储器指令来替换,这些指令随后被分解成分解的软件事务性存储器指令。分解的指令允许了解指令语义的编译器执行在传统的事务性存储器系统上不可用的优化。执行高级软件事务性存储器优化,诸如过程调用周围的代码移动、添加提供强原子性的操作、移除不必要的读取—更新升级、以及移除对新分配的对象的操作。
文档编号G06F9/45GK101542437SQ200680045337
公开日2009年9月23日 申请日期2006年11月27日 优先权日2005年12月7日
发明者A·E·肖纳, D·R·小泰迪蒂, M·R·佩里斯科, T·L·哈里斯 申请人:微软公司
网友询问留言 已有0条留言
  • 还没有人留言评论。精彩留言会获得点赞!
1