本申请要求2006年3月23日提交的美国专利申请第11/389,451号的优先权,后者要求2005年12月7日提交的美国临时申请第60/748,386号的优先权。
详细描述
此处所示的示例描述了基于软件和硬件的事务性存储器系统,以及对这些系统的性能改进。具体地,以下的实现示例描述了:分解的软件事务操作;使用编译器中间表示(“IR”)中的STM原语来允许代码优化(其术语在以下解释);用于改进这些原语的性能的编译器改进;使用结合表的运行时日志过滤;以及高效的运行时按对象操作。尽管此处所提供的描述是作为对特定软件事务性存储器操作实现的优化来提供的,但是可以认识到,此处所描述的技术和系统可对各种实现操作,并且不一定暗示对此处所描述的技术的实现、性能或要求的任何限制。
1.软件事务性存储器系统的示例
原子块提供了对编写并发程序的问题的有希望的简化。在此处所描述的系统中,一代码块被标记为原子的,并且编译器和运行时系统在该块内提供那些表现为原子的操作,包括函数调用。程序员不再需要担心手动锁定、低级竞争条件或死锁。原子块也可提供异常恢复,由此如果异常终止一个块,则该块的副作用被回退。这甚至在单线程化应用中也是有价值的:出错处理代码通常难以编写和测试。原子块的实现缩放到大型多处理器机器,因为它们是并行性保留的:原子块可并发地执行,只要一个块中正被更新的位置不在任何其它块中被访问。这保留了在常规的数据高速缓存中所允许的共享的种类。
此处所描述的技术参考了与编译器和运行时系统紧密集成的STM实现。该实现的一个特征是它是直接更新STM。这允许直接在堆中更新对象而不是在对象的私有阴影副本(shadow copy)上工作,或经由对象引用和当前对象内容之间的额外的间接层次来更新。这对于成功提交的事务而言更高效。
此处所描述的系统和技术利用了该实现的提供分解的STM接口这一特征。例如,事务存储obj.field=42被拆分成以下步骤:(a)记录obj正被当前线程更新,(b)将field所保持的旧的值记入日志,以及(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 TMStart()
void TMAbort()
bool TMCommit()
bool TMIsValid()
word TMRead(addr addr)
void TMWrite(addr addr,word value)
第一组用于管理事务:TMStart启动当前线程中的一个事务。TMAbort异常中止当前线程的事务。TMCommit试图提交当前线程的事务。如果事务不能提交(例如,在一个实现中,由于一并发事务已经更新了其所访问的位置之一),则TMCommit返回假,并且丢弃当前事务。否则,TMCommit返回真,并且在该事务期间所作出的任何更新被原子地传播到共享堆。TMIsValid当且仅当当前线程的事务可在调用的时候提交时才返回真。第二组操作执行数据访问:TMRead返回所指定的位置的当前值,或由TWWrite在当前事务中写入的最新近的值。
在此处所描述的技术的一个实现中,直接用STM来编程的过程通过使得编译器重写原子块中的存储器访问来使用STM操作,并使其生成所调用方法的专门版本来确保TMRead和TMWrite被用于对在原子块中作出的所有存储器访问来自动化。
上述设计遭受限制其适用性的多个问题。以下代码示例说明了这一点。以下示出的示例1a迭代通过标记节点this.Head和this.Tail之间的链表的各元素。它将节点的Value字段相加,并将结果存储在this.Sum中。示例1b示出了自动发出对所有存储器访问的TMRead和TMWrite的调用的一个示例。
然而,对于这一基于字的系统会出现若干性能问题。首先,TMRead和TMWrite的许多实现使用在每一TMRead和TMWrtie操作上搜索的事务日志。TMRead必须看见同一事务的更早期的存储,因此它搜索保持试探更新的事务日志。这一搜索可能无法缩放以支持大型事务。性能取决于事务日志的长度以及辅助索引结构的有效性。其次,对STM库的不透明调用阻碍了优化(例如,不再可能从循环中提升读取this.Tail,因为TMRead的行为对于编译器是未知的)。最后,单块TM操作导致重复的工作。例如,在循环中访问一字段时的重复搜索。
1.4分解的直接访问STM
在此处提供的示例中使用的分解的直接访问STM实现解决了这些问题。第一个问题是通过将系统设计成使得事务可直接对堆执行读和写操作来解决的,从而使读自然地看见前一事务存储而无需任何搜索。仍需要日志来回退异常中止的事务并跟踪关于所访问的位置的版本化信息。对于短事务,这些日志是只能被追加的。由此,不论事务的大小如何,都不需要搜索。
第二个问题是通过在编译的早期引入TM操作并扩展后续分析和优化阶段以使其知晓其语义来解决的。最后,第三个问题是通过将单块TM操作分解成分离的步骤以避免重复工作来解决的。例如,对事务日志的管理与实际数据访问分开,这通常允许从循环中提升日志管理。
该接口将事务性存储器操作分解成四个组:
tm_mgr DTMGetTMMgr()
void DTMStart(tm_mgr tx)
void DTMAbort(tm_mgr tx)
bool DTMCommit(tm_mgr tx)
bool DTMIsValid(tm_mgr tx)
void DTMOpenForRead(tm_mgr tx,object obj)
void DTMOpenForUpdate(tm_mgr tx,object obj)
object DTMAddrToSurrogate(tm_mgr tx,addr addr)
void DTMLogFieldStore(tm_mgr tx,object obj,int offset)
void DTMLogAddrStore(tm_mgr tx,addr obj)
前两个组是直接的,提供了获取当前线程的事务管理器的DTMGetTMMgr,然后提供了普通的事务性管理操作。第三组提供了竞争检测:DTMOpenForRead和DTMOpenForUpdate指示所指定的对象将以只读模式访问,或者它随后可被更新。对静态字段的访问由代表静态字段来保持版本化信息的代理对象调解:DTMAddrToSurrogate将地址映射到其代理。最后一组维护一撤消日志,需要在异常中止时回退更新。DTMLogFieldStore处理对对象字段的存储,而DTMLogAddrStore处理对任何地址的存储。
对这些操作的调用必须被正确定序以提供原子性。有三个规则:(a)当读取一位置时,该位置必须被打开以供读取,(b)当更新一位置或对该位置将存储记入日志时必须打开该位置以供更新,(c)在更新一位置之前必须将该位置的旧值记入日志。在实践中,这意味着对于一对象的字段的TMRead的调用被拆分成DTMGetTMMgr、DTMOpenForRead的序列,然后是字段读取。对TMWrite的调用则被拆分成DTMGetTMMge、DTMOpenForUpdate、DTMLogAddrStore的序列,然后是字段写入。对静态字段的TMRead的调用被拆分成DTMGetTMMgr、DTMAddrToSurrogate、DTMOpenForRead的序列,然后是静态字段读取。对TMWrite的调用则被拆分成DTMGetTMMgr、DTMAddrToSurrogate、DTMOpenForUpdate、DTMLogAddrStore的序列,然后是静态字段写入。
以下示例展示了使用分解的直接访问STM的一个示例。示例1中的代码迭代通过标志节点this.Head和this.Tail之间的链表的各元素。它对节点的Value字段求和,并将结果储存在this.Sum中。示例2示出了如何使用分解的直接访问STM来实现Sum(求和)。
示例1a
public int Sum(){
Node n=this.Head;
int t=0;
do{
t+=n.Value;
if(n==this.Tail)
{
this.Sum=t;
return t;
}
n=n.Next;
}while(true)
}
示例1b
public int Sum(){
Node n=TMRead(&this.Head);
int t=0;
do{
t+=TMRead{&n.Value);
if(n==TMRead(&this.Tail))
{
TMWrite(&this.Sum,t);
return t;
}
n=TMRead(&n.Next);
}while(true)
}
示例2
public int Sum(){
tm_mgr tx=DTMGetTMMgr();
DTMOpenForRead(tx,this);
Node n=this.head;
int t=0;
do{
DTMOpenForRead(tx,n);
t+=n.Value;
DTMOpenForRead(tx,this);
if(n==this.Tail){
DTMOpenForUpdate(tx,this);
DTMLogFieldStore(tx,this,offsetof(List.Sum));
this.Sum=t;
return t;
}
DTMOpenForRead(tx,n);
n=n.Next;
}while(true}
}
2.编译器优化
第2节描述了利用采用STM操作的知识来配置的编译器对分解的STM操作的优化。应当注意,如在本申请中所使用的,术语“优化”、“经优化的”等是本领域中一般指在不参考任何特定改进程度的改进的术语。由此,在各种情形中,尽管“优化”可以改进系统或技术的性能的一个或多个方面,但是它并不一定要求该系统或技术的每一方面都被改进。另外,在各种情形中,“优化”不一定意味着对任一方面的达到任何特定的最小或最大程度的改进。此外,尽管“经优化的”系统或技术可能在一个或多个领域中显示出性能改进,但是它也可能在其它领域中显示出性能降低。最后,尽管“优化”在某些情形中可改进系统或技术的性能,它也可能在其它情形中降低性能。在以下描述的特定环境中,尽管优化将导致冗余的或多于的STM指令或日志写的移除,这可能提供提高的性能,但是这些优化不应意味着将移除每一可能的冗余或多于的指令。
图1是示出用于利用软件事务性存储器来创建经优化的程序120的编译器100的一个示例的框图。在所示的示例中,编码器100取源代码110作为输入。如图所示,源代码110包含一个或多个原子块115。如上所述,在一个实现中,这些原子块的包括避免了希望利用STM的程序员的额外编程;这些块由编译器修改以包括分解的STM指令,这些指令然后被优化。尽管图1示出了单一的一段源代码,但是应当认识到,这仅仅是出于简化说明的目的;此处所描述的技术和系统也适用于一起编译的多个源代码文件,以及使用已经编译好的代码的源代码。另外,在各种实现中,使用不同的代码语言,包括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的进程的另一实现中,编译器还通过分解日志操作来减少日志管理成本,从而允许将日志管理工作的成本分摊到多个操作上。特别地,在一个实现中,DTMOpen*和DTMLog*操作以对当前数组中存在空间的检查开始。对于DTMOpenForRead,这是在该代码的快速路径版本中必须执行的唯一检查。为分摊这些检查的成本,编译器利用了一个新的操作EnsureLogMemory,该操作取指示在给定日志中保留多少槽的整数。DTMOpen*和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_for_update(loc);
write(loc,val);
变为:
dummy1=open_for_update(loc);
dummy2=log_for_update(loc,dummy1);
write(loc,val,dummy2);
接着,在框640处,对STM指令执行公共子表达式消除(“CSE”),之后在框660处对指令执行冗余加载-存储消除,并在框680处执行代码移动优化。
在一个示例中,这些优化可对DTMGetTMMgr操作执行,因为它是恒定的,且因此为CSE提供了机会。类似地,由于DTMOpenForRead、DTMOpenForUpdate、DTMAddrToSurrogate和DTMLog*操作在事务内是幂等的,因此它们符合CSE或代码运动的条件。对此优化的一个约束是在一个实现中,代码运动不能扩展超过事务边界。在另一实现中,扩展CSE以提供对在DTMOpenForUpdate之后发生的DTMOpenForRead的消除。该优化可被执行是因为更新访问包含了读访问。
在其它实现中,可对嵌套事务之间的操作执行CSE。由此,在一个示例中,嵌套事务中的DTMOpenForRead被外部事务内DTMOpenForRead或DTMOpenForUpdate包含,且由此可被消除。在另一示例中,嵌套事务中的DTMOpenForUpdate被外部事务中的DTMOpenForUpdate包含,且被消除。
在另一实现中,DTMGetTMMge操作可通过从每一线程的Thread对象中取出用于线程的当前事务管理器(并在必要时创建事务管理器)来实现。Bartok编译器因此可将GetCurrentThread指令作为服从代码运动的常数操作来处理。
作为一个示例,在上述进程执行之后,示例2的代码被简化为以下更高效的代码:
示例3
public int Sum(){
tm_mgr tx=DTMGetTMMgr();
DTMOpenForRead(tx,this);
Node n=this.head;
int t=0;
do{
DTMOpenForRead(tx,n);
t+=n.Value;
if(n==this.Tail){
DTMOpenForUpdate(tx,this);
DTMLogFieldStore(tx,this,offsetof(List.Sum));
this.Sum=t;
return t;
}
n=n.Next;
}while(true)
}
2.2高级STM优化
2.2.1实现强原子性
上述技术可用于构建“原子块”,其中一个原子块中的存储器访问相对于第二原子块中的访问不可分割地发生。然而,由一个线程执行的“原子”块在第二个线程执行冲突的存储器访问而没有使用“原子”块时可能看上去并不是不可分割地执行的。具有这一特征的设计可被认为是提供了“弱原子性”。
此处所描述的技术的一个实现涉及如何提供“强原子性”,其中原子块看上去相对于所有存储器访问都是不可分割地执行的,而非仅仅是那些在其它原子块中作出的存储器访问。
一个基本实现扩展了上述STM,其通过(a)标识发生在任何原子块外部的对共享存储器的所有访问,(b)将这些访问重写为短原子块,而具有对强原子性的支持。
例如,假定一程序从字段“o1.x”的内容中读取,并将结果储存在字段“02.x”中。这最初由编译器中间表示(IR)的两个指令来表示:
L1:
t1=getfield<x>(o1)
L2:
putfield<x>(o2,t1)
基本实现将这些扩展为诸如以下的代码:
L1:
DTMStart(tm)
DTMOpenForRead(tm,o1)
t1=getfield<x>(o1)
DTMCommit(tm)//C1
L2:
DTMStart(tm)
DTMOpenForUpdate(tm,o2)
logfield<x>(o2)
putfield<x>(o2,t1)
DTMCommit(tm)//C2
(在某些实现中,所编写的实际代码更复杂,因为如果在提交操作C1或C2期间有竞争,则它还必须包括从L1到L2的重新执行事务的代码路径。该代码的确切细节将取决于如何在IR中表示STM操作而变化。)
该基本形式将提供强原子性,但是由于附加的事务启动、事务提交、打开来读取、打开来更新、以及日志操作的成本超过了原始字段访问的成本,因此其执行不良。
为提高效率同时仍提供强原子性实现,此处所描述的技术的一个实现使用了专门的IR操作来加速仅访问单个存储器位置的短事务的执行。
有两种情况要考虑:从单个位置读取的事务,以及更新单个位置的事务(包括对单个位置执行读取-修改-写入操作的事务)。这两种情况都涉及对STM字的检查,这将在以下更详细描述。第一种情况在扩展的IR中通过(a)为所涉及的对象读取STM字,(b)读取字段,(c)重新读取STM字,并检查所读取的值匹配(a)中的值并且该值不表明存在并发冲突访问来表示。第二种情况在扩展的IR中通过(a)更新事务更新,(b)更新字段,(c)再次更新STM字,表明它不再服从非事务性更新来表示。
由此,一个示例IR看上去如下:
L1:
s1=openoneobjforread(o1)
t1=getfield<x>(o1)
if(!checkoneobj(o1,s1))goto L1
L2:
s2=openoneobjforupdate(o2)
putfield<x>(o2,t1)
commitoneobj(o2,s2)
该实现涉及与上述STM实现的两个区别。第一个区别是,不像上述STM实现,临时存储是在局部变量而非事务日志中找到的。这意味着变量可以在处理器寄存器中分配以使其访问变得更快。第二个区别是在L2处开始的事务不能异常中止,因此无需将在“o2.x”中盖写的值记入日志。
在又一强原子性实现中,编译器执行进一步的优化以限制必须以此方式扩展的字段的数量。在一个示例中,编译器执行基于类型的分析以标识可在原子块中写入的所有字段。原子块中确保从不经受访问的任何其它字段可被直接访问,因此无需在其周围插入强原子性操作。
图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处,编译器确定是否有另外的非原子存储器访问。如果有,则该进程重复。如果没有,则该进程结束。
2.2.2移除读取-更新升级
STM编译器的各种实现执行的另一高级优化是避免当DTMOpenForRead操作之后有DTMOpenForUpdate操作时发生的不必要的日志记录。此处所描述的技术固有的一个设计假设是读比写更常见,这就是这些技术使用分开的DTMOpenForUpdate和DTMOpenForRead操作的原因;打开来读取指令能够更快地完成。然而,有时候读取对象然后写入对象(规范的示例是“obj.field++”)。在这一情况中,带有打开操作的IR看上去如下:
DTMOpenForRead(obj);
t=obj.field;
t=t+1;
DTMOpenForUpdate(obj);
DTMLogFieldStore(obj,offsetof(obj.field));
obj.field=t;
如果程序到达打开来读取点,则可以看到,它将到达打开来更新点,从而忽略了此时的异常。由于对同一对象的打开来更新包含了打开来读取,因此打开来读取操作被浪费了。这在一个实现中被称为读取-更新升级。仅仅在较早期执行打开来更新操作将是更高效的:
DTMOpenForUpdate(obj);
t=obj.field;
t=t+1;
DTMLogFieldStore(obj,offsetof(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的进程,直到在给定每一块的末端的当前集合时不再有任何东西改变。编译器可向后走查该块,以用相同的方式更新该集合来获得用于每一程序点的集合。
此时,出于框810的目的,标识“必须在将来被打开来更新”集合中的变量。然后,在一个实现中,对这些变量的每一个添加打开来更新操作,从而允许CSE稍后移除额外的打开来更新操作。在另一实现中,使用局部冗余性(“PRE”)来代替打开来更新指令及其后的CSE优化的渐进相加。这是更一般的解决方案,并且可产生在相同路径上具有更少打开指令的代码。
在一个实现中,上述分析假设不会引发异常,因此忽略了异常边,并在假定没有抛出异常的情况下计算将来肯定会被打开来更新的对象的集合。这是因为异常不是常见的情况。这一精度损失不会影响正确性。然而,可扩展替换实现以考虑异常边,以便产生精确的结果。
另外,在替换实现中,以上分析可被修改成忽略其它代码段。这可通过利用表明忽略的代码与被分析的代码相比相对较不频繁地执行的试探来完成。在一个实现中,这些试探是静态地确定的;在另一实现中,它们是从简介信息确定的。
作为一个示例,在执行了上述进程之后,示例3的代码被简化为以下更高效的代码:
示例3.1
public int Sum(){
tm_mgr tx=DTMGetTMMgr();
DTMOpenForUpdate(tx,this);
Node n=this.head;
int t=0;
do{
DTMOpenForRead(tx,n);
t+=n.Value;
if(n==this.Tail){
DTMLogFieldStore(tx,this,offsetof(List.Sum));
this.Sum=t;
return t;
}
n=n.Next;
}while(true)
}
2.2.3在存在过程调用的情况下移动操作
许多现有的编译器优化只能比较、消除和移动函数内的代码,因为这些技术一般太过昂贵以至于无法应用于整个程序图。然而,通过跨过程边界移动STM操作的高级STM优化,这些优化可更高效地执行。
作为一个示例,给定代码:
Foo(object obj){
DTMOpenForUpdate(obj);
...
}
Bar(){
obj=...;
DTMOpenForUpdate(obj);
Foo(obj);
}
很清楚,Foo总是打开由其参数引用的对象来更新。Foo的调用者还可打开该对象(如上所述),或者它可在一循环(或多个其它东西)内调用Foo。然而,该过程调用防止对具有调用者中的代码的Foo的动作的分析/优化。该优化跨调用界限移动打开操作以便为其它优化创建更多机会。CSE是一个明显的候选者,因为调用者可能已经完成了移动到它的操作。也可改进其它非事务专用优化(例如,如果同一对象在一循环中被反复传递给一函数,则可将打开从该循环中提升出来)。
在一个示例中,该优化是对DTMGetTMMgr以及DTMOpenFor*操作实现的。在替换实现中,该优化可对如果调用一方法则必须被打开的其它操作执行的。另外,在替换实现中,该优化可对在调用一方法时通常发生的其它操作执行,这牺牲了非常见的情况下的精度和性能以换取常见情况下的更好性能,而不损失有效性。在一个实现中,编译器对非虚拟(也称为“直接”)调用执行优化;这包括被“解除虚拟化”的虚拟调用(例如,确定仅单个调用目标存在并用直接调用替换虚拟调用)。
图10是用于通过跨方法边界移动STM操作来优化STM操作的示例进程1000的流程图。进程1000对应于图5的框560。在各种实现中,所示的进程框可被合并、划分成子框、或省略。该进程在框1010处开始,在那里定位包含可被移动到该方法之外的操作的方法。接着,在框1020处,克隆该方法以创建该方法的允许该操作在该方法外部执行的版本。如果该操作给出结果,则框1020的进程还向所克隆的方法添加自变量以使该结果可被传递到该方法。
接着,在框1030处,将该操作移动出所克隆的方法到用于该方法的一个或多个调用地点。在替换实现中,在不移动操作的情况下创建所克隆的方法,而非完全克隆该方法并移除操作。最后,在框1040处,用所克隆的方法来替换对原始方法的调用。在所替换的调用的一个实现中,包括由所克隆的方法使用的附加自变量。这些附加自变量的示例在以下示出。
在调用替换的另一实现中,编译器维护它克隆的一组方法以及从这些方法到其克隆(专门的)版本的映射。编译器然后再次扫描程序中的所有方法来替换调用。在某些情况下,该技术完全消除了原始版本的函数。然而,在某些情况下,(例如,如果取该函数的地址),则仍会有对非专门版本的调用并且该调用不能被移除。
不同操作将导致方法以不同的方式来克隆。在一个示例中,如果一方法包含GetTxMgr,则编译器克隆该方法、添加一额外参数来接收事务管理器、并用该参数来替换GetTxMgr的所有出现:
FuncUsesMgr(){
...
m=GetTxMgr();
...
}
==>FuncUsesMgr_copy(TxMgr mgr){
...
m=mgr;
...
}
在此示例中,对该方法的调用被改为对克隆的方法的调用,并具有包含事务管理器的附加自变量:
Call<FuncUsesMgr>()
==>mgr=GetTxMgr();
FuncUsesMgr_copy(mgr);
在另一示例中,代替令单个特性跟踪和创建基于(事务管理器)的专门克隆,而是有许多(每一参数和每一静态代理)。例如,
Foo(object obj1,object obj2,object obj3){
DTMOpenForRead(obj1);
DTMOpenForUpdate(obj3);
....
}
在此示例中,编译器可能希望创建期望调用者适当地打开obj1和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_____;
else
p=q;
...
}
并不能够容易地提供关于p是否总是引用新分配的对象的信息。由此,编译器必须执行分析来标识变量是否符合日志移除的条件。
在一个实现中,编译器使用在每一程序点处利用表明每一变量是否已知肯定引用新分配的对象的向量的位向量。尽管该实现将正确地标识出对其可移除日志操作的引用,但是它一般较慢,并且涉及许多存储器使用。在另一实现中,位向量可提供关于一大段代码,诸如基本块的概要信息。该实现对于过程间分析仍是较慢的。
作为替换,在一个实现中,编译器使用了流敏感过程间分析来标识总是被绑定到自从事务开始以来分配的对象的变量。图12示出了这一示例进程1200的流程图。进程1200对应于图11的框1110。在各种实现中,所示进程框可被合并、划分成子框、或省略。在所示的实现中,在事务中的每一基本块上执行进程1200。
图12所示的进程是对整个程序的每一函数执行的,以便并发地构建并解析依赖性图。对于每一函数,该进程在框1210处开始,在那里创建从对象类型化变量到依赖性图中的点阵元素或节点的映射。该映射表示可被分配给块中的任一点处的变量的值的种类。在一个实现中,该点阵具有三个元素:“旧”,表示引用可能不是新分配的对象的变量;“新”,表示引用必须是新分配的对象的变量;以及“未知”,用于对其没有信息的变量。在框1220处,该映射中的所有值被设为“未知”。接着,在框1230处,编译器前向移动通过基本块以检查该块中的第一个操作。在判定框1235处,编译器确定它正在检查什么类型的操作。如果该操作是对象分配,则在框1240处,编译器对于被分配的变量向该映射添加“新”。如果该操作是赋值、强制类型转换或过程调用,则在框1250处,编译器在变量之间传播点阵值。由此,赋值和强制类型转换将其抽象值传播到所分配到的变量。调用将抽象值传播到调用形式并传播来自返回值的抽象值。然而,如果操作是除了以上情况之外的任何操作,则在框1260处,修改该点阵以对于向其分配操作的变量表示“旧”。在一个实现中,该分析还考虑在要新分配的当前事务的提交的子事务内分配的对象。
编译器然后对从局部变量到点阵值或图节点的映射前向传播信息,并在函数内迭代直到达到一固定点。由此,在判定框1265处,编译器确定是否到达诸如if语句的结束等连接点。如果已到达连接点,则在框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 GetSTMWord(Object o)
bool OpenSTMWord(Object o,word prev,word next)
void CloseSTMWord(Object o,word next)
snapshot GetSTMSnapshot(Object o)
word SnapshotToWord(snapshot s)
对象的STM字具有两个字段。一个字段是指示该对象当前是否被任何事务打开来更新的单个比特。如果该比特被设置,则该字的其余部分标识拥有的事务。否则,该字的其余部分保持一版本号。OpenSTMWord对STM字执行原子比较并交换(compare-and-swap)(从prev(前一项)到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-即,只要一线程释放对该对象的更新访问-就改变快照。这提供了足以检测冲突的信息。
确保这一条件的一种方法是将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.1STM日志结构的示例
每一线程具有带三个日志的单独的事务管理器。读对象日志和更新对象日志跟踪该事务已打开来读取或打开来更新的对象。撤消日志跟踪在异常中止时必须被撤消的更新。所有日志都是顺序地写入的,并且从不被搜索。使用单独的日志是因为其中的条目具有不同的格式,并且因为在提交期间,系统需要轮流在不同种类的条目上迭代。每一条目被组织成条目数组的列表,使得它们可在不复制的情况下增长。
图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
(tm_mgr tx,object obj){
word stm_word=GetSTMWord(obj);
if(!IsOwnedSTMWord(stm_word)){
entry->obj=obj;
entry->stm_word=stτn_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 DTMOpenForRead(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指示没有发生冲突:
·V1-该对象在事务持续期间的任一点处未被打开来更新。
·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==cur^snapshot){
//快照匹配:没有事务关闭该对象
if(!IsOwnedSTMWord(cur_stm_word)){
//V1:OK:快照未改变,没有冲突
}else if(GetOwnerFromSTMWord(cur_stm_word)==tx){
//V2:OK:在读取前由当前tx打开
//来更新
}else{
//V4:由另一tx打开来更新
Becomelnvalid(tx);
}
}else{
//快照失配:STM字上的慢速路径测试
word old_stm_word=SnapshotToWord(old_snapshot);
if(!IsOwnedSTMWord(old_stm_word)){
if(old_stτn_word==cur_stm_word){
//V1:OK:STM字在事务期间
//被扩大
}else if(!IsOwnedSTMWord(cur_stm_word)){
//V5:另一tx的冲突更新
Becomelnvalid(tx);
}else if(GetOwnerFromSTMWord(cur_stm_word)==tx){
//当前tx打开该对象来更新...
update_entry *update_entry=
GetEntryFromSTMWord(cur_stm_word);
if(update_entry->stm_word!=
SnapshotToWord(old_snapshot)){
//V5:...但是另一tx在当前tx打开该对象之前
//打开并关闭该对象来更新
//
Becomelnvalid(tx);
}else{
//V3:OK:没有另一tx的介入的访问
}
}else{
//V5:该对象由另一事务打开
Becomelnvalid(tx);
}
}else if(GetOwnerFromSTMWord(cur_stm_was)==tx){
//V2:OK:在读取之前由当前tx打开来更新
}else{
//V4:STM字不变,但是先前由
//另一事务打开来更新
Becomelnvalid(tx);
}
}
}
示例7
void CloseUpdatedObject(tm_mgr tx,object obj,update_entry
*entry){
word old_stm_word=entry->stm_word;
word new_stm_word=GetNextVersion(old_stm_word);
CloseSTMWord(obj,new_word);
}
图19c示出了对该列表结构的所得的更新,其中新版本号91被放置在该列表对象的首部。
可以观察到,随着对版本号有29个比特可用,可以获得大约500M的不同版本。所示的设计对于版本号溢出是安全的,只要在一运行的事务打开对象来读取时一版本号在同一对象中不被重复使用-允许读取事务成功提交而无需检测可能对该号码有大约500M的更新的A-B-A问题。
对于正确性,在一个实现中,这是通过(a)至少每500M事务执行无用信息收集一次,以及(b)在每次无用信息收集时确认运行的事务。读取对象日志中的条目仅当所记入日志的版本号匹配当前版本号时才有效:结果是每次无用信息收集“复位”500M事务的“时钟”而无需访问每一对象来更新其版本号。
5.运行时日志过滤
本节描述了利用概率性散列方案来过滤来自读取对象日志和撤消日志的重复以便过滤重复的运行时技术。日志过滤一般是有用的,这是因为a)日志可能占据大量空间,从而耗尽系统资源,以及b)一旦将一特定存储器位置记入日志为已被写入或读取,则无需进一步在日志中记录它。这是因为,在确认期间,所需的来自读取对象日志的唯一信息是事务之前该对象的STM快照,而所需的来自撤消日志的唯一信息是在事务之前更新的存储器位置的值。由于这不在事务内改变,因此对每一事务一给定存储器位置只需一个日志条目。
在第4节的实现中,不必过滤更新对象日志中的条目。这是因为DTMOpenForUpdate不允许在同一事务内对同一更新的对象首部创建重复的日志条目。在其它实现中,可能创建这种重复,并且因此可能过滤重复。
一般而言,过滤器支持两个操作。第一个操作是“过滤”操作,它在指定的字必须在过滤器中存在时返回真。如果指定的字可能在过滤器中不存在则返回假,并在该字的确不存在时将该字添加到过滤器。这一过滤器因而担当在搜索时容许假否定的概率集(即,它可在字实际上在过滤器中存在时声明该字不在过滤器中,但是不能在字实际上不在过滤器中时声明其在过滤器中)。第二个操作是“清除”操作,它移除过滤器中所有的字。
在软件事务性存储器(STM)的上下文中,过滤器可用于减少相同字的内容被写入STM保持的事务日志之一的次数。
5.2散列表过滤的示例
此处所描述的过滤方案使用结合表来概率性地检测对读取对象日志和撤消日志的重复日志记录请求。尽管此处所描述的实现参考了散列表,但是可以认识到,在替换实现中,过滤技术和系统可使用结合表的不同实现。一种实现使用了将地址的散列映射到与具有该散列的地址有关的最新近的日志记录操作的细节的按线程表。
可以注意到,在一个实现中,只需一个结合表来同时过滤读取对象日志和撤消日志。对读取对象日志的存储使用了该对象的首部字的地址,而对撤消日志的存储使用了所记入日志的字的地址。由于这些地址集是不相交的,因此单个表不会展示出读取对象和更新访问之间的冲突,且因此可用于两个日志。
图20示出了该表的设计。图20示出了被实现为散列表200的结合表。如图20所示,散列表2000中的每一条目包括存储器地址2020和交易号2030。条目是按照一系列槽号2010来组织的。
在一个实现中,标识用于一特定存储器地址的槽号的散列码通过将地址拆分成散列索引和标签来获得。由此,在这一实现中,散列函数只需使用来自字W的最低有效位中的几位来选择要在表中使用的槽S。字W中的位因此可被认为被拆分成两个部分:最低有效位是散列码,其用于标识要使用的槽,而其余的位用作唯一地标识地址的标签。例如,字0x1000具有标签1,槽0,而字0x1001具有标签1,槽1,字0x2000具有标签2,槽0,字0x2001具有标签2,槽1等等。在替换实现中,使用不同的散列方案。
另外,尽管散列表2000将事务号示为与存储器地址分离,但是在各种实现中,事务号诸如使用异或(XOR)运算与存储器地址组合。在一个实现中,使用异或运算因为它是相对快速的运算,并且可以被连续的异或撤消。在替换实现中,使用不同的记录事务号的方法,诸如用事务号来替换存储器地址中的低序位,或使用加法运算而非异或运算。这些是有用的是因为它们各自共享了这样的性质:对于散列成相同的散列码的两个地址a1和a2,以及两个事务号t1和t2,仅当a1=a2且t1=t2时op(a1,t1)才等于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技术中的各种实现。然后,这一集成并不是简单的。为示出这一问题,考虑以下示例:
atomic{
t1=new LargeTemporaryObject();
//计算E1
t2=new LargeTemporaryObject();
//计算E2
}
出于示例的目的,假设在E1和E2处执行的计算都足够复杂以使必须对其完成GC而不耗尽存储器。此外,假设绑定到t1的LargeTemporaryObject仅在E1中使用,类似地,绑定到t2的LargeTemporaryObject仅在E2中使用。如果在没有“原子”块的情况下执行,则一旦E1完成,t1占据的空间将被回收。
本示例不能用现有的事务性存储器系统和GC来执行。在这些系统中,将发生以下两个问题之一:
1.某些不知道TM的GC在发生GC时迫使所有存储器事务异常中止。在这些系统上,诸如E1和E2等计算从不能在原子块中执行。
2.其它不知道TM的GC迫使对象保留比此处的知道TM的GC的对象所保留的时间更长。在这些系统上,示例可成功执行,但是t1和t2将被保留到原子块的最后,即使GC在其间知道t1随后不需要的E2期间发生。
在一个实现中,这些问题由知道TM的GC解决,该GC(a)允许GC在线程正出于执行原子块的中间的同时发生,以及(b)允许GC恢复可确保是程序不需要的对象,而不论原子块是成功完成还是被重新执行。
在各种实现中,无用信息收集技术包括在用于标识在当前原子块内分配的对象的原子事务块的实现中使用的技术。各实现还包括用于标识STM的数据结构引用的哪些对象确保是程序不需要的技术。最后,GC实现包括用于标识TM的数据结构中的哪些条目对于程序的将来执行是不需要的技术。
尽管以下描述特别地依赖于以上所描述的系统,但是此处描述的实现不限于该设置;它们可用于其它形式的事务性存储器,可能包括硬件事务性存储器。
此处所描述的实现是参考令世界惊叹的跟踪无用信息收集器,例如标记-清扫无用信息收集器或复制无用信息收集器来描述的。然而,这是出于展示简明的目的,并且各实现不限于该设置;可使用已知的方法来将STM与其它无用信息收集技术,如世代无用信息收集、并发无用信息收集或并行无用信息收集集成。在一个实现中,STM与世代无用信息收集集成。
在较高层次,在令世界惊叹的跟踪GC的操作可被概括为以下过程。首先,停止应用程序中的所有应用程序线程(有时称为“增变线程(mutator thread)”)。接着,访问增变线程最初用于访问对象的每一个“根”,从而确保从这些根所引用的对象在收集之后被保留。(根包括处理器的运行增变线程的保存寄存器内容、线程的栈上的对象引用、以及这些线程可通过程序的静态字段来访问的对象引用)。因此,被保留的对象通常被称为“灰色”,而其余对象最初被称为“白色”。然后,对每一灰色对象,访问它所包含的对象引用。这些引用所标识的任何白色对象进而被标记为灰色,并且一旦访问了灰色对象中的所有引用,该对象被标记为黑色。重复此步骤,直到再没有灰色对象。留下的任何白色对象被认为是无用信息,并且可以使它们所占据的空间可被增变线程用于重新分配。最后,重启增变线程。在以下示例中,灰色对象将被称为“已访问”对象,而已知为白色的对象是“不可达的”。
在将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)、通信介质和以上任一种的组合。
此处所描述的技术可在诸如程序模块中所包括的在真实或虚拟目标处理器上的计算环境中执行的计算机可执行指令的一般上下文中描述。一般而言,程序模块包括执行特定任务或实现特定抽象数据类型的例程、程序、库、对象、类、组件、数据结构等。程序模块的功能可如各种实施例中所需地被组合或在程序模块之间拆分。用于程序模块的计算机可执行指令可在本地或分布式计算环境中执行。
出于表示的目的,详细描述使用了如“确定”、“生成”、“比较”和“写入”等术语来描述计算环境中的计算机操作。这些术语是对由计算机执行的操作的高级抽象,并且不应与人类执行的动作混淆。对应于这些术语的实际计算机操作可取决于实现而变化。
鉴于此处所描述的主题的许多可能的变型,要求保护落入所附权利要求书及其等效技术方案的范围和精神之内的所有这样的实施例作为本发明。