欢迎加入开源鸿蒙PC社区:https://harmonypc.csdn.net/
欢迎在PC社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper

BitMagic 是一个 header-only 的 C++ 库,使用时包含头文件即可。这样的库看起来很适合作为跨平台适配的起点:不用先处理一串外部依赖,也没有必须安装的动态库。但 bmagic 7.13.4 在鸿蒙PC上的适配,主要时间花在了测试程序的构建、启动和长时间运行上。

这次适配没有修改上游源码,四项构建差异都通过 Makefile 已有变量处理。真正漫长的是验证:最后一轮分块测试从 2026 年 9 月 26 日持续到 10 月 3 日,按任务报告的起止时间约为七天八小时,其中一个测试块就运行了 94 小时 23 分钟。

本文依据这次任务记录复盘构建与验证过程,并补充一个在鸿蒙PC上重新编译运行的小示例。长时间压力测试的数据来自历史执行记录,小示例则在 2026 年 10 月 5 日重新实测。

1. BitMagic 能用来做什么

BitMagic 提供压缩位向量、稀疏向量和相关集合运算,可用于索引、检索和数据分析。以位向量为例,可以用置位的位置表示一组编号,再围绕这些编号做统计和集合处理。这类能力可以作为桌面程序的数据处理组件,但库本身不提供桌面图形界面。

本次适配使用 7.13.4 版本。它的公共头文件依赖 C++ 标准库和随源码提供的头文件,没有额外的第三方库依赖。任务中的目标环境如下:

项目环境
设备系统HarmonyOS PC
架构aarch64
内核版本输出HongMeng Kernel 1.12.0
C++ 编译器clang++ 15.0.4
示例编译目标aarch64-unknown-linux-ohos
适配版本bmagic 7.13.4

header-only 省去了构建独立库文件的步骤,但使用这些头文件的程序仍然需要编译和链接。上游的三套测试程序正好暴露了这两层的差异。

2. 一个带空格的内核版本,怎么变成编译错误

上游构建先探测系统,再选取平台配置。任务记录中,平台探测遇到了两个障碍:旧的探测逻辑不认识 OHOS,探测脚本创建的临时目录又在设备文件系统上出现写入权限问题。

这里没有继续修改探测脚本,而是使用上游 makefile.in 预留的 OSNAME 变量,显式选择 aarch64-linux-gnu 平台配置。这只是选择一份可复用的构建规则,并不表示设备变成了 GNU/Linux,实际编译器仍然面向 OHOS。

选中平台后,编译命令仍然出错。问题来自平台文件拼接 uname 输出的方式:它把系统名和内核版本组合成一个预处理宏,却没有处理版本字符串中的空格。

设备的 uname -r 输出是:

HongMeng Kernel 1.12.0

生成的参数因此出现了这样的内容:

-D__HarmonyOS_HongMeng Kernel 1_12_0

原本希望传给编译器一个宏,实际却拆成多个参数。Kernel 等内容被当作输入文件名,随即出现文件不存在的错误。只看错误尾部,很容易以为源码文件缺失;回到完整编译命令,才能发现它们来自内核版本字符串。

修复方式是覆盖 OS_VER,把平台标签固定为一个不含空格的参数:OS_VER=-D__HarmonyOS_HongMeng__。任务记录确认这个宏只是平台标签,没有对应的源码条件分支,因此不需要改动库的实现。

遇到类似错误,可以先查看 uname -r,再用 make -n 检查实际将要执行的命令。系统信息可以用于展示,但拼进编译参数时,必须考虑空格和参数边界。

3. 编译完成后,程序为什么一启动就崩溃

更隐蔽的问题出现在链接阶段。三套测试程序已经生成,却在启动后立即发生段错误,退出码为 139。任务记录中的一次失败只用了约 0.04 秒,还没有进入正常的压力测试流程。

诊断线索来自链接器警告:

ld.lld: warning: cannot find entry symbol xport-dynamic

xport-dynamic 并不是程序里的入口函数。上游平台配置使用了 -export-dynamic,在本次 OHOS 链接调用中,它被按入口参数 -e xport-dynamic 解析。继续检查 ELF 头,异常产物的入口地址为 0x0。

这时应先处理入口配置,不能直接把崩溃归因于位图算法。适配方案通过 COMMON_LDFLAGS=-rdynamic 覆盖原参数,由 clang++ 驱动传递导出动态符号的选项。历史记录中,重链后的入口地址恢复正常,程序可以启动。

这一结论针对本次工具链和调用方式,不意味着所有链接器都会同样解释该选项。复现类似故障时,应把链接警告、完整命令和 ELF 头放在一起看。

加上设备编译器的选择,四项差异最终对应以下变量:

变量本次设置用途
OSNAMEaarch64-linux-gnu选择上游已有平台规则
OS_VER-D__HarmonyOS_HongMeng__避免内核版本空格拆散参数
COMMON_LDFLAGS-rdynamic修正本次链接调用中的选项解释
CXX、CC、LDclang++、clang、clang++使用设备实际提供的编译器与链接驱动

这些变量已经足够表达本次构建差异,所以适配保留了上游源码,未额外维护源码补丁。

4. 测试能启动了,接下来却要按天等待

上游测试说明直接写着“runs for days”。它有三套独立测试程序:bmtest、bmtest64 和 ptest。前两套包含大量位图、序列化、稀疏向量等压力测试,第三套覆盖线程相关测试。

把它们编译出来,并不能证明这些测试已经通过。普通 CI 的执行窗口也无法容纳这次完整的压力测试。因此,任务在设备上额外使用上游已有选择器分块执行:先按 bmtest 的 13 个逻辑块和 bmtest64 的 9 个逻辑块组织,再把支持细分的耗时块拆开。

