Android 爛裝置想跑人臉辨識 番外篇 – 從 50ms 到 3ms:ARM NEON、FastCV 與記憶體極限榨汁指南

「在百元級爛機器上搞 AI,最高境界不是把模型砍到認不出人,而是別讓 CPU 站在記憶體門口乾等 150 個時脈。」

在系列文第 0 篇 〈到底有多爛?〉 裡我講過,我們手上是一顆 2019 年的高通驍龍 QM215(4 顆 1.3GHz A53、無 NPU、實體 2GB RAM)。開機系統吃 1.3GB,相機吃 950MB,AI 吃 300MB,總共 3,180MB 直接把 2GB 記憶體打爆,CPU 長期 90% 滿載,刷個臉要等 1.5 秒。

第 1 篇 〈還沒評估,別談優化〉 我們用 dumpsys meminfo 抓出全機只剩 64MB 可用,隨時準備暴斃。

很多人遇到這種卡頓,第一件事就是吵著要換晶片或嫌 AI 太重。但掛上 Profiler 抓火焰圖,你會看到一個極度諷刺的真相:
CPU 有超過七成的時間根本沒在算數學,而是在那裡乾等記憶體把資料送過來(Memory Stall)!

我們做的是專用門禁機,「開機慢個十幾秒完全無所謂,但開機後必須 200ms 秒開門、且連續跑一年不准 OOM」

這篇番外篇將前置理論濃縮到極致,重點放在:真實專案中 4 大動刀點的取捨全景,以及如何用 Post 1 的儀表板做全鏈路成果驗收!


一、 極速理論:記憶體牆、vld4q 與 A53 調校精要

1. 記憶體排布(SoA vs AoS):20 倍讀取差距

Cortex-A53 讀 L1 快取只要 1 ~ 3 時脈,讀 DRAM 要等 150 ~ 200 時脈

  • 傳統物件寫法(AoS):Java 堆 new Point[307200],30.7 萬個物件散落各處,Cache Miss 破 40%,純等記憶體耗費 15 ~ 20ms
  • 連續平面排布(SoA):Native 配置 float[1228800],命中 64-byte Cache Line 預取,讀取時間降到 0.8ms(提速 20 倍)

2. 晶片硬體分流與工具選型

  • vld4q_f32:ARM 晶圓內建交叉分流電路(Crossbar Switch),1 條指令完成 64 bytes 載入並自動分流到 4 個 128-bit 暫存器;搭配牛頓逼近開方(vrsqrteq_f32),解算耗時由 3.54ms 壓至 0.29ms(提速 12 倍)
  • 選型原則:標準相機操作(YUV 轉碼)選 Qualcomm FastCV(支援 DSP 卸載,不重造輪子);自研特規演算法(Sony ToF 4 相位解調)選 手寫 ARM NEON

3. QM215(Cortex-A53)四大微架構榨汁術

  1. 32KB L1 行級算子融合(Fused Kernel):一行 640 點(2.5KB)全在 L1 快取跑完「讀入 → 查表 → 開方 → 3D 投影」,消滅 3 次 4.9MB DRAM 沖刷。
  2. A53 雙發射指令交錯(Dual-Issue):將 vld4q(Load)與 vmulq(Math)交錯編排,觸發 Slot 0 + Slot 1 並行,IPC 翻倍。
  3. 展開步長 2x ~ 4x:活躍暫存器控制在 20 個以內,杜絕 Stack 溢出(Register Spilling)。
  4. 4 核心專核綁定(CPU Affinity):Core 0(UI/系統)、Core 1(RGB/FastCV)、Core 2(ToF/NEON)、Core 3(AI/ACL)。

二、 專案落地實錄:手上手稿的 4 大動刀點與取捨真相

真實工業界的工程決策從來不是盲目炫技,而是精算 ROI 與防崩潰邊界:

1. ToF 3D 點雲解算(TofRawToDepthNeon.cpp

  • 決策【100% 狂暴使用 NEON 向量化 + Atan2 查表】
  • 為什麼一定要用?
    這是整台門禁機每秒跑 30 次的死穴。原本純 C++ 逐點解算一張圖要吃掉整整 50ms(門禁機直接卡死在 20fps 以下),BCTC 活體防偽直接破產。
  • 落地成果
    利用 vld4q_f32 4-Lane 自動分流、Atan2 九九乘法表與牛頓開方,Sony RAW12 轉 3D 點雲與 IR 影像的耗時從 50ms 暴降至 3ms(提速 16 倍),為後續 AI 辨識搶下 47ms 的黃金算力!

2. 相機 YUV 格式轉換(CameraRgbHelpler.java

  • 決策【主路徑用 QFastCV,但備援路徑刻意「不寫 NEON」】
  • 為什麼主路徑要用?
    Camera2 API 吐出來的 YUV 帶有硬體 Padding(寬度 640 但 Stride 是 672),每秒 30 幀在 Java 逐 byte 搬運會吃掉 15ms。呼叫 QFastCV.yuv420SPToYuv420P 在 Native 秒拆 UV 分量,耗時直接降到 1.5ms
  • 為什麼備援路徑(yuv420888toNV21)最後選擇保留純 Java?
    備援路徑的核心使命是「保底防崩潰(Fail-safe)」。它只在 FastCV 初始化失敗或遇到異常解析度時觸發。如果備援路徑也寫底層 NEON,萬一指針越界噴出 SIGSEGV 閃退,備援機制就徹底失去意義。保留純 Java 雖然要花 20ms,但保證系統 100% 不死。

3. 1:N 萬人特徵比對(NesFeatureMatcher.java

  • 決策【100% 使用 ARM 官方 ACL 函式庫,不自己手寫 SGEMM】
  • 為什麼要用?
    10,000 人臉 × 512 維特徵 = 512 萬次浮點乘加。用 Java 跑要 40 ~ 60ms(刷臉會有明顯停頓感)。
  • 為什麼不手寫彙編?
    ARM 官方團隊花了十年把 Cortex-A53 的矩陣乘法分塊與快取預取調校到極致。我們直接接入 aclsdk(Arm Compute Library),調用 SGEMM 核心(cachedNesMatch),在 1ms 內秒殺 1 萬人比對。成熟架構師絕不為了炫技去重造輪子。

4. 萬人特徵冷開機載入(UpgradeModelFeature.java

  • 決策【完全放棄 NEON/mmap,採用「50 筆批次非同步」】
  • 為什麼最後選擇「不用」NEON 與 mmap?
    特徵升級只在「OTA 系統更新後的第一次冷開機」發生一次。「門禁機開機慢十幾秒完全沒關係」,寫 500 行極易踩到記憶體洩漏的 C++ mmap 去省開機的 10 秒是負 ROI。我們在 Java 代碼裡採用極簡策略:if (upgradedFaceList.size() > 50),每 50 筆批次寫入一次資料庫並非同步餵給 ACL,安全、無記憶體負擔且足夠好用。

三、 成果驗收:用 Post 1 儀表板檢驗全鏈路戰果

依照第 1 篇 〈還沒評估,別談優化〉 的工具組,我們進行了全維度的數據驗收:

1. adb shell dumpsys meminfo(全機總量驗收)

【調校前】
Total RAM: 1,909,104K
Used RAM:  1,503,496K (系統 1.3G + 鏡頭 950M + AI 300M,記憶體嚴重超載)
Free RAM:     64,232K (瀕危真空區,隨時 OOM)
ZRAM / KSM:頻繁壓縮換頁,ksmd 搶佔 20% CPU

【調校後】
Total RAM: 1,909,104K
Used RAM:    770,120K (AOSP 瘦身至 450M + 零拷貝相機 120M + 特徵常駐 200M)
Free RAM:  1,013,734K (可用實體記憶體突破 1GB,安全餘裕超過 50%!)
ZRAM / KSM:KSM 徹底關閉奪回 20% CPU,ZRAM 僅作背景保底

2. cat /proc/$PID/smaps(記憶體顯微鏡驗收)

檢查相機 DirectBuffer 與 1:N 特徵矩陣所在的 Anonymous 記憶體段:

  • Rss == SizeSwap == 0 kB:確認 4.9MB 緩衝區 100% 留在實體 RAM,沒有任何一頁被換出到 ZRAM。
  • VmFlags 標誌檢查:出現 lo(Locked in RAM)un(Unmergeable),證明 mlock()madvise() 成功生效,徹底免疫 ZRAM 解壓尖峰與 KSM 快取污染!

3. Android Studio Profiler(GC 頻率與火焰圖耗時)

  • GC Activity(垃圾回收):從每秒數十次小型 GC 鋸齒,降至 每分鐘 0 次 GC(一條筆直平線),證明環形緩衝池達成 100% 零記憶體抖動。
  • 全鏈路耗時分段驗收(30fps 實測)
    • 相機 YUV 轉碼(CameraRgbHelpler):15ms → 1.5ms
    • ToF RAW12 轉 3D 點雲(tofsdk):50ms → 3.0ms
    • 1:N 萬人特徵矩陣乘法(aclsdk):40ms → 1.0ms
    • AI 活體與人臉偵測推論:240ms
    • 全鏈路端到端總耗時:從原本卡死崩潰的 1.5 秒 → 暴降至 280 毫秒,以極大優勢通過 BCTC 增強級金融認證!

4. 24/7 連續壓力測試

在室溫 35°C 烤機環境下連續運行 30 天:

  • CPU 總負載:穩定在 35% ~ 40%,晶片溫度低於 55°C,完全不觸發降頻。
  • 穩定性:連續 720 小時運行 0 次 OOM、0 次閃退、0 幀撕裂


參考資料 (References)

  1. John L. Hennessy, David A. Patterson. Computer Architecture: A Quantitative Approach (6th Edition). Morgan Kaufmann, 2017.
  2. Peter J. Denning. “The Locality Principle.” Communications of the ACM (CACM), 2005.
    https://dl.acm.org/doi/10.1145/1070838.1070856
  3. Wm. A. Wulf, Sally A. McKee. “Hitting the Memory Wall: Implications of the Obvious.” ACM SIGARCH Computer Architecture News, 1995.
    https://dl.acm.org/doi/10.1145/216585.216588
  4. Ulrich Drepper. “What Every Programmer Should Know About Memory.” Red Hat, 2007.
    https://people.freebsd.org/~lstewart/articles/cpumemory.pdf
  5. Mike Acton. “Data-Oriented Design and C++.” CppCon 2014 Keynote.
    https://github.com/CppCon/CppCon2014
  6. ARM Developer: Cortex-A Series Programmer’s Guide for ARMv8-A (DEN0024A) & NEON Programmer’s Guide (DEN0018A).
    https://developer.arm.com/documentation/den0018/a/

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *