本文记录使用命令 OHOS_ARCH=aarch64 OHOS_ABI=arm64-v8a sh ./create-hnp.sh 构建 Stream 内存带宽基准测试工具的完整过程,包括环境、构建链路、关键日志、常见问题与解决方案、产物验证与重建方法,便于复现与运维。

📖 Stream 简介

STREAM(Sustainable Memory Bandwidth in High Performance Computers)是一个经典的内存带宽基准测试工具,用于测量系统的可持续内存带宽。它通过执行四种简单的向量操作(Copy、Scale、Add、Triad)来评估内存子系统的性能,是 HPC(高性能计算)领域广泛使用的基准测试工具。

🎯 Stream 的作用与重要性

Stream 是内存性能评估的核心工具,提供了:

  • 内存带宽测试:测量系统的内存带宽性能
  • 性能基准:提供标准的内存性能基准测试
  • 系统评估:评估内存子系统的性能
  • 性能对比:对比不同系统的内存性能
  • 优化参考:为应用优化提供内存性能参考

🔧 Stream 核心特性

1. 内存带宽测试
  • Copy 操作a[i] = b[i] - 测试内存复制带宽
  • Scale 操作a[i] = q * b[i] - 测试内存缩放带宽
  • Add 操作a[i] = b[i] + c[i] - 测试内存加法带宽
  • Triad 操作a[i] = b[i] + q * c[i] - 测试内存三元运算带宽
2. 测试模式
  • 单线程测试:单线程内存带宽测试
  • 多线程测试:多线程并行内存带宽测试
  • 可配置数组大小:可配置测试数组大小
  • 可配置迭代次数:可配置测试迭代次数
3. 结果输出
  • 性能指标:输出每种操作的带宽(MB/s)
  • 性能报告:生成详细的性能测试报告
  • 对比分析:对比不同测试结果
  • 可视化:支持结果可视化
4. 应用场景
  • 内存性能评估:评估系统的内存带宽性能
  • 性能基准:建立内存性能基准测试
  • 系统对比:对比不同系统的内存性能
  • 优化参考:为应用优化提供参考
  • 硬件测试:测试内存硬件性能

