Go、Java、C++,下一代测序工具开发谁更强?
简介
背景介绍
我们有一个成熟的多线程框架:elPrep,用于在测序流水线中准备SAM和BAM文件。为了获得良好的性能,我们的软件体系结构希望一次通过一个SAM/BAM文件执行多个准备步骤,并尽可能地将测序数据保留在主存储器中。与其他SAM/BAM工具类似,elPrep中堆的内存管理是一项复杂的任务,而且在近期的开发中,这个问题成为了该项目实现编程语言中严重的生产力瓶颈。因此,我们研究了三种可以替代的编程语言:首先是Go和Java(使用并发以及并行垃圾回收器),还有C++ 17(使用引用计数来处理大量堆对象)。我们用这三种语言重新实现了elPrep,并分别测试了运行时的性能和内存使用。
结果
Go实现的性能最佳,可在运行时性能和内存使用之间达到最佳平衡。尽管Java基准报告的运行时间比Go快一些,但运行Java的内存使用量明显偏高。C++ 17的运行速度明显慢于Go和Java,而且使用的内存却比Go还要多。我们的分析表明,对于我们这种情况来说,与引用计数相比,通过并发以及并行垃圾回收管理大量对象的效果更好。
结论
根据我们的基准测试结果,我们选择Go作为新的elPrep实现语言,而且我们认为Go是开发其他处理SAM/BAM数据生物信息学工具的理想选择。
背景介绍
序列比对/映射格式(SAM / BAM)是生物信息学界存储映射测序数据的标准。分析SAM/BAM文件的工具有很多。Broad和Sanger研究所开发的SAMtools、Picard以及Genome Analysis Toolkit(GATK)软件包被视为SAM/BAM文件许多操作的参考实现。这些操作包括读取排序、标记聚合酶链反应和光学复制、重新校准基质量得分、插入缺失重新排列以及各种过滤选项。
很多替代软件包专门提供这些操作的优化,这些软件包或提供替代算法,或使用并行化、分布或其他特定于实现语言(通常是C、C++或Java)的优化技术。
我们开发的elPrep是一个开源的多线程框架,用于在测序流水线中处理SAM/BAM文件,专门为优化计算性能而设计。它可以代替SAMtools、Picard和GATK实现的许多操作,而且还会产生相同的结果。elPrep允许用户通过一个命令在一个流水线中指定任意组合的SAM/BAM操作。然后,elPrep独特的软件体系结构可确保无论你指定了多少操作,运行这样的流水线时只需传递一次SAM/BAM文件。该框架能够合并和并行执行操作,明显加快流水线的整体执行速度。
虽然我们并没有将精力集中在优化单个SAM/BAM操作上,但结果表明,我们合并操作的方法更优。例如,与使用GATK4相比,elPrep执行4个步骤的广泛最佳实践的速度提高了13倍,整个基因组数据的处理速度提高了7.4倍,而耗费的计算资源则更少。
大多数编程语言都包含类似的通过显式或隐式分配内存存储堆对象的方法,堆对象与堆栈值不同,并不局限于函数或方法调用的生命周期。然而,各个编程语言在如何重新分配堆对象的内存方面有很大不同。
简单来说,主要有三种方法:手动管理内存必须在程序源代码中显式释放内存(例如,在C中调用free)。垃圾回收内存由运行时库中单独的组件(称为垃圾收集器)自动管理。在任意时间点,它都需要遍历对象图,以确定正在运行的程序仍可直接或间接访问哪些对象,并回收不可访问的对象占用的内存。使用这种方法时不必明确地针对对象生命期进行建模,并且可以在程序中更自由地传递指针。大多数垃圾回收器实现都会中断正在运行的程序,然后在垃圾回收完成后继续执行,并使用顺序算法执行对象图遍历。
然而,Java和Go所采用的高级实现技术可以在运行程序的同时遍历对象图,尽可能减少中断,并使用多线程并行算法加速现代多核处理器上的垃圾收集。引用计数通过维护每个堆对象的引用计数来管理内存。每当发生指针分配时,引用计数就会根据每个对象引用的指针数目相应的增加或减少。每当引用计数降至零时,就释放对应的对象。
elPrep最初(直到2.6版)是使用Common Lisp编程语言实现的。大多数现有的Common Lisp实现都使用会中断应用程序的顺序垃圾收集器。为了获得良好的性能,我们有必要明确控制垃圾收集器运行的频率和时间,以避免不必要地中断主程序,尤其是在并行阶段。因此,我们还必须避免不必要的内存分配,并尽可能重用已分配的内存,以减少垃圾收集器运行的次数。
然而,我们最近在尝试为elPrep添加更多功能(例如光学重复标记、基本质量得分重新校准等)时,需要为这些新步骤分配额外的内存,于是情况更加复杂,并且保持内存分配和垃圾回收检查成为了严重的生产力瓶颈。因此,为了继续开发elPrep并实现良好的性能,我们开始寻找其他拥有不同的内存管理方法的编程语言。
现有关于比较编程语言及其实现性能的文献通常都会使用特定的算法或内核,而根本不关心这些语言是否涵盖特定领域,如生物信息学、经济学或数值计算,还是只与编程有关的一般编程语言。仅有一篇文章考虑了并行算法。比较编程语言性能的在线资源也将重点放在特定的算法或内核上。elPrep的性能不仅来自并行排序或并发重复标记等步骤的高效并行算法,还源于将这些步骤整理成单通道、多线程流水线的整体软件架构。
由于现有的文献未涵盖此类软件体系结构,因此在本文中我们进行了一番研究。
elPrep是一个开放式软件框架,允许流水线中不同功能步骤的任意组合,例如重复标记、读取排序、替换读取组等;另外,elPrep还提供了编写第三方工具的功能步骤。这种开放性导致很难在程序运行期间准确地确定分配对象的生存周期。众所周知,在开发此类软件框架时,手动管理内存会导致生产力极其低下。例如,IBM的旧金山项目,从手动管理内存的C++转换到拥有垃圾回收的Java后生产力提高了大约300%。其他处理SAM/BAM文件的开放式软件框架包括GATK4、Picard以及htsjdk。
因此,手动管理内存对elPrep来说并不实际,而且并发、并行的垃圾回收和引用计数是唯一的选择。我们希望选择能够得到社区长期支持的成熟编程语言,最后我们发现Java和Go是唯一支持并发以及并行垃圾回收的编程语言,而C++则采用引用计数。
我们的研究包括使用C++ 17、Go和Java中重新实现elPrep,并对运行时的性能和内存使用进行基准测试。这些都是非常成熟的编程语言,从某种意义上说,它们完全支持各种调用的常见准备流水线工作,包括读取排序、重复标记和其他一些常用步骤。尽管elPrep的这三种重新实现仅支持有限的功能集,但在每种情况下,我们都可以通过额外的努力来完善软件体系结构,以支持elPrep 2.6版及更高版本的所有功能。
结果
我们使用elPrep的软件体系结构,以选定的三种编程语言运行了一条常见的准备流水线工作,结果表明Go实现的性能最佳,其次是Java实现,最后是C++ 17实现。
为了确认这个结果,我们针对全基因组测序数据集执行了五步准备流水线。该准备流水线包括以下步骤:
-
通过排序读取调整顺序 -
删除未映射的读取 -
标记重复读取 -
替换读取组 -
重新排序和过滤序列字典
-
Go:29.33GBh -
Java:38.63GBh -
C++ 17:44.26 GBh
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-
字符串去重复选项可以在垃圾回收期间识别内容相同的字符串,然后通过让这些字符串共享同一个基础字符数组来消除冗余。由于SAM/BAM文件中的大部分读取数据都由字符串表示,因此使用这个选项似乎有很大的好处。
-
使用“MinFreeHeap”和“MaxFreeHeap”选项配置垃圾收集后可用堆空间的最小和最大百分比,以最小化堆的大小。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
更多精彩推荐
☞
☞
☞
☞
你点的每个“在看”,我都认真当成了喜欢