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.
Files changed (163) hide show
  1. package/README.en.md +3 -0
  2. package/README.md +2 -0
  3. package/package.json +2 -3
  4. package/qwin-nowasm-x86.exe +0 -0
  5. package/qwin-nowasm.exe +0 -0
  6. package/qwin-x86.exe +0 -0
  7. package/qwin.exe +0 -0
  8. package/examples/PdfCanvas.js +0 -38
  9. package/examples/PdfCanvas.tsx +0 -54
  10. package/examples/execd/main.js +0 -23379
  11. package/examples/execd/tray.js +0 -192
  12. package/examples/execd/worker.js +0 -122
  13. package/examples/pdf_preview2.js +0 -398
  14. package/examples/pdf_preview2.ts +0 -353
  15. package/examples/pdf_viewer.js +0 -22515
  16. package/examples/pdf_viewer.tsx +0 -228
  17. package/examples/test_gallery.js +0 -18692
  18. package/examples/test_gallery.tsx +0 -291
  19. package/examples/test_react_lazy.js +0 -18560
  20. package/examples/test_react_lazy.tsx +0 -65
  21. package/examples/test_react_listbox.js +0 -18562
  22. package/examples/test_react_listbox.tsx +0 -73
  23. package/examples/test_root_window.js +0 -18532
  24. package/examples/test_root_window.tsx +0 -41
  25. package/examples/test_tab.js +0 -18563
  26. package/examples/test_tab.tsx +0 -77
  27. package/examples/tray_demo.js +0 -86
  28. package/examples/tray_demo.tsx +0 -89
  29. package/test/add.wasm +0 -0
  30. package/test/bench_ffi_callraw.js +0 -235
  31. package/test/bench_ffi_callraw.ts +0 -235
  32. package/test/bench_ffi_overhead.js +0 -139
  33. package/test/bench_ffi_overhead.ts +0 -141
  34. package/test/bench_sendmessage.js +0 -104
  35. package/test/bench_sendmessage.ts +0 -100
  36. package/test/bench_sendmessage_nonnull.js +0 -187
  37. package/test/bench_sendmessage_nonnull.ts +0 -188
  38. package/test/complex.wasm +0 -0
  39. package/test/complex_imports.wasm +0 -0
  40. package/test/debug/http_server_e2e.js +0 -31
  41. package/test/debug/minwasm.js +0 -52
  42. package/test/debug/mupdf_debug.js +0 -86
  43. package/test/debug/wasm.js +0 -122
  44. package/test/debug/wasm_export_debug.js +0 -54
  45. package/test/debug/wasm_names.js +0 -36
  46. package/test/example.pdf +0 -0
  47. package/test/global_imports.wasm +0 -0
  48. package/test/import_func.wasm +0 -0
  49. package/test/imports.wasm +0 -0
  50. package/test/mupdf-cdn.js +0 -40
  51. package/test/mupdf-cdn.ts +0 -43
  52. package/test/run.js +0 -111
  53. package/test/run.ts +0 -116
  54. package/test/sjlj.wasm +0 -0
  55. package/test/test_basic.js +0 -7
  56. package/test/test_basic.ts +0 -9
  57. package/test/test_basic_fetch.js +0 -63
  58. package/test/test_basic_fetch.ts +0 -59
  59. package/test/test_brotli.js +0 -48
  60. package/test/test_brotli.ts +0 -52
  61. package/test/test_fetch_cache.js +0 -169
  62. package/test/test_fetch_cache.ts +0 -187
  63. package/test/test_ffi.js +0 -157
  64. package/test/test_ffi.ts +0 -170
  65. package/test/test_ffi_abi.js +0 -290
  66. package/test/test_ffi_abi.ts +0 -274
  67. package/test/test_ffi_array.js +0 -116
  68. package/test/test_ffi_array.ts +0 -141
  69. package/test/test_ffi_bind.js +0 -656
  70. package/test/test_ffi_bind.ts +0 -670
  71. package/test/test_ffi_struct.js +0 -779
  72. package/test/test_ffi_struct.ts +0 -828
  73. package/test/test_ffi_struct_layout.js +0 -495
  74. package/test/test_ffi_struct_layout.ts +0 -543
  75. package/test/test_ffi_user32.js +0 -103
  76. package/test/test_ffi_user32.ts +0 -113
  77. package/test/test_ffi_windows.js +0 -172
  78. package/test/test_ffi_windows.ts +0 -187
  79. package/test/test_files.js +0 -102
  80. package/test/test_files.ts +0 -121
  81. package/test/test_frame_encoding.js +0 -128
  82. package/test/test_frame_encoding.ts +0 -132
  83. package/test/test_gdi_draw.js +0 -681
  84. package/test/test_gdi_draw.ts +0 -725
  85. package/test/test_helper.js +0 -91
  86. package/test/test_helper.ts +0 -88
  87. package/test/test_http_import.js +0 -96
  88. package/test/test_http_import.ts +0 -93
  89. package/test/test_http_server.js +0 -214
  90. package/test/test_http_server.ts +0 -211
  91. package/test/test_import_dir.js +0 -4
  92. package/test/test_import_dir.ts +0 -5
  93. package/test/test_ipv6.js +0 -134
  94. package/test/test_ipv6.ts +0 -130
  95. package/test/test_jsx.js +0 -1314
  96. package/test/test_meta_url.js +0 -3
  97. package/test/test_meta_url.ts +0 -4
  98. package/test/test_mupdf_render.js +0 -51
  99. package/test/test_mupdf_render.ts +0 -55
  100. package/test/test_mupdf_twice.js +0 -62
  101. package/test/test_mupdf_twice.ts +0 -65
  102. package/test/test_mupdf_wasm.js +0 -39
  103. package/test/test_mupdf_wasm.ts +0 -32
  104. package/test/test_net_event.js +0 -57
  105. package/test/test_net_event.ts +0 -56
  106. package/test/test_net_fetch.js +0 -384
  107. package/test/test_net_fetch.ts +0 -373
  108. package/test/test_net_websocket.js +0 -180
  109. package/test/test_net_websocket.ts +0 -164
  110. package/test/test_polyfill.js +0 -144
  111. package/test/test_polyfill.ts +0 -159
  112. package/test/test_printer.js +0 -137
  113. package/test/test_printer.ts +0 -148
  114. package/test/test_proc.js +0 -141
  115. package/test/test_proc.ts +0 -163
  116. package/test/test_react_complex.js +0 -18678
  117. package/test/test_react_counter.js +0 -18555
  118. package/test/test_react_flex.js +0 -18871
  119. package/test/test_react_hidden.js +0 -18560
  120. package/test/test_react_listview_link.js +0 -18590
  121. package/test/test_react_notify.js +0 -18797
  122. package/test/test_react_render.js +0 -18550
  123. package/test/test_registry.js +0 -74
  124. package/test/test_registry.ts +0 -95
  125. package/test/test_sysinfo.js +0 -122
  126. package/test/test_sysinfo.ts +0 -141
  127. package/test/test_url.js +0 -173
  128. package/test/test_url.ts +0 -183
  129. package/test/test_wasm_basic.js +0 -85
  130. package/test/test_wasm_basic.ts +0 -73
  131. package/test/test_wasm_import_global.js +0 -41
  132. package/test/test_wasm_import_global.ts +0 -45
  133. package/test/test_wasm_sjlj.js +0 -145
  134. package/test/test_wasm_sjlj.ts +0 -125
  135. package/test/test_wasm_types.js +0 -97
  136. package/test/test_wasm_types.ts +0 -124
  137. package/test/test_wolfssl_read.js +0 -203
  138. package/test/test_wolfssl_read.ts +0 -184
  139. package/test/test_worker.js +0 -21
  140. package/test/test_worker.ts +0 -24
  141. package/test/test_worker_concurrent.js +0 -45
  142. package/test/test_worker_concurrent.ts +0 -56
  143. package/test/test_worker_data_url.js +0 -46
  144. package/test/test_worker_data_url.ts +0 -51
  145. package/test/test_worker_fetch.js +0 -26
  146. package/test/test_worker_fetch.ts +0 -27
  147. package/test/test_worker_net.js +0 -69
  148. package/test/test_worker_net.ts +0 -66
  149. package/test/test_worker_wasm_concurrent.js +0 -44
  150. package/test/test_worker_wasm_concurrent.ts +0 -45
  151. package/test/types.wasm +0 -0
  152. package/test/worker_fetch.js +0 -13
  153. package/test/worker_fetch.ts +0 -13
  154. package/test/worker_helper.js +0 -1
  155. package/test/worker_helper.ts +0 -1
  156. package/test/worker_http.js +0 -18
  157. package/test/worker_http.ts +0 -20
  158. package/test/worker_import_lib.js +0 -12
  159. package/test/worker_import_lib.ts +0 -13
  160. package/test/worker_module.js +0 -16
  161. package/test/worker_module.ts +0 -17
  162. package/test/worker_wasm_concurrent.js +0 -22
  163. 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);