本文最后更新于 54 天前,如有失效请评论区留言。
实验背景
在进行 GATK HaplotypeCaller 变异检测时,对不同的内存配置和并行任务数进行了性能测试。本次实验基于一批全基因组测序数据的处理,旨在找到最优的资源分配策略。
gatk-4.6.2.0
实验配置
-------------------- A Bench.sh Script By Teddysun -------------------
Version : v2025-05-08
Usage : wget -qO- bench.sh | bash
----------------------------------------------------------------------
CPU Model : Intel(R) Core(TM) i9-14900KF
CPU Cores : 32 @ 800.000 MHz
CPU Cache : 36864 KB
AES-NI : ✓ Enabled
VM-x/AMD-V : ✓ Enabled
Total Disk : 3.6 TB (2.0 TB Used)
Total Mem : 125.6 GB (8.5 GB Used)
Total Swap : 8.0 GB (688.9 MB Used)
System uptime : 9 days, 21 hour 3 min
Load average : 2.01, 1.50, 1.26
OS : Ubuntu 24.04.3 LTS
Arch : x86_64 (64 Bit)
Kernel : 6.14.0-36-generic
TCP CC : cubic
Virtualization : Dedicated
IPv4/IPv6 : ✗ Offline / ✗ Offline
Region : No ISP detected
----------------------------------------------------------------------
I/O Speed(1st run) : 2.6 GB/s
I/O Speed(2nd run) : 2.9 GB/s
I/O Speed(3rd run) : 2.9 GB/s
I/O Speed(average) : 2867.2 MB/s
----------------------------------------------------------------------
初始配置
- 参数设置:
-Xmx20g -Xms8g(最大堆内存20GB,初始堆内存8GB) - 并行任务数: 20个
- CPU核心: 每任务分配4个核心(由
--native-pair-hmm-threads 4控制) - 任务资源占用: 每个任务实际内存占用约5%-6%
- 完成时间: 约26-28小时完成整批任务
后续配置(单个任务重跑)
- 参数设置:
-Xmx40g -Xms20g(最大堆内存40GB,初始堆内存20GB) - 并行任务数: 仅1个(重跑失败的任务)
- CPU核心: 保持4个核心
- 任务资源占用: 实际内存占用约10%-12%
- 完成时间: 约6小时完成单个任务
性能对比分析
时间效率对比
| 配置 | 每任务平均时间 | 总处理时间 | 效率提升 |
|---|---|---|---|
| 20g/8g × 20并行(已实际运行) | ~27小时/任务 | ~27小时/20任务 | 基准 |
| 40g/20g × 1任务(已实际运行) | 6小时/任务 | 6小时/任务 | 单任务约4.5倍 |
| 40g/20g × 10任务(推测) | 6小时/任务 | 6小时/10任务 | 总体约2倍 |
实际效率会增加一倍,因为相当于12个小时能跑完20个(单任务内存使用量加倍,并行任务数减半,但速度快了约4倍。即1/2*4=2)。
内存使用效率
- 低内存配置: 20GB最大内存,实际使用(5%-6% * 总128G)
- 高内存配置: 40GB最大内存,实际使用(10%-12% * 总128G)
关键发现
- 内存增加显著提升速度: 内存加倍(20g/8g →40g/20g)使单任务运行时间从约27小时减少到6小时
- 内存使用率较低: 即使在40g/20g配置下,实际内存使用率也仅10%-12% * 总128G(12g左右),表明HaplotypeCaller有进一步优化空间
- 并行效率瓶颈: 20个并行任务时,系统可能存在I/O竞争或CPU调度开销
推论与假设
基于观察数据,可以提出以下假设:
假设1:适度增加内存、减少并行数可能更高效
如果采用以下配置:
- 参数设置:
-Xmx40g -Xms20g - 并行任务数: 10个(而非20个)
- 预期效果:
- 每个任务运行时间可能从27小时减少到6小时左右(非线性的,考虑到cpu和内存占用率都较高时,可能出现性能下降)
- 总处理时间可能从减半(例如200个样品,原本20g/8g × 20并行两周,40g/20g × 10任务可能只需要一周)
假设2:资源竞争是关键限制因素
当20个任务并行时:
- I/O竞争: 大量BAM/GVCF文件同时读写
- CPU竞争: 虽然每任务4核心,但80个核心的总需求可能导致调度开销
- 内存碎片: JVM垃圾回收可能更频繁
重要注意事项
⚠️ 单个任务的性能不代表并行性能
关键提示: 单个任务在6小时内完成是在系统资源非常充足的情况下实现的:
- 没有其他并行任务竞争I/O
- CPU核心完全专用于该任务
- 内存分配无竞争
- 临时文件I/O无干扰
当并行10个相同配置的任务时:
- I/O瓶颈: 10个任务同时读写BAM/GVCF文件会显著降低I/O性能
- CPU调度开销: 40个核心(10×4)的调度比4个核心复杂(实际是10×2,因为这一步每个任务最多使用不到3个线程,单任务在1.5-2.5线程)
- 内存竞争: 虽然每个任务分配40GB,但实际使用约12GB,总需求约12GB,仍在系统容量内
- 网络/存储延迟: 并行任务会增加共享存储的访问延迟
⚠️ 非线性的扩展性
GATK HaplotypeCaller的性能扩展不是线性的:
- 从1个任务到10个任务,总时间不会减少到1/10
- 从20GB内存到40GB内存,速度提升可能不是2倍
- 最佳配置需要在资源利用和并行效率间平衡
推荐配置策略
基于本次实验,建议以下配置策略:
小规模集群(<128GB内存)
# 中等并行度,中等内存
-Xmx30g -Xms12g
并行任务数:8-12个
每任务线程:4个
大规模集群(>256GB内存)
# 较高内存,适度并行
-Xmx40g -Xms20g
并行任务数:10-15个
每任务线程:4-6个
关键优化建议
- 监控实际使用: 使用
top、htop或ps监控实际内存使用 - I/O优化: 将临时目录 (
--tmp-dir) 设置为高性能存储(如SSD) - 分批处理: 如果样本数超过50,考虑分批次运行
- 参数调优: 根据参考基因组大小调整
--max-reads-per-alignment-start
后续实验建议
为了更准确评估性能,建议进行控制实验:
- 固定样本测试: 使用相同5个样本,测试不同配置
- 资源监控: 记录CPU、内存、I/O使用率
- 缩放性测试: 测试1、5、10、15个并行任务的效率
- 内存阶梯测试: 测试20GB、30GB、40GB、50GB内存配置
结论
本次实验表明,GATK HaplotypeCaller 对内存配置敏感,适当增加内存可以显著减少运行时间。然而,单个任务的优化效果不能简单线性推广到并行环境。
在资源充足的情况下,单任务-Xmx40g -Xms20g 配置显著优于 -Xmx20g -Xms8g。但并行多个任务时,需要综合考虑系统总资源、I/O瓶颈和调度开销。
最佳实践: 根据具体硬件配置和样本数量,通过小规模测试确定最优的内存和并行配置,再进行大规模处理。
最后附上两个代码
#!/bin/bash
# 设置目录和文件
input_bam_dir="/home/vensin/workspace/snpcalling_wild/4.picard_dedup/markdup" # Picard去重后的BAM文件
output_dir="/home/data/5.gatk_haplotypecaller"
reference_genome="/home/vensin/workspace/snpcalling_wild/0.genome/SFZ.A.onlychr.fa"
gatk_path="/home/vensin/software/gatk-4.6.2.0/gatk"
# 创建输出目录
mkdir -p "$output_dir/gvcf" "$output_dir/tmp" "$output_dir/logs" "$output_dir/metrics"
# 设置日志文件
log_file="$output_dir/haplotypecaller_processing.log"
echo "GATK HaplotypeCaller processing started at $(date)" > "$log_file"
# 检查输入BAM文件数量
echo "=== 检查输入BAM文件 ===" >> "$log_file"
bam_count=$(find "$input_bam_dir" -name '*.markdup.bam' | wc -l)
echo "找到 $bam_count 个标记重复的BAM文件" >> "$log_file"
if [ "$bam_count" -eq 0 ]; then
echo "错误: 未找到标记重复的BAM文件" | tee -a "$log_file"
exit 1
fi
# 显示前几个样本
echo "BAM文件示例:" >> "$log_file"
find "$input_bam_dir" -name '*.markdup.bam' | head -5 | xargs -n 1 basename >> "$log_file"
# 检查参考基因组和相关文件
echo "=== 检查参考基因组和相关文件 ===" >> "$log_file"
if [ ! -f "$reference_genome" ]; then
echo "错误: 参考基因组文件不存在: $reference_genome" | tee -a "$log_file"
exit 1
fi
if [ ! -f "$reference_genome.fai" ]; then
echo "创建FASTA索引..." >> "$log_file"
samtools faidx "$reference_genome" >> "$log_file" 2>&1
fi
dict_file="${reference_genome%.*}.dict"
if [ ! -f "$dict_file" ]; then
echo "创建序列字典..." >> "$log_file"
$gatk_path CreateSequenceDictionary -R "$reference_genome" -O "$dict_file" >> "$log_file" 2>&1
fi
# 创建HaplotypeCaller处理脚本
HC_SCRIPT="$output_dir/haplotypecaller_process_sample.sh"
cat > "$HC_SCRIPT" << 'EOF'
#!/bin/bash
# HaplotypeCaller样本处理脚本
input_bam=$1
output_dir=$2
reference_genome=$3
gatk_path=$4
base_name=$(basename "$input_bam" .markdup.bam)
# 样本特定的日志
sample_log="$output_dir/logs/${base_name}.log"
echo "开始HaplotypeCaller处理样本: $base_name" > "$sample_log"
echo " 输入BAM: $(basename "$input_bam")" >> "$sample_log"
# 创建样本特定的临时目录
sample_tmp_dir="$output_dir/tmp/$base_name"
mkdir -p "$sample_tmp_dir"
# 输出文件
gvcf_output="$output_dir/gvcf/${base_name}.g.vcf.gz"
# 执行 GATK HaplotypeCaller
echo " 运行HaplotypeCaller..." >> "$sample_log"
$gatk_path --java-options "-Xmx20g -Xms8g" HaplotypeCaller \
-R "$reference_genome" \
-I "$input_bam" \
-O "$gvcf_output" \
-ERC GVCF \
--tmp-dir "$sample_tmp_dir" \
--native-pair-hmm-threads 4 \
--sample-ploidy 2 >> "$sample_log"
hc_exit_code=$?
if [ $hc_exit_code -eq 0 ]; then
echo " HaplotypeCaller 成功: $base_name" >> "$sample_log"
# 验证输出的GVCF文件
if [ -f "$gvcf_output" ]; then
echo " GVCF文件生成成功: $(basename "$gvcf_output")" >> "$sample_log"
echo "$base_name: 成功" >> "$output_dir/haplotypecaller_processing.log"
# 可选:收集一些基本统计
echo " 收集GVCF统计..." >> "$sample_log"
$gatk_path --java-options "-Xmx6g" CountVariants \
-V "$gvcf_output" 2>> "$sample_log" | head -5 >> "$sample_log"
else
echo " GVCF文件未生成: $base_name" >> "$sample_log"
echo "$base_name: GVCF文件缺失" >> "$output_dir/haplotypecaller_processing.log"
hc_exit_code=1
fi
else
echo " HaplotypeCaller 失败 (退出码: $hc_exit_code): $base_name" >> "$sample_log"
echo "$base_name: 失败" >> "$output_dir/haplotypecaller_processing.log"
fi
# 清理临时文件
rm -rf "$sample_tmp_dir"
echo " 处理完成: $base_name" >> "$sample_log"
exit $hc_exit_code
EOF
chmod +x "$HC_SCRIPT"
# 处理所有样本 - 使用 GNU Parallel 进行并行处理
echo "=== 开始GATK HaplotypeCaller处理 ===" >> "$log_file"
# 检查是否安装了 GNU Parallel
if command -v parallel >/dev/null 2>&1; then
echo "使用 GNU Parallel 并行处理 ..." >> "$log_file"
# 创建任务列表文件
task_file="$output_dir/task_list.txt"
find "$input_bam_dir" -name '*.markdup.bam' | sort > "$task_file"
# 使用 parallel 并行处理
cat "$task_file" | parallel -j 20 --joblog "$output_dir/parallel_jobs.log" \
"$HC_SCRIPT {} $output_dir $reference_genome $gatk_path"
# 计算成功数量
successful_jobs=$(grep -c ": 成功" "$log_file" 2>/dev/null || echo 0)
else
echo "GNU Parallel 未安装,使用串行处理..." >> "$log_file"
# 串行处理
success_count=0
processed_count=0
while IFS= read -r input_bam; do
if [ -n "$input_bam" ]; then
((processed_count++))
echo "处理样本 $processed_count/$bam_count: $(basename "$input_bam" .markdup.bam)" | tee -a "$log_file"
if "$HC_SCRIPT" "$input_bam" "$output_dir" "$reference_genome" "$gatk_path"; then
((success_count++))
fi
fi
done < <(find "$input_bam_dir" -name '*.markdup.bam' | sort)
successful_jobs=$success_count
fi
# 清理临时脚本
rm -f "$HC_SCRIPT"
# 统计结果
successful_jobs=${successful_jobs:-0}
bam_count=${bam_count:-0}
failed_count=$((bam_count - successful_jobs))
echo "处理完成: $successful_jobs/$bam_count 个样本成功处理" >> "$log_file"
# 显示处理摘要
echo "=== HaplotypeCaller处理摘要 ===" >> "$log_file"
echo "成功: $successful_jobs" >> "$log_file"
echo "失败: $failed_count" >> "$log_file"
# 生成GVCF文件统计汇总
echo "=== GVCF文件统计汇总 ===" >> "$log_file"
for gvcf_file in "$output_dir/gvcf"/*.g.vcf.gz; do
if [ -f "$gvcf_file" ]; then
sample_name=$(basename "$gvcf_file" .g.vcf.gz)
echo "样本: $sample_name" >> "$log_file"
# 检查文件完整性
if $gatk_path ValidateVariants -V "$gvcf_file" 2>/dev/null; then
echo " 文件验证: 通过" >> "$log_file"
# 获取变体数量统计
variant_count=$($gatk_path --java-options "-Xmx6g" CountVariants -V "$gvcf_file" 2>/dev/null | awk '/CountVariants/{getline; print $1}')
if [ -n "$variant_count" ]; then
echo " 变体数量: $variant_count" >> "$log_file"
fi
else
echo " 文件验证: 失败" >> "$log_file"
fi
echo "---" >> "$log_file"
fi
done
echo "GATK HaplotypeCaller processing completed at $(date)" >> "$log_file"
# 在终端显示最终结果
echo "GATK HaplotypeCaller处理完成!"
echo "成功处理: $successful_jobs/$bam_count 个样本"
echo "详细日志: $log_file"
echo "GVCF文件: $output_dir/gvcf/"
echo "各样本日志: $output_dir/logs/"
#!/bin/bash
# 设置目录和文件
input_bam_dir="/home/data/XNF_1" # Picard去重后的BAM文件
output_dir="/home/data/5.gatk_haplotypecaller_XNF_1"
reference_genome="/home/vensin/workspace/snpcalling_wild/0.genome/SFZ.A.onlychr.fa"
gatk_path="/home/vensin/software/gatk-4.6.2.0/gatk"
# 创建输出目录
mkdir -p "$output_dir/gvcf" "$output_dir/tmp" "$output_dir/logs" "$output_dir/metrics"
# 设置日志文件
log_file="$output_dir/haplotypecaller_processing.log"
echo "GATK HaplotypeCaller processing started at $(date)" > "$log_file"
# 检查输入BAM文件数量
echo "=== 检查输入BAM文件 ===" >> "$log_file"
bam_count=$(find "$input_bam_dir" -name '*.markdup.bam' | wc -l)
echo "找到 $bam_count 个标记重复的BAM文件" >> "$log_file"
if [ "$bam_count" -eq 0 ]; then
echo "错误: 未找到标记重复的BAM文件" | tee -a "$log_file"
exit 1
fi
# 显示前几个样本
echo "BAM文件示例:" >> "$log_file"
find "$input_bam_dir" -name '*.markdup.bam' | head -5 | xargs -n 1 basename >> "$log_file"
# 检查参考基因组和相关文件
echo "=== 检查参考基因组和相关文件 ===" >> "$log_file"
if [ ! -f "$reference_genome" ]; then
echo "错误: 参考基因组文件不存在: $reference_genome" | tee -a "$log_file"
exit 1
fi
if [ ! -f "$reference_genome.fai" ]; then
echo "创建FASTA索引..." >> "$log_file"
samtools faidx "$reference_genome" >> "$log_file" 2>&1
fi
dict_file="${reference_genome%.*}.dict"
if [ ! -f "$dict_file" ]; then
echo "创建序列字典..." >> "$log_file"
$gatk_path CreateSequenceDictionary -R "$reference_genome" -O "$dict_file" >> "$log_file" 2>&1
fi
# 创建HaplotypeCaller处理脚本
HC_SCRIPT="$output_dir/haplotypecaller_process_sample.sh"
cat > "$HC_SCRIPT" << 'EOF'
#!/bin/bash
# HaplotypeCaller样本处理脚本
input_bam=$1
output_dir=$2
reference_genome=$3
gatk_path=$4
base_name=$(basename "$input_bam" .markdup.bam)
# 样本特定的日志
sample_log="$output_dir/logs/${base_name}.log"
echo "开始HaplotypeCaller处理样本: $base_name" > "$sample_log"
echo " 输入BAM: $(basename "$input_bam")" >> "$sample_log"
# 创建样本特定的临时目录
sample_tmp_dir="$output_dir/tmp/$base_name"
mkdir -p "$sample_tmp_dir"
# 输出文件
gvcf_output="$output_dir/gvcf/${base_name}.g.vcf.gz"
# 执行 GATK HaplotypeCaller
echo " 运行HaplotypeCaller..." >> "$sample_log"
$gatk_path --java-options "-Xmx40g -Xms20g" HaplotypeCaller \
-R "$reference_genome" \
-I "$input_bam" \
-O "$gvcf_output" \
-ERC GVCF \
--tmp-dir "$sample_tmp_dir" \
--native-pair-hmm-threads 8 \
--sample-ploidy 2 >> "$sample_log"
hc_exit_code=$?
if [ $hc_exit_code -eq 0 ]; then
echo " HaplotypeCaller 成功: $base_name" >> "$sample_log"
# 验证输出的GVCF文件
if [ -f "$gvcf_output" ]; then
echo " GVCF文件生成成功: $(basename "$gvcf_output")" >> "$sample_log"
echo "$base_name: 成功" >> "$output_dir/haplotypecaller_processing.log"
# 可选:收集一些基本统计
echo " 收集GVCF统计..." >> "$sample_log"
$gatk_path --java-options "-Xmx6g" CountVariants \
-V "$gvcf_output" 2>> "$sample_log" | head -5 >> "$sample_log"
else
echo " GVCF文件未生成: $base_name" >> "$sample_log"
echo "$base_name: GVCF文件缺失" >> "$output_dir/haplotypecaller_processing.log"
hc_exit_code=1
fi
else
echo " HaplotypeCaller 失败 (退出码: $hc_exit_code): $base_name" >> "$sample_log"
echo "$base_name: 失败" >> "$output_dir/haplotypecaller_processing.log"
fi
# 清理临时文件
rm -rf "$sample_tmp_dir"
echo " 处理完成: $base_name" >> "$sample_log"
exit $hc_exit_code
EOF
chmod +x "$HC_SCRIPT"
# 处理所有样本 - 使用 GNU Parallel 进行并行处理
echo "=== 开始GATK HaplotypeCaller处理 ===" >> "$log_file"
# 检查是否安装了 GNU Parallel
if command -v parallel >/dev/null 2>&1; then
echo "使用 GNU Parallel 并行处理 ..." >> "$log_file"
# 创建任务列表文件
task_file="$output_dir/task_list.txt"
find "$input_bam_dir" -name '*.markdup.bam' | sort > "$task_file"
# 使用 parallel 并行处理
cat "$task_file" | parallel -j 15 --joblog "$output_dir/parallel_jobs.log" \
"$HC_SCRIPT {} $output_dir $reference_genome $gatk_path"
# 计算成功数量
successful_jobs=$(grep -c ": 成功" "$log_file" 2>/dev/null || echo 0)
else
echo "GNU Parallel 未安装,使用串行处理..." >> "$log_file"
# 串行处理
success_count=0
processed_count=0
while IFS= read -r input_bam; do
if [ -n "$input_bam" ]; then
((processed_count++))
echo "处理样本 $processed_count/$bam_count: $(basename "$input_bam" .markdup.bam)" | tee -a "$log_file"
if "$HC_SCRIPT" "$input_bam" "$output_dir" "$reference_genome" "$gatk_path"; then
((success_count++))
fi
fi
done < <(find "$input_bam_dir" -name '*.markdup.bam' | sort)
successful_jobs=$success_count
fi
# 清理临时脚本
rm -f "$HC_SCRIPT"
# 统计结果
successful_jobs=${successful_jobs:-0}
bam_count=${bam_count:-0}
failed_count=$((bam_count - successful_jobs))
echo "处理完成: $successful_jobs/$bam_count 个样本成功处理" >> "$log_file"
# 显示处理摘要
echo "=== HaplotypeCaller处理摘要 ===" >> "$log_file"
echo "成功: $successful_jobs" >> "$log_file"
echo "失败: $failed_count" >> "$log_file"
# 生成GVCF文件统计汇总
echo "=== GVCF文件统计汇总 ===" >> "$log_file"
for gvcf_file in "$output_dir/gvcf"/*.g.vcf.gz; do
if [ -f "$gvcf_file" ]; then
sample_name=$(basename "$gvcf_file" .g.vcf.gz)
echo "样本: $sample_name" >> "$log_file"
# 检查文件完整性
if $gatk_path ValidateVariants -V "$gvcf_file" 2>/dev/null; then
echo " 文件验证: 通过" >> "$log_file"
# 获取变体数量统计
variant_count=$($gatk_path --java-options "-Xmx6g" CountVariants -V "$gvcf_file" 2>/dev/null | awk '/CountVariants/{getline; print $1}')
if [ -n "$variant_count" ]; then
echo " 变体数量: $variant_count" >> "$log_file"
fi
else
echo " 文件验证: 失败" >> "$log_file"
fi
echo "---" >> "$log_file"
fi
done
echo "GATK HaplotypeCaller processing completed at $(date)" >> "$log_file"
# 在终端显示最终结果
echo "GATK HaplotypeCaller处理完成!"
echo "成功处理: $successful_jobs/$bam_count 个样本"
echo "详细日志: $log_file"
echo "GVCF文件: $output_dir/gvcf/"
echo "各样本日志: $output_dir/logs/"
记录时间:2025年12月8日
数据规模:平均约22×全基因组重测序数据
硬件环境:高性能计算集群
记录目的:供后续分析参考,优化GATK流程资源配置