「翻译」在生物信息学中使用 GNU-Parallel
原文出处:https://www.danielecook.com/using-gnu-parallel-for-bioinformatics/
GNU Parallel[1] 是一个用于加速生信分析不可或缺的一个工具。它允许你非常简单地对命令并行化处理。下面我将介绍一些如何使用它以及如何将它应用于生信。
很多高性能计算平台节点已经预先安装了它。你可以从 homebrew[2] 或其他包管理器找到和安装它。
基本用法
让我们从一个简单的例子开始:
seq 1 5 | parallel -j 4 echo
这里我们 (1) 打印了数字 1 到 5,且 (2) 将该序列数据通过管道传进了 parallel
命令。我们提供了一个命令 echo
,它将通过 -j=4
的选项指定进行并行化。我们可以通过添加 --dry-run
打印将要运行的命令。
seq 1 5 | parallel --dry-run -j 4 echo
echo 3
echo 4
echo 5
echo 2
echo 1
结果是乱序的!这是并行化的本质:不是所有的任务都会花费相同的时间,所以有的结束的早,有的结束的晚,因此输出顺序并不一致。我们可以使用 -k
选项强制程序执行“先入先出”准则。让我们看一下下面的结果:
seq 1 5 | parallel -j 4 -k echo
1
2
3
4
5
像其他任何命令一样,我们可以将输出保存到文件中:
seq 1 5 | parallel -j 4 -k echo > out.txt
-j
为了让 GNU Parllel 工作,你需要一个多核 CPU。指定超过所拥有的核心数会让性能变得糟糕。因此,调节 -j
选项以便于命令更好地工作是非常重要的。
幸运地是,parallel
运行你通过 -j
指定计算占有的 CPU 比例或相对数量。
parallel -j 100% # 使用 100% 的核心数
parallel -j -1 # 使用比所有核心数少 1 个的核心数
parallel -j +1 # 使用比所有核心数多 1 个的核心数
使用 :::
传递参数
使用 :::
指定并行指定的命令参数(列表来源)。
parallel -j 4 -k echo ::: `seq 1 5`
「注意」,上面这种情况能够传递的参数数量是有限的,通过管道传递参数或像下面一样通过文件传递参数可能更好:
seq 1 5 | parallel -j 4 -k echo
使用 ::::
传递文件内的参数
For large argument lists you can specify a file with a list of arguments. Specify a file of arguments (one per line) using ::::
.
如果参数列表很大,你可以通过文件指定,文件每一行对应要并行的一个参数:
parallel -j 4 -k echo :::: my_args.txt
使用 `
默认 parallel
假定参数放在输入命令的结尾,但是你可以通过 {}
显式地指定:
parallel --dry-run -j 4 -k echo "{} "<-- a number"" ::: `seq 1 5`
echo "1 <-- a number"
echo "2 <-- a number"
echo "3 <-- a number"
echo "4 <-- a number"
echo "5 <-- a number"
注意上面我们使用转移符号输出了双引号。
组合
你可以组合 :::
和 :::
来添加额外的参数,然后它们会生成所有可能的组合。这对测试有不同参数组合的命令非常有用:
parallel --dry-run -k -j 4 Rscript run_analysis.R {1} {2} ::: `seq 1 2` ::: A B C
Rscript run_analysis.R 1 A
Rscript run_analysis.R 1 B
Rscript run_analysis.R 1 C
Rscript run_analysis.R 2 A
Rscript run_analysis.R 2 B
Rscript run_analysis.R 2 C
并行化函数
在一些情况下,你想要执行一系列的命令。例如,下面的代码计算了输入DNA序列互补配对的碱基计数:
echo "ATTA" | tr ATCG TAGC |
python -c "import sys; o=sys.stdin.read().strip(); print(o, o.count('T'), o.count('G'), o.count('C'), o.count('A'))"
这个命令有 2 个操作。但我们可以将它整合为 'one-liner':创建一个 bash 函数,导出它,然后使用它作为输入:
function count_nts {
# $1 is the first argument passed to the function
echo $1 | tr ATCG TAGC |
python -c "import sys; o=sys.stdin.read().strip(); print(o, o.count('T'), o.count('G'), o.count('C'), o.count('A'))"
}
# Use the `-f` flag to export functions
export -f count_nts
parallel -j 4 count_nts ::: TAAT TTT AAAAT GCGCAT | tr ' ' 't'
有了上面的基础,让我们看看如何使用它加速生信分析。
使用 GNU Parallel 进行 Variant Calling
当处理 BAMs 或 VCFs 时,你可以并行处理所有的染色体。大多数变异检测软件或注释工具允许你通过指定区间一次处理一个染色体。这允许我们使用「拆分-应用-组合」策略到该分析中。
从 BAM 中分割染色体
chrom_list=`samtools idxstats in.bam | cut -f 1 | grep -v '*'`
# For c. elegans you can would see the following 7
# I
# II
# III
# IV
# V
# X
# MtDNA
我们可以创建一个 bash 函数:
function bam_chromosomes {
samtools idxstats $1 | cut -f 1 | grep -v '*'
}
应用操作到每一条染色体
下面是处理代码:
#!/bin/bash
genome=path/to/genome.fa
export genome # This is critical!
function parallel_call {
bcftools mpileup
--fasta-ref ${genome}
--regions $2
--output-type u
$1 |
bcftools call --multiallelic-caller
--variants-only
--output-type u - > ${1/.bam/}.$2.bcf
}
export -f parallel_call
chrom_set=`bam_chromosomes test.bam`
parallel --verbose -j 4 parallel_call sample_A.bam ::: ${chrom_set}
一些重要的注意事项:
- 你必须导出
export
所有并行化函数中使用到的变量,例如上面的genome
。 -
--output-type=u
是出于效率考虑。 - Finally, I use a variable substitution to remove the extension from the bam and to generate a <sample>.<chromsome>.bcf filename:
组合变异检测结果
一旦我们完成工作,接着我们使用 bash 数组和组合所有结合并将其廉洁为单个文件。
# Generate an array of the resulting files
# to be concatenated.
sample_name="sample_A"
set -- `echo $chrom_set | tr "n" " "`
set -- "${@/#/${sample_name}.}" && set -- "${@/%/.bcf}"
# This will generate a list of the output files:
# sample_A.I.bcf sample_B.II.bcf etc.
set -- "${@/#/test.}" && set -- "${@/%/.bcf}"
# Output compressed result
bcftools concat $@ --output-type b > $sample_name.bcf
# Remove intermediate files
rm $@
为了确保所有中间文件被删除,这里使用了bash trap[3]。
总结
GNU Parallel 可以极大提高简单并行场景任务处理效率。虽然需要编写额外的代码用于处理拆分和组合两步,但这可以得到极大的效率提升。
Reference
[1]
GNU Parallel: https://www.gnu.org/software/parallel/
[2]
homebrew: https://www.danielecook.com/using-gnu-parallel-for-bioinformatics/brew.sh
[3]
bash trap: http://redsymbol.net/articles/bash-exit-traps/
- javaSE之线程联合
- ASM 翻译系列第三十一弹:了解ASM文件的空间分配
- 备份重于一切:远离“Gitlab删库事件”,QBackup是你的最佳选择!
- FFMPEG指令
- Gitlab删库事件回顾,备份手段还停留在“原始社会”?
- ASM 翻译系列第三十二弹:自制数据抽取小工具
- WordPress集成SendCloud邮件代发,规避SMTP泄漏网站主机真实IP的风险
- ASM 翻译系列第三十三弹:REQUIRED_MIRROR_FREE_MB的含义
- 放弃Python转向Go语言:这9大理由就够了 !(附代码)
- CCKiller:Linux轻量级CC攻击防御工具,秒级检查、自动拉黑和释放
- 利用HSTS安全协议柔性解决全站HTTPS的兼容性问题
- Nginx内容替换模块http_substitutions_filter_module及实用案例分享
- libmemcached编译安装报错解决记录
- 解决网站静态缓存后WP-PostViews插件不计数的问题
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- i++和++i傻傻分不清楚?这里给你最清楚的解答
- android APT 使用
- Flutter异步编程async与await的基本使用
- 教大家一个万能PPT图片排版技巧,太赞了!
- 重复读取 HttpServletRequest 中 InputStream 的方法
- 测试面试题集锦(三)| 计算机网络和数据库篇(附答案)
- 关于Scikit-Learn你(也许)不知道的10件事
- 技术天地 | CSS-in-JS:一个充满争议的技术方案
- 安全研究 | 通过域名劫持实现Azure DevOps账户劫持
- 一款针对DLL劫持的恶意DLL生成器
- AuthMatrix:一款针对Web应用和服务的认证安全检测BurpSuite工具
- KITT-Lite:基于Python实现的轻量级命令行渗透测试工具集
- 一次KimSuky攻击事件分析
- OpenCV中如何使用滚动条动态调整参数
- 一文读懂「分布式架构」