# mem_alloc_bench

两个纯 POSIX C 的匿名内存工具, 单文件、零依赖, 同一份源码可在
**iOS**(越狱设备) / **HarmonyOS** / macOS / Linux 上编译运行:

| 源码 | 功能 |
|---|---|
| `mem_stress.c` | 按入参大小 mmap 匿名页并逐页触页提交, 进行**内存加压**; 可选周期性重触对抗压缩回收, 可选保持时长后释放 |
| `mem_bench.c`  | 按入参大小申请匿名页, 分轮统计 **mmap / 填充(memset) / munmap** 耗时, 输出 avg/min/max 与吞吐 |

## 用法

### mem_stress — 内存加压

```
usage: mem_stress [-f] [-k sec] [-t sec] [-qv] <size>

  <size>    bytes of anonymous memory to commit
            e.g. 268435456 | 100K | 512M | 1500M | 2G | 1T
            (binary units: K=1024, M=1024^2, G=1024^3; B/iB suffix ok)
  -f        full fill: memset the whole region (default: 1 byte per page)
  -k sec    re-touch all pages every <sec> while holding, to defeat
            compressor/zram reclaim (0 = off, default off)
  -t sec    hold duration after touch, then release
            (default: hold until SIGINT/SIGTERM)
  -q        quiet (suppress progress lines)
  -v        verbose progress
```

```console
$ ./mem_stress 512M -t 2
alloc  : 536870912 bytes (512.00 MiB), 32768 page(s) x 16384 B, mmap 0.040 ms
touch  :  10% (    51.20 MiB) elapsed 0.01 s
...
touch  :  99% (   506.88 MiB) elapsed 0.09 s
touch  : touched 32768 pages (512.00 MiB) in 0.092 s [5580.1 MiB/s, 357.1K pages/s]
rss    : resident 229.52 MiB, phys_footprint 513.22 MiB
hold   : 2.000 s
release: munmap ok, total 2.120 s, peak rss 233.16 MiB
```

要点:

- 默认**每页写 1 字节**触发缺页提交物理内存 (加压的标准姿势); `-f` 改为整块 memset。
- 默认**一直持有**直到 `Ctrl-C` / SIGTERM; `-t` 指定保持秒数。
- `-k sec` 周期性向所有页写入新值: 迫使被 XNU compressor / Linux zram 压缩的页
  解压回 RAM、干净页重新变脏, 用于长时间维持真实压力。
- iOS/macOS 上额外打印 `phys_footprint` (jetsam 的判定口径, = 驻留脏页 + 已压缩脏页)。
  上面真实样例里 RSS 229 MiB < footprint 513 MiB, 就是压缩器已经吃掉一部分页的
  直接证据 —— 加压时看 footprint 才是真实压力。

### mem_bench — mmap / memset 耗时统计

```
usage: mem_bench [-r n] [-m mode] [-q] <size>

  <size>    bytes of anonymous memory per run
            e.g. 268435456 | 100K | 512M | 2G (binary units, B/iB suffix ok)
  -r n      repeat runs (default 3)
  -m mode   fill mode after mmap (default memset):
              memset  memset the whole region (faults + bandwidth)
              touch   write 1 byte per page (first-touch fault cost, ns/page)
              none    mmap only (mapping setup/teardown cost)
  -q        quiet: per-run lines suppressed
```

```console
$ ./mem_bench 512M -r 3
target : 536870912 bytes (512.00 MiB), 32768 page(s) x 16384 B, mode=memset, runs=3
run 1/3: mmap=      3.0 us | fill(memset)=    82.261 ms (  6221.3 MiB/s) | unmap=   8.328 ms | rss=  513.44 MiB
run 2/3: mmap=      5.0 us | fill(memset)=    55.465 ms (  9231.9 MiB/s) | unmap=   5.724 ms | rss=  512.94 MiB
run 3/3: mmap=     12.0 us | fill(memset)=    42.753 ms ( 11975.5 MiB/s) | unmap=   5.822 ms | rss=  512.94 MiB
---- summary (3 runs, 512.00 MiB, memset mode) ----
mmap   : avg      7.667 us | min      3.000 us | max     12.000 us
fill   : avg     60.160 ms | min     42.753 ms | max     82.261 ms
unmap  : avg      6.658 ms | min      5.724 ms | max      8.328 ms
memset throughput: avg 9143.6 MiB/s over 3 run(s)
peak rss: 512.78 MiB
```

要点:

- mmap 本身只是建立映射 (µs 级); 真正的成本在填充阶段 (缺页 + 写入),
  两者分开计时, 恰好回答"申请"与"提交"各自的耗时。
- `-m touch` 每页只写 1 字节, 输出 **ns/page** —— 首次触页缺页的单页成本,
  可直接对比 iOS (16K 页) 与鸿蒙 (4K 页) 平台。
