java 创建图形选择组件_用Java创建高度可扩展的组件
java 创建图形选择组件
随着多核处理器成为主流,应用程序开发人员面临巨大压力,要求他们编写正确的高度可扩展的应用程序以利用基础硬件。 此外,必须移植旧版应用程序才能在新架构上运行。 确保应用程序可伸缩性的有效方法是使用高度可伸缩的组件来构建应用程序。 例如,在各种应用程序中,java.util.concurrent.ConcurrentHashMap可以替换同步的HashTable,并使应用程序更具可伸缩性。 因此,提供一组可以直接引入应用程序以引入并行性的高度可扩展的构建块将非常有用。
作为Amino Library Project的一部分,我们创建了一组高度可扩展的并发Java组件。 在本文中,我们将描述在创建此开源库时使用的一些想法。
总览
作为软件工程师,我们必须并行处理我们的应用程序,以使其能够在多核计算机上很好地扩展。 并行化应用程序的一种方法是将它们分解为子任务,其中每个子任务在执行时都与应用程序的其他子任务进行通信和同步。 这通常称为基于任务的并行性。 我们可以根据子任务的通信模式对应用程序进行分类。
- 令人尴尬的并行–这些应用程序的子任务之间从未或很少相互通信。 通常,每个任务仅操纵其私有数据。 这些类型的问题的一些示例是:
- 蒙特卡洛模拟程序
- 快速排序功能,易于使用fork / join模式对其进行并行化。 当我们处理令人尴尬的并行应用程序时,很容易获得良好的可伸缩性。
- 粗粒度和细粒度–这些是子任务需要相互通信的应用程序。 根据子任务之间的通信频率,应用程序表现出粗粒度或细粒度的并行性。 生产者/消费者问题就是这类计算的一个例子。 生产者生成驱动消费者的数据。 从生产者向消费者发送数据需要进行通信。 与尴尬的并行应用程序相比,处理需要在子任务之间频繁通信的应用程序时,要获得良好的可伸缩性要困难得多。
针对可伸缩性进行了优化的组件对于解决这些难题非常有帮助。 例如,如果有一个高度可扩展的队列组件,则使生产者/消费者规模很好地变得相对容易。 在本文中,我们提供了几种通用技术来使软件组件更好地扩展。
可扩展性简介
应用程序概要分析是开发过程的重要方面。 完成概要分析以了解应用程序的执行特征。 概要文件数据通常用于提高应用程序的性能和可伸缩性。 大多数探查器遵循简单的执行模式。 有一个检测阶段。 在此阶段,根据所需信息的类型,将检测不同级别的计算堆栈。 硬件计数器可用于监视硬件事件。 可以通过检测各种OS事件来监视操作系统(OS)。 如果存在虚拟机(VM)作为计算堆栈的一部分,则可以对其进行检测以获取对VM中事件的访问。 最终,可以对应用程序进行检测以从实际应用程序中获取事件。 好的分析器可以即时检测所有内容,而无需重新编译应用程序。 大多数分析器通常会检测VM和应用程序层。 在执行检测应用程序期间,将生成跟踪。 跟踪捕获各种有趣的信息,例如功能执行时间,资源利用率,硬件性能计数器,锁使用信息,系统时间,用户时间等。根据探查器的类型,可以在写入跟踪信息之前使用跟踪信息进行计算。到文件系统。 最后,有一个显示组件,用于可视化跟踪数据。
针对并发应用程序的探查器需要提供有关线程,锁争用和同步点的详细信息。 我们在Amino性能分析工作中使用的一些重要的探查器包括:
- Java Lock Monitor(JLM)– Java开发人员使用此工具来了解其应用程序中锁的用法。 在运行时,检测组件收集各种锁统计信息,并且此信息用于确定锁争用。 该工具可以生成包含有关锁和争用的详细信息的表。 IBM Java锁分析器(JLA)是可用于提供锁信息的另一个工具。 它提供与JLA类似的信息,除了它具有图形用户界面以显示锁和竞争数据外。 x86和Power都支持这两种工具。
- THOR –该工具可用于对并发Java应用程序进行详细的性能分析。 它从硬件到应用程序层对整个执行堆栈进行了深入分析。 信息是从堆栈的所有四个层(硬件,操作系统,jvm和应用程序)收集的。 向用户提供有关锁争用以及代码中争用发生的位置,瓶颈,内核之间的线程迁移以及由于争用导致的性能下降的信息。 x86和Power支持该工具。
- AIX性能工具– IBM的AIX操作系统附带了一组低级性能分析和调试工具。 一些重要的方面包括:
- 简单性能锁定分析工具(SPLAT)–这是AIX操作系统提供的锁定分析工具。 该工具可以从命令行运行,并且可以分析各种内核级锁。 它还可以分析和显示用户级别锁(读/写和互斥锁)的争用。 必须在启用跟踪选项的情况下执行应用程序执行。 跟踪数据由SPLAT工具使用。
- XProfiler –这是一个基于X-Windows的分析工具,用于对C应用程序进行功能级别的分析。 查看器可用于在用户代码中显示计算密集型功能。 要使用XProfiler,必须使用特殊标志(-pg选项)编译代码。
- prof,tprof和gprof –各种prof命令可用于分析用户应用程序。 prof命令可用于获取应用程序中调用的所有外部符号和例程的平面配置文件。 gprof是prof的超集,可用于获取应用程序的调用图配置文件。 最后,tprof是用于获取有关应用程序的宏和微概要分析信息的命令。 tprof可用于获取有关各个指令,子例程,线程和进程的时序数据。 它可用于获取用户模式例程,库例程,Java类,Java方法,内核例程,内核扩展等的处理器使用率。
减少热点
传统的性能调整方法。 也就是说,那些用于顺序应用程序的应用程序适用于并行应用程序。 在完成使用传统方法可以完成的工作之后,检查流程中多个线程如何访问共享数据可能会很有用。 通过使用诸如JLM或JLA之类的工具,我们可以发现在共享资源访问期间线程之间的竞争方式。
例如,如果一个应用程序有100个线程,而所有这些线程都需要从java.util.HashTable中放入/获取元素,则由于线程争用,此应用程序的性能将不佳。 每个线程都需要等待很长时间才能访问HashTable。
如果我们在上面的示例中使用JLM之类的工具,它将显示与哈希表对象关联的监视器很热。 摆脱这一热点的一种方法是用具有相同基本功能的高度可扩展的组件替换哈希表。 在这种特殊情况下,我们可以轻松地用java.util.concurrent.ConcurrentHashMap替换哈希表。 ConcurrentHashMap将其内部数据结构分为多个段。 在应用程序中用ConcurrentHashMap替换HashTable允许线程竞争多个子组件,而不是单个大型组件。 使用ConcurrentHashMap的应用程序中的争用率很可能会比以前低很多。
还有另一种降低访问共享组件的竞争率的方法。 如果组件的操作具有相反的语义(如堆栈上的推入和弹出),则这两个操作可以成功执行而无需接触中央数据结构。 该技术由Danny Hendler,Nir Shavit和Lena Yerushalmi引入,并已针对Amino libray中的某些数据结构实现。 图1显示了该方法在堆栈中的性能改进。
图1.性能比较:EBStack和TreiberStack
使用无锁/无等待算法
传统上,普遍使用基于锁的方法来确保共享数据的一致性以及对关键部分的独占访问。 锁相对容易理解,但是基于锁的算法会带来很多挑战。 锁引入的一些众所周知的问题是死锁,活动锁,优先级倒置,锁争用等。锁争用往往会降低组件和算法的可伸缩性。
如今,无锁和无等待算法已有二十多年的历史。 他们被认为是解决大多数与锁相关的问题的方法。 这类算法允许并发更新共享数据结构,而无需使用任何锁定机制。 它不仅解决了与在代码中使用锁相关的一些基本问题,而且还有助于创建具有良好可伸缩性的算法。 最初,这些无锁和无等待的算法仅具有理论上的意义。 但是,随着算法社区的进步和新型硬件支持的发展,无锁技术已在诸如操作系统内核,虚拟机,线程库等实际产品中得到越来越多的使用。
从1.5版开始,JDK中实现了无锁算法,例如ConcurrentLinkedQueue,AtomicInteger等。它们的可伸缩性通常比相应的基于锁的组件更好。 当我们开始实现作为Amino库的一部分的新组件时,我们选择以研究社区的最新非阻塞算法为基础。 这使得Amino数据结构具有高度可伸缩性和高效性。 有时,尤其是在内核数较少的情况下,无锁数据结构的吞吐量可能比基于锁的数据结构更差,但通常它们具有更好的吞吐量特性。
尽可能减少CAS操作
从JDK 1.5开始,Doug Lea开发的java.util.concurrent包中提供了一个高效的无锁FIFO队列。 队列算法基于对单链接列表的并发操作,最初是由Michael和Scott开发的。 它的出队操作需要一个比较和交换(CAS),而入队操作需要两个成功的CAS才能完成。 对于入队和出队操作,这似乎很简单。 但是分析(见图2)表明,CAS占用了执行时间的很大一部分,连续两次成功CAS的排队要求增加了失败的机会。 在现代多处理器上,即使是成功的CAS操作也要比加载或存储花费更多的时间来完成,因为它们需要排他所有权和刷新处理器的写缓冲区。
图2. CAS操作的执行时间
从图2中可以看出,CAS操作的执行时间百分比为46.08%,几乎占整个执行时间的一半。 配置文件数据存在一指令延迟。 实际时间在以下指令“ SETE AL”上累积。
Mozes和Shavit(MoS队列)的无锁队列算法提供了减少队列中CAS操作数量的新颖方法。 关键思想是用一个“乐观的”双向链接列表替换其单指针链接列表,该指针使用昂贵的比较交换(CAS)操作插入,该指针的指针使用简单的存储进行更新,但可以修复如果发生错误的事件排序,从而导致双向链表变得不一致。
我们已经完成了一个基准测试,以比较JSR166y和Mos-Queue的ConcurrentLinkedQueue的性能。 结果如图3所示。您可以看到,对于大量线程,Mos-Queue的性能优于JDK ConcurrentLinkedQueue。
图3.性能比较:ConcurrentLinkedQueue和Mos-Queue
Mos-Queue的更详细说明可以在Mozes和Shavit的论文“无锁FIFO队列的乐观方法”中找到。
减少内存分配
Java虚拟机(JVM)具有非常强大和高效的内存管理系统。 JVM中使用的垃圾收集器(GC)可以压缩活动对象,因此GC之后堆中不存在任何漏洞。 由于GC之后可用空间将是连续的,因此内存分配仅比增加指针复杂。
如果不占用大量内存,则对于多线程应用程序也是如此。 JVM为每个线程分配一个线程本地缓冲区。 在分配内存期间,首先使用线程本地缓冲区。 。 线程局部缓冲区用完之前,不会触摸全局堆。
以线程本地方式分配内存的功能对应用程序性能非常有帮助,但是如果分配太频繁,它将无法正常工作。 根据我们的经验,如果分配频率很高,线程本地缓冲区将很快耗尽。
如果循环中需要临时对象,则ThreadLocal类可能会有所帮助。 如果将临时对象存储在ThreadLocal对象中,则可以在每次循环迭代中重用它。 尽管ThreadLocal类具有相关的开销,但大多数情况下,它比在循环内进行频繁分配要好。
图4.性能比较:ThreadLocal和Allocation
结论
在本文中,我们介绍了在Java中创建高度可扩展的组件的几个重要原则。 通常,这些原则通常是有帮助的,但是它们不能代替仔细的测试和性能调整。
资源资源
- JLA ,IBM Java锁分析器
- Maged。的“ 基于CAS的共享双端队列无锁算法 ”。 这是一种无锁双端队列的高效算法。
- Michael和Scott所著的“ 简单,快速,实用的非阻塞和阻塞并发队列算法 ”。 这是一种高效的无锁队列算法。
- Mozes和Shavit的“ 乐观的无锁FIFO队列方法 ”。 它是另一种高效的无锁队列算法。
- 在“ Amino网站项目”中 ,获取Amino项目网站的来源。
- 浏览技术书店,获取有关这些主题和其他技术主题的书籍。
- 《可伸缩的无锁堆栈算法》,Danny Hendler,Nir Shavit和Lena Yerushalmi,SPAA 2004,第206-215页。
关于作者
Zhi Gan是IBM中国开发实验室的软件工程师,获得博士学位后加入IBM。 上海交通大学计算机安全专业。 Gan博士在SOA(面向服务的体系结构),AOP(面向方面的编程)和Eclipse方面拥有丰富的经验。 他目前的重点是Java / C ++并行软件开发。
Raja Das是IBM Software Group的软件架构师。 目前,他正在开发用于多核/多核系统的库和框架。 之前,他是WebSphere®Partner Gateway产品架构师。 Das博士的兴趣包括编程语言,并行软件和系统。
戴小军是IBM中国开发实验室的软件工程师,在获得中国科学院软件研究所的计算机科学博士学位后,他加入了IBM。 他在敏捷方法论和编程语言方面拥有丰富的经验。 戴先生目前的工作重点是并发编程和多核平台。
java 创建图形选择组件