quickwin 3.0.1 → 3.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +3 -0
- package/README.md +2 -0
- package/package.json +2 -3
- package/qwin-nowasm-x86.exe +0 -0
- package/qwin-nowasm.exe +0 -0
- package/qwin-x86.exe +0 -0
- package/qwin.exe +0 -0
- package/examples/PdfCanvas.js +0 -38
- package/examples/PdfCanvas.tsx +0 -54
- package/examples/execd/main.js +0 -23379
- package/examples/execd/tray.js +0 -192
- package/examples/execd/worker.js +0 -122
- package/examples/pdf_preview2.js +0 -398
- package/examples/pdf_preview2.ts +0 -353
- package/examples/pdf_viewer.js +0 -22515
- package/examples/pdf_viewer.tsx +0 -228
- package/examples/test_gallery.js +0 -18692
- package/examples/test_gallery.tsx +0 -291
- package/examples/test_react_lazy.js +0 -18560
- package/examples/test_react_lazy.tsx +0 -65
- package/examples/test_react_listbox.js +0 -18562
- package/examples/test_react_listbox.tsx +0 -73
- package/examples/test_root_window.js +0 -18532
- package/examples/test_root_window.tsx +0 -41
- package/examples/test_tab.js +0 -18563
- package/examples/test_tab.tsx +0 -77
- package/examples/tray_demo.js +0 -86
- package/examples/tray_demo.tsx +0 -89
- package/test/add.wasm +0 -0
- package/test/bench_ffi_callraw.js +0 -235
- package/test/bench_ffi_callraw.ts +0 -235
- package/test/bench_ffi_overhead.js +0 -139
- package/test/bench_ffi_overhead.ts +0 -141
- package/test/bench_sendmessage.js +0 -104
- package/test/bench_sendmessage.ts +0 -100
- package/test/bench_sendmessage_nonnull.js +0 -187
- package/test/bench_sendmessage_nonnull.ts +0 -188
- package/test/complex.wasm +0 -0
- package/test/complex_imports.wasm +0 -0
- package/test/debug/http_server_e2e.js +0 -31
- package/test/debug/minwasm.js +0 -52
- package/test/debug/mupdf_debug.js +0 -86
- package/test/debug/wasm.js +0 -122
- package/test/debug/wasm_export_debug.js +0 -54
- package/test/debug/wasm_names.js +0 -36
- package/test/example.pdf +0 -0
- package/test/global_imports.wasm +0 -0
- package/test/import_func.wasm +0 -0
- package/test/imports.wasm +0 -0
- package/test/mupdf-cdn.js +0 -40
- package/test/mupdf-cdn.ts +0 -43
- package/test/run.js +0 -111
- package/test/run.ts +0 -116
- package/test/sjlj.wasm +0 -0
- package/test/test_basic.js +0 -7
- package/test/test_basic.ts +0 -9
- package/test/test_basic_fetch.js +0 -63
- package/test/test_basic_fetch.ts +0 -59
- package/test/test_brotli.js +0 -48
- package/test/test_brotli.ts +0 -52
- package/test/test_fetch_cache.js +0 -169
- package/test/test_fetch_cache.ts +0 -187
- package/test/test_ffi.js +0 -157
- package/test/test_ffi.ts +0 -170
- package/test/test_ffi_abi.js +0 -290
- package/test/test_ffi_abi.ts +0 -274
- package/test/test_ffi_array.js +0 -116
- package/test/test_ffi_array.ts +0 -141
- package/test/test_ffi_bind.js +0 -656
- package/test/test_ffi_bind.ts +0 -670
- package/test/test_ffi_struct.js +0 -779
- package/test/test_ffi_struct.ts +0 -828
- package/test/test_ffi_struct_layout.js +0 -495
- package/test/test_ffi_struct_layout.ts +0 -543
- package/test/test_ffi_user32.js +0 -103
- package/test/test_ffi_user32.ts +0 -113
- package/test/test_ffi_windows.js +0 -172
- package/test/test_ffi_windows.ts +0 -187
- package/test/test_files.js +0 -102
- package/test/test_files.ts +0 -121
- package/test/test_frame_encoding.js +0 -128
- package/test/test_frame_encoding.ts +0 -132
- package/test/test_gdi_draw.js +0 -681
- package/test/test_gdi_draw.ts +0 -725
- package/test/test_helper.js +0 -91
- package/test/test_helper.ts +0 -88
- package/test/test_http_import.js +0 -96
- package/test/test_http_import.ts +0 -93
- package/test/test_http_server.js +0 -214
- package/test/test_http_server.ts +0 -211
- package/test/test_import_dir.js +0 -4
- package/test/test_import_dir.ts +0 -5
- package/test/test_ipv6.js +0 -134
- package/test/test_ipv6.ts +0 -130
- package/test/test_jsx.js +0 -1314
- package/test/test_meta_url.js +0 -3
- package/test/test_meta_url.ts +0 -4
- package/test/test_mupdf_render.js +0 -51
- package/test/test_mupdf_render.ts +0 -55
- package/test/test_mupdf_twice.js +0 -62
- package/test/test_mupdf_twice.ts +0 -65
- package/test/test_mupdf_wasm.js +0 -39
- package/test/test_mupdf_wasm.ts +0 -32
- package/test/test_net_event.js +0 -57
- package/test/test_net_event.ts +0 -56
- package/test/test_net_fetch.js +0 -384
- package/test/test_net_fetch.ts +0 -373
- package/test/test_net_websocket.js +0 -180
- package/test/test_net_websocket.ts +0 -164
- package/test/test_polyfill.js +0 -144
- package/test/test_polyfill.ts +0 -159
- package/test/test_printer.js +0 -137
- package/test/test_printer.ts +0 -148
- package/test/test_proc.js +0 -141
- package/test/test_proc.ts +0 -163
- package/test/test_react_complex.js +0 -18678
- package/test/test_react_counter.js +0 -18555
- package/test/test_react_flex.js +0 -18871
- package/test/test_react_hidden.js +0 -18560
- package/test/test_react_listview_link.js +0 -18590
- package/test/test_react_notify.js +0 -18797
- package/test/test_react_render.js +0 -18550
- package/test/test_registry.js +0 -74
- package/test/test_registry.ts +0 -95
- package/test/test_sysinfo.js +0 -122
- package/test/test_sysinfo.ts +0 -141
- package/test/test_url.js +0 -173
- package/test/test_url.ts +0 -183
- package/test/test_wasm_basic.js +0 -85
- package/test/test_wasm_basic.ts +0 -73
- package/test/test_wasm_import_global.js +0 -41
- package/test/test_wasm_import_global.ts +0 -45
- package/test/test_wasm_sjlj.js +0 -145
- package/test/test_wasm_sjlj.ts +0 -125
- package/test/test_wasm_types.js +0 -97
- package/test/test_wasm_types.ts +0 -124
- package/test/test_wolfssl_read.js +0 -203
- package/test/test_wolfssl_read.ts +0 -184
- package/test/test_worker.js +0 -21
- package/test/test_worker.ts +0 -24
- package/test/test_worker_concurrent.js +0 -45
- package/test/test_worker_concurrent.ts +0 -56
- package/test/test_worker_data_url.js +0 -46
- package/test/test_worker_data_url.ts +0 -51
- package/test/test_worker_fetch.js +0 -26
- package/test/test_worker_fetch.ts +0 -27
- package/test/test_worker_net.js +0 -69
- package/test/test_worker_net.ts +0 -66
- package/test/test_worker_wasm_concurrent.js +0 -44
- package/test/test_worker_wasm_concurrent.ts +0 -45
- package/test/types.wasm +0 -0
- package/test/worker_fetch.js +0 -13
- package/test/worker_fetch.ts +0 -13
- package/test/worker_helper.js +0 -1
- package/test/worker_helper.ts +0 -1
- package/test/worker_http.js +0 -18
- package/test/worker_http.ts +0 -20
- package/test/worker_import_lib.js +0 -12
- package/test/worker_import_lib.ts +0 -13
- package/test/worker_module.js +0 -16
- package/test/worker_module.ts +0 -17
- package/test/worker_wasm_concurrent.js +0 -22
- package/test/worker_wasm_concurrent.ts +0 -22
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
// 一次性性能 bench(归因场):绕过 callPacked 直调 ffi.ffiCall,把 bind() 调用链拆层。
|
|
2
|
-
// 不入 CI。A 臂 = bind() 同场对照(对齐 bench_ffi_overhead.ts 的 S0-S3 量级)。
|
|
3
|
-
// 用法:make js 后 win11 POST /exec {"cmd":"qwin.exe test\\bench_ffi_callraw.js","timeout":600000}
|
|
4
|
-
// 臂(同 proc、同帧布局,唯一变量是「每次调用经过哪些层」):
|
|
5
|
-
// A bind rest 参数 + callPacked 全链(每调 argFrame/DataView/retBuf/readRet-dv
|
|
6
|
-
// 分配、getWidth 闭包、writeSlot 对象字面量、ffiCall、readRet)
|
|
7
|
-
// B raw 帧复用 + 每调最小写槽/读返 + ffiCall → native 桩 + QuickJS C 边界
|
|
8
|
-
// C raw-alloc 每调 new ArrayBuffer(32)+DataView+ArrayBuffer(8)+读返 DataView
|
|
9
|
-
// (复刻 callPacked 分配面)+ 写槽 + ffiCall(仅 S0/S1)
|
|
10
|
-
// 差值解读:B = trampoline 地板;C−B = 每调分配;A−C = callPacked JS 层其余。
|
|
11
|
-
// x64 帧约定:argFrame 恒 32B(4×8 槽,桩无条件读 4 槽),retBuf 8B(ffiCall ≥8 校验)。
|
|
12
|
-
// S1/S2/S3 各臂消费返回值到 sink(A/B/C 同口径,防 DCE 疑虑 + 收尾打印证活)。
|
|
13
|
-
// 结束必须 DestroyWindow + std.exit。
|
|
14
|
-
import * as std from 'std'
|
|
15
|
-
import * as gui from 'gui'
|
|
16
|
-
import * as ffi from 'ffi'
|
|
17
|
-
import { bind } from '../lib/ffi/bind.js'
|
|
18
|
-
|
|
19
|
-
const WM_NULL = 0x0000
|
|
20
|
-
const FRAME_BYTES = 32 // x64:4 槽 × 8B,桩无条件读 slots[0..3]
|
|
21
|
-
const ROUNDS = 15
|
|
22
|
-
const TARGET_MS = 400
|
|
23
|
-
|
|
24
|
-
const med = (t: number[]): number => {
|
|
25
|
-
const s = [...t].sort((x, y) => x - y)
|
|
26
|
-
const m = s.length >> 1
|
|
27
|
-
return s.length % 2 ? s[m]! : (s[m - 1]! + s[m]!) / 2
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// 文件日志(cwd=Z:\quickwin → 宿主 _build/bench_ffi_callraw_log.txt)
|
|
31
|
-
const log = std.open('bench_ffi_callraw_log.txt', 'w')
|
|
32
|
-
function step(msg: string): void {
|
|
33
|
-
std.printf('%s\n', msg)
|
|
34
|
-
if (log) { log.puts(msg + '\n'); log.flush() }
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
step('stage: imports ok')
|
|
38
|
-
|
|
39
|
-
// ---- proc 地址(B/C 臂绕过 bind 直驱 ffiCall)----
|
|
40
|
-
function procOf(dll: string, name: string): number {
|
|
41
|
-
const h = ffi.LoadLibrary(dll)
|
|
42
|
-
if (!h) throw new Error('LoadLibrary(' + dll + ') failed')
|
|
43
|
-
const p = ffi.GetProcAddress(h, name)
|
|
44
|
-
if (!p) throw new Error('GetProcAddress(' + dll + '!' + name + ') failed')
|
|
45
|
-
return p
|
|
46
|
-
}
|
|
47
|
-
const pNop0 = procOf('test/nop.dll', 'nop0')
|
|
48
|
-
const pNop1 = procOf('test/nop.dll', 'nop1')
|
|
49
|
-
const pPid = procOf('kernel32.dll', 'GetCurrentProcessId')
|
|
50
|
-
const pSm = procOf('user32.dll', 'SendMessageW')
|
|
51
|
-
|
|
52
|
-
// B 臂复用帧 + 视图(一次分配,跨轮复用)
|
|
53
|
-
const frame = new ArrayBuffer(FRAME_BYTES)
|
|
54
|
-
const retBuf = new ArrayBuffer(8)
|
|
55
|
-
const fv = new DataView(frame)
|
|
56
|
-
const rv = new DataView(retBuf)
|
|
57
|
-
let sink = 0
|
|
58
|
-
|
|
59
|
-
const fNop0 = bind('test/nop.dll', 'nop0', ' -> void')
|
|
60
|
-
const fNop1 = bind('test/nop.dll', 'nop1', 'i32 -> i32')
|
|
61
|
-
const fPid = bind('kernel32.dll', 'GetCurrentProcessId', ' -> u32')
|
|
62
|
-
|
|
63
|
-
const winRaw = gui.CreateWindow('STATIC', 'bench-title', 0, 0, 0, 20, 20, null, null)
|
|
64
|
-
if (!winRaw) throw new Error('CreateWindow(STATIC) failed')
|
|
65
|
-
const hwnd: gui.HWND = winRaw
|
|
66
|
-
const fSm = bind('user32.dll', 'SendMessageW', '<HWND>ptr u32 <>ptr <>ptr -> <>ptr')
|
|
67
|
-
step('stage: window created')
|
|
68
|
-
|
|
69
|
-
// ---- 可行性探针:任一臂签名/帧布局错位立刻炸 ----
|
|
70
|
-
fNop0()
|
|
71
|
-
if (fNop1(1) !== 2) throw new Error('sanity: bind nop1(1) !== 2')
|
|
72
|
-
if (fPid() <= 0) throw new Error('sanity: bind pid <= 0')
|
|
73
|
-
ffi.ffiCall(pNop0, frame, retBuf, 0)
|
|
74
|
-
fv.setInt32(0, 1, true)
|
|
75
|
-
ffi.ffiCall(pNop1, frame, retBuf, 0)
|
|
76
|
-
if (rv.getInt32(0, true) !== 2) throw new Error('sanity: raw nop1 !== 2, got ' + rv.getInt32(0, true))
|
|
77
|
-
ffi.ffiCall(pPid, frame, retBuf, 0)
|
|
78
|
-
if (rv.getUint32(0, true) <= 0) throw new Error('sanity: raw pid <= 0')
|
|
79
|
-
// S3 帧布局预演(hwnd 槽 + msg + wParam + lParam)
|
|
80
|
-
fv.setBigUint64(0, BigInt(Number(hwnd)), true)
|
|
81
|
-
fv.setUint32(8, WM_NULL, true)
|
|
82
|
-
fv.setBigUint64(16, 0n, true)
|
|
83
|
-
fv.setBigUint64(24, 0x1234n, true)
|
|
84
|
-
ffi.ffiCall(pSm, frame, retBuf, 0)
|
|
85
|
-
const smRet = rv.getBigUint64(0, true)
|
|
86
|
-
step('stage: probes ok, raw SendMessage ret=' + String(smRet) + ' (expect 0n)')
|
|
87
|
-
|
|
88
|
-
// ---- 臂循环:函数取到局部 const,两侧/各臂同形态 ----
|
|
89
|
-
const runA0 = (n: number): number => {
|
|
90
|
-
const f = fNop0
|
|
91
|
-
const t0 = Date.now()
|
|
92
|
-
for (let i = 0; i < n; i++) f()
|
|
93
|
-
return Date.now() - t0
|
|
94
|
-
}
|
|
95
|
-
const runB0 = (n: number): number => {
|
|
96
|
-
const p = pNop0, fr = frame, rb = retBuf
|
|
97
|
-
const t0 = Date.now()
|
|
98
|
-
for (let i = 0; i < n; i++) ffi.ffiCall(p, fr, rb, 0)
|
|
99
|
-
return Date.now() - t0
|
|
100
|
-
}
|
|
101
|
-
const runC0 = (n: number): number => {
|
|
102
|
-
const p = pNop0
|
|
103
|
-
const t0 = Date.now()
|
|
104
|
-
for (let i = 0; i < n; i++) {
|
|
105
|
-
const f = new ArrayBuffer(FRAME_BYTES)
|
|
106
|
-
const d = new DataView(f) // callPacked 无条件建 dv,照抄
|
|
107
|
-
void d
|
|
108
|
-
ffi.ffiCall(p, f, new ArrayBuffer(8), 0)
|
|
109
|
-
}
|
|
110
|
-
return Date.now() - t0
|
|
111
|
-
}
|
|
112
|
-
const runA1 = (n: number): number => {
|
|
113
|
-
const f = fNop1
|
|
114
|
-
const t0 = Date.now()
|
|
115
|
-
for (let i = 0; i < n; i++) sink += f(1)
|
|
116
|
-
return Date.now() - t0
|
|
117
|
-
}
|
|
118
|
-
const runB1 = (n: number): number => {
|
|
119
|
-
const p = pNop1, fr = frame, rb = retBuf, d = fv, rd = rv
|
|
120
|
-
const t0 = Date.now()
|
|
121
|
-
for (let i = 0; i < n; i++) {
|
|
122
|
-
d.setInt32(0, 1, true)
|
|
123
|
-
ffi.ffiCall(p, fr, rb, 0)
|
|
124
|
-
sink += rd.getInt32(0, true)
|
|
125
|
-
}
|
|
126
|
-
return Date.now() - t0
|
|
127
|
-
}
|
|
128
|
-
const runC1 = (n: number): number => {
|
|
129
|
-
const p = pNop1
|
|
130
|
-
const t0 = Date.now()
|
|
131
|
-
for (let i = 0; i < n; i++) {
|
|
132
|
-
const f = new ArrayBuffer(FRAME_BYTES)
|
|
133
|
-
const d = new DataView(f)
|
|
134
|
-
d.setInt32(0, 1, true)
|
|
135
|
-
const r = new ArrayBuffer(8)
|
|
136
|
-
ffi.ffiCall(p, f, r, 0)
|
|
137
|
-
sink += new DataView(r).getInt32(0, true) // readRet 同构:每次 new DataView
|
|
138
|
-
}
|
|
139
|
-
return Date.now() - t0
|
|
140
|
-
}
|
|
141
|
-
const runA2 = (n: number): number => {
|
|
142
|
-
const f = fPid
|
|
143
|
-
const t0 = Date.now()
|
|
144
|
-
for (let i = 0; i < n; i++) sink += f()
|
|
145
|
-
return Date.now() - t0
|
|
146
|
-
}
|
|
147
|
-
const runB2 = (n: number): number => {
|
|
148
|
-
const p = pPid, fr = frame, rb = retBuf, rd = rv
|
|
149
|
-
const t0 = Date.now()
|
|
150
|
-
for (let i = 0; i < n; i++) {
|
|
151
|
-
ffi.ffiCall(p, fr, rb, 0)
|
|
152
|
-
sink += rd.getUint32(0, true)
|
|
153
|
-
}
|
|
154
|
-
return Date.now() - t0
|
|
155
|
-
}
|
|
156
|
-
const runA3 = (n: number): number => {
|
|
157
|
-
const f = fSm
|
|
158
|
-
const t0 = Date.now()
|
|
159
|
-
for (let i = 0; i < n; i++) sink += f(hwnd, WM_NULL, 0, 0x1234)
|
|
160
|
-
return Date.now() - t0
|
|
161
|
-
}
|
|
162
|
-
const runB3 = (n: number): number => {
|
|
163
|
-
const p = pSm, fr = frame, rb = retBuf, d = fv, rd = rv
|
|
164
|
-
const t0 = Date.now()
|
|
165
|
-
for (let i = 0; i < n; i++) {
|
|
166
|
-
d.setBigUint64(0, BigInt(Number(hwnd)), true)
|
|
167
|
-
d.setUint32(8, WM_NULL, true)
|
|
168
|
-
d.setBigUint64(16, 0n, true)
|
|
169
|
-
d.setBigUint64(24, 0x1234n, true)
|
|
170
|
-
ffi.ffiCall(p, fr, rb, 0)
|
|
171
|
-
sink += Number(rd.getBigUint64(0, true))
|
|
172
|
-
}
|
|
173
|
-
return Date.now() - t0
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
type Arm = { label: string; n: number; fn: (n: number) => number }
|
|
177
|
-
|
|
178
|
-
// 自校准:探针逐级放大 n0 至 dt≥20ms(1ms 量化步长 ≤5%),按 TARGET_MS 定批量。
|
|
179
|
-
// B 臂可能比 A 快两个量级,固定批量必有一侧撞量化墙——与 overhead 场同法。
|
|
180
|
-
function calibrate(label: string, fn: (n: number) => number): number {
|
|
181
|
-
let n0 = 10_000
|
|
182
|
-
let dt = 0
|
|
183
|
-
for (;;) {
|
|
184
|
-
dt = fn(n0)
|
|
185
|
-
if (dt >= 20 || n0 >= 10_000_000) break
|
|
186
|
-
n0 *= 10
|
|
187
|
-
}
|
|
188
|
-
const per = dt / n0
|
|
189
|
-
const n = Math.max(10_000, Math.min(10_000_000, Math.round(TARGET_MS / per)))
|
|
190
|
-
step(' calib ' + label + ': probe n=' + String(n0) + ' dt=' + String(dt) +
|
|
191
|
-
'ms → batch=' + String(n))
|
|
192
|
-
return n
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const mkArm = (label: string, fn: (n: number) => number): Arm =>
|
|
196
|
-
({ label, n: calibrate(label, fn), fn })
|
|
197
|
-
|
|
198
|
-
function suite(name: string, arms: Arm[]): void {
|
|
199
|
-
step('suite: ' + name + ' warmup')
|
|
200
|
-
for (const a of arms) a.fn(a.n)
|
|
201
|
-
const times: number[][] = arms.map(() => [])
|
|
202
|
-
for (let r = 0; r < ROUNDS; r++) {
|
|
203
|
-
for (let k = 0; k < arms.length; k++) {
|
|
204
|
-
const i = (r + k) % arms.length
|
|
205
|
-
times[i]!.push(arms[i]!.fn(arms[i]!.n))
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
for (let r = 0; r < Math.min(ROUNDS, 3); r++) {
|
|
209
|
-
step(' round ' + String(r) + ': ' +
|
|
210
|
-
arms.map((a, i) => a.label + '=' + String(times[i]![r]) + 'ms').join(' '))
|
|
211
|
-
}
|
|
212
|
-
const ns = (i: number, ms: number) => (ms * 1e6 / arms[i]!.n).toFixed(1)
|
|
213
|
-
const meds = times.map(med)
|
|
214
|
-
const mins = times.map(t => Math.min(...t))
|
|
215
|
-
step(' ** median: ' + arms.map((a, i) => a.label + '=' + ns(i, meds[i]!) + 'ns').join(' | '))
|
|
216
|
-
step(' min: ' + arms.map((a, i) => a.label + '=' + ns(i, mins[i]!) + 'ns').join(' | '))
|
|
217
|
-
if (arms.length >= 3) {
|
|
218
|
-
const a = Number(ns(0, meds[0]!)), b = Number(ns(1, meds[1]!)), c = Number(ns(2, meds[2]!))
|
|
219
|
-
step(' split: C-B=' + (c - b).toFixed(1) + 'ns (每调分配) A-C=' +
|
|
220
|
-
(a - c).toFixed(1) + 'ns (callPacked JS 层其余)')
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
step('bench_ffi_callraw (attribution): A=bind / B=raw ffiCall / C=raw+per-call alloc; ' +
|
|
225
|
-
'calib ~' + String(TARGET_MS) + 'ms, ' + String(ROUNDS) + 'r (median)')
|
|
226
|
-
suite('S0 nop0 0参 void', [mkArm('A bind', runA0), mkArm('B raw ', runB0), mkArm('C allc', runC0)])
|
|
227
|
-
suite('S1 nop1 1×i32', [mkArm('A bind', runA1), mkArm('B raw ', runB1), mkArm('C allc', runC1)])
|
|
228
|
-
suite('S2 GetCurrentProcessId', [mkArm('A bind', runA2), mkArm('B raw ', runB2)])
|
|
229
|
-
suite('S3 SendMessageW WM_NULL', [mkArm('A bind', runA3), mkArm('B raw ', runB3)])
|
|
230
|
-
|
|
231
|
-
step('sink=' + String(sink) + ' (liveness: >0 证明各臂 native 调用真实发生)')
|
|
232
|
-
gui.DestroyWindow(hwnd)
|
|
233
|
-
step('DONE')
|
|
234
|
-
if (log) log.close()
|
|
235
|
-
std.exit(0)
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
// 一次性性能 bench:我方 bind() FFI vs node:ffi(Node v26.10) 同机同场景对比(win11 VM)
|
|
2
|
-
// 不入 CI。对照脚本 test/bench_nodeffi.mjs(node 侧同场景)。结论见对话,未落文档。
|
|
3
|
-
// 用法:make js 后 POST /exec {"cmd":"qwin.exe test/bench_ffi_overhead.js"}
|
|
4
|
-
// 场景(跨运行时按 ns/call 归一,每臂独立自校准到 ~400ms/样本):
|
|
5
|
-
// S0 nop0 test/nop.dll 0 参 void —— 纯 JS→C 往返 trampoline(无封送无返回读取)
|
|
6
|
-
// S1 nop1 test/nop.dll 1×i32 —— 单标量封送往返
|
|
7
|
-
// S2 pid kernel32 GetCurrentProcessId 0 参 —— 真实系统 DLL
|
|
8
|
-
// S3 sm user32 SendMessageW WM_NULL 自有窗口 —— 完整链路 + gui C 直调地板参照
|
|
9
|
-
// 方法学沿用 bench_sendmessage_nonnull:预热 1 + 臂序轮转 + median 主指标/min。
|
|
10
|
-
// Date.now() 1ms 量化 → 批量自校准(探针 ≥20ms 才有 ≤5% 步长,目标 ~400ms/样本)。
|
|
11
|
-
// 全部消息发自有窗口(直调 wndproc)。结束必须 DestroyWindow + std.exit。
|
|
12
|
-
import * as std from 'std';
|
|
13
|
-
import * as gui from 'gui';
|
|
14
|
-
import { bind } from '../lib/ffi/bind.js';
|
|
15
|
-
const WM_NULL = 0x0000;
|
|
16
|
-
const ROUNDS = 15;
|
|
17
|
-
const TARGET_MS = 400;
|
|
18
|
-
// median(离群稳健)供 calibrate 与 suite 共用
|
|
19
|
-
const med = (t) => {
|
|
20
|
-
const s = [...t].sort((x, y) => x - y);
|
|
21
|
-
const m = s.length >> 1;
|
|
22
|
-
return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2;
|
|
23
|
-
};
|
|
24
|
-
// 文件日志(cwd=Z:\quickwin,宿主机 _build/bench_ffi_overhead_log.txt 可实时读)——
|
|
25
|
-
// exec 只在进程退出后回包,挂了靠它定位卡点
|
|
26
|
-
const log = std.open('bench_ffi_overhead_log.txt', 'w');
|
|
27
|
-
function step(msg) {
|
|
28
|
-
std.printf('%s\n', msg);
|
|
29
|
-
if (log) {
|
|
30
|
-
log.puts(msg + '\n');
|
|
31
|
-
log.flush();
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
step('stage: imports ok');
|
|
35
|
-
const winRaw = gui.CreateWindow('STATIC', 'bench-title', 0, 0, 0, 20, 20, null, null);
|
|
36
|
-
if (!winRaw)
|
|
37
|
-
throw new Error('CreateWindow(STATIC) failed');
|
|
38
|
-
const win = winRaw;
|
|
39
|
-
step('stage: window created');
|
|
40
|
-
const fNop0 = bind('test/nop.dll', 'nop0', ' -> void');
|
|
41
|
-
const fNop1 = bind('test/nop.dll', 'nop1', 'i32 -> i32');
|
|
42
|
-
const fPid = bind('kernel32.dll', 'GetCurrentProcessId', ' -> u32');
|
|
43
|
-
const fSm = bind('user32.dll', 'SendMessageW', '<HWND>ptr u32 <>ptr <>ptr -> <>ptr');
|
|
44
|
-
// 每臂循环体:函数先取到局部 const(与 node 侧同形态——模块槽访问 vs 局部槽访问的
|
|
45
|
-
// 每迭代差在 ns 级,两边都取局部才对齐);调用结果不使用(QuickJS 解释器不消除
|
|
46
|
-
// 副作用调用,与原 bench 同口径)
|
|
47
|
-
const runNop0 = (n) => {
|
|
48
|
-
const f = fNop0;
|
|
49
|
-
const t0 = Date.now();
|
|
50
|
-
for (let i = 0; i < n; i++)
|
|
51
|
-
f();
|
|
52
|
-
return Date.now() - t0;
|
|
53
|
-
};
|
|
54
|
-
const runNop1 = (n) => {
|
|
55
|
-
const f = fNop1;
|
|
56
|
-
const t0 = Date.now();
|
|
57
|
-
for (let i = 0; i < n; i++)
|
|
58
|
-
f(1);
|
|
59
|
-
return Date.now() - t0;
|
|
60
|
-
};
|
|
61
|
-
const runPid = (n) => {
|
|
62
|
-
const f = fPid;
|
|
63
|
-
const t0 = Date.now();
|
|
64
|
-
for (let i = 0; i < n; i++)
|
|
65
|
-
f();
|
|
66
|
-
return Date.now() - t0;
|
|
67
|
-
};
|
|
68
|
-
const runSm = (n) => {
|
|
69
|
-
const f = fSm;
|
|
70
|
-
const t0 = Date.now();
|
|
71
|
-
for (let i = 0; i < n; i++)
|
|
72
|
-
f(win, WM_NULL, 0, 0x1234);
|
|
73
|
-
return Date.now() - t0;
|
|
74
|
-
};
|
|
75
|
-
const runGuiSm = (n) => {
|
|
76
|
-
const f = gui.SendMessage;
|
|
77
|
-
const t0 = Date.now();
|
|
78
|
-
for (let i = 0; i < n; i++)
|
|
79
|
-
f(win, WM_NULL, 0, 0x1234);
|
|
80
|
-
return Date.now() - t0;
|
|
81
|
-
};
|
|
82
|
-
// 自校准:探针逐级放大 n0 直到 dt≥20ms(量化步长 1ms ≤ 5%),再按 TARGET_MS 定批量。
|
|
83
|
-
// 不预设单次调用量级——我方与 gui 地板相差 100×,固定批量必有一侧撞量化墙。
|
|
84
|
-
function calibrate(label, fn) {
|
|
85
|
-
let n0 = 10_000;
|
|
86
|
-
let dt = 0;
|
|
87
|
-
for (;;) {
|
|
88
|
-
dt = fn(n0);
|
|
89
|
-
if (dt >= 20 || n0 >= 10_000_000)
|
|
90
|
-
break;
|
|
91
|
-
n0 *= 10;
|
|
92
|
-
}
|
|
93
|
-
const per = dt / n0;
|
|
94
|
-
const n = Math.max(10_000, Math.min(10_000_000, Math.round(TARGET_MS / per)));
|
|
95
|
-
step(' calib ' + label + ': probe n=' + String(n0) + ' dt=' + String(dt) +
|
|
96
|
-
'ms → batch=' + String(n));
|
|
97
|
-
return n;
|
|
98
|
-
}
|
|
99
|
-
function mkArm(label, fn) {
|
|
100
|
-
return { label, n: calibrate(label, fn), fn };
|
|
101
|
-
}
|
|
102
|
-
function suite(name, arms) {
|
|
103
|
-
step('suite: ' + name + ' warmup');
|
|
104
|
-
for (const a of arms)
|
|
105
|
-
a.fn(a.n); // 预热各 1 轮
|
|
106
|
-
const times = arms.map(() => []);
|
|
107
|
-
for (let r = 0; r < ROUNDS; r++) {
|
|
108
|
-
// 臂序按 r 轮转起跑,抵消顺序性偏置(温度/调度漂移)
|
|
109
|
-
for (let k = 0; k < arms.length; k++) {
|
|
110
|
-
const i = (r + k) % arms.length;
|
|
111
|
-
times[i].push(arms[i].fn(arms[i].n));
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
// 每轮原始数据只 dump 前 3 轮(15 轮全打没意义,log 只留诊断线索)
|
|
115
|
-
for (let r = 0; r < Math.min(ROUNDS, 3); r++) {
|
|
116
|
-
step(' round ' + String(r) + ': ' +
|
|
117
|
-
arms.map((a, i) => a.label + '=' + String(times[i][r]) + 'ms').join(' '));
|
|
118
|
-
}
|
|
119
|
-
const ns = (i, ms) => (ms * 1e6 / arms[i].n).toFixed(1);
|
|
120
|
-
// median 是主指标(离群稳健);min 留作参照
|
|
121
|
-
const meds = times.map(med);
|
|
122
|
-
const mins = times.map(t => Math.min(...t));
|
|
123
|
-
step(' ** median: ' + arms.map((a, i) => a.label + '=' + ns(i, meds[i]) + 'ns').join(' | '));
|
|
124
|
-
step(' min: ' + arms.map((a, i) => a.label + '=' + ns(i, mins[i]) + 'ns').join(' | '));
|
|
125
|
-
}
|
|
126
|
-
step('bench_ffi_overhead (ours): per-arm calib ~' + String(TARGET_MS) + 'ms, ' +
|
|
127
|
-
String(ROUNDS) + 'r (median 主指标), 对照 test/bench_nodeffi.mjs');
|
|
128
|
-
suite('S0 nop0 0参 void 纯往返', [mkArm('ours', runNop0)]);
|
|
129
|
-
suite('S1 nop1 1×i32 封送往返', [mkArm('ours', runNop1)]);
|
|
130
|
-
suite('S2 GetCurrentProcessId 0参真实API', [mkArm('ours', runPid)]);
|
|
131
|
-
suite('S3 SendMessageW WM_NULL 自有窗口 (gui=直调地板)', [
|
|
132
|
-
mkArm('ours', runSm),
|
|
133
|
-
mkArm('gui ', runGuiSm),
|
|
134
|
-
]);
|
|
135
|
-
gui.DestroyWindow(win);
|
|
136
|
-
step('DONE');
|
|
137
|
-
if (log)
|
|
138
|
-
log.close();
|
|
139
|
-
std.exit(0);
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
// 一次性性能 bench:我方 bind() FFI vs node:ffi(Node v26.10) 同机同场景对比(win11 VM)
|
|
2
|
-
// 不入 CI。对照脚本 test/bench_nodeffi.mjs(node 侧同场景)。结论见对话,未落文档。
|
|
3
|
-
// 用法:make js 后 POST /exec {"cmd":"qwin.exe test/bench_ffi_overhead.js"}
|
|
4
|
-
// 场景(跨运行时按 ns/call 归一,每臂独立自校准到 ~400ms/样本):
|
|
5
|
-
// S0 nop0 test/nop.dll 0 参 void —— 纯 JS→C 往返 trampoline(无封送无返回读取)
|
|
6
|
-
// S1 nop1 test/nop.dll 1×i32 —— 单标量封送往返
|
|
7
|
-
// S2 pid kernel32 GetCurrentProcessId 0 参 —— 真实系统 DLL
|
|
8
|
-
// S3 sm user32 SendMessageW WM_NULL 自有窗口 —— 完整链路 + gui C 直调地板参照
|
|
9
|
-
// 方法学沿用 bench_sendmessage_nonnull:预热 1 + 臂序轮转 + median 主指标/min。
|
|
10
|
-
// Date.now() 1ms 量化 → 批量自校准(探针 ≥20ms 才有 ≤5% 步长,目标 ~400ms/样本)。
|
|
11
|
-
// 全部消息发自有窗口(直调 wndproc)。结束必须 DestroyWindow + std.exit。
|
|
12
|
-
import * as std from 'std'
|
|
13
|
-
import * as gui from 'gui'
|
|
14
|
-
import { bind } from '../lib/ffi/bind.js'
|
|
15
|
-
|
|
16
|
-
const WM_NULL = 0x0000
|
|
17
|
-
const ROUNDS = 15
|
|
18
|
-
const TARGET_MS = 400
|
|
19
|
-
|
|
20
|
-
// median(离群稳健)供 calibrate 与 suite 共用
|
|
21
|
-
const med = (t: number[]): number => {
|
|
22
|
-
const s = [...t].sort((x, y) => x - y)
|
|
23
|
-
const m = s.length >> 1
|
|
24
|
-
return s.length % 2 ? s[m]! : (s[m - 1]! + s[m]!) / 2
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// 文件日志(cwd=Z:\quickwin,宿主机 _build/bench_ffi_overhead_log.txt 可实时读)——
|
|
28
|
-
// exec 只在进程退出后回包,挂了靠它定位卡点
|
|
29
|
-
const log = std.open('bench_ffi_overhead_log.txt', 'w')
|
|
30
|
-
function step(msg: string): void {
|
|
31
|
-
std.printf('%s\n', msg)
|
|
32
|
-
if (log) { log.puts(msg + '\n'); log.flush() }
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
step('stage: imports ok')
|
|
36
|
-
|
|
37
|
-
const winRaw = gui.CreateWindow('STATIC', 'bench-title', 0, 0, 0, 20, 20, null, null)
|
|
38
|
-
if (!winRaw) throw new Error('CreateWindow(STATIC) failed')
|
|
39
|
-
const win: gui.HWND = winRaw
|
|
40
|
-
step('stage: window created')
|
|
41
|
-
|
|
42
|
-
const fNop0 = bind('test/nop.dll', 'nop0', ' -> void')
|
|
43
|
-
const fNop1 = bind('test/nop.dll', 'nop1', 'i32 -> i32')
|
|
44
|
-
const fPid = bind('kernel32.dll', 'GetCurrentProcessId', ' -> u32')
|
|
45
|
-
const fSm = bind('user32.dll', 'SendMessageW', '<HWND>ptr u32 <>ptr <>ptr -> <>ptr')
|
|
46
|
-
|
|
47
|
-
// 每臂循环体:函数先取到局部 const(与 node 侧同形态——模块槽访问 vs 局部槽访问的
|
|
48
|
-
// 每迭代差在 ns 级,两边都取局部才对齐);调用结果不使用(QuickJS 解释器不消除
|
|
49
|
-
// 副作用调用,与原 bench 同口径)
|
|
50
|
-
const runNop0 = (n: number): number => {
|
|
51
|
-
const f = fNop0
|
|
52
|
-
const t0 = Date.now()
|
|
53
|
-
for (let i = 0; i < n; i++) f()
|
|
54
|
-
return Date.now() - t0
|
|
55
|
-
}
|
|
56
|
-
const runNop1 = (n: number): number => {
|
|
57
|
-
const f = fNop1
|
|
58
|
-
const t0 = Date.now()
|
|
59
|
-
for (let i = 0; i < n; i++) f(1)
|
|
60
|
-
return Date.now() - t0
|
|
61
|
-
}
|
|
62
|
-
const runPid = (n: number): number => {
|
|
63
|
-
const f = fPid
|
|
64
|
-
const t0 = Date.now()
|
|
65
|
-
for (let i = 0; i < n; i++) f()
|
|
66
|
-
return Date.now() - t0
|
|
67
|
-
}
|
|
68
|
-
const runSm = (n: number): number => {
|
|
69
|
-
const f = fSm
|
|
70
|
-
const t0 = Date.now()
|
|
71
|
-
for (let i = 0; i < n; i++) f(win, WM_NULL, 0, 0x1234)
|
|
72
|
-
return Date.now() - t0
|
|
73
|
-
}
|
|
74
|
-
const runGuiSm = (n: number): number => {
|
|
75
|
-
const f = gui.SendMessage
|
|
76
|
-
const t0 = Date.now()
|
|
77
|
-
for (let i = 0; i < n; i++) f(win, WM_NULL, 0, 0x1234)
|
|
78
|
-
return Date.now() - t0
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
type Arm = { label: string; n: number; fn: (n: number) => number }
|
|
82
|
-
|
|
83
|
-
// 自校准:探针逐级放大 n0 直到 dt≥20ms(量化步长 1ms ≤ 5%),再按 TARGET_MS 定批量。
|
|
84
|
-
// 不预设单次调用量级——我方与 gui 地板相差 100×,固定批量必有一侧撞量化墙。
|
|
85
|
-
function calibrate(label: string, fn: (n: number) => number): number {
|
|
86
|
-
let n0 = 10_000
|
|
87
|
-
let dt = 0
|
|
88
|
-
for (;;) {
|
|
89
|
-
dt = fn(n0)
|
|
90
|
-
if (dt >= 20 || n0 >= 10_000_000) break
|
|
91
|
-
n0 *= 10
|
|
92
|
-
}
|
|
93
|
-
const per = dt / n0
|
|
94
|
-
const n = Math.max(10_000, Math.min(10_000_000, Math.round(TARGET_MS / per)))
|
|
95
|
-
step(' calib ' + label + ': probe n=' + String(n0) + ' dt=' + String(dt) +
|
|
96
|
-
'ms → batch=' + String(n))
|
|
97
|
-
return n
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function mkArm(label: string, fn: (n: number) => number): Arm {
|
|
101
|
-
return { label, n: calibrate(label, fn), fn }
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function suite(name: string, arms: Arm[]): void {
|
|
105
|
-
step('suite: ' + name + ' warmup')
|
|
106
|
-
for (const a of arms) a.fn(a.n) // 预热各 1 轮
|
|
107
|
-
const times: number[][] = arms.map(() => [])
|
|
108
|
-
for (let r = 0; r < ROUNDS; r++) {
|
|
109
|
-
// 臂序按 r 轮转起跑,抵消顺序性偏置(温度/调度漂移)
|
|
110
|
-
for (let k = 0; k < arms.length; k++) {
|
|
111
|
-
const i = (r + k) % arms.length
|
|
112
|
-
times[i]!.push(arms[i]!.fn(arms[i]!.n))
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
// 每轮原始数据只 dump 前 3 轮(15 轮全打没意义,log 只留诊断线索)
|
|
116
|
-
for (let r = 0; r < Math.min(ROUNDS, 3); r++) {
|
|
117
|
-
step(' round ' + String(r) + ': ' +
|
|
118
|
-
arms.map((a, i) => a.label + '=' + String(times[i]![r]) + 'ms').join(' '))
|
|
119
|
-
}
|
|
120
|
-
const ns = (i: number, ms: number) => (ms * 1e6 / arms[i]!.n).toFixed(1)
|
|
121
|
-
// median 是主指标(离群稳健);min 留作参照
|
|
122
|
-
const meds = times.map(med)
|
|
123
|
-
const mins = times.map(t => Math.min(...t))
|
|
124
|
-
step(' ** median: ' + arms.map((a, i) => a.label + '=' + ns(i, meds[i]!) + 'ns').join(' | '))
|
|
125
|
-
step(' min: ' + arms.map((a, i) => a.label + '=' + ns(i, mins[i]!) + 'ns').join(' | '))
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
step('bench_ffi_overhead (ours): per-arm calib ~' + String(TARGET_MS) + 'ms, ' +
|
|
129
|
-
String(ROUNDS) + 'r (median 主指标), 对照 test/bench_nodeffi.mjs')
|
|
130
|
-
suite('S0 nop0 0参 void 纯往返', [mkArm('ours', runNop0)])
|
|
131
|
-
suite('S1 nop1 1×i32 封送往返', [mkArm('ours', runNop1)])
|
|
132
|
-
suite('S2 GetCurrentProcessId 0参真实API', [mkArm('ours', runPid)])
|
|
133
|
-
suite('S3 SendMessageW WM_NULL 自有窗口 (gui=直调地板)', [
|
|
134
|
-
mkArm('ours', runSm),
|
|
135
|
-
mkArm('gui ', runGuiSm),
|
|
136
|
-
])
|
|
137
|
-
|
|
138
|
-
gui.DestroyWindow(win)
|
|
139
|
-
step('DONE')
|
|
140
|
-
if (log) log.close()
|
|
141
|
-
std.exit(0)
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
// 一次性性能 bench:gui.SendMessage(C 模块直调) vs FFI bind SendMessageW(libffi trampoline)
|
|
2
|
-
// 不入 CI。用法:make js 后 POST /exec {"cmd":"qwin.exe test/bench_sendmessage.js"}
|
|
3
|
-
// 每场景预热 1 轮 + 5 轮计数(奇偶交叉起跑顺序),取各自最小值降抖动。
|
|
4
|
-
// 注意:全部消息发给自有窗口(直调 wndproc)——跨进程消息走消息经纪,几十 μs/次且
|
|
5
|
-
// IPC 开销掩盖入口链差异,不作测点。结束必须 DestroyWindow + std.exit:留活窗会让
|
|
6
|
-
// qwin 主流程驻留消息循环,exec 永不返回。
|
|
7
|
-
import * as std from 'std';
|
|
8
|
-
import * as gui from 'gui';
|
|
9
|
-
import { SendMessage } from '../lib/windows/user32.js';
|
|
10
|
-
import { WCHAR } from '../lib/ffi/bind.js';
|
|
11
|
-
import { PtrArrayBuffer } from '../lib/ffi/ctype.js';
|
|
12
|
-
const N = 500_000;
|
|
13
|
-
// S2 含 WCHAR.encode 每次分配:500k 时 GC 抖动把单轮拖到 190s+(实测 ~380μs/迭代),
|
|
14
|
-
// 降到 50k——统计上仍远超噪声需求
|
|
15
|
-
const N_S2 = 50_000;
|
|
16
|
-
const ROUNDS = 5;
|
|
17
|
-
const ROUNDS_S2 = 3;
|
|
18
|
-
const WM_SETTEXT = 0x000c;
|
|
19
|
-
const WM_GETTEXT = 0x000d;
|
|
20
|
-
// 文件日志(cwd=Z:\quickwin,宿主机 _build/bench_log.txt 可实时读)——exec 只在进程
|
|
21
|
-
// 退出后回包,挂了靠它定位卡点
|
|
22
|
-
const log = std.open('bench_log.txt', 'w');
|
|
23
|
-
function step(msg) {
|
|
24
|
-
std.printf('%s\n', msg);
|
|
25
|
-
if (log) {
|
|
26
|
-
log.puts(msg + '\n');
|
|
27
|
-
log.flush();
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
step('stage: imports ok');
|
|
31
|
-
const winRaw = gui.CreateWindow('STATIC', 'bench-title', 0, 0, 0, 20, 20, null, null);
|
|
32
|
-
if (!winRaw)
|
|
33
|
-
throw new Error('CreateWindow(STATIC) failed');
|
|
34
|
-
const win = winRaw;
|
|
35
|
-
step('stage: window created');
|
|
36
|
-
// 两侧入口各自提为常量(循环内属性查找开销对齐)
|
|
37
|
-
const gSM = gui.SendMessage;
|
|
38
|
-
const fSM = SendMessage;
|
|
39
|
-
const TEXT = 'bench';
|
|
40
|
-
const buf = new PtrArrayBuffer(64);
|
|
41
|
-
// —— S1(WM_NULL 纯数字,最纯入口链开销):实现已随首轮跑数移除,数据见 .agents/SENDMESSAGE_BENCH.md ——
|
|
42
|
-
// —— S2:WM_SETTEXT 字符串 lParam(任务 3 多态点的真实成本形态):
|
|
43
|
-
// gui 侧 C 内编码;FFI 侧调用点 WCHAR.encode 在循环内(迁移后的真实写法)——
|
|
44
|
-
function s2g() {
|
|
45
|
-
const t0 = Date.now();
|
|
46
|
-
for (let i = 0; i < N_S2; i++)
|
|
47
|
-
gSM(win, WM_SETTEXT, 0, TEXT);
|
|
48
|
-
return Date.now() - t0;
|
|
49
|
-
}
|
|
50
|
-
function s2f() {
|
|
51
|
-
const t0 = Date.now();
|
|
52
|
-
for (let i = 0; i < N_S2; i++) {
|
|
53
|
-
const w = WCHAR.encode(TEXT);
|
|
54
|
-
fSM(win, WM_SETTEXT, 0, w.ptr);
|
|
55
|
-
}
|
|
56
|
-
return Date.now() - t0;
|
|
57
|
-
}
|
|
58
|
-
// —— S3:WM_GETTEXT 缓冲出参往返,两侧 JS 实参完全相同(纯入口差 + C 侧拷贝共模)——
|
|
59
|
-
function s3g() {
|
|
60
|
-
const t0 = Date.now();
|
|
61
|
-
for (let i = 0; i < N; i++)
|
|
62
|
-
gSM(win, WM_GETTEXT, 64, buf.ptr);
|
|
63
|
-
return Date.now() - t0;
|
|
64
|
-
}
|
|
65
|
-
function s3f() {
|
|
66
|
-
const t0 = Date.now();
|
|
67
|
-
for (let i = 0; i < N; i++)
|
|
68
|
-
fSM(win, WM_GETTEXT, 64, buf.ptr);
|
|
69
|
-
return Date.now() - t0;
|
|
70
|
-
}
|
|
71
|
-
function suite(name, n, rounds, rg, rf) {
|
|
72
|
-
step('suite: ' + name + ' warmup');
|
|
73
|
-
rg();
|
|
74
|
-
rf(); // 预热各 1 轮
|
|
75
|
-
const gs = [];
|
|
76
|
-
const fs = [];
|
|
77
|
-
for (let r = 0; r < rounds; r++) {
|
|
78
|
-
// 奇偶交叉起跑顺序,抵消顺序性偏置(温度/调度漂移)
|
|
79
|
-
if (r % 2 === 0) {
|
|
80
|
-
gs.push(rg());
|
|
81
|
-
fs.push(rf());
|
|
82
|
-
}
|
|
83
|
-
else {
|
|
84
|
-
fs.push(rf());
|
|
85
|
-
gs.push(rg());
|
|
86
|
-
}
|
|
87
|
-
step(' round ' + String(r) + ': gui=' + String(gs[gs.length - 1]) +
|
|
88
|
-
'ms ffi=' + String(fs[fs.length - 1]) + 'ms');
|
|
89
|
-
}
|
|
90
|
-
const gmin = Math.min(...gs), fmin = Math.min(...fs);
|
|
91
|
-
const ns = (ms) => (ms * 1e6 / n).toFixed(0);
|
|
92
|
-
step(' ==> gui min=' + String(gmin) + 'ms (' + ns(gmin) + ' ns/call), ffi min=' +
|
|
93
|
-
String(fmin) + 'ms (' + ns(fmin) + ' ns/call), ffi/gui=' + (fmin / gmin).toFixed(2) +
|
|
94
|
-
'x, delta=' + ns(fmin - gmin) + 'ns');
|
|
95
|
-
}
|
|
96
|
-
step('bench_sendmessage: round2 (S1 done in round1), S2 n=' + String(N_S2) +
|
|
97
|
-
'/' + String(ROUNDS_S2) + 'r, S3 n=' + String(N) + '/' + String(ROUNDS) + 'r (min)');
|
|
98
|
-
suite('S2 WM_SETTEXT 字符串 lParam (FFI 侧含 WCHAR.encode)', N_S2, ROUNDS_S2, s2g, s2f);
|
|
99
|
-
suite('S3 WM_GETTEXT 缓冲出参往返 (两侧实参相同)', N, ROUNDS, s3g, s3f);
|
|
100
|
-
gui.DestroyWindow(win);
|
|
101
|
-
step('DONE');
|
|
102
|
-
if (log)
|
|
103
|
-
log.close();
|
|
104
|
-
std.exit(0);
|