- 每轮 memset 使用不同填充值 (0x11/0x22/…), 并做读回校验, 防止零页/去重类优化。

## 编译

### 本机 (macOS / Linux)

```console
$ make                     # 或 cc -O2 -o mem_stress mem_stress.c
$ ./mem_stress 512M -t 5
$ ./mem_bench 256M
```

### iOS (越狱设备, 无需 Xcode 工程)

```console
$ make ios                 # 产物在 ios/, arm64 Mach-O
# 等价命令:
$ xcrun -sdk iphoneos clang -arch arm64 -O2 -miphoneos-version-min=12.0 \
      -o ios/mem_stress mem_stress.c
```

部署到设备 (iproxy 2222 → 设备 22, root/alpine):

```console
$ scp -P 2222 ios/mem_stress ios/mem_bench root@localhost:/var/mobile/ios_memslice/
$ ssh -p 2222 root@localhost \
    '/usr/bin/ldid -S /var/mobile/ios_memslice/mem_stress && chmod +x /var/mobile/ios_memslice/mem_stress'
$ ssh -p 2222 root@localhost '/var/mobile/ios_memslice/mem_stress 200M -t 10'
```

注意 (2026-09 越狱设备实测的坑):

- **必须在设备端 `ldid -S` 签名**; Mac 侧 ldid 签完传上去会被 SIGKILL (rc=137)。
- **覆盖已有二进制必须先 `rm` 再传**, 直接覆盖同样触发 rc=137。
- `scp`/sftp 上传后必须 `chmod +x`。
- 本工具只需普通 mmap, 无需任何 entitlements。
- 非越狱 iOS 无法直接运行 CLI 二进制; 同一份源码可嵌进 App target 编译执行。

### HarmonyOS (DevEco Studio 的 OHOS NDK, musl libc)

```console
$ make ohos               # 产物在 ohos/
# 等价命令 (OHOS_NATIVE 按实际安装路径调整):
$ OHOS_NATIVE=/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native
$ $OHOS_NATIVE/llvm/bin/clang --target=aarch64-linux-ohos \
      --sysroot=$OHOS_NATIVE/sysroot -O2 -o ohos/mem_stress mem_stress.c
```

部署到设备 (hdc, 设备需处于开发者模式):

```console
$ hdc file send ohos/mem_stress /data/local/tmp/mem_stress
$ hdc file send ohos/mem_bench  /data/local/tmp/mem_bench
$ hdc shell "chmod +x /data/local/tmp/mem_stress /data/local/tmp/mem_bench"
$ hdc shell "/data/local/tmp/mem_stress 200M -t 10"
$ hdc shell "/data/local/tmp/mem_bench 256M -r 5"
```

注意:

- 必须用 **OHOS NDK 的 clang** (链接 musl, 动态链接器
  `/lib/ld-musl-aarch64.so.1`); 普通 glibc 交叉链产物在设备上无法运行。
- 模拟器 (x86_64) 把 target 改为 `x86_64-linux-ohos`:
  `make ohos OHOS_TRIPLE=x86_64-linux-ohos`。
- 鸿蒙内核同样存在 zram 压缩回收, 长时间加压建议加 `-k 5` 之类的重触选项。

## 验证状态 (2026-10 实测)

| 平台 | 编译 | 运行 |
|---|---|---|
| macOS (Apple Silicon, 16K 页) | ✅ `-Wall -Wextra` 零警告 | ✅ 全模式 + SIGTERM 干净退出 |
| iOS arm64 (`xcrun -sdk iphoneos clang`) | ✅ 零警告, 纯 libSystem 符号, 无需 entitlements | 待设备 (需越狱部署, 见上) |
| Linux aarch64 glibc (VM) | ✅ 零警告 | ✅ 全模式 (4K 页, /proc RSS) |
| Linux aarch64 **musl** (VM, 鸿蒙同族 libc) | ✅ 零警告 | ✅ 全模式 |
| HarmonyOS 真机 (OHOS NDK) | 命令已给出, 本机无 NDK/hdc | 待设备 |

## 已知平台行为

- **单位均为二进制**: K=1024, M=1024², G=1024³, T=1024⁴; 大小自动向上取整到页。
- **iOS 单进程匿名保留上限 ~4GiB** (iOS 14.8 实测: mmap 4G 成功 / 8G 失败),
  超限 mmap 直接报错; 更常见的是压力过大先被 **jetsam SIGKILL** —— 属预期行为。
- iOS 极限加压 (free 页 < 500) 有内核 panic 风险, 手机上测试请留余量。
- macOS/Linux 上两者同样可用 (`make` 即可), 便于先在本机验证行为再到手机上跑。
