G0: dich chinh - khong co drift bridge (run2 drained=True, recv=sent=10000), rut de xuat bu drift

This commit is contained in:
2026-08-28 12:15:27 +07:00
parent d78af9304f
commit d7dd626dd5
2 changed files with 49 additions and 24 deletions
+33 -24
View File
@@ -2,7 +2,8 @@
Ngày: 2026-08-27. Probe: `native_bridge/debug/g0_latency_probe.py` (chạy với Ngày: 2026-08-27. Probe: `native_bridge/debug/g0_latency_probe.py` (chạy với
`timeBeginPeriod(1)`, 10k block/config, pipeline thật Python→SHM→fx_vst_bridge→SHM). `timeBeginPeriod(1)`, 10k block/config, pipeline thật Python→SHM→fx_vst_bridge→SHM).
Log: `native_bridge/debug/g0_probe_run_full.log`. Log: `native_bridge/debug/g0_probe_run_full.log` (run 1),
`native_bridge/debug/g0_probe_run2.log` (run 2 — có `pump_rate` + drain đầy đủ).
## 1. Bảng latency plugin (getLatencySamples, @48kHz) ## 1. Bảng latency plugin (getLatencySamples, @48kHz)
@@ -24,20 +25,21 @@ trước thiếu nó → Sleep 15.6ms granularity → pump ~22ms/batch ≠ 21.33
→ pureComp bị "stall 1.9s" giả. Với pacing chuẩn, stall đó biến mất — là → pureComp bị "stall 1.9s" giả. Với pacing chuẩn, stall đó biến mất — là
artifact probe, KHÔNG phải VST stall. artifact probe, KHÔNG phải VST stall.
| Config | mean ms | σ | p50 | p99 | max | recv/sent | | Config | mean ms | σ | p50 | p99 | max | recv/sent | pump_rate | drained |
|---|---|---|---|---|---|---| |---|---|---|---|---|---|---|---|---|
| empty | 21.54 | 1.74 | 21.64 | 22.03 | 25.73 | 9996/10000 | | empty | 21.67 | 0.88 | 21.70 | 22.03 | 26.51 | 10000/10000 | 184.8 | ✅ |
| pureComp | 21.69 | 0.26 | 21.66 | 22.02 | 28.94 | 9996/10000 | | pureComp | 21.69 | 0.15 | 21.67 | 22.03 | 24.07 | 10000/10000 | 184.9 | ✅ |
| Ozone 11 | 21.68 | 0.15 | 21.65 | 22.02 | 23.20 | 9996/10000 | | Ozone 11 | 21.70 | 0.14 | 21.68 | 22.02 | 22.20 | 10000/10000 | 184.8 | ✅ |
- Round-trip ≈ 21.5–21.7ms = đúng 1 batch 4 block (21.33ms) + overhead nhỏ - Round-trip ≈ 21.5–21.7ms = đúng 1 batch 4 block (21.33ms) + overhead nhỏ
(SHM copy + bridge proc 0.001–0.45ms). Latency plugin KHÔNG nằm trong (SHM copy + bridge proc 0.001–0.45ms). Latency plugin KHÔNG nằm trong
round-trip — plugin latency bù bằng PDC (không chặn pipeline). round-trip — plugin latency bù bằng PDC (không chặn pipeline).
- σ round-trip: 0.15–0.26ms (pureComp/Ozone) — RẤT nhỏ → **Gate G2 MỞ**: PDC - **`drained=True` mọi config (run 2): bridge xử lí HẾT 10000 block, recv = sent
— KHÔNG mất block, KHÔNG backlog tích lũy.** Đây là bằng chứng quyết định:
không có drift thật (xem §4).
- σ round-trip: 0.14–0.15ms (pureComp/Ozone) — RẤT nhỏ → **Gate G2 MỞ**: PDC
mẫu có ý nghĩa, không cần giảm jitter trước (không cần relay WS→SHM sớm). mẫu có ý nghĩa, không cần giảm jitter trước (không cần relay WS→SHM sớm).
- empty σ=1.74: 1 outlier khởi động (min=0.04ms), p50/p99 vẫn 21.6/22.0. - empty σ=0.88: 1 outlier khởi động (min=0.05ms), p50/p99 vẫn 21.7/22.0.
- recv=9996 < 10000: 4 block cuối chưa drain hết lúc probe kết thúc (bridge
chậm hơn pace probe — xem §4), không phải mất block.
## 3. Inter-arrival deviation ## 3. Inter-arrival deviation
@@ -55,20 +57,27 @@ Block về theo batch 4 (bridge xử lí take=4/lần) → inter-arrival lệch
| Nguồn | procAvg | procMax | loopAvg | loopMax | rate blk/s | vs realtime 187.5 | | Nguồn | procAvg | procMax | loopAvg | loopMax | rate blk/s | vs realtime 187.5 |
|---|---|---|---|---|---|---| |---|---|---|---|---|---|---|
| probe empty | 0.001ms | 0.05ms | 22.0ms | 1010ms | 181.7 | −3.1% | | probe empty | 0.001ms | 0.06ms | 22.0ms | 1017ms | 181.5 | −3.2% |
| probe pureComp | 0.45ms | 0.80ms | 22.0ms | 1012ms | 181.6 | −3.1% | | probe pureComp | 0.46ms | 1.37ms | 22.0ms | 1011ms | 181.6 | −3.1% |
| probe Ozone | 0.14ms | 0.35ms | 22.0ms | 1008ms | 181.7 | −3.1% | | probe Ozone | 0.14ms | 0.71ms | 22.0ms | 1015ms | 181.5 | −3.2% |
| app thật (master chain đầy) | 3.4ms | 10.0ms | 21.4ms | 156ms | 186.5 | −0.5% | | app thật (master chain đầy) | 3.4ms | 10.0ms | 21.4ms | 156ms | 186.5 | −0.5% |
- **Rate "chậm 3% (probe) / 0.5% (app)" KHÔNG phải drift thật — artifact đo.**
Bằng chứng: run 2 `drained=True`, recv=sent=10000 mọi config — nếu bridge
thật sự chậm 3%, backlog 300+ block phải tích lại và drain phải mất >1.5s
sau pump (thực tế drain xong ngay trong vòng lặp pump, `pump_rate` chỉ thấp
vì probe tự sleep lệch pha với nhịp bridge poll 1ms + granularity sleep).
Bridge xử lí ASAP khi có input (`avail>0` → process ngay) — không có pace
riêng để "chậm"; output rate = input rate. App thật rate 186.5 thấp hơn vì
runSec gồm idle trước khi audio chảy (chain load, chờ play).
- **loopMax ≈ 1s ở MỌI config probe kể cả empty** — spike 1s/lần chạy ~53s, - **loopMax ≈ 1s ở MỌI config probe kể cả empty** — spike 1s/lần chạy ~53s,
không phải VST (empty cũng có). Không xuất hiện trong log app thật gần nhất không phải VST (empty cũng có). Không xuất hiện trong log app thật gần nhất
(loopMax 156ms) — nghi Windows scheduler/antivirus/power, cần theo dõi khi (loopMax 156ms) — nghi Windows scheduler/antivirus/power, cần theo dõi khi
chạy thật. Probe round-trip max vẫn ≤29ms → spike xảy ra lúc bridge idle, chạy thật. Probe round-trip max vẫn ≤27ms → spike xảy ra lúc bridge idle,
không chặn block đang xử lí. không chặn block đang xử lí (không gây underrun).
- **Drift: bridge chậm 0.5–3.1% so realtime** (rate < 187.5). App thật −0.5% - **Kết luận: KHÔNG cần bù drift.** FILL 8 block + underrun-fade/resume hiện
(186.5): mỗi giây thiếu ~1 block → FILL 8 block (42.7ms) cạn sau ~8s nếu có đủ — u=3/session (fx_stats) hiếm, do main-thread stall (GUI), không phải
không có cơ chế bù. Probe −3.1% là đo có pace probe riêng (nhịp probe + nhịp drift đều. Đề xuất resync/bù rate ban đầu rút lại (xem §6).
bridge lệch nhau); con số tin cậy cho drift thật là từ app: −0.5%.
## 5. ~0.22s gồm gì (xác nhận) ## 5. ~0.22s gồm gì (xác nhận)
@@ -93,12 +102,12 @@ Latency path thực đo + hằng số app:
## 6. Kết luận cho G2+ ## 6. Kết luận cho G2+
1. **Gate G2: MỞ.** σ round-trip 0.15–0.26ms → PDC mẫu có nghĩa; không cần 1. **Gate G2: MỞ.** σ round-trip 0.14–0.15ms → PDC mẫu có nghĩa; không cần
relay WS→SHM trước G2. relay WS→SHM trước G2.
2. **Drift bridge −0.5% là phát hiện mới** (không có trong plan §1–§8): FILL 2. **Không có drift bridge** (đã kiểm chứng run 2: drained=True, recv=sent).
cạn dần trên session dài → cần quyết định: (a) chấp nhận (underrun hiếm, có Không cần bù rate/resync — FILL 8 + underrun-fade/resume đủ. Tránh thêm
resume), (b) bù/resync định kỳ, (c) thêm giám sát rate. **Báo user để review code không cần thiết. Nếu sau này thấy underrun đều trên session dài →
trước khi đưa vào plan.** đo lại `pump_rate`/`drained` trước khi kết luận drift.
3. **loopMax ~1s spike** (probe, cả empty): theo dõi, chưa thấy ở app thật. 3. **loopMax ~1s spike** (probe, cả empty): theo dõi, chưa thấy ở app thật.
4. Plugin latency bảng §1 đủ cho G2: `graph.getLatencySamples()` sẽ thay hằng 4. Plugin latency bảng §1 đủ cho G2: `graph.getLatencySamples()` sẽ thay hằng
số; dữ liệu hiện tại xác nhận pureComp là plugin duy nhất có latency thật. số; dữ liệu hiện tại xác nhận pureComp là plugin duy nhất có latency thật.
+16
View File
@@ -69,6 +69,7 @@ def run(name, chain, n_blocks):
next_idx = 0 next_idx = 0
batch_dur = PERIOD * 4 batch_dur = PERIOD * 4
batches = n_blocks // 4 batches = n_blocks // 4
pump_t0 = time.perf_counter()
for b in range(batches): for b in range(batches):
t0 = time.perf_counter() t0 = time.perf_counter()
for _ in range(4): for _ in range(4):
@@ -83,6 +84,19 @@ def run(name, chain, n_blocks):
dt = time.perf_counter() - t0 dt = time.perf_counter() - t0
if dt < batch_dur: if dt < batch_dur:
time.sleep(batch_dur - dt) time.sleep(batch_dur - dt)
# Drain phần còn lại (bridge xử lí nốt backlog) — nếu bridge theo kịp
# input, recv đủ = sent ngay sau pump; thiếu kéo dài = drift thật.
drain_t0 = time.perf_counter()
while len(recv_t) < len(send_t) and time.perf_counter() - drain_t0 < 2.0:
outs = sess.read_output()
base = len(recv_t)
for k in range(len(outs)):
recv_t.append((base + k, time.perf_counter()))
if not outs:
time.sleep(0.002)
pump_t1 = time.perf_counter()
stats["pump_rate"] = len(recv_t) / (pump_t1 - pump_t0)
stats["drained"] = len(recv_t) == len(send_t)
# round-trip per block # round-trip per block
rt = [] rt = []
for j, tr in recv_t: for j, tr in recv_t:
@@ -135,6 +149,8 @@ def main():
st = run(name, chain, args.blocks) st = run(name, chain, args.blocks)
results[name] = st results[name] = st
print("plugin_latency:", st.get("plugin_latency")) print("plugin_latency:", st.get("plugin_latency"))
print("pump_rate=%.3f blk/s drained=%s (bridge theo kip input: rate ~187.5 va drained=True)" %
(st.get("pump_rate", 0), st.get("drained")))
rt = st.get("roundtrip_ms", {}) rt = st.get("roundtrip_ms", {})
print("roundtrip_ms n=%d mean=%.3f sd=%.3f min=%.3f p50=%.3f p99=%.3f max=%.3f" % print("roundtrip_ms n=%d mean=%.3f sd=%.3f min=%.3f p50=%.3f p99=%.3f max=%.3f" %
(rt.get("n", 0), rt.get("mean", 0), rt.get("sd", 0), rt.get("min", 0), (rt.get("n", 0), rt.get("mean", 0), rt.get("sd", 0), rt.get("min", 0),