🚀 构建入口与环境

  • 📝 执行命令OHOS_ARCH=aarch64 OHOS_ABI=arm64-v8a sh ./create-hnp.sh
  • 🔧 入口脚本create-hnp.sh
    • 检查必需的环境变量 OHOS_ARCHOHOS_ABI
    • 导出 LC_CTYPETOOL_HOMEOHOS_SDK_HOME
    • 执行 make -C build-hnp
  • 📦 顶层构建build-hnp/Makefile
    • PKGS 变量定义需要构建的包列表(包含 stream
    • 通过 check-pkgs 机制自动检测 PKGS 变化并触发重新构建
    • 自动合并 external-hnp 目录下的外部 HNP 包
    • base.hnp 依赖所有包的 .stamp 和外部 HNP 包
    • 总目标 all: copy,打包 base.hnp 并拷贝到 entry/hnp/$(OHOS_ABI)

⚙️ Stream 包的构建配置

  • 📁 包目录build-hnp/stream/Makefile
    • 继承通用规则:include ../utils/Makefrag
    • 源地址:
      • https://github.com/jeffhammond/STREAM/archive/6703f7504a38a8da96b353cadafa64d3c2d7a2d3.zip(标准 STREAM)
      • https://github.com/jlinford/stream/archive/3b92a251022474a293cefc1291bc4fe7b78b62e7.zip(增强版 Stream,支持 SVE)
    • 版本:两个不同的实现版本
  • 🔨 构建流程
    1. 下载两个源码包(从 GitHub,ZIP 格式)
    2. 解压第一个包到 temp/STREAM-6703f7504a38a8da96b353cadafa64d3c2d7a2d3
    3. 编译 stream_c.exe(标准版本,静态链接)
    4. 解压第二个包到 temp/stream-3b92a251022474a293cefc1291bc4fe7b78b62e7
    5. 编译 stream_zfill-acle.exe(SVE 优化版本)
    6. 编译 stream_zfill.exe(zfill 版本)
    7. 复制所有可执行文件到 build/bin
    8. 复制到 ../sysroot
  • ⚙️ 编译参数
    • stream_c.exe
      • CC=$(OHOS_ARCH)-unknown-linux-ohos-clang
      • CFLAGS="-O3 -static -DNDEBUG -DSTREAM_ARRAY_SIZE=120000000 -DNTIMES=200"
      • LDFLAGS="-static"
    • stream_zfill-acle.exe
      • CC=$(OHOS_ARCH)-unknown-linux-ohos-clang
      • CFLAGS="-march=armv8.5-a+sve -O3 -static -DNDEBUG -DSTREAM_ARRAY_SIZE=120000000 -DNTIMES=200"
      • LDFLAGS="-static -lomp"
      • KERNEL=zfill-acle
    • stream_zfill.exe
      • CC=$(OHOS_ARCH)-unknown-linux-ohos-clang
      • CFLAGS="-O3 -static -DNDEBUG -DSTREAM_ARRAY_SIZE=120000000 -DNTIMES=200"
      • LDFLAGS="-static -lomp"
      • KERNEL=zfill
  • 🔧 通用工具链与路径build-hnp/utils/Makefrag
    • CC/CXX/LD/AR/RANLIB/... 均指向 OHOS SDK 的 LLVM 工具链
    • 下载支持多镜像回退:wgetcurl,主镜像失败时自动尝试备用镜像

📋 关键日志与过程节点

  • 📥 下载与解包
    • 从 GitHub 下载两个 ZIP 源码包
    • 完成解压并进入相应的源码目录
    • 下载规则支持多镜像回退:wgetcurl 兜底
  • 🔨 编译阶段
    • stream_c.exe
      • 使用标准 C 编译器编译
      • 静态链接,优化级别 O3
      • 数组大小:120000000,迭代次数:200
    • stream_zfill-acle.exe
      • 使用 SVE(Scalable Vector Extension)指令集编译
      • 架构:armv8.5-a+sve
      • 链接 OpenMP 库(-lomp
    • stream_zfill.exe
      • 使用 zfill 内核编译
      • 链接 OpenMP 库(-lomp
  • 📦 打包
    • 完成 base.hnp 重打包,拷贝产物到 entry/hnp/arm64-v8a/
    • Stream 工具已成功打包到 base.hnp

✅ 产物验证

📦 检查打包文件

ls build-hnp/base.hnp  # 应存在
ls entry/hnp/arm64-v8a/*.hnp  # 应包含 base.hnp 与 base-public.hnp

🔍 检查二进制文件

# 检查 Stream 可执行文件
ls -lh build-hnp/sysroot/bin/stream*.exe
file build-hnp/sysroot/bin/stream*.exe

✅ 构建验证结果

  • ✅ Stream 可执行文件已安装:
    • stream_c.exe (811K) - 标准 STREAM 基准测试
    • stream_zfill-acle.exe (3.6M) - SVE 优化版本
    • stream_zfill.exe (3.6M) - zfill 版本
  • ✅ 文件类型:ELF 64-bit LSB executable, ARM aarch64
  • ✅ 静态链接:statically linked
  • ✅ 包含调试信息:with debug_info, not stripped
  • ✅ 已打包到 base.hnp

💻 终端中执行的示例命令

💾 Stream 基本使用

1. 基本运行
# 运行标准 STREAM 基准测试
stream_c.exe

# 运行 SVE 优化版本
stream_zfill-acle.exe

# 运行 zfill 版本
stream_zfill.exe

# 运行并保存结果到文件
stream_c.exe > stream_result.txt 2>&1

# 运行并显示详细信息
stream_c.exe 2>&1 | tee stream_output.txt

image-20251126151424918

2. 理解输出结果
# STREAM 输出示例:
# STREAM version $Revision: 5.10 $
# 
# This system uses 8 bytes per DOUBLE PRECISION word.
# 
# Array size = 120000000, Offset = 0
# Total memory required = 2746.6 MB.
# Each test will run 200 times.
# The *best* time for each test is used.
# 
# Function    Best Rate MB/s  Avg time     Min time     Max time
# Copy:           12345.6     0.0155       0.0155       0.0156
# Scale:          12345.6     0.0155       0.0155       0.0156
# Add:            12345.6     0.0155       0.0155       0.0156
# Triad:          12345.6     0.0155       0.0155       0.0156

# 提取性能数据
stream_c.exe | grep -E "(Copy|Scale|Add|Triad)" | awk '{print $2}'

# 提取最佳带宽
stream_c.exe | grep "Copy:" | awk '{print $2}'

# 提取最小时间
stream_c.exe | grep "Copy:" | awk '{print $5}'

image-20251126151951819

3. 性能对比
# 对比不同版本的性能
echo "=== Standard STREAM ===" > comparison.txt
stream_c.exe >> comparison.txt
echo "" >> comparison.txt
echo "=== SVE Optimized ===" >> comparison.txt
stream_zfill-acle.exe >> comparison.txt
echo "" >> comparison.txt
echo "=== Zfill Version ===" >> comparison.txt
stream_zfill.exe >> comparison.txt

# 提取并对比性能数据
for exe in stream_c.exe stream_zfill-acle.exe stream_zfill.exe; do
    echo "=== $exe ==="
    $exe | grep -E "(Copy|Scale|Add|Triad)"
done

# 生成性能对比报告
{
    echo "STREAM Benchmark Comparison"
    echo "=========================="
    echo ""
    for exe in stream_c.exe stream_zfill-acle.exe stream_zfill.exe; do
        echo "=== $exe ==="
        $exe | grep -E "(Copy|Scale|Add|Triad)"
        echo ""
    done
} > benchmark_comparison.txt
4. 批量测试
# 运行多次测试并取平均值
for i in {1..10}; do
    echo "Run $i:"
    stream_c.exe | grep "Copy:" | awk '{print $2}'
done | awk '{sum+=$1; count++} END {print "Average:", sum/count, "MB/s"}'

# 运行多次测试并保存所有结果
for i in {1..10}; do
    echo "=== Run $i ===" >> all_results.txt
    stream_c.exe >> all_results.txt
    echo "" >> all_results.txt
done

# 统计性能数据
stream_c.exe | grep -E "(Copy|Scale|Add|Triad)" | awk '{print $2}' | awk '{sum+=$1; count++} END {print "Average Bandwidth:", sum/count, "MB/s"}'
5. 性能分析
# 提取所有操作的性能数据
stream_c.exe | grep -E "(Copy|Scale|Add|Triad)" | awk '{print $1, $2}' > performance_data.txt

# 计算平均带宽
stream_c.exe | grep -E "(Copy|Scale|Add|Triad)" | awk '{sum+=$2; count++} END {print "Average:", sum/count, "MB/s"}'

# 找出最佳性能的操作
stream_c.exe | grep -E "(Copy|Scale|Add|Triad)" | sort -k2 -rn | head -1

# 找出最差性能的操作
stream_c.exe | grep -E "(Copy|Scale|Add|Triad)" | sort -k2 -n | head -1

# 提取性能指标到 CSV
echo "Operation,Bandwidth_MB_s,Avg_Time,Min_Time,Max_Time" > stream_results.csv
stream_c.exe | grep -E "(Copy|Scale|Add|Triad)" | awk '{print $1","$2","$3","$5","$6}' >> stream_results.csv
6. 实际应用示例
# 运行基准测试并保存结果
stream_c.exe > baseline.txt

# 进行系统优化后再次测试
stream_c.exe > optimized.txt

# 对比优化前后的性能
diff baseline.txt optimized.txt

# 使用脚本自动化测试
#!/bin/bash
echo "STREAM Benchmark Test"
echo "===================="
echo "Date: $(date)"
echo ""
echo "Standard STREAM:"
stream_c.exe
echo ""
echo "SVE Optimized:"
stream_zfill-acle.exe
echo ""
echo "Zfill Version:"
stream_zfill.exe

# 在后台运行测试
nohup stream_c.exe > stream_background.log 2>&1 &

# 监控测试进程
watch -n 1 'ps aux | grep stream'

# 使用 perf 分析性能
perf record -g stream_c.exe
perf report

# 使用 strace 跟踪系统调用
strace -o stream_trace.txt stream_c.exe

# 测试不同数组大小(需要重新编译)
# 修改 Makefile 中的 STREAM_ARRAY_SIZE 值

# 测试不同迭代次数(需要重新编译)
# 修改 Makefile 中的 NTIMES 值

# 运行压力测试
for i in {1..100}; do
    echo "Iteration $i"
    stream_c.exe > /dev/null
done

# 生成性能报告
{
    echo "STREAM Benchmark Report"
    echo "======================"
    echo "Date: $(date)"
    echo "System: $(uname -a)"
    echo ""
    echo "Standard STREAM Results:"
    stream_c.exe
    echo ""
    echo "SVE Optimized Results:"
    stream_zfill-acle.exe
    echo ""
    echo "Zfill Version Results:"
    stream_zfill.exe
} > stream_report.txt

# 提取关键性能指标
stream_c.exe | awk '/^Copy:/ {print "Copy Bandwidth:", $2, "MB/s"}'
stream_c.exe | awk '/^Scale:/ {print "Scale Bandwidth:", $2, "MB/s"}'
stream_c.exe | awk '/^Add:/ {print "Add Bandwidth:", $2, "MB/s"}'
stream_c.exe | awk '/^Triad:/ {print "Triad Bandwidth:", $2, "MB/s"}'

# 计算总体性能
stream_c.exe | grep -E "(Copy|Scale|Add|Triad)" | awk '{sum+=$2; count++} END {print "Overall Average:", sum/count, "MB/s"}'

# 格式化输出为表格
stream_c.exe | grep -E "(Copy|Scale|Add|Triad)" | awk '{printf "%-10s %10.2f MB/s\n", $1, $2}'

# 生成 JSON 格式结果
stream_c.exe | grep -E "(Copy|Scale|Add|Triad)" | awk '{printf "{\"operation\":\"%s\",\"bandwidth\":%s,\"avg_time\":%s,\"min_time\":%s,\"max_time\":%s}\n", $1, $2, $3, $5, $6}'
7. 性能监控
# 运行测试并监控系统资源
stream_c.exe &
STREAM_PID=$!
top -p $STREAM_PID
wait $STREAM_PID

# 监控内存使用
stream_c.exe &
STREAM_PID=$!
while kill -0 $STREAM_PID 2>/dev/null; do
    ps -p $STREAM_PID -o rss,vsz
    sleep 1
done

# 监控 CPU 使用
stream_c.exe &
STREAM_PID=$!
while kill -0 $STREAM_PID 2>/dev/null; do
    ps -p $STREAM_PID -o %cpu,%mem
    sleep 1
done

# 使用 htop 监控
htop -p $(pgrep stream)
8. 结果处理与分析
# 解析结果并提取关键指标
parse_stream_results() {
    local file=$1
    echo "=== Results from $file ==="
    grep -E "(Copy|Scale|Add|Triad)" "$file" | while read line; do
        op=$(echo "$line" | awk '{print $1}')
        bw=$(echo "$line" | awk '{print $2}')
        echo "$op: $bw MB/s"
    done
}

# 使用函数解析结果
parse_stream_results stream_result.txt

# 对比多个结果文件
compare_results() {
    for file in "$@"; do
        echo "=== $file ==="
        parse_stream_results "$file"
        echo ""
    done
}

# 对比结果
compare_results baseline.txt optimized.txt

# 生成性能图表数据
stream_c.exe | grep -E "(Copy|Scale|Add|Triad)" | awk '{print $1, $2}' > chart_data.txt

# 计算性能提升百分比
baseline=$(./stream_c.exe | grep "Copy:" | awk '{print $2}')
optimized=$(./stream_zfill-acle.exe | grep "Copy:" | awk '{print $2}')
improvement=$(echo "scale=2; ($optimized - $baseline) / $baseline * 100" | bc)
echo "Performance improvement: $improvement%"

🧪 功能验证脚本

#!/bin/bash
# Stream 工具验证脚本

STREAM_BIN="build-hnp/sysroot/bin"

echo "=== Stream 工具验证 ==="

# 检查可执行文件
echo ""
echo "=== 可执行文件验证 ==="
for exe in stream_c.exe stream_zfill-acle.exe stream_zfill.exe; do
    if [ -f "$STREAM_BIN/$exe" ]; then
        echo "✓ $exe: 存在"
        file "$STREAM_BIN/$exe"
        echo "  文件大小: $(ls -lh "$STREAM_BIN/$exe" | awk '{print $5}')"
        echo "  架构信息: $(file "$STREAM_BIN/$exe" | grep -o "ARM aarch64")"
        echo "  链接类型: $(file "$STREAM_BIN/$exe" | grep -o "statically linked\|dynamically linked")"
    else
        echo "✗ $exe: 缺失"
    fi
done

# 测试基本功能(在目标设备上)
echo ""
echo "=== 功能测试(需要在目标设备上运行)==="
echo "测试标准 STREAM:"
echo "  $STREAM_BIN/stream_c.exe"
echo ""
echo "测试 SVE 优化版本:"
echo "  $STREAM_BIN/stream_zfill-acle.exe"
echo ""
echo "测试 zfill 版本:"
echo "  $STREAM_BIN/stream_zfill.exe"
echo ""
echo "运行并保存结果:"
echo "  $STREAM_BIN/stream_c.exe > stream_result.txt 2>&1"

🐛 构建过程遇到的问题及解决方法

❌ 问题 1:GitHub 下载失败

  • 🔍 症状:无法从 GitHub 下载源码包
  • 🔎 原因:网络问题或 GitHub 访问受限
  • ✅ 解决方法
    • 手动下载源码包放置到 build-hnp/stream/download/ 目录
    • 使用代理或镜像站点下载
    • 通用下载规则支持 curl 兜底
    • 位置:build-hnp/stream/Makefile:20-26

❌ 问题 2:ZIP 解压失败

  • 🔍 症状:解压 ZIP 文件失败
  • 🔎 原因:ZIP 文件损坏或 unzip 工具不可用
  • ✅ 解决方法
    • 检查 ZIP 文件完整性
    • 确保系统安装了 unzip 工具
    • 可以手动解压 ZIP 文件到相应的 temp 目录
    • 位置:build-hnp/stream/Makefile:6,10

❌ 问题 3:OpenMP 库链接失败(stream_zfill 版本)

  • 🔍 症状:链接错误,无法找到 libomp 或 OpenMP 符号
  • 🔎 原因:OpenMP 库未安装或路径不正确
  • ✅ 解决方法
    • 确保 OpenMP 库在 sysroot/lib 中可用
    • 检查 -lomp 链接标志是否正确
    • 如果不需要 OpenMP 支持,可以移除 -lomp 链接标志
    • 位置:build-hnp/stream/Makefile:11,14

❌ 问题 4:SVE 指令集不支持

  • 🔍 症状:编译错误,SVE 指令无法识别
  • 🔎 原因:目标平台不支持 SVE 指令集或编译器不支持
  • ✅ 解决方法
    • 检查目标平台是否支持 SVE(ARMv8.5+)
    • 如果平台不支持,可以移除 -march=armv8.5-a+sve 标志
    • 使用标准版本 stream_c.exe 作为替代
    • 位置:build-hnp/stream/Makefile:11

❌ 问题 5:静态链接失败

  • 🔍 症状:静态链接错误,无法找到静态库
  • 🔎 原因:静态库未安装或路径不正确
  • ✅ 解决方法
    • 确保静态库在 sysroot/lib 中可用
    • 检查 -static 链接标志是否正确
    • 如果静态链接失败,可以尝试动态链接
    • 位置:build-hnp/stream/Makefile:7,11,14

❌ 问题 6:数组大小过大导致内存不足

  • 🔍 症状:运行时内存不足或段错误
  • 🔎 原因STREAM_ARRAY_SIZE 设置过大,超出可用内存
  • ✅ 解决方法
    • 减小 STREAM_ARRAY_SIZE 值(当前为 120000000)
    • 根据系统可用内存调整数组大小
    • 位置:build-hnp/stream/Makefile:7,11,14

❌ 问题 7:Makefile 目标不存在

  • 🔍 症状make stream_c.exemake KERNEL=zfill-acle all 失败
  • 🔎 原因:上游 Makefile 不支持这些目标或参数
  • ✅ 解决方法
    • 检查上游 Makefile 支持的目标
    • 根据实际 Makefile 调整构建命令
    • 可能需要直接编译源文件而不是使用 Makefile
    • 位置:build-hnp/stream/Makefile:7,11,14

❌ 问题 8:架构不支持

  • 🔍 症状Unsupported OHOS_ARCH=
  • 🔎 原因:传入的架构不在支持列表中
  • ✅ 解决方法
    • 确保传入支持架构(aarch64x86_64
    • 位置:build-hnp/Makefile:1-49

🔄 重建与扩展

  • 🔧 重建单包

    make -C build-hnp rebuild-stream  # 触发子包重新编译并刷新 .stamp
    
  • 🧹 清理

    make -C build-hnp clean  # 清理 sysroot、所有 .stamp 和 PKGS_MARKER
    
  • 📦 扩展:Stream 是内存性能评估的核心工具,被广泛用于内存带宽测试和性能基准

  • 🔄 自动重建机制

    • 修改 PKGS 后,check-pkgs 会自动检测变化并触发重新构建
    • 新增外部 HNP 包到 external-hnp 目录后,会自动合并到 base.hnp

💡 实践建议

  • 🔧 构建配置:根据目标平台选择是否启用 SVE 优化
  • 🚀 内存配置:根据系统可用内存调整 STREAM_ARRAY_SIZE
  • 📦 性能测试:Stream 为内存性能测试提供了强大的工具支持
  • 🔗 版本选择:根据需求选择合适的版本(标准版、SVE 优化版、zfill 版)
  • 🌐 结果分析:使用多个版本进行对比测试以获得更全面的性能数据

📝 结论与建议

  • ✅ 本次已在 aarch64 环境下完成 Stream 内存带宽基准测试工具的交叉编译与打包,三个版本的可执行文件已安装到 sysroot 并纳入 HNP 包。
  • 💡 为保证构建稳定
    • 使用直接编译方式,不依赖复杂的构建系统
    • 静态链接确保可执行文件独立运行
    • 确保通过 create-hnp.sh 触发构建以获得完整环境变量
    • 利用 check-pkgs 机制自动检测包列表变化,无需手动清理
    • Stream 为内存性能测试提供了强大的工具支持
    • 常见陷阱包括 GitHub 下载失败、OpenMP 库链接失败、SVE 指令集不支持;当前已通过构建配置和参数处理
    • 建议根据系统可用内存调整数组大小,并根据需求选择合适的版本进行测试
    • 已完成 aarch64 目标下 Stream 的交叉编译与打包,工具进入 sysroot 并纳入 HNP 包

📚 以上为 Stream 构建的深度解读与实践记录。

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