分块时需要保持调用点覆盖。比如一个选择器拆成多个子选择器,要确认原来会运行的测试仍然都能触达,不能因为子块跑得快就认为覆盖等价。每个执行块还需要独立工作目录、日志和完成标记,防止临时文件相互影响。

这里的续跑粒度是“已完成的块”。设备中断后可以跳过已经成功的块,但中断块仍要从头运行,并没有保存压力测试内部的计算状态。这个区别决定了长块仍然需要足够长的连续运行窗口。

任务记录中的几项实测耗时如下:

执行块实测耗时
bmtest -bvb141 小时 53 分钟
bmtest -csv022 小时 23 分钟
bmtest64 -csv54 小时 16 分钟
bmtest64 -bvo94 小时 23 分钟

这些时间反映本次设备与调度条件下的运行成本,不是性能基准。最后一个块于 2026 年 10 月 3 日 23:52:01 结束,记录的退出码为 0。

长测试还有一个容易误判的现象:日志很久不更新。任务中出现过长时间没有新输出、但进程 CPU 时间持续增加的情况。判断是否挂死,要同时查看进程状态、CPU 时间和输出变化;单凭日志安静,不能直接停止任务。

5. 229 这个数字是怎么算出来的

早期使用简单文本匹配统计函数调用,得到过 209。这个方法会漏掉带参数的调用,也不能正确处理注释和嵌套条件,后来已被弃用。

最终统计依据实际执行过的选择器,分析入口中的条件与调用点,再对覆盖并集去重:bmtest 为 142 个调用点,bmtest64 为 79 个调用点,ptest 为 8 个参数化子运行。任务报告按这套分项口径汇总为 229,记录 PASS 为 229、FAIL、SKIP 和 NOT_RUN 均为 0。

这组数字有明确边界:前两项是调用点,第三项是参数化子运行,所以它是任务报告的混合口径汇总,不能解释成 229 条同等规模的独立断言,更不能拿来比较不同项目的测试强度。

此外,包消费者测试有 19 条断言,单独记录通过。这里不把它们与上述 229 直接相加为统一的“用例总数”。消费者测试还修复过一个问题:Release 配置中的 NDEBUG 会让 assert 不再执行,因而增加了 -UNDEBUG 并核对实际构建结果。测试代码里写着断言,并不代表运行时一定检查了它。

6. 在鸿蒙PC上运行一个小示例

读者不必先跑几天压力测试,可以从上游 samples/bvsample01/sample1.cpp 入手。它用 {1, 2, 3} 初始化位向量,再设置几个位置,输出置位数量和索引,随后演示清空、修改和交换。

下面使用上游原始示例,源码遵循其随附的 Apache-2.0 许可。运行前需准备完整的 BitMagic 7.13.4 源码和可用的 clang++;SRC 是本次设备已有源码路径,换设备时应改成自己的实际目录。$HOME/ws/tmp 也需事先存在且可写。

(
    set -eu
    SRC=/storage/Users/currentUser/tmp/bmagic-7.13.4/src/BitMagic-7.13.4
    OUT=$(mktemp -d "$HOME/ws/tmp/bmagic-blog-20261005.XXXXXX")
    export TMPDIR="$OUT" TEMP="$OUT" TMP="$OUT"

    uname -a
    clang++ --version
    clang++ -std=c++17 -O2 -UNDEBUG \
        -I"$SRC/src" \
        "$SRC/samples/bvsample01/sample1.cpp" \
        -o "$OUT/bvsample01"
    "$OUT/bvsample01"
)

2026 年 10 月 5 日,在上述鸿蒙PC环境中重新编译并运行该示例,程序输出如下,退出码为 0:

1. bitcount: 3
2. bitcount: 6
1,2,3,10,100,1000000
3. bitcount: 0
4. bitcount: 3
5. bitcount: 2
6. bitcount: 2

输出可以逐步对照源码理解:初始有三个位置置位,增加三个后变成六个;清空后数量归零;再次设置三个位置、取消其中一个,数量变为二;最后交换两个位置,置位总数仍为二。示例末尾还用断言检查交换结果,编译时保留了这些断言。

这次小示例证明的是直接使用上游头文件的编译与运行,不是从制品仓安装 Conan 包的验证,也不能替代完整的压力测试。
鸿蒙PC真机运行截图

7. 这次适配留下的经验

本次最值得复用的做法,是先找构建系统已有的配置入口。上游提供了平台名、编译宏和链接参数的覆盖方式,就可以把平台差异集中在配方里,减少后续维护补丁的负担。但每一项覆盖都需要说明作用,尤其不能把“选用 Linux 命名的规则”写成“鸿蒙等同于 Linux”。

验证则需要从程序启动继续往下走。链接器警告解释了入口异常,运行结果验证了修复,长时间测试检查了更广的功能路径。每个阶段回答的问题不同,任意一个“成功”都不应替代后面的检查。

长任务还需要从一开始就考虑证据保存:日志采用追加方式,记录每次尝试的时间和退出码,完成标记与具体执行块对应,原始证据另行备份。原始日志丢失后,即使清单还在,复核能力也会下降。

对准备适配类似 C++ 库的开发者,可以先运行一个小消费者确认基本使用,再检查上游测试入口和实际耗时。遇到启动崩溃,保留链接警告并检查 ELF;遇到长时间无输出,先观察进程;整理测试报告时,把程序数、调用点、参数实例和断言分开写。这样下一位复现者才能知道哪些结果可以直接核对,哪些部分还需要继续验证。

资料与源码

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