「在百元級爛機器上搞 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)四大微架構榨汁術
- 32KB L1 行級算子融合(Fused Kernel):一行 640 點(2.5KB)全在 L1 快取跑完「讀入 → 查表 → 開方 → 3D 投影」,消滅 3 次 4.9MB DRAM 沖刷。
- A53 雙發射指令交錯(Dual-Issue):將
vld4q(Load)與vmulq(Math)交錯編排,觸發 Slot 0 + Slot 1 並行,IPC 翻倍。 - 展開步長 2x ~ 4x:活躍暫存器控制在 20 個以內,杜絕 Stack 溢出(Register Spilling)。
- 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_f324-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 == Size且Swap == 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 增強級金融認證!
- 相機 YUV 轉碼(
4. 24/7 連續壓力測試
在室溫 35°C 烤機環境下連續運行 30 天:
- CPU 總負載:穩定在 35% ~ 40%,晶片溫度低於 55°C,完全不觸發降頻。
- 穩定性:連續 720 小時運行 0 次 OOM、0 次閃退、0 幀撕裂!
參考資料 (References)
- John L. Hennessy, David A. Patterson. Computer Architecture: A Quantitative Approach (6th Edition). Morgan Kaufmann, 2017.
- Peter J. Denning. “The Locality Principle.” Communications of the ACM (CACM), 2005.
https://dl.acm.org/doi/10.1145/1070838.1070856 - 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 - Ulrich Drepper. “What Every Programmer Should Know About Memory.” Red Hat, 2007.
https://people.freebsd.org/~lstewart/articles/cpumemory.pdf - Mike Acton. “Data-Oriented Design and C++.” CppCon 2014 Keynote.
https://github.com/CppCon/CppCon2014 - ARM Developer: Cortex-A Series Programmer’s Guide for ARMv8-A (DEN0024A) & NEON Programmer’s Guide (DEN0018A).
https://developer.arm.com/documentation/den0018/a/

發佈留言