chore: 初始化 ViperApps 仓库
纳入 11 个示例工程(不含 ViperOS,另行开专题)的源码:已完成新语法迁移(R1-R16 机械规则与 R1/R12 改写)。.gitignore 排除 ViperOS/、各工程编译产物(.tpv_build/.native_build/output/temp)、二进制与 ViperMNIST/data 数据集。
This commit is contained in:
commit
df8e301720
54 files changed
+12112
No files matched your search
+35
@@ -0,0 +1,35 @@
|
||||
# ViperOS 另行开专题,不入本仓库(其自身忽略规则见 ViperOS/.gitignore)
|
||||
ViperOS/
|
||||
|
||||
# 编译产物
|
||||
**/.tpv_build/
|
||||
**/.native_build/
|
||||
**/output/
|
||||
**/output_mlir/
|
||||
**/temp/
|
||||
**/temp_mlir/
|
||||
*.o
|
||||
*.obj
|
||||
*.exe
|
||||
*.dll
|
||||
*.dll.a
|
||||
*.lib
|
||||
*.a
|
||||
*.so
|
||||
*.elf
|
||||
*.ll
|
||||
*.img
|
||||
*.iso
|
||||
*.fd
|
||||
*.log
|
||||
|
||||
# 数据集(MNIST 二进制,约 46MB,运行前另行准备)
|
||||
ViperMNIST/data/
|
||||
|
||||
# Python / IDE / 缓存
|
||||
**/__pycache__/
|
||||
*.py[cod]
|
||||
.cache/
|
||||
.vscode/
|
||||
.idea/
|
||||
.trae/
|
||||
@@ -0,0 +1,516 @@
|
||||
# ============================================================
|
||||
# main.vp — ViperAIChat:DeepSeek/OpenAI 兼容的桌面聊天客户端
|
||||
#
|
||||
# UI(微信/QQ 聊天气泡风格 + 深色主题,全局微软雅黑):
|
||||
# 1) 顶部条:标题 + 右侧【设置】按钮(点按展开/收起配置面板)
|
||||
# 2) 配置面板(默认收起):API Key(QLineEdit 密码回显,留空由用户填)、
|
||||
# 模型名(默认 deepseek-v4-flash)、API 地址(默认 DeepSeek 端点)
|
||||
# 3) 中部:QListWidget 气泡聊天区(自己=右侧绿色气泡,AI=左侧深灰气泡,
|
||||
# 错误=左侧暗红气泡),带滚动
|
||||
# 4) 底部输入条:输入框 +【发送】+【清空】
|
||||
# 5) 发送 = 异步(vthreading 后台线程)+ SSE 流式(requests/https
|
||||
# post_stream,stream:true):AI 回复逐段刷新进同一条气泡;
|
||||
# 期间【发送】禁用,QTimer 主线程轮询增量。
|
||||
#
|
||||
# 说明:气泡行由 vqt6 QListWidget.addBubble()/replaceLastBubble() 提供,
|
||||
# 需重建 vqt6 静态库 includes/vqt6/build/lib/libvqt6_bridge.a。
|
||||
# ============================================================
|
||||
|
||||
import t
|
||||
from stdint import *
|
||||
import stdlib
|
||||
import stdio
|
||||
import string
|
||||
import memhub
|
||||
import vqt6
|
||||
import vthreading
|
||||
import requests
|
||||
import requests.https as rhttps
|
||||
import json
|
||||
import w32.win32memory as win32mem
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 常量
|
||||
# ============================================================
|
||||
# CA bundle(HTTPS 证书链校验用)
|
||||
CA_BUNDLE: str = "D:/msys64/mingw64/etc/ssl/certs/ca-bundle.crt"
|
||||
DEFAULT_MODEL: str = "deepseek-v4-flash"
|
||||
DEFAULT_API_URL: str = "https://api.deepseek.com/chat/completions"
|
||||
|
||||
# 气泡角色
|
||||
define WHO_USER = 1 # 自己:右侧绿色气泡
|
||||
define WHO_AI = 0 # AI:左侧深灰气泡
|
||||
define WHO_SYS = 2 # 系统/错误:左侧暗红气泡
|
||||
|
||||
# 气泡最大宽度(超出自动换行)
|
||||
define BUBBLE_MAX_W = 430
|
||||
|
||||
# 全局字体:微软雅黑(作用于整窗所有控件,含气泡)
|
||||
APP_FONT_QSS: str = '* { font-family: "Microsoft YaHei", "PingFang SC", sans-serif; }'
|
||||
|
||||
# 气泡 QSS(与 addBubble 的 hpad/vpad 参数匹配)
|
||||
USER_QSS: str = "background-color:#95EC69;color:#111111;border-radius:10px;padding:8px 14px;"
|
||||
AI_QSS: str = "background-color:#3A3C40;color:#ECECEC;border:1px solid #4A4C50;border-radius:10px;padding:8px 14px;"
|
||||
SYS_QSS: str = "background-color:#5C2E2E;color:#FFB4B4;border-radius:10px;padding:6px 12px;"
|
||||
|
||||
# 请求/回复缓冲上限(与服务端流式回复体量对应)
|
||||
define CUR_MSG_CAP = 8192
|
||||
define RESP_CAP = 262144
|
||||
define PULL_CAP = 8192
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 模块级全局
|
||||
# ============================================================
|
||||
_mbuddy: memhub.MemBuddy* = None # requests/https 内存池
|
||||
_json_pool: memhub.MemPool* = None # json 解析内存池(保留:错误回退解析等)
|
||||
|
||||
chat_list: vqt6.QListWidget* = None
|
||||
input_edit: vqt6.QLineEdit* = None
|
||||
key_edit: vqt6.QLineEdit* = None
|
||||
model_edit: vqt6.QLineEdit* = None
|
||||
url_edit: vqt6.QLineEdit* = None
|
||||
settings_panel: vqt6.QWidget* = None
|
||||
settings_btn: vqt6.QPushButton* = None
|
||||
|
||||
settings_open: int = 0
|
||||
|
||||
# ---- 异步流式状态 ----
|
||||
streaming_active: int = 0 # 1 = 后台线程流式请求中
|
||||
worker_handle: t.CPtr = None # 后台线程句柄(结束时等待并关闭)
|
||||
send_ptr: t.CPtr = None # 发送按钮原始指针(流式期间禁用)
|
||||
acc_len: sizet = 0 # 已累积的回复内容长度(resp_acc)
|
||||
resp_acc: t.CArray[char, 262144] # AI 流式回复累积文本(UI 线程写)
|
||||
disp_buf: t.CArray[char, 262144] # 刷新用(裁剪掉末尾不完整 UTF-8 字符)
|
||||
pull_tmp: t.CArray[char, 8192] # 拉取增量用临时缓冲
|
||||
cur_url: t.CArray[char, 2048] # 供后台线程读取的请求参数
|
||||
cur_key: t.CArray[char, 2048]
|
||||
cur_model: t.CArray[char, 256]
|
||||
cur_msg: t.CArray[char, 8192]
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 工具函数
|
||||
# ============================================================
|
||||
def AddBubble(msg: str, who: int):
|
||||
"""追加一条气泡。who: WHO_USER/WHO_AI/WHO_SYS。"""
|
||||
if msg is None or string.strlen(msg) == 0:
|
||||
msg = " "
|
||||
if who == WHO_USER:
|
||||
chat_list.addBubble(msg, USER_QSS, 1, BUBBLE_MAX_W, 28, 16)
|
||||
elif who == WHO_SYS:
|
||||
chat_list.addBubble(msg, SYS_QSS, 0, BUBBLE_MAX_W, 12, 12)
|
||||
else:
|
||||
chat_list.addBubble(msg, AI_QSS, 0, BUBBLE_MAX_W, 28, 16)
|
||||
|
||||
|
||||
def ReplaceStreamBubble(msg: str):
|
||||
"""把最后一条气泡替换为最新流式回复文本(AI 气泡样式)。"""
|
||||
if msg is None or string.strlen(msg) == 0:
|
||||
msg = "…"
|
||||
chat_list.replaceLastBubble(msg, AI_QSS, 0, BUBBLE_MAX_W, 28, 16)
|
||||
|
||||
|
||||
def StreamRefresh():
|
||||
"""把累积回复裁剪掉末尾不完整 UTF-8 字符后刷新气泡(防流式切字显示 �)。"""
|
||||
global acc_len
|
||||
if acc_len == 0:
|
||||
return
|
||||
end: sizet = acc_len
|
||||
while end > 0:
|
||||
s: sizet = end - 1
|
||||
while s > 0 and (resp_acc[s] & 0xC0) == 0x80:
|
||||
s -= 1
|
||||
lead: char = resp_acc[s]
|
||||
need: sizet = 1
|
||||
if (lead & 0xE0) == 0xC0:
|
||||
need = 2
|
||||
elif (lead & 0xF0) == 0xE0:
|
||||
need = 3
|
||||
elif (lead & 0xF8) == 0xF0:
|
||||
need = 4
|
||||
if end - s >= need:
|
||||
break
|
||||
end = s
|
||||
if end == 0:
|
||||
return # 全是半截字符,等后续字节再显示
|
||||
for j in range(end):
|
||||
disp_buf[j] = resp_acc[j]
|
||||
disp_buf[end] = 0
|
||||
ReplaceStreamBubble(disp_buf)
|
||||
|
||||
|
||||
def JsonEscape(src: str, dst: str, cap: int) -> int:
|
||||
"""把 src 转义为可放入 JSON 字符串字面量的内容写入 dst(\\ " \\n \\r \\t)。"""
|
||||
if src is None:
|
||||
src = ""
|
||||
if dst is None or cap <= 0:
|
||||
return -1
|
||||
i: int = 0
|
||||
w: int = 0
|
||||
while src[i] != 0:
|
||||
ch: char = src[i]
|
||||
if ch == '"' or ch == '\\':
|
||||
if w + 2 >= cap: return -1
|
||||
dst[w] = '\\'
|
||||
dst[w + 1] = ch
|
||||
w += 2
|
||||
elif ch == '\n':
|
||||
if w + 2 >= cap: return -1
|
||||
dst[w] = '\\'
|
||||
dst[w + 1] = 'n'
|
||||
w += 2
|
||||
elif ch == '\r':
|
||||
if w + 2 >= cap: return -1
|
||||
dst[w] = '\\'
|
||||
dst[w + 1] = 'r'
|
||||
w += 2
|
||||
elif ch == '\t':
|
||||
if w + 2 >= cap: return -1
|
||||
dst[w] = '\\'
|
||||
dst[w + 1] = 't'
|
||||
w += 2
|
||||
else:
|
||||
if w + 1 >= cap: return -1
|
||||
dst[w] = ch
|
||||
w += 1
|
||||
i += 1
|
||||
if w + 1 >= cap:
|
||||
return -1
|
||||
dst[w] = 0
|
||||
return 0
|
||||
|
||||
|
||||
def _CopyStr(dst: str, src: str, cap: int):
|
||||
"""拷贝字符串到定长缓冲(自动补 0)。"""
|
||||
if dst is None or cap <= 0:
|
||||
return
|
||||
n: int = 0
|
||||
if src is not None:
|
||||
while n < cap - 1 and src[n] != 0:
|
||||
dst[n] = src[n]
|
||||
n += 1
|
||||
dst[n] = 0
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 回调
|
||||
# ============================================================
|
||||
def OnToggleSettings(slot_id: int, data: t.CPtr):
|
||||
"""【设置】按钮:展开/收起顶部配置面板。"""
|
||||
global settings_open
|
||||
settings_open = 1 - settings_open
|
||||
if settings_open != 0:
|
||||
settings_panel.show()
|
||||
settings_btn.setText("收起配置")
|
||||
else:
|
||||
settings_panel.hide()
|
||||
settings_btn.setText("设置")
|
||||
|
||||
|
||||
def StreamWorker(arg: t.CPtr) -> t.CPtr:
|
||||
"""后台线程:发送 stream:true 请求并阻塞接收(SSE 增量由 https 库抽取)。"""
|
||||
# 读配置与消息(主线程已拷贝到模块数组)
|
||||
esc_buf: str = stdlib.malloc(CUR_MSG_CAP * 2 + 64)
|
||||
body_buf: str = stdlib.malloc(32768)
|
||||
auth_buf: str = stdlib.malloc(2048)
|
||||
if esc_buf is None or body_buf is None or auth_buf is None:
|
||||
if esc_buf is not None: stdlib.free(esc_buf)
|
||||
if body_buf is not None: stdlib.free(body_buf)
|
||||
if auth_buf is not None: stdlib.free(auth_buf)
|
||||
return t.CPtr(0)
|
||||
|
||||
if JsonEscape(cur_msg, esc_buf, CUR_MSG_CAP * 2 + 64) == 0:
|
||||
stdio.snprintf(auth_buf, 2048, "Bearer %s", cur_key)
|
||||
stdio.snprintf(body_buf, 32768,
|
||||
'{"model":"%s","messages":[{"role":"user","content":"%s"}],"stream":true}',
|
||||
cur_model, esc_buf)
|
||||
rhttps.post_stream(cur_url, body_buf, "application/json", True, CA_BUNDLE, auth_buf)
|
||||
|
||||
stdlib.free(esc_buf)
|
||||
stdlib.free(body_buf)
|
||||
stdlib.free(auth_buf)
|
||||
return t.CPtr(0)
|
||||
|
||||
|
||||
def OnSend(slot_id: int, data: t.CPtr):
|
||||
"""发送:校验配置 → 起后台线程做流式请求(不阻塞 UI)。"""
|
||||
global streaming_active, worker_handle, acc_len
|
||||
if streaming_active != 0:
|
||||
return # 上一轮还在回复中
|
||||
|
||||
msg: str = input_edit.text()
|
||||
if msg is None or string.strlen(msg) == 0:
|
||||
return
|
||||
|
||||
key_txt: str = key_edit.text()
|
||||
model_txt: str = model_edit.text()
|
||||
url_txt: str = url_edit.text()
|
||||
|
||||
if key_txt is None or string.strlen(key_txt) == 0:
|
||||
AddBubble("【配置错误】请先填写 API Key(点右上角【设置】)。", WHO_SYS)
|
||||
return
|
||||
if model_txt is None or string.strlen(model_txt) == 0:
|
||||
AddBubble("【配置错误】模型名不能为空。", WHO_SYS)
|
||||
return
|
||||
if url_txt is None or string.strlen(url_txt) == 0:
|
||||
AddBubble("【配置错误】API 地址不能为空。", WHO_SYS)
|
||||
return
|
||||
|
||||
input_edit.clear()
|
||||
AddBubble(msg, WHO_USER) # 自己:右侧绿色气泡
|
||||
|
||||
# 拷贝请求参数到模块数组(后台线程读;主线程此后再写前会等本轮结束)
|
||||
_CopyStr(cur_url, url_txt, 2048)
|
||||
_CopyStr(cur_key, key_txt, 2048)
|
||||
_CopyStr(cur_model, model_txt, 256)
|
||||
_CopyStr(cur_msg, msg, CUR_MSG_CAP)
|
||||
acc_len = 0
|
||||
resp_acc[0] = 0
|
||||
|
||||
# 流式回复占位气泡 + 起后台线程
|
||||
rhttps.stream_begin()
|
||||
AddBubble("…", WHO_AI)
|
||||
vqt6.vqt6_qwidget_set_enabled(send_ptr, 0) # 发送期间禁用
|
||||
streaming_active = 1
|
||||
|
||||
th = vthreading.Thread(StreamWorker, t.CPtr(0))
|
||||
th.start()
|
||||
worker_handle = t.CPtr(th.handle)
|
||||
|
||||
|
||||
def OnStreamTick(slot_id: int, data: t.CPtr):
|
||||
"""QTimer:主线程增量拉取流式内容并刷新 AI 气泡;结束时收尾。"""
|
||||
global streaming_active, acc_len, worker_handle
|
||||
if streaming_active == 0:
|
||||
return
|
||||
|
||||
# 拉取新内容
|
||||
n: INT = rhttps.stream_pull(pull_tmp, PULL_CAP)
|
||||
if n > 0:
|
||||
i: sizet = 0
|
||||
while i < sizet(n) and acc_len < sizet(RESP_CAP) - 1:
|
||||
resp_acc[acc_len] = pull_tmp[i]
|
||||
acc_len += 1
|
||||
i += 1
|
||||
resp_acc[acc_len] = 0
|
||||
StreamRefresh()
|
||||
|
||||
st: INT = rhttps.stream_state()
|
||||
if st == 0:
|
||||
return # 仍在接收
|
||||
|
||||
# ---- 结束:最终拉取 + 收尾 ----
|
||||
n2: INT = rhttps.stream_pull(pull_tmp, PULL_CAP)
|
||||
if n2 > 0:
|
||||
i2: sizet = 0
|
||||
while i2 < sizet(n2) and acc_len < sizet(RESP_CAP) - 1:
|
||||
resp_acc[acc_len] = pull_tmp[i2]
|
||||
acc_len += 1
|
||||
i2 += 1
|
||||
resp_acc[acc_len] = 0
|
||||
StreamRefresh()
|
||||
|
||||
if st == 1:
|
||||
if acc_len == 0:
|
||||
ReplaceStreamBubble("(空回复)")
|
||||
else:
|
||||
# 2 = 失败
|
||||
err_txt: str = rhttps.stream_error_text()
|
||||
if err_txt is None or string.strlen(err_txt) == 0:
|
||||
err_txt = rhttps.last_error()
|
||||
if err_txt is None or string.strlen(err_txt) == 0:
|
||||
err_txt = "未知网络/证书错误"
|
||||
ebuf: str = stdlib.malloc(1024)
|
||||
if ebuf is not None:
|
||||
stdio.snprintf(ebuf, 1024, "【请求失败】%s", err_txt)
|
||||
AddBubble(ebuf, WHO_SYS)
|
||||
stdlib.free(ebuf)
|
||||
else:
|
||||
AddBubble("【请求失败】", WHO_SYS)
|
||||
|
||||
streaming_active = 0
|
||||
vqt6.vqt6_qwidget_set_enabled(send_ptr, 1)
|
||||
if worker_handle is not None:
|
||||
vthreading.wait_handle(worker_handle, 2000)
|
||||
vthreading.close_handle(worker_handle)
|
||||
worker_handle = None
|
||||
|
||||
|
||||
def OnClearHistory(slot_id: int, data: t.CPtr):
|
||||
"""清空聊天历史(流式中提示不可清)。"""
|
||||
if streaming_active != 0:
|
||||
AddBubble("正在回复中,稍后再清空。", WHO_SYS)
|
||||
return
|
||||
chat_list.clear()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 主入口
|
||||
# ============================================================
|
||||
@t.CExport
|
||||
def main() -> int:
|
||||
global _mbuddy, _json_pool
|
||||
global chat_list, input_edit, key_edit, model_edit, url_edit
|
||||
global settings_panel, settings_btn, send_ptr
|
||||
|
||||
# ---- 内存池:requests(16MB) + json(4MB) ----
|
||||
http_arena: bytes = stdlib.malloc(16 * 1024 * 1024)
|
||||
if http_arena is None:
|
||||
return 1
|
||||
_mbuddy = memhub.MemBuddy(http_arena, 16 * 1024 * 1024)
|
||||
requests._mbuddy = _mbuddy
|
||||
|
||||
json_mem: t.CPtr = win32mem.VirtualAlloc(0, 4 * 1024 * 1024, 0x3000, 0x04)
|
||||
if json_mem is None:
|
||||
return 1
|
||||
_json_pool = memhub.MemPool(json_mem, 4 * 1024 * 1024)
|
||||
|
||||
# ---- 应用与主窗口 ----
|
||||
app = vqt6.QApplication([])
|
||||
win = vqt6.QMainWindow()
|
||||
win.setWindowTitle("Viper AI Chat")
|
||||
win.resize(900, 680)
|
||||
|
||||
central = vqt6.QWidget()
|
||||
win.setCentralWidget(central._ptr)
|
||||
|
||||
vroot = vqt6.QVBoxLayout(central)
|
||||
vroot.setContentsMargins(0, 0, 0, 0)
|
||||
vroot.setSpacing(0)
|
||||
|
||||
# ============================================================
|
||||
# 顶部条:标题 + 【设置】
|
||||
# ============================================================
|
||||
hdr = vqt6.QWidget()
|
||||
hdr.setStyleSheet("QWidget { background-color:#2A2B2E; border-bottom:1px solid #3F4045; }")
|
||||
hrow = vqt6.QHBoxLayout(hdr)
|
||||
hrow.setContentsMargins(14, 8, 12, 8)
|
||||
hrow.setSpacing(8)
|
||||
|
||||
title_lb = vqt6.QLabel("Viper AI Chat")
|
||||
title_lb.setStyleSheet("color:#F2F2F2;font-size:16px;font-weight:600;background:transparent;")
|
||||
hrow.addWidget(title_lb._ptr)
|
||||
hrow.addStretch(1)
|
||||
|
||||
_st_btn = vqt6.QPushButton("设置")
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(_st_btn._ptr,
|
||||
"QPushButton { background:#33353A; color:#B9C8F0; border:1px solid #4A4C52; border-radius:13px; padding:3px 14px; font-size:13px; }"
|
||||
" QPushButton:hover { background:#3E4046; } QPushButton:pressed { background:#484B52; }")
|
||||
_st_btn.clicked(OnToggleSettings, 0)
|
||||
hrow.addWidget(_st_btn._ptr)
|
||||
settings_btn = _st_btn
|
||||
vroot.addWidget(hdr._ptr)
|
||||
|
||||
# ============================================================
|
||||
# 配置面板(默认收起)
|
||||
# ============================================================
|
||||
_panel = vqt6.QWidget()
|
||||
_panel.setStyleSheet("QWidget { background-color:#222326; border-bottom:1px solid #3F4045; }")
|
||||
pv = vqt6.QVBoxLayout(_panel)
|
||||
pv.setContentsMargins(14, 8, 14, 10)
|
||||
pv.setSpacing(6)
|
||||
settings_panel = _panel
|
||||
|
||||
prow1 = vqt6.QHBoxLayout()
|
||||
plb1 = vqt6.QLabel("API Key:")
|
||||
plb1.setStyleSheet("color:#B8B8BC;font-size:13px;background:transparent;")
|
||||
prow1.addWidget(plb1._ptr)
|
||||
_key_le = vqt6.QLineEdit("")
|
||||
_key_le.setPlaceholderText("sk-... 填入 API Key(留空不默认写 Key)")
|
||||
vqt6.vqt6_qlineedit_set_echo_mode(_key_le._ptr, 2) # Password = 2
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(_key_le._ptr,
|
||||
"QLineEdit { background:#2E2F34; color:#ECECEC; border:1px solid #484A50; border-radius:6px; padding:4px 8px; font-size:13px; selection-background-color:#95EC69; selection-color:#111111; }")
|
||||
prow1.addWidget(_key_le._ptr)
|
||||
key_edit = _key_le
|
||||
pv.addLayout(prow1._ptr)
|
||||
|
||||
prow2 = vqt6.QHBoxLayout()
|
||||
plb2 = vqt6.QLabel("模型:")
|
||||
plb2.setStyleSheet("color:#B8B8BC;font-size:13px;background:transparent;")
|
||||
prow2.addWidget(plb2._ptr)
|
||||
_model_le = vqt6.QLineEdit(DEFAULT_MODEL)
|
||||
_model_le.setPlaceholderText("默认 deepseek-v4-flash")
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(_model_le._ptr,
|
||||
"QLineEdit { background:#2E2F34; color:#ECECEC; border:1px solid #484A50; border-radius:6px; padding:4px 8px; font-size:13px; selection-background-color:#95EC69; selection-color:#111111; }")
|
||||
prow2.addWidget(_model_le._ptr)
|
||||
model_edit = _model_le
|
||||
pv.addLayout(prow2._ptr)
|
||||
|
||||
prow3 = vqt6.QHBoxLayout()
|
||||
plb3 = vqt6.QLabel("API 地址:")
|
||||
plb3.setStyleSheet("color:#B8B8BC;font-size:13px;background:transparent;")
|
||||
prow3.addWidget(plb3._ptr)
|
||||
_url_le = vqt6.QLineEdit(DEFAULT_API_URL)
|
||||
_url_le.setPlaceholderText("https://.../chat/completions")
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(_url_le._ptr,
|
||||
"QLineEdit { background:#2E2F34; color:#ECECEC; border:1px solid #484A50; border-radius:6px; padding:4px 8px; font-size:13px; selection-background-color:#95EC69; selection-color:#111111; }")
|
||||
prow3.addWidget(_url_le._ptr)
|
||||
url_edit = _url_le
|
||||
pv.addLayout(prow3._ptr)
|
||||
|
||||
vroot.addWidget(_panel._ptr)
|
||||
_panel.hide()
|
||||
|
||||
# ============================================================
|
||||
# 中部:气泡聊天区(QListWidget)
|
||||
# ============================================================
|
||||
_chat = vqt6.QListWidget()
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(_chat._ptr,
|
||||
"QListWidget { background-color:#161719; border:none; outline:0; }"
|
||||
" QListWidget::item { background:transparent; border:none; }"
|
||||
" QListWidget::item:hover { background:transparent; }"
|
||||
" QListWidget::item:selected { background:transparent; color:#ECECEC; }"
|
||||
" QScrollBar:vertical { background:#161719; width:8px; margin:0; }"
|
||||
" QScrollBar::handle:vertical { background:#4A4B50; border-radius:4px; min-height:30px; }"
|
||||
" QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height:0px; }"
|
||||
" QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background:#161719; }")
|
||||
vroot.addWidget(_chat._ptr)
|
||||
chat_list = _chat
|
||||
|
||||
# ============================================================
|
||||
# 底部输入条
|
||||
# ============================================================
|
||||
inbar = vqt6.QWidget()
|
||||
inbar.setStyleSheet("QWidget { background-color:#2A2B2E; border-top:1px solid #3F4045; }")
|
||||
irow = vqt6.QHBoxLayout(inbar)
|
||||
irow.setContentsMargins(12, 8, 12, 8)
|
||||
irow.setSpacing(8)
|
||||
|
||||
_in_le = vqt6.QLineEdit("")
|
||||
_in_le.setPlaceholderText("输入消息,点击【发送】")
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(_in_le._ptr,
|
||||
"QLineEdit { background:#2E2F34; color:#ECECEC; border:1px solid #484A50; border-radius:18px; padding:6px 14px; font-size:14px; selection-background-color:#95EC69; selection-color:#111111; }")
|
||||
irow.addWidget(_in_le._ptr)
|
||||
input_edit = _in_le
|
||||
|
||||
send_btn = vqt6.QPushButton("发送")
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(send_btn._ptr,
|
||||
"QPushButton { background-color:#07C160; color:#FFFFFF; border:none; border-radius:18px; padding:6px 26px; font-size:14px; font-weight:600; }"
|
||||
" QPushButton:hover { background-color:#06AD56; } QPushButton:pressed { background-color:#059B4C; }")
|
||||
send_btn.clicked(OnSend, 0)
|
||||
irow.addWidget(send_btn._ptr)
|
||||
send_ptr = t.CPtr(send_btn._ptr)
|
||||
|
||||
clear_btn = vqt6.QPushButton("清空")
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(clear_btn._ptr,
|
||||
"QPushButton { background:transparent; color:#8A8B90; border:none; padding:0 8px; font-size:13px; }"
|
||||
" QPushButton:hover { color:#C0C4F0; }")
|
||||
clear_btn.clicked(OnClearHistory, 0)
|
||||
irow.addWidget(clear_btn._ptr)
|
||||
vroot.addWidget(inbar._ptr)
|
||||
|
||||
# ---- 流式刷新定时器 ----
|
||||
_st_timer = vqt6.QTimer()
|
||||
_st_timer.timeout(OnStreamTick, 0)
|
||||
_st_timer.start(40)
|
||||
|
||||
# ---- 全局微软雅黑 ----
|
||||
central.setStyleSheet(APP_FONT_QSS)
|
||||
|
||||
win.show()
|
||||
|
||||
AddBubble("你好,我是 Viper AI。点右上角【设置】填入 API Key 后即可开始对话。", WHO_AI)
|
||||
|
||||
return app.exec()
|
||||
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"name": "ViperAIChat - DeepSeek/OpenAI compatible chat client (vqt6 + requests + json)",
|
||||
"version": "1.0.0",
|
||||
"description": "Desktop chat client: vqt6 GUI, requests(https) for network, json for parsing responses",
|
||||
"source_dir": "./App",
|
||||
"build_dir": "./.tpv_build",
|
||||
"compiler": {
|
||||
"cmd": "llc",
|
||||
"flags": [
|
||||
"-filetype=obj",
|
||||
"-relocation-model=pic"
|
||||
]
|
||||
},
|
||||
"linker": {
|
||||
"cmd": "D:/msys64/mingw64/bin/clang++.exe",
|
||||
"flags": [
|
||||
"-fuse-ld=lld",
|
||||
"-lmsvcrt",
|
||||
"-lpthread",
|
||||
"-lmingwex",
|
||||
"-lkernel32",
|
||||
"-lws2_32",
|
||||
"-ldnsapi",
|
||||
"-liphlpapi",
|
||||
"-lgdi32",
|
||||
"-lcrypt32",
|
||||
"-ladvapi32",
|
||||
"-L", "D:/Qt/6.11.2/mingw_64/lib",
|
||||
"-lQt6Core",
|
||||
"-lQt6Gui",
|
||||
"-lQt6Widgets",
|
||||
"-lQt6Network",
|
||||
"-lQt6Charts",
|
||||
"-lws2_32",
|
||||
"-lcrypt32",
|
||||
"-ladvapi32",
|
||||
"-liphlpapi",
|
||||
"-ldnsapi"
|
||||
],
|
||||
"static_libs": [
|
||||
"../../includes/vqt6/build/lib/libvqt6_bridge.a",
|
||||
"../../dlls/lib/libssl.a",
|
||||
"../../dlls/lib/libcrypto.a"
|
||||
],
|
||||
"output": "app.exe"
|
||||
},
|
||||
"includes": [
|
||||
"../../includes"
|
||||
],
|
||||
"target": {
|
||||
"triple": "x86_64-pc-windows-gnu",
|
||||
"datalayout": "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
|
||||
},
|
||||
"options": {
|
||||
"slice_level": 3,
|
||||
"sha1_slice_level": 1,
|
||||
"target": "llvm",
|
||||
"strict_mode": true
|
||||
},
|
||||
"runtime": {
|
||||
"qt6_path": "D:/Qt/6.11.2/mingw_64",
|
||||
"qt6_dlls": ["Qt6Core.dll", "Qt6Gui.dll", "Qt6Widgets.dll", "Qt6Network.dll", "Qt6Charts.dll", "Qt6OpenGL.dll", "Qt6OpenGLWidgets.dll"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
# ============================================================
|
||||
# main.vp — ViperCPW:CreateProcessA 独立复现工程
|
||||
#
|
||||
# 目的:把原先塞在 TransPyV\App\main.vp 里的 [TEMP-PROBE] 探针整体搬到
|
||||
# 一个独立 Viper 工程里,脱开 TPV 单独复现 / 二分。
|
||||
#
|
||||
# 待复现现象:自举出的 TransPyV.exe 内 CreateProcessA 恒返回 0 且
|
||||
# GetLastError=87(ERROR_INVALID_PARAMETER);同参数在 Python/ctypes
|
||||
# 下 ok=1。
|
||||
#
|
||||
# TPV 侧已逐项实测 ok=0/err=87 的维度(本工程用于在"非 TPV 宿主"上复核):
|
||||
# IR/机器码栈传参(逐槽核对正确)、si.cb=104 与结构体布局、命令行内容
|
||||
# (逐字节回读正确、手工复制不用 strcpy 也 87)、应用名(含绝对路径)、
|
||||
# lpCurrentDirectory、lpEnvironment、bInheritHandles(0/1)、
|
||||
# dwCreationFlags(0/CREATE_NO_WINDOW)。
|
||||
#
|
||||
# 本工程要回答的问题:这是"仅 TPV.exe 特有",还是"任何 Viper 程序都如此"。
|
||||
#
|
||||
# 五组用例(与 TPV 探针同参数、同 si 构造),每组打印 ok 与 GetLastError:
|
||||
# #1 lpApplicationName=NULL + "llc --version",inh=1,flags=0
|
||||
# #2 同 #1 但 inh=0
|
||||
# #3 同 #1 但 flags=CREATE_NO_WINDOW
|
||||
# #4 lpApplicationName="D:\msys64\clang64\bin\llc.exe"(绝对路径)
|
||||
# #5 lpApplicationName=NULL + 报错里那条原样命令行(正斜杠相对路径)
|
||||
# 对照组:打印本进程 GetCurrentDirectoryA 的结果(证明本进程能正常调 Win32)。
|
||||
#
|
||||
# 判定:
|
||||
# 五组全 ok=0/err=87 ⇒ 与 TransPyV.exe 同因,问题在 Viper 生成代码/运行期,
|
||||
# 与 TPV 自身逻辑无关(任何 Viper 程序都如此)。
|
||||
# 任一组 ok=1 ⇒ 该维度即是 TPV 特有问题,按组二分定位。
|
||||
# ============================================================
|
||||
import t, c
|
||||
from stdint import *
|
||||
import stdio
|
||||
import stdlib
|
||||
import string
|
||||
import w32.win32base
|
||||
import w32.win32file
|
||||
import w32.win32process
|
||||
import w32.win32console
|
||||
|
||||
|
||||
# 控制台代码页:UTF-8
|
||||
# 先例:ViperApps/ViperQtHelloWorld/App/main.vp:21(pagecode: t.CDefine = 65001)
|
||||
define PAGE_CODE = 65001
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 单组复现:构造 si/pi → 调 CreateProcessA → 打印 ok / GetLastError / si 字段
|
||||
# si 构造与 TPV 的 _run_cmd_wait 逐字一致:
|
||||
# T() 构造 → string.memset 显式清零 → si.cb = STARTUPINFOA.__sizeof__()
|
||||
# ============================================================
|
||||
def ProbeCreateProcessA(Tag: str, AppName: str, CommandLine: str,
|
||||
InheritHandles: int, CreationFlags: ULONG) -> int:
|
||||
"""[CPW] 单组 CreateProcessA:打印 ok / GetLastError / si 关键字段与地址
|
||||
|
||||
Tag : 分组标签
|
||||
AppName : lpApplicationName(None = NULL,走 PATH 解析)
|
||||
CommandLine : lpCommandLine
|
||||
InheritHandles : 第 5 实参 bInheritHandles
|
||||
CreationFlags : 第 6 实参 dwCreationFlags
|
||||
lpEnvironment / lpCurrentDirectory 恒传 NULL(TPV 侧已实测与 87 无关)。
|
||||
"""
|
||||
# 命令行必须放进可写堆缓冲:CreateProcessA 会原地改写 lpCommandLine,
|
||||
# 不能传只读字面量。
|
||||
# 先例:ViperApps/ViperZipfileProject/App/main.vp:64+69
|
||||
# (stdlib.malloc → string.strcpy);
|
||||
# includes/subprocess.vp:66-75(alloc + 逐字节填入 + 结尾 0)
|
||||
# string.strlen 声明:includes/string.vp:69;string.strcpy 声明:includes/string.vp:7
|
||||
# stdlib.malloc 声明:includes/stdlib.vp:5
|
||||
CmdLength: sizet = string.strlen(CommandLine)
|
||||
CmdBuffer: bytes = stdlib.malloc(CmdLength + 1)
|
||||
if CmdBuffer is None:
|
||||
stdio.printf("[CPW] %s MALLOC-FAILED\n", Tag)
|
||||
return 1
|
||||
string.strcpy(CmdBuffer, CommandLine)
|
||||
|
||||
# si/pi:T() 构造只 alloca 不 memset,必须显式清零
|
||||
# 先例:TransPyV/App/lib/Phase/BuildPipeline.vp:530-532
|
||||
# (string.memset(t.CPtr(c.Addr(si)), 0, STARTUPINFOA.__sizeof__()))
|
||||
# 类型引用写法:includes/subprocess.vp:99-100
|
||||
# c.Addr() 取址:includes/condition.vp:26;t.CPtr(c.Addr(x)):includes/subprocess.vp:121
|
||||
StartupInfo: w32.win32process.STARTUPINFOA = w32.win32process.STARTUPINFOA()
|
||||
ProcessInfo: w32.win32process.PROCESS_INFORMATION = w32.win32process.PROCESS_INFORMATION()
|
||||
string.memset(t.CPtr(&StartupInfo), 0, w32.win32process.STARTUPINFOA.__sizeof__())
|
||||
string.memset(t.CPtr(&ProcessInfo), 0, w32.win32process.PROCESS_INFORMATION.__sizeof__())
|
||||
# si.cb = 结构体大小:includes/subprocess.vp:100
|
||||
StartupInfo.cb = w32.win32process.STARTUPINFOA.__sizeof__()
|
||||
|
||||
# 应用名描述:None 显式打印成 NULL(不臆造内容,也不兜底成别的路径)
|
||||
AppDesc: str = "NULL"
|
||||
if AppName is not None:
|
||||
AppDesc = AppName
|
||||
|
||||
# GetLastError 基线:调用前读取,用于判定 err 是否为残留值
|
||||
# GetLastError 声明:includes/w32/win32base.vp:62
|
||||
# 调用后紧跟读取的先例:ViperApps/ViperZipfileProject/App/main.vp:116
|
||||
PreError: uint32 = w32.win32base.GetLastError()
|
||||
|
||||
# 单条调用:10 实参,lpEnvironment / lpCurrentDirectory 恒 NULL
|
||||
# 先例:includes/subprocess.vp:112-123(t.CPtr(0) 表 NULL 见 :113/:115/:119/:120)
|
||||
OkValue: int = w32.win32process.CreateProcessA(
|
||||
AppName, CmdBuffer, t.CPtr(0), t.CPtr(0), InheritHandles, CreationFlags,
|
||||
t.CPtr(0), t.CPtr(0), t.CPtr(&StartupInfo), t.CPtr(&ProcessInfo))
|
||||
# GetLastError 必须紧跟调用读取(printf 等都会覆盖它)
|
||||
ErrorValue: uint32 = w32.win32base.GetLastError()
|
||||
|
||||
# --- 字段转储:整数字段与指针字段统一 uint64() 后按 %llu 打印 ---
|
||||
# uint64(结构体字段) 先例:TransPyV/App/lib/Stub/StubMerger/StubMergerStore.vp:79
|
||||
# (uint64(scan_result.Entries))
|
||||
# uint64(c.Addr(x)) 先例:ViperApps/ViperOS/VKernel/Kernel/sched/sched.vp:217
|
||||
# (entry_addr: uint64 = uint64(c.Addr(_thread_entry)))
|
||||
# uint64(malloc 缓冲) 先例:TransPyV/App/lib/Stub/StubMerger/StubMergerBuild.vp:1313 段
|
||||
# (uint64(ext_ptr))
|
||||
# %llu 先例:includes/memhub.vp:792
|
||||
CbValue: uint64 = uint64(StartupInfo.cb)
|
||||
FlagsValue: uint64 = uint64(StartupInfo.dwFlags)
|
||||
CbReserved2Value: uint64 = uint64(StartupInfo.cbReserved2)
|
||||
Reserved2Value: uint64 = uint64(StartupInfo.lpReserved2)
|
||||
StdInputValue: uint64 = uint64(StartupInfo.hStdInput)
|
||||
StdOutputValue: uint64 = uint64(StartupInfo.hStdOutput)
|
||||
StdErrorValue: uint64 = uint64(StartupInfo.hStdError)
|
||||
SiAddress: uint64 = uint64(&StartupInfo)
|
||||
PiAddress: uint64 = uint64(&ProcessInfo)
|
||||
CmdAddress: uint64 = uint64(CmdBuffer)
|
||||
|
||||
# %s 传 str 先例:ViperApps/ViperZipfileProject/App/main.vp:29
|
||||
stdio.printf("[CPW] %s ok=%d err=%llu pre_err=%llu app=%s cmd=%s cl.addr=%llu\n",
|
||||
Tag, OkValue, uint64(ErrorValue), uint64(PreError),
|
||||
AppDesc, CommandLine, CmdAddress)
|
||||
# si 关键字段:cb / dwFlags / cbReserved2 / lpReserved2 / 三个 hStd* + si/pi 地址
|
||||
# 字段定义:includes/w32/win32process.vp:42-60(STARTUPINFOA)
|
||||
stdio.printf("[CPW] %s si.cb=%llu si.dwFlags=%llu si.cbReserved2=%llu si.lpReserved2=%llu si.hStdIn=%llu si.hStdOut=%llu si.hStdErr=%llu si.addr=%llu pi.addr=%llu\n",
|
||||
Tag, CbValue, FlagsValue, CbReserved2Value, Reserved2Value,
|
||||
StdInputValue, StdOutputValue, StdErrorValue, SiAddress, PiAddress)
|
||||
|
||||
# 成功时关闭子进程句柄(避免句柄泄漏)
|
||||
# 先例:includes/subprocess.vp:179-180
|
||||
if OkValue != 0:
|
||||
w32.win32base.CloseHandle(ProcessInfo.hThread)
|
||||
w32.win32base.CloseHandle(ProcessInfo.hProcess)
|
||||
stdlib.free(CmdBuffer)
|
||||
# stdout 全缓冲,逐组刷新以保证崩溃前输出可见
|
||||
# 先例:ViperApps/ViperZipfileProject/App/main.vp:33(stdio.fflush(None))
|
||||
stdio.fflush(None)
|
||||
return 0
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 对照组:本进程 GetCurrentDirectoryA(证明本进程可正常调 Win32)
|
||||
# ============================================================
|
||||
def PrintCurrentDirectory() -> int:
|
||||
"""[CPW] 对照组:打印本进程当前目录与错误码"""
|
||||
# 缓冲与调用形态先例:includes/os/_win32.vp:76-91(getcwd:malloc → 调用 →
|
||||
# buf[length] = 0 → str(buf));MAX_PATH 声明:includes/w32/win32base.vp:18
|
||||
CwdLength: ULONG = 0
|
||||
CwdError: uint32 = 0
|
||||
CwdDesc: str = "<empty>"
|
||||
CwdBuffer: bytes = stdlib.malloc(w32.win32base.MAX_PATH + 1)
|
||||
if CwdBuffer is None:
|
||||
stdio.printf("[CPW] control GetCurrentDirectoryA MALLOC-FAILED\n")
|
||||
return 1
|
||||
# GetCurrentDirectoryA 声明:includes/w32/win32file.vp:129
|
||||
CwdLength = w32.win32file.GetCurrentDirectoryA(w32.win32base.MAX_PATH + 1, CwdBuffer)
|
||||
CwdError = w32.win32base.GetLastError() # 必须紧跟调用读取
|
||||
if CwdLength > 0:
|
||||
if CwdLength < w32.win32base.MAX_PATH + 1:
|
||||
CwdBuffer[CwdLength] = 0
|
||||
else:
|
||||
CwdBuffer[w32.win32base.MAX_PATH] = 0 # 缓冲区不足:截断显示
|
||||
CwdDesc = str(CwdBuffer)
|
||||
stdio.printf("[CPW] control GetCurrentDirectoryA len=%llu err=%llu cwd=%s\n",
|
||||
uint64(CwdLength), uint64(CwdError), CwdDesc)
|
||||
stdlib.free(CwdBuffer)
|
||||
stdio.fflush(None)
|
||||
return 0
|
||||
|
||||
|
||||
# ============================================================
|
||||
# main: 入口
|
||||
# @t.CExport + main() -> int 先例:ViperApps/ViperZipfileProject/App/main.vp:305-306
|
||||
# ============================================================
|
||||
@t.CExport
|
||||
def main() -> int:
|
||||
# 控制台 UTF-8(中文注释/标签不乱码)
|
||||
# 声明:includes/w32/win32console.vp:81-82
|
||||
# 先例:ViperApps/ViperQtHelloWorld/App/main.vp:18+34-35
|
||||
w32.win32console.SetConsoleOutputCP(PAGE_CODE)
|
||||
w32.win32console.SetConsoleCP(PAGE_CODE)
|
||||
|
||||
# stdio.printf 声明:includes/stdio.vp:4
|
||||
stdio.printf("ViperCPW: CreateProcessA standalone repro (Viper host, not TransPyV.exe)\n")
|
||||
PrintCurrentDirectory()
|
||||
|
||||
# #1 基线:NULL 应用名 + inh=1 + flags=0
|
||||
ProbeCreateProcessA("#1 NULL-app inh=1 flags=0", None, "llc --version", 1, 0)
|
||||
# #2 同 #1 但 bInheritHandles=0
|
||||
ProbeCreateProcessA("#2 NULL-app inh=0 flags=0", None, "llc --version", 0, 0)
|
||||
# #3 同 #1 但 dwCreationFlags=CREATE_NO_WINDOW
|
||||
# CREATE_NO_WINDOW 定义:includes/w32/win32process.vp:23
|
||||
ProbeCreateProcessA("#3 NULL-app inh=1 flags=CREATE_NO_WINDOW", None, "llc --version",
|
||||
1, w32.win32process.CREATE_NO_WINDOW)
|
||||
# #4 绝对路径应用名
|
||||
ProbeCreateProcessA("#4 abs-app", "D:\\msys64\\clang64\\bin\\llc.exe", "llc --version", 1, 0)
|
||||
# #5 报错里那条原样命令行(正斜杠相对路径,与 TPV 探针 #3 逐字相同)
|
||||
ProbeCreateProcessA("#5 raw-cmdline(fwd slashes)", None,
|
||||
"llc -filetype=obj -relocation-model=pic -o ./TransPyV/.tpv_build/output/xx/xxx.obj ./TransPyV/.tpv_build/temp/xx/xxx.ll",
|
||||
1, 0)
|
||||
|
||||
stdio.printf("ViperCPW: DONE\n")
|
||||
stdio.fflush(None)
|
||||
return 0
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "ViperCPW - CreateProcessA standalone repro",
|
||||
"version": "1.0.0",
|
||||
"description": "Standalone Viper repro for CreateProcessA returning 0 / GetLastError=87",
|
||||
"source_dir": "./App",
|
||||
"build_dir": "./.tpv_build",
|
||||
"compiler": {
|
||||
"cmd": "llc",
|
||||
"flags": [
|
||||
"-filetype=obj",
|
||||
"-relocation-model=pic"
|
||||
]
|
||||
},
|
||||
"linker": {
|
||||
"cmd": "D:/msys64/mingw64/bin/clang++.exe",
|
||||
"flags": [
|
||||
"-fuse-ld=lld",
|
||||
"-lmsvcrt",
|
||||
"-lucrt",
|
||||
"-lpthread",
|
||||
"-lmingwex",
|
||||
"-lkernel32"
|
||||
],
|
||||
"output": "app.exe"
|
||||
},
|
||||
"includes": [
|
||||
"../../includes"
|
||||
],
|
||||
"target": {
|
||||
"triple": "x86_64-pc-windows-gnu",
|
||||
"datalayout": "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
|
||||
},
|
||||
"options": {
|
||||
"slice_level": 3,
|
||||
"target": "llvm",
|
||||
"strict_mode": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
*.o
|
||||
*.obj
|
||||
*.exe
|
||||
*.img
|
||||
*.iso
|
||||
**/__pycache__/
|
||||
.cache/
|
||||
*.pyc
|
||||
|
||||
# 编译
|
||||
**/output/
|
||||
**/temp/
|
||||
**/.tpv_build/
|
||||
@@ -0,0 +1,158 @@
|
||||
# main2.vp — ViperFLProject fltk GUI 测试(回调 + 控件展示)
|
||||
#
|
||||
# 构建需 TPV/TPC 支持 project.vpj linker.static_libs 静态链接
|
||||
# (本工程已配置链接 ../includes/fltk/cfltk/build 下的 cfltk2 + FLTK 静态库)。
|
||||
#
|
||||
# 回调说明:Viper 模块级函数可直接作为 Fl_Callback 传入(TPV 自动把函数名
|
||||
# bitcast 为函数指针)。回调签名统一为 def cb(w: bytes, data: bytes),
|
||||
# 其中 w 是触发回调的控件指针,data 是注册时传入的用户数据。
|
||||
import t, c
|
||||
import stdlib
|
||||
import viperlib
|
||||
import fltk
|
||||
|
||||
|
||||
# 模块级全局:供回调访问 main() 中创建的控件指针(回调是 C 层直接调用,
|
||||
# 拿不到 main 的局部变量);空指针用 None(C null),不能用整数 0。
|
||||
term_ptr: t.CPtr = None
|
||||
status_ptr: t.CPtr = None
|
||||
scheme_idx: int = 0
|
||||
theme_idx: int = 0
|
||||
|
||||
|
||||
def on_btn_click(w: bytes, data: bytes):
|
||||
"""按钮点击回调:改按钮文字 + 状态栏显示"""
|
||||
fltk.Fl_Widget_set_label(w, "Clicked!")
|
||||
fltk.Fl_Widget_redraw(w)
|
||||
fltk.Fl_Output_set_value(status_ptr, "Button clicked")
|
||||
|
||||
|
||||
def on_check(w: bytes, data: bytes):
|
||||
"""复选框回调:状态栏显示勾选状态"""
|
||||
v: int = fltk.Fl_Check_Button_is_checked(w)
|
||||
if v != 0:
|
||||
fltk.Fl_Output_set_value(status_ptr, "Checked")
|
||||
else:
|
||||
fltk.Fl_Output_set_value(status_ptr, "Unchecked")
|
||||
|
||||
|
||||
def on_slider(w: bytes, data: bytes):
|
||||
"""滑动条回调:状态栏实时显示数值"""
|
||||
v: double = fltk.Fl_Slider_value(w)
|
||||
buf: bytes = stdlib.malloc(32)
|
||||
if buf is not None:
|
||||
viperlib.snprintf(buf, 32, "Slider = %.2f", v)
|
||||
fltk.Fl_Output_set_value(status_ptr, buf)
|
||||
stdlib.free(buf)
|
||||
|
||||
|
||||
def on_go(w: bytes, data: bytes):
|
||||
"""按钮回调:读取输入框文本 → 终端打印"""
|
||||
txt: str = fltk.Fl_Input_value(data)
|
||||
fltk.Fl_Terminal_append(term_ptr, "input: ")
|
||||
fltk.Fl_Terminal_append(term_ptr, txt)
|
||||
fltk.Fl_Terminal_append(term_ptr, "\n")
|
||||
|
||||
|
||||
def on_menu_open(w: bytes, data: bytes):
|
||||
fltk.Fl_Output_set_value(status_ptr, "Menu: File/Open")
|
||||
|
||||
|
||||
def on_menu_about(w: bytes, data: bytes):
|
||||
fltk.Fl_Output_set_value(status_ptr, "Menu: Help/About")
|
||||
|
||||
|
||||
def on_style(w: bytes, data: bytes):
|
||||
"""风格切换回调:base→plastic→gtk+→gleam→oxy 循环"""
|
||||
global scheme_idx
|
||||
scheme_idx = scheme_idx + 1
|
||||
if scheme_idx == 1:
|
||||
fltk.Fl.set_scheme("plastic")
|
||||
elif scheme_idx == 2:
|
||||
fltk.Fl.set_scheme("gtk+")
|
||||
elif scheme_idx == 3:
|
||||
fltk.Fl.set_scheme("gleam")
|
||||
elif scheme_idx == 4:
|
||||
fltk.Fl.set_scheme("oxy")
|
||||
else:
|
||||
fltk.Fl.set_scheme("base")
|
||||
scheme_idx = 0
|
||||
fltk.Fl_Output_set_value(status_ptr, f"Scheme: {fltk.Fl.scheme_string()}")
|
||||
|
||||
|
||||
def on_theme(w: bytes, data: bytes):
|
||||
"""Fl_Theme 主题切换:base→Dark→Metro 循环"""
|
||||
global theme_idx
|
||||
theme_idx = theme_idx + 1
|
||||
if theme_idx == 1:
|
||||
fltk.Fl.set_theme_dark()
|
||||
elif theme_idx == 2:
|
||||
fltk.Fl.set_theme_metro()
|
||||
else:
|
||||
fltk.Fl.set_scheme("base")
|
||||
fltk.Fl.reload_scheme()
|
||||
theme_idx = 0
|
||||
fltk.Fl_Output_set_value(status_ptr, f"Theme: {theme_idx}")
|
||||
|
||||
|
||||
def on_tick(w: bytes, data: bytes):
|
||||
"""定时器回调:每秒终端打印一次并续订"""
|
||||
fltk.Fl_Terminal_append(term_ptr, "tick\n")
|
||||
fltk.Fl.add_timeout(1.0, on_tick, 0)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
global term_ptr, status_ptr
|
||||
fltk.Fl_init_all()
|
||||
|
||||
win = fltk.Fl_Window(600, 460, "Viper FLTK Demo")
|
||||
menubar = fltk.Fl_Menu_Bar(0, 0, 600, 25)
|
||||
status = fltk.Fl_Output(10, 30, 580, 25)
|
||||
btn = fltk.Fl_Button(10, 70, 120, 30, "Click Me")
|
||||
check = fltk.Fl_Check_Button(150, 70, 100, 30, "Enable")
|
||||
slider = fltk.Fl_Slider(280, 70, 200, 30)
|
||||
input_ = fltk.Fl_Input(10, 110, 210, 30, "Type here")
|
||||
go = fltk.Fl_Button(230, 110, 55, 30, "Go")
|
||||
style = fltk.Fl_Button(295, 110, 55, 30, "Style")
|
||||
theme = fltk.Fl_Button(360, 110, 55, 30, "Theme")
|
||||
spinner = fltk.Fl_Spinner(425, 110, 100, 30)
|
||||
browser = fltk.Fl_Browser(10, 150, 280, 140)
|
||||
tree = fltk.Fl_Tree(300, 150, 280, 140)
|
||||
term = fltk.Fl_Terminal(10, 300, 570, 120, "Terminal")
|
||||
win.end()
|
||||
|
||||
# 保存全局指针(回调用)
|
||||
term_ptr = t.CPtr(term._ptr)
|
||||
status_ptr = t.CPtr(status._ptr)
|
||||
|
||||
# 菜单(回调 + 快捷键 Ctrl+O:0x4000 | 'o'(111))
|
||||
menubar.add("File/Open", 0x4000 | 111, 0, on_menu_open)
|
||||
menubar.add("Help/About", 0, 0, on_menu_about)
|
||||
|
||||
# 回调注册
|
||||
btn.set_callback(on_btn_click, 0)
|
||||
check.set_callback(on_check, 0)
|
||||
slider.set_callback(on_slider, 0)
|
||||
go.set_callback(on_go, input_._ptr)
|
||||
style.set_callback(on_style, 0)
|
||||
theme.set_callback(on_theme, 0)
|
||||
|
||||
# 数值微调框
|
||||
spinner.set_minimum(0.0)
|
||||
spinner.set_maximum(100.0)
|
||||
spinner.set_step(1.0)
|
||||
spinner.set_value(42.0)
|
||||
|
||||
# 列表 + 树
|
||||
browser.add("Item 1")
|
||||
browser.add("Item 2")
|
||||
browser.add("Item 3")
|
||||
tree.add("Root/Child A/Leaf 1")
|
||||
tree.add("Root/Child B")
|
||||
|
||||
# 定时器
|
||||
fltk.Fl.add_timeout(1.0, on_tick, 0)
|
||||
|
||||
win.show()
|
||||
fltk.Fl.run()
|
||||
return 0
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 TermiNexus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,289 @@
|
||||
# Viper Template Project
|
||||
|
||||
[English](#english) | [中文](#中文)
|
||||
|
||||
A minimal, ready-to-use template for bootstrapping a new [Viper](../wiki/01-overview.md) language project on **Windows (x86_64-pc-windows-gnu)**.
|
||||
|
||||
Windows 平台(x86_64-pc-windows-gnu)下 [Viper](../wiki/01-overview.md) 语言新项目的最小可用模板。
|
||||
|
||||
---
|
||||
|
||||
<a id="中文"></a>
|
||||
|
||||
## 中文
|
||||
|
||||
### 这是什么
|
||||
|
||||
`ViperTemplateProject` 是基于 `Viper` 语言的 **Windows 桌面应用模板**。克隆本仓库后,你可以立即得到一个能够:
|
||||
|
||||
- 设置 Windows 控制台为 UTF-8 编码(避免中文乱码)
|
||||
- 打印一行 `你好,世界`
|
||||
- 通过 `llc + clang++`(mingw 工具链)编译为原生 `app.exe`
|
||||
|
||||
的最小 Viper 项目。
|
||||
|
||||
模板对应的语言编译器是 [TransPyC](https://git.gvsds.com/GVSDS/TransPyC);关于 Viper 语言本身的概览,参见 [TransPyC 仓库 wiki](../wiki/01-overview.md)。
|
||||
|
||||
### 目录结构
|
||||
|
||||
```
|
||||
ViperTemplateProject/
|
||||
├── .gitignore # 忽略 build/、*.o、*.exe、缓存等
|
||||
├── README.md # 本文件
|
||||
├── project.vpj # 项目配置(编译器、链接器、目标三元组等)
|
||||
└── App/
|
||||
└── main.py # 入口文件
|
||||
```
|
||||
|
||||
### 前提条件
|
||||
|
||||
| 工具 | 说明 |
|
||||
|------|------|
|
||||
| `python` ≥ 3.10 | 用于运行 TransPyC 编译器 |
|
||||
| `llc` | LLVM 静态编译器,编译 `.ll` → `.o` |
|
||||
| `clang++` | mingw 工具链下的链接驱动(`x86_64-w64-mingw32`) |
|
||||
| `TransPyC` 仓库 | 本编译器,克隆在 `ViperTemplateProject/..` 同一目录下 |
|
||||
|
||||
环境变量建议(PowerShell):
|
||||
|
||||
```powershell
|
||||
$env:PATH = "D:\msys64\mingw64\bin;D:\LLVM\bin;$env:PATH"
|
||||
```
|
||||
|
||||
### 快速开始
|
||||
|
||||
```powershell
|
||||
# 1. 确保目录布局正确
|
||||
# D:\Users\TermiNexus\Desktop\TransPyC\
|
||||
# ├── TransPyC\ # 编译器仓库
|
||||
# └── ViperTemplateProject\ # 本仓库
|
||||
|
||||
cd D:\Users\TermiNexus\Desktop\TransPyC\ViperTemplateProject
|
||||
|
||||
# 2. 编译并运行
|
||||
python ..\TransPyC\Projectrans.py `
|
||||
--project .\project.vpj `
|
||||
--clean-cache=false
|
||||
|
||||
# 3. 产物位于 output\app.exe
|
||||
.\output\app.exe
|
||||
```
|
||||
|
||||
预期输出:
|
||||
|
||||
```
|
||||
你好,世界
|
||||
```
|
||||
|
||||
### 编写你的第一个程序
|
||||
|
||||
打开 `App/main.py`,替换 `print("你好,世界")` 为任意合法 Viper 表达式。例如:
|
||||
|
||||
```python
|
||||
import w32.win32console as w32cmd
|
||||
import t, c
|
||||
|
||||
pagecode: t.CDefine = 65001
|
||||
|
||||
@t.CExport
|
||||
def main() -> int:
|
||||
w32cmd.SetConsoleOutputCP(pagecode)
|
||||
w32cmd.SetConsoleCP(pagecode)
|
||||
|
||||
# 你的代码从这里开始
|
||||
a: t.CInt = 1
|
||||
b: t.CInt = 2
|
||||
print("a + b =", a + b)
|
||||
|
||||
return 0
|
||||
```
|
||||
|
||||
### 修改项目配置
|
||||
|
||||
打开 `project.vpj`,按需调整:
|
||||
|
||||
| 字段 | 含义 | 模板默认值 |
|
||||
|------|------|-----------|
|
||||
| `name` | 项目名(仅显示用) | `Standard Template Project` |
|
||||
| `source_dir` | 源文件根目录 | `./App` |
|
||||
| `temp_dir` | 中间产物目录(`.pyi` / `.stub.ll`) | `./temp` |
|
||||
| `output_dir` | 最终产物目录 | `./output` |
|
||||
| `compiler.cmd` | 汇编器 | `llc` |
|
||||
| `compiler.flags` | 传给 `llc` 的参数 | `["-filetype=obj", "-relocation-model=pic"]` |
|
||||
| `linker.cmd` | 链接器 | `clang++` |
|
||||
| `linker.flags` | 链接器参数 | mingw 标准库 + `kernel32` |
|
||||
| `linker.output` | 产物名 | `app.exe` |
|
||||
| `includes` | 标准库搜索路径 | `["../includes"]` |
|
||||
| `target.triple` | LLVM 目标三元组 | `x86_64-pc-windows-gnu` |
|
||||
| `target.datalayout` | LLVM 数据布局 | mingw 默认 |
|
||||
| `options.target` | 后端目标 | `llvm` |
|
||||
| `options.strict_mode` | 严格类型检查 | `true` |
|
||||
| `options.slice_level` | 切片级别 | `3` |
|
||||
|
||||
### 跨平台使用
|
||||
|
||||
本模板默认针对 **Windows + mingw**。若需在其他平台使用,修改 `project.vpj` 的 `compiler.cmd` / `linker.cmd` / `target.triple`:
|
||||
|
||||
- **Linux (x86_64)**:`triple = "x86_64-unknown-linux-gnu"`,`linker.cmd = "clang++"`
|
||||
- **macOS (x86_64)**:`triple = "x86_64-apple-darwin*"`,`linker.cmd = "clang++"`
|
||||
- **裸机 x86_64 (ViperOS)**:`triple = "x86_64-none-elf"`,`linker.cmd = "ld.lld"`,需要 `isr.s` 启动文件
|
||||
|
||||
### 常见问题
|
||||
|
||||
**Q: 报错 `cannot find includes/os` / `cannot find includes/atom` 之类?**
|
||||
A: 检查 `project.vpj` 中的 `includes` 字段是否指向 `TransPyC/includes`。`../includes` 假设本仓库与 `TransPyC` 仓库同级。
|
||||
|
||||
**Q: 输出 `app.exe` 双击闪退?**
|
||||
A: 在终端中运行 `.\output\app.exe` 而非双击,并确认 mingw 与 LLVM 在 `PATH` 中。
|
||||
|
||||
**Q: 想完全清理构建产物?**
|
||||
A: 使用 `python ..\TransPyC\Projectrans.py --project .\project.vpj --clean-cache` 清理缓存;删除 `output/` 和 `temp/` 即可彻底重置。
|
||||
|
||||
**Q: `StandardTemplateProject` 和本仓库是什么关系?**
|
||||
A: 本仓库 `ViperTemplateProject` 是 `StandardTemplateProject` 的独立仓库版本。如果你仍想保留 `StandardTemplateProject` 放在 `TransPyC` 仓库内,只需把它加入 `TransPyC/.gitignore` 的 `# 排除独立仓库的目录` 小节。
|
||||
|
||||
### 进一步阅读
|
||||
|
||||
- [Viper 语言概览](../wiki/01-overview.md)
|
||||
- [类型系统](../wiki/02-type-system.md)
|
||||
- [OOP 与对象模型](../wiki/06-oop.md)
|
||||
- [自举路线图](../wiki/13-bootstrapping.md)
|
||||
|
||||
---
|
||||
|
||||
<a id="english"></a>
|
||||
|
||||
## English
|
||||
|
||||
### What is this
|
||||
|
||||
`ViperTemplateProject` is a **Windows desktop application template** based on the [Viper](../wiki/01-overview.md) language. After cloning, you immediately have a minimal Viper project that:
|
||||
|
||||
- Sets the Windows console to UTF-8 (avoids mojibake for non-ASCII output)
|
||||
- Prints `你好,世界`
|
||||
- Compiles to a native `app.exe` via `llc + clang++` (mingw toolchain)
|
||||
|
||||
The corresponding compiler is [TransPyC](https://git.gvsds.com/GVSDS/TransPyC). For a Viper language overview, see the [TransPyC wiki](../wiki/01-overview.md).
|
||||
|
||||
### Directory layout
|
||||
|
||||
```
|
||||
ViperTemplateProject/
|
||||
├── .gitignore # ignores build/, *.o, *.exe, caches, etc.
|
||||
├── README.md # this file
|
||||
├── project.vpj # project config (compiler, linker, target triple, ...)
|
||||
└── App/
|
||||
└── main.py # entry point
|
||||
```
|
||||
|
||||
### Prerequisites
|
||||
|
||||
| Tool | Notes |
|
||||
|------|-------|
|
||||
| `python` ≥ 3.10 | runs the TransPyC compiler |
|
||||
| `llc` | LLVM static compiler, `.ll` → `.o` |
|
||||
| `clang++` | mingw toolchain driver (`x86_64-w64-mingw32`) |
|
||||
| `TransPyC` repo | the compiler, cloned as a sibling of `ViperTemplateProject` |
|
||||
|
||||
Suggested `PATH` (PowerShell):
|
||||
|
||||
```powershell
|
||||
$env:PATH = "D:\msys64\mingw64\bin;D:\LLVM\bin;$env:PATH"
|
||||
```
|
||||
|
||||
### Quick start
|
||||
|
||||
```powershell
|
||||
# 1. Make sure the layout is right
|
||||
# D:\Users\TermiNexus\Desktop\TransPyC\
|
||||
# ├── TransPyC\ # compiler repo
|
||||
# └── ViperTemplateProject\ # this repo
|
||||
|
||||
cd D:\Users\TermiNexus\Desktop\TransPyC\ViperTemplateProject
|
||||
|
||||
# 2. Build and run
|
||||
python ..\TransPyC\Projectrans.py `
|
||||
--project .\project.vpj `
|
||||
--clean-cache=false
|
||||
|
||||
# 3. Output is at output\app.exe
|
||||
.\output\app.exe
|
||||
```
|
||||
|
||||
Expected output:
|
||||
|
||||
```
|
||||
你好,世界
|
||||
```
|
||||
|
||||
### Writing your first program
|
||||
|
||||
Open `App/main.py` and replace `print("你好,世界")` with any valid Viper expression. Example:
|
||||
|
||||
```python
|
||||
import w32.win32console as w32cmd
|
||||
import t, c
|
||||
|
||||
pagecode: t.CDefine = 65001
|
||||
|
||||
@t.CExport
|
||||
def main() -> int:
|
||||
w32cmd.SetConsoleOutputCP(pagecode)
|
||||
w32cmd.SetConsoleCP(pagecode)
|
||||
|
||||
# your code goes here
|
||||
a: t.CInt = 1
|
||||
b: t.CInt = 2
|
||||
print("a + b =", a + b)
|
||||
|
||||
return 0
|
||||
```
|
||||
|
||||
### Editing project.vpj
|
||||
|
||||
| Field | Meaning | Template default |
|
||||
|-------|---------|------------------|
|
||||
| `name` | display name | `Standard Template Project` |
|
||||
| `source_dir` | source root | `./App` |
|
||||
| `temp_dir` | intermediate artifacts (`.pyi` / `.stub.ll`) | `./temp` |
|
||||
| `output_dir` | final artifacts | `./output` |
|
||||
| `compiler.cmd` | assembler | `llc` |
|
||||
| `compiler.flags` | flags passed to `llc` | `["-filetype=obj", "-relocation-model=pic"]` |
|
||||
| `linker.cmd` | linker | `clang++` |
|
||||
| `linker.flags` | linker flags | mingw stdlib + `kernel32` |
|
||||
| `linker.output` | output name | `app.exe` |
|
||||
| `includes` | standard library search paths | `["../includes"]` |
|
||||
| `target.triple` | LLVM target triple | `x86_64-pc-windows-gnu` |
|
||||
| `target.datalayout` | LLVM data layout | mingw default |
|
||||
| `options.target` | backend target | `llvm` |
|
||||
| `options.strict_mode` | strict type checking | `true` |
|
||||
| `options.slice_level` | slice level | `3` |
|
||||
|
||||
### Cross-platform use
|
||||
|
||||
This template targets **Windows + mingw** by default. To use on another platform, adjust `compiler.cmd` / `linker.cmd` / `target.triple` in `project.vpj`:
|
||||
|
||||
- **Linux (x86_64)**: `triple = "x86_64-unknown-linux-gnu"`, `linker.cmd = "clang++"`
|
||||
- **macOS (x86_64)**: `triple = "x86_64-apple-darwin*"`, `linker.cmd = "clang++"`
|
||||
- **Bare-metal x86_64 (ViperOS)**: `triple = "x86_64-none-elf"`, `linker.cmd = "ld.lld"`, requires `isr.s` startup
|
||||
|
||||
### FAQ
|
||||
|
||||
**Q: Error like `cannot find includes/os` or `cannot find includes/atom`?**
|
||||
A: Check that the `includes` field in `project.vpj` points to the `TransPyC/includes` directory. `../includes` assumes this repo lives next to `TransPyC`.
|
||||
|
||||
**Q: `app.exe` flashes and closes on double-click?**
|
||||
A: Run `.\output\app.exe` from a terminal, not by double-clicking. Make sure mingw and LLVM are on `PATH`.
|
||||
|
||||
**Q: How to fully clean build artifacts?**
|
||||
A: `python ..\TransPyC\Projectrans.py --project .\project.vpj --clean-cache` clears the cache. To reset everything, also delete `output/` and `temp/`.
|
||||
|
||||
**Q: What is the relationship with `StandardTemplateProject`?**
|
||||
A: This repo `ViperTemplateProject` is the standalone-repo version of `StandardTemplateProject`. If you still want `StandardTemplateProject` to live inside the `TransPyC` repo, add it to the `# 排除独立仓库的目录` section in `TransPyC/.gitignore`.
|
||||
|
||||
### Further reading
|
||||
|
||||
- [Viper language overview](../wiki/01-overview.md)
|
||||
- [Type system](../wiki/02-type-system.md)
|
||||
- [OOP and object model](../wiki/06-oop.md)
|
||||
- [Bootstrapping roadmap](../wiki/13-bootstrapping.md)
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "ViperFLProject - FLTK GUI Test",
|
||||
"version": "1.0.0",
|
||||
"source_dir": "./App",
|
||||
"build_dir": "./.tpv_build",
|
||||
"compiler": {
|
||||
"cmd": "llc",
|
||||
"flags": [
|
||||
"-filetype=obj",
|
||||
"-relocation-model=pic"
|
||||
]
|
||||
},
|
||||
"linker": {
|
||||
"cmd": "D:/msys64/mingw64/bin/clang++.exe",
|
||||
"flags": [
|
||||
"-fuse-ld=lld",
|
||||
"-lmsvcrt",
|
||||
"-lucrt",
|
||||
"-lpthread",
|
||||
"-lmingwex",
|
||||
"-lkernel32",
|
||||
"-lcomdlg32",
|
||||
"-lcomctl32",
|
||||
"-lole32",
|
||||
"-luuid",
|
||||
"-lwinmm",
|
||||
"-lgdi32",
|
||||
"-lgdiplus",
|
||||
"-ladvapi32",
|
||||
"-lshell32",
|
||||
"-lws2_32",
|
||||
"-luser32",
|
||||
"-lwinspool"
|
||||
],
|
||||
"static_libs": [
|
||||
"../../includes/fltk/cfltk/build/libcfltk2.a",
|
||||
"../../includes/fltk/cfltk/build/libfltk_theme.a",
|
||||
"../../includes/fltk/cfltk/build/fltk/lib/libfltk_images.a",
|
||||
"../../includes/fltk/cfltk/build/fltk/lib/libfltk.a",
|
||||
"../../includes/fltk/cfltk/build/fltk/lib/libfltk_png.a",
|
||||
"../../includes/fltk/cfltk/build/fltk/lib/libfltk_jpeg.a",
|
||||
"../../includes/fltk/cfltk/build/fltk/lib/libfltk_z.a"
|
||||
],
|
||||
"output": "app.exe"
|
||||
},
|
||||
"includes": [
|
||||
"../../includes"
|
||||
],
|
||||
"target": {
|
||||
"triple": "x86_64-pc-windows-gnu",
|
||||
"datalayout": "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
|
||||
},
|
||||
"options": {
|
||||
"slice_level": 3,
|
||||
"target": "llvm",
|
||||
"strict_mode": true
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,472 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ============================================================
|
||||
# MnistData - MNIST IDX 数据层(CLI 与 Qt 面板共用)
|
||||
#
|
||||
# 职责:
|
||||
# * 标准文件名拼装与完整性预检(idx_usable)
|
||||
# * .gz 解压写入 IDX: 本地文件(inflate_local) / 联网(download_inflate)
|
||||
# * 就绪保障 ensure_mnist(缺或坏 → 本地 --file 优先, 其次联网)
|
||||
# * IDX → 训练/测试缓冲(load_mnist_images / load_mnist_labels)
|
||||
#
|
||||
# 说明: fileio.File.read_all 契约是"最多读 max_count-1 字节并补 NUL",
|
||||
# 故所有整读调用都按 目标长度+1 申请与请求, 否则会丢最后一字节。
|
||||
# ============================================================
|
||||
import t, c
|
||||
from stdint import *
|
||||
import stdlib
|
||||
import string
|
||||
import stdio
|
||||
import viperlib
|
||||
import os
|
||||
import w32.fileio as fileio
|
||||
import gzip
|
||||
import requests
|
||||
import requests.https as reqhttps
|
||||
|
||||
# 28x28 灰度输入 / 10 类输出
|
||||
N_IN: sizet = 784
|
||||
N_OUT: sizet = 10
|
||||
|
||||
# MNIST 官方镜像(ossci S3)
|
||||
MNIST_BASE: str = "https://ossci-datasets.s3.amazonaws.com/mnist"
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 基础读写
|
||||
# ============================================================
|
||||
def be32(buf: t.CPtr, off: sizet) -> uint32:
|
||||
"""IDX 大端 u32"""
|
||||
ub: uchar* = buf
|
||||
return (uint32(ub[off]) << 24) | (uint32(ub[off + 1]) << 16) \
|
||||
| (uint32(ub[off + 2]) << 8) | uint32(ub[off + 3])
|
||||
|
||||
|
||||
def read_file_all(path: str, fsize: sizet) -> t.CPtr:
|
||||
"""整读文件到 malloc 缓冲, 失败返回 None
|
||||
注意: read_all 最多读 max_count-1 字节并补 NUL, 故按 fsize+1 申请/请求"""
|
||||
f: fileio.File* = fileio.File(path, fileio.MODE.R)
|
||||
if f.closed:
|
||||
return None
|
||||
buf: t.CPtr = stdlib.malloc(fsize + 1)
|
||||
if buf is None:
|
||||
f.close()
|
||||
return None
|
||||
got: LONG = f.read_all(buf, ULONG(fsize + 1))
|
||||
f.close()
|
||||
if got <= 0 or sizet(got) != fsize:
|
||||
stdlib.free(buf)
|
||||
return None
|
||||
return buf
|
||||
|
||||
|
||||
def file_exists(path: str) -> int:
|
||||
f: fileio.File* = fileio.File(path, fileio.MODE.R)
|
||||
if f.closed:
|
||||
return 0
|
||||
f.close()
|
||||
return 1
|
||||
|
||||
|
||||
def atof(s: str, dflt: double) -> double:
|
||||
"""简易字符串 → double(支持整数部分/小数部分)"""
|
||||
if s is None:
|
||||
return dflt
|
||||
val: double = double(0.0)
|
||||
i: sizet = 0
|
||||
neg: int = 0
|
||||
if s[i] == 45: # '-'
|
||||
neg = 1
|
||||
i += 1
|
||||
while s[i] >= 48 and s[i] <= 57:
|
||||
val = val * double(10.0) + double(s[i] - 48)
|
||||
i += 1
|
||||
if s[i] == 46: # '.'
|
||||
i += 1
|
||||
div: double = double(10.0)
|
||||
while s[i] >= 48 and s[i] <= 57:
|
||||
val = val + double(s[i] - 48) / div
|
||||
div = div * double(10.0)
|
||||
i += 1
|
||||
if neg != 0:
|
||||
val = -val
|
||||
return val
|
||||
|
||||
|
||||
def idx_usable(path: str, is_lb: int) -> int:
|
||||
"""IDX 完整性预检: 魔数 + 维度 + 声明张数与文件尺寸吻合(残缺/仍是 .gz 都判坏)。
|
||||
is_lb: 1=标签 0=图像。1=可用"""
|
||||
f: fileio.File* = fileio.File(path, fileio.MODE.R)
|
||||
if f.closed:
|
||||
return 0
|
||||
sz: LONGLONG = f.size()
|
||||
if sz < 16:
|
||||
f.close()
|
||||
return 0
|
||||
hdr: t.CPtr = stdlib.malloc(64)
|
||||
if hdr is None:
|
||||
f.close()
|
||||
return 0
|
||||
# read_all 最多读 count-1 字节: 取 17 才能拿到 16 字节头部
|
||||
got: LONG = f.read_all(hdr, ULONG(17))
|
||||
f.close()
|
||||
if got < 16:
|
||||
stdlib.free(hdr)
|
||||
return 0
|
||||
hb: uchar* = hdr
|
||||
if hb[0] != 0 or hb[1] != 0 or hb[2] != 8:
|
||||
stdlib.free(hdr)
|
||||
return 0
|
||||
ok: int = 0
|
||||
if is_lb == 0:
|
||||
if hb[3] == 3:
|
||||
n_img: sizet = sizet(be32(hdr, 4))
|
||||
rows: sizet = sizet(be32(hdr, 8))
|
||||
cols: sizet = sizet(be32(hdr, 12))
|
||||
if rows == 28 and cols == 28 and 16 + n_img * N_IN <= sizet(sz):
|
||||
ok = 1
|
||||
else:
|
||||
if hb[3] == 1:
|
||||
n_lb: sizet = sizet(be32(hdr, 4))
|
||||
if 8 + n_lb <= sizet(sz):
|
||||
ok = 1
|
||||
stdlib.free(hdr)
|
||||
return ok
|
||||
|
||||
|
||||
# ============================================================
|
||||
# .gz → IDX
|
||||
# ============================================================
|
||||
def download_inflate(url: str, out_path: str) -> int:
|
||||
"""下载 .gz → 内存解压 → 写原始 IDX 文件。0=成功"""
|
||||
stdio.printf("[download] %s\n", url)
|
||||
# Windows 无系统 CA 路径, 证书链校验必然失败; MNIST 公开数据集, 关闭校验
|
||||
r: requests.Response* = reqhttps.get(url, False)
|
||||
if r is None:
|
||||
stdio.printf("[download] 请求失败: %s\n", reqhttps.last_error())
|
||||
return 1
|
||||
if r.status_code != 200:
|
||||
stdio.printf("[download] HTTP %d\n", r.status_code)
|
||||
requests.release(r)
|
||||
return 1
|
||||
src: str = r.text
|
||||
slen: sizet = sizet(r.body_len)
|
||||
if src is None or slen < 18:
|
||||
stdio.printf("[download] 响应体异常\n")
|
||||
requests.release(r)
|
||||
return 1
|
||||
want: sizet = sizet(gzip.gzip_isize(src, slen))
|
||||
cap: sizet = want
|
||||
if cap == 0:
|
||||
cap = slen * 16
|
||||
if cap < 1024:
|
||||
cap = 1024
|
||||
# 保险起见: ISIZE 与实际不符时报"目标缓冲不足", 就翻倍重试(正常情况下一次成功)
|
||||
dst: t.CPtr = None
|
||||
n: LONG = 0
|
||||
while True:
|
||||
dst = stdlib.malloc(cap)
|
||||
if dst is None:
|
||||
requests.release(r)
|
||||
return 1
|
||||
n = gzip.gzip_decode_bytes(src, slen, dst, cap)
|
||||
if n == -9 or n == -18 or n == -23:
|
||||
stdlib.free(dst)
|
||||
dst = None
|
||||
if cap >= 1073741824:
|
||||
break
|
||||
cap = cap * 2
|
||||
continue
|
||||
break
|
||||
requests.release(r)
|
||||
if n <= 0:
|
||||
stdio.printf("[download] gzip 解压失败(err=%ld)\n", n)
|
||||
if dst is not None:
|
||||
stdlib.free(dst)
|
||||
return 1
|
||||
f: fileio.File* = fileio.File(out_path, fileio.MODE.W)
|
||||
if f.closed:
|
||||
stdio.printf("[download] 无法写入: %s\n", out_path)
|
||||
stdlib.free(dst)
|
||||
return 1
|
||||
f.write(dst, ULONG(n))
|
||||
f.close()
|
||||
stdlib.free(dst)
|
||||
stdio.printf("[download] 已写入 %s (%zu 字节)\n", out_path, sizet(n))
|
||||
return 0
|
||||
|
||||
|
||||
def inflate_local(gz_path: str, out_path: str) -> int:
|
||||
"""本地已下载的 .gz → 解压写入 out_path(GzipFile OOP 范式)。0=成功"""
|
||||
stdio.printf("[gzip] 本地解压: %s\n", gz_path)
|
||||
gz: gzip.GzipFile* = gzip.GzipFile(gz_path)
|
||||
if gz is None or gzip.gzip_ok(gz) == 0:
|
||||
stdio.printf("[gzip] 打开失败(err=%d): %s\n", gzip.gzip_err(gz), gz_path)
|
||||
return 1
|
||||
buf: BYTEPTR = gzip.gzip_read(gz)
|
||||
n: sizet = gzip.gzip_dst_len(gz)
|
||||
if buf is None or n == 0:
|
||||
stdio.printf("[gzip] 解压失败(err=%d): %s\n", gzip.gzip_err(gz), gz_path)
|
||||
gzip.gzip_close(gz)
|
||||
return 1
|
||||
f: fileio.File* = fileio.File(out_path, fileio.MODE.W)
|
||||
if f.closed:
|
||||
stdio.printf("[gzip] 无法写入: %s\n", out_path)
|
||||
gzip.gzip_close(gz)
|
||||
return 1
|
||||
f.write(buf, ULONG(n))
|
||||
f.close()
|
||||
gzip.gzip_close(gz)
|
||||
stdio.printf("[gzip] 已写入 %s (%zu 字节)\n", out_path, n)
|
||||
return 0
|
||||
|
||||
|
||||
def ensure_mnist(data_dir: str, use_test: int, do_dl: int,
|
||||
loc_im: str, loc_lb: str) -> int:
|
||||
"""确保两组 IDX 存在。优先用本地 .gz(--file) 解压, 其次联网下载。0=就绪"""
|
||||
os.mkdir(data_dir) # 不存在则创建(已存在/失败返回 -1, 可忽略)
|
||||
p_im: str = stdlib.malloc(1024)
|
||||
p_lb: str = stdlib.malloc(1024)
|
||||
url_im: str = stdlib.malloc(1024)
|
||||
url_lb: str = stdlib.malloc(1024)
|
||||
if use_test == 0:
|
||||
viperlib.snprintf(p_im, 1024, "%s/train-images-idx3-ubyte", data_dir)
|
||||
viperlib.snprintf(p_lb, 1024, "%s/train-labels-idx1-ubyte", data_dir)
|
||||
viperlib.snprintf(url_im, 1024, "%s/train-images-idx3-ubyte.gz", MNIST_BASE)
|
||||
viperlib.snprintf(url_lb, 1024, "%s/train-labels-idx1-ubyte.gz", MNIST_BASE)
|
||||
else:
|
||||
viperlib.snprintf(p_im, 1024, "%s/t10k-images-idx3-ubyte", data_dir)
|
||||
viperlib.snprintf(p_lb, 1024, "%s/t10k-labels-idx1-ubyte", data_dir)
|
||||
viperlib.snprintf(url_im, 1024, "%s/t10k-images-idx3-ubyte.gz", MNIST_BASE)
|
||||
viperlib.snprintf(url_lb, 1024, "%s/t10k-labels-idx1-ubyte.gz", MNIST_BASE)
|
||||
# 图像: 目标缺失/损坏 → 优先本地 loc_im, 其次 --download 联网
|
||||
# 指定了本地文件就以本地为准: 读不到/解压失败直接报错, 绝不偷偷转联网
|
||||
if idx_usable(p_im, 0) == 0:
|
||||
if loc_im is not None:
|
||||
if file_exists(loc_im) == 0:
|
||||
stdio.printf("[data] 本地图像文件不存在: %s\n", loc_im)
|
||||
return 1
|
||||
if inflate_local(loc_im, p_im) != 0:
|
||||
return 1
|
||||
elif do_dl != 0:
|
||||
if download_inflate(url_im, p_im) != 0:
|
||||
return 1
|
||||
else:
|
||||
return 1
|
||||
# 标签: 同理
|
||||
if idx_usable(p_lb, 1) == 0:
|
||||
if loc_lb is not None:
|
||||
if file_exists(loc_lb) == 0:
|
||||
stdio.printf("[data] 本地标签文件不存在: %s\n", loc_lb)
|
||||
return 1
|
||||
if inflate_local(loc_lb, p_lb) != 0:
|
||||
return 1
|
||||
elif do_dl != 0:
|
||||
if download_inflate(url_lb, p_lb) != 0:
|
||||
return 1
|
||||
else:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
# 缺失位掩码: 1=训练图像 2=训练标签 4=测试图像 8=测试标签
|
||||
define MISS_TRAIN_IM = 1
|
||||
define MISS_TRAIN_LB = 2
|
||||
define MISS_TEST_IM = 4
|
||||
define MISS_TEST_LB = 8
|
||||
|
||||
|
||||
def data_missing_mask(data_dir: str) -> int:
|
||||
"""检测 4 个 IDX 的完整性, 返回缺失位掩码(0=全部就绪)"""
|
||||
os.mkdir(data_dir) # 不存在则创建(已存在/失败返回 -1, 可忽略)
|
||||
p_im: str = stdlib.malloc(1024)
|
||||
p_lb: str = stdlib.malloc(1024)
|
||||
if p_im is None or p_lb is None:
|
||||
return MISS_TRAIN_IM | MISS_TRAIN_LB | MISS_TEST_IM | MISS_TEST_LB
|
||||
m: int = 0
|
||||
idx_paths(data_dir, 0, p_im, p_lb)
|
||||
if idx_usable(p_im, 0) == 0:
|
||||
m = m | MISS_TRAIN_IM
|
||||
if idx_usable(p_lb, 1) == 0:
|
||||
m = m | MISS_TRAIN_LB
|
||||
idx_paths(data_dir, 1, p_im, p_lb)
|
||||
if idx_usable(p_im, 0) == 0:
|
||||
m = m | MISS_TEST_IM
|
||||
if idx_usable(p_lb, 1) == 0:
|
||||
m = m | MISS_TEST_LB
|
||||
stdlib.free(p_im)
|
||||
stdlib.free(p_lb)
|
||||
return m
|
||||
|
||||
|
||||
def ensure_pair(data_dir: str, use_test: int, gz_img: str, gz_lb: str) -> int:
|
||||
"""用本地一对 .gz(图像 idx3 + 标签 idx1) 补齐指定集合(train/test)的缺失项。
|
||||
0=已就绪; 1=失败(缺哪个文件会打印说明)。已就绪的那一半不动。"""
|
||||
if gz_img is None or gz_lb is None:
|
||||
stdio.printf("[data] 需要同时提供 图像(idx3) 与 标签(idx1) 两个 .gz\n")
|
||||
return 1
|
||||
p_im: str = stdlib.malloc(1024)
|
||||
p_lb: str = stdlib.malloc(1024)
|
||||
if p_im is None or p_lb is None:
|
||||
return 1
|
||||
idx_paths(data_dir, use_test, p_im, p_lb)
|
||||
rc: int = 0
|
||||
if idx_usable(p_im, 0) == 0:
|
||||
if file_exists(gz_img) == 0:
|
||||
stdio.printf("[data] 图像 .gz 不存在: %s\n", gz_img)
|
||||
rc = 1
|
||||
elif inflate_local(gz_img, p_im) != 0:
|
||||
rc = 1
|
||||
if rc == 0:
|
||||
if idx_usable(p_lb, 1) == 0:
|
||||
if file_exists(gz_lb) == 0:
|
||||
stdio.printf("[data] 标签 .gz 不存在: %s\n", gz_lb)
|
||||
rc = 1
|
||||
elif inflate_local(gz_lb, p_lb) != 0:
|
||||
rc = 1
|
||||
stdlib.free(p_im)
|
||||
stdlib.free(p_lb)
|
||||
return rc
|
||||
|
||||
|
||||
def miss_mark(flag: int) -> str:
|
||||
if flag != 0:
|
||||
return "缺"
|
||||
return "OK"
|
||||
|
||||
|
||||
def missing_text(mask: int, out: str, cap: sizet):
|
||||
"""把缺失掩码写成可读文本(供界面显示/日志)"""
|
||||
if out is None:
|
||||
return
|
||||
train_im: int = 0
|
||||
if (mask & MISS_TRAIN_IM) != 0:
|
||||
train_im = 1
|
||||
train_lb: int = 0
|
||||
if (mask & MISS_TRAIN_LB) != 0:
|
||||
train_lb = 1
|
||||
test_im: int = 0
|
||||
if (mask & MISS_TEST_IM) != 0:
|
||||
test_im = 1
|
||||
test_lb: int = 0
|
||||
if (mask & MISS_TEST_LB) != 0:
|
||||
test_lb = 1
|
||||
if mask == 0:
|
||||
viperlib.snprintf(out, cap, "全部就绪 (训练集 + 测试集)")
|
||||
return
|
||||
viperlib.snprintf(out, cap,
|
||||
"缺失: 训练集 图像%s 标签%s | 测试集 图像%s 标签%s",
|
||||
miss_mark(train_im), miss_mark(train_lb),
|
||||
miss_mark(test_im), miss_mark(test_lb))
|
||||
|
||||
|
||||
# ============================================================
|
||||
# IDX → 内存缓冲
|
||||
# ============================================================
|
||||
def load_mnist_images(path: str, dst: double*,
|
||||
max_n: sizet) -> sizet:
|
||||
"""读 IDX 图像, 归一化写 dst[i*784+j] = pixel/255; 返回实际张数"""
|
||||
f: fileio.File* = fileio.File(path, fileio.MODE.R)
|
||||
if f.closed:
|
||||
stdio.printf("[mnist] 无法打开图像文件: %s\n", path)
|
||||
return 0
|
||||
sz: LONGLONG = f.size()
|
||||
if sz <= 16:
|
||||
stdio.printf("[mnist] 图像文件过小: %s\n", path)
|
||||
f.close()
|
||||
return 0
|
||||
f.close()
|
||||
whole: t.CPtr = read_file_all(path, sizet(sz))
|
||||
if whole is None:
|
||||
stdio.printf("[mnist] 读取失败: %s\n", path)
|
||||
return 0
|
||||
# IDX 图像魔数: 00 00 08 03(压缩 .gz 以 1F 8B 开头, 会在此被识别)
|
||||
wu: uchar* = whole
|
||||
if wu[0] != 0 or wu[1] != 0 or wu[2] != 8 or wu[3] != 3:
|
||||
stdio.printf("[mnist] 图像文件头非法(可能仍是 .gz 压缩数据, 请先解压): %s\n", path)
|
||||
stdlib.free(whole)
|
||||
return 0
|
||||
n_total: sizet = sizet(be32(whole, 4))
|
||||
rows: sizet = sizet(be32(whole, 8))
|
||||
cols: sizet = sizet(be32(whole, 12))
|
||||
if rows != 28 or cols != 28:
|
||||
stdio.printf("[mnist] 非 28x28: %dx%d\n", int(rows), int(cols))
|
||||
stdlib.free(whole)
|
||||
return 0
|
||||
use_n: sizet = n_total
|
||||
if max_n != 0 and max_n < use_n:
|
||||
use_n = max_n
|
||||
if 16 + use_n * N_IN > sizet(sz):
|
||||
stdio.printf("[mnist] 图像数据不足: 头部声明=%zu, 文件最多可容纳=%zu (%s)\n",
|
||||
n_total, (sizet(sz) - 16) / N_IN, path)
|
||||
stdlib.free(whole)
|
||||
return 0
|
||||
ub: uchar* = whole
|
||||
d: double* = dst
|
||||
inv255: double = double(1.0) / double(255.0)
|
||||
for i in range(use_n):
|
||||
for j in range(N_IN):
|
||||
d[i * N_IN + j] = double(ub[16 + i * N_IN + j]) * inv255
|
||||
stdlib.free(whole)
|
||||
return use_n
|
||||
|
||||
|
||||
def load_mnist_labels(path: str, dst: int*,
|
||||
max_n: sizet) -> sizet:
|
||||
"""读 IDX 标签到 dst(每元素 1 字节), 返回实际张数"""
|
||||
f: fileio.File* = fileio.File(path, fileio.MODE.R)
|
||||
if f.closed:
|
||||
stdio.printf("[mnist] 无法打开标签文件: %s\n", path)
|
||||
return 0
|
||||
sz: LONGLONG = f.size()
|
||||
if sz <= 8:
|
||||
stdio.printf("[mnist] 标签文件过小: %s\n", path)
|
||||
f.close()
|
||||
return 0
|
||||
f.close()
|
||||
whole: t.CPtr = read_file_all(path, sizet(sz))
|
||||
if whole is None:
|
||||
return 0
|
||||
# IDX 标签魔数: 00 00 08 01
|
||||
wu2: uchar* = whole
|
||||
if wu2[0] != 0 or wu2[1] != 0 or wu2[2] != 8 or wu2[3] != 1:
|
||||
stdio.printf("[mnist] 标签文件头非法(可能仍是 .gz 压缩数据, 请先解压): %s\n", path)
|
||||
stdlib.free(whole)
|
||||
return 0
|
||||
n_total: sizet = sizet(be32(whole, 4))
|
||||
use_n: sizet = n_total
|
||||
if max_n != 0 and max_n < use_n:
|
||||
use_n = max_n
|
||||
if 8 + use_n > sizet(sz):
|
||||
stdio.printf("[mnist] 标签数据不足: 头部声明=%zu, 文件最多可容纳=%zu (%s)\n",
|
||||
n_total, sizet(sz) - 8, path)
|
||||
stdlib.free(whole)
|
||||
return 0
|
||||
ub: uchar* = whole
|
||||
lb: int* = dst
|
||||
for i in range(use_n):
|
||||
lb[i] = int(ub[8 + i])
|
||||
stdlib.free(whole)
|
||||
return use_n
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 路径拼装(窗口/命令行共用)
|
||||
# ============================================================
|
||||
def idx_paths(data_dir: str, use_test: int, out_img: str, out_lbl: str):
|
||||
"""把 IDX 明文路径写入调用方给的 1024 字节缓冲"""
|
||||
if use_test == 0:
|
||||
viperlib.snprintf(out_img, 1024, "%s/train-images-idx3-ubyte", data_dir)
|
||||
viperlib.snprintf(out_lbl, 1024, "%s/train-labels-idx1-ubyte", data_dir)
|
||||
else:
|
||||
viperlib.snprintf(out_img, 1024, "%s/t10k-images-idx3-ubyte", data_dir)
|
||||
viperlib.snprintf(out_lbl, 1024, "%s/t10k-labels-idx1-ubyte", data_dir)
|
||||
|
||||
|
||||
def gz_urls(use_test: int, out_img: str, out_lbl: str):
|
||||
"""把 .gz 下载 URL 写入调用方给的 1024 字节缓冲"""
|
||||
if use_test == 0:
|
||||
viperlib.snprintf(out_img, 1024, "%s/train-images-idx3-ubyte.gz", MNIST_BASE)
|
||||
viperlib.snprintf(out_lbl, 1024, "%s/train-labels-idx1-ubyte.gz", MNIST_BASE)
|
||||
else:
|
||||
viperlib.snprintf(out_img, 1024, "%s/t10k-images-idx3-ubyte.gz", MNIST_BASE)
|
||||
viperlib.snprintf(out_lbl, 1024, "%s/t10k-labels-idx1-ubyte.gz", MNIST_BASE)
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,293 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ============================================================
|
||||
# ViperMNIST - 28x28 手写数字识别(MLP / CNN, torch-lite)
|
||||
#
|
||||
# 入口分发:
|
||||
# * 无命令行参数 → 启动 Qt6 图形面板(数据/训练/推理, 含 3D 网络图)
|
||||
# * 带命令行参数 → 走原 CLI(train / use)
|
||||
#
|
||||
# CLI 用法:
|
||||
# app.exe --mode train --data-dir data --epochs 5 --batch 64 --lr 0.01 \
|
||||
# --hidden 256 --save models/mnist.bin --limit 60000
|
||||
# app.exe --mode train --arch cnn --c1 8 --c2 16 (卷积 + 池化, 更抗形变)
|
||||
# app.exe --mode use --data-dir data --load models/mnist.bin --show 10
|
||||
# --download 自动下载缺失数据; --file 指定本地已下载 .gz(可 1~2 次)
|
||||
# --no-aug 关闭数据增强(默认开: 训练时随机平移 ±2px)
|
||||
# 注: use 模式的架构/hidden/通道以模型文件头为准, 无需与命令行走一致
|
||||
#
|
||||
# 模型: MLP = Linear(784->hidden) -> ReLU -> Linear(hidden->10)
|
||||
# CNN = Conv(1->c1,3x3,pad1) -> ReLU -> MaxPool2 -> Conv(c1->c2,3x3,pad1)
|
||||
# -> ReLU -> MaxPool2 -> Linear(49*c2->hidden) -> ReLU -> Linear(hidden->10)
|
||||
# 权重形状同 PyTorch: Conv(out,in,k,k) / Linear(out,in), b(out)
|
||||
# 计算: torch-lite 线性前向走 vipersimd.simd_dot4d, relu 走 simd_max4d
|
||||
# ============================================================
|
||||
import t, c
|
||||
from stdint import *
|
||||
import stdlib
|
||||
import string
|
||||
import stdio
|
||||
import sys
|
||||
import memhub
|
||||
import argparse
|
||||
import requests
|
||||
import torch
|
||||
import w32.win32console as w32cmd
|
||||
import lib.MnistData as MnistData
|
||||
import lib.MnistNet as MnistNet
|
||||
import lib.AppGui as AppGui
|
||||
|
||||
define CODE_PAGE = 65001 # UTF-8 控制台
|
||||
|
||||
# 主内存池(CLI 与 GUI 共用)
|
||||
define POOL_SIZE = 536870912 # 512 MB
|
||||
|
||||
|
||||
# ============================================================
|
||||
# CLI
|
||||
# ============================================================
|
||||
def run_cli(pool: memhub.MemBuddy*) -> int:
|
||||
parser: argparse.ArgumentParser* = argparse.ArgumentParser(
|
||||
"ViperMNIST", "28x28 MNIST 手写数字识别 (train/use)", pool=pool)
|
||||
parser.add_argument("--mode", None, argparse.STRING, 0, "train", False,
|
||||
argparse.STORE, "train=训练 / use=加载模型推理")
|
||||
parser.add_argument("--data-dir", None, argparse.STRING, 0, "data", False,
|
||||
argparse.STORE, "MNIST IDX 文件所在目录")
|
||||
parser.add_argument("--epochs", None, argparse.INT, 5, None, False,
|
||||
argparse.STORE, "训练轮数")
|
||||
parser.add_argument("--batch", None, argparse.INT, 64, None, False,
|
||||
argparse.STORE, "批大小")
|
||||
parser.add_argument("--lr", None, argparse.STRING, 0, "0.01", False,
|
||||
argparse.STORE, "学习率(小数; 训练用重球法, 默认 0.01)")
|
||||
parser.add_argument("--hidden", None, argparse.INT, 256, None, False,
|
||||
argparse.STORE, "隐藏层宽度(CNN 下为 FC1 宽度)")
|
||||
parser.add_argument("--arch", None, argparse.STRING, 0, "mlp", False,
|
||||
argparse.STORE, "架构: mlp=全连接 / cnn=卷积+池化")
|
||||
parser.add_argument("--c1", None, argparse.INT, 8, None, False,
|
||||
argparse.STORE, "CNN conv1 通道数(仅 --arch cnn 生效)")
|
||||
parser.add_argument("--c2", None, argparse.INT, 16, None, False,
|
||||
argparse.STORE, "CNN conv2 通道数(仅 --arch cnn 生效)")
|
||||
parser.add_argument("--no-aug", None, argparse.BOOL, 0, None, False,
|
||||
argparse.STORE_TRUE, "关闭数据增强(默认开启: 训练时随机平移 ±2px)")
|
||||
parser.add_argument("--limit", None, argparse.INT, 0, None, False,
|
||||
argparse.STORE, "样本上限(0=全部)")
|
||||
parser.add_argument("--save", None, argparse.STRING, 0, "mnist.bin",
|
||||
False, argparse.STORE, "训练保存路径")
|
||||
parser.add_argument("--load", None, argparse.STRING, 0, "mnist.bin",
|
||||
False, argparse.STORE, "推理加载路径")
|
||||
parser.add_argument("--show", None, argparse.INT, 10, None, False,
|
||||
argparse.STORE, "use 模式打印前 N 条预测")
|
||||
parser.add_argument("--seed", None, argparse.INT, 42, None, False,
|
||||
argparse.STORE, "随机种子")
|
||||
parser.add_argument("--download", None, argparse.BOOL, 0, None, False,
|
||||
argparse.STORE_TRUE, "自动下载缺失的 MNIST 数据")
|
||||
parser.add_argument("--file", None, argparse.STRING, 0, None, False,
|
||||
argparse.STORE, "本地已下载的 .gz(可 1~2 次: 图像/标签各一份), 优先本地解压替代联网")
|
||||
parser.add_argument("--help", "-h", argparse.BOOL, 0, None, False,
|
||||
argparse.STORE_TRUE, "显示帮助并退出")
|
||||
|
||||
args: argparse.ParsedArgs* = parser.parse_args(sys._argc, sys._argv)
|
||||
if args is None:
|
||||
stdio.printf("参数解析失败\n")
|
||||
return 1
|
||||
if args.get_bool("help"):
|
||||
parser.print_help()
|
||||
return 0
|
||||
|
||||
mode: str = args.get_str("mode")
|
||||
data_dir: str = args.get_str("data-dir")
|
||||
epochs: int = args.get_int("epochs")
|
||||
batch: int = args.get_int("batch")
|
||||
hidden: int = args.get_int("hidden")
|
||||
limit: int = args.get_int("limit")
|
||||
show: int = args.get_int("show")
|
||||
seed: int = args.get_int("seed")
|
||||
save_path: str = args.get_str("save")
|
||||
load_path: str = args.get_str("load")
|
||||
arch_str: str = args.get_str("arch")
|
||||
c1_arg: int = args.get_int("c1")
|
||||
c2_arg: int = args.get_int("c2")
|
||||
lr_str: str = args.get_str("lr")
|
||||
download: int = 0
|
||||
if args.get_bool("download"):
|
||||
download = 1
|
||||
no_aug: int = 0
|
||||
if args.get_bool("no-aug"):
|
||||
no_aug = 1
|
||||
|
||||
# --file 本地 .gz(可 1~2 次): 按文件名含 -labels-/-images- 归类, 无名匹配按先后补空位
|
||||
loc_im: str = None
|
||||
loc_lb: str = None
|
||||
i_arg: int = 1
|
||||
while i_arg + 1 < sys._argc:
|
||||
if string.strcmp(sys._argv[i_arg], "--file") == 0:
|
||||
fv: str = sys._argv[i_arg + 1]
|
||||
is_lb: int = 0
|
||||
if string.strstr(fv, "-labels-") is not None:
|
||||
is_lb = 1
|
||||
is_im: int = 0
|
||||
if string.strstr(fv, "-images-") is not None:
|
||||
is_im = 1
|
||||
if is_lb != 0 and is_im == 0 and loc_lb is None:
|
||||
loc_lb = fv
|
||||
elif is_im != 0 and is_lb == 0 and loc_im is None:
|
||||
loc_im = fv
|
||||
elif loc_im is None:
|
||||
loc_im = fv
|
||||
elif loc_lb is None:
|
||||
loc_lb = fv
|
||||
i_arg += 2
|
||||
else:
|
||||
i_arg += 1
|
||||
file_set: int = 0
|
||||
if loc_im is not None or loc_lb is not None:
|
||||
file_set = 1
|
||||
|
||||
lr: double = double(0.01)
|
||||
if lr_str is not None and string.strlen(lr_str) > 0:
|
||||
lr = MnistData.atof(lr_str, lr)
|
||||
|
||||
MnistNet.rand_seed(uint64(seed))
|
||||
torch.manual_seed(int64(seed))
|
||||
|
||||
h: sizet = sizet(hidden)
|
||||
# 架构与数据增强: 与 GUI 口径一致(通道数只在 CNN 下有意义)
|
||||
arch: int = MnistNet.ARCH_MLP
|
||||
if string.strcmp(arch_str, "cnn") == 0 or string.strcmp(arch_str, "CNN") == 0:
|
||||
arch = MnistNet.ARCH_CNN
|
||||
c1: sizet = 0
|
||||
c2: sizet = 0
|
||||
if arch == MnistNet.ARCH_CNN:
|
||||
c1 = sizet(c1_arg)
|
||||
c2 = sizet(c2_arg)
|
||||
augment: int = 1
|
||||
if no_aug != 0:
|
||||
augment = 0
|
||||
stdio.printf("ViperMNIST mode=%s data=%s limit=%d hidden=%d epochs=%d batch=%d lr=%.3f arch=%s\n",
|
||||
mode, data_dir, limit, hidden, epochs, batch, lr, arch_str)
|
||||
|
||||
p_img: str = stdlib.malloc(1024)
|
||||
p_lbl: str = stdlib.malloc(1024)
|
||||
|
||||
if string.strcmp(mode, "train") == 0:
|
||||
net: MnistNet.Net* = MnistNet.Net(pool, h, sizet(arch), c1, c2)
|
||||
if MnistNet.net_ok(net) == 0:
|
||||
stdio.printf("FAIL: 网络分配失败\n")
|
||||
return 1
|
||||
MnistNet.init_net_weights(net)
|
||||
MnistData.idx_paths(data_dir, 0, p_img, p_lbl)
|
||||
if download != 0 or file_set != 0:
|
||||
if MnistData.ensure_mnist(data_dir, 0, download, loc_im, loc_lb) != 0:
|
||||
stdio.printf("数据就绪失败, 请检查 --file 路径 / 网络 / data-dir\n")
|
||||
return 1
|
||||
cap: sizet = sizet(60000)
|
||||
if limit > 0 and limit < 60000:
|
||||
cap = sizet(limit)
|
||||
xbuf: double* = stdlib.malloc(cap * MnistData.N_IN * 8)
|
||||
ybuf: int* = stdlib.malloc(cap * 4)
|
||||
if xbuf is None or ybuf is None:
|
||||
stdio.printf("FAIL: 训练数据缓冲分配失败\n")
|
||||
return 1
|
||||
n_im: sizet = MnistData.load_mnist_images(p_img, xbuf, cap)
|
||||
n_lb: sizet = MnistData.load_mnist_labels(p_lbl, ybuf, cap)
|
||||
n_tr: sizet = n_im
|
||||
if n_lb < n_tr:
|
||||
n_tr = n_lb
|
||||
stdio.printf("加载训练样本=%zu 张 (%s)\n", n_tr, MnistNet.arch_name(arch))
|
||||
if n_tr == 0:
|
||||
stdio.printf("未找到可用数据。如 data 目录文件残缺/损坏, 请加 --download 重新拉取或用 --file 指定本地 .gz\n")
|
||||
return 1
|
||||
MnistNet.prog_init()
|
||||
MnistNet.train_run(pool, net, xbuf, ybuf, n_tr,
|
||||
sizet(batch), sizet(epochs), lr, save_path, augment)
|
||||
stdlib.free(xbuf)
|
||||
stdlib.free(ybuf)
|
||||
elif string.strcmp(mode, "use") == 0:
|
||||
# 架构/hidden/通道以模型文件头为准(命令行给的对推理无意义), 先读头再建网
|
||||
minfo: int* = stdlib.malloc(20)
|
||||
if minfo is None:
|
||||
stdio.printf("FAIL: 模型头缓冲分配失败\n")
|
||||
return 1
|
||||
if MnistNet.peek_model(load_path, minfo) == 0:
|
||||
stdlib.free(minfo)
|
||||
stdio.printf("加载模型失败, 请先训练(--mode train): %s\n", load_path)
|
||||
return 1
|
||||
f_arch: int = minfo[0]
|
||||
f_hidden: int = minfo[1]
|
||||
f_c1: int = minfo[2]
|
||||
f_c2: int = minfo[3]
|
||||
stdlib.free(minfo)
|
||||
uh: sizet = sizet(f_hidden)
|
||||
uc1: sizet = 0
|
||||
uc2: sizet = 0
|
||||
if f_arch == MnistNet.ARCH_CNN:
|
||||
uc1 = sizet(f_c1)
|
||||
uc2 = sizet(f_c2)
|
||||
net2: MnistNet.Net* = MnistNet.Net(pool, uh, sizet(f_arch),
|
||||
uc1, uc2)
|
||||
if MnistNet.net_ok(net2) == 0:
|
||||
stdio.printf("FAIL: 网络分配失败\n")
|
||||
return 1
|
||||
MnistData.idx_paths(data_dir, 1, p_img, p_lbl)
|
||||
if download != 0 or file_set != 0:
|
||||
if MnistData.ensure_mnist(data_dir, 1, download, loc_im, loc_lb) != 0:
|
||||
stdio.printf("数据就绪失败, 请检查 --file 路径 / 网络 / data-dir\n")
|
||||
return 1
|
||||
cap2: sizet = sizet(10000)
|
||||
if limit > 0 and limit < 10000:
|
||||
cap2 = sizet(limit)
|
||||
xbuf2: double* = stdlib.malloc(cap2 * MnistData.N_IN * 8)
|
||||
ybuf2: int* = stdlib.malloc(cap2 * 4)
|
||||
if xbuf2 is None or ybuf2 is None:
|
||||
stdio.printf("FAIL: 测试数据缓冲分配失败\n")
|
||||
return 1
|
||||
n_te: sizet = MnistData.load_mnist_images(p_img, xbuf2, cap2)
|
||||
n_tl: sizet = MnistData.load_mnist_labels(p_lbl, ybuf2, cap2)
|
||||
if n_tl < n_te:
|
||||
n_te = n_tl
|
||||
stdio.printf("加载测试样本=%zu 张\n", n_te)
|
||||
if n_te == 0:
|
||||
return 1
|
||||
if MnistNet.load_model(net2, load_path) != 0:
|
||||
stdio.printf("加载模型失败, 请先训练(--mode train)\n")
|
||||
return 1
|
||||
stdio.printf("加载模型: %s (%s, hidden=%d)\n",
|
||||
load_path, MnistNet.arch_name(f_arch), f_hidden)
|
||||
MnistNet.evaluate_net(pool, net2, xbuf2, ybuf2, n_te,
|
||||
sizet(batch), sizet(show))
|
||||
stdlib.free(xbuf2)
|
||||
stdlib.free(ybuf2)
|
||||
else:
|
||||
stdio.printf("未知模式: %s (可选 train/use)\n", mode)
|
||||
return 1
|
||||
|
||||
stdlib.free(p_img)
|
||||
stdlib.free(p_lbl)
|
||||
return 0
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 入口
|
||||
# ============================================================
|
||||
def main() -> int:
|
||||
# UTF-8 控制台(与 TransPyV main 一致)
|
||||
w32cmd.SetConsoleOutputCP(CODE_PAGE)
|
||||
w32cmd.SetConsoleCP(CODE_PAGE)
|
||||
|
||||
# 主内存池: CLI 与 GUI 共用, 先建池再解析 argv(GUI 分支也需要池)
|
||||
arena: bytes = stdlib.malloc(POOL_SIZE)
|
||||
if arena is None:
|
||||
stdio.printf("FAIL: 无法分配主池\n")
|
||||
return 1
|
||||
mb: memhub.MemBuddy* = memhub.MemBuddy(arena, POOL_SIZE, 0)
|
||||
if mb is None:
|
||||
stdio.printf("FAIL: MemBuddy 初始化失败\n")
|
||||
return 1
|
||||
argparse._mbuddy = mb
|
||||
sys._mbuddy = mb
|
||||
requests._mbuddy = mb # requests/https 内部用 _mbuddy 分配响应缓冲
|
||||
sys._init_argv()
|
||||
|
||||
# 无参数 → Qt 面板; 有参数 → CLI
|
||||
if sys._argc <= 1:
|
||||
return AppGui.run(mb)
|
||||
return run_cli(mb)
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"name": "ViperMNIST",
|
||||
"version": "2.0.0",
|
||||
"description": "MNIST 手写数字识别(28x28 灰度, 两层 MLP): 无参数启动 vqt6 Qt 面板(数据/训练/推理 + 3D 网络图 + Qt Charts 曲线), 带参数走原 CLI(train/use); 计算走 includes/torch (仿 PyTorch 迷你库, 线性层内积 vipersimd dot4/relu max4d)",
|
||||
"source_dir": "./App",
|
||||
"build_dir": "./.tpv_build",
|
||||
"compiler": {
|
||||
"cmd": "llc",
|
||||
"flags": [
|
||||
"-filetype=obj",
|
||||
"-relocation-model=pic"
|
||||
]
|
||||
},
|
||||
"linker": {
|
||||
"cmd": "D:/msys64/mingw64/bin/clang++.exe",
|
||||
"flags": [
|
||||
"-fuse-ld=lld",
|
||||
"-lmsvcrt",
|
||||
"-lpthread",
|
||||
"-lmingwex",
|
||||
"-lkernel32",
|
||||
"-lws2_32",
|
||||
"-ldnsapi",
|
||||
"-liphlpapi",
|
||||
"-lgdi32",
|
||||
"-lcrypt32",
|
||||
"-ladvapi32",
|
||||
"-L", "D:/Qt/6.11.2/mingw_64/lib",
|
||||
"-lQt6Core",
|
||||
"-lQt6Gui",
|
||||
"-lQt6Widgets",
|
||||
"-lQt6Network",
|
||||
"-lQt6Charts",
|
||||
"-lws2_32",
|
||||
"-lcrypt32",
|
||||
"-ladvapi32",
|
||||
"-liphlpapi",
|
||||
"-ldnsapi"
|
||||
],
|
||||
"static_libs": [
|
||||
"../../includes/vqt6/build/lib/libvqt6_bridge.a",
|
||||
"../../dlls/lib/libssl.a",
|
||||
"../../dlls/lib/libcrypto.a"
|
||||
],
|
||||
"output": "app.exe"
|
||||
},
|
||||
"includes": [
|
||||
"../../includes"
|
||||
],
|
||||
"target": {
|
||||
"triple": "x86_64-pc-windows-gnu",
|
||||
"datalayout": "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
|
||||
},
|
||||
"options": {
|
||||
"slice_level": 3,
|
||||
"sha1_slice_level": 1,
|
||||
"target": "llvm",
|
||||
"strict_mode": true
|
||||
},
|
||||
"runtime": {
|
||||
"qt6_path": "D:/Qt/6.11.2/mingw_64",
|
||||
"qt6_dlls": [
|
||||
"Qt6Core.dll",
|
||||
"Qt6Gui.dll",
|
||||
"Qt6Widgets.dll",
|
||||
"Qt6Network.dll",
|
||||
"Qt6Charts.dll",
|
||||
"Qt6OpenGL.dll",
|
||||
"Qt6OpenGLWidgets.dll"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
*.o
|
||||
*.obj
|
||||
*.exe
|
||||
*.img
|
||||
*.iso
|
||||
**/__pycache__/
|
||||
.cache/
|
||||
*.pyc
|
||||
|
||||
# 编译
|
||||
**/output/
|
||||
**/temp/
|
||||
**/.tpv_build/
|
||||
@@ -0,0 +1,46 @@
|
||||
# main.vp — ViperOpenSSLProject:版本 + 摘要演示
|
||||
#
|
||||
# 使用 includes/openssl FFI 层(import openssl):
|
||||
# 1) 打印 OpenSSL 版本号
|
||||
# 2) 计算 "hello openssl" 的 MD5 / SHA256 十六进制摘要
|
||||
#
|
||||
# 链接目标尚未定(msys2 预装 3.6.2 动态库 或 openssl_358_C 自编译),
|
||||
# 配好 project.vpj 的 -lcrypto/-lssl(或 static_libs)后才可链接运行。
|
||||
import t, c
|
||||
import stdlib
|
||||
import binascii
|
||||
import openssl
|
||||
import w32.win32console as w32cmd
|
||||
import viperlib
|
||||
|
||||
|
||||
def print_digest(name: str, buf: bytes, length: int):
|
||||
hex_str: str = binascii.hexlify(buf, length)
|
||||
print(f"{name}: {hex_str}")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
w32cmd.SetConsoleOutputCP(65001)
|
||||
w32cmd.SetConsoleCP(65001)
|
||||
|
||||
# ---- 1) 版本 ----
|
||||
ver: str = openssl.OpenSSL_version(openssl.OpenSslVersion)
|
||||
print("OpenSSL version:", ver)
|
||||
num: uint32 = openssl.OpenSSL_version_num()
|
||||
print(f"Version number : 0x{num:x}")
|
||||
|
||||
# ---- 2) 摘要 ----
|
||||
data: str = "hello openssl"
|
||||
dlen: int = len(data)
|
||||
|
||||
md5_buf: bytes = stdlib.malloc(openssl.Md5DigestLength)
|
||||
openssl.MD5(data, dlen, md5_buf)
|
||||
print_digest("MD5 ", md5_buf, openssl.Md5DigestLength)
|
||||
stdlib.free(md5_buf)
|
||||
|
||||
sha256_buf: bytes = stdlib.malloc(openssl.Sha256DigestLength)
|
||||
openssl.SHA256(data, dlen, sha256_buf)
|
||||
print_digest("SHA256", sha256_buf, openssl.Sha256DigestLength)
|
||||
stdlib.free(sha256_buf)
|
||||
|
||||
return 0
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 TermiNexus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,36 @@
|
||||
# ViperOpenSSLProject
|
||||
|
||||
基于 [ViperTemplateProject](../ViperTemplateProject) 模板创建的 **OpenSSL 3.x FFI 测试工程**,
|
||||
用来验证 `includes/openssl` FFI 层(摘要/哈希、EVP、证书 PKI、SSL/TLS)的链接与调用。
|
||||
|
||||
## 说明
|
||||
|
||||
- FFI 层源码在 `../includes/openssl/`(`import openssl` 使用)。
|
||||
- 当前 App/main.vp 为「版本 + SHA256/MD5 摘要」演示。
|
||||
- 链接目标:**已选 msys2 mingw64 预装 OpenSSL 3.6.2 动态库**(`-L D:/msys64/mingw64/lib -lssl -lcrypto`,见 project.vpj)。
|
||||
运行 `app.exe` 前需把 `D:/msys64/mingw64/bin` 加入 PATH(依赖 `libcrypto-3-x64.dll` / `libssl-3-x64.dll`)。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
ViperOpenSSLProject/
|
||||
├── .gitignore
|
||||
├── LICENSE
|
||||
├── README.md
|
||||
├── project.vpj # 编译/链接配置(msys2 3.6.2 动态库)
|
||||
└── App/
|
||||
└── main.vp # 版本 + 摘要演示
|
||||
```
|
||||
|
||||
## 备选:openssl_358_C 源码自编译静态库
|
||||
|
||||
若改用 `../includes/openssl/openssl_358_C` 自编译,先把生成的
|
||||
`libcrypto.a` / `libssl.a` 填入 `linker.static_libs`(并去掉上面的 `-lcrypto -lssl`)。
|
||||
|
||||
## 编译运行
|
||||
|
||||
```powershell
|
||||
cd D:\Users\TermiNexus\Desktop\TransPyC\ViperOpenSSLProject
|
||||
python ..\Projectrans.py --project .\project.vpj --clean-cache=false
|
||||
.\output\app.exe # 需 D:\msys64\mingw64\bin 在 PATH(依赖 libcrypto-3-x64.dll/libssl-3-x64.dll)
|
||||
```
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "ViperOpenSSLProject - OpenSSL 3.x FFI Test",
|
||||
"version": "1.0.0",
|
||||
"source_dir": "./App",
|
||||
"build_dir": "./.tpv_build",
|
||||
"compiler": {
|
||||
"cmd": "llc",
|
||||
"flags": [
|
||||
"-filetype=obj",
|
||||
"-relocation-model=pic"
|
||||
]
|
||||
},
|
||||
"linker": {
|
||||
"cmd": "D:/msys64/mingw64/bin/clang++.exe",
|
||||
"flags": [
|
||||
"-fuse-ld=lld",
|
||||
"-lws2_32",
|
||||
"-lcrypt32",
|
||||
"-ladvapi32",
|
||||
"-lmsvcrt",
|
||||
"-lmingwex",
|
||||
"-lgdi32",
|
||||
"-lpthread",
|
||||
"-lkernel32"
|
||||
],
|
||||
"static_libs": [
|
||||
"../../dlls/lib/libssl.a",
|
||||
"../../dlls/lib/libcrypto.a"
|
||||
],
|
||||
"output": "app.exe"
|
||||
},
|
||||
"includes": [
|
||||
"../../includes"
|
||||
],
|
||||
"target": {
|
||||
"triple": "x86_64-pc-windows-gnu",
|
||||
"datalayout": "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
|
||||
},
|
||||
"options": {
|
||||
"slice_level": 3,
|
||||
"target": "llvm",
|
||||
"strict_mode": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
# ============================================================
|
||||
# ViperQtHelloWorld - vqt6 的第一个示例
|
||||
#
|
||||
# 创建一个 Qt6 窗口:
|
||||
# - 标题: "vqt6 Hello"
|
||||
# - 大小: 400x300
|
||||
# - 居中文本: "你好,Viper!"
|
||||
# - 一个按钮: "退出"(点击关闭应用)
|
||||
#
|
||||
# 使用 vqt6 (Viper 的 Qt6 绑定) 创建窗口
|
||||
# ============================================================
|
||||
|
||||
import t
|
||||
import c
|
||||
import vqt6
|
||||
import stdlib
|
||||
import memhub
|
||||
import w32.win32console as w32cmd
|
||||
import viperlib
|
||||
|
||||
define pagecode = 65001
|
||||
|
||||
# 本模块的内存池句柄: list[str] 等泛型容器都从 _mbuddy 里分配, 必须在 main
|
||||
# 开头先初始化(项目约定, 见 includes/ast/__init__.vp 顶部说明)。不初始化时它
|
||||
# 就是全零, QApplication([]) 构造 [] 时会通过它做虚调用 → 空指针崩溃。
|
||||
_mbuddy: memhub.MemBuddy*
|
||||
define POOL_SIZE = 1 * 1024 * 1024
|
||||
|
||||
|
||||
@t.CExport
|
||||
def main() -> int:
|
||||
global _mbuddy
|
||||
|
||||
w32cmd.SetConsoleOutputCP(pagecode)
|
||||
w32cmd.SetConsoleCP(pagecode)
|
||||
|
||||
# 模块内存池 (QApplication([]) 里的 [] 要在这里分配)
|
||||
arena: bytes = stdlib.malloc(POOL_SIZE)
|
||||
if arena is None:
|
||||
print("[vqt6] 内存池分配失败")
|
||||
return 1
|
||||
_mbuddy = memhub.MemBuddy(arena, POOL_SIZE)
|
||||
|
||||
print("[vqt6] 正在创建 QApplication...")
|
||||
|
||||
# 1. 创建 QApplication
|
||||
app: vqt6.QApplication = vqt6.QApplication([])
|
||||
|
||||
print("[vqt6] 正在创建窗口...")
|
||||
|
||||
# 2. 创建主窗口
|
||||
window: vqt6.QWidget = vqt6.QWidget()
|
||||
window.setWindowTitle("vqt6 Hello")
|
||||
window.resize(400, 300)
|
||||
window.setGeometry(100, 100, 400, 300)
|
||||
|
||||
# 3. 创建居中文本标签
|
||||
label: vqt6.QLabel = vqt6.QLabel("你好,Viper!", window)
|
||||
label.setAlignment(vqt6.QT_ALIGN_CENTER)
|
||||
|
||||
# 4. 创建退出按钮
|
||||
button: vqt6.QPushButton = vqt6.QPushButton("退出", window)
|
||||
|
||||
# 5. 显示窗口
|
||||
window.show()
|
||||
|
||||
print("[vqt6] 窗口已显示,进入事件循环")
|
||||
print("[vqt6] 关闭窗口以退出程序")
|
||||
|
||||
# 6. 进入事件循环
|
||||
ret: int = app.exec()
|
||||
|
||||
print(f"[vqt6] 应用退出,返回码: {ret}")
|
||||
return ret
|
||||
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "vqt6 Hello World",
|
||||
"version": "0.1.0",
|
||||
"description": "vqt6 第一个示例 - 创建一个 Qt6 窗口,点击按钮退出",
|
||||
"source_dir": "./App",
|
||||
"build_dir": "./.tpv_build",
|
||||
"compiler": {
|
||||
"cmd": "llc",
|
||||
"flags": ["-filetype=obj", "-relocation-model=pic"]
|
||||
},
|
||||
"linker": {
|
||||
"cmd": "D:/msys64/mingw64/bin/clang++.exe",
|
||||
"flags": [
|
||||
"-fuse-ld=lld",
|
||||
"-lmsvcrt",
|
||||
"-lucrt",
|
||||
"-lpthread",
|
||||
"-lmingwex",
|
||||
"-lkernel32",
|
||||
"-lws2_32",
|
||||
"-ldnsapi",
|
||||
"-liphlpapi",
|
||||
"-L", "D:/Qt/6.11.2/mingw_64/lib",
|
||||
"-lQt6Core",
|
||||
"-lQt6Gui",
|
||||
"-lQt6Widgets",
|
||||
"-lQt6Network",
|
||||
"-lQt6Charts"
|
||||
],
|
||||
"static_libs": [
|
||||
"../../includes/vqt6/build/lib/libvqt6_bridge.a"
|
||||
],
|
||||
"output": "app.exe"
|
||||
},
|
||||
"includes": [
|
||||
"../../includes"
|
||||
],
|
||||
"target": {
|
||||
"triple": "x86_64-pc-windows-gnu",
|
||||
"datalayout": "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
|
||||
},
|
||||
"options": {
|
||||
"slice_level": 3,
|
||||
"sha1_slice_level": 1,
|
||||
"target": "llvm",
|
||||
"strict_mode": true
|
||||
},
|
||||
"runtime": {
|
||||
"qt6_path": "D:/Qt/6.11.2/mingw_64",
|
||||
"qt6_dlls": ["Qt6Core.dll", "Qt6Gui.dll", "Qt6Widgets.dll", "Qt6Network.dll", "Qt6Charts.dll", "Qt6OpenGL.dll", "Qt6OpenGLWidgets.dll"]
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 242 B |
Binary file not shown.
|
After Width: | Height: | Size: 404 B |
@@ -0,0 +1,102 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# make_check_icons.py — 生成复选框指示器 PNG(未勾=灰框 / 勾选=蓝框+白对勾)
|
||||
# 纯 Python + zlib 实现,无第三方依赖;4x4 子采样抗锯齿
|
||||
import os
|
||||
import struct
|
||||
import zlib
|
||||
|
||||
W = 32
|
||||
SS = 4 # 子采样
|
||||
|
||||
|
||||
def rounded_inside(x, y, pad, rad):
|
||||
x0, y0 = float(pad), float(pad)
|
||||
x1, y1 = float(W - pad), float(W - pad)
|
||||
if x < x0 or x > x1 or y < y0 or y > y1:
|
||||
return False
|
||||
cx = min(max(x, x0 + rad), x1 - rad)
|
||||
cy = min(max(y, y0 + rad), y1 - rad)
|
||||
return (x - cx) ** 2 + (y - cy) ** 2 <= rad * rad
|
||||
|
||||
|
||||
def stroke_inside(x, y, pad, rad, th):
|
||||
if rounded_inside(x, y, pad, rad):
|
||||
if not rounded_inside(x, y, pad + th, max(0.0, rad - th)):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def seg_dist(px, py, ax, ay, bx, by):
|
||||
vx, vy = bx - ax, by - ay
|
||||
wx, wy = px - ax, py - ay
|
||||
c1 = vx * wx + vy * wy
|
||||
if c1 <= 0:
|
||||
return (wx * wx + wy * wy) ** 0.5
|
||||
c2 = vx * vx + vy * vy
|
||||
if c2 <= c1:
|
||||
return ((px - bx) ** 2 + (py - by) ** 2) ** 0.5
|
||||
t = c1 / c2
|
||||
dx, dy = px - (ax + t * vx), py - (ay + t * vy)
|
||||
return (dx * dx + dy * dy) ** 0.5
|
||||
|
||||
|
||||
CHECK_SEGS = (((8.0, 17.0), (14.0, 23.0)), ((14.0, 23.0), (25.0, 10.0)))
|
||||
CHECK_W = 3.2
|
||||
|
||||
|
||||
def render(checked):
|
||||
rows = []
|
||||
step = 1.0 / SS
|
||||
for py in range(W):
|
||||
row = bytearray()
|
||||
for px in range(W):
|
||||
r = g = b = a = 0
|
||||
for sy in range(SS):
|
||||
for sx in range(SS):
|
||||
x = px + (sx + 0.5) * step
|
||||
y = py + (sy + 0.5) * step
|
||||
col = None
|
||||
if checked:
|
||||
if rounded_inside(x, y, 2.0, 7.0):
|
||||
col = (42, 109, 168) # #2a6da8 蓝底
|
||||
for (p1, p2) in CHECK_SEGS:
|
||||
if seg_dist(x, y, p1[0], p1[1], p2[0], p2[1]) <= CHECK_W:
|
||||
col = (255, 255, 255) # 白对勾
|
||||
break
|
||||
else:
|
||||
if stroke_inside(x, y, 2.0, 7.0, 2.4):
|
||||
col = (122, 130, 140) # 灰框
|
||||
if col is not None:
|
||||
r += col[0]
|
||||
g += col[1]
|
||||
b += col[2]
|
||||
a += 255
|
||||
n = SS * SS
|
||||
row += bytes((r // n, g // n, b // n, a // n))
|
||||
rows.append(bytes(row))
|
||||
return rows
|
||||
|
||||
|
||||
def write_png(path, w, h, rgba_rows):
|
||||
def chunk(tag, data):
|
||||
out = struct.pack(">I", len(data)) + tag + data
|
||||
out += struct.pack(">I", zlib.crc32(tag + data) & 0xFFFFFFFF)
|
||||
return out
|
||||
|
||||
raw = b"".join(b"\x00" + r for r in rgba_rows)
|
||||
sig = b"\x89PNG\r\n\x1a\n"
|
||||
ihdr = struct.pack(">IIBBBBB", w, h, 8, 6, 0, 0, 0)
|
||||
idat = zlib.compress(raw, 9)
|
||||
with open(path, "wb") as f:
|
||||
f.write(sig + chunk(b"IHDR", ihdr) + chunk(b"IDAT", idat) + chunk(b"IEND", b""))
|
||||
|
||||
|
||||
def main():
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
write_png(os.path.join(here, "check_off.png"), W, W, render(False))
|
||||
write_png(os.path.join(here, "check_on.png"), W, W, render(True))
|
||||
print("icons written:", os.path.join(here, "check_off.png"), os.path.join(here, "check_on.png"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,857 @@
|
||||
# main.vp — ViperQtProject:VS Installer 风格安装向导演示
|
||||
#
|
||||
# 纯界面演示(无真实安装逻辑):
|
||||
# 步骤0 欢迎 → 步骤1 许可协议 → 步骤2 组件选择(树状图)
|
||||
# → 步骤3 安装选项 → 步骤4 安装进度 → 步骤5 完成
|
||||
#
|
||||
# 完善功能:
|
||||
# - 许可协议页:4 份协议逐份查看、逐份勾选同意(下一步才放下一份,不可跳过)
|
||||
# - 组件选择:QTreeWidget 树状图分组 + 勾选实时统计(数量 / 磁盘占用)
|
||||
# - 安装选项(独立页):发布渠道 QComboBox + 安装目录 + 快捷方式/PATH/桌面图标
|
||||
# - 安装进度:进度条 + 阶段文案 + 实时日志(QPlainTextEdit)
|
||||
# - 安装按钮点击后禁用,防重复
|
||||
#
|
||||
# 说明:
|
||||
# - 回调签名统一 def Cb(slot_id: int, data: t.CPtr)
|
||||
# - 跨模块 FFI 调用避免返回指针的函数(用 *_utf8 便捷版 + 整数返回函数)
|
||||
# - 模块级全局只存 t.CPtr 指针(不存对象)
|
||||
import t
|
||||
import stdlib
|
||||
import viperlib
|
||||
import vqt6
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 全局指针(回调里访问)
|
||||
# ============================================================
|
||||
app_ptr: t.CPtr = None
|
||||
stack_ptr: t.CPtr = None
|
||||
nav0_ptr: t.CPtr = None
|
||||
nav1_ptr: t.CPtr = None
|
||||
nav2_ptr: t.CPtr = None
|
||||
nav3_ptr: t.CPtr = None
|
||||
nav4_ptr: t.CPtr = None
|
||||
nav5_ptr: t.CPtr = None
|
||||
progress_ptr: t.CPtr = None
|
||||
progress_status_ptr: t.CPtr = None
|
||||
log_ptr: t.CPtr = None
|
||||
path_label_ptr: t.CPtr = None
|
||||
summary_ptr: t.CPtr = None
|
||||
disk_ptr: t.CPtr = None
|
||||
finish_summary_ptr: t.CPtr = None
|
||||
timer_ptr: t.CPtr = None
|
||||
tree_ptr: t.CPtr = None
|
||||
license_box_ptr: t.CPtr = None
|
||||
eula_name_ptr: t.CPtr = None
|
||||
agree_cb_ptr: t.CPtr = None
|
||||
license_next_ptr: t.CPtr = None
|
||||
channel_ptr: t.CPtr = None
|
||||
install_btn_ptr: t.CPtr = None
|
||||
opt0_ptr: t.CPtr = None
|
||||
opt1_ptr: t.CPtr = None
|
||||
opt2_ptr: t.CPtr = None
|
||||
theme_sel_ptr: t.CPtr = None
|
||||
central_ptr: t.CPtr = None
|
||||
big_ptr: t.CPtr = None
|
||||
sub_ptr: t.CPtr = None
|
||||
desc_ptr: t.CPtr = None
|
||||
lh_ptr: t.CPtr = None
|
||||
h2_ptr: t.CPtr = None
|
||||
h2b_ptr: t.CPtr = None
|
||||
h3_ptr: t.CPtr = None
|
||||
s3_ptr: t.CPtr = None
|
||||
h4_ptr: t.CPtr = None
|
||||
ok_ptr: t.CPtr = None
|
||||
fsum_ptr: t.CPtr = None
|
||||
oh_ptr: t.CPtr = None
|
||||
|
||||
# 全局状态
|
||||
cur_step: int = 0
|
||||
progress_val: int = 0
|
||||
comp_count: int = 0
|
||||
install_path: str = "C:/Program Files/Viper6"
|
||||
channel_name: str = "稳定版(Stable)"
|
||||
eula_idx: int = 0
|
||||
eula_agreed: int = 0
|
||||
cur_theme: int = 0
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 深色主题 QSS(VS Installer 风格,单行字面量)
|
||||
# ============================================================
|
||||
dark_qss: str = "QWidget { background-color: #1e1e1e; color: #d4d4d4; font-family: \"Segoe UI\", \"Microsoft YaHei UI\", \"Microsoft YaHei\", \"PingFang SC\", \"Noto Sans CJK SC\", sans-serif; font-size: 13px; } QPushButton { background-color: #333333; border: 1px solid #555555; border-radius: 4px; padding: 7px 16px; color: #ffffff; } QPushButton:hover { background-color: #454545; border-color: #6a6a6a; } QPushButton:pressed { background-color: #2a6da8; border-color: #4a86c8; } QPushButton:disabled { background-color: #2a2a2a; color: #666666; border-color: #3a3a3a; } QLineEdit, QTextEdit { background-color: #252526; border: 1px solid #555555; border-radius: 4px; padding: 5px 8px; color: #ffffff; } QPlainTextEdit { background-color: #1a1a1a; border: 1px solid #555555; border-radius: 4px; color: #cccccc; } QCheckBox { color: #d4d4d4; spacing: 10px; font-size: 14px; } QProgressBar { background-color: #252526; border: 1px solid #555555; border-radius: 4px; text-align: center; color: #ffffff; font-size: 13px; } QProgressBar::chunk { background-color: #4a86c8; border-radius: 3px; } QTreeWidget { background-color: #1a1a1a; border: 1px solid #555555; border-radius: 4px; color: #d4d4d4; } QTreeWidget::item { height: 26px; padding-left: 4px; } QTreeWidget::item:selected { background-color: #2a6da8; color: #ffffff; } QComboBox { background-color: #252526; border: 1px solid #555555; border-radius: 4px; padding: 5px 10px; color: #ffffff; } QComboBox:hover { border-color: #6a6a6a; } QComboBox::drop-down { border: none; width: 22px; } QComboBox QAbstractItemView { background-color: #252526; color: #ffffff; border: 1px solid #555555; selection-background-color: #2a6da8; } QScrollBar:vertical { background: #252526; width: 10px; margin: 0px; border: none; border-radius: 5px; } QScrollBar::handle:vertical { background: #4a4a4a; min-height: 24px; border-radius: 5px; } QScrollBar::handle:vertical:hover { background: #5a5a5a; } QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0px; } QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: transparent; } QScrollBar:horizontal { background: #252526; height: 10px; margin: 0px; border: none; border-radius: 5px; } QScrollBar::handle:horizontal { background: #4a4a4a; min-width: 24px; border-radius: 5px; } QScrollBar::handle:horizontal:hover { background: #5a5a5a; } QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { width: 0px; } QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { background: transparent; }"
|
||||
|
||||
light_qss: str = "QWidget { background-color: #f5f6f8; color: #1e1e1e; font-family: \"Segoe UI\", \"Microsoft YaHei UI\", \"Microsoft YaHei\", \"PingFang SC\", \"Noto Sans CJK SC\", sans-serif; font-size: 13px; } QPushButton { background-color: #ffffff; border: 1px solid #b9c0cb; border-radius: 4px; padding: 7px 16px; color: #1e1e1e; } QPushButton:hover { background-color: #eef1f6; border-color: #8a97a8; } QPushButton:pressed { background-color: #2a6da8; border-color: #2a6da8; color: #ffffff; } QPushButton:disabled { background-color: #eceef1; color: #9aa3b0; border-color: #d3d8e0; } QLineEdit, QTextEdit { background-color: #ffffff; border: 1px solid #b9c0cb; border-radius: 4px; padding: 5px 8px; color: #1e1e1e; } QPlainTextEdit { background-color: #ffffff; border: 1px solid #c6ccd6; border-radius: 4px; color: #1e1e1e; } QCheckBox { color: #1e1e1e; spacing: 10px; font-size: 14px; } QProgressBar { background-color: #e2e6ec; border: 1px solid #c6ccd6; border-radius: 4px; text-align: center; color: #1e1e1e; font-size: 13px; } QProgressBar::chunk { background-color: #2a6da8; border-radius: 3px; } QTreeWidget { background-color: #ffffff; border: 1px solid #c6ccd6; border-radius: 4px; color: #1e1e1e; } QTreeWidget::item { height: 26px; padding-left: 4px; } QTreeWidget::item:selected { background-color: #d6e4f5; color: #1e1e1e; } QComboBox { background-color: #ffffff; border: 1px solid #b9c0cb; border-radius: 4px; padding: 5px 10px; color: #1e1e1e; } QComboBox:hover { border-color: #8a97a8; } QComboBox::drop-down { border: none; width: 22px; } QComboBox QAbstractItemView { background-color: #ffffff; color: #1e1e1e; border: 1px solid #b9c0cb; selection-background-color: #d6e4f5; } QScrollBar:vertical { background: #e2e6ec; width: 10px; margin: 0px; border: none; border-radius: 5px; } QScrollBar::handle:vertical { background: #b0b9c6; min-height: 24px; border-radius: 5px; } QScrollBar::handle:vertical:hover { background: #9aa3b0; } QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0px; } QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: transparent; } QScrollBar:horizontal { background: #e2e6ec; height: 10px; margin: 0px; border: none; border-radius: 5px; } QScrollBar::handle:horizontal { background: #b0b9c6; min-width: 24px; border-radius: 5px; } QScrollBar::handle:horizontal:hover { background: #9aa3b0; } QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { width: 0px; } QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { background: transparent; }"
|
||||
|
||||
|
||||
# 导航项样式
|
||||
nav_active_qss: str = "font-size: 15px; color: #ffffff; font-weight: bold; background-color: #2a6da8; padding: 10px 14px; border-radius: 4px;"
|
||||
nav_idle_qss: str = "font-size: 15px; color: #8a8a8a; padding: 10px 14px;"
|
||||
nav_active_qss_light: str = "font-size: 15px; color: #ffffff; font-weight: bold; background-color: #2a6da8; padding: 10px 14px; border-radius: 4px;"
|
||||
nav_idle_qss_light: str = "font-size: 15px; color: #5a6470; padding: 10px 14px;"
|
||||
|
||||
# ============================================================
|
||||
# 文字样式角色(深/浅主题变体)
|
||||
# ============================================================
|
||||
t_d_huge: str = "font-size: 34px; color: #ffffff; font-weight: bold;"
|
||||
t_l_huge: str = "font-size: 34px; color: #1e1e1e; font-weight: bold;"
|
||||
t_d_title: str = "font-size: 22px; color: #ffffff; font-weight: bold;"
|
||||
t_l_title: str = "font-size: 22px; color: #1e1e1e; font-weight: bold;"
|
||||
t_d_sub: str = "font-size: 16px; color: #9cdcfe;"
|
||||
t_l_sub: str = "font-size: 16px; color: #2a6da8;"
|
||||
t_d_dim: str = "font-size: 13px; color: #9a9a9a;"
|
||||
t_l_dim: str = "font-size: 13px; color: #5a6470;"
|
||||
t_d_accent: str = "font-size: 13px; color: #9cdcfe;"
|
||||
t_l_accent: str = "font-size: 13px; color: #2a6da8;"
|
||||
t_d_ok: str = "font-size: 30px; color: #4ec9b0; font-weight: bold;"
|
||||
t_l_ok: str = "font-size: 30px; color: #178a63; font-weight: bold;"
|
||||
t_d_body: str = "font-size: 14px; color: #d4d4d4;"
|
||||
t_l_body: str = "font-size: 14px; color: #1e1e1e;"
|
||||
t_d_pathbox: str = "background-color: #252526; border: 1px solid #555555; border-radius: 4px; padding: 5px 8px; color: #ffffff;"
|
||||
t_l_pathbox: str = "background-color: #ffffff; border: 1px solid #b9c0cb; border-radius: 4px; padding: 5px 8px; color: #1e1e1e;"
|
||||
t_d_section: str = "font-size: 15px; color: #ffffff; font-weight: bold; padding-top: 6px;"
|
||||
t_l_section: str = "font-size: 15px; color: #1e1e1e; font-weight: bold; padding-top: 6px;"
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 主题辅助:返回当前主题下的样式字符串
|
||||
# ============================================================
|
||||
def NavActiveCss() -> str:
|
||||
if cur_theme == 1:
|
||||
return nav_active_qss_light
|
||||
return nav_active_qss
|
||||
|
||||
|
||||
def NavIdleCss() -> str:
|
||||
if cur_theme == 1:
|
||||
return nav_idle_qss_light
|
||||
return nav_idle_qss
|
||||
|
||||
|
||||
def CssHuge() -> str:
|
||||
if cur_theme == 1:
|
||||
return t_l_huge
|
||||
return t_d_huge
|
||||
|
||||
|
||||
def CssTitle() -> str:
|
||||
if cur_theme == 1:
|
||||
return t_l_title
|
||||
return t_d_title
|
||||
|
||||
|
||||
def CssSub() -> str:
|
||||
if cur_theme == 1:
|
||||
return t_l_sub
|
||||
return t_d_sub
|
||||
|
||||
|
||||
def CssDim() -> str:
|
||||
if cur_theme == 1:
|
||||
return t_l_dim
|
||||
return t_d_dim
|
||||
|
||||
|
||||
def CssAccent() -> str:
|
||||
if cur_theme == 1:
|
||||
return t_l_accent
|
||||
return t_d_accent
|
||||
|
||||
|
||||
def CssOk() -> str:
|
||||
if cur_theme == 1:
|
||||
return t_l_ok
|
||||
return t_d_ok
|
||||
|
||||
|
||||
def CssBody() -> str:
|
||||
if cur_theme == 1:
|
||||
return t_l_body
|
||||
return t_d_body
|
||||
|
||||
|
||||
def CssPathBox() -> str:
|
||||
if cur_theme == 1:
|
||||
return t_l_pathbox
|
||||
return t_d_pathbox
|
||||
|
||||
|
||||
def CssSection() -> str:
|
||||
if cur_theme == 1:
|
||||
return t_l_section
|
||||
return t_d_section
|
||||
|
||||
|
||||
# ============================================================
|
||||
# EULA 文档文本(多份协议文件)
|
||||
# ============================================================
|
||||
license_text: str = "VIPER 6.0 最终用户许可协议\n\n重要 - 请仔细阅读:本最终用户许可协议(以下简称\"协议\")是您与 Viper Software Foundation 之间就 Viper 6.0 软件达成的法律协议。\n\n1. 许可授予。在遵守本协议条款的前提下,您被授予一份非独占、不可转让的许可,允许在您的设备上安装和使用本软件用于开发与测试目的。\n\n2. 使用限制。您不得对本软件进行反向工程、反编译或反汇编(适用法律明确允许的除外);不得删除或修改软件中的版权声明。\n\n3. 开源组件。本软件包含根据 MIT、BSD 与 LGPL 许可发布的第三方开源组件,相关许可文本随附于软件文档中。\n\n4. 免责声明。本软件按\"现状\"提供,不附带任何明示或默示的担保,包括但不限于适销性、特定用途适用性及不侵权的担保。\n\n5. 责任限制。在任何情况下,Viper Software Foundation 均不对因使用或无法使用本软件而产生的任何间接、偶然、特殊或后果性损害承担责任。\n\n6. 协议终止。若您违反本协议的任何条款,本协议将自动终止,您应停止使用并删除本软件的全部副本。\n\n请阅读以上条款。要安装 Viper 6.0,必须接受这些条款并勾选下方的\"我接受许可协议中的条款\"。"
|
||||
|
||||
oss_text: str = "第三方开源组件许可声明\n\n本软件的部分功能基于以下第三方开源组件构建,相关许可全文可在各组件的发行包或代码仓库中查阅。\n\n1. Qt 6(LGPL v3 / GPL v3 / 商业许可)\nQt 图形界面框架及 Qt Widgets / Qt Network 模块,用于本向导的用户界面。本演示程序以 LGPL 条款使用 Qt 的共享库方式运行。\n\n2. LLVM / Clang 工具链(Apache License 2.0)\nViper 编译器后端与目标代码生成依赖 LLVM 基础设施。\n\n3. 开源许可证文本库(CC0)\n本向导使用的协议模板文本来源于公共领域资料,可自由复制与修改。\n\n4. 其它依赖\nmingw-w64 运行库(BSD/公共领域)与 zlib(zlib License)等。\n\n上述组件仅用于本软件的构建与运行,不构成对 Viper 语言本身的授权限制。"
|
||||
|
||||
privacy_text: str = "隐私政策\n\n本安装向导为本地演示程序,在安装过程中不会收集、上传或存储任何个人身份信息。\n\n1. 本地数据处理。您选择的组件、安装目录与安装选项仅保存在本机配置中,用于完成安装流程。\n\n2. 遥测与诊断。本软件默认不发送任何遥测数据。若您日后启用了诊断反馈功能,仅会上传安装日志中与错误定位相关的必要信息。\n\n3. 第三方链接。向导中出现的任何外部链接(如帮助文档)将在您主动点击时打开,相关站点不受本政策约束。\n\n4. 联系我们。如对本政策有任何疑问,可通过 Viper Software Foundation 官网的支持页面与我们联系。"
|
||||
|
||||
credits_text: str = "商标与版权声明\n\nViper、Viper 徽标与相关名称是 Viper Software Foundation 及其关联方的商标。\n\n本演示程序界面设计参考了行业通用安装向导的交互范式,仅用于功能与技术展示,不构成对任何第三方安装程序视觉风格的授权主张。\n\n本软件版权归 Viper Software Foundation 所有,保留一切权利。\n\n版本信息:Viper 6.0.1 · 2026"
|
||||
|
||||
# EULA 文档名(显示序号用)
|
||||
doc_name0: str = "Viper 6.0 最终用户许可协议"
|
||||
doc_name1: str = "第三方开源组件许可声明"
|
||||
doc_name2: str = "隐私政策"
|
||||
doc_name3: str = "商标与版权声明"
|
||||
|
||||
# 发布渠道文本
|
||||
channel_stable: str = "稳定版(Stable)"
|
||||
channel_preview: str = "预览版(Preview)"
|
||||
channel_dev: str = "开发版(Developer)"
|
||||
str_yes: str = "是"
|
||||
str_no: str = "否"
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 工具:切换步骤 + 导航高亮
|
||||
# ============================================================
|
||||
def SetNavStyle(ptr: t.CPtr, qss: str):
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(ptr, qss)
|
||||
|
||||
|
||||
def GotoStep(step: int):
|
||||
global cur_step
|
||||
cur_step = step
|
||||
vqt6.vqt6_qstackedwidget_set_current_index(stack_ptr, step)
|
||||
# 导航高亮(按当前主题)
|
||||
SetNavStyle(nav0_ptr, NavActiveCss() if step == 0 else NavIdleCss())
|
||||
SetNavStyle(nav1_ptr, NavActiveCss() if step == 1 else NavIdleCss())
|
||||
SetNavStyle(nav2_ptr, NavActiveCss() if step == 2 else NavIdleCss())
|
||||
SetNavStyle(nav3_ptr, NavActiveCss() if step == 3 else NavIdleCss())
|
||||
SetNavStyle(nav4_ptr, NavActiveCss() if step == 4 else NavIdleCss())
|
||||
SetNavStyle(nav5_ptr, NavActiveCss() if step == 5 else NavIdleCss())
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 步骤0:欢迎页
|
||||
# ============================================================
|
||||
def OnNext(slot_id: int, data: t.CPtr):
|
||||
EnterEulaPage()
|
||||
GotoStep(1)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 步骤1:许可协议(逐份查看 + 逐份同意)
|
||||
# ============================================================
|
||||
def ShowEulaDoc(idx: int):
|
||||
"""显示第 idx 份协议,并回填该协议的已同意状态"""
|
||||
txt: str = license_text
|
||||
name: str = doc_name0
|
||||
if idx == 1:
|
||||
txt = oss_text
|
||||
name = doc_name1
|
||||
elif idx == 2:
|
||||
txt = privacy_text
|
||||
name = doc_name2
|
||||
elif idx == 3:
|
||||
txt = credits_text
|
||||
name = doc_name3
|
||||
# 标题(x/4 · 协议名)
|
||||
buf: bytes = stdlib.malloc(128)
|
||||
viperlib.snprintf(buf, 128, "%d/4 · %s", idx + 1, name)
|
||||
vqt6.vqt6_qlabel_set_text_utf8(eula_name_ptr, buf)
|
||||
stdlib.free(buf)
|
||||
# 正文
|
||||
vqt6.vqt6_qplaintextedit_set_plain_text_utf8(license_box_ptr, txt)
|
||||
# 回填该协议已同意状态
|
||||
agreed: int = (eula_agreed >> idx) & 1
|
||||
vqt6.vqt6_qcheckbox_set_checked(agree_cb_ptr, agreed)
|
||||
vqt6.vqt6_qwidget_set_enabled(license_next_ptr, agreed)
|
||||
|
||||
|
||||
def EnterEulaPage():
|
||||
"""进入许可页:全部已同意则停在末份,否则续接当前进度"""
|
||||
global eula_idx
|
||||
if eula_agreed == 15:
|
||||
ShowEulaDoc(3)
|
||||
else:
|
||||
ShowEulaDoc(eula_idx)
|
||||
|
||||
|
||||
def OnLicenseToggled(slot_id: int, data: t.CPtr):
|
||||
"""勾选当前协议后才允许下一步"""
|
||||
checked: int = vqt6.vqt6_qcheckbox_is_checked(agree_cb_ptr)
|
||||
vqt6.vqt6_qwidget_set_enabled(license_next_ptr, checked)
|
||||
|
||||
|
||||
def OnLicenseNext(slot_id: int, data: t.CPtr):
|
||||
"""逐份同意:点下一步才放下一份;末份同意后进入组件选择"""
|
||||
global eula_idx, eula_agreed
|
||||
idx: int = eula_idx
|
||||
if idx < 0 or idx > 3:
|
||||
GotoStep(2)
|
||||
return
|
||||
if vqt6.vqt6_qcheckbox_is_checked(agree_cb_ptr) != 0:
|
||||
eula_agreed = eula_agreed | (1 << idx)
|
||||
if idx >= 3:
|
||||
GotoStep(2)
|
||||
else:
|
||||
eula_idx = idx + 1
|
||||
ShowEulaDoc(eula_idx)
|
||||
|
||||
|
||||
def OnLicenseBack(slot_id: int, data: t.CPtr):
|
||||
GotoStep(0)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 步骤2:组件选择(树状图)
|
||||
# ============================================================
|
||||
def OnBack(slot_id: int, data: t.CPtr):
|
||||
EnterEulaPage()
|
||||
GotoStep(1)
|
||||
|
||||
|
||||
def OnCompNext(slot_id: int, data: t.CPtr):
|
||||
GotoStep(3)
|
||||
|
||||
|
||||
def UpdateCompInfo():
|
||||
"""统计树勾选组件数与磁盘占用(跨模块用返回 int 的 vqt6 助手)"""
|
||||
global comp_count
|
||||
n: int = vqt6.qtree_checked_count(tree_ptr)
|
||||
mb: int = vqt6.qtree_checked_size(tree_ptr)
|
||||
comp_count = n
|
||||
buf1: bytes = stdlib.malloc(96)
|
||||
viperlib.snprintf(buf1, 96, "已选择 %d 个组件", n)
|
||||
vqt6.vqt6_qlabel_set_text_utf8(summary_ptr, buf1)
|
||||
stdlib.free(buf1)
|
||||
buf2: bytes = stdlib.malloc(96)
|
||||
viperlib.snprintf(buf2, 96, "所需磁盘空间: %d MB / 可用 51200 MB", mb)
|
||||
vqt6.vqt6_qlabel_set_text_utf8(disk_ptr, buf2)
|
||||
stdlib.free(buf2)
|
||||
|
||||
|
||||
def OnCompToggled(slot_id: int, data: t.CPtr):
|
||||
"""树节点勾选变化:重算组件数与磁盘占用"""
|
||||
UpdateCompInfo()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 步骤3:安装选项(独立页)
|
||||
# ============================================================
|
||||
def OnOptionsBack(slot_id: int, data: t.CPtr):
|
||||
GotoStep(2)
|
||||
|
||||
|
||||
def OnBrowse(slot_id: int, data: t.CPtr):
|
||||
global install_path
|
||||
p: str = vqt6.QFileDialog.getExistingDirectory(None, "选择安装目录", "C:/")
|
||||
if p is not None and p != "":
|
||||
install_path = p
|
||||
vqt6.vqt6_qlabel_set_text_utf8(path_label_ptr, p)
|
||||
|
||||
|
||||
def OnChannelChanged(slot_id: int, data: t.CPtr):
|
||||
"""发布渠道切换"""
|
||||
global channel_name
|
||||
idx: int = vqt6.vqt6_qcombobox_current_index(channel_ptr)
|
||||
if idx == 1: channel_name = channel_preview
|
||||
elif idx == 2: channel_name = channel_dev
|
||||
else: channel_name = channel_stable
|
||||
|
||||
|
||||
def OnInstall(slot_id: int, data: t.CPtr):
|
||||
"""进入安装页并启动模拟进度"""
|
||||
global progress_val
|
||||
vqt6.vqt6_qwidget_set_enabled(install_btn_ptr, 0)
|
||||
GotoStep(4)
|
||||
progress_val = 0
|
||||
vqt6.vqt6_qprogressbar_set_value(progress_ptr, 0)
|
||||
vqt6.vqt6_qlabel_set_text_utf8(progress_status_ptr, "正在准备安装环境...")
|
||||
vqt6.vqt6_qplaintextedit_append_utf8(log_ptr, "== 开始安装 Viper 6.0 ==\n")
|
||||
vqt6.vqt6_qtimer_start(timer_ptr, 80)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 步骤4:安装进度(QTimer 模拟)
|
||||
# ============================================================
|
||||
def OnProgressTick(slot_id: int, data: t.CPtr):
|
||||
global progress_val
|
||||
progress_val += 4
|
||||
vqt6.vqt6_qprogressbar_set_value(progress_ptr, progress_val)
|
||||
if progress_val == 4:
|
||||
vqt6.vqt6_qplaintextedit_append_utf8(log_ptr, "[1/4] 正在准备安装环境...\n")
|
||||
elif progress_val == 28:
|
||||
vqt6.vqt6_qlabel_set_text_utf8(progress_status_ptr, "正在复制核心文件...")
|
||||
vqt6.vqt6_qplaintextedit_append_utf8(log_ptr, "[2/4] 正在复制核心文件...\n")
|
||||
elif progress_val == 52:
|
||||
vqt6.vqt6_qlabel_set_text_utf8(progress_status_ptr, "正在配置 Qt6 组件...")
|
||||
vqt6.vqt6_qplaintextedit_append_utf8(log_ptr, "[3/4] 正在配置 Qt6 组件...\n")
|
||||
elif progress_val == 76:
|
||||
vqt6.vqt6_qlabel_set_text_utf8(progress_status_ptr, "正在注册系统组件...")
|
||||
vqt6.vqt6_qplaintextedit_append_utf8(log_ptr, "[4/4] 正在注册系统组件...\n")
|
||||
elif progress_val >= 100:
|
||||
vqt6.vqt6_qtimer_stop(timer_ptr)
|
||||
vqt6.vqt6_qplaintextedit_append_utf8(log_ptr, "安装完成!\n")
|
||||
# 完成页摘要(含安装选项 + 渠道)
|
||||
yn0: str = str_yes if vqt6.vqt6_qcheckbox_is_checked(opt0_ptr) != 0 else str_no
|
||||
yn1: str = str_yes if vqt6.vqt6_qcheckbox_is_checked(opt1_ptr) != 0 else str_no
|
||||
yn2: str = str_yes if vqt6.vqt6_qcheckbox_is_checked(opt2_ptr) != 0 else str_no
|
||||
buf: bytes = stdlib.malloc(512)
|
||||
viperlib.snprintf(buf, 512, "安装位置: %s\n发布渠道: %s\n已安装组件: %d 个\n快捷方式: %s / PATH: %s / 桌面图标: %s\n\nViper 6.0 已成功安装到您的系统。", install_path, channel_name, comp_count, yn0, yn1, yn2)
|
||||
vqt6.vqt6_qlabel_set_text_utf8(finish_summary_ptr, buf)
|
||||
stdlib.free(buf)
|
||||
GotoStep(5)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 步骤5:完成页
|
||||
# ============================================================
|
||||
def OnFinish(slot_id: int, data: t.CPtr):
|
||||
vqt6.vqt6_qapplication_quit(app_ptr)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 主题切换(右上角)
|
||||
# ============================================================
|
||||
def RestyleAll():
|
||||
"""按当前主题重刷带内联样式的关键控件"""
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(big_ptr, CssHuge())
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(sub_ptr, CssSub())
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(desc_ptr, CssDim())
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(lh_ptr, CssTitle())
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(h2_ptr, CssTitle())
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(h2b_ptr, CssDim())
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(h3_ptr, CssTitle())
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(s3_ptr, CssDim())
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(h4_ptr, CssTitle())
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(eula_name_ptr, CssAccent())
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(summary_ptr, CssAccent())
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(disk_ptr, CssAccent())
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(oh_ptr, CssSection())
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(ok_ptr, CssOk())
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(fsum_ptr, CssBody())
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(progress_status_ptr, CssDim())
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(path_label_ptr, CssPathBox())
|
||||
|
||||
|
||||
def ApplyTheme(theme: int):
|
||||
"""切换深/浅主题:先设应用调色板(原生控件配色),再换 QSS 并重刷内联样式"""
|
||||
global cur_theme
|
||||
cur_theme = theme
|
||||
vqt6.vqt6_qapplication_set_theme_palette(app_ptr, theme)
|
||||
if theme == 1:
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(central_ptr, light_qss)
|
||||
else:
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(central_ptr, dark_qss)
|
||||
RestyleAll()
|
||||
GotoStep(cur_step)
|
||||
|
||||
|
||||
def SetTextPtrs(big: t.CPtr, sub: t.CPtr, desc: t.CPtr, lh: t.CPtr, h2: t.CPtr, h2b: t.CPtr, h3: t.CPtr, s3: t.CPtr, h4: t.CPtr, ok: t.CPtr, fsum: t.CPtr, oh: t.CPtr):
|
||||
"""记录各页文本标签指针(供主题切换重刷内联样式)"""
|
||||
global big_ptr, sub_ptr, desc_ptr, lh_ptr
|
||||
global h2_ptr, h2b_ptr, h3_ptr, s3_ptr, h4_ptr, ok_ptr, fsum_ptr, oh_ptr
|
||||
big_ptr = big
|
||||
sub_ptr = sub
|
||||
desc_ptr = desc
|
||||
lh_ptr = lh
|
||||
h2_ptr = h2
|
||||
h2b_ptr = h2b
|
||||
h3_ptr = h3
|
||||
s3_ptr = s3
|
||||
h4_ptr = h4
|
||||
ok_ptr = ok
|
||||
fsum_ptr = fsum
|
||||
oh_ptr = oh
|
||||
|
||||
|
||||
def OnThemeChanged(slot_id: int, data: t.CPtr):
|
||||
idx: int = vqt6.vqt6_qcombobox_current_index(theme_sel_ptr)
|
||||
ApplyTheme(idx)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
global app_ptr, stack_ptr
|
||||
global nav0_ptr, nav1_ptr, nav2_ptr, nav3_ptr, nav4_ptr, nav5_ptr
|
||||
global progress_ptr, progress_status_ptr, log_ptr, path_label_ptr
|
||||
global summary_ptr, disk_ptr, finish_summary_ptr, timer_ptr
|
||||
global tree_ptr, license_box_ptr, eula_name_ptr
|
||||
global agree_cb_ptr, license_next_ptr, install_btn_ptr
|
||||
global channel_ptr, opt0_ptr, opt1_ptr, opt2_ptr
|
||||
global theme_sel_ptr, central_ptr
|
||||
|
||||
app = vqt6.QApplication(t.CPtr(0))
|
||||
app_ptr = t.CPtr(app._ptr)
|
||||
vqt6.vqt6_qapplication_set_theme_palette(app_ptr, 0)
|
||||
# 启用 item-view(组件树)勾选与复选框同一套自绘渲染
|
||||
vqt6.vqt6_enable_custom_item_checks(app_ptr)
|
||||
|
||||
# 主窗口(更宽更高)
|
||||
win = vqt6.QMainWindow()
|
||||
win.setWindowTitle("Viper 6.0 Installer")
|
||||
win.resize(1180, 780)
|
||||
vqt6.vqt6_qwidget_set_minimum_size(win._ptr, 1080, 700)
|
||||
|
||||
central = vqt6.QWidget()
|
||||
central.setStyleSheet(dark_qss)
|
||||
win.setCentralWidget(central._ptr)
|
||||
central_ptr = t.CPtr(central._ptr)
|
||||
|
||||
vroot = vqt6.QVBoxLayout(central)
|
||||
vroot.setContentsMargins(0, 0, 0, 0)
|
||||
vroot.setSpacing(0)
|
||||
|
||||
# ===== 右上角:主题切换 =====
|
||||
topbar = vqt6.QHBoxLayout()
|
||||
topbar.setContentsMargins(0, 6, 14, 2)
|
||||
topbar.setSpacing(8)
|
||||
vroot.addLayout(topbar._ptr)
|
||||
topbar.addStretch(1)
|
||||
theme_lb = vqt6.QLabel("主题:")
|
||||
topbar.addWidget(theme_lb._ptr)
|
||||
theme_sel = vqt6.QComboBox()
|
||||
theme_sel.addItem("深色")
|
||||
theme_sel.addItem("浅色")
|
||||
theme_sel.setCurrentIndex(0)
|
||||
topbar.addWidget(theme_sel._ptr)
|
||||
theme_sel_ptr = t.CPtr(theme_sel._ptr)
|
||||
theme_sel.currentIndexChanged(OnThemeChanged, 0)
|
||||
|
||||
root = vqt6.QHBoxLayout()
|
||||
root.setContentsMargins(0, 0, 0, 0)
|
||||
vroot.addLayout(root._ptr)
|
||||
|
||||
# ===== 左导航面板(宽度固定,高度随窗口拉伸) =====
|
||||
nav_panel = vqt6.QWidget()
|
||||
vqt6.vqt6_qwidget_set_minimum_size(nav_panel._ptr, 240, 0)
|
||||
vqt6.vqt6_qwidget_set_maximum_size(nav_panel._ptr, 240, 10000)
|
||||
nav_box = vqt6.QVBoxLayout(nav_panel)
|
||||
nav_box.setContentsMargins(10, 16, 10, 10)
|
||||
nav_box.setSpacing(6)
|
||||
|
||||
title_lb = vqt6.QLabel("Viper 6.0")
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(title_lb._ptr, "font-size: 20px; color: #4a86c8; font-weight: bold; padding: 6px 14px;")
|
||||
nav_box.addWidget(title_lb._ptr)
|
||||
|
||||
nav0 = vqt6.QLabel("欢迎")
|
||||
nav0.setStyleSheet(nav_active_qss)
|
||||
nav_box.addWidget(nav0._ptr)
|
||||
nav0_ptr = t.CPtr(nav0._ptr)
|
||||
|
||||
nav1 = vqt6.QLabel("许可协议")
|
||||
nav1.setStyleSheet(nav_idle_qss)
|
||||
nav_box.addWidget(nav1._ptr)
|
||||
nav1_ptr = t.CPtr(nav1._ptr)
|
||||
|
||||
nav2 = vqt6.QLabel("选择组件")
|
||||
nav2.setStyleSheet(nav_idle_qss)
|
||||
nav_box.addWidget(nav2._ptr)
|
||||
nav2_ptr = t.CPtr(nav2._ptr)
|
||||
|
||||
nav3 = vqt6.QLabel("安装选项")
|
||||
nav3.setStyleSheet(nav_idle_qss)
|
||||
nav_box.addWidget(nav3._ptr)
|
||||
nav3_ptr = t.CPtr(nav3._ptr)
|
||||
|
||||
nav4 = vqt6.QLabel("安装进度")
|
||||
nav4.setStyleSheet(nav_idle_qss)
|
||||
nav_box.addWidget(nav4._ptr)
|
||||
nav4_ptr = t.CPtr(nav4._ptr)
|
||||
|
||||
nav5 = vqt6.QLabel("完成")
|
||||
nav5.setStyleSheet(nav_idle_qss)
|
||||
nav_box.addWidget(nav5._ptr)
|
||||
nav5_ptr = t.CPtr(nav5._ptr)
|
||||
|
||||
nav_box.addStretch(1)
|
||||
ver_lb = vqt6.QLabel("v6.0.1 · 2026")
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(ver_lb._ptr, "color: #666666; padding: 6px 14px;")
|
||||
nav_box.addWidget(ver_lb._ptr)
|
||||
|
||||
root.addWidget(nav_panel._ptr)
|
||||
|
||||
# ===== 右侧步骤容器 =====
|
||||
stack = vqt6.QStackedWidget()
|
||||
root.addWidget(stack._ptr)
|
||||
stack_ptr = t.CPtr(stack._ptr)
|
||||
|
||||
# ---------- 页0:欢迎 ----------
|
||||
page0 = vqt6.QWidget()
|
||||
p0 = vqt6.QVBoxLayout(page0)
|
||||
p0.setContentsMargins(36, 40, 36, 24)
|
||||
p0.setSpacing(12)
|
||||
|
||||
big = vqt6.QLabel("Viper 6.0")
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(big._ptr, "font-size: 34px; color: #ffffff; font-weight: bold;")
|
||||
p0.addWidget(big._ptr)
|
||||
|
||||
sub = vqt6.QLabel("为 Viper 语言平台安装工具链、Qt6 运行时与开发组件")
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(sub._ptr, "font-size: 16px; color: #9cdcfe;")
|
||||
p0.addWidget(sub._ptr)
|
||||
|
||||
desc = vqt6.QLabel("本向导将引导您完成 Viper 6.0 的安装。您可以自定义要安装的组件、\n安装选项与发布渠道,安装过程由模拟进度与日志展示(演示界面,无真实安装行为)。")
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(desc._ptr, "font-size: 13px; color: #9a9a9a;")
|
||||
p0.addWidget(desc._ptr)
|
||||
|
||||
p0.addStretch(1)
|
||||
next_btn = vqt6.QPushButton("下一步 >")
|
||||
p0.addWidget(next_btn._ptr)
|
||||
next_btn.clicked(OnNext, 0)
|
||||
|
||||
stack.addWidget(page0._ptr)
|
||||
|
||||
# ---------- 页1:许可协议(逐份同意) ----------
|
||||
page1 = vqt6.QWidget()
|
||||
p1 = vqt6.QVBoxLayout(page1)
|
||||
p1.setContentsMargins(36, 32, 36, 24)
|
||||
p1.setSpacing(10)
|
||||
|
||||
lh = vqt6.QLabel("许可协议")
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(lh._ptr, "font-size: 22px; color: #ffffff; font-weight: bold;")
|
||||
p1.addWidget(lh._ptr)
|
||||
|
||||
eula_name = vqt6.QLabel("1/4 · Viper 6.0 最终用户许可协议")
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(eula_name._ptr, "font-size: 13px; color: #9cdcfe;")
|
||||
p1.addWidget(eula_name._ptr)
|
||||
eula_name_ptr = t.CPtr(eula_name._ptr)
|
||||
|
||||
license_box = vqt6.QPlainTextEdit()
|
||||
license_box.setPlainText(license_text)
|
||||
license_box.setReadOnly(1)
|
||||
p1.addWidget(license_box._ptr)
|
||||
license_box_ptr = t.CPtr(license_box._ptr)
|
||||
|
||||
agree_cb = vqt6.QCheckBox("我接受上述协议条款")
|
||||
p1.addWidget(agree_cb._ptr)
|
||||
agree_cb_ptr = t.CPtr(agree_cb._ptr)
|
||||
agree_cb.toggled(OnLicenseToggled, 0)
|
||||
|
||||
lbrow = vqt6.QHBoxLayout()
|
||||
p1.addLayout(lbrow._ptr)
|
||||
lbrow.addStretch(1)
|
||||
lic_back = vqt6.QPushButton("< 上一步")
|
||||
lbrow.addWidget(lic_back._ptr)
|
||||
lic_back.clicked(OnLicenseBack, 0)
|
||||
lic_next = vqt6.QPushButton("下一步 >")
|
||||
lic_next.setEnabled(0)
|
||||
lbrow.addWidget(lic_next._ptr)
|
||||
license_next_ptr = t.CPtr(lic_next._ptr)
|
||||
lic_next.clicked(OnLicenseNext, 0)
|
||||
|
||||
stack.addWidget(page1._ptr)
|
||||
|
||||
# ---------- 页2:组件选择(树状图) ----------
|
||||
page2 = vqt6.QWidget()
|
||||
p2 = vqt6.QVBoxLayout(page2)
|
||||
p2.setContentsMargins(36, 28, 36, 20)
|
||||
p2.setSpacing(8)
|
||||
|
||||
h2 = vqt6.QLabel("选择要安装的组件")
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(h2._ptr, "font-size: 22px; color: #ffffff; font-weight: bold;")
|
||||
p2.addWidget(h2._ptr)
|
||||
|
||||
h2b = vqt6.QLabel("展开分类并勾选需要的组件,实时统计安装体积与磁盘占用。")
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(h2b._ptr, "font-size: 13px; color: #9a9a9a;")
|
||||
p2.addWidget(h2b._ptr)
|
||||
|
||||
tree = vqt6.QTreeWidget()
|
||||
tree.setHeaderHidden(1)
|
||||
p2.addWidget(tree._ptr)
|
||||
tree_ptr = t.CPtr(tree._ptr)
|
||||
|
||||
# 叶子磁盘占用经 setSize 存入节点 UserRole,统计时自动读取
|
||||
g0 = vqt6.QTreeWidgetItem("开发工具链")
|
||||
leaf = vqt6.QTreeWidgetItem("Viper 核心编译器与运行时(编译器 + 标准库运行时)[850 MB]")
|
||||
leaf.setCheckable(1)
|
||||
leaf.setChecked(1)
|
||||
leaf.setSize(850)
|
||||
g0.addChild(leaf)
|
||||
leaf = vqt6.QTreeWidgetItem("Viper 标准库与 includes 包 [350 MB]")
|
||||
leaf.setCheckable(1)
|
||||
leaf.setChecked(1)
|
||||
leaf.setSize(350)
|
||||
g0.addChild(leaf)
|
||||
tree.addTopLevelItem(g0)
|
||||
|
||||
g1 = vqt6.QTreeWidgetItem("GUI 与网络开发")
|
||||
leaf = vqt6.QTreeWidgetItem("Qt6 桌面开发框架(GUI + vqt6 桥接库)[1200 MB]")
|
||||
leaf.setCheckable(1)
|
||||
leaf.setChecked(1)
|
||||
leaf.setSize(1200)
|
||||
g1.addChild(leaf)
|
||||
leaf = vqt6.QTreeWidgetItem("网络与调试工具(QtNetwork + TPV)[220 MB]")
|
||||
leaf.setCheckable(1)
|
||||
leaf.setChecked(0)
|
||||
leaf.setSize(220)
|
||||
g1.addChild(leaf)
|
||||
tree.addTopLevelItem(g1)
|
||||
|
||||
g2 = vqt6.QTreeWidgetItem("附加内容")
|
||||
leaf = vqt6.QTreeWidgetItem("示例项目与开发文档 [90 MB]")
|
||||
leaf.setCheckable(1)
|
||||
leaf.setChecked(0)
|
||||
leaf.setSize(90)
|
||||
g2.addChild(leaf)
|
||||
tree.addTopLevelItem(g2)
|
||||
|
||||
tree.expandAll()
|
||||
tree.itemChanged(OnCompToggled, 0)
|
||||
|
||||
summary = vqt6.QLabel("已选择 3 个组件")
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(summary._ptr, "font-size: 13px; color: #9cdcfe;")
|
||||
p2.addWidget(summary._ptr)
|
||||
summary_ptr = t.CPtr(summary._ptr)
|
||||
|
||||
disk = vqt6.QLabel("所需磁盘空间: 2400 MB / 可用 51200 MB")
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(disk._ptr, "font-size: 13px; color: #9cdcfe;")
|
||||
p2.addWidget(disk._ptr)
|
||||
disk_ptr = t.CPtr(disk._ptr)
|
||||
|
||||
btn_row2 = vqt6.QHBoxLayout()
|
||||
p2.addLayout(btn_row2._ptr)
|
||||
btn_row2.addStretch(1)
|
||||
back_btn = vqt6.QPushButton("< 上一步")
|
||||
btn_row2.addWidget(back_btn._ptr)
|
||||
back_btn.clicked(OnBack, 0)
|
||||
comp_next_btn = vqt6.QPushButton("下一步 >")
|
||||
btn_row2.addWidget(comp_next_btn._ptr)
|
||||
comp_next_btn.clicked(OnCompNext, 0)
|
||||
|
||||
stack.addWidget(page2._ptr)
|
||||
|
||||
# ---------- 页3:安装选项(独立页) ----------
|
||||
page3 = vqt6.QWidget()
|
||||
p3 = vqt6.QVBoxLayout(page3)
|
||||
p3.setContentsMargins(36, 28, 36, 20)
|
||||
p3.setSpacing(12)
|
||||
|
||||
h3 = vqt6.QLabel("安装选项")
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(h3._ptr, "font-size: 22px; color: #ffffff; font-weight: bold;")
|
||||
p3.addWidget(h3._ptr)
|
||||
|
||||
s3 = vqt6.QLabel("选择发布渠道、界面集成方式与安装目录。")
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(s3._ptr, "font-size: 13px; color: #9a9a9a;")
|
||||
p3.addWidget(s3._ptr)
|
||||
|
||||
ch_row = vqt6.QHBoxLayout()
|
||||
p3.addLayout(ch_row._ptr)
|
||||
ch_lb = vqt6.QLabel("发布渠道:")
|
||||
ch_row.addWidget(ch_lb._ptr)
|
||||
channel_cb = vqt6.QComboBox()
|
||||
channel_cb.addItem(channel_stable)
|
||||
channel_cb.addItem(channel_preview)
|
||||
channel_cb.addItem(channel_dev)
|
||||
channel_cb.setCurrentIndex(0)
|
||||
ch_row.addWidget(channel_cb._ptr)
|
||||
ch_row.addStretch(1)
|
||||
channel_ptr = t.CPtr(channel_cb._ptr)
|
||||
channel_cb.currentIndexChanged(OnChannelChanged, 0)
|
||||
|
||||
oh = vqt6.QLabel("界面集成方式")
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(oh._ptr, "font-size: 15px; color: #ffffff; font-weight: bold; padding-top: 6px;")
|
||||
p3.addWidget(oh._ptr)
|
||||
|
||||
opt0 = vqt6.QCheckBox("创建开始菜单快捷方式")
|
||||
opt0.setChecked(1)
|
||||
p3.addWidget(opt0._ptr)
|
||||
opt0_ptr = t.CPtr(opt0._ptr)
|
||||
|
||||
opt1 = vqt6.QCheckBox("将 viper 命令添加到系统 PATH")
|
||||
opt1.setChecked(0)
|
||||
p3.addWidget(opt1._ptr)
|
||||
opt1_ptr = t.CPtr(opt1._ptr)
|
||||
|
||||
opt2 = vqt6.QCheckBox("创建桌面图标")
|
||||
opt2.setChecked(0)
|
||||
p3.addWidget(opt2._ptr)
|
||||
opt2_ptr = t.CPtr(opt2._ptr)
|
||||
|
||||
# 安装位置行
|
||||
path_row = vqt6.QHBoxLayout()
|
||||
p3.addLayout(path_row._ptr)
|
||||
path_lb = vqt6.QLabel("安装位置:")
|
||||
path_row.addWidget(path_lb._ptr)
|
||||
path_lb2 = vqt6.QLabel(install_path)
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(path_lb2._ptr, "background-color: #252526; border: 1px solid #555555; border-radius: 4px; padding: 5px 8px; color: #ffffff;")
|
||||
path_row.addWidget(path_lb2._ptr)
|
||||
path_label_ptr = t.CPtr(path_lb2._ptr)
|
||||
browse_btn = vqt6.QPushButton("浏览...")
|
||||
path_row.addWidget(browse_btn._ptr)
|
||||
browse_btn.clicked(OnBrowse, 0)
|
||||
|
||||
p3.addStretch(1)
|
||||
|
||||
btn_row3 = vqt6.QHBoxLayout()
|
||||
p3.addLayout(btn_row3._ptr)
|
||||
btn_row3.addStretch(1)
|
||||
back3_btn = vqt6.QPushButton("< 上一步")
|
||||
btn_row3.addWidget(back3_btn._ptr)
|
||||
back3_btn.clicked(OnOptionsBack, 0)
|
||||
install_btn = vqt6.QPushButton("安装")
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(install_btn._ptr, "background-color: #2a6da8; border: 1px solid #2a6da8; border-radius: 4px; padding: 8px 22px; color: #ffffff; font-size: 14px; font-weight: bold;")
|
||||
btn_row3.addWidget(install_btn._ptr)
|
||||
install_btn_ptr = t.CPtr(install_btn._ptr)
|
||||
install_btn.clicked(OnInstall, 0)
|
||||
|
||||
stack.addWidget(page3._ptr)
|
||||
|
||||
# ---------- 页4:安装进度 ----------
|
||||
page4 = vqt6.QWidget()
|
||||
p4 = vqt6.QVBoxLayout(page4)
|
||||
p4.setContentsMargins(36, 36, 36, 24)
|
||||
p4.setSpacing(12)
|
||||
|
||||
h4 = vqt6.QLabel("正在安装 Viper 6.0...")
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(h4._ptr, "font-size: 22px; color: #ffffff; font-weight: bold;")
|
||||
p4.addWidget(h4._ptr)
|
||||
|
||||
prog = vqt6.QProgressBar()
|
||||
prog.setRange(0, 100)
|
||||
prog.setValue(0)
|
||||
p4.addWidget(prog._ptr)
|
||||
progress_ptr = t.CPtr(prog._ptr)
|
||||
|
||||
pstat = vqt6.QLabel("正在准备安装环境...")
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(pstat._ptr, "font-size: 13px; color: #9a9a9a;")
|
||||
p4.addWidget(pstat._ptr)
|
||||
progress_status_ptr = t.CPtr(pstat._ptr)
|
||||
|
||||
log = vqt6.QPlainTextEdit()
|
||||
log.setReadOnly(1)
|
||||
p4.addWidget(log._ptr)
|
||||
log_ptr = t.CPtr(log._ptr)
|
||||
|
||||
stack.addWidget(page4._ptr)
|
||||
|
||||
# ---------- 页5:完成 ----------
|
||||
page5 = vqt6.QWidget()
|
||||
p5 = vqt6.QVBoxLayout(page5)
|
||||
p5.setContentsMargins(36, 40, 36, 24)
|
||||
p5.setSpacing(14)
|
||||
|
||||
ok = vqt6.QLabel("安装完成")
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(ok._ptr, "font-size: 30px; color: #4ec9b0; font-weight: bold;")
|
||||
p5.addWidget(ok._ptr)
|
||||
|
||||
fsum = vqt6.QLabel("")
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(fsum._ptr, "font-size: 14px; color: #d4d4d4;")
|
||||
p5.addWidget(fsum._ptr)
|
||||
finish_summary_ptr = t.CPtr(fsum._ptr)
|
||||
|
||||
p5.addStretch(1)
|
||||
finish_btn = vqt6.QPushButton("完成")
|
||||
vqt6.vqt6_qwidget_set_stylesheet_utf8(finish_btn._ptr, "background-color: #2a6da8; border-radius: 4px; padding: 8px 22px; color: #ffffff; font-size: 14px; font-weight: bold;")
|
||||
p5.addWidget(finish_btn._ptr)
|
||||
finish_btn.clicked(OnFinish, 0)
|
||||
|
||||
stack.addWidget(page5._ptr)
|
||||
|
||||
# ===== 定时器(模拟安装进度) =====
|
||||
timer = vqt6.QTimer()
|
||||
timer.timeout(OnProgressTick, 0)
|
||||
timer_ptr = t.CPtr(timer._ptr)
|
||||
|
||||
# 初始化组件统计
|
||||
SetTextPtrs(big._ptr, sub._ptr, desc._ptr, lh._ptr, h2._ptr, h2b._ptr, h3._ptr, s3._ptr, h4._ptr, ok._ptr, fsum._ptr, oh._ptr)
|
||||
UpdateCompInfo()
|
||||
|
||||
GotoStep(0)
|
||||
win.show()
|
||||
return app.exec()
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "ViperQtProject - Qt6 GUI Test",
|
||||
"version": "1.0.0",
|
||||
"source_dir": "./App",
|
||||
"build_dir": "./.tpv_build",
|
||||
"compiler": {
|
||||
"cmd": "llc",
|
||||
"flags": [
|
||||
"-filetype=obj",
|
||||
"-relocation-model=pic"
|
||||
]
|
||||
},
|
||||
"linker": {
|
||||
"cmd": "D:/msys64/mingw64/bin/clang++.exe",
|
||||
"flags": [
|
||||
"-fuse-ld=lld",
|
||||
"-lmsvcrt",
|
||||
"-lucrt",
|
||||
"-lpthread",
|
||||
"-lmingwex",
|
||||
"-lkernel32",
|
||||
"-lcomdlg32",
|
||||
"-lcomctl32",
|
||||
"-lole32",
|
||||
"-luuid",
|
||||
"-lwinmm",
|
||||
"-lgdi32",
|
||||
"-lgdiplus",
|
||||
"-ladvapi32",
|
||||
"-lshell32",
|
||||
"-lws2_32",
|
||||
"-luser32",
|
||||
"-L D:/Qt/6.11.2/mingw_64/lib",
|
||||
"-lQt6Core",
|
||||
"-lQt6Gui",
|
||||
"-lQt6Widgets",
|
||||
"-lQt6Network",
|
||||
"-lQt6Charts"
|
||||
],
|
||||
"static_libs": [
|
||||
"../../includes/vqt6/build/lib/libvqt6_bridge.a"
|
||||
],
|
||||
"output": "app.exe"
|
||||
},
|
||||
"includes": [
|
||||
"../../includes"
|
||||
],
|
||||
"target": {
|
||||
"triple": "x86_64-pc-windows-gnu",
|
||||
"datalayout": "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
|
||||
},
|
||||
"options": {
|
||||
"slice_level": 3,
|
||||
"target": "llvm",
|
||||
"strict_mode": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
# main.vp — ViperRequestsTest: HTTP + HTTPS client test
|
||||
#
|
||||
# Coverage:
|
||||
# 1) requests.get - plain-socket HTTP GET
|
||||
# 2) rhttps.get verify=True - HTTPS with chain verification (without a CA it is expected to fail, with reason)
|
||||
# 3) rhttps.get verify=False - HTTPS channel demo (prints TLS version / cipher / peer subject)
|
||||
# 4) rhttps.post - HTTPS POST
|
||||
#
|
||||
# Usage: requests._mbuddy = my_mbuddy (https reuses the same memory pool)
|
||||
import t, c
|
||||
from stdint import *
|
||||
import stdio
|
||||
import stdlib
|
||||
import string
|
||||
import socket
|
||||
import memhub
|
||||
import requests
|
||||
import requests.https as rhttps
|
||||
import requests.download as rd
|
||||
import zlib.pyzlib as pyzlib
|
||||
import w32.win32file as wf
|
||||
import w32.win32base as wb
|
||||
|
||||
|
||||
# Global MBuddy pointer
|
||||
_mbuddy: memhub.MemBuddy*
|
||||
|
||||
|
||||
def _PrintBody(head: str, resp: requests.Response*):
|
||||
"""Print the response status and a body snippet."""
|
||||
if resp is None:
|
||||
return
|
||||
code: INT = resp.status_code
|
||||
length: INT = resp.body_len
|
||||
stdio.printf("%s -> status=%d, body_len=%d\n", head, code, length)
|
||||
if resp.text is not None and length > 0:
|
||||
show: INT = 160
|
||||
if length < show:
|
||||
show = length
|
||||
txt: str = resp.text
|
||||
i: INT = 0
|
||||
while i < show and txt[i] != 0:
|
||||
stdio.printf("%c", txt[i])
|
||||
i += 1
|
||||
stdio.printf("\n")
|
||||
|
||||
|
||||
def _PrintHeader(resp: requests.Response*, name: str):
|
||||
"""Demonstrate a case-insensitive response header lookup."""
|
||||
hbuf: str = _mbuddy.alloc(128)
|
||||
if hbuf is None:
|
||||
return
|
||||
if requests.header_value(resp, name, hbuf, 128) == 0:
|
||||
stdio.printf(" [header] %s: %s\n", name, hbuf)
|
||||
else:
|
||||
stdio.printf(" [header] %s: (missing)\n", name)
|
||||
_mbuddy.free(hbuf)
|
||||
|
||||
|
||||
@t.CExport
|
||||
def main() -> int:
|
||||
global _mbuddy
|
||||
stdio.printf("ViperRequestsTest: HTTP + HTTPS client test\n")
|
||||
|
||||
# ---- Init the memory pool (16MB arena) and hand it to the requests package ----
|
||||
arena: bytes = stdlib.malloc(16 * 1024 * 1024)
|
||||
if arena is None:
|
||||
stdio.printf("FATAL: malloc arena failed\n")
|
||||
return 1
|
||||
|
||||
_mbuddy = memhub.MemBuddy(arena, 16 * 1024 * 1024)
|
||||
requests._mbuddy = _mbuddy
|
||||
|
||||
# ---- 1) HTTP GET ----
|
||||
stdio.printf("\n--- 1) HTTP GET (http://example.com/) ---\n")
|
||||
resp = requests.get("http://example.com/")
|
||||
if resp is not None:
|
||||
_PrintBody("requests.get", resp)
|
||||
_PrintHeader(resp, "Content-Type") # header lookup (case-insensitive)
|
||||
_PrintHeader(resp, "X-Not-Exist") # absent -> (missing)
|
||||
requests.release(resp)
|
||||
else:
|
||||
stdio.printf("requests.get: FAIL (network unavailable)\n")
|
||||
|
||||
# ---- 2) HTTPS GET, verification ON (explicit CA bundle, chain checks out) ----
|
||||
stdio.printf("\n--- 2) HTTPS GET verify=True (Mozilla CA bundle) ---\n")
|
||||
resp2 = rhttps.get("https://example.com/", True, "D:/msys64/mingw64/etc/ssl/certs/ca-bundle.crt")
|
||||
if resp2 is not None:
|
||||
_PrintBody("https.get(verify=True)", resp2)
|
||||
stdio.printf(" verify result = %d (0=chain ok), enabled = %d\n",
|
||||
rhttps.last_verify_result(), rhttps.last_verify_enabled())
|
||||
requests.release(resp2)
|
||||
else:
|
||||
err: str = rhttps.last_error()
|
||||
stdio.printf("https.get(verify=True): REJECTED (cert verify failed)\n")
|
||||
stdio.printf(" reason: %s\n", err if err is not None else "?")
|
||||
|
||||
# ---- 3) HTTPS GET, verification OFF (TLS channel + metadata demo) ----
|
||||
stdio.printf("\n--- 3) HTTPS GET verify=False (TLS info) ---\n")
|
||||
resp3 = rhttps.get("https://example.com/", False)
|
||||
if resp3 is not None:
|
||||
_PrintBody("https.get(verify=False)", resp3)
|
||||
_PrintHeader(resp3, "content-type") # https responses also expose headers (lowercase demo)
|
||||
tls_info: str = rhttps.last_tls_info()
|
||||
stdio.printf(" tls: %s\n", tls_info if tls_info is not None else "?")
|
||||
requests.release(resp3)
|
||||
else:
|
||||
err3: str = rhttps.last_error()
|
||||
stdio.printf("https.get(verify=False): FAIL\n")
|
||||
stdio.printf(" reason: %s\n", err3 if err3 is not None else "?")
|
||||
|
||||
# ---- 4) HTTPS POST (a 405 from the peer is fine; verifies send + response parsing) ----
|
||||
stdio.printf("\n--- 4) HTTPS POST (https://example.com/) ---\n")
|
||||
resp4 = rhttps.post("https://example.com/", "hello=viper", "application/x-www-form-urlencoded", False)
|
||||
if resp4 is not None:
|
||||
_PrintBody("https.post(verify=False)", resp4)
|
||||
requests.release(resp4)
|
||||
else:
|
||||
err4: str = rhttps.last_error()
|
||||
stdio.printf("https.post: FAIL\n")
|
||||
stdio.printf(" reason: %s\n", err4 if err4 is not None else "?")
|
||||
|
||||
# ---- 5) chunked decode (RFC7230 official sample, offline) ----
|
||||
stdio.printf("\n--- 5) chunked decode (RFC7230 sample) ---\n")
|
||||
chunked_src: str = "4\r\nWiki\r\n5\r\npedia\r\nE\r\n in\r\n\r\nchunks.\r\n0\r\n\r\n"
|
||||
clen: INT = INT(string.strlen(chunked_src))
|
||||
out: str = _mbuddy.alloc(128)
|
||||
if out is not None:
|
||||
dlen: INT = requests.decode_chunked(chunked_src, clen, out, 128)
|
||||
stdio.printf("decode_chunked(%d bytes) -> %d bytes: ", clen, dlen)
|
||||
if dlen > 0:
|
||||
di: INT = 0
|
||||
while di < dlen and out[di] != 0:
|
||||
stdio.printf("%c", out[di])
|
||||
di += 1
|
||||
stdio.printf("\n")
|
||||
_mbuddy.free(out)
|
||||
else:
|
||||
stdio.printf("decode_chunked: alloc failed\n")
|
||||
|
||||
# ---- 6) redirect follow (httpbin, 3 hops) ----
|
||||
stdio.printf("\n--- 6) redirect follow (https://httpbin.org/redirect/3) ---\n")
|
||||
resp6 = rhttps.get("https://httpbin.org/redirect/3", False)
|
||||
if resp6 is not None:
|
||||
_PrintBody("https.get(redirect/3)", resp6)
|
||||
requests.release(resp6)
|
||||
else:
|
||||
err6: str = rhttps.last_error()
|
||||
stdio.printf("redirect: FAIL\n reason: %s\n", err6 if err6 is not None else "?")
|
||||
|
||||
# ---- 7) gzip decode (Content-Encoding: gzip handled automatically) ----
|
||||
stdio.printf("\n--- 7) gzip decode (https://httpbin.org/gzip) ---\n")
|
||||
resp7 = rhttps.get("https://httpbin.org/gzip", False)
|
||||
if resp7 is not None:
|
||||
_PrintBody("https.get(gzip)", resp7)
|
||||
gerr: str = requests.last_error()
|
||||
if gerr is not None and gerr[0] != 0:
|
||||
stdio.printf(" [gzip] %s\n", gerr)
|
||||
tebuf: str = _mbuddy.alloc(64)
|
||||
if tebuf is not None:
|
||||
if requests.header_value(resp7, "Transfer-Encoding", tebuf, 64) == 0:
|
||||
stdio.printf(" [te] %s\n", tebuf)
|
||||
_mbuddy.free(tebuf)
|
||||
requests.release(resp7)
|
||||
else:
|
||||
err7: str = rhttps.last_error()
|
||||
stdio.printf("gzip: FAIL\n reason: %s\n", err7 if err7 is not None else "?")
|
||||
|
||||
# ---- 8) cert verification block (badssl self-signed not in CA bundle -> verify=True rejects) ----
|
||||
stdio.printf("\n--- 8) cert verify block (self-signed.badssl.com) ---\n")
|
||||
resp8 = rhttps.get("https://self-signed.badssl.com/", True, "D:/msys64/mingw64/etc/ssl/certs/ca-bundle.crt")
|
||||
if resp8 is not None:
|
||||
stdio.printf("verify=True: PASS (verify result = %d, unexpected for self-signed)\n", rhttps.last_verify_result())
|
||||
_PrintBody(" body", resp8)
|
||||
requests.release(resp8)
|
||||
else:
|
||||
err8: str = rhttps.last_error()
|
||||
stdio.printf("verify=True: REJECTED as expected (verify result = %d)\n reason: %s\n",
|
||||
rhttps.last_verify_result(), err8 if err8 is not None else "?")
|
||||
resp8b = rhttps.get("https://self-signed.badssl.com/", False)
|
||||
if resp8b is not None:
|
||||
_PrintBody("verify=False (bypass)", resp8b)
|
||||
requests.release(resp8b)
|
||||
else:
|
||||
err8b: str = rhttps.last_error()
|
||||
stdio.printf("verify=False: FAIL\n reason: %s\n", err8b if err8b is not None else "?")
|
||||
|
||||
# ---- 5b) pyzlib gzip round-trip (pool=None / stdlib path) ----
|
||||
stdio.printf("\n--- 5b) pyzlib gzip round-trip (pool=None) ---\n")
|
||||
gsrc: str = "The quick brown fox jumps over the lazy dog while the morning sun rises slowly above the quiet hills of the eastern valley. A gentle breeze carries the fresh scent of pine trees across the winding path. Birds gather on the wooden fence and sing their cheerful songs. Farmers prepare their fields for another day of honest work. Children laugh and play near the old stone bridge. Every small village tells its own story through the eyes of its people, their traditions, and the memories shared around warm evening fires under a sky full of stars that have watched over this land for centuries. The river continues its endless journey toward the distant sea, carrying with it the hopes and dreams of all who live along its banks."
|
||||
glen: sizet = sizet(string.strlen(gsrc))
|
||||
clen2: sizet = 0
|
||||
gz1: BYTEPTR = pyzlib.compress(None, BYTEPTR(gsrc), glen, 6, 31, &clen2)
|
||||
if gz1 is not None:
|
||||
stdio.printf("compress ok -> %zu bytes gzip\n", clen2)
|
||||
olen2: sizet = 0
|
||||
gz2: BYTEPTR = pyzlib.decompress(None, gz1, clen2, 15, 4096, &olen2)
|
||||
if gz2 is not None:
|
||||
g2s: str = char(uint64(gz2), t.CPtr)
|
||||
g2s[olen2] = 0
|
||||
stdio.printf("decompress ok -> %zu bytes: %s\n", olen2, g2s)
|
||||
stdlib.free(gz1)
|
||||
stdlib.free(gz2)
|
||||
else:
|
||||
stdio.printf("decompress FAIL: %s\n", pyzlib.get_error())
|
||||
else:
|
||||
stdio.printf("compress FAIL: %s\n", pyzlib.get_error())
|
||||
|
||||
# ---- 6) Large-file chunked download (httpbin 1MB: serial + parallel, byte-by-byte compare) ----
|
||||
stdio.printf("\n--- 6) Large-file chunked download (https://httpbin.org/bytes/1048576) ---\n")
|
||||
DL_URL: str = "https://httpbin.org/bytes/1048576"
|
||||
CA_BUNDLE: str = "D:/msys64/mingw64/etc/ssl/certs/ca-bundle.crt"
|
||||
|
||||
rc6a: INT = rd.download(DL_URL, "dl_serial.bin", 1, 262144, True, CA_BUNDLE)
|
||||
stdio.printf("6a serial download rc=%d last_error=%s\n", rc6a, rd.last_error())
|
||||
rc6b: INT = rd.download(DL_URL, "dl_parallel.bin", 4, 262144, True, CA_BUNDLE)
|
||||
stdio.printf("6b parallel(4) download rc=%d last_error=%s\n", rc6b, rd.last_error())
|
||||
|
||||
# Both files must have the same size and equal 1048576
|
||||
exp_size: LONGLONG = 1048576
|
||||
ok6: INT = 1
|
||||
i6: INT = 0
|
||||
paths6: t.CArray[str, None] = ["dl_serial.bin", "dl_parallel.bin"]
|
||||
sizes6: t.CArray[LONGLONG, None] = [0, 0]
|
||||
for i6 in range(2):
|
||||
h6: wb.HANDLE = wf.CreateFileA(
|
||||
paths6[i6], wf.GENERIC_READ, wf.FILE_SHARE_READ, None,
|
||||
wf.OPEN_EXISTING, wf.FILE_ATTRIBUTE_NORMAL, None)
|
||||
if h6 == wb.INVALID_HANDLE_VALUE:
|
||||
stdio.printf("6 check: open %s failed\n", paths6[i6])
|
||||
ok6 = 0
|
||||
continue
|
||||
li6: wb.LARGE_INTEGER = wb.LARGE_INTEGER()
|
||||
if wf.GetFileSizeEx(h6, &li6) == 0:
|
||||
stdio.printf("6 check: GetFileSizeEx %s failed\n", paths6[i6])
|
||||
ok6 = 0
|
||||
else:
|
||||
sizes6[i6] = li6.QuadPart
|
||||
stdio.printf("6 check: %s size=%lld\n", paths6[i6], li6.QuadPart)
|
||||
wb.CloseHandle(h6)
|
||||
if ok6 != 0 and sizes6[0] == exp_size and sizes6[1] == exp_size:
|
||||
# Byte-by-byte content comparison
|
||||
same6: INT = 1
|
||||
a6: str = stdlib.malloc(65536)
|
||||
b6: str = stdlib.malloc(65536)
|
||||
if a6 is None or b6 is None:
|
||||
stdio.printf("6 compare: out of memory\n")
|
||||
else:
|
||||
ha6: wb.HANDLE = wf.CreateFileA(
|
||||
paths6[0], wf.GENERIC_READ, wf.FILE_SHARE_READ, None,
|
||||
wf.OPEN_EXISTING, wf.FILE_ATTRIBUTE_NORMAL, None)
|
||||
hb6: wb.HANDLE = wf.CreateFileA(
|
||||
paths6[1], wf.GENERIC_READ, wf.FILE_SHARE_READ, None,
|
||||
wf.OPEN_EXISTING, wf.FILE_ATTRIBUTE_NORMAL, None)
|
||||
if ha6 == wb.INVALID_HANDLE_VALUE or hb6 == wb.INVALID_HANDLE_VALUE:
|
||||
stdio.printf("6 compare: open failed\n")
|
||||
else:
|
||||
off6: LONGLONG = 0
|
||||
while off6 < exp_size:
|
||||
ra: ULONG = 0
|
||||
rb: ULONG = 0
|
||||
chunk6: ULONG = 65536
|
||||
rem6: LONGLONG = exp_size - off6
|
||||
if rem6 < LONGLONG(65536):
|
||||
chunk6 = ULONG(rem6)
|
||||
wf.ReadFile(ha6, a6, chunk6, &ra, None)
|
||||
wf.ReadFile(hb6, b6, chunk6, &rb, None)
|
||||
if ra != chunk6 or rb != chunk6:
|
||||
stdio.printf("6 compare: short read at %lld\n", off6)
|
||||
same6 = 0
|
||||
break
|
||||
for k6 in range(chunk6):
|
||||
if a6[k6] != b6[k6]:
|
||||
stdio.printf("6 compare: MISMATCH at %lld\n", off6 + LONGLONG(k6))
|
||||
same6 = 0
|
||||
break
|
||||
if same6 == 0:
|
||||
break
|
||||
off6 += LONGLONG(chunk6)
|
||||
stdio.printf("6 compare: serial == parallel(4) %s\n",
|
||||
"YES" if same6 != 0 else "NO")
|
||||
if ha6 != wb.INVALID_HANDLE_VALUE:
|
||||
wb.CloseHandle(ha6)
|
||||
if hb6 != wb.INVALID_HANDLE_VALUE:
|
||||
wb.CloseHandle(hb6)
|
||||
if a6 is not None:
|
||||
stdlib.free(a6)
|
||||
if b6 is not None:
|
||||
stdlib.free(b6)
|
||||
else:
|
||||
stdio.printf("6 compare: size mismatch or download failed (rc6a=%d rc6b=%d)\n", rc6a, rc6b)
|
||||
|
||||
# Remove the downloaded files
|
||||
wf.DeleteFileA("dl_serial.bin")
|
||||
wf.DeleteFileA("dl_parallel.bin")
|
||||
|
||||
# ---- Cleanup ----
|
||||
socket.SocketCleanup()
|
||||
stdio.printf("\nViperRequestsTest: DONE\n")
|
||||
return 0
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "ViperRequestsTest - HTTP(S) Client Test",
|
||||
"version": "1.0.0",
|
||||
"source_dir": "./App",
|
||||
"build_dir": "./.tpv_build",
|
||||
"compiler": {
|
||||
"cmd": "llc",
|
||||
"flags": [
|
||||
"-filetype=obj",
|
||||
"-relocation-model=pic"
|
||||
]
|
||||
},
|
||||
"linker": {
|
||||
"cmd": "D:/msys64/mingw64/bin/clang++.exe",
|
||||
"flags": [
|
||||
"-fuse-ld=lld",
|
||||
"-lws2_32",
|
||||
"-lcrypt32",
|
||||
"-ladvapi32",
|
||||
"-lmsvcrt",
|
||||
"-lmingwex",
|
||||
"-lgdi32",
|
||||
"-lpthread",
|
||||
"-lkernel32"
|
||||
],
|
||||
"static_libs": [
|
||||
"../../dlls/lib/libssl.a",
|
||||
"../../dlls/lib/libcrypto.a"
|
||||
],
|
||||
"output": "app.exe"
|
||||
},
|
||||
"includes": [
|
||||
"../../includes"
|
||||
],
|
||||
"target": {
|
||||
"triple": "x86_64-pc-windows-gnu",
|
||||
"datalayout": "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
|
||||
},
|
||||
"options": {
|
||||
"slice_level": 3,
|
||||
"target": "llvm",
|
||||
"strict_mode": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,387 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ============================================================
|
||||
# ViperSpeedBenchmark - Viper(vipersimd) 进程内多次 run 基准实现
|
||||
#
|
||||
# 内核(与 bench_c.c / bench_cpp.cpp / bench_py.py 一致):
|
||||
# out[i] = a[i] * b[i] + c[i] i = 0 .. LEN-1, LEN = 16384 (double)
|
||||
#
|
||||
# 每次计时 run = ITERS = 65536 趟内核, 单趟 2 次浮点运算/元素,
|
||||
# 总 flops = LEN*ITERS*2 ≈ 2.147e9(与 C/C++/Python 完全同规模)。
|
||||
#
|
||||
# SIMD 实现: vipersimd.simd_fma_array —— 单趟融合乘加批量内核
|
||||
# (×8 双缓冲 AVX2 ymm0-15, 每大迭代 16 路 load 在飞后 8 条
|
||||
# vfmadd213pd 一次完成 out=a*b+c, 加数 c 直接用内存操作数)。
|
||||
# vfmadd213pd 语义: ymmD = ymmS1*ymmD + mem(单次舍入, 真硬件 FMA)。
|
||||
# 因此不再有 mul→tmp→add 两遍写法的中间数组与 1.5 倍内存流量;
|
||||
# 由于是单次舍入, Viper 的逐元素结果(及 CHECK)与 C/C++/Python 的
|
||||
# "先乘后加两次舍入"可能差 1ulp(各实现内自洽, README §1 已注明)。
|
||||
#
|
||||
# 命令行: app.exe [runs] [len] [iters] [mode]
|
||||
# runs : 默认 1; 0 = 仅热身(不输出 RESULT)。由 run_bench.py 传 10。
|
||||
# len : 向量长度(4 的倍数, 默认 LEN)
|
||||
# iters: 每 run 内核趟数(默认 ITERS)
|
||||
# mode : 内核编号(见下方 KERNEL_* 常量), 默认 0 = FMA SIMD
|
||||
# 偶数 = SIMD(vipersimd.<op>_array) / 奇数 = 同操作的标量(原生)对照
|
||||
# 「SIMD vs Native」用同一个 exe、只切换 mode, 除向量化外无其它差异。
|
||||
# 命令行解析为自包含实现(GetCommandLineA 手工分词), 不依赖 sys/memhub。
|
||||
#
|
||||
# 流程(与其它实现一致):
|
||||
# * 入口 OpenThread(GetCurrentThreadId()) 取真句柄, SetThreadAffinityMask 绑到
|
||||
# 首个可用逻辑核并提线程优先级, 打印 AFFINITY <core>;
|
||||
# * 进程开头打印一次 CFG <len> <iters>;
|
||||
# * 分配 a/b/c/out 并用固定种子 LCG 填充 a/b/c(不计时);
|
||||
# * 2 轮满负荷热身(每轮 = ITERS 趟内核 + 升序读回 out 求校验和);
|
||||
# * 连续计时 runs 次, 每次 RESULT <wall_us> <cpu_us> <flops>;
|
||||
# * 全部结束后 CHECK <sum>(最后一次 run 的 out 升序累加)。
|
||||
# 双计时只包住 ITERS 趟内核本身:
|
||||
# wall_us = QPC 墙钟微秒(含被抢占 / 被降频的时间, 对调度敏感);
|
||||
# cpu_us = QueryThreadCycleTime 本线程消耗的 CPU 周期数换算成微秒
|
||||
# (只计真正执行过的周期, 对抢占 / 降频不敏感);
|
||||
# TSC 频率在入口用 QPC 标定(三次 30ms 忙等取最大), 并打印 TSC <hz>。
|
||||
# 不用 GetThreadTimes: 其记账粒度跟随调度时钟节拍(实测硬地板 15.625ms),
|
||||
# 几十毫秒的 run 误差 ±40%, 且 Win10 2004+ 无法用 timeBeginPeriod 改善。
|
||||
# ============================================================
|
||||
from stdint import *
|
||||
import vipersimd
|
||||
import vipermath
|
||||
import stdlib
|
||||
import stdio
|
||||
import t
|
||||
|
||||
# 内核编号(命令行第 4 个参数): 只决定"跑哪份内核", 不影响计时/统计逻辑。
|
||||
# 成对编号 = 同一操作的 SIMD 与标量(原生)对照, 供 SIMD 加速比测量:
|
||||
# 0 / 1 = FMA out = a*b + c —— 与 C/C++/Python 对齐的主内核
|
||||
# 2 / 3 = ADD out = a + b
|
||||
# 4 / 5 = MUL out = a * b
|
||||
# 6 / 7 = SQRT out = sqrt(a)
|
||||
# 偶数 = SIMD(vipersimd.<op>_array), 奇数 = 标量(vipersimd.scalar_<op>_array)。
|
||||
# 标量版用的是对应标量指令(vaddsd/vmulsd/vsqrtsd/vfmadd213sd), 与 SIMD 版
|
||||
# 数学等价、逐位同结果, 因此两边的 CHECK 可直接对照。
|
||||
define KERNEL_FMA_SIMD = 0
|
||||
define KERNEL_FMA_SCALAR = 1
|
||||
define KERNEL_ADD_SIMD = 2
|
||||
define KERNEL_ADD_SCALAR = 3
|
||||
define KERNEL_MUL_SIMD = 4
|
||||
define KERNEL_MUL_SCALAR = 5
|
||||
define KERNEL_SQRT_SIMD = 6
|
||||
define KERNEL_SQRT_SCALAR = 7
|
||||
define KERNEL_MAX = 7 # 合法 mode 上界
|
||||
|
||||
# 向量长度(4 的倍数, 满足 vipersimd 批量内核按 4 个 double 分批)
|
||||
# 负载约束: LEN × ITERS × 2 恒为 ≈2.147e9 flops/run(与 C/C++/Python 可比),
|
||||
# 只调 LEN 改"工作集": LEN=16384 -> 四份共 512 KiB, 稳居本机每核 1 MiB 私有 L2。
|
||||
define LEN = 16384
|
||||
define ITERS = 65536 # 每 run 内核趟数
|
||||
define RUNS_MAX = 200 # runs 上限
|
||||
|
||||
# 伪随机 LCG 参数(minstd / Park-Miller), 固定种子 -> 可复现
|
||||
define LCG_A = 48271.0
|
||||
define LCG_M = 2147483647.0
|
||||
define LCG_SEED = 12345.0
|
||||
|
||||
# Win32 常量
|
||||
define THREAD_ALL_ACCESS = 0x1FFFFF # OpenThread 权限(足够绑亲和 + 查周期)
|
||||
define THREAD_PRIORITY_HIGHEST = 2 # SetThreadPriority 档位
|
||||
|
||||
|
||||
# extern: Win32 高精度计时 (kernel32, 由 project.vpj 的 -lkernel32 链接)
|
||||
def QueryPerformanceCounter(lpPerformanceCount: t.CPtr) -> int | t.CExtern: pass
|
||||
def QueryPerformanceFrequency(lpFrequency: t.CPtr) -> int | t.CExtern: pass
|
||||
# extern: 原始命令行(整行, 含程序路径), 用于自解析第 1 个参数
|
||||
def GetCommandLineA() -> CHARPTR | t.CExtern: pass
|
||||
# extern: 线程绑定 / 线程 CPU 周期(kernel32, 由 project.vpj 的 -lkernel32 链接)
|
||||
# 关键: 不要用 GetCurrentThread() —— 它返回伪句柄 (HANDLE)-2, 是个"负指针",
|
||||
# 在 Viper 的 t.CPtr 语义下未必能原样传给 C(实测 QueryThreadCycleTime 只会
|
||||
# 写回一次调用开销 ~0.23us 的量级)。改用 GetCurrentThreadId() + OpenThread()
|
||||
# 拿一个真正的、纯数值的句柄, 全程用 t.CSizeT 表达, 彻底绕开指针/NULL 语义。
|
||||
def GetCurrentThreadId() -> sizet | t.CExtern: pass
|
||||
def OpenThread(dwDesiredAccess: sizet, bInheritHandle: int, dwThreadId: sizet) -> sizet | t.CExtern: pass
|
||||
def SetThreadAffinityMask(hThread: sizet, dwThreadAffinityMask: sizet) -> sizet | t.CExtern: pass
|
||||
def SetThreadPriority(hThread: sizet, nPriority: int) -> int | t.CExtern: pass
|
||||
def QueryThreadCycleTime(hThread: sizet, CycleTime: t.CPtr) -> int | t.CExtern: pass
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# 自包含命令行解析: 取第 idx 个整数参数(idx 从 0 起, 0 号是 runs)。
|
||||
# 分词规则: 先跳过程序路径(容忍引号), 之后每个空白分隔的 token 记作一个参数;
|
||||
# token 里的十进制数字即该参数值, 缺失/非数字则返回 def_val。
|
||||
# 命令行: app.exe [runs] [len] [iters]
|
||||
# ------------------------------------------------------------
|
||||
def cmdline_int(idx: int, def_val: int64) -> int64:
|
||||
cmd: CHARPTR = GetCommandLineA()
|
||||
if cmd is None:
|
||||
return def_val
|
||||
i: sizet = 0
|
||||
# 跳过前导空白
|
||||
while cmd[i] == 32 or cmd[i] == 9:
|
||||
i += 1
|
||||
# 跳过第 0 个 token(程序路径; 可能带引号)
|
||||
if cmd[i] == 34:
|
||||
i += 1
|
||||
while cmd[i] != 0 and cmd[i] != 34:
|
||||
i += 1
|
||||
if cmd[i] == 34:
|
||||
i += 1
|
||||
else:
|
||||
while cmd[i] != 0 and cmd[i] != 32 and cmd[i] != 9:
|
||||
i += 1
|
||||
# 逐个 token 往后找第 idx 个参数
|
||||
for cur in range(idx + 1):
|
||||
while cmd[i] == 32 or cmd[i] == 9:
|
||||
i += 1
|
||||
if cmd[i] == 0:
|
||||
return def_val
|
||||
# 读十进制整数(可空, 用 seen 区分 0 与"没有数字")
|
||||
val: int64 = 0
|
||||
seen: int = 0
|
||||
while cmd[i] >= 48 and cmd[i] <= 57:
|
||||
val = val * 10 + int64(cmd[i] - 48)
|
||||
i += 1
|
||||
seen = 1
|
||||
if cur == idx:
|
||||
if seen == 1:
|
||||
return val
|
||||
return def_val
|
||||
# 跳过本 token 剩余字符(保证每轮至少前进一个字符, 不会死循环)
|
||||
while cmd[i] != 0 and cmd[i] != 32 and cmd[i] != 9:
|
||||
i += 1
|
||||
return def_val
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# 填充 a/b/c: 逐元素依次消耗同一个 LCG 流(a[i], b[i], c[i])
|
||||
# 迭代顺序与 C/C++/Python 完全一致, 保证输入逐位相同。
|
||||
# ------------------------------------------------------------
|
||||
def fill_abc(a: t.CPtr, b: t.CPtr, c: t.CPtr, n: sizet):
|
||||
da: double* = a
|
||||
db: double* = b
|
||||
dc: double* = c
|
||||
state: double = double(LCG_SEED)
|
||||
m: double = double(LCG_M)
|
||||
mul: double = double(LCG_A)
|
||||
for i in range(n):
|
||||
state = vipermath.fmod(state * mul, m)
|
||||
da[i] = state / m
|
||||
state = vipermath.fmod(state * mul, m)
|
||||
db[i] = state / m
|
||||
state = vipermath.fmod(state * mul, m)
|
||||
dc[i] = state / m
|
||||
|
||||
|
||||
# 校验和: 升序逐元素累加 out(与其它语言顺序一致; 读回保证写不被优化)
|
||||
def checksum_sum(out: t.CPtr, n: sizet) -> double:
|
||||
dout: double* = out
|
||||
s: double = double(0.0)
|
||||
for i in range(n):
|
||||
s += dout[i]
|
||||
return s
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# 连续跑 iters 趟内核(热身与计时都走这里, 保证两边完全同一条路径)。
|
||||
# mode 选内核(见顶部 KERNEL_*): 偶数 = SIMD, 奇数 = 同操作的标量(原生)对照。
|
||||
# 分派用等值判断链而非函数指针: 每趟多一次分支, 相对"一趟内核"(微秒级)可忽略;
|
||||
# 且所有 mode 共用同一个调用点, 不会因代码布局差异污染 SIMD/原生对照。
|
||||
# mode 已在 main 入口校验为 0..KERNEL_MAX, 所以最后一个 else 必然落在
|
||||
# KERNEL_SQRT_SCALAR 上(与 else-if 链等价, 不是"兜底")。
|
||||
# ------------------------------------------------------------
|
||||
def run_iters(a: t.CPtr, b: t.CPtr, cbuf: t.CPtr, out: t.CPtr,
|
||||
n: sizet, iters: sizet, mode: int):
|
||||
for k in range(iters):
|
||||
if mode == int(KERNEL_FMA_SIMD):
|
||||
vipersimd.simd_fma_array(a, b, cbuf, out, n)
|
||||
elif mode == int(KERNEL_FMA_SCALAR):
|
||||
vipersimd.scalar_fma_array(a, b, cbuf, out, n)
|
||||
elif mode == int(KERNEL_ADD_SIMD):
|
||||
vipersimd.simd_add_array(a, b, out, n)
|
||||
elif mode == int(KERNEL_ADD_SCALAR):
|
||||
vipersimd.scalar_add_array(a, b, out, n)
|
||||
elif mode == int(KERNEL_MUL_SIMD):
|
||||
vipersimd.simd_mul_array(a, b, out, n)
|
||||
elif mode == int(KERNEL_MUL_SCALAR):
|
||||
vipersimd.scalar_mul_array(a, b, out, n)
|
||||
elif mode == int(KERNEL_SQRT_SIMD):
|
||||
vipersimd.simd_sqrt_array(a, out, n)
|
||||
else:
|
||||
vipersimd.scalar_sqrt_array(a, out, n)
|
||||
|
||||
|
||||
# 读取 8 字节有符号 64 位整数(有符号 64 位)
|
||||
# 既用于 QPC 写入的 LARGE_INTEGER.QuadPart, 也用于 QueryThreadCycleTime 写回的 ULONG64。
|
||||
# 计时换算走纯 i64 整数路径: us = (dt * 1000000) / freq, 避免浮点表达式的低级错误
|
||||
def qpc_ticks(p: t.CPtr) -> int64:
|
||||
v: INT64PTR = p
|
||||
return v[0]
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# 打开当前线程的"真句柄"(失败返回 0)。不要用 GetCurrentThread(): 它返回
|
||||
# 伪句柄 (HANDLE)-2(负指针), 在 Viper 的 t.CPtr 语义下传递不可靠。
|
||||
# 进程退出时系统回收, 这里不显式 CloseHandle。
|
||||
# ------------------------------------------------------------
|
||||
def open_own_thread() -> sizet:
|
||||
return OpenThread(sizet(THREAD_ALL_ACCESS), int(0), GetCurrentThreadId())
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# 线程绑定: 绑到"第一个可用逻辑核", 返回 CPU 序号; 失败返回 -1。
|
||||
# 用 mask = 1, 2, 4, 8 ... 逐个试: 第一个被系统接受的位就是进程亲和掩码里
|
||||
# 编号最小的核 —— 四个实现会绑到同一个核, 横向对比才有意义。
|
||||
# SetThreadAffinityMask 成功时返回"此前的掩码"(可能非 1), 失败返回 0。
|
||||
# ------------------------------------------------------------
|
||||
def bind_first_available_core(h: sizet) -> int:
|
||||
mask: sizet = sizet(1)
|
||||
for i in range(64):
|
||||
if SetThreadAffinityMask(h, mask) != sizet(0):
|
||||
return i
|
||||
mask = mask * sizet(2)
|
||||
return -1
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# 本线程累计消耗的 CPU 周期数(QueryThreadCycleTime 写回 cyc_buf, 8 字节)。
|
||||
# 现代 x86 的 TSC 是 invariant 的, 与核心实际频率无关, 所以"周期数"就是
|
||||
# "本线程真正在 CPU 上跑过的时间", 对抢占与降频都不敏感。
|
||||
# ------------------------------------------------------------
|
||||
def thread_cycles(h: sizet, cyc_buf: t.CPtr) -> int64:
|
||||
QueryThreadCycleTime(h, cyc_buf)
|
||||
return qpc_ticks(cyc_buf)
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# 用 QPC 标定 TSC 频率(周期/秒): 忙等 30ms 数周期, 重复 3 次取最大。
|
||||
# 为什么取最大: 标定窗口内本线程一旦被抢占, 消耗周期就少于墙钟流逝时间,
|
||||
# 算出 hz 偏小 -> 后续 cpu_us 系统性偏大(表现为"cpu_us 偶尔比 wall_us 还大"
|
||||
# 这种看着像 bug 的伪影)。取三次最大值 = 挑最没被打断的那一次。
|
||||
# 必须在绑核之后调用, 否则跨核的 TSC 可能不同步。返回 0 = 标定失败。
|
||||
# q0_buf/q1_buf 用于括住忙等窗口, cyc_buf 存周期数(都由调用方提供, 免重复分配)。
|
||||
# ------------------------------------------------------------
|
||||
def calibrate_tsc_hz(h: sizet, freq_i: int64, q0_buf: t.CPtr, q1_buf: t.CPtr, cyc_buf: t.CPtr) -> double:
|
||||
best: double = double(0.0)
|
||||
span_i: int64 = (freq_i * int64(30)) / int64(1000) # 30ms 的 QPC 计次
|
||||
for trial in range(3):
|
||||
c0: int64 = thread_cycles(h, cyc_buf)
|
||||
QueryPerformanceCounter(q0_buf)
|
||||
dt: int64 = int64(0)
|
||||
while dt < span_i:
|
||||
QueryPerformanceCounter(q1_buf)
|
||||
dt = qpc_ticks(q1_buf) - qpc_ticks(q0_buf)
|
||||
c1: int64 = thread_cycles(h, cyc_buf)
|
||||
if dt > int64(0) and c1 > c0:
|
||||
hz: double = double(c1 - c0) * double(freq_i) / double(dt)
|
||||
if hz > best:
|
||||
best = hz
|
||||
return best
|
||||
|
||||
|
||||
def main() -> int:
|
||||
# 命令行: app.exe [runs] [len] [iters] [mode]; 省略则用顶部常量的默认值
|
||||
runs_req: int64 = cmdline_int(0, int64(1))
|
||||
n_req: int64 = cmdline_int(1, int64(LEN))
|
||||
iters_req: int64 = cmdline_int(2, int64(ITERS))
|
||||
mode_req: int64 = cmdline_int(3, int64(KERNEL_FMA_SIMD))
|
||||
# 内核按 4-lane ymm 分批, LEN 必须是 4 的倍数; 参数非法直接报错, 不静默修正
|
||||
if n_req < int64(4) or n_req % int64(4) != int64(0) or iters_req < int64(1):
|
||||
stdio.printf("bad args: len=%d(需 >=4 且为 4 的倍数) iters=%d\n",
|
||||
int(n_req), int(iters_req))
|
||||
return 2
|
||||
# mode 越界直接报错退出, 避免"悄悄跑成别的内核"污染 SIMD/原生对照
|
||||
if mode_req < int64(0) or mode_req > int64(KERNEL_MAX):
|
||||
stdio.printf("bad args: mode=%d(只接受 0..7: FMA/ADD/MUL/SQRT 各一对 SIMD/原生)\n",
|
||||
int(mode_req))
|
||||
return 2
|
||||
if runs_req < int64(0):
|
||||
runs_req = int64(0)
|
||||
if runs_req > int64(RUNS_MAX):
|
||||
runs_req = int64(RUNS_MAX)
|
||||
|
||||
n: sizet = sizet(n_req)
|
||||
iters: sizet = sizet(iters_req)
|
||||
mode: int = int(mode_req) # 0=SIMD / 1=Scalar(已校验)
|
||||
nb: sizet = n * sizet(8)
|
||||
nruns: sizet = sizet(runs_req)
|
||||
|
||||
# 先拿当前线程的真句柄(失败就没法绑核/取周期, 直接报错退出, 不静默降级)
|
||||
h_thread: sizet = open_own_thread()
|
||||
if h_thread == sizet(0):
|
||||
stdio.printf("OpenThread(GetCurrentThreadId()) failed\n")
|
||||
return 1
|
||||
|
||||
# 线程绑定(必做) + 提优先级: 与 C/C++/Python 同口径, 并压低背景进程抢占的尖峰
|
||||
core: int = bind_first_available_core(h_thread)
|
||||
SetThreadPriority(h_thread, int(THREAD_PRIORITY_HIGHEST))
|
||||
|
||||
# 四块缓冲区 malloc(对齐由 stdlib 保证 16B 以上); 单趟内核不再需要 tmp
|
||||
a: t.CPtr = stdlib.malloc(nb)
|
||||
b: t.CPtr = stdlib.malloc(nb)
|
||||
c: t.CPtr = stdlib.malloc(nb)
|
||||
out: t.CPtr = stdlib.malloc(nb)
|
||||
if not a or not b or not c or not out:
|
||||
stdio.printf("malloc failed\n")
|
||||
return 1
|
||||
|
||||
# 进程开头打印一次配置(其它实现同一协议: CFG <len> <iters> + AFFINITY <core>)
|
||||
stdio.printf("CFG %d %d\n", int(n), int(iters))
|
||||
stdio.printf("MODE %d\n", mode)
|
||||
stdio.printf("AFFINITY %d\n", core)
|
||||
|
||||
# 输入生成一次(不计时)
|
||||
fill_abc(a, b, c, n)
|
||||
|
||||
# QPC 频率/计次缓冲 + CPU 周期缓冲(均 == 8 字节)
|
||||
freq_buf: t.CPtr = stdlib.malloc(sizet(8))
|
||||
t0_buf: t.CPtr = stdlib.malloc(sizet(8))
|
||||
t1_buf: t.CPtr = stdlib.malloc(sizet(8))
|
||||
cyc_buf: t.CPtr = stdlib.malloc(sizet(8))
|
||||
if not freq_buf or not t0_buf or not t1_buf or not cyc_buf:
|
||||
stdio.printf("malloc failed\n")
|
||||
return 1
|
||||
QueryPerformanceFrequency(freq_buf)
|
||||
freq_i: int64 = qpc_ticks(freq_buf)
|
||||
if freq_i <= int64(0):
|
||||
freq_i = int64(10000000) # 兜底: 假设 10MHz(与实测一致)
|
||||
|
||||
# TSC 频率标定(必须在绑核之后): 把 QueryThreadCycleTime 的周期数换算成微秒
|
||||
tsc_hz: double = calibrate_tsc_hz(h_thread, freq_i, t0_buf, t1_buf, cyc_buf)
|
||||
stdio.printf("TSC %.4e\n", tsc_hz)
|
||||
|
||||
# 运算量用纯整数表示(展示/换算时才转 double), 绕开浮点表达式低级错误。
|
||||
# 每元素浮点运算数: FMA = 2(乘+加), ADD / MUL / SQRT = 1。
|
||||
ops_per_elem: int64 = int64(1)
|
||||
if mode == int(KERNEL_FMA_SIMD) or mode == int(KERNEL_FMA_SCALAR):
|
||||
ops_per_elem = int64(2)
|
||||
total_i: int64 = int64(n) * int64(iters) * ops_per_elem
|
||||
|
||||
last_chk: double = double(0.0)
|
||||
|
||||
# 2 轮满负荷热身(每轮 = ITERS 趟内核 + 读回 out 求校验和)
|
||||
for w in range(sizet(2)):
|
||||
run_iters(a, b, c, out, n, iters, mode)
|
||||
last_chk = checksum_sum(out, n)
|
||||
|
||||
# 连续计时 nruns 次; 计时只包住 ITERS 趟内核本身
|
||||
# 双计时: 墙钟 QPC + 线程 CPU(QueryThreadCycleTime), 同时包住同一段内核
|
||||
for r in range(nruns):
|
||||
cy0: int64 = thread_cycles(h_thread, cyc_buf)
|
||||
QueryPerformanceCounter(t0_buf)
|
||||
run_iters(a, b, c, out, n, iters, mode)
|
||||
QueryPerformanceCounter(t1_buf)
|
||||
cy1: int64 = thread_cycles(h_thread, cyc_buf)
|
||||
|
||||
# i64 整数换算: us = (dt * 1e6) / freq, 杜绝浮点表达式的读回歧义
|
||||
dt_i: int64 = qpc_ticks(t1_buf) - qpc_ticks(t0_buf)
|
||||
us_i: int64 = (dt_i * int64(1000000)) / freq_i
|
||||
us: double = double(us_i)
|
||||
# 周期 -> 微秒; 标定失败(tsc_hz==0)时给 0, 表现为一眼可见的异常
|
||||
cpu_us: double = double(0.0)
|
||||
if tsc_hz > double(0.0):
|
||||
cpu_us = double(cy1 - cy0) * double(1000000.0) / tsc_hz
|
||||
flops_i: int64 = (total_i * int64(1000000)) / us_i
|
||||
flops: double = double(flops_i)
|
||||
stdio.printf("RESULT %.3f %.3f %.0f\n", us, cpu_us, flops)
|
||||
|
||||
# 计时区外读回 out: 使本轮写入可观察, 阻止跨 run 优化
|
||||
last_chk = checksum_sum(out, n)
|
||||
|
||||
stdio.printf("CHECK %.17g\n", last_chk)
|
||||
return 0
|
||||
@@ -0,0 +1,332 @@
|
||||
# ViperSpeedBenchmark
|
||||
|
||||
"同内核多实现测速 + 组间差值核验" 基准测试工程(2026-09 修复版)。
|
||||
|
||||
在同一数学内核上对比 **Viper-SIMD / Viper-Scalar(同一 exe 的"向量化 vs 未向量化"对照) /
|
||||
手写 C / 手写 C++ / Python** 五个实现,
|
||||
按"进程内多次 run = 一组 × 5 组(默认; 首组仅汇报不计入统计)"记录原始数据、
|
||||
做组间差值核验,每次 run 的运算量统一为 **≈ 2.147e9 flops**。
|
||||
|
||||
## 1. 被测内核与负载(四语言一致)
|
||||
|
||||
```text
|
||||
double 向量, LEN = 16384 (2^14, 4 的倍数)
|
||||
输入 a / b / c 用固定种子伪随机(LCG, minstd 参数)填充一次, 三语言逐位一致
|
||||
out[i] = a[i] * b[i] + c[i] # 元素独立, 单趟融合乘加; 每元素 2 次浮点运算
|
||||
每次计时 run = ITERS = 65536 趟内核 -> 总 flops = LEN*ITERS*2 = 2147483648 ≈ 2.147e9
|
||||
```
|
||||
|
||||
- **工作集刻意压在私有 L2 之内(512 KiB)**: `a/b/c/out` 四份共 512 KiB, 本机
|
||||
(AMD Ryzen AI 7 H 350)每核 **1 MiB 私有 L2**, 所以访存不落到共享 L3 / DRAM ——
|
||||
不再被其它进程抢带宽, 这是消除"零星高时延尖峰"的关键。`LEN × ITERS × 2` 恒为
|
||||
≈2.147e9 flops/run, 所以 GFLOPS 与旧版(DRAM 级工作集)可直接对比。
|
||||
再往下压到 L1(需 `LEN ≤ 1024`)会让每趟的内核调用/派发开销占比飙升, 对
|
||||
Python+numpy 尤其致命(实测每趟固定开销约 0.4 µs)。
|
||||
|
||||
- **LCG 公式**(所有实现逐位一致):
|
||||
`state = fmod(state * 48271.0, 2147483647.0); val = state / 2147483647.0`
|
||||
种子固定 `12345.0`。C 用 `math.h::fmod`, Viper 用 `vipermath.fmod`, Python 用
|
||||
`math.fmod`(取值范围内逐位一致)。填充顺序统一为逐元素 `a[i], b[i], c[i]`
|
||||
依次消耗同一条 LCG 流。
|
||||
- **Viper = 单趟真硬件 FMA**: 在 `vipersimd` 新增批量内核
|
||||
`simd_fma_array(a, b, c_val, out, n)`(仿照 `simd_mul_array` 的 ×8 双缓冲 AVX2
|
||||
ymm 循环, 每大迭代 16 路 load 在飞后 8 条 `vfmadd213pd` 一次完成 `out=a*b+c`,
|
||||
加数 c 直接用内存操作数) —— **不是**旧版 `mul→tmp→add` 的两遍写法, 无中间数组、
|
||||
无 1.5 倍内存流量(见 `includes/vipersimd.vp`)。
|
||||
- **Viper 原生对照内核(SIMD 加速比的基准线)**: 每个操作都有一对"SIMD / 未向量化"
|
||||
实现, 两侧用的是对应宽度的同一条 IEEE 指令, 因此逐元素结果**逐位一致**, CHECK 可直接对照:
|
||||
|
||||
| mode | 操作 | SIMD 实现 | 原生(标量)实现 | 标量指令 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 0 / 1 | FMA `a*b+c` | `simd_fma_array` | `scalar_fma_array` | `vfmadd213sd` |
|
||||
| 2 / 3 | ADD `a+b` | `simd_add_array` | `scalar_add_array` | `vaddsd` |
|
||||
| 4 / 5 | MUL `a*b` | `simd_mul_array` | `scalar_mul_array` | `vmulsd` |
|
||||
| 6 / 7 | SQRT `sqrt(a)` | `simd_sqrt_array` | `scalar_sqrt_array` | `vsqrtsd` |
|
||||
|
||||
同一个 `app.exe` 靠命令行第 4 个参数 `mode` 选内核, 除"是否向量化"外没有任何其它
|
||||
差异; `run_bench.py` 把它们记成 `viper* / viper_scalar*` 两组数据, 供
|
||||
`plot_bench.py` 图1 画 ADD/MUL/SQRT 的延迟对比与加速比。
|
||||
> ADD/MUL/SQRT 每元素只有 1 次浮点运算(FMA 是 2 次), 所以它们的单 run flops 也
|
||||
> 如实按 1 计, 耗时约为 FMA 的一半 —— 图1 比的是"同操作下原生 vs SIMD"的倍数,
|
||||
> 不受这个差异影响。
|
||||
- **CHECK 舍入口径(2026-09 起 C/C++ 也开 FMA)**: C/C++ 与 Viper 都走
|
||||
**单趟真硬件 FMA** —— C/C++ 编译加 `-mfma -ffp-contract=fast`, 让
|
||||
`a[i]*b[i]+c[i]` 收缩成一条 `vfmadd`, 逐元素结果是**单次舍入**, 与 Viper 的
|
||||
`simd_fma_array` 同口径, 两者 CHECK 应当逐位一致; Python+numpy 仍是
|
||||
`multiply→tmp→add` **两次舍入**, 与前三者可能差 1 ulp。各实现**内部自洽**
|
||||
(每次进程 CHECK 恒定), 但 numpy 与原生实现**不逐位相等**属预期。
|
||||
- **线程绑定(每个实现必做)**: 进程入口调 `SetThreadAffinityMask` 把自身绑到
|
||||
**第一个可用逻辑核**(从 `1<<0` 逐个试, 第一个被系统接受的位), 并以
|
||||
`AFFINITY <core>` 行回报; `-1` = 绑定失败(不致命)。各实现会绑到同一个核,
|
||||
消除跨核迁移、大小核异构与缓存失效带来的抖动, 横向对比才有意义。
|
||||
- **双计时(每次 run 同时采集两个数)**:
|
||||
|
||||
| 字段 | 来源 | 特性 |
|
||||
| --- | --- | --- |
|
||||
| `wall_us` | `QueryPerformanceCounter` | 墙钟; **含**被抢占 / 被降频的时间, 对调度敏感 |
|
||||
| `cpu_us` | `QueryThreadCycleTime` | 只计本线程真正执行过的周期; **不含**被抢占时间, 对降频也免疫 |
|
||||
|
||||
TSC 频率由各实现**入口自行标定**: 绑核之后忙等 30ms 数周期, 重复 **3 次取最大**
|
||||
(取最大是为了压掉"标定窗口内被抢占导致 hz 偏小"的系统性正偏置 —— 该偏置会让
|
||||
`cpu_us` 偶尔比 `wall_us` 还大, 看着像 bug)。
|
||||
- **为什么不用 `GetThreadTimes`**: 它的 CPU 记账粒度跟随调度时钟节拍, 本机实测
|
||||
硬地板 **15.625 ms** —— 一个 ~37ms 的 run 只能被记成 2 或 3 个节拍
|
||||
(31250 / 46875 µs), 误差 **±40%**, 连"CPU 时间 > 墙钟时间"这种物理上不可能
|
||||
的结果都会出现。已验证 `timeBeginPeriod(1)` 在 Win10 2004+ **无法**改善该粒度
|
||||
(加完后值仍是 15625 的整数倍)。`QueryThreadCycleTime` 的粒度是周期级。
|
||||
- **统一输出协议**(每个可执行进程开头: `CFG` / `AFFINITY` / `TSC` 各一行; 之后每次 run 一行):
|
||||
```text
|
||||
CFG <len> <iters>
|
||||
AFFINITY <core> # -1 = 绑定失败
|
||||
TSC <hz> # 入口标定的 TSC 频率; 0 = 标定失败(该实现周期计数未推进)
|
||||
RESULT <wall_us> <cpu_us> <flops> # flops = (LEN*ITERS*2) / 墙钟秒
|
||||
CHECK <sum> # 全部结束后, 最后一次 run 的 out 升序累加
|
||||
```
|
||||
- **线程优先级**: 四个实现绑核后统一调 `SetThreadPriority(THREAD_PRIORITY_HIGHEST)`,
|
||||
压低背景进程抢占造成的"零星高时延尖峰"。
|
||||
- **Viper 侧为什么用 `OpenThread` 而不是 `GetCurrentThread()`**: 后者返回伪句柄
|
||||
`(HANDLE)-2`(负指针)。在 C / Python 里它只是一段裸位模式, 可以原样传下去;
|
||||
但 Viper 的 `t.CPtr` 语义下传递不可靠 —— 实测 `QueryThreadCycleTime` 只写回
|
||||
约 `0.230 µs`(≈ 一次调用开销的周期数), 即计数器根本没被读出来。
|
||||
改用 `GetCurrentThreadId()` + `OpenThread()` 拿真句柄, 全程 `t.CSizeT` 表达,
|
||||
彻底绕开指针/NULL 语义。
|
||||
|
||||
## 2. 修复内容(相对旧版 bench_report 暴露的问题)
|
||||
|
||||
| 旧问题 | 本次修复 |
|
||||
| --- | --- |
|
||||
| 噪声过大 → 差值核验全 FAIL | 旧版"每次 run 一次冷进程"; 现每组=**一次进程启动**, 进程内先 **2 轮满负荷热身**(每轮 = ITERS 趟 + 读回 out 校验)再连续计时 N 次, 消除冷页/启动/频率爬升噪声 |
|
||||
| Python 段缺失 | run_bench.py 默认各实现都跑(`py` 用 `sys.executable bench_py.py <runs>`), 任何启动/解析异常打印完整 traceback |
|
||||
| vipersimd 力度不足 | 弃用 `simd_mul_array`+`simd_add_array` 两遍, 新增/调用 `simd_fma_array` 单趟 FMA(×8 ymm 双缓冲, 3 输入 1 输出全走寄存器/访存流水) |
|
||||
| 运算量太小 | LEN=16384 × ITERS=65536 × 2 ≈ **2.147e9 flops/次 run**(旧版约 0.5e6) |
|
||||
| 访存打到共享 L3 / DRAM, 被其它进程抢带宽 → 零星高时延尖峰 | 把工作集压到 **512 KiB**(四份缓冲合计), 稳居每核私有 L2 之内; 同时绑核 + 提线程优先级 |
|
||||
| 跨核迁移 / 调度噪声无法定位 | 每个实现入口 **绑定固定逻辑核**(`SetThreadAffinityMask`), 并**双计时**: 墙钟(QPC) + 线程 CPU(`QueryThreadCycleTime`)。二者差额即被抢占/被降频吃掉的部分 |
|
||||
| 离群样本把组均值拉偏 | 组内**先按中位数上界裁剪**(`--trim-ratio`, 默认 1.2)再统计; 并提供 `--stat median/geomean` 应对双峰分布 |
|
||||
| C/C++ 与 Viper 舍入口径不一致 | C/C++ 放开 FMA 收缩(`-mfma -ffp-contract=fast`), 与 Viper 的 `simd_fma_array` 同为单趟硬件 FMA 单次舍入 |
|
||||
|
||||
## 3. 文件清单
|
||||
|
||||
| 文件 | 说明 |
|
||||
| --- | --- |
|
||||
| `project.vpj` | Viper 工程配置(产物 `.tpv_build/output/app.exe`) |
|
||||
| `App/main.vp` | Viper 实现: 8 个内核(FMA/ADD/MUL/SQRT × SIMD/原生) + 线程绑定 + QPC/周期双计时 + 进程内多 run; 自解析命令行 `[runs] [len] [iters] [mode]`(GetCommandLineA, 不依赖 sys/memhub) |
|
||||
| `bench_c.c` | 手写 C: 裸数组 + **全指针 `__restrict`**(fill/内核/校验和/local 指针都加) + noinline, `-O2 -mavx2 -mfma -ffp-contract=fast` 自动向量化单趟 FMA |
|
||||
| `bench_cpp.cpp` | 手写 C++: `std::vector` + `__restrict`, 同口径(`-O2 -mavx2 -mfma -ffp-contract=fast`) |
|
||||
| `bench_py.py` | Python: 优先 numpy(就地 ufunc **两次舍入**, 与 C/C++/Viper 的 FMA 单次舍入可能差 1 ulp); 无 numpy 自动纯 Python 降规模回退(趟数=8, 如实打印 CFG)。线程绑定与线程 CPU 时间走 ctypes 调 kernel32 |
|
||||
| `run_bench.py` | 总编排: 每组一次子进程 + 解析 `CFG/MODE/AFFINITY/TSC/RESULT/CHECK` + 离群裁剪 + 分组统计与差值核验 + 多尺寸扫描 + 控制台/`bench_report.txt`/CSV + `--flops-check`。Viper 侧 8 个内核变体(`viper`/`viper_scalar`/`viper_add`/`viper_scalar_add`/`viper_mul`/`viper_scalar_mul`/`viper_sqrt`/`viper_scalar_sqrt`, 同一个 exe 只差 `mode`) + `c` / `cpp` / `py` |
|
||||
| `plot_bench.py` | 读 run_bench 导出的 CSV 出 4 张图(matplotlib): ①左=ADD/MUL/SQRT 原生 vs SIMD 延迟、右=各操作加速比 ②各实现耗时折线(叠加每次 run 实测点) ③Overall Mean 耗时对比 ④Measured GFLOPS 对比; 另附控制台文字小结 |
|
||||
| `bench_report.txt` | 最近一次运行日志(由 run_bench.py 生成) |
|
||||
| `../../includes/vipersimd.vp` | 新增批量内核 `simd_fma_array`(单趟融合乘加, ×8 ymm 双缓冲) 与 4 个原生对照内核 `scalar_fma_array` / `scalar_add_array` / `scalar_mul_array` / `scalar_sqrt_array`(逐元素标量指令, 与对应 SIMD 版逐位同结果) |
|
||||
|
||||
## 4. 前置条件
|
||||
|
||||
- Viper 工具链: 可构建 `project.vpj`(见 §5.1)。
|
||||
- C/C++ 工具链(默认): `D:/msys64/mingw64/bin/clang.exe` 与 `clang++.exe`;
|
||||
其它目录用 `--cc-dir` 指定。
|
||||
- Python 3.8+(numpy **可选**, 有则全规模 2.147e9 flops; 无则纯 Python 回退)。
|
||||
- CPU 需支持 **AVX2 + FMA**(vipersimd 与 C/C++ 的 `-mavx2 -mfma` 均使用 YMM/FMA)。
|
||||
|
||||
## 5. 如何构建与运行
|
||||
|
||||
### 5.1 构建 Viper 部分(一次性)
|
||||
|
||||
在本工程目录(`ViperSpeedBenchmark/`)用你惯用的 tpv 方式构建 `project.vpj`,
|
||||
产物应为 `.tpv_build/output/app.exe`。若未构建, `run_bench.py` 会提示并**跳过
|
||||
全部 Viper 内核变体** 继续其它实现。
|
||||
|
||||
> **改了 `App/main.vp` 或 `includes/vipersimd.vp` 后必须重建**, 否则任一 Viper
|
||||
> 内核变体都会因为产物回报的 `MODE` 与期望不符(旧 exe 不认命令行第 4 个参数、
|
||||
> 或没有新内核)而显式报错 —— 这是有意为之: 宁可失败, 也不让某个内核的数据被
|
||||
> 另一个内核冒充, 污染 SIMD/原生 对照。
|
||||
|
||||
### 5.2 运行完整基准(默认: 8 个 Viper 内核变体 + C/C++/Python, 共 11 个; 每组 = 一次进程, 进程内 10 次 run)
|
||||
|
||||
```bat
|
||||
python run_bench.py
|
||||
```
|
||||
|
||||
常用参数:
|
||||
|
||||
```bat
|
||||
:: 只跑部分实现
|
||||
python run_bench.py --impl c,cpp
|
||||
:: Viper 的 SIMD vs 原生 对照(同一个 app.exe, 只差命令行 mode 参数)
|
||||
python run_bench.py --impl viper,viper_scalar --sizes 1024,4096,16384
|
||||
:: 只要图1 的内核成对数据(默认已含它们, 这里只是把范围收窄)
|
||||
python run_bench.py --impl viper_add,viper_scalar_add,viper_mul,viper_scalar_mul,viper_sqrt,viper_scalar_sqrt
|
||||
:: 只要图2/3/4 的跨语言对比(跳过 6 个 ADD/MUL/SQRT 变体)
|
||||
python run_bench.py --impl viper,viper_scalar,c,cpp,py
|
||||
:: 快速冒烟(减少组数/每组次数; 每次进程仍做 2 轮满热身)
|
||||
python run_bench.py --impl c,cpp,py --groups 2 --runs-per-group 3
|
||||
:: 自定义容差 / 导出原始数据 CSV / 启用 flops 核验
|
||||
python run_bench.py --tol 0.05 --csv out.csv
|
||||
python run_bench.py --flops-check
|
||||
:: 多尺寸扫描: LEN 决定工作集(四份缓冲共 LEN*32 字节), ITERS 自动配平
|
||||
python run_bench.py --sizes 1024,4096,16384,65536,262144
|
||||
:: 抗噪口径: 用线程 CPU 时间当主指标 + 中位数 + 裁剪
|
||||
python run_bench.py --metric cpu --stat median --trim-ratio 1.2
|
||||
:: 关闭离群裁剪(回到"全部样本计入")
|
||||
python run_bench.py --trim-ratio 0
|
||||
:: Python 模式强制
|
||||
python run_bench.py --impl py --py-mode pure
|
||||
python run_bench.py --impl py --py-mode numpy
|
||||
:: 自定义 clang 目录 / 自定义 viper 产物
|
||||
python run_bench.py --cc-dir C:/msys64/mingw64/bin
|
||||
python run_bench.py --viper-exe D:/path/to/app.exe
|
||||
```
|
||||
|
||||
C/C++ 源码更新(或编译选项变化)后会自动重编到 `.native_build/`。
|
||||
|
||||
**单 run 期望时长**: 每次 run ≈ 2.147e9 flops, 工作集 512 KiB 常驻 L2。实测吞吐
|
||||
取决于机器: 原生(C/C++/Viper, 单线程 AVX2+FMA)大致 **几十 ms** 量级;
|
||||
Python+numpy 与之同规模、通常慢 1~3 倍; 纯 Python 回退
|
||||
(无 numpy)会把趟数自动降为 8, 单 run 约 0.2~0.5 s。**完整默认跑 11 个实现 × 10 进程,
|
||||
墙钟约 1~5 分钟**, 快速验证请用 §5.2 冒烟参数。
|
||||
|
||||
### 5.3 输出与日志
|
||||
|
||||
- 控制台实时输出; 结束后全文写入 `bench_report.txt`。
|
||||
- `--csv out.csv`: 逐样本原始行, 列 =
|
||||
`impl, len, iters, group, run, wall_us, cpu_us, flops, check, trimmed`
|
||||
(`trimmed=1` 表示该样本被离群裁剪剔除, 未计入该组的组统计量)。
|
||||
- `--flops-check`: 核对各实现 CFG 单 run 总运算量是否 ≈ 2.147e9, 并逐 RESULT 行
|
||||
验证 `flops` 字段与 `us` 换算一致(`flops = CFG_total / 秒`);
|
||||
对纯 Python 降规模配置给出提示。注意 `flops` 是**吞吐**, 只有单 run 时长恰 ≈1 s
|
||||
时数值才 ≈2e9, 报告会同时给出实测 GFLOPS。
|
||||
|
||||
## 6. 多尺寸扫描与出图
|
||||
|
||||
### 6.1 `--sizes`: 一次跑出"吞吐 vs 工作集"曲线
|
||||
|
||||
`--sizes` 接受逗号分隔的 `LEN` 列表。**只调 LEN, ITERS 自动配平**(`ITERS = 2^30 / LEN`),
|
||||
所以每次 run 的运算量恒为 ≈2.147e9 flops, 各尺寸的 GFLOPS 可直接横向比较。
|
||||
工作集 = `a/b/c/out` 四份合计 = `LEN × 32` 字节:
|
||||
|
||||
| LEN | ITERS | 工作集 | 落在 |
|
||||
| --- | --- | --- | --- |
|
||||
| `1024` | 1048576 | 32 KiB | L1D(本机 48 KiB) |
|
||||
| `4096` | 262144 | 128 KiB | L2 |
|
||||
| `16384` | 65536 | 512 KiB | **L2(默认, 本机每核 1 MiB)** |
|
||||
| `65536` | 16384 | 2 MiB | L3(本机 16 MB 共享) |
|
||||
| `262144` | 4096 | 8 MiB | L3 / 边缘 |
|
||||
| `1048576` | 1024 | 32 MiB | DRAM |
|
||||
|
||||
`LEN` 必须是 4 的倍数(内核按 4-lane ymm 分批), 且必须让 `2^30 / LEN` 整除;
|
||||
不满足时 run_bench.py **直接报错退出**, 不做静默修正。
|
||||
|
||||
> 想看"纯算力"(SIMD 能力)看小尺寸那一端, 想看"访存带宽"看大尺寸那一端。
|
||||
> 本机四个实现应该都会在 **512 KiB~2 MiB 附近出现一个峰**(私有 L2 内),
|
||||
> 再加大就掉到共享 L3/DRAM 上 —— 曲线拐点就是这台机器各级缓存的边界。
|
||||
|
||||
### 6.2 自动导 CSV
|
||||
|
||||
run_bench.py **默认自动导出** CSV(带时间戳, 如 `bench_20260910_203105.csv`),
|
||||
不用再加 `--csv`。要指定文件名用 `--csv name.csv`, 完全不写用 `--no-csv`。
|
||||
|
||||
CSV 列: `impl, len, iters, group, run, wall_us, cpu_us, flops, check, trimmed`
|
||||
(`trimmed=1` 表示该样本被离群裁剪剔除)。
|
||||
|
||||
### 6.3 `plot_bench.py`: 从 CSV 出 4 张图
|
||||
|
||||
```bat
|
||||
python plot_bench.py :: 自动取最新的 bench_*.csv
|
||||
python plot_bench.py bench_20260910_203105.csv
|
||||
python plot_bench.py --metric cpu
|
||||
python plot_bench.py bench_xxx.csv --out-dir figs --prefix run1
|
||||
python plot_bench.py --simd-impl viper --scalar-impl viper_scalar
|
||||
```
|
||||
|
||||
产出 4 张 PNG(默认与 CSV 同目录, 文件名前缀 = CSV 名):
|
||||
|
||||
1. `<前缀>_1_simd_vs_scalar.png` —— **左右双面板**:
|
||||
- **左**: 每种操作(ADD / MUL / SQRT)一组柱, 组内左=原生(标量)、右=**ViperSIMD**,
|
||||
柱顶标该操作的每 run 均值(us)
|
||||
- **右**: 同一批操作的 **SIMD 加速比**(原生延迟 ÷ SIMD 延迟), 柱顶标 `xN.NN`,
|
||||
并画一条 `x1 = no gain` 参考线
|
||||
**前提**: CSV 里要同时有这三种操作的 原生+SIMD 成对数据(见 §5.2 的 "图1 ... 内核成对数据");
|
||||
缺哪一对就少画哪一根, 全缺则整张图跳过并提示。只取 CSV 里**最小的那个 LEN**。
|
||||
2. `<前缀>_2_exec_time.png` —— **Execution Time(microseconds)**:
|
||||
每实现一条均值折线, 并把**每组内每次 run 的实测点**画成同色散点(各实现横向错开
|
||||
以免重叠)。多尺寸时横轴 = LEN(折线即"工作集-耗时曲线"); **只跑了 1 个 LEN 时
|
||||
横轴自动改用组号**(每组 = 一次进程内的多次 run), 否则单点连不成折线。
|
||||
动态范围 >30 倍时纵轴自动转对数(`--y-scale log|linear`)。
|
||||
3. `<前缀>_3_overall_mean.png` —— **Overall Mean Execution Time**:
|
||||
每实现一根柱(全部尺寸、全部组有效样本合并平均), 误差棒 = 样本标准差,
|
||||
柱顶标绝对耗时, 柱身标"相对最快"倍数。
|
||||
4. `<前缀>_4_gflops.png` —— **Measured GFLOPS Throughput**:
|
||||
每实现一根柱(`GFLOPS = LEN×ITERS×2 ÷ 单 run 秒数`, 逐样本算后取平均),
|
||||
误差棒 = 样本标准差, 柱身标"相对最快"倍数。
|
||||
|
||||
三张柱/线图都跟随 `--metric`(`wall` 默认 QPC 墙钟 / `cpu` 线程 CPU 时间),
|
||||
且与 run_bench.py 报告同口径: CSV 里 `trimmed=1` 的离群样本不计入。
|
||||
|
||||
同时在控制台打一份文字小结(`impl / LEN / 工作集 / 每run耗时 / GFLOPS / CPU比 / trimmed`)。
|
||||
|
||||
> 依赖 `matplotlib` + `numpy`。**图内文字一律用英文**(短句 + 多行换行, 避免长句把标题
|
||||
> 撑爆); 控制台小结仍为中文。字体已显式配置 `Microsoft YaHei`, 万一要出中文图也不会变方块。
|
||||
|
||||
---
|
||||
|
||||
## 7. 统计口径与差值核验(默认容差 5%)
|
||||
|
||||
每个实现(`impl`)每次 run 产出 **两个**原始值(`wall_us` 与 `cpu_us`); **一组 = 一次
|
||||
进程启动**内的 `runs_per_group`(=10)次连续计时。
|
||||
|
||||
### 7.1 主指标、离群裁剪、统计量
|
||||
|
||||
- **主指标 `--metric`**(默认 `wall`): 决定组均值 / 总体 / 差值核验用哪个字段。
|
||||
另一指标同时采集, 在报告的"对照"行与最终汇总里一并给出, 也都写进 CSV。
|
||||
被抢占较多的机器上建议试试 `--metric cpu`。
|
||||
- **离群裁剪 `--trim-ratio`**(默认 `1.2`): 每组**先**按组内中位数剔上界离群点 ——
|
||||
只保留 `值 <= 组内中位数 × 1.2` 的样本, 再算该组统计量。`<=0` 关闭裁剪。
|
||||
- **统计量 `--stat`**(默认 `mean`): `mean`(裁剪后算术平均) / `median` / `geomean`。
|
||||
报告里三个值**同时列出**, 不必重跑。**双峰分布**(一半样本被降频或被抢占)下
|
||||
`median` 的抗噪能力远强于算术平均。
|
||||
|
||||
### 7.2 各统计量的定义
|
||||
|
||||
对每个实现:
|
||||
|
||||
- **组统计值**: 组内 10 次原始值**先经离群裁剪**, 再按 `--stat` 取统计量
|
||||
(默认 = 裁剪后算术平均)。报告同时列出该组的 均值 / 中位数 / 几何均值。
|
||||
- **组内差(max-min)**: 裁剪后保留样本的最大值 - 最小值(主要反映进程内稳态抖动)。
|
||||
- **总体统计值**: 该实现全部 `统计组数 × runs_per_group` 个**裁剪后**样本按 `--stat`
|
||||
统计(默认排除首组后)。
|
||||
- **样本标准差 / 最大偏差**: 裁剪后样本的 n-1 无偏标准差; `|样本-总体|` 最大者。
|
||||
- **组 vs 总体差值**: `组统计值 - 总体统计值`(绝对 us 与相对百分比)。
|
||||
- **相邻组差值**: `组统计值[g+1] - 组统计值[g]`(绝对 us 与相对百分比)。
|
||||
- **差值核验**: 每组 `|组统计值-总体|/总体 <= tol` 且 `|相邻组差值|/总体 <= tol`
|
||||
→ `PASS`, 否则列出违规组 → `FAIL`。默认 tol = 0.05, `--tol` 可调。
|
||||
|
||||
> **首组效应规避(默认)**: 组1(第一次进程启动)在报告中**仍逐组/逐 run 展示**,
|
||||
> 但**不计入**总体均值、样本标准差、最大偏差与差值核验(含组1->组2 相邻差),
|
||||
> 用于剔除冷启动/频率爬升阶段的偏差。需要把组1纳入统计时加 `--include-first-group`。
|
||||
|
||||
> 扰动缓解说明: (1) 每个进程入口 **绑定到固定逻辑核**(`AFFINITY`), 消除跨核迁移与
|
||||
> 大小核异构; (2) 每个进程内先 **2 轮满负荷热身**(每轮 = ITERS 趟内核 + 读回 out
|
||||
> 求校验和), 缓存/TLB/频率进入稳态后才开始计时; (3) 组内多次 run 取自**同一进程**,
|
||||
> 没有冷启动开销; (4) 每次 run 同时给出**墙钟**与**线程 CPU 时间**, 二者的差额就是
|
||||
> 被抢占 / 被降频吃掉的部分 —— 若墙钟抖动大而 CPU 时间稳, 说明是调度噪声而非代码问题;
|
||||
> (5) 组内离群点先按中位数上界裁掉, 双峰分布下可用 `--stat median` 进一步抗噪;
|
||||
> (6) 尺寸扫描时 **LEN×ITERS 恒定**(单 run 运算量恒为 ≈2.147e9 flops), 只让工作集变化,
|
||||
> 组间只剩真实抖动。
|
||||
|
||||
## 8. 防止"优化器把循环删掉"与已知注意点
|
||||
|
||||
- **C/C++**: 内核 noinline + 全指针 `__restrict`(C 与 C++ 统一拼写, 不再一个用
|
||||
C99 `restrict` 一个用 `__restrict`); 每轮热身与每次 run **之后**
|
||||
升序读回 `out` 求和(计时区外), 使计时区内每条写可观察、阻止跨 run 写折叠;
|
||||
未开 fast-math, 但显式开 `-ffp-contract=fast` + `-mfma`, 允许收缩成单趟 FMA
|
||||
(与 Viper 同口径、同一舍入次数)。要复现旧的"两次舍入"口径做 A/B, 把
|
||||
`run_bench.py` 里的 `NATIVE_FLAGS` 改回 `-ffp-contract=off` 并去掉 `-mfma` 即可。
|
||||
- **Viper**: `simd_fma_array` 内联汇编自带 memory clobber, 计时区外同样读回校验。
|
||||
- **Python**: numpy 就地 ufunc / 纯 Python 循环都不存在被删问题; 纯 Python 回退按
|
||||
实际趟数打印 CFG(≠2e9 配置), `--flops-check` 会给出提示。
|
||||
- Viper 与 C/C++ 现在同为单次舍入 FMA, CHECK 应当逐位一致; numpy 是两次舍入, 与它们
|
||||
可能差 1 ulp(见 §1), 跨实现比较 CHECK 数值前先确认这一点。
|
||||
- **线程 CPU 时间(`cpu_us`)的适用边界**: 它由 `QueryThreadCycleTime` 的周期数除以
|
||||
**入口标定的 TSC 频率**得到。现代 x86 的 TSC 是 invariant 的(与核心实际频率无关),
|
||||
所以该值就是"本线程真正跑过的时间"; 但在极老的非 invariant TSC 机器上, 周期数会
|
||||
跟着核心频率走, 换算出的 `cpu_us` 会失真 —— 那种机器上请以 `--metric wall` 为准。
|
||||
另外标定本身有约 0.1% 的残差(已用三次取最大压掉大部分), 不影响各实现之间的横向对比。
|
||||
- 建议空闲机器上跑完整基准; CPU 需支持 AVX2+FMA(见 §4)。
|
||||
@@ -0,0 +1,551 @@
|
||||
impl,len,iters,group,run,wall_us,cpu_us,flops,check,trimmed
|
||||
viper,16384,65536,1,1,144887.0,0.077,14821782823.0,12302.997770370706,0
|
||||
viper,16384,65536,1,2,146648.0,0.078,14643797719.0,12302.997770370706,0
|
||||
viper,16384,65536,1,3,153844.0,0.081,13958839135.0,12302.997770370706,0
|
||||
viper,16384,65536,1,4,152763.0,0.081,14057616359.0,12302.997770370706,0
|
||||
viper,16384,65536,1,5,145445.0,0.077,14764919027.0,12302.997770370706,0
|
||||
viper,16384,65536,1,6,145390.0,0.077,14770504491.0,12302.997770370706,0
|
||||
viper,16384,65536,1,7,145063.0,0.077,14803800059.0,12302.997770370706,0
|
||||
viper,16384,65536,1,8,145226.0,0.077,14787184443.0,12302.997770370706,0
|
||||
viper,16384,65536,1,9,145576.0,0.077,14751632466.0,12302.997770370706,0
|
||||
viper,16384,65536,1,10,145136.0,0.077,14796354095.0,12302.997770370706,0
|
||||
viper,16384,65536,2,1,148678.0,0.079,14443856172.0,12302.997770370706,0
|
||||
viper,16384,65536,2,2,146928.0,0.078,14615891103.0,12302.997770370706,0
|
||||
viper,16384,65536,2,3,146486.0,0.078,14659992408.0,12302.997770370706,0
|
||||
viper,16384,65536,2,4,145338.0,0.077,14775789181.0,12302.997770370706,0
|
||||
viper,16384,65536,2,5,145337.0,0.077,14775890846.0,12302.997770370706,0
|
||||
viper,16384,65536,2,6,146536.0,0.078,14654990227.0,12302.997770370706,0
|
||||
viper,16384,65536,2,7,146770.0,0.077,14631625318.0,12302.997770370706,0
|
||||
viper,16384,65536,2,8,151202.0,0.08,14202746312.0,12302.997770370706,0
|
||||
viper,16384,65536,2,9,145632.0,0.077,14745960008.0,12302.997770370706,0
|
||||
viper,16384,65536,2,10,145855.0,0.077,14723414678.0,12302.997770370706,0
|
||||
viper,16384,65536,3,1,148714.0,0.078,14440359670.0,12302.997770370706,0
|
||||
viper,16384,65536,3,2,148051.0,0.078,14505026294.0,12302.997770370706,0
|
||||
viper,16384,65536,3,3,145547.0,0.077,14754571705.0,12302.997770370706,0
|
||||
viper,16384,65536,3,4,147119.0,0.078,14596915748.0,12302.997770370706,0
|
||||
viper,16384,65536,3,5,150602.0,0.08,14259330208.0,12302.997770370706,0
|
||||
viper,16384,65536,3,6,147435.0,0.078,14565629925.0,12302.997770370706,0
|
||||
viper,16384,65536,3,7,147624.0,0.078,14546981845.0,12302.997770370706,0
|
||||
viper,16384,65536,3,8,146917.0,0.078,14616985427.0,12302.997770370706,0
|
||||
viper,16384,65536,3,9,146468.0,0.077,14661794030.0,12302.997770370706,0
|
||||
viper,16384,65536,3,10,146964.0,0.078,14612310824.0,12302.997770370706,0
|
||||
viper,16384,65536,4,1,145652.0,0.077,14743935187.0,12302.997770370706,0
|
||||
viper,16384,65536,4,2,146504.0,0.078,14658191230.0,12302.997770370706,0
|
||||
viper,16384,65536,4,3,146522.0,0.078,14656390494.0,12302.997770370706,0
|
||||
viper,16384,65536,4,4,146059.0,0.077,14702850546.0,12302.997770370706,0
|
||||
viper,16384,65536,4,5,145909.0,0.077,14717965636.0,12302.997770370706,0
|
||||
viper,16384,65536,4,6,146462.0,0.077,14662394668.0,12302.997770370706,0
|
||||
viper,16384,65536,4,7,145349.0,0.077,14774670950.0,12302.997770370706,0
|
||||
viper,16384,65536,4,8,145227.0,0.077,14787082622.0,12302.997770370706,0
|
||||
viper,16384,65536,4,9,145863.0,0.077,14722607158.0,12302.997770370706,0
|
||||
viper,16384,65536,4,10,145437.0,0.077,14765731196.0,12302.997770370706,0
|
||||
viper,16384,65536,5,1,145607.0,0.077,14748491817.0,12302.997770370706,0
|
||||
viper,16384,65536,5,2,145385.0,0.077,14771012470.0,12302.997770370706,0
|
||||
viper,16384,65536,5,3,145343.0,0.077,14775280873.0,12302.997770370706,0
|
||||
viper,16384,65536,5,4,145413.0,0.077,14768168238.0,12302.997770370706,0
|
||||
viper,16384,65536,5,5,145235.0,0.077,14786268103.0,12302.997770370706,0
|
||||
viper,16384,65536,5,6,145502.0,0.077,14759134912.0,12302.997770370706,0
|
||||
viper,16384,65536,5,7,145365.0,0.077,14773044735.0,12302.997770370706,0
|
||||
viper,16384,65536,5,8,145567.0,0.077,14752544519.0,12302.997770370706,0
|
||||
viper,16384,65536,5,9,145926.0,0.077,14716251031.0,12302.997770370706,0
|
||||
viper,16384,65536,5,10,145536.0,0.077,14755686895.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,1,1,561969.0,561464.723,3821356067.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,1,2,564294.0,563801.756,3805611344.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,1,3,562172.0,561689.507,3819976178.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,1,4,566048.0,565675.12,3793818983.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,1,5,572126.0,571424.172,3753515218.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,1,6,577757.0,576952.532,3716932288.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,1,7,565624.0,565283.833,3796662885.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,1,8,569439.0,569099.943,3771226853.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,1,9,572073.0,571494.657,3753862965.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,1,10,563496.0,562749.137,3811000695.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,2,1,568249.0,567740.784,3779124376.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,2,2,565576.0,564981.662,3796985105.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,2,3,565233.0,564421.863,3799289227.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,2,4,563892.0,563267.925,3808324374.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,2,5,564975.0,564231.249,3801024201.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,2,6,564652.0,564101.808,3803198515.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,2,7,564716.0,564374.411,3802767493.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,2,8,563528.0,563127.375,3810784287.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,2,9,566909.0,566105.244,3788057074.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,2,10,564590.0,563910.41,3803616160.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,3,1,565859.0,565589.367,3795086139.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,3,2,564449.0,563585.67,3804566308.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,3,3,560153.0,559859.396,3833744794.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,3,4,562346.0,561672.276,3818794208.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,3,5,561160.0,560846.126,3826865150.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,3,6,560703.0,560168.848,3829984230.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,3,7,561167.0,560770.836,3826817414.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,3,8,561967.0,561449.018,3821369667.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,3,9,561335.0,560803.881,3825672099.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,3,10,560911.0,560425.144,3828563975.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,4,1,565473.0,564428.415,3797676720.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,4,2,566108.0,565381.005,3793416888.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,4,3,565812.0,565159.399,3795401384.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,4,4,564992.0,564457.94,3800909832.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,4,5,562782.0,562205.547,3815835701.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,4,6,564976.0,564093.912,3801017473.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,4,7,564492.0,563913.446,3804276496.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,4,8,571145.0,570055.144,3759962265.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,4,9,571054.0,570530.691,3760561432.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,4,10,563639.0,563242.606,3810033812.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,5,1,563862.0,563007.163,3808526994.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,5,2,563053.0,562556.047,3813999122.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,5,3,562800.0,562372.174,3815713660.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,5,4,563539.0,563131.375,3810709902.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,5,5,564263.0,563508.927,3805820420.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,5,6,578726.0,578089.089,3710708777.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,5,7,592638.0,591476.638,3623600997.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,5,8,574560.0,574056.46,3737614257.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,5,9,563656.0,563055.901,3809918900.0,12302.997770370706,0
|
||||
viper_scalar,16384,65536,5,10,567374.0,566308.208,3784952514.0,12302.997770370706,0
|
||||
viper_add,16384,65536,1,1,108386.0,0.091,9906646836.0,16340.870255275997,0
|
||||
viper_add,16384,65536,1,2,109070.0,0.091,9844520253.0,16340.870255275997,0
|
||||
viper_add,16384,65536,1,3,109372.0,0.092,9817337380.0,16340.870255275997,0
|
||||
viper_add,16384,65536,1,4,108952.0,0.091,9855182318.0,16340.870255275997,0
|
||||
viper_add,16384,65536,1,5,108804.0,0.091,9868587772.0,16340.870255275997,0
|
||||
viper_add,16384,65536,1,6,109399.0,0.092,9814914432.0,16340.870255275997,0
|
||||
viper_add,16384,65536,1,7,109337.0,0.092,9820480020.0,16340.870255275997,0
|
||||
viper_add,16384,65536,1,8,111066.0,0.093,9667601462.0,16340.870255275997,0
|
||||
viper_add,16384,65536,1,9,108704.0,0.091,9877666176.0,16340.870255275997,0
|
||||
viper_add,16384,65536,1,10,109093.0,0.091,9842444739.0,16340.870255275997,0
|
||||
viper_add,16384,65536,2,1,109174.0,0.091,9835142286.0,16340.870255275997,0
|
||||
viper_add,16384,65536,2,2,110421.0,0.093,9724072631.0,16340.870255275997,0
|
||||
viper_add,16384,65536,2,3,108427.0,0.091,9902900790.0,16340.870255275997,0
|
||||
viper_add,16384,65536,2,4,108161.0,0.091,9927254962.0,16340.870255275997,0
|
||||
viper_add,16384,65536,2,5,108456.0,0.091,9900252858.0,16340.870255275997,0
|
||||
viper_add,16384,65536,2,6,108340.0,0.091,9910853092.0,16340.870255275997,0
|
||||
viper_add,16384,65536,2,7,108832.0,0.091,9866048809.0,16340.870255275997,0
|
||||
viper_add,16384,65536,2,8,109515.0,0.092,9804518321.0,16340.870255275997,0
|
||||
viper_add,16384,65536,2,9,108773.0,0.091,9871400292.0,16340.870255275997,0
|
||||
viper_add,16384,65536,2,10,108454.0,0.091,9900435428.0,16340.870255275997,0
|
||||
viper_add,16384,65536,3,1,108034.0,0.09,9938925005.0,16340.870255275997,0
|
||||
viper_add,16384,65536,3,2,107488.0,0.09,9989411134.0,16340.870255275997,0
|
||||
viper_add,16384,65536,3,3,107625.0,0.09,9976695228.0,16340.870255275997,0
|
||||
viper_add,16384,65536,3,4,108422.0,0.091,9903357473.0,16340.870255275997,0
|
||||
viper_add,16384,65536,3,5,110647.0,0.093,9704210904.0,16340.870255275997,0
|
||||
viper_add,16384,65536,3,6,109870.0,0.092,9772839027.0,16340.870255275997,0
|
||||
viper_add,16384,65536,3,7,109604.0,0.092,9796556913.0,16340.870255275997,0
|
||||
viper_add,16384,65536,3,8,108675.0,0.091,9880302038.0,16340.870255275997,0
|
||||
viper_add,16384,65536,3,9,108418.0,0.091,9903722850.0,16340.870255275997,0
|
||||
viper_add,16384,65536,3,10,108321.0,0.091,9912591501.0,16340.870255275997,0
|
||||
viper_add,16384,65536,4,1,109235.0,0.091,9829650057.0,16340.870255275997,0
|
||||
viper_add,16384,65536,4,2,108650.0,0.091,9882575462.0,16340.870255275997,0
|
||||
viper_add,16384,65536,4,3,108695.0,0.091,9878484051.0,16340.870255275997,0
|
||||
viper_add,16384,65536,4,4,108227.0,0.091,9921201031.0,16340.870255275997,0
|
||||
viper_add,16384,65536,4,5,107843.0,0.09,9956527767.0,16340.870255275997,0
|
||||
viper_add,16384,65536,4,6,107661.0,0.09,9973359192.0,16340.870255275997,0
|
||||
viper_add,16384,65536,4,7,107706.0,0.09,9969192282.0,16340.870255275997,0
|
||||
viper_add,16384,65536,4,8,108501.0,0.091,9896146800.0,16340.870255275997,0
|
||||
viper_add,16384,65536,4,9,108714.0,0.091,9876757584.0,16340.870255275997,0
|
||||
viper_add,16384,65536,4,10,108820.0,0.091,9867136776.0,16340.870255275997,0
|
||||
viper_add,16384,65536,5,1,108715.0,0.091,9876666734.0,16340.870255275997,0
|
||||
viper_add,16384,65536,5,2,109480.0,0.092,9807652758.0,16340.870255275997,0
|
||||
viper_add,16384,65536,5,3,108206.0,0.091,9923126480.0,16340.870255275997,0
|
||||
viper_add,16384,65536,5,4,108012.0,0.091,9940949375.0,16340.870255275997,0
|
||||
viper_add,16384,65536,5,5,108924.0,0.091,9857715691.0,16340.870255275997,0
|
||||
viper_add,16384,65536,5,6,110145.0,0.092,9748439093.0,16340.870255275997,0
|
||||
viper_add,16384,65536,5,7,112057.0,0.094,9582103964.0,16340.870255275997,0
|
||||
viper_add,16384,65536,5,8,109956.0,0.092,9765195387.0,16340.870255275997,0
|
||||
viper_add,16384,65536,5,9,108903.0,0.091,9859616576.0,16340.870255275997,0
|
||||
viper_add,16384,65536,5,10,109536.0,0.092,9802638621.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,1,1,269930.0,269751.887,3977852865.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,1,2,274199.0,273960.985,3915921735.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,1,3,261735.0,261500.037,4102400611.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,1,4,270404.0,270234.09,3970879957.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,1,5,258664.0,258368.942,4151106547.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,1,6,258743.0,258148.758,4149839122.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,1,7,262064.0,261838.718,4097250381.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,1,8,270069.0,269919.73,3975805531.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,1,9,261868.0,261541.275,4100317045.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,1,10,254664.0,254088.59,4216307856.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,2,1,360304.0,359945.347,2980099649.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,2,2,365588.0,365343.64,2937026992.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,2,3,365021.0,364084.968,2941589179.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,2,4,361872.0,361542.603,2967186806.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,2,5,367328.0,367080.713,2923114557.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,2,6,364731.0,364062.749,2943928056.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,2,7,360212.0,359955.051,2980860781.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,2,8,362685.0,362547.866,2960535517.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,2,9,361384.0,361067.201,2971193589.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,2,10,361610.0,361160.678,2969336644.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,3,1,361089.0,361345.139,2973620974.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,3,2,363834.0,363488.511,2951186046.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,3,3,372305.0,372394.941,2884038151.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,3,4,363456.0,363485.297,2954255326.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,3,5,361990.0,362082.846,2966219575.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,3,6,368353.0,368539.196,2914980532.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,3,7,358959.0,359030.726,2991265921.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,3,8,360859.0,360639.405,2975516265.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,3,9,365321.0,365539.386,2939173559.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,3,10,359939.0,360021.143,2983121651.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,4,1,362484.0,362319.897,2962177155.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,4,2,360776.0,360388.501,2976200811.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,4,3,360570.0,360488.804,2977901167.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,4,4,357658.0,357117.717,3002146810.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,4,5,362915.0,362767.559,2958659256.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,4,6,361928.0,361627.084,2966727702.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,4,7,361976.0,361418.266,2966334298.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,4,8,360717.0,360445.863,2976687608.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,4,9,363428.0,363256.964,2954482934.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,4,10,358779.0,358471.929,2992766644.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,5,1,367149.0,366907.264,2924539693.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,5,2,360919.0,360577.27,2975021608.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,5,3,364497.0,363715.476,2945818001.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,5,4,362951.0,362627.042,2958365795.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,5,5,360708.0,360441.936,2976761879.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,5,6,367280.0,366961.919,2923496580.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,5,7,381777.0,381450.873,2812484314.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,5,8,368746.0,368451.852,2911873821.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,5,9,371674.0,370922.635,2888934453.0,16340.870255275997,0
|
||||
viper_scalar_add,16384,65536,5,10,369805.0,369498.361,2903535171.0,16340.870255275997,0
|
||||
viper_mul,16384,65536,1,1,113188.0,0.018,9486357423.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,1,2,118870.0,0.019,9032908420.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,1,3,121283.0,0.02,8853193143.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,1,4,125240.0,0.02,8573473522.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,1,5,125201.0,0.02,8576144152.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,1,6,117159.0,0.019,9164825783.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,1,7,112412.0,0.018,9551843433.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,1,8,108514.0,0.018,9894961240.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,1,9,110677.0,0.018,9701580490.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,1,10,109073.0,0.018,9844249484.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,2,1,109627.0,0.018,9794501573.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,2,2,108540.0,0.018,9892590971.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,2,3,110288.0,0.018,9735799216.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,2,4,109679.0,0.018,9789857894.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,2,5,109828.0,0.018,9776576319.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,2,6,109087.0,0.018,9842986093.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,2,7,109921.0,0.018,9768304727.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,2,8,110724.0,0.018,9697462374.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,2,9,109869.0,0.018,9772927977.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,2,10,109581.0,0.018,9798613117.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,3,1,108389.0,0.018,9906372639.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,3,2,109416.0,0.018,9813389485.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,3,3,109801.0,0.018,9778980373.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,3,4,111084.0,0.018,9666034928.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,3,5,108999.0,0.018,9850932797.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,3,6,108915.0,0.018,9858530266.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,3,7,108802.0,0.018,9868769177.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,3,8,108199.0,0.018,9923768463.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,3,9,108512.0,0.018,9895143615.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,3,10,108110.0,0.018,9931938063.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,4,1,109473.0,0.018,9808279886.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,4,2,108758.0,0.018,9872761764.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,4,3,108287.0,0.018,9915703861.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,4,4,108348.0,0.018,9910121312.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,4,5,108169.0,0.018,9926520759.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,4,6,108311.0,0.018,9913506698.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,4,7,108570.0,0.018,9889857456.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,4,8,108299.0,0.018,9914605157.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,4,9,109462.0,0.018,9809265535.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,4,10,108846.0,0.018,9864779817.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,5,1,107840.0,0.018,9956804747.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,5,2,108129.0,0.018,9930192862.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,5,3,107886.0,0.018,9952559405.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,5,4,107851.0,0.018,9955789227.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,5,5,108757.0,0.018,9872852542.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,5,6,107978.0,0.018,9944079571.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,5,7,108209.0,0.018,9922851370.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,5,8,107701.0,0.018,9969655100.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,5,9,107950.0,0.018,9946658860.0,4066.711980202381,0
|
||||
viper_mul,16384,65536,5,10,118290.0,0.019,9077198613.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,1,1,382511.0,382243.964,2807087440.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,1,2,361407.0,360924.039,2971004501.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,1,3,385639.0,385407.352,2784318556.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,1,4,357934.0,357639.301,2999831879.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,1,5,364822.0,364393.414,2943193732.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,1,6,385011.0,384859.282,2788860120.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,1,7,360933.0,360813.706,2974906212.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,1,8,361609.0,361402.224,2969344855.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,1,9,376713.0,376540.946,2850291399.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,1,10,377328.0,376728.988,2845645761.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,2,1,363880.0,363220.237,2950812971.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,2,2,358508.0,358247.848,2995028908.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,2,3,361418.0,361174.197,2970914077.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,2,4,360484.0,359990.568,2978611599.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,2,5,358900.0,358783.811,2991757659.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,2,6,357768.0,357551.02,3001223765.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,2,7,364769.0,364337.105,2943621371.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,2,8,360368.0,360072.652,2979570394.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,2,9,359779.0,359395.051,2984448297.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,2,10,358393.0,357841.125,2995989944.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,3,1,365924.0,365817.481,2934330145.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,3,2,357841.0,357477.505,3000611511.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,3,3,363538.0,362949.479,2953588961.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,3,4,358023.0,357688.36,2999086159.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,3,5,363320.0,362932.557,2955361180.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,3,6,360904.0,360325.981,2975145257.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,3,7,358845.0,358494.949,2992216204.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,3,8,361702.0,361621.062,2968581384.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,3,9,359017.0,358839.572,2990782676.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,3,10,362350.0,362152.417,2963272592.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,4,1,362296.0,362232.482,2963714266.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,4,2,365337.0,364963.049,2939044838.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,4,3,360291.0,360170.341,2980207176.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,4,4,359582.0,359457.356,2986083352.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,4,5,362058.0,361912.346,2965662473.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,4,6,358421.0,358002.449,2995755895.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,4,7,366971.0,366822.72,2925958247.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,4,8,361110.0,360863.697,2973448046.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,4,9,359445.0,359311.151,2987221477.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,4,10,361398.0,361179.633,2971078489.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,5,1,360532.0,360385.072,2978215037.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,5,2,357848.0,357518.321,3000552815.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,5,3,356531.0,356285.606,3011636643.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,5,4,357194.0,356547.157,3006046641.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,5,5,358292.0,358065.863,2996834492.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,5,6,359978.0,359701.59,2982798459.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,5,7,356973.0,356231.243,3007907668.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,5,8,355808.0,355459.278,3017756273.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,5,9,357656.0,357325.227,3002163598.0,4066.711980202381,0
|
||||
viper_scalar_mul,16384,65536,5,10,366931.0,366687.331,2926277212.0,4066.711980202381,0
|
||||
viper_sqrt,16384,65536,1,1,425028.0,424330.444,2526284912.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,1,2,427121.0,426472.707,2513905483.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,1,3,427605.0,427335.289,2511060029.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,1,4,426766.0,426447.968,2515996644.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,1,5,423432.0,423205.654,2535806986.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,1,6,424913.0,424715.091,2526968635.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,1,7,429582.0,429018.828,2499503759.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,1,8,425844.0,425592.001,2521444059.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,1,9,429431.0,429085.1,2500382655.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,1,10,425315.0,424633.898,2524580191.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,2,1,431170.0,431211.057,2490298081.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,2,2,427120.0,427071.71,2513911369.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,2,3,428939.0,429145.433,2503250634.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,2,4,430751.0,430634.394,2492720444.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,2,5,430359.0,430341.719,2494990981.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,2,6,426254.0,426477.82,2519018763.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,2,7,428145.0,428224.602,2507892942.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,2,8,427578.0,427754.866,2511218594.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,2,9,431506.0,431211.455,2488358966.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,2,10,428079.0,428181.414,2508279602.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,3,1,436518.0,436193.844,2459788196.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,3,2,425882.0,425665.183,2521219079.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,3,3,422882.0,422769.72,2539105055.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,3,4,429338.0,428869.186,2500924269.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,3,5,427436.0,426953.229,2512052854.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,3,6,426659.0,426188.766,2516627620.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,3,7,424727.0,424291.042,2528075267.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,3,8,426575.0,426402.216,2517123188.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,3,9,424816.0,424608.996,2527545629.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,3,10,424396.0,424265.005,2530046993.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,4,1,423797.0,423758.64,2533622994.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,4,2,425260.0,424765.635,2524906701.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,4,3,426468.0,426478.622,2517754729.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,4,4,425961.0,425896.977,2520751486.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,4,5,425677.0,425656.603,2522433262.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,4,6,424640.0,424484.669,2528593217.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,4,7,429174.0,429240.796,2501879946.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,4,8,427652.0,427680.202,2510784058.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,4,9,427871.0,427820.081,2509498947.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,4,10,426151.0,426334.337,2519627606.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,5,1,429924.0,429841.982,2497515430.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,5,2,428081.0,427952.096,2508267883.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,5,3,428262.0,428372.476,2507207793.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,5,4,428447.0,428531.925,2506125201.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,5,5,431290.0,431517.217,2489605193.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,5,6,424007.0,424003.867,2532368154.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,5,7,427486.0,427707.381,2511759037.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,5,8,425877.0,425858.371,2521248679.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,5,9,427600.0,427924.535,2511089391.0,10892.477135620607,0
|
||||
viper_sqrt,16384,65536,5,10,428598.0,428848.18,2505242264.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,1,1,1655631.0,1654619.128,648539332.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,1,2,1658031.0,1656352.761,647600572.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,1,3,1656603.0,1655110.675,648158806.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,1,4,1659375.0,1658058.469,647076052.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,1,5,1662711.0,1661472.467,645777783.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,1,6,1656631.0,1655375.87,648147851.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,1,7,1660383.0,1658912.123,646683219.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,1,8,1661788.0,1658717.285,646136465.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,1,9,1713700.0,1696208.933,626563473.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,1,10,1763374.0,1698646.45,608913267.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,2,1,1718963.0,1693533.009,624645105.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,2,2,1713289.0,1695718.035,626713779.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,2,3,1702424.0,1690845.428,630713514.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,2,4,1697342.0,1687038.151,632601929.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,2,5,1704120.0,1691993.752,630085806.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,2,6,1724370.0,1704244.485,622686444.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,2,7,1704089.0,1694119.776,630097268.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,2,8,1706390.0,1689589.393,629247606.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,2,9,1711230.0,1695934.762,627467858.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,2,10,1703510.0,1692170.526,630311429.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,3,1,1699962.0,1688656.831,631626956.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,3,2,1696688.0,1687812.747,632845770.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,3,3,1720098.0,1694616.263,624232935.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,3,4,1711634.0,1697160.884,627319756.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,3,5,1704049.0,1691141.872,630112058.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,3,6,1694452.0,1685652.737,633680873.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,3,7,1713822.0,1694562.496,626518870.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,3,8,1701165.0,1690239.127,631180293.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,3,9,1756291.0,1707665.744,611368972.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,3,10,1735251.0,1702392.178,618781850.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,4,1,1697523.0,1687134.336,632534477.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,4,2,1704722.0,1693269.863,629863299.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,4,3,1710285.0,1691626.815,627814559.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,4,4,1708731.0,1693046.29,628385523.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,4,5,1706060.0,1690136.175,629369321.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,4,6,1703215.0,1689474.049,630420601.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,4,7,1703178.0,1689257.884,630434296.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,4,8,1704186.0,1687758.602,630061404.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,4,9,1707757.0,1691445.892,628743916.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,4,10,1693773.0,1686211.69,633934903.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,5,1,1708980.0,1702600.927,628293967.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,5,2,1702417.0,1696588.546,630716107.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,5,3,1693243.0,1685588.444,634133331.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,5,4,1750755.0,1699780.201,613302160.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,5,5,1692381.0,1687762.847,634456321.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,5,6,1693507.0,1687649.465,634034476.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,5,7,1731661.0,1697375.229,620064680.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,5,8,1697333.0,1689334.983,632605283.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,5,9,1700358.0,1691853.426,631479855.0,10892.477135620607,0
|
||||
viper_scalar_sqrt,16384,65536,5,10,1692731.0,1685667.88,634325137.0,10892.477135620607,0
|
||||
c,16384,65536,1,1,154883.0,154730.847,13865199202.0,12302.997770370706,0
|
||||
c,16384,65536,1,2,155007.0,154838.341,13854107544.0,12302.997770370706,0
|
||||
c,16384,65536,1,3,155372.7,155186.839,13821499195.0,12302.997770370706,0
|
||||
c,16384,65536,1,4,154989.6,154673.703,13855662883.0,12302.997770370706,0
|
||||
c,16384,65536,1,5,155094.6,154956.344,13846282514.0,12302.997770370706,0
|
||||
c,16384,65536,1,6,155407.6,155019.997,13818395291.0,12302.997770370706,0
|
||||
c,16384,65536,1,7,155398.4,154943.729,13819213377.0,12302.997770370706,0
|
||||
c,16384,65536,1,8,155279.7,155157.013,13829777157.0,12302.997770370706,0
|
||||
c,16384,65536,1,9,155415.4,155014.291,13817701772.0,12302.997770370706,0
|
||||
c,16384,65536,1,10,155197.8,154892.189,13837075319.0,12302.997770370706,0
|
||||
c,16384,65536,2,1,158394.5,157979.307,13557817020.0,12302.997770370706,0
|
||||
c,16384,65536,2,2,157655.3,157314.315,13621385694.0,12302.997770370706,0
|
||||
c,16384,65536,2,3,157179.3,156906.155,13662636543.0,12302.997770370706,0
|
||||
c,16384,65536,2,4,156842.4,156542.129,13691984106.0,12302.997770370706,0
|
||||
c,16384,65536,2,5,156702.3,156505.795,13704225452.0,12302.997770370706,0
|
||||
c,16384,65536,2,6,156690.3,156591.978,13705274979.0,12302.997770370706,0
|
||||
c,16384,65536,2,7,156801.4,156626.302,13695564249.0,12302.997770370706,0
|
||||
c,16384,65536,2,8,156986.8,156879.026,13679389910.0,12302.997770370706,0
|
||||
c,16384,65536,2,9,156792.2,156598.682,13696367855.0,12302.997770370706,0
|
||||
c,16384,65536,2,10,156490.0,156323.334,13722817100.0,12302.997770370706,0
|
||||
c,16384,65536,3,1,157722.5,157165.97,13615582101.0,12302.997770370706,0
|
||||
c,16384,65536,3,2,157544.2,157208.509,13630991480.0,12302.997770370706,0
|
||||
c,16384,65536,3,3,157305.9,157094.106,13651640835.0,12302.997770370706,0
|
||||
c,16384,65536,3,4,157210.9,157031.851,13659890300.0,12302.997770370706,0
|
||||
c,16384,65536,3,5,157469.2,157001.421,13637483698.0,12302.997770370706,0
|
||||
c,16384,65536,3,6,157350.8,157097.809,13647745344.0,12302.997770370706,0
|
||||
c,16384,65536,3,7,157387.1,157079.391,13644597607.0,12302.997770370706,0
|
||||
c,16384,65536,3,8,157387.5,157113.121,13644562929.0,12302.997770370706,0
|
||||
c,16384,65536,3,9,157857.6,157656.213,13603929415.0,12302.997770370706,0
|
||||
c,16384,65536,3,10,157292.5,157135.343,13652803840.0,12302.997770370706,0
|
||||
c,16384,65536,4,1,154727.9,154781.52,13879097745.0,12302.997770370706,0
|
||||
c,16384,65536,4,2,154657.9,154719.489,13885379589.0,12302.997770370706,0
|
||||
c,16384,65536,4,3,154835.6,154802.661,13869443771.0,12302.997770370706,0
|
||||
c,16384,65536,4,4,154940.1,154952.269,13860089467.0,12302.997770370706,0
|
||||
c,16384,65536,4,5,155313.5,154958.385,13826767461.0,12302.997770370706,0
|
||||
c,16384,65536,4,6,154979.7,155037.745,13856547974.0,12302.997770370706,0
|
||||
c,16384,65536,4,7,155172.4,155037.946,13839340295.0,12302.997770370706,0
|
||||
c,16384,65536,4,8,155640.9,155256.596,13797682023.0,12302.997770370706,0
|
||||
c,16384,65536,4,9,155357.2,155204.384,13822878167.0,12302.997770370706,0
|
||||
c,16384,65536,4,10,155475.0,155440.168,13812404875.0,12302.997770370706,0
|
||||
c,16384,65536,5,1,155419.7,155201.92,13817319478.0,12302.997770370706,0
|
||||
c,16384,65536,5,2,155936.3,155544.484,13771544201.0,12302.997770370706,0
|
||||
c,16384,65536,5,3,156049.5,155703.048,13761554174.0,12302.997770370706,0
|
||||
c,16384,65536,5,4,156140.5,155835.183,13753533824.0,12302.997770370706,0
|
||||
c,16384,65536,5,5,155708.9,155545.786,13791656405.0,12302.997770370706,0
|
||||
c,16384,65536,5,6,155744.0,155670.715,13788548182.0,12302.997770370706,0
|
||||
c,16384,65536,5,7,155851.6,155737.083,13779028563.0,12302.997770370706,0
|
||||
c,16384,65536,5,8,155443.7,155236.363,13815186128.0,12302.997770370706,0
|
||||
c,16384,65536,5,9,155727.2,155499.534,13790035703.0,12302.997770370706,0
|
||||
c,16384,65536,5,10,155294.8,155160.084,13828432427.0,12302.997770370706,0
|
||||
cpp,16384,65536,1,1,138717.4,138626.65,15480996962.0,12302.997770370706,0
|
||||
cpp,16384,65536,1,2,139222.2,138830.007,15424865057.0,12302.997770370706,0
|
||||
cpp,16384,65536,1,3,139166.4,138961.208,15431049794.0,12302.997770370706,0
|
||||
cpp,16384,65536,1,4,139297.3,138955.51,15416548978.0,12302.997770370706,0
|
||||
cpp,16384,65536,1,5,138977.6,138752.649,15452012756.0,12302.997770370706,0
|
||||
cpp,16384,65536,1,6,138929.1,138821.298,15457407037.0,12302.997770370706,0
|
||||
cpp,16384,65536,1,7,138930.6,138754.149,15457240147.0,12302.997770370706,0
|
||||
cpp,16384,65536,1,8,138718.3,138601.43,15480896522.0,12302.997770370706,0
|
||||
cpp,16384,65536,1,9,139134.5,138900.263,15434587741.0,12302.997770370706,0
|
||||
cpp,16384,65536,1,10,139139.6,138768.962,15434022004.0,12302.997770370706,0
|
||||
cpp,16384,65536,2,1,158205.2,157630.556,13574039589.0,12302.997770370706,0
|
||||
cpp,16384,65536,2,2,156628.3,156414.343,13710700097.0,12302.997770370706,0
|
||||
cpp,16384,65536,2,3,156578.3,156442.568,13715078322.0,12302.997770370706,0
|
||||
cpp,16384,65536,2,4,156910.1,156651.938,13686076601.0,12302.997770370706,0
|
||||
cpp,16384,65536,2,5,156900.6,156351.79,13686905264.0,12302.997770370706,0
|
||||
cpp,16384,65536,2,6,156532.0,156378.712,13719135052.0,12302.997770370706,0
|
||||
cpp,16384,65536,2,7,157730.2,157399.765,13614917422.0,12302.997770370706,0
|
||||
cpp,16384,65536,2,8,157131.6,156873.829,13666784071.0,12302.997770370706,0
|
||||
cpp,16384,65536,2,9,156531.6,156365.7,13719170110.0,12302.997770370706,0
|
||||
cpp,16384,65536,2,10,157083.2,156744.417,13670995040.0,12302.997770370706,0
|
||||
cpp,16384,65536,3,1,156659.7,156460.152,13707952000.0,12302.997770370706,0
|
||||
cpp,16384,65536,3,2,156986.7,156762.278,13679398624.0,12302.997770370706,0
|
||||
cpp,16384,65536,3,3,156831.3,156657.368,13692953180.0,12302.997770370706,0
|
||||
cpp,16384,65536,3,4,157028.2,156659.865,13675783382.0,12302.997770370706,0
|
||||
cpp,16384,65536,3,5,157476.3,157072.01,13636868837.0,12302.997770370706,0
|
||||
cpp,16384,65536,3,6,157466.7,157340.706,13637700212.0,12302.997770370706,0
|
||||
cpp,16384,65536,3,7,156628.2,156474.67,13710708851.0,12302.997770370706,0
|
||||
cpp,16384,65536,3,8,157626.0,157450.418,13623917679.0,12302.997770370706,0
|
||||
cpp,16384,65536,3,9,159188.4,159129.427,13490201849.0,12302.997770370706,0
|
||||
cpp,16384,65536,3,10,157300.8,157017.155,13652083448.0,12302.997770370706,0
|
||||
cpp,16384,65536,4,1,156636.2,156453.035,13710008593.0,12302.997770370706,0
|
||||
cpp,16384,65536,4,2,156593.9,156362.739,13713712016.0,12302.997770370706,0
|
||||
cpp,16384,65536,4,3,156260.0,156211.582,13743015794.0,12302.997770370706,0
|
||||
cpp,16384,65536,4,4,156724.7,156417.399,13702266765.0,12302.997770370706,0
|
||||
cpp,16384,65536,4,5,157160.6,156995.603,13664262213.0,12302.997770370706,0
|
||||
cpp,16384,65536,4,6,157060.9,156730.524,13672936090.0,12302.997770370706,0
|
||||
cpp,16384,65536,4,7,156791.6,156389.665,13696420267.0,12302.997770370706,0
|
||||
cpp,16384,65536,4,8,156280.6,156207.579,13741204270.0,12302.997770370706,0
|
||||
cpp,16384,65536,4,9,156386.3,156279.652,13731916722.0,12302.997770370706,0
|
||||
cpp,16384,65536,4,10,156405.3,156273.244,13730248579.0,12302.997770370706,0
|
||||
cpp,16384,65536,5,1,156185.6,156207.551,13749562367.0,12302.997770370706,0
|
||||
cpp,16384,65536,5,2,156640.7,156627.191,13709614730.0,12302.997770370706,0
|
||||
cpp,16384,65536,5,3,157866.9,157903.737,13603128002.0,12302.997770370706,0
|
||||
cpp,16384,65536,5,4,159420.7,158834.856,13470544591.0,12302.997770370706,0
|
||||
cpp,16384,65536,5,5,159041.3,158919.89,13502679166.0,12302.997770370706,0
|
||||
cpp,16384,65536,5,6,158517.5,158523.28,13547296974.0,12302.997770370706,0
|
||||
cpp,16384,65536,5,7,159832.6,159657.408,13435830037.0,12302.997770370706,0
|
||||
cpp,16384,65536,5,8,157943.1,157999.382,13596565143.0,12302.997770370706,0
|
||||
cpp,16384,65536,5,9,157776.6,157821.813,13610913456.0,12302.997770370706,0
|
||||
cpp,16384,65536,5,10,159092.1,158900.654,13498367600.0,12302.997770370706,0
|
||||
py,16384,65536,1,1,270105.5,269361.231,7950536542.0,12302.997770370706,0
|
||||
py,16384,65536,1,2,295817.2,290114.402,7259495553.0,12302.997770370706,0
|
||||
py,16384,65536,1,3,285267.1,281948.777,7527975178.0,12302.997770370706,0
|
||||
py,16384,65536,1,4,321491.9,310261.649,6679744180.0,12302.997770370706,0
|
||||
py,16384,65536,1,5,310146.1,300939.485,6924103344.0,12302.997770370706,0
|
||||
py,16384,65536,1,6,273217.4,271444.688,7859981275.0,12302.997770370706,0
|
||||
py,16384,65536,1,7,279803.7,276569.728,7674965156.0,12302.997770370706,0
|
||||
py,16384,65536,1,8,292109.6,289088.693,7351636673.0,12302.997770370706,0
|
||||
py,16384,65536,1,9,317042.4,311412.462,6773490385.0,12302.997770370706,0
|
||||
py,16384,65536,1,10,306348.6,301511.363,7009934591.0,12302.997770370706,0
|
||||
py,16384,65536,2,1,361007.0,359262.468,5948592820.0,12302.997770370706,0
|
||||
py,16384,65536,2,2,357231.8,354701.665,6011457121.0,12302.997770370706,0
|
||||
py,16384,65536,2,3,357132.4,356153.576,6013130279.0,12302.997770370706,0
|
||||
py,16384,65536,2,4,358224.7,355164.979,5994795021.0,12302.997770370706,0
|
||||
py,16384,65536,2,5,355772.7,354893.818,6036111394.0,12302.997770370706,0
|
||||
py,16384,65536,2,6,363391.7,361530.615,5909556129.0,12302.997770370706,0
|
||||
py,16384,65536,2,7,388684.6,381123.086,5525003172.0,12302.997770370706,0
|
||||
py,16384,65536,2,8,374073.9,371471.274,5740800542.0,12302.997770370706,0
|
||||
py,16384,65536,2,9,366974.1,364032.281,5851867061.0,12302.997770370706,0
|
||||
py,16384,65536,2,10,357846.2,355560.993,6001135818.0,12302.997770370706,0
|
||||
py,16384,65536,3,1,321663.7,319550.204,6676176541.0,12302.997770370706,0
|
||||
py,16384,65536,3,2,318841.9,316381.417,6735261733.0,12302.997770370706,0
|
||||
py,16384,65536,3,3,323303.2,320643.038,6642321041.0,12302.997770370706,0
|
||||
py,16384,65536,3,4,324448.0,321376.45,6618883914.0,12302.997770370706,0
|
||||
py,16384,65536,3,5,319115.2,317033.011,6729493449.0,12302.997770370706,0
|
||||
py,16384,65536,3,6,314154.5,312958.115,6835756445.0,12302.997770370706,0
|
||||
py,16384,65536,3,7,322727.7,320772.978,6654165874.0,12302.997770370706,0
|
||||
py,16384,65536,3,8,329358.0,326390.385,6520210980.0,12302.997770370706,0
|
||||
py,16384,65536,3,9,333096.3,329451.163,6447035431.0,12302.997770370706,0
|
||||
py,16384,65536,3,10,328863.8,326227.135,6530009226.0,12302.997770370706,0
|
||||
py,16384,65536,4,1,327686.8,325583.102,6553464003.0,12302.997770370706,0
|
||||
py,16384,65536,4,2,325331.9,323938.468,6600900951.0,12302.997770370706,0
|
||||
py,16384,65536,4,3,325181.0,323931.907,6603964094.0,12302.997770370706,0
|
||||
py,16384,65536,4,4,333083.1,329616.866,6447290925.0,12302.997770370706,0
|
||||
py,16384,65536,4,5,325646.0,324420.937,6594534089.0,12302.997770370706,0
|
||||
py,16384,65536,4,6,325615.9,324278.778,6595143689.0,12302.997770370706,0
|
||||
py,16384,65536,4,7,334437.1,331441.539,6421188463.0,12302.997770370706,0
|
||||
py,16384,65536,4,8,343412.0,340132.387,6253373930.0,12302.997770370706,0
|
||||
py,16384,65536,4,9,345214.6,342299.681,6220720815.0,12302.997770370706,0
|
||||
py,16384,65536,4,10,345490.2,342047.31,6215758502.0,12302.997770370706,0
|
||||
py,16384,65536,5,1,338754.7,338363.705,6339347168.0,12302.997770370706,0
|
||||
py,16384,65536,5,2,340203.8,339426.299,6312344683.0,12302.997770370706,0
|
||||
py,16384,65536,5,3,341557.2,339784.672,6287332394.0,12302.997770370706,0
|
||||
py,16384,65536,5,4,343866.6,341744.945,6245106818.0,12302.997770370706,0
|
||||
py,16384,65536,5,5,340456.4,338980.724,6307661269.0,12302.997770370706,0
|
||||
py,16384,65536,5,6,340737.7,339536.402,6302453905.0,12302.997770370706,0
|
||||
py,16384,65536,5,7,351876.3,349691.551,6102950520.0,12302.997770370706,0
|
||||
py,16384,65536,5,8,356357.3,353807.901,6026209223.0,12302.997770370706,0
|
||||
py,16384,65536,5,9,363895.8,357476.377,5901369700.0,12302.997770370706,0
|
||||
py,16384,65536,5,10,361117.0,355299.912,5946780816.0,12302.997770370706,0
|
||||
|
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 94 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 69 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 65 KiB |
@@ -0,0 +1,279 @@
|
||||
/*
|
||||
* bench_c.c - 手写 C 基准实现 (进程内多次 run, 单进程输出多行)
|
||||
*
|
||||
* 内核(与 App/main.vp / bench_cpp.cpp / bench_py.py 一致):
|
||||
* out[i] = a[i] * b[i] + c[i] i = 0 .. LEN-1, LEN = 16384 (double)
|
||||
* 每次计时 run 连续执行 ITERS = 65536 趟内核, 单趟 2 次浮点运算/元素,
|
||||
* 即每次 run 总 flops = LEN*ITERS*2 ≈ 2.147e9。
|
||||
*
|
||||
* 命令行: bench_c.exe [runs]
|
||||
* runs: 默认 1; 0 = 仅做热身(不输出 RESULT); 建议 10(由 run_bench.py 传入)。
|
||||
*
|
||||
* 流程:
|
||||
* 0) 入口先 SetThreadAffinityMask 绑到首个可用逻辑核(消除跨核迁移 / 大小核
|
||||
* 异构 / 缓存失效抖动), 并以 AFFINITY <core> 行回报(-1 = 绑定失败, 不致命);
|
||||
* 1) 进程开头打印一次 CFG <len> <iters>;
|
||||
* 2) 分配 a/b/c/out 并用固定种子 LCG 填充 a/b/c(不计时);
|
||||
* 3) 2 轮"满负荷热身"(每轮 = ITERS 趟内核 + 升序读回 out 求和), 之后再计时;
|
||||
* 4) 连续计时 runs 次, 每次输出一行:
|
||||
* RESULT <wall_us> <cpu_us> <flops>
|
||||
* wall_us = QPC 墙钟微秒(含被抢占 / 被降频的时间, 对调度敏感);
|
||||
* cpu_us = QueryThreadCycleTime 线程 CPU 微秒(只计本线程真正执行过的
|
||||
* 周期, 对抢占 / 降频不敏感; TSC 频率在入口用 QPC 标定);
|
||||
* flops = (LEN*ITERS*2)/秒, 按墙钟秒换算(与其它实现同口径);
|
||||
* 计时只包住 ITERS 趟内核本身(不含分配/填充/热身/读回校验);
|
||||
* 5) 全部结束后输出 CHECK <sum>(用最后一次 run 留下的 out 升序累加)。
|
||||
*
|
||||
* 防优化器删除 / 别名约束:
|
||||
* - 所有指针一律加 __restrict(与 bench_cpp.cpp 同一拼写, clang/GCC/MSVC 都认):
|
||||
* out 与 a/b/c 互不重叠, 关掉"保守别名分析"这个变量, 让 C 的自动向量化
|
||||
* 行为与 C++ 收敛(旧版 C 写 C99 的 restrict、C++ 写 __restrict, 两份源码
|
||||
* 拼写不一致, 容易把差异误判成语言差异);
|
||||
* - 编译用 -O2 -mavx2 -mfma -ffp-contract=fast: 允许 FMA 收缩,
|
||||
* out[i]=a[i]*b[i]+c[i] 生成单趟 vfmadd(单次舍入), 与 Viper 的
|
||||
* simd_fma_array 同口径(不再强制"先乘后加"两次舍入);
|
||||
* - 每轮热身与每次 run 之后都升序读回 out 求校验和 -> 计时区内的每一条
|
||||
* out 写都"可观察", 不可能被整趟折叠/删除; noinline 防止内核被内联后
|
||||
* 与循环展开/标签冲突并杜绝跨趟优化。
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
|
||||
/* QueryThreadCycleTime 是 Vista+ API: 显式抬到 0x0601 让 mingw 头文件声明它 */
|
||||
#ifndef _WIN32_WINNT
|
||||
#define _WIN32_WINNT 0x0601
|
||||
#endif
|
||||
#include <windows.h>
|
||||
|
||||
/* 负载约束: LEN × ITERS × 2 恒为 ≈2.147e9 flops/run(与其它实现可比),
|
||||
* 只调 LEN 来改"工作集"大小。
|
||||
* LEN=16384 -> a/b/c/out 四份共 512 KiB, 稳居本机(AMD Ryzen AI 7 H 350)
|
||||
* 每核 1 MiB 私有 L2 之内: 访存不再打到共享 L3 / DRAM, 也就不会被其它进程
|
||||
* 抢带宽 —— 这正是之前 C++ 出现"零星高时延尖峰"的主因。
|
||||
* 再往下压到 L1(需 LEN<=1024)会让每趟的内核调用/派发开销占比飙升,
|
||||
* 对 Python+numpy 尤其致命(实测每趟固定开销约 0.4us)。 */
|
||||
#define LEN 16384 /* 元素数(2^14, 4 的倍数, 满足 ymm 4-lane 分批) */
|
||||
#define ITERS 65536 /* 每 run 的内核趟数(令 LEN*ITERS*2 仍 ≈2.147e9) */
|
||||
#define CFG_RUNS_MAX 200 /* runs 上限(防止误传超大值) */
|
||||
|
||||
#define LCG_A 48271.0
|
||||
#define LCG_M 2147483647.0
|
||||
#define LCG_SEED 12345.0
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#define NOINLINE __declspec(noinline)
|
||||
#else
|
||||
#define NOINLINE __attribute__((noinline))
|
||||
#endif
|
||||
|
||||
/* ------------------------------------------------------------
|
||||
* 线程绑定: 把当前线程绑到"第一个可用逻辑核", 返回该 CPU 序号; 失败返回 -1。
|
||||
* 从 1<<0 逐个试, 第一个被系统接受的位就是进程亲和掩码里编号最小的核 ——
|
||||
* 同一台机器上四个实现会绑到同一个核, 横向对比才有意义。
|
||||
* ------------------------------------------------------------ */
|
||||
static int bind_first_available_core(void) {
|
||||
int i;
|
||||
const int nbits = (int)(sizeof(DWORD_PTR) * 8);
|
||||
for (i = 0; i < nbits; ++i) {
|
||||
if (SetThreadAffinityMask(GetCurrentThread(), (DWORD_PTR)1 << i) != 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------
|
||||
* 线程 CPU 时间: QueryThreadCycleTime 返回本线程真正消耗过的 CPU 周期数
|
||||
* (现代 x86 的 TSC 是 invariant 的, 与核心实际频率无关)。
|
||||
*
|
||||
* 为什么不用 GetThreadTimes: 它的记账粒度跟随调度时钟节拍, 本机实测硬地板
|
||||
* 15.625ms —— 一个几十毫秒的 run 只能被记成 2~3 个节拍, 误差 ±40%,
|
||||
* 连"CPU 时间 > 墙钟时间"这种物理上不可能的结果都会出现。
|
||||
* 且 timeBeginPeriod(1) 在 Win10 2004+ 已无法改善该粒度(实测确认无效)。
|
||||
* QueryThreadCycleTime 的粒度是周期级, 彻底解决这个问题。
|
||||
* ------------------------------------------------------------ */
|
||||
static long long query_thread_cycles(void) {
|
||||
ULONG64 cycles = 0;
|
||||
QueryThreadCycleTime(GetCurrentThread(), &cycles);
|
||||
return (long long)cycles;
|
||||
}
|
||||
|
||||
/* 用 QPC 标定 TSC 频率(周期/秒): 忙等 30ms 数周期, 重复 3 次取最大。返回 0 = 失败。
|
||||
*
|
||||
* 为什么要取最大而不是平均: 标定窗口内本线程一旦被抢占, 消耗的周期数就少于
|
||||
* 墙钟经过的时间, 算出 hz 偏小 -> 后续 cpu_us 系统性偏大(实测约 +0.13%,
|
||||
* 会表现为"cpu_us 偶尔比 wall_us 还大"这种看着像 bug 的伪影)。取三次里的
|
||||
* 最大值 = 挑"最没被打断"的那次, 把该偏置压掉。
|
||||
* 必须在 SetThreadAffinityMask 之后调用, 否则跨核的 TSC 可能不同步。 */
|
||||
static double calibrate_tsc_hz(LARGE_INTEGER freq) {
|
||||
double best = 0.0;
|
||||
int t;
|
||||
for (t = 0; t < 3; ++t) {
|
||||
LARGE_INTEGER a, b;
|
||||
long long c0, c1;
|
||||
double span = (double)freq.QuadPart * 0.030; /* 30ms 对应的 QPC 计次 */
|
||||
double dt, hz;
|
||||
c0 = query_thread_cycles();
|
||||
QueryPerformanceCounter(&a);
|
||||
do {
|
||||
QueryPerformanceCounter(&b);
|
||||
} while ((double)(b.QuadPart - a.QuadPart) < span);
|
||||
c1 = query_thread_cycles();
|
||||
dt = (double)(b.QuadPart - a.QuadPart);
|
||||
if (dt <= 0.0 || c1 <= c0) {
|
||||
continue;
|
||||
}
|
||||
hz = (double)(c1 - c0) * (double)freq.QuadPart / dt;
|
||||
if (hz > best) {
|
||||
best = hz;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
/* 填充 a/b/c: 逐元素依次消耗同一个 LCG 流(a[i], b[i], c[i]) */
|
||||
static void fill_abc(double *__restrict a,
|
||||
double *__restrict b,
|
||||
double *__restrict c,
|
||||
long n) {
|
||||
double state = LCG_SEED;
|
||||
const double m = LCG_M;
|
||||
const double mul = LCG_A;
|
||||
long i;
|
||||
for (i = 0; i < n; ++i) {
|
||||
state = fmod(state * mul, m);
|
||||
a[i] = state / m;
|
||||
state = fmod(state * mul, m);
|
||||
b[i] = state / m;
|
||||
state = fmod(state * mul, m);
|
||||
c[i] = state / m;
|
||||
}
|
||||
}
|
||||
|
||||
/* 内核: out[i] = a[i]*b[i] + c[i] (元素间独立, -O2 + AVX2 + FMA 自动向量化单趟融合乘加)
|
||||
* __restrict: 四个指针互不别名, 编译器无需为"可能重叠"生成保守标量路径。 */
|
||||
static NOINLINE void kernel_fma(double *__restrict out,
|
||||
const double *__restrict a,
|
||||
const double *__restrict b,
|
||||
const double *__restrict c,
|
||||
long n) {
|
||||
long i;
|
||||
for (i = 0; i < n; ++i) {
|
||||
out[i] = a[i] * b[i] + c[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* 校验和: 升序逐元素累加 out(读回 out -> 保证内核的写未被删除) */
|
||||
static double checksum_sum(const double *__restrict out, long n) {
|
||||
double s = 0.0;
|
||||
long i;
|
||||
for (i = 0; i < n; ++i) {
|
||||
s += out[i];
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
/* 命令行: bench_c.exe [runs] [len] [iters]
|
||||
* 省略则用上面 #define 的默认值; run_bench.py 做多尺寸扫描时会显式传入。 */
|
||||
long n = LEN;
|
||||
long iters = ITERS;
|
||||
size_t nb;
|
||||
double total_flops;
|
||||
/* 四个缓冲各自独立, 指针全部 __restrict: 与内核形参同一 no-alias 承诺 */
|
||||
double *__restrict a, *__restrict b, *__restrict c, *__restrict out;
|
||||
LARGE_INTEGER freq, t0, t1;
|
||||
double last_chk = 0.0;
|
||||
double tsc_hz;
|
||||
int runs = 1;
|
||||
int r;
|
||||
int core;
|
||||
long k;
|
||||
|
||||
if (argc > 1) {
|
||||
runs = atoi(argv[1]);
|
||||
}
|
||||
if (argc > 2) {
|
||||
n = atol(argv[2]);
|
||||
}
|
||||
if (argc > 3) {
|
||||
iters = atol(argv[3]);
|
||||
}
|
||||
if (runs < 0) runs = 0;
|
||||
if (runs > CFG_RUNS_MAX) runs = CFG_RUNS_MAX;
|
||||
/* 内核按 4-lane ymm 分批, LEN 必须是 4 的倍数; 参数非法直接报错, 不静默修正 */
|
||||
if (n < 4 || n % 4 != 0 || iters < 1) {
|
||||
fprintf(stderr, "bad args: len=%ld(需 >=4 且为 4 的倍数) iters=%ld\n", n, iters);
|
||||
return 2;
|
||||
}
|
||||
nb = (size_t)n * sizeof(double);
|
||||
total_flops = (double)n * (double)iters * 2.0;
|
||||
|
||||
/* 线程绑定(必做) + 提优先级: 消除跨核迁移, 并压低背景进程抢占造成的零星尖峰 */
|
||||
core = bind_first_available_core();
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
|
||||
|
||||
printf("CFG %ld %ld\n", n, iters);
|
||||
printf("AFFINITY %d\n", core);
|
||||
fflush(stdout);
|
||||
|
||||
a = (double *)malloc(nb);
|
||||
b = (double *)malloc(nb);
|
||||
c = (double *)malloc(nb);
|
||||
out = (double *)malloc(nb);
|
||||
if (!a || !b || !c || !out) {
|
||||
fprintf(stderr, "malloc failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 输入生成一次(不计时) */
|
||||
fill_abc(a, b, c, n);
|
||||
|
||||
QueryPerformanceFrequency(&freq);
|
||||
|
||||
/* TSC 频率标定(必须在绑核之后): 把 QueryThreadCycleTime 的周期数换算成微秒 */
|
||||
tsc_hz = calibrate_tsc_hz(freq);
|
||||
printf("TSC %.4e\n", tsc_hz);
|
||||
fflush(stdout);
|
||||
|
||||
/* 2 轮满负荷热身(每轮 = ITERS 趟内核 + 读回 out 求校验和) */
|
||||
for (r = 0; r < 2; ++r) {
|
||||
for (k = 0; k < iters; ++k) {
|
||||
kernel_fma(out, a, b, c, n);
|
||||
}
|
||||
last_chk = checksum_sum(out, n);
|
||||
}
|
||||
|
||||
/* 连续计时 runs 次; 计时只包住 ITERS 趟内核
|
||||
* 双计时: 墙钟 QPC + 线程 CPU(QueryThreadCycleTime), 同时包住同一段内核 */
|
||||
for (r = 0; r < runs; ++r) {
|
||||
double us, cpu_us, flops;
|
||||
long long cy0, cy1;
|
||||
cy0 = query_thread_cycles();
|
||||
QueryPerformanceCounter(&t0);
|
||||
for (k = 0; k < iters; ++k) {
|
||||
kernel_fma(out, a, b, c, n);
|
||||
}
|
||||
QueryPerformanceCounter(&t1);
|
||||
cy1 = query_thread_cycles();
|
||||
us = ((double)(t1.QuadPart - t0.QuadPart) * 1000000.0) /
|
||||
(double)freq.QuadPart;
|
||||
/* 周期 -> 微秒; 标定失败(tsc_hz==0)时给 0, 表现为一眼可见的异常 */
|
||||
cpu_us = (tsc_hz > 0.0) ? (double)(cy1 - cy0) * 1000000.0 / tsc_hz : 0.0;
|
||||
flops = total_flops / (us * 1e-6);
|
||||
printf("RESULT %.3f %.3f %.0f\n", us, cpu_us, flops);
|
||||
fflush(stdout);
|
||||
/* 计时区外读回 out: 使本轮写入可观察, 阻止跨 run 的写折叠 */
|
||||
last_chk = checksum_sum(out, n);
|
||||
}
|
||||
|
||||
printf("CHECK %.17g\n", last_chk);
|
||||
fflush(stdout);
|
||||
|
||||
free(a);
|
||||
free(b);
|
||||
free(c);
|
||||
free(out);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* bench_cpp.cpp - 手写 C++ 基准实现 (进程内多次 run, 单进程输出多行)
|
||||
*
|
||||
* 与 bench_c.c 完全同口径:
|
||||
* 内核 out[i] = a[i]*b[i] + c[i], LEN = 16384, 每次 run = ITERS = 65536 趟,
|
||||
* 总 flops ≈ 2.147e9; 命令行第 1 参数 = runs(默认 1; 0 = 仅热身)。
|
||||
*
|
||||
* - 容器 std::vector; 内核经裸指针 + __restrict 传入, 便于自动向量化。
|
||||
* - 线程绑定: 入口 SetThreadAffinityMask 绑到首个可用逻辑核, 以 AFFINITY <core>
|
||||
* 行回报(-1 = 绑定失败)。与 bench_c.c / main.vp / bench_py.py 绑同一个核。
|
||||
* - 双计时: 每次 run 同时采集 QPC 墙钟微秒 与 QueryThreadCycleTime 线程 CPU
|
||||
* 微秒, 输出 RESULT <wall_us> <cpu_us> <flops>(flops 按墙钟秒换算)。
|
||||
* - 随机输入与 CHECK 口径与 C/Viper/Python 完全一致(固定种子 minstd LCG)。
|
||||
* - 编译口径与 bench_c.c 相同: -O2 -mavx2 -mfma -ffp-contract=fast,
|
||||
* 允许 FMA 收缩成单趟 vfmadd(单次舍入), 与 Viper 的 simd_fma_array 对齐。
|
||||
* - 防删: 每轮热身/每次 run 后升序读回 out 求 CHECK; noinline; __restrict。
|
||||
*/
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
/* QueryThreadCycleTime 是 Vista+ API: 显式抬到 0x0601 让 mingw 头文件声明它 */
|
||||
#ifndef _WIN32_WINNT
|
||||
#define _WIN32_WINNT 0x0601
|
||||
#endif
|
||||
#include <windows.h>
|
||||
|
||||
namespace {
|
||||
|
||||
/* 负载约束: N_ELEM × N_ITERS × 2 恒为 ≈2.147e9 flops/run(与其它实现可比),
|
||||
* 只调 N_ELEM 来改"工作集"大小。
|
||||
* N_ELEM=16384 -> a/b/c/out 四份共 512 KiB, 稳居本机每核 1 MiB 私有 L2 之内:
|
||||
* 访存不再打到共享 L3 / DRAM, 也就不会被其它进程抢带宽 —— 这正是之前 C++
|
||||
* 出现"零星高时延尖峰"的主因(与 std::vector/向量化无关, 计时区内核逐字相同)。 */
|
||||
constexpr long N_ELEM = 16384;
|
||||
constexpr long N_ITERS = 65536;
|
||||
constexpr int RUNS_MAX = 200;
|
||||
|
||||
constexpr double LCG_A = 48271.0;
|
||||
constexpr double LCG_M = 2147483647.0;
|
||||
constexpr double LCG_SEED = 12345.0;
|
||||
|
||||
/* ------------------------------------------------------------
|
||||
* 线程绑定: 绑到"第一个可用逻辑核", 返回 CPU 序号; 失败返回 -1。
|
||||
* 从 1<<0 逐个试, 第一个被接受的位就是进程亲和掩码里编号最小的核 ——
|
||||
* 四个实现绑到同一个核, 横向对比才有意义。
|
||||
* ------------------------------------------------------------ */
|
||||
int bind_first_available_core() {
|
||||
const int nbits = static_cast<int>(sizeof(DWORD_PTR) * 8);
|
||||
for (int i = 0; i < nbits; ++i) {
|
||||
if (SetThreadAffinityMask(GetCurrentThread(),
|
||||
static_cast<DWORD_PTR>(1) << i) != 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------
|
||||
* 线程 CPU 时间: QueryThreadCycleTime 返回本线程真正消耗过的 CPU 周期数
|
||||
* (现代 x86 的 TSC 是 invariant 的, 与核心实际频率无关)。
|
||||
*
|
||||
* 为什么不用 GetThreadTimes: 它的记账粒度跟随调度时钟节拍, 本机实测硬地板
|
||||
* 15.625ms —— 几十毫秒的 run 只能被记成 2~3 个节拍(误差 ±40%), 连
|
||||
* "CPU 时间 > 墙钟时间"这种物理上不可能的结果都会出现; 且
|
||||
* timeBeginPeriod(1) 在 Win10 2004+ 已无法改善该粒度(实测确认无效)。
|
||||
* ------------------------------------------------------------ */
|
||||
long long query_thread_cycles() {
|
||||
ULONG64 cycles = 0;
|
||||
QueryThreadCycleTime(GetCurrentThread(), &cycles);
|
||||
return static_cast<long long>(cycles);
|
||||
}
|
||||
|
||||
/* 用 QPC 标定 TSC 频率(周期/秒): 忙等 30ms 数周期, 重复 3 次取最大。0.0 = 失败。
|
||||
*
|
||||
* 为什么取最大: 标定窗口内本线程一旦被抢占, 消耗周期就少于墙钟流逝时间,
|
||||
* 算出 hz 偏小 -> 后续 cpu_us 系统性偏大(实测约 +0.13%, 表现为"cpu_us 偶尔
|
||||
* 比 wall_us 还大"这种看着像 bug 的伪影)。取三次最大值 = 挑最没被打断的一次。
|
||||
* 必须在 SetThreadAffinityMask 之后调用, 否则跨核的 TSC 可能不同步。 */
|
||||
double calibrate_tsc_hz(const LARGE_INTEGER &freq) {
|
||||
const double span = static_cast<double>(freq.QuadPart) * 0.030; /* 30ms 的 QPC 计次 */
|
||||
double best = 0.0;
|
||||
for (int t = 0; t < 3; ++t) {
|
||||
LARGE_INTEGER a{}, b{};
|
||||
const long long c0 = query_thread_cycles();
|
||||
QueryPerformanceCounter(&a);
|
||||
do {
|
||||
QueryPerformanceCounter(&b);
|
||||
} while (static_cast<double>(b.QuadPart - a.QuadPart) < span);
|
||||
const long long c1 = query_thread_cycles();
|
||||
const double dt = static_cast<double>(b.QuadPart - a.QuadPart);
|
||||
if (dt <= 0.0 || c1 <= c0) {
|
||||
continue;
|
||||
}
|
||||
const double hz =
|
||||
static_cast<double>(c1 - c0) * static_cast<double>(freq.QuadPart) / dt;
|
||||
if (hz > best) {
|
||||
best = hz;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#define NOINLINE __declspec(noinline)
|
||||
#else
|
||||
#define NOINLINE __attribute__((noinline))
|
||||
#endif
|
||||
|
||||
/* 填充 a/b/c: 逐元素依次消耗同一个 LCG 流(a[i], b[i], c[i]) */
|
||||
void fill_abc(double *a, double *b, double *c, long n) {
|
||||
double state = LCG_SEED;
|
||||
const double m = LCG_M;
|
||||
const double mul = LCG_A;
|
||||
for (long i = 0; i < n; ++i) {
|
||||
state = std::fmod(state * mul, m);
|
||||
a[i] = state / m;
|
||||
state = std::fmod(state * mul, m);
|
||||
b[i] = state / m;
|
||||
state = std::fmod(state * mul, m);
|
||||
c[i] = state / m;
|
||||
}
|
||||
}
|
||||
|
||||
/* 内核: out[i] = a[i]*b[i] + c[i] (元素间独立, -O2 + AVX2 + FMA 自动向量化单趟融合乘加) */
|
||||
static NOINLINE void kernel_fma(double *__restrict out,
|
||||
const double *__restrict a,
|
||||
const double *__restrict b,
|
||||
const double *__restrict c,
|
||||
long n) {
|
||||
for (long i = 0; i < n; ++i) {
|
||||
out[i] = a[i] * b[i] + c[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* 校验和: 升序逐元素累加 out(读回 out -> 保证内核的写未被删除) */
|
||||
double checksum_sum(const double *__restrict out, long n) {
|
||||
double s = 0.0;
|
||||
for (long i = 0; i < n; ++i) {
|
||||
s += out[i];
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
/* 命令行: bench_cpp.exe [runs] [len] [iters]
|
||||
* 省略则用上面 constexpr 的默认值; run_bench.py 多尺寸扫描时会显式传入。 */
|
||||
long n = N_ELEM;
|
||||
long iters = N_ITERS;
|
||||
int runs = 1;
|
||||
if (argc > 1) {
|
||||
runs = std::atoi(argv[1]);
|
||||
}
|
||||
if (argc > 2) {
|
||||
n = std::atol(argv[2]);
|
||||
}
|
||||
if (argc > 3) {
|
||||
iters = std::atol(argv[3]);
|
||||
}
|
||||
if (runs < 0) runs = 0;
|
||||
if (runs > RUNS_MAX) runs = RUNS_MAX;
|
||||
/* 内核按 4-lane ymm 分批, LEN 必须是 4 的倍数; 参数非法直接报错, 不静默修正 */
|
||||
if (n < 4 || n % 4 != 0 || iters < 1) {
|
||||
std::fprintf(stderr, "bad args: len=%ld(需 >=4 且为 4 的倍数) iters=%ld\n", n, iters);
|
||||
return 2;
|
||||
}
|
||||
const double total_flops = static_cast<double>(n) * static_cast<double>(iters) * 2.0;
|
||||
|
||||
/* 线程绑定(必做) + 提优先级: 消除跨核迁移, 并压低背景进程抢占造成的零星尖峰 */
|
||||
const int core = bind_first_available_core();
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
|
||||
|
||||
std::printf("CFG %ld %ld\n", n, iters);
|
||||
std::printf("AFFINITY %d\n", core);
|
||||
std::fflush(stdout);
|
||||
|
||||
std::vector<double> a(n), b(n), c(n), out(n);
|
||||
double *pa = a.data();
|
||||
double *pb = b.data();
|
||||
double *pc = c.data();
|
||||
double *pout = out.data();
|
||||
|
||||
LARGE_INTEGER freq, t0, t1;
|
||||
double last_chk = 0.0;
|
||||
|
||||
/* 输入生成一次(不计时) */
|
||||
fill_abc(pa, pb, pc, n);
|
||||
|
||||
QueryPerformanceFrequency(&freq);
|
||||
|
||||
/* TSC 频率标定(必须在绑核之后): 把 QueryThreadCycleTime 的周期数换算成微秒 */
|
||||
const double tsc_hz = calibrate_tsc_hz(freq);
|
||||
std::printf("TSC %.4e\n", tsc_hz);
|
||||
std::fflush(stdout);
|
||||
|
||||
/* 2 轮满负荷热身(每轮 = ITERS 趟内核 + 读回 out 求校验和) */
|
||||
for (int r = 0; r < 2; ++r) {
|
||||
for (long k = 0; k < iters; ++k) {
|
||||
kernel_fma(pout, pa, pb, pc, n);
|
||||
}
|
||||
last_chk = checksum_sum(pout, n);
|
||||
}
|
||||
|
||||
/* 连续计时 runs 次; 计时只包住 ITERS 趟内核
|
||||
* 双计时: 墙钟 QPC + 线程 CPU(QueryThreadCycleTime), 同时包住同一段内核 */
|
||||
for (int r = 0; r < runs; ++r) {
|
||||
const long long cy0 = query_thread_cycles();
|
||||
QueryPerformanceCounter(&t0);
|
||||
for (long k = 0; k < iters; ++k) {
|
||||
kernel_fma(pout, pa, pb, pc, n);
|
||||
}
|
||||
QueryPerformanceCounter(&t1);
|
||||
const long long cy1 = query_thread_cycles();
|
||||
|
||||
const double us =
|
||||
(static_cast<double>(t1.QuadPart - t0.QuadPart) * 1000000.0) /
|
||||
static_cast<double>(freq.QuadPart);
|
||||
/* 周期 -> 微秒; 标定失败(tsc_hz==0)时给 0, 表现为一眼可见的异常 */
|
||||
const double cpu_us = (tsc_hz > 0.0)
|
||||
? static_cast<double>(cy1 - cy0) * 1000000.0 / tsc_hz : 0.0;
|
||||
const double flops = total_flops / (us * 1e-6);
|
||||
std::printf("RESULT %.3f %.3f %.0f\n", us, cpu_us, flops);
|
||||
std::fflush(stdout);
|
||||
/* 计时区外读回 out: 阻止跨 run 的写折叠 */
|
||||
last_chk = checksum_sum(pout, n);
|
||||
}
|
||||
|
||||
std::printf("CHECK %.17g\n", last_chk);
|
||||
std::fflush(stdout);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
bench_py.py - Python 基准实现 (进程内多次 run, 单进程输出多行)
|
||||
|
||||
内核(与 App/main.vp / bench_c.c / bench_cpp.cpp 一致):
|
||||
out[i] = a[i] * b[i] + c[i] LEN = 16384, 每次 run = ITERS = 65536 趟
|
||||
每次 run 总 flops = LEN*ITERS*2 ≈ 2.147e9, 与其它实现同规模可比。
|
||||
|
||||
用法: python bench_py.py [runs] # runs 默认 1; 0 = 仅热身
|
||||
|
||||
模式(由环境变量 VSB_PY_MODE 控制, 默认 auto):
|
||||
* auto / numpy: 需要 numpy。内核用预分配 out 缓冲 + 两个就地 ufunc:
|
||||
numpy.multiply(na, nb, out=out); numpy.add(out, nc, out=out)
|
||||
逐元素两次舍入(a*b 先舍入一次, +c 再舍入一次)。注意 C/C++ 与 Viper 现在都走
|
||||
单趟 FMA(单次舍入, 编译加 -ffp-contract=fast), 所以 numpy 的逐元素结果与它们
|
||||
可能差 1 ulp —— 这是舍入口径差异, 不是错误。
|
||||
* auto 无 numpy / pure: 退化为纯 Python 循环。纯 Python 无法在合理时间内完成
|
||||
65536 趟(LEN=16384), 因此自动把每 run 趟数降为 PY_PURE_ITERS=8 并如实打印
|
||||
CFG <len> <iters>; run_bench 的 --flops-check 会据此提示"非 2e9 配置"。
|
||||
每元素仍两次舍入(CPython 不把 a*b+c 收缩成 FMA), 与 numpy 逐位一致。
|
||||
|
||||
计时: time.perf_counter_ns (Windows 底层即 QPC) -> 微秒, 只包住 ITERS 趟内核。
|
||||
线程 CPU 时间用 ctypes 调 kernel32.QueryThreadCycleTime 取本线程消耗的 CPU
|
||||
周期数, 再用入口标定出的 TSC 频率换算成微秒; 两者同口径包住同一段内核。
|
||||
(不用 GetThreadTimes: 它的记账粒度跟随调度时钟节拍, 实测硬地板 15.625ms,
|
||||
几十毫秒的 run 误差 ±40%, 且在 Win10 2004+ 无法用 timeBeginPeriod 改善。)
|
||||
线程绑定: 入口用 ctypes 调 kernel32.SetThreadAffinityMask 绑到首个可用逻辑核
|
||||
(与 C/C++/Viper 实现绑同一个核), 以 AFFINITY <core> 行回报, -1 = 失败。
|
||||
热身: 进程内先做 2 轮满负荷热身(每轮 = 实际趟数 + 升序读回 out 求校验和)。
|
||||
输出(进程开头一次 CFG + 一次 AFFINITY, 之后每次 run 一行):
|
||||
CFG <len> <iters>
|
||||
AFFINITY <core>
|
||||
RESULT <wall_us> <cpu_us> <flops> # flops = (LEN*ITERS*2)/墙钟秒
|
||||
CHECK <sum> # 最后一次 run 留下的 out 升序累加
|
||||
"""
|
||||
import ctypes
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from ctypes import wintypes
|
||||
|
||||
# 负载约束: LEN × ITERS × 2 恒为 ≈2.147e9 flops/run(与其它实现可比),
|
||||
# 只调 LEN 改"工作集": LEN=16384 -> 四份共 512 KiB, 稳居本机每核 1 MiB 私有 L2。
|
||||
LEN = 16384 # 元素数(与 C/C++/Viper 一致)
|
||||
ITERS = 65536 # 每 run 的内核趟数(numpy/自动模式)
|
||||
PY_PURE_ITERS = 8 # 纯 Python 回退趟数(降规模, 保证可测且如实报 CFG)
|
||||
RUNS_MAX = 200
|
||||
|
||||
_LCG_A = 48271.0
|
||||
_LCG_M = 2147483647.0
|
||||
_LCG_SEED = 12345.0
|
||||
|
||||
# win32 SetThreadPriority 的 THREAD_PRIORITY_HIGHEST
|
||||
_THREAD_PRIORITY_HIGHEST = 2
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Win32 线程绑定 / 线程 CPU 时间(经 ctypes), 与 C/C++/Viper 实现同口径
|
||||
# ------------------------------------------------------------
|
||||
_IS_WINDOWS = os.name == "nt"
|
||||
|
||||
if _IS_WINDOWS:
|
||||
_K32 = ctypes.WinDLL("kernel32", use_last_error=True)
|
||||
_K32.GetCurrentThread.restype = wintypes.HANDLE
|
||||
_K32.SetThreadAffinityMask.argtypes = [wintypes.HANDLE, ctypes.c_size_t]
|
||||
_K32.SetThreadAffinityMask.restype = ctypes.c_size_t
|
||||
_K32.SetThreadPriority.argtypes = [wintypes.HANDLE, ctypes.c_int]
|
||||
_K32.SetThreadPriority.restype = wintypes.BOOL
|
||||
_K32.QueryThreadCycleTime.argtypes = [wintypes.HANDLE,
|
||||
ctypes.POINTER(ctypes.c_ulonglong)]
|
||||
_K32.QueryThreadCycleTime.restype = wintypes.BOOL
|
||||
_K32.QueryPerformanceCounter.argtypes = [ctypes.POINTER(ctypes.c_longlong)]
|
||||
_K32.QueryPerformanceCounter.restype = wintypes.BOOL
|
||||
_K32.QueryPerformanceFrequency.argtypes = [ctypes.POINTER(ctypes.c_longlong)]
|
||||
_K32.QueryPerformanceFrequency.restype = wintypes.BOOL
|
||||
else:
|
||||
_K32 = None
|
||||
|
||||
|
||||
def _bind_first_available_core():
|
||||
"""把当前线程绑到首个可用逻辑核, 返回 CPU 序号; 失败/非 Windows 返回 -1。
|
||||
|
||||
从 1<<0 逐个试, 第一个被系统接受的位即亲和掩码里编号最小的核 ——
|
||||
四个实现会绑到同一个核, 横向对比才有意义。
|
||||
"""
|
||||
if _K32 is None:
|
||||
return -1
|
||||
h = _K32.GetCurrentThread()
|
||||
for i in range(64):
|
||||
if _K32.SetThreadAffinityMask(h, ctypes.c_size_t(1 << i)):
|
||||
return i
|
||||
return -1
|
||||
|
||||
|
||||
def _thread_cycles():
|
||||
"""本线程累计消耗的 CPU 周期数(QueryThreadCycleTime; 非 Windows 返回 0)。
|
||||
|
||||
现代 x86 的 TSC 是 invariant 的, 与核心实际频率无关, 所以"周期数"就是
|
||||
"本线程真正在 CPU 上跑过的时间", 对抢占与降频都不敏感。
|
||||
"""
|
||||
if _K32 is None:
|
||||
return 0
|
||||
c = ctypes.c_ulonglong(0)
|
||||
_K32.QueryThreadCycleTime(_K32.GetCurrentThread(), ctypes.byref(c))
|
||||
return c.value
|
||||
|
||||
|
||||
def _calibrate_tsc_hz():
|
||||
"""用 QPC 标定 TSC 频率(周期/秒): 忙等 30ms 数周期, 重复 3 次取最大。
|
||||
|
||||
为什么取最大: 标定窗口内本线程一旦被抢占, 消耗周期就少于墙钟流逝时间,
|
||||
算出 hz 偏小 -> 后续 cpu_us 系统性偏大(实测约 +0.13%, 表现为"cpu_us 偶尔
|
||||
比 wall_us 还大"这种看着像 bug 的伪影)。取三次最大值 = 挑最没被打断的一次。
|
||||
必须在 _bind_first_available_core() 之后调用, 否则跨核的 TSC 可能不同步。
|
||||
返回 0.0 表示标定失败。
|
||||
"""
|
||||
if _K32 is None:
|
||||
return 0.0
|
||||
qf = ctypes.c_longlong(0)
|
||||
_K32.QueryPerformanceFrequency(ctypes.byref(qf))
|
||||
target = qf.value * 30 // 1000 # 30ms 对应的 QPC 计次
|
||||
best = 0.0
|
||||
for _ in range(3):
|
||||
a = ctypes.c_longlong(0)
|
||||
b = ctypes.c_longlong(0)
|
||||
c0 = _thread_cycles()
|
||||
_K32.QueryPerformanceCounter(ctypes.byref(a))
|
||||
while True:
|
||||
_K32.QueryPerformanceCounter(ctypes.byref(b))
|
||||
if b.value - a.value >= target:
|
||||
break
|
||||
c1 = _thread_cycles()
|
||||
dt = b.value - a.value
|
||||
if dt <= 0 or c1 <= c0:
|
||||
continue
|
||||
hz = (c1 - c0) * qf.value / dt
|
||||
if hz > best:
|
||||
best = hz
|
||||
return best
|
||||
|
||||
|
||||
def _fill_abc(n):
|
||||
"""逐元素填充 a/b/c: 依次消耗同一个 LCG 流(a[i], b[i], c[i])。
|
||||
|
||||
返回三个 float 列表, 与 C(fmod)/Viper(vipermath.fmod) 逐位一致。
|
||||
"""
|
||||
a = [0.0] * n
|
||||
b = [0.0] * n
|
||||
c = [0.0] * n
|
||||
state = _LCG_SEED
|
||||
for i in range(n):
|
||||
state = math.fmod(state * _LCG_A, _LCG_M)
|
||||
a[i] = state / _LCG_M
|
||||
state = math.fmod(state * _LCG_A, _LCG_M)
|
||||
b[i] = state / _LCG_M
|
||||
state = math.fmod(state * _LCG_A, _LCG_M)
|
||||
c[i] = state / _LCG_M
|
||||
return a, b, c
|
||||
|
||||
|
||||
def _checksum_seq(values):
|
||||
"""升序逐元素累加(纯 Python double 左折叠, 与 C/Viper 校验顺序一致)"""
|
||||
s = 0.0
|
||||
for v in values:
|
||||
s += v
|
||||
return s
|
||||
|
||||
|
||||
def _run_numpy_pass(np_, na, nb, nc, out):
|
||||
"""单趟内核: out = a*b + c (两趟就地 ufunc, 逐元素两次舍入)"""
|
||||
np_.multiply(na, nb, out=out)
|
||||
np_.add(out, nc, out=out)
|
||||
|
||||
|
||||
def _argv_int(idx, default):
|
||||
"""取第 idx 个命令行整数参数(1 起, 即 sys.argv[idx]); 缺失/非法返回 default。"""
|
||||
if len(sys.argv) > idx:
|
||||
try:
|
||||
return int(sys.argv[idx])
|
||||
except ValueError:
|
||||
pass
|
||||
return default
|
||||
|
||||
|
||||
def main():
|
||||
# 命令行: bench_py.py [runs] [len] [iters]
|
||||
# 省略则用本文件顶部的默认值; run_bench.py 多尺寸扫描时会显式传入。
|
||||
runs = max(0, min(_argv_int(1, 1), RUNS_MAX))
|
||||
n = _argv_int(2, LEN)
|
||||
iters_req = _argv_int(3, ITERS)
|
||||
if n < 4 or n % 4 != 0 or iters_req < 1:
|
||||
sys.stderr.write(
|
||||
f"bench_py: bad args len={n}(需 >=4 且为 4 的倍数) iters={iters_req}\n")
|
||||
return 2
|
||||
|
||||
# 线程绑定(必做) + 提优先级: 与 C/C++/Viper 同口径, 并压低背景进程抢占的尖峰
|
||||
core = _bind_first_available_core()
|
||||
_K32.SetThreadPriority(_K32.GetCurrentThread(), _THREAD_PRIORITY_HIGHEST)
|
||||
# TSC 频率标定(必须在绑核之后): 把 QueryThreadCycleTime 的周期数换算成微秒
|
||||
tsc_hz = _calibrate_tsc_hz()
|
||||
|
||||
def cpu_us_from(cy0, cy1):
|
||||
"""周期差 -> 微秒; 标定失败(tsc_hz==0)时给 0, 表现为一眼可见的异常。"""
|
||||
return (cy1 - cy0) * 1000000.0 / tsc_hz if tsc_hz > 0.0 else 0.0
|
||||
|
||||
mode = os.environ.get("VSB_PY_MODE", "auto").strip().lower()
|
||||
numpy = None
|
||||
if mode in ("auto", "numpy"):
|
||||
try:
|
||||
import numpy as _np
|
||||
numpy = _np
|
||||
except Exception:
|
||||
numpy = None
|
||||
if mode == "numpy" and numpy is None:
|
||||
sys.stderr.write("bench_py: 强制 numpy 模式但本机无 numpy; "
|
||||
"请改 VSB_PY_MODE=auto/pure 或安装 numpy\n")
|
||||
return 1
|
||||
|
||||
use_numpy = numpy is not None
|
||||
|
||||
# 纯 Python 回退把趟数降规模(否则跑不完); n 仍用请求值, 如实打印 CFG
|
||||
iters = iters_req if use_numpy else PY_PURE_ITERS
|
||||
|
||||
print(f"CFG {n} {iters}", flush=True)
|
||||
print(f"AFFINITY {core}", flush=True)
|
||||
print(f"TSC {tsc_hz:.4e}", flush=True)
|
||||
|
||||
a, b, c = _fill_abc(n)
|
||||
|
||||
if use_numpy:
|
||||
# 值逐位不变: list -> float64 ndarray(仅一次转换)
|
||||
na = numpy.array(a, dtype=numpy.float64)
|
||||
nb = numpy.array(b, dtype=numpy.float64)
|
||||
nc = numpy.array(c, dtype=numpy.float64)
|
||||
out = numpy.empty(n, dtype=numpy.float64)
|
||||
|
||||
def warm_round():
|
||||
for _ in range(iters):
|
||||
_run_numpy_pass(numpy, na, nb, nc, out)
|
||||
|
||||
def timed_round():
|
||||
cy0 = _thread_cycles()
|
||||
t0 = time.perf_counter_ns()
|
||||
for _ in range(iters):
|
||||
_run_numpy_pass(numpy, na, nb, nc, out)
|
||||
t1 = time.perf_counter_ns()
|
||||
cy1 = _thread_cycles()
|
||||
return (t1 - t0) * 1e-3, cpu_us_from(cy0, cy1)
|
||||
|
||||
def readback_sum():
|
||||
return _checksum_seq(out.tolist())
|
||||
else:
|
||||
# 纯 Python 回退: 列表就地写 out
|
||||
la, lb, lc = a, b, c
|
||||
out = [0.0] * n
|
||||
|
||||
def warm_round():
|
||||
for _ in range(iters):
|
||||
for i in range(n):
|
||||
out[i] = la[i] * lb[i] + lc[i]
|
||||
|
||||
def timed_round():
|
||||
cy0 = _thread_cycles()
|
||||
t0 = time.perf_counter_ns()
|
||||
for _ in range(iters):
|
||||
for i in range(n):
|
||||
out[i] = la[i] * lb[i] + lc[i]
|
||||
t1 = time.perf_counter_ns()
|
||||
cy1 = _thread_cycles()
|
||||
return (t1 - t0) * 1e-3, cpu_us_from(cy0, cy1)
|
||||
|
||||
def readback_sum():
|
||||
return _checksum_seq(out)
|
||||
|
||||
if not use_numpy:
|
||||
sys.stderr.write("bench_py: numpy 不可用, 使用纯 Python 回退"
|
||||
f"(LEN={n}, 趟数={iters}, 总 flops 非 2e9)\n")
|
||||
|
||||
total_flops = float(n) * float(iters) * 2.0
|
||||
|
||||
# 2 轮满负荷热身(每轮 = 实际趟数 + 读回 out 求校验和)
|
||||
last_chk = 0.0
|
||||
for _ in range(2):
|
||||
warm_round()
|
||||
last_chk = readback_sum()
|
||||
|
||||
# 连续计时 runs 次; 计时只包住 iters 趟内核, 墙钟与线程 CPU 同时包住
|
||||
for _ in range(runs):
|
||||
wall_us, cpu_us = timed_round()
|
||||
flops = total_flops / (wall_us * 1e-6)
|
||||
print(f"RESULT {wall_us:.3f} {cpu_us:.3f} {flops:.0f}", flush=True)
|
||||
last_chk = readback_sum()
|
||||
|
||||
print(f"CHECK {last_chk:.17g}", flush=True)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,560 @@
|
||||
==============================================================================
|
||||
ViperSpeedBenchmark - run_bench.py (线程绑定 + 双计时 + 离群裁剪版)
|
||||
时间 : 2026-09-10 21:57:43
|
||||
参数 : groups=5, runs/group=10, tol=5.0%
|
||||
实现 : viper,viper_scalar,viper_add,viper_scalar_add,viper_mul,viper_scalar_mul,viper_sqrt,viper_scalar_sqrt,c,cpp,py
|
||||
负载 : 每次 run ≈ 2.147e+09 flops(LEN×ITERS×2 恒定, 跨尺寸可比)
|
||||
LEN= 16384 ITERS= 65536 工作集= 512.0 KiB
|
||||
采集方式 : 每组启动一次子进程, 进程内 2 轮满热身 + 连续计时 N 次
|
||||
线程绑定 : 子进程入口 SetThreadAffinityMask 绑到首个可用逻辑核(AFFINITY 行回报)
|
||||
主指标 : 墙钟(QPC) (另一指标同时采集, 汇总里对照)
|
||||
统计量 : 算术平均(裁剪后)
|
||||
离群裁剪 : 组内保留 <= 中位数×1.2 的样本
|
||||
统计口径 : 首组(组1)仅汇报不计入统计(规避首组效应)
|
||||
==============================================================================
|
||||
[c] 编译 bench_c.c -> .native_build/
|
||||
[c] 编译成功: D:\Users\TermiNexus\Desktop\TransPyC\ViperApps\ViperSpeedBenchmark\.native_build\bench_c.exe
|
||||
[cpp] 编译 bench_cpp.cpp -> .native_build/
|
||||
[cpp] 编译成功: D:\Users\TermiNexus\Desktop\TransPyC\ViperApps\ViperSpeedBenchmark\.native_build\bench_cpp.exe
|
||||
[viper] 找到产物: D:\Users\TermiNexus\Desktop\TransPyC\ViperApps\ViperSpeedBenchmark\.tpv_build\output\app.exe
|
||||
8 个内核变体共用此 exe, 只差命令行 mode 参数: viper(mode=0), viper_scalar(mode=1), viper_add(mode=2), viper_scalar_add(mode=3), viper_mul(mode=4), viper_scalar_mul(mode=5), viper_sqrt(mode=6), viper_scalar_sqrt(mode=7)
|
||||
|
||||
##############################################################################
|
||||
# 工作尺寸 LEN=16384 ITERS=65536 四份缓冲共 512.0 KiB
|
||||
##############################################################################
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
实现: viper (每组 = 一次进程启动, 进程内满热身 + 连续 10 次 run)
|
||||
方式: FMA SIMD (vipersimd.simd_fma_array) 内核, QPC 计时
|
||||
命令: D:\Users\TermiNexus\Desktop\TransPyC\ViperApps\ViperSpeedBenchmark\.tpv_build\output\app.exe 10 16384 65536 0
|
||||
[viper] 组1/5 完成 | 墙钟均值=146997.8 us | 线程CPU均值=0.1 us | 墙钟max-min=8957.0
|
||||
[viper] 组2/5 完成 | 墙钟均值=146876.2 us | 线程CPU均值=0.1 us | 墙钟max-min=5865.0
|
||||
[viper] 组3/5 完成 | 墙钟均值=147544.1 us | 线程CPU均值=0.1 us | 墙钟max-min=5055.0
|
||||
[viper] 组4/5 完成 | 墙钟均值=145898.4 us | 线程CPU均值=0.1 us | 墙钟max-min=1295.0
|
||||
[viper] 组5/5 完成 | 墙钟均值=145487.9 us | 线程CPU均值=0.1 us | 墙钟max-min=691.0
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
实现: viper_scalar (每组 = 一次进程启动, 进程内满热身 + 连续 10 次 run)
|
||||
方式: FMA 原生 (vipersimd.scalar_fma_array) 内核, QPC 计时
|
||||
命令: D:\Users\TermiNexus\Desktop\TransPyC\ViperApps\ViperSpeedBenchmark\.tpv_build\output\app.exe 10 16384 65536 1
|
||||
[viper_scalar] 组1/5 完成 | 墙钟均值=567499.8 us | 线程CPU均值=566963.5 us | 墙钟max-min=15788.0
|
||||
[viper_scalar] 组2/5 完成 | 墙钟均值=565232.0 us | 线程CPU均值=564626.3 us | 墙钟max-min=4721.0
|
||||
[viper_scalar] 组3/5 完成 | 墙钟均值=562005.0 us | 线程CPU均值=561517.1 us | 墙钟max-min=5706.0
|
||||
[viper_scalar] 组4/5 完成 | 墙钟均值=566047.3 us | 线程CPU均值=565346.8 us | 墙钟max-min=8363.0
|
||||
[viper_scalar] 组5/5 完成 | 墙钟均值=569447.1 us | 线程CPU均值=568756.2 us | 墙钟max-min=29838.0
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
实现: viper_add (每组 = 一次进程启动, 进程内满热身 + 连续 10 次 run)
|
||||
方式: ADD SIMD (vipersimd.simd_add_array) 内核, QPC 计时
|
||||
命令: D:\Users\TermiNexus\Desktop\TransPyC\ViperApps\ViperSpeedBenchmark\.tpv_build\output\app.exe 10 16384 65536 2
|
||||
[viper_add] 组1/5 完成 | 墙钟均值=109218.3 us | 线程CPU均值=0.1 us | 墙钟max-min=2680.0
|
||||
[viper_add] 组2/5 完成 | 墙钟均值=108855.3 us | 线程CPU均值=0.1 us | 墙钟max-min=2260.0
|
||||
[viper_add] 组3/5 完成 | 墙钟均值=108710.4 us | 线程CPU均值=0.1 us | 墙钟max-min=3159.0
|
||||
[viper_add] 组4/5 完成 | 墙钟均值=108405.2 us | 线程CPU均值=0.1 us | 墙钟max-min=1574.0
|
||||
[viper_add] 组5/5 完成 | 墙钟均值=109393.4 us | 线程CPU均值=0.1 us | 墙钟max-min=4045.0
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
实现: viper_scalar_add (每组 = 一次进程启动, 进程内满热身 + 连续 10 次 run)
|
||||
方式: ADD 原生 (vipersimd.scalar_add_array) 内核, QPC 计时
|
||||
命令: D:\Users\TermiNexus\Desktop\TransPyC\ViperApps\ViperSpeedBenchmark\.tpv_build\output\app.exe 10 16384 65536 3
|
||||
[viper_scalar_add] 组1/5 完成 | 墙钟均值=264234.0 us | 线程CPU均值=263935.3 us | 墙钟max-min=19535.0
|
||||
[viper_scalar_add] 组2/5 完成 | 墙钟均值=363073.5 us | 线程CPU均值=362679.1 us | 墙钟max-min=7116.0
|
||||
[viper_scalar_add] 组3/5 完成 | 墙钟均值=363610.5 us | 线程CPU均值=363656.7 us | 墙钟max-min=13346.0
|
||||
[viper_scalar_add] 组4/5 完成 | 墙钟均值=361123.1 us | 线程CPU均值=360830.3 us | 墙钟max-min=5770.0
|
||||
[viper_scalar_add] 组5/5 完成 | 墙钟均值=367550.6 us | 线程CPU均值=367155.5 us | 墙钟max-min=21069.0
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
实现: viper_mul (每组 = 一次进程启动, 进程内满热身 + 连续 10 次 run)
|
||||
方式: MUL SIMD (vipersimd.simd_mul_array) 内核, QPC 计时
|
||||
命令: D:\Users\TermiNexus\Desktop\TransPyC\ViperApps\ViperSpeedBenchmark\.tpv_build\output\app.exe 10 16384 65536 4
|
||||
[viper_mul] 组1/5 完成 | 墙钟均值=116161.7 us | 线程CPU均值=0.0 us | 墙钟max-min=16726.0
|
||||
[viper_mul] 组2/5 完成 | 墙钟均值=109714.4 us | 线程CPU均值=0.0 us | 墙钟max-min=2184.0
|
||||
[viper_mul] 组3/5 完成 | 墙钟均值=109022.7 us | 线程CPU均值=0.0 us | 墙钟max-min=2974.0
|
||||
[viper_mul] 组4/5 完成 | 墙钟均值=108652.3 us | 线程CPU均值=0.0 us | 墙钟max-min=1304.0
|
||||
[viper_mul] 组5/5 完成 | 墙钟均值=109059.1 us | 线程CPU均值=0.0 us | 墙钟max-min=10589.0
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
实现: viper_scalar_mul (每组 = 一次进程启动, 进程内满热身 + 连续 10 次 run)
|
||||
方式: MUL 原生 (vipersimd.scalar_mul_array) 内核, QPC 计时
|
||||
命令: D:\Users\TermiNexus\Desktop\TransPyC\ViperApps\ViperSpeedBenchmark\.tpv_build\output\app.exe 10 16384 65536 5
|
||||
[viper_scalar_mul] 组1/5 完成 | 墙钟均值=371390.7 us | 线程CPU均值=371095.3 us | 墙钟max-min=27705.0
|
||||
[viper_scalar_mul] 组2/5 完成 | 墙钟均值=360426.7 us | 线程CPU均值=360061.4 us | 墙钟max-min=7001.0
|
||||
[viper_scalar_mul] 组3/5 完成 | 墙钟均值=361146.4 us | 线程CPU均值=360829.9 us | 墙钟max-min=8083.0
|
||||
[viper_scalar_mul] 组4/5 完成 | 墙钟均值=361690.9 us | 线程CPU均值=361491.5 us | 墙钟max-min=8550.0
|
||||
[viper_scalar_mul] 组5/5 完成 | 墙钟均值=358774.3 us | 线程CPU均值=358420.7 us | 墙钟max-min=11123.0
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
实现: viper_sqrt (每组 = 一次进程启动, 进程内满热身 + 连续 10 次 run)
|
||||
方式: SQRT SIMD (vipersimd.simd_sqrt_array) 内核, QPC 计时
|
||||
命令: D:\Users\TermiNexus\Desktop\TransPyC\ViperApps\ViperSpeedBenchmark\.tpv_build\output\app.exe 10 16384 65536 6
|
||||
[viper_sqrt] 组1/5 完成 | 墙钟均值=426503.7 us | 线程CPU均值=426083.7 us | 墙钟max-min=6150.0
|
||||
[viper_sqrt] 组2/5 完成 | 墙钟均值=428990.1 us | 线程CPU均值=429025.4 us | 墙钟max-min=5252.0
|
||||
[viper_sqrt] 组3/5 完成 | 墙钟均值=426922.9 us | 线程CPU均值=426620.7 us | 墙钟max-min=13636.0
|
||||
[viper_sqrt] 组4/5 完成 | 墙钟均值=426265.1 us | 线程CPU均值=426211.7 us | 墙钟max-min=5377.0
|
||||
[viper_sqrt] 组5/5 完成 | 墙钟均值=427957.2 us | 线程CPU均值=428055.8 us | 墙钟max-min=7283.0
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
实现: viper_scalar_sqrt (每组 = 一次进程启动, 进程内满热身 + 连续 10 次 run)
|
||||
方式: SQRT 原生 (vipersimd.scalar_sqrt_array) 内核, QPC 计时
|
||||
命令: D:\Users\TermiNexus\Desktop\TransPyC\ViperApps\ViperSpeedBenchmark\.tpv_build\output\app.exe 10 16384 65536 7
|
||||
[viper_scalar_sqrt] 组1/5 完成 | 墙钟均值=1674822.7 us | 线程CPU均值=1665347.4 us | 墙钟max-min=107743.0
|
||||
[viper_scalar_sqrt] 组2/5 完成 | 墙钟均值=1708572.7 us | 线程CPU均值=1693518.7 us | 墙钟max-min=27028.0
|
||||
[viper_scalar_sqrt] 组3/5 完成 | 墙钟均值=1713341.2 us | 线程CPU均值=1693990.1 us | 墙钟max-min=61839.0
|
||||
[viper_scalar_sqrt] 组4/5 完成 | 墙钟均值=1703943.0 us | 线程CPU均值=1689936.2 us | 墙钟max-min=16512.0
|
||||
[viper_scalar_sqrt] 组5/5 完成 | 墙钟均值=1706336.6 us | 线程CPU均值=1692420.2 us | 墙钟max-min=58374.0
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
实现: c (每组 = 一次进程启动, 进程内满热身 + 连续 10 次 run)
|
||||
方式: C 手写 -O2 -mavx2 -mfma(-ffp-contract=fast) 自动向量化单趟 FMA, QPC
|
||||
命令: D:\Users\TermiNexus\Desktop\TransPyC\ViperApps\ViperSpeedBenchmark\.native_build\bench_c.exe 10 16384 65536
|
||||
[c] 组1/5 完成 | 墙钟均值=155204.6 us | 线程CPU均值=154941.3 us | 墙钟max-min=532.4
|
||||
[c] 组2/5 完成 | 墙钟均值=157053.5 us | 线程CPU均值=156826.7 us | 墙钟max-min=1904.5
|
||||
[c] 组3/5 完成 | 墙钟均值=157452.8 us | 线程CPU均值=157158.4 us | 墙钟max-min=646.7
|
||||
[c] 组4/5 完成 | 墙钟均值=155110.0 us | 线程CPU均值=155019.1 us | 墙钟max-min=983.0
|
||||
[c] 组5/5 完成 | 墙钟均值=155731.6 us | 线程CPU均值=155513.4 us | 墙钟max-min=845.7
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
实现: cpp (每组 = 一次进程启动, 进程内满热身 + 连续 10 次 run)
|
||||
方式: C++ 手写(std::vector) -O2 -mavx2 -mfma(-ffp-contract=fast) 单趟 FMA, QPC
|
||||
命令: D:\Users\TermiNexus\Desktop\TransPyC\ViperApps\ViperSpeedBenchmark\.native_build\bench_cpp.exe 10 16384 65536
|
||||
[cpp] 组1/5 完成 | 墙钟均值=139023.3 us | 线程CPU均值=138797.2 us | 墙钟max-min=579.9
|
||||
[cpp] 组2/5 完成 | 墙钟均值=157023.1 us | 线程CPU均值=156725.4 us | 墙钟max-min=1673.6
|
||||
[cpp] 组3/5 完成 | 墙钟均值=157319.2 us | 线程CPU均值=157102.4 us | 墙钟max-min=2560.2
|
||||
[cpp] 组4/5 完成 | 墙钟均值=156630.0 us | 线程CPU均值=156432.1 us | 墙钟max-min=900.6
|
||||
[cpp] 组5/5 完成 | 墙钟均值=158231.7 us | 线程CPU均值=158139.6 us | 墙钟max-min=3647.0
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
实现: py (每组 = 一次进程启动, 进程内满热身 + 连续 10 次 run)
|
||||
方式: Python(numpy 就地 ufunc; 无 numpy 自动纯 Python 降规模回退)
|
||||
命令: D:\Python312\python.exe D:\Users\TermiNexus\Desktop\TransPyC\ViperApps\ViperSpeedBenchmark\bench_py.py 10 16384 65536
|
||||
[py] 组1/5 完成 | 墙钟均值=295135.0 us | 线程CPU均值=290265.2 us | 墙钟max-min=51386.4
|
||||
[py] 组2/5 完成 | 墙钟均值=364033.9 us | 线程CPU均值=361389.5 us | 墙钟max-min=32911.9
|
||||
[py] 组3/5 完成 | 墙钟均值=323557.2 us | 线程CPU均值=321078.4 us | 墙钟max-min=18941.8
|
||||
[py] 组4/5 完成 | 墙钟均值=333109.9 us | 线程CPU均值=330769.1 us | 墙钟max-min=20309.2
|
||||
[py] 组5/5 完成 | 墙钟均值=347882.3 us | 线程CPU均值=345411.2 us | 墙钟max-min=25141.1
|
||||
|
||||
##############################################################################
|
||||
# 结果汇总: 工作尺寸 LEN=16384 ITERS=65536 四份缓冲共 512.0 KiB
|
||||
##############################################################################
|
||||
|
||||
==============================================================================
|
||||
实现 viper CFG(len×iters)=16384x65536 总运算量/run=2.147e+09 flops
|
||||
线程绑定 AFFINITY = CPU#0(SetThreadAffinityMask 成功)
|
||||
TSC 标定频率 = 1.9932e+09 Hz
|
||||
内核 MODE = 0 FMA SIMD (vipersimd.simd_fma_array)
|
||||
CHECK(首次) = 12302.997770370706
|
||||
主指标 墙钟(QPC) 统计量=算术平均(裁剪后)
|
||||
总体统计 overall : 146451.650 us (统计 4/5 组, 有效样本 40, 裁剪剔除 0; 首组仅汇报)
|
||||
对照 线程CPU(QueryThreadCycleTime): 0.077 us
|
||||
样本标准差 sample std : 1388.250 us
|
||||
最大偏差 max|raw-overall|: 4750.350 us (3.244%)
|
||||
实测吞吐(按 CFG, 主指标) : 14.663 GFLOPS
|
||||
------------------------------------------------------------------------------
|
||||
[组 1] 统计值= 146997.80 us | 均值= 146997.80 | 中位数= 145417.50 | 几何均值= 146963.80 | 裁剪剔除 0
|
||||
组内差max-min= 8957.00 | 组-总体= +546.15 us (0.37%) [首组: 仅汇报, 不计入统计]
|
||||
相邻组差: n/a(首组)
|
||||
组内 10 次原始(主指标): 144887.0, 146648.0, 153844.0, 152763.0, 145445.0, 145390.0, 145063.0, 145226.0, 145576.0, 145136.0 us
|
||||
[组 2] 统计值= 146876.20 us | 均值= 146876.20 | 中位数= 146511.00 | 几何均值= 146866.25 | 裁剪剔除 0
|
||||
组内差max-min= 5865.00 | 组-总体= +424.55 us (0.29%)
|
||||
相邻组差: n/a(组1 不计入统计)
|
||||
组内 10 次原始(主指标): 148678.0, 146928.0, 146486.0, 145338.0, 145337.0, 146536.0, 146770.0, 151202.0, 145632.0, 145855.0 us
|
||||
[组 3] 统计值= 147544.10 us | 均值= 147544.10 | 中位数= 147277.00 | 几何均值= 147538.35 | 裁剪剔除 0
|
||||
组内差max-min= 5055.00 | 组-总体= +1092.45 us (0.75%)
|
||||
相邻组差 组2->组3: +667.90 us (0.46%)
|
||||
组内 10 次原始(主指标): 148714.0, 148051.0, 145547.0, 147119.0, 150602.0, 147435.0, 147624.0, 146917.0, 146468.0, 146964.0 us
|
||||
[组 4] 统计值= 145898.40 us | 均值= 145898.40 | 中位数= 145886.00 | 几何均值= 145897.67 | 裁剪剔除 0
|
||||
组内差max-min= 1295.00 | 组-总体= -553.25 us (0.38%)
|
||||
相邻组差 组3->组4: -1645.70 us (1.12%)
|
||||
组内 10 次原始(主指标): 145652.0, 146504.0, 146522.0, 146059.0, 145909.0, 146462.0, 145349.0, 145227.0, 145863.0, 145437.0 us
|
||||
[组 5] 统计值= 145487.90 us | 均值= 145487.90 | 中位数= 145457.50 | 几何均值= 145487.79 | 裁剪剔除 0
|
||||
组内差max-min= 691.00 | 组-总体= -963.75 us (0.66%)
|
||||
相邻组差 组4->组5: -410.50 us (0.28%)
|
||||
组内 10 次原始(主指标): 145607.0, 145385.0, 145343.0, 145413.0, 145235.0, 145502.0, 145365.0, 145567.0, 145926.0, 145536.0 us
|
||||
------------------------------------------------------------------------------
|
||||
差值核验(tol=5.0%): PASS [组1 排除: 首组仅汇报]
|
||||
统计组(排除首组): |组统计值-总体|/总体 <= tol, 且相邻组|差值|/总体 <= tol
|
||||
|
||||
==============================================================================
|
||||
实现 viper_scalar CFG(len×iters)=16384x65536 总运算量/run=2.147e+09 flops
|
||||
线程绑定 AFFINITY = CPU#0(SetThreadAffinityMask 成功)
|
||||
TSC 标定频率 = 1.9935e+09 Hz
|
||||
内核 MODE = 1 FMA 原生 (vipersimd.scalar_fma_array)
|
||||
CHECK(首次) = 12302.997770370706
|
||||
主指标 墙钟(QPC) 统计量=算术平均(裁剪后)
|
||||
总体统计 overall : 565682.850 us (统计 4/5 组, 有效样本 40, 裁剪剔除 0; 首组仅汇报)
|
||||
对照 线程CPU(QueryThreadCycleTime): 565061.584 us
|
||||
样本标准差 sample std : 5697.022 us
|
||||
最大偏差 max|raw-overall|: 26955.150 us (4.765%)
|
||||
实测吞吐(按 CFG, 主指标) : 3.796 GFLOPS
|
||||
------------------------------------------------------------------------------
|
||||
[组 1] 统计值= 567499.80 us | 均值= 567499.80 | 中位数= 565836.00 | 几何均值= 567478.55 | 裁剪剔除 0
|
||||
组内差max-min=15788.00 | 组-总体= +1816.95 us (0.32%) [首组: 仅汇报, 不计入统计]
|
||||
相邻组差: n/a(首组)
|
||||
组内 10 次原始(主指标): 561969.0, 564294.0, 562172.0, 566048.0, 572126.0, 577757.0, 565624.0, 569439.0, 572073.0, 563496.0 us
|
||||
[组 2] 统计值= 565232.00 us | 均值= 565232.00 | 中位数= 564845.50 | 几何均值= 565230.43 | 裁剪剔除 0
|
||||
组内差max-min= 4721.00 | 组-总体= -450.85 us (0.08%)
|
||||
相邻组差: n/a(组1 不计入统计)
|
||||
组内 10 次原始(主指标): 568249.0, 565576.0, 565233.0, 563892.0, 564975.0, 564652.0, 564716.0, 563528.0, 566909.0, 564590.0 us
|
||||
[组 3] 统计值= 562005.00 us | 均值= 562005.00 | 中位数= 561251.00 | 几何均值= 562002.41 | 裁剪剔除 0
|
||||
组内差max-min= 5706.00 | 组-总体= -3677.85 us (0.65%)
|
||||
相邻组差 组2->组3: -3227.00 us (0.57%)
|
||||
组内 10 次原始(主指标): 565859.0, 564449.0, 560153.0, 562346.0, 561160.0, 560703.0, 561167.0, 561967.0, 561335.0, 560911.0 us
|
||||
[组 4] 统计值= 566047.30 us | 均值= 566047.30 | 中位数= 565232.50 | 几何均值= 566040.91 | 裁剪剔除 0
|
||||
组内差max-min= 8363.00 | 组-总体= +364.45 us (0.06%)
|
||||
相邻组差 组3->组4: +4042.30 us (0.71%)
|
||||
组内 10 次原始(主指标): 565473.0, 566108.0, 565812.0, 564992.0, 562782.0, 564976.0, 564492.0, 571145.0, 571054.0, 563639.0 us
|
||||
[组 5] 统计值= 569447.10 us | 均值= 569447.10 | 中位数= 564062.50 | 几何均值= 569372.28 | 裁剪剔除 0
|
||||
组内差max-min=29838.00 | 组-总体= +3764.25 us (0.67%)
|
||||
相邻组差 组4->组5: +3399.80 us (0.60%)
|
||||
组内 10 次原始(主指标): 563862.0, 563053.0, 562800.0, 563539.0, 564263.0, 578726.0, 592638.0, 574560.0, 563656.0, 567374.0 us
|
||||
------------------------------------------------------------------------------
|
||||
差值核验(tol=5.0%): PASS [组1 排除: 首组仅汇报]
|
||||
统计组(排除首组): |组统计值-总体|/总体 <= tol, 且相邻组|差值|/总体 <= tol
|
||||
|
||||
==============================================================================
|
||||
实现 viper_add CFG(len×iters)=16384x65536 总运算量/run=2.147e+09 flops
|
||||
线程绑定 AFFINITY = CPU#0(SetThreadAffinityMask 成功)
|
||||
TSC 标定频率 = 1.9937e+09 Hz
|
||||
内核 MODE = 2 ADD SIMD (vipersimd.simd_add_array)
|
||||
CHECK(首次) = 16340.870255275997
|
||||
主指标 墙钟(QPC) 统计量=算术平均(裁剪后)
|
||||
总体统计 overall : 108841.075 us (统计 4/5 组, 有效样本 40, 裁剪剔除 0; 首组仅汇报)
|
||||
对照 线程CPU(QueryThreadCycleTime): 0.091 us
|
||||
样本标准差 sample std : 926.230 us
|
||||
最大偏差 max|raw-overall|: 3215.925 us (2.955%)
|
||||
实测吞吐(按 CFG, 主指标) : 19.730 GFLOPS
|
||||
------------------------------------------------------------------------------
|
||||
[组 1] 统计值= 109218.30 us | 均值= 109218.30 | 中位数= 109081.50 | 几何均值= 109216.15 | 裁剪剔除 0
|
||||
组内差max-min= 2680.00 | 组-总体= +377.23 us (0.35%) [首组: 仅汇报, 不计入统计]
|
||||
相邻组差: n/a(首组)
|
||||
组内 10 次原始(主指标): 108386.0, 109070.0, 109372.0, 108952.0, 108804.0, 109399.0, 109337.0, 111066.0, 108704.0, 109093.0 us
|
||||
[组 2] 统计值= 108855.30 us | 均值= 108855.30 | 中位数= 108614.50 | 几何均值= 108853.36 | 裁剪剔除 0
|
||||
组内差max-min= 2260.00 | 组-总体= +14.23 us (0.01%)
|
||||
相邻组差: n/a(组1 不计入统计)
|
||||
组内 10 次原始(主指标): 109174.0, 110421.0, 108427.0, 108161.0, 108456.0, 108340.0, 108832.0, 109515.0, 108773.0, 108454.0 us
|
||||
[组 3] 统计值= 108710.40 us | 均值= 108710.40 | 中位数= 108420.00 | 几何均值= 108706.12 | 裁剪剔除 0
|
||||
组内差max-min= 3159.00 | 组-总体= -130.68 us (0.12%)
|
||||
相邻组差 组2->组3: -144.90 us (0.13%)
|
||||
组内 10 次原始(主指标): 108034.0, 107488.0, 107625.0, 108422.0, 110647.0, 109870.0, 109604.0, 108675.0, 108418.0, 108321.0 us
|
||||
[组 4] 统计值= 108405.20 us | 均值= 108405.20 | 中位数= 108575.50 | 几何均值= 108404.05 | 裁剪剔除 0
|
||||
组内差max-min= 1574.00 | 组-总体= -435.88 us (0.40%)
|
||||
相邻组差 组3->组4: -305.20 us (0.28%)
|
||||
组内 10 次原始(主指标): 109235.0, 108650.0, 108695.0, 108227.0, 107843.0, 107661.0, 107706.0, 108501.0, 108714.0, 108820.0 us
|
||||
[组 5] 统计值= 109393.40 us | 均值= 109393.40 | 中位数= 109202.00 | 几何均值= 109387.84 | 裁剪剔除 0
|
||||
组内差max-min= 4045.00 | 组-总体= +552.32 us (0.51%)
|
||||
相邻组差 组4->组5: +988.20 us (0.91%)
|
||||
组内 10 次原始(主指标): 108715.0, 109480.0, 108206.0, 108012.0, 108924.0, 110145.0, 112057.0, 109956.0, 108903.0, 109536.0 us
|
||||
------------------------------------------------------------------------------
|
||||
差值核验(tol=5.0%): PASS [组1 排除: 首组仅汇报]
|
||||
统计组(排除首组): |组统计值-总体|/总体 <= tol, 且相邻组|差值|/总体 <= tol
|
||||
|
||||
==============================================================================
|
||||
实现 viper_scalar_add CFG(len×iters)=16384x65536 总运算量/run=2.147e+09 flops
|
||||
线程绑定 AFFINITY = CPU#0(SetThreadAffinityMask 成功)
|
||||
TSC 标定频率 = 1.9943e+09 Hz
|
||||
内核 MODE = 3 ADD 原生 (vipersimd.scalar_add_array)
|
||||
CHECK(首次) = 16340.870255275997
|
||||
主指标 墙钟(QPC) 统计量=算术平均(裁剪后)
|
||||
总体统计 overall : 363839.425 us (统计 4/5 组, 有效样本 40, 裁剪剔除 0; 首组仅汇报)
|
||||
对照 线程CPU(QueryThreadCycleTime): 363580.365 us
|
||||
样本标准差 sample std : 4532.884 us
|
||||
最大偏差 max|raw-overall|: 17937.575 us (4.930%)
|
||||
实测吞吐(按 CFG, 主指标) : 5.902 GFLOPS
|
||||
------------------------------------------------------------------------------
|
||||
[组 1] 统计值= 264234.00 us | 均值= 264234.00 | 中位数= 261966.00 | 几何均值= 264163.53 | 裁剪剔除 0
|
||||
组内差max-min=19535.00 | 组-总体= -99605.42 us (27.38%) [首组: 仅汇报, 不计入统计]
|
||||
相邻组差: n/a(首组)
|
||||
组内 10 次原始(主指标): 269930.0, 274199.0, 261735.0, 270404.0, 258664.0, 258743.0, 262064.0, 270069.0, 261868.0, 254664.0 us
|
||||
[组 2] 统计值= 363073.50 us | 均值= 363073.50 | 中位数= 362278.50 | 几何均值= 363066.16 | 裁剪剔除 0
|
||||
组内差max-min= 7116.00 | 组-总体= -765.92 us (0.21%)
|
||||
相邻组差: n/a(组1 不计入统计)
|
||||
组内 10 次原始(主指标): 360304.0, 365588.0, 365021.0, 361872.0, 367328.0, 364731.0, 360212.0, 362685.0, 361384.0, 361610.0 us
|
||||
[组 3] 统计值= 363610.50 us | 均值= 363610.50 | 中位数= 362723.00 | 几何均值= 363589.64 | 裁剪剔除 0
|
||||
组内差max-min=13346.00 | 组-总体= -228.92 us (0.06%)
|
||||
相邻组差 组2->组3: +537.00 us (0.15%)
|
||||
组内 10 次原始(主指标): 361089.0, 363834.0, 372305.0, 363456.0, 361990.0, 368353.0, 358959.0, 360859.0, 365321.0, 359939.0 us
|
||||
[组 4] 统计值= 361123.10 us | 均值= 361123.10 | 中位数= 361352.00 | 几何均值= 361118.96 | 裁剪剔除 0
|
||||
组内差max-min= 5770.00 | 组-总体= -2716.33 us (0.75%)
|
||||
相邻组差 组3->组4: -2487.40 us (0.68%)
|
||||
组内 10 次原始(主指标): 362484.0, 360776.0, 360570.0, 357658.0, 362915.0, 361928.0, 361976.0, 360717.0, 363428.0, 358779.0 us
|
||||
[组 5] 统计值= 367550.60 us | 均值= 367550.60 | 中位数= 367214.50 | 几何均值= 367503.87 | 裁剪剔除 0
|
||||
组内差max-min=21069.00 | 组-总体= +3711.17 us (1.02%)
|
||||
相邻组差 组4->组5: +6427.50 us (1.77%)
|
||||
组内 10 次原始(主指标): 367149.0, 360919.0, 364497.0, 362951.0, 360708.0, 367280.0, 381777.0, 368746.0, 371674.0, 369805.0 us
|
||||
------------------------------------------------------------------------------
|
||||
差值核验(tol=5.0%): PASS [组1 排除: 首组仅汇报]
|
||||
统计组(排除首组): |组统计值-总体|/总体 <= tol, 且相邻组|差值|/总体 <= tol
|
||||
|
||||
==============================================================================
|
||||
实现 viper_mul CFG(len×iters)=16384x65536 总运算量/run=2.147e+09 flops
|
||||
线程绑定 AFFINITY = CPU#0(SetThreadAffinityMask 成功)
|
||||
TSC 标定频率 = 1.9927e+09 Hz
|
||||
内核 MODE = 4 MUL SIMD (vipersimd.simd_mul_array)
|
||||
CHECK(首次) = 4066.7119802023808
|
||||
主指标 墙钟(QPC) 统计量=算术平均(裁剪后)
|
||||
总体统计 overall : 109112.125 us (统计 4/5 组, 有效样本 40, 裁剪剔除 0; 首组仅汇报)
|
||||
对照 线程CPU(QueryThreadCycleTime): 0.018 us
|
||||
样本标准差 sample std : 1708.655 us
|
||||
最大偏差 max|raw-overall|: 9177.875 us (8.411%)
|
||||
实测吞吐(按 CFG, 主指标) : 19.681 GFLOPS
|
||||
------------------------------------------------------------------------------
|
||||
[组 1] 统计值= 116161.70 us | 均值= 116161.70 | 中位数= 115173.50 | 几何均值= 116007.81 | 裁剪剔除 0
|
||||
组内差max-min=16726.00 | 组-总体= +7049.57 us (6.46%) [首组: 仅汇报, 不计入统计]
|
||||
相邻组差: n/a(首组)
|
||||
组内 10 次原始(主指标): 113188.0, 118870.0, 121283.0, 125240.0, 125201.0, 117159.0, 112412.0, 108514.0, 110677.0, 109073.0 us
|
||||
[组 2] 统计值= 109714.40 us | 均值= 109714.40 | 中位数= 109753.50 | 几何均值= 109712.93 | 裁剪剔除 0
|
||||
组内差max-min= 2184.00 | 组-总体= +602.27 us (0.55%)
|
||||
相邻组差: n/a(组1 不计入统计)
|
||||
组内 10 次原始(主指标): 109627.0, 108540.0, 110288.0, 109679.0, 109828.0, 109087.0, 109921.0, 110724.0, 109869.0, 109581.0 us
|
||||
[组 3] 统计值= 109022.70 us | 均值= 109022.70 | 中位数= 108858.50 | 几何均值= 109019.40 | 裁剪剔除 0
|
||||
组内差max-min= 2974.00 | 组-总体= -89.43 us (0.08%)
|
||||
相邻组差 组2->组3: -691.70 us (0.63%)
|
||||
组内 10 次原始(主指标): 108389.0, 109416.0, 109801.0, 111084.0, 108999.0, 108915.0, 108802.0, 108199.0, 108512.0, 108110.0 us
|
||||
[组 4] 统计值= 108652.30 us | 均值= 108652.30 | 中位数= 108459.00 | 几何均值= 108651.34 | 裁剪剔除 0
|
||||
组内差max-min= 1304.00 | 组-总体= -459.82 us (0.42%)
|
||||
相邻组差 组3->组4: -370.40 us (0.34%)
|
||||
组内 10 次原始(主指标): 109473.0, 108758.0, 108287.0, 108348.0, 108169.0, 108311.0, 108570.0, 108299.0, 109462.0, 108846.0 us
|
||||
[组 5] 统计值= 109059.10 us | 均值= 109059.10 | 中位数= 107964.00 | 几何均值= 109017.39 | 裁剪剔除 0
|
||||
组内差max-min=10589.00 | 组-总体= -53.02 us (0.05%)
|
||||
相邻组差 组4->组5: +406.80 us (0.37%)
|
||||
组内 10 次原始(主指标): 107840.0, 108129.0, 107886.0, 107851.0, 108757.0, 107978.0, 108209.0, 107701.0, 107950.0, 118290.0 us
|
||||
------------------------------------------------------------------------------
|
||||
差值核验(tol=5.0%): PASS [组1 排除: 首组仅汇报]
|
||||
统计组(排除首组): |组统计值-总体|/总体 <= tol, 且相邻组|差值|/总体 <= tol
|
||||
|
||||
==============================================================================
|
||||
实现 viper_scalar_mul CFG(len×iters)=16384x65536 总运算量/run=2.147e+09 flops
|
||||
线程绑定 AFFINITY = CPU#0(SetThreadAffinityMask 成功)
|
||||
TSC 标定频率 = 1.9935e+09 Hz
|
||||
内核 MODE = 5 MUL 原生 (vipersimd.scalar_mul_array)
|
||||
CHECK(首次) = 4066.7119802023808
|
||||
主指标 墙钟(QPC) 统计量=算术平均(裁剪后)
|
||||
总体统计 overall : 360509.575 us (统计 4/5 组, 有效样本 40, 裁剪剔除 0; 首组仅汇报)
|
||||
对照 线程CPU(QueryThreadCycleTime): 360200.872 us
|
||||
样本标准差 sample std : 2866.346 us
|
||||
最大偏差 max|raw-overall|: 6461.425 us (1.792%)
|
||||
实测吞吐(按 CFG, 主指标) : 5.957 GFLOPS
|
||||
------------------------------------------------------------------------------
|
||||
[组 1] 统计值= 371390.70 us | 均值= 371390.70 | 中位数= 370767.50 | 几何均值= 371242.25 | 裁剪剔除 0
|
||||
组内差max-min=27705.00 | 组-总体= +10881.12 us (3.02%) [首组: 仅汇报, 不计入统计]
|
||||
相邻组差: n/a(首组)
|
||||
组内 10 次原始(主指标): 382511.0, 361407.0, 385639.0, 357934.0, 364822.0, 385011.0, 360933.0, 361609.0, 376713.0, 377328.0 us
|
||||
[组 2] 统计值= 360426.70 us | 均值= 360426.70 | 中位数= 360073.50 | 几何均值= 360419.87 | 裁剪剔除 0
|
||||
组内差max-min= 7001.00 | 组-总体= -82.88 us (0.02%)
|
||||
相邻组差: n/a(组1 不计入统计)
|
||||
组内 10 次原始(主指标): 363880.0, 358508.0, 361418.0, 360484.0, 358900.0, 357768.0, 364769.0, 360368.0, 359779.0, 358393.0 us
|
||||
[组 3] 统计值= 361146.40 us | 均值= 361146.40 | 中位数= 361303.00 | 几何均值= 361137.33 | 裁剪剔除 0
|
||||
组内差max-min= 8083.00 | 组-总体= +636.83 us (0.18%)
|
||||
相邻组差 组2->组3: +719.70 us (0.20%)
|
||||
组内 10 次原始(主指标): 365924.0, 357841.0, 363538.0, 358023.0, 363320.0, 360904.0, 358845.0, 361702.0, 359017.0, 362350.0 us
|
||||
[组 4] 统计值= 361690.90 us | 均值= 361690.90 | 中位数= 361254.00 | 几何均值= 361682.05 | 裁剪剔除 0
|
||||
组内差max-min= 8550.00 | 组-总体= +1181.33 us (0.33%)
|
||||
相邻组差 组3->组4: +544.50 us (0.15%)
|
||||
组内 10 次原始(主指标): 362296.0, 365337.0, 360291.0, 359582.0, 362058.0, 358421.0, 366971.0, 361110.0, 359445.0, 361398.0 us
|
||||
[组 5] 统计值= 358774.30 us | 均值= 358774.30 | 中位数= 357752.00 | 几何均值= 358761.47 | 裁剪剔除 0
|
||||
组内差max-min=11123.00 | 组-总体= -1735.28 us (0.48%)
|
||||
相邻组差 组4->组5: -2916.60 us (0.81%)
|
||||
组内 10 次原始(主指标): 360532.0, 357848.0, 356531.0, 357194.0, 358292.0, 359978.0, 356973.0, 355808.0, 357656.0, 366931.0 us
|
||||
------------------------------------------------------------------------------
|
||||
差值核验(tol=5.0%): PASS [组1 排除: 首组仅汇报]
|
||||
统计组(排除首组): |组统计值-总体|/总体 <= tol, 且相邻组|差值|/总体 <= tol
|
||||
|
||||
==============================================================================
|
||||
实现 viper_sqrt CFG(len×iters)=16384x65536 总运算量/run=2.147e+09 flops
|
||||
线程绑定 AFFINITY = CPU#0(SetThreadAffinityMask 成功)
|
||||
TSC 标定频率 = 1.9937e+09 Hz
|
||||
内核 MODE = 6 SQRT SIMD (vipersimd.simd_sqrt_array)
|
||||
CHECK(首次) = 10892.477135620607
|
||||
主指标 墙钟(QPC) 统计量=算术平均(裁剪后)
|
||||
总体统计 overall : 427533.825 us (统计 4/5 组, 有效样本 40, 裁剪剔除 0; 首组仅汇报)
|
||||
对照 线程CPU(QueryThreadCycleTime): 427478.406 us
|
||||
样本标准差 sample std : 2601.950 us
|
||||
最大偏差 max|raw-overall|: 8984.175 us (2.101%)
|
||||
实测吞吐(按 CFG, 主指标) : 5.023 GFLOPS
|
||||
------------------------------------------------------------------------------
|
||||
[组 1] 统计值= 426503.70 us | 均值= 426503.70 | 中位数= 426305.00 | 几何均值= 426499.52 | 裁剪剔除 0
|
||||
组内差max-min= 6150.00 | 组-总体= -1030.12 us (0.24%) [首组: 仅汇报, 不计入统计]
|
||||
相邻组差: n/a(首组)
|
||||
组内 10 次原始(主指标): 425028.0, 427121.0, 427605.0, 426766.0, 423432.0, 424913.0, 429582.0, 425844.0, 429431.0, 425315.0 us
|
||||
[组 2] 统计值= 428990.10 us | 均值= 428990.10 | 中位数= 428542.00 | 几何均值= 428986.54 | 裁剪剔除 0
|
||||
组内差max-min= 5252.00 | 组-总体= +1456.27 us (0.34%)
|
||||
相邻组差: n/a(组1 不计入统计)
|
||||
组内 10 次原始(主指标): 431170.0, 427120.0, 428939.0, 430751.0, 430359.0, 426254.0, 428145.0, 427578.0, 431506.0, 428079.0 us
|
||||
[组 3] 统计值= 426922.90 us | 均值= 426922.90 | 中位数= 426228.50 | 几何均值= 426907.65 | 裁剪剔除 0
|
||||
组内差max-min=13636.00 | 组-总体= -610.92 us (0.14%)
|
||||
相邻组差 组2->组3: -2067.20 us (0.48%)
|
||||
组内 10 次原始(主指标): 436518.0, 425882.0, 422882.0, 429338.0, 427436.0, 426659.0, 424727.0, 426575.0, 424816.0, 424396.0 us
|
||||
[组 4] 统计值= 426265.10 us | 均值= 426265.10 | 中位数= 426056.00 | 几何均值= 426262.38 | 裁剪剔除 0
|
||||
组内差max-min= 5377.00 | 组-总体= -1268.73 us (0.30%)
|
||||
相邻组差 组3->组4: -657.80 us (0.15%)
|
||||
组内 10 次原始(主指标): 423797.0, 425260.0, 426468.0, 425961.0, 425677.0, 424640.0, 429174.0, 427652.0, 427871.0, 426151.0 us
|
||||
[组 5] 统计值= 427957.20 us | 均值= 427957.20 | 中位数= 428171.50 | 几何均值= 427952.99 | 裁剪剔除 0
|
||||
组内差max-min= 7283.00 | 组-总体= +423.38 us (0.10%)
|
||||
相邻组差 组4->组5: +1692.10 us (0.40%)
|
||||
组内 10 次原始(主指标): 429924.0, 428081.0, 428262.0, 428447.0, 431290.0, 424007.0, 427486.0, 425877.0, 427600.0, 428598.0 us
|
||||
------------------------------------------------------------------------------
|
||||
差值核验(tol=5.0%): PASS [组1 排除: 首组仅汇报]
|
||||
统计组(排除首组): |组统计值-总体|/总体 <= tol, 且相邻组|差值|/总体 <= tol
|
||||
|
||||
==============================================================================
|
||||
实现 viper_scalar_sqrt CFG(len×iters)=16384x65536 总运算量/run=2.147e+09 flops
|
||||
线程绑定 AFFINITY = CPU#0(SetThreadAffinityMask 成功)
|
||||
TSC 标定频率 = 1.9938e+09 Hz
|
||||
内核 MODE = 7 SQRT 原生 (vipersimd.scalar_sqrt_array)
|
||||
CHECK(首次) = 10892.477135620607
|
||||
主指标 墙钟(QPC) 统计量=算术平均(裁剪后)
|
||||
总体统计 overall : 1708048.375 us (统计 4/5 组, 有效样本 40, 裁剪剔除 0; 首组仅汇报)
|
||||
对照 线程CPU(QueryThreadCycleTime): 1692466.293 us
|
||||
样本标准差 sample std : 14518.834 us
|
||||
最大偏差 max|raw-overall|: 48242.625 us (2.824%)
|
||||
实测吞吐(按 CFG, 主指标) : 1.257 GFLOPS
|
||||
------------------------------------------------------------------------------
|
||||
[组 1] 统计值= 1674822.70 us | 均值= 1674822.70 | 中位数= 1659879.00 | 几何均值= 1674489.72 | 裁剪剔除 0
|
||||
组内差max-min=107743.00 | 组-总体= -33225.68 us (1.95%) [首组: 仅汇报, 不计入统计]
|
||||
相邻组差: n/a(首组)
|
||||
组内 10 次原始(主指标): 1655631.0, 1658031.0, 1656603.0, 1659375.0, 1662711.0, 1656631.0, 1660383.0, 1661788.0, 1713700.0, 1763374.0 us
|
||||
[组 2] 统计值= 1708572.70 us | 均值= 1708572.70 | 中位数= 1705255.00 | 几何均值= 1708554.56 | 裁剪剔除 0
|
||||
组内差max-min=27028.00 | 组-总体= +524.32 us (0.03%)
|
||||
相邻组差: n/a(组1 不计入统计)
|
||||
组内 10 次原始(主指标): 1718963.0, 1713289.0, 1702424.0, 1697342.0, 1704120.0, 1724370.0, 1704089.0, 1706390.0, 1711230.0, 1703510.0 us
|
||||
[组 3] 统计值= 1713341.20 us | 均值= 1713341.20 | 中位数= 1707841.50 | 几何均值= 1713242.18 | 裁剪剔除 0
|
||||
组内差max-min=61839.00 | 组-总体= +5292.82 us (0.31%)
|
||||
相邻组差 组2->组3: +4768.50 us (0.28%)
|
||||
组内 10 次原始(主指标): 1699962.0, 1696688.0, 1720098.0, 1711634.0, 1704049.0, 1694452.0, 1713822.0, 1701165.0, 1756291.0, 1735251.0 us
|
||||
[组 4] 统计值= 1703943.00 us | 均值= 1703943.00 | 中位数= 1704454.00 | 几何均值= 1703936.28 | 裁剪剔除 0
|
||||
组内差max-min=16512.00 | 组-总体= -4105.38 us (0.24%)
|
||||
相邻组差 组3->组4: -9398.20 us (0.55%)
|
||||
组内 10 次原始(主指标): 1697523.0, 1704722.0, 1710285.0, 1708731.0, 1706060.0, 1703215.0, 1703178.0, 1704186.0, 1707757.0, 1693773.0 us
|
||||
[组 5] 统计值= 1706336.60 us | 均值= 1706336.60 | 中位数= 1698845.50 | 几何均值= 1706235.99 | 裁剪剔除 0
|
||||
组内差max-min=58374.00 | 组-总体= -1711.77 us (0.10%)
|
||||
相邻组差 组4->组5: +2393.60 us (0.14%)
|
||||
组内 10 次原始(主指标): 1708980.0, 1702417.0, 1693243.0, 1750755.0, 1692381.0, 1693507.0, 1731661.0, 1697333.0, 1700358.0, 1692731.0 us
|
||||
------------------------------------------------------------------------------
|
||||
差值核验(tol=5.0%): PASS [组1 排除: 首组仅汇报]
|
||||
统计组(排除首组): |组统计值-总体|/总体 <= tol, 且相邻组|差值|/总体 <= tol
|
||||
|
||||
==============================================================================
|
||||
实现 c CFG(len×iters)=16384x65536 总运算量/run=2.147e+09 flops
|
||||
线程绑定 AFFINITY = CPU#0(SetThreadAffinityMask 成功)
|
||||
TSC 标定频率 = 1.9943e+09 Hz
|
||||
CHECK(首次) = 12302.997770370706
|
||||
主指标 墙钟(QPC) 统计量=算术平均(裁剪后)
|
||||
总体统计 overall : 156336.977 us (统计 4/5 组, 有效样本 40, 裁剪剔除 0; 首组仅汇报)
|
||||
对照 线程CPU(QueryThreadCycleTime): 156129.403 us
|
||||
样本标准差 sample std : 1028.969 us
|
||||
最大偏差 max|raw-overall|: 2057.523 us (1.316%)
|
||||
实测吞吐(按 CFG, 主指标) : 13.736 GFLOPS
|
||||
------------------------------------------------------------------------------
|
||||
[组 1] 统计值= 155204.58 us | 均值= 155204.58 | 中位数= 155238.75 | 几何均值= 155204.46 | 裁剪剔除 0
|
||||
组内差max-min= 532.40 | 组-总体= -1132.40 us (0.72%) [首组: 仅汇报, 不计入统计]
|
||||
相邻组差: n/a(首组)
|
||||
组内 10 次原始(主指标): 154883.0, 155007.0, 155372.7, 154989.6, 155094.6, 155407.6, 155398.4, 155279.7, 155415.4, 155197.8 us
|
||||
[组 2] 统计值= 157053.45 us | 均值= 157053.45 | 中位数= 156821.90 | 几何均值= 157052.52 | 裁剪剔除 0
|
||||
组内差max-min= 1904.50 | 组-总体= +716.47 us (0.46%)
|
||||
相邻组差: n/a(组1 不计入统计)
|
||||
组内 10 次原始(主指标): 158394.5, 157655.3, 157179.3, 156842.4, 156702.3, 156690.3, 156801.4, 156986.8, 156792.2, 156490.0 us
|
||||
[组 3] 统计值= 157452.82 us | 均值= 157452.82 | 中位数= 157387.30 | 几何均值= 157452.70 | 裁剪剔除 0
|
||||
组内差max-min= 646.70 | 组-总体= +1115.84 us (0.71%)
|
||||
相邻组差 组2->组3: +399.37 us (0.26%)
|
||||
组内 10 次原始(主指标): 157722.5, 157544.2, 157305.9, 157210.9, 157469.2, 157350.8, 157387.1, 157387.5, 157857.6, 157292.5 us
|
||||
[组 4] 统计值= 155110.02 us | 均值= 155110.02 | 中位数= 155076.05 | 几何均值= 155109.70 | 裁剪剔除 0
|
||||
组内差max-min= 983.00 | 组-总体= -1226.96 us (0.78%)
|
||||
相邻组差 组3->组4: -2342.80 us (1.50%)
|
||||
组内 10 次原始(主指标): 154727.9, 154657.9, 154835.6, 154940.1, 155313.5, 154979.7, 155172.4, 155640.9, 155357.2, 155475.0 us
|
||||
[组 5] 统计值= 155731.62 us | 均值= 155731.62 | 中位数= 155735.60 | 几何均值= 155731.40 | 裁剪剔除 0
|
||||
组内差max-min= 845.70 | 组-总体= -605.36 us (0.39%)
|
||||
相邻组差 组4->组5: +621.60 us (0.40%)
|
||||
组内 10 次原始(主指标): 155419.7, 155936.3, 156049.5, 156140.5, 155708.9, 155744.0, 155851.6, 155443.7, 155727.2, 155294.8 us
|
||||
------------------------------------------------------------------------------
|
||||
差值核验(tol=5.0%): PASS [组1 排除: 首组仅汇报]
|
||||
统计组(排除首组): |组统计值-总体|/总体 <= tol, 且相邻组|差值|/总体 <= tol
|
||||
|
||||
==============================================================================
|
||||
实现 cpp CFG(len×iters)=16384x65536 总运算量/run=2.147e+09 flops
|
||||
线程绑定 AFFINITY = CPU#0(SetThreadAffinityMask 成功)
|
||||
TSC 标定频率 = 1.9945e+09 Hz
|
||||
CHECK(首次) = 12302.997770370706
|
||||
主指标 墙钟(QPC) 统计量=算术平均(裁剪后)
|
||||
总体统计 overall : 157301.015 us (统计 4/5 组, 有效样本 40, 裁剪剔除 0; 首组仅汇报)
|
||||
对照 线程CPU(QueryThreadCycleTime): 157099.861 us
|
||||
样本标准差 sample std : 949.566 us
|
||||
最大偏差 max|raw-overall|: 2531.585 us (1.609%)
|
||||
实测吞吐(按 CFG, 主指标) : 13.652 GFLOPS
|
||||
------------------------------------------------------------------------------
|
||||
[组 1] 统计值= 139023.30 us | 均值= 139023.30 | 中位数= 139056.05 | 几何均值= 139023.17 | 裁剪剔除 0
|
||||
组内差max-min= 579.90 | 组-总体= -18277.71 us (11.62%) [首组: 仅汇报, 不计入统计]
|
||||
相邻组差: n/a(首组)
|
||||
组内 10 次原始(主指标): 138717.4, 139222.2, 139166.4, 139297.3, 138977.6, 138929.1, 138930.6, 138718.3, 139134.5, 139139.6 us
|
||||
[组 2] 统计值= 157023.11 us | 均值= 157023.11 | 中位数= 156905.35 | 几何均值= 157022.23 | 裁剪剔除 0
|
||||
组内差max-min= 1673.60 | 组-总体= -277.90 us (0.18%)
|
||||
相邻组差: n/a(组1 不计入统计)
|
||||
组内 10 次原始(主指标): 158205.2, 156628.3, 156578.3, 156910.1, 156900.6, 156532.0, 157730.2, 157131.6, 156531.6, 157083.2 us
|
||||
[组 3] 统计值= 157319.23 us | 均值= 157319.23 | 中位数= 157164.50 | 几何均值= 157317.65 | 裁剪剔除 0
|
||||
组内差max-min= 2560.20 | 组-总体= +18.22 us (0.01%)
|
||||
相邻组差 组2->组3: +296.12 us (0.19%)
|
||||
组内 10 次原始(主指标): 156659.7, 156986.7, 156831.3, 157028.2, 157476.3, 157466.7, 156628.2, 157626.0, 159188.4, 157300.8 us
|
||||
[组 4] 统计值= 156630.01 us | 均值= 156630.01 | 中位数= 156615.05 | 几何均值= 156629.73 | 裁剪剔除 0
|
||||
组内差max-min= 900.60 | 组-总体= -671.00 us (0.43%)
|
||||
相邻组差 组3->组4: -689.22 us (0.44%)
|
||||
组内 10 次原始(主指标): 156636.2, 156593.9, 156260.0, 156724.7, 157160.6, 157060.9, 156791.6, 156280.6, 156386.3, 156405.3 us
|
||||
[组 5] 统计值= 158231.71 us | 均值= 158231.71 | 中位数= 158230.30 | 几何均值= 158227.72 | 裁剪剔除 0
|
||||
组内差max-min= 3647.00 | 组-总体= +930.70 us (0.59%)
|
||||
相邻组差 组4->组5: +1601.70 us (1.02%)
|
||||
组内 10 次原始(主指标): 156185.6, 156640.7, 157866.9, 159420.7, 159041.3, 158517.5, 159832.6, 157943.1, 157776.6, 159092.1 us
|
||||
------------------------------------------------------------------------------
|
||||
差值核验(tol=5.0%): PASS [组1 排除: 首组仅汇报]
|
||||
统计组(排除首组): |组统计值-总体|/总体 <= tol, 且相邻组|差值|/总体 <= tol
|
||||
|
||||
==============================================================================
|
||||
实现 py CFG(len×iters)=16384x65536 总运算量/run=2.147e+09 flops
|
||||
线程绑定 AFFINITY = CPU#0(SetThreadAffinityMask 成功)
|
||||
TSC 标定频率 = 1.9818e+09 Hz
|
||||
CHECK(首次) = 12302.997770370706
|
||||
主指标 墙钟(QPC) 统计量=算术平均(裁剪后)
|
||||
总体统计 overall : 342145.820 us (统计 4/5 组, 有效样本 40, 裁剪剔除 0; 首组仅汇报)
|
||||
对照 线程CPU(QueryThreadCycleTime): 339662.053 us
|
||||
样本标准差 sample std : 17641.199 us
|
||||
最大偏差 max|raw-overall|: 46538.780 us (13.602%)
|
||||
实测吞吐(按 CFG, 主指标) : 6.277 GFLOPS
|
||||
------------------------------------------------------------------------------
|
||||
[组 1] 统计值= 295134.95 us | 均值= 295134.95 | 中位数= 293963.40 | 几何均值= 294631.09 | 裁剪剔除 0
|
||||
组内差max-min=51386.40 | 组-总体= -47010.87 us (13.74%) [首组: 仅汇报, 不计入统计]
|
||||
相邻组差: n/a(首组)
|
||||
组内 10 次原始(主指标): 270105.5, 295817.2, 285267.1, 321491.9, 310146.1, 273217.4, 279803.7, 292109.6, 317042.4, 306348.6 us
|
||||
[组 2] 统计值= 364033.91 us | 均值= 364033.91 | 中位数= 359615.85 | 几何均值= 363905.38 | 裁剪剔除 0
|
||||
组内差max-min=32911.90 | 组-总体= +21888.09 us (6.40%)
|
||||
相邻组差: n/a(组1 不计入统计)
|
||||
组内 10 次原始(主指标): 361007.0, 357231.8, 357132.4, 358224.7, 355772.7, 363391.7, 388684.6, 374073.9, 366974.1, 357846.2 us
|
||||
[组 3] 统计值= 323557.23 us | 均值= 323557.23 | 中位数= 323015.45 | 几何均值= 323512.72 | 裁剪剔除 0
|
||||
组内差max-min=18941.80 | 组-总体= -18588.59 us (5.43%)
|
||||
相邻组差 组2->组3: -40476.68 us (11.83%)
|
||||
组内 10 次原始(主指标): 321663.7, 318841.9, 323303.2, 324448.0, 319115.2, 314154.5, 322727.7, 329358.0, 333096.3, 328863.8 us
|
||||
[组 4] 统计值= 333109.86 us | 均值= 333109.86 | 中位数= 330384.95 | 几何均值= 333009.75 | 裁剪剔除 0
|
||||
组内差max-min=20309.20 | 组-总体= -9035.96 us (2.64%)
|
||||
相邻组差 组3->组4: +9552.63 us (2.79%)
|
||||
组内 10 次原始(主指标): 327686.8, 325331.9, 325181.0, 333083.1, 325646.0, 325615.9, 334437.1, 343412.0, 345214.6, 345490.2 us
|
||||
[组 5] 统计值= 347882.28 us | 均值= 347882.28 | 中位数= 342711.90 | 几何均值= 347765.12 | 裁剪剔除 0
|
||||
组内差max-min=25141.10 | 组-总体= +5736.46 us (1.68%)
|
||||
相邻组差 组4->组5: +14772.42 us (4.32%)
|
||||
组内 10 次原始(主指标): 338754.7, 340203.8, 341557.2, 343866.6, 340456.4, 340737.7, 351876.3, 356357.3, 363895.8, 361117.0 us
|
||||
------------------------------------------------------------------------------
|
||||
差值核验(tol=5.0%): FAIL [组1 排除: 首组仅汇报]
|
||||
组对总体超差(违规组): [2, 3]
|
||||
相邻组超差(违规对): ['组1->组2']
|
||||
|
||||
==============================================================================
|
||||
汇总: 各实现总体统计值(us) 与 实测 GFLOPS(按各自 CFG); 主指标=墙钟(QPC)
|
||||
|
||||
==== LEN=16384 (工作集 512.0 KiB) ====
|
||||
viper : [墙钟] 146451.650 us 14.663 GFLOPS | [CPU ] 0.077 us 27709466.426 GFLOPS | CPU#0 | PASS
|
||||
viper_scalar: [墙钟] 565682.850 us 3.796 GFLOPS | [CPU ] 565061.584 us 3.800 GFLOPS | CPU#0 | PASS
|
||||
viper_add: [墙钟] 108841.075 us 19.730 GFLOPS | [CPU ] 0.091 us 23546969.825 GFLOPS | CPU#0 | PASS
|
||||
viper_scalar_add: [墙钟] 363839.425 us 5.902 GFLOPS | [CPU ] 363580.365 us 5.906 GFLOPS | CPU#0 | PASS
|
||||
viper_mul: [墙钟] 109112.125 us 19.681 GFLOPS | [CPU ] 0.018 us 119139176.033 GFLOPS | CPU#0 | PASS
|
||||
viper_scalar_mul: [墙钟] 360509.575 us 5.957 GFLOPS | [CPU ] 360200.872 us 5.962 GFLOPS | CPU#0 | PASS
|
||||
viper_sqrt: [墙钟] 427533.825 us 5.023 GFLOPS | [CPU ] 427478.406 us 5.024 GFLOPS | CPU#0 | PASS
|
||||
viper_scalar_sqrt: [墙钟] 1708048.375 us 1.257 GFLOPS | [CPU ] 1692466.293 us 1.269 GFLOPS | CPU#0 | PASS
|
||||
c : [墙钟] 156336.977 us 13.736 GFLOPS | [CPU ] 156129.403 us 13.755 GFLOPS | CPU#0 | PASS
|
||||
cpp : [墙钟] 157301.015 us 13.652 GFLOPS | [CPU ] 157099.861 us 13.670 GFLOPS | CPU#0 | PASS
|
||||
py : [墙钟] 342145.820 us 6.277 GFLOPS | [CPU ] 339662.053 us 6.322 GFLOPS | CPU#0 | FAIL
|
||||
==============================================================================
|
||||
@@ -0,0 +1,511 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
plot_bench.py - 读 run_bench.py 导出的 CSV, 生成 4 张对比图(无需重跑基准)
|
||||
|
||||
用法:
|
||||
python plot_bench.py # 自动取最新的 bench_*.csv
|
||||
python plot_bench.py bench_20260910_2031.csv
|
||||
python plot_bench.py bench_xxx.csv --metric cpu
|
||||
python plot_bench.py bench_xxx.csv --out-dir figs --prefix run1
|
||||
|
||||
产出(4 张 PNG, 默认与 CSV 同目录, 前缀 = CSV 文件名):
|
||||
<前缀>_1_simd_vs_scalar.png 左: ADD/MUL/SQRT 的 原生 vs SIMD 延迟对比(柱状)
|
||||
右: 各操作"比原生快多少倍"(柱状 + xN.NN 标注)
|
||||
<前缀>_2_exec_time.png 各实现 Execution Time 折线(叠加每次 run 的实测点)
|
||||
<前缀>_3_overall_mean.png 各实现 Overall Mean Execution Time 对比
|
||||
<前缀>_4_gflops.png 各实现 Measured GFLOPS 吞吐对比
|
||||
|
||||
统计口径与 run_bench.py 报告一致: CSV 里 trimmed=1 的样本(被离群裁剪剔除)不计入。
|
||||
图1 需要 CSV 里同时有 ADD/MUL/SQRT 的 原生+SIMD 成对数据, 例如:
|
||||
python run_bench.py --impl viper_add,viper_scalar_add,viper_mul,viper_scalar_mul,viper_sqrt,viper_scalar_sqrt
|
||||
缺哪一对就少画哪一根, 全缺则整张图跳过并提示。
|
||||
"""
|
||||
import argparse
|
||||
import csv
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import matplotlib
|
||||
matplotlib.use("Agg") # 无 GUI 后端, 只存文件
|
||||
import matplotlib.pyplot as plt # noqa: E402
|
||||
import numpy as np # noqa: E402
|
||||
|
||||
# 图表标题/坐标轴都是中文: 必须显式指定带 CJK 字形的字体, 否则全是方块
|
||||
matplotlib.rcParams["font.sans-serif"] = [
|
||||
"Microsoft YaHei", "SimHei", "Microsoft JhengHei", "DejaVu Sans"]
|
||||
matplotlib.rcParams["axes.unicode_minus"] = False
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
|
||||
# 实现名 -> (显示名, 颜色), 固定顺序便于多次运行配色一致
|
||||
# 显示名刻意短: 图内文字统一英文, 图例太长会把绘图区挤扁
|
||||
IMPL_STYLE = [
|
||||
("viper", "Viper SIMD", "#d62728"),
|
||||
("viper_scalar", "Viper Scalar", "#ff7f0e"),
|
||||
("c", "C (-O2 -mavx2 -mfma)", "#1f77b4"),
|
||||
("cpp", "C++ (std::vector)", "#2ca02c"),
|
||||
("py", "Python + numpy", "#9467bd"),
|
||||
]
|
||||
|
||||
# 图1: 三种操作 -> (显示名, SIMD 实现名, 原生实现名)。
|
||||
# 每一对都是同一个 app.exe 的两个 mode, 除"是否向量化"外无任何差异
|
||||
# (见 App/main.vp 的 KERNEL_* 与 run_bench.py 的 VIPER_KERNELS)。
|
||||
KERNEL_PAIRS = [
|
||||
("ADD", "viper_add", "viper_scalar_add"),
|
||||
("MUL", "viper_mul", "viper_scalar_mul"),
|
||||
("SQRT", "viper_sqrt", "viper_scalar_sqrt"),
|
||||
]
|
||||
|
||||
# 图1 配色: 原生(标量) vs SIMD
|
||||
COL_NATIVE = "#8c8c8c"
|
||||
COL_SIMD = "#d62728"
|
||||
|
||||
|
||||
def style_for(impl):
|
||||
"""实现名 -> (显示名, 颜色); 未登记的实现给灰色, 不静默改名。"""
|
||||
for name, disp, color in IMPL_STYLE:
|
||||
if name == impl:
|
||||
return disp, color
|
||||
return impl, "#7f7f7f"
|
||||
|
||||
|
||||
def metric_txt(metric):
|
||||
"""耗时口径 -> 英文短名(图内文字一律用英文, 避免中文长句撑爆标题)。"""
|
||||
return "wall clock" if metric == "wall" else "thread CPU"
|
||||
|
||||
|
||||
def human_kib(size):
|
||||
"""LEN(元素数, double) -> 工作集描述(四份缓冲共 LEN*32 字节)"""
|
||||
kib = size * 32 / 1024.0
|
||||
if kib >= 1024:
|
||||
return f"{size}\n({kib / 1024:.0f} MiB)"
|
||||
return f"{size}\n({kib:.0f} KiB)"
|
||||
|
||||
|
||||
def load_csv(path):
|
||||
"""读 CSV -> {(impl, len): {"wall":[...], "cpu":[...], "iters":n, "trimmed":n,
|
||||
"groups": {组号: {"wall":[...], "cpu":[...]}}}}
|
||||
|
||||
注意 iters 要留着: 每个尺寸的 LEN×ITERS×2 = 该尺寸下单 run 的总运算量,
|
||||
是 GFLOPS 换算的唯一依据(py 纯解释器回退会降规模, iters 与其它实现不同)。
|
||||
groups 供"只有一个 LEN 时改用组号当横轴"用(见 fig_exec_time)。
|
||||
"""
|
||||
data = {}
|
||||
with open(path, "r", newline="", encoding="utf-8-sig") as f:
|
||||
for row in csv.DictReader(f):
|
||||
key = (row["impl"], int(row["len"]))
|
||||
rec = data.setdefault(key, {"wall": [], "cpu": [], "iters": int(row["iters"]),
|
||||
"trimmed": 0, "groups": {}})
|
||||
if int(row["trimmed"]) == 1:
|
||||
rec["trimmed"] += 1
|
||||
continue
|
||||
wv = float(row["wall_us"])
|
||||
cv = float(row["cpu_us"])
|
||||
rec["wall"].append(wv)
|
||||
rec["cpu"].append(cv)
|
||||
grp = rec["groups"].setdefault(int(row["group"]), {"wall": [], "cpu": []})
|
||||
grp["wall"].append(wv)
|
||||
grp["cpu"].append(cv)
|
||||
if int(row["iters"]) != rec["iters"]:
|
||||
rec["iters"] = int(row["iters"]) # 防御: 同名 key 的 iters 应一致
|
||||
return data
|
||||
|
||||
|
||||
def series(rec, metric):
|
||||
"""取某实现某尺寸在指定口径下的有效样本(计时值必须 > 0)。"""
|
||||
v = np.asarray(rec[metric], dtype=float)
|
||||
return v[np.isfinite(v) & (v > 0.0)]
|
||||
|
||||
|
||||
def annotate_bars(ax, xs, ys, spec=".1f", dy_ratio=0.02, fontsize=9, color="black"):
|
||||
"""在柱顶标数值(xs/ys 为等长序列)。
|
||||
|
||||
spec 用 format() 的格式说明符(如 ".1f" / ",.0f"), 不用 % 风格 ——
|
||||
Python 的 % 格式化不支持千分位(%,.0f 会直接抛 ValueError)。
|
||||
"""
|
||||
span = (max(ys) - min(ys)) if len(ys) > 1 else (abs(ys[0]) if ys else 1.0)
|
||||
dy = (span if span > 0 else 1.0) * dy_ratio
|
||||
for x, y in zip(xs, ys):
|
||||
ax.text(x, y + dy, format(y, spec), ha="center", va="bottom",
|
||||
fontsize=fontsize, color=color)
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# 图1: ViperSIMD - ADD / MUL / SQRT 的 原生 vs SIMD(左右双面板)
|
||||
# ------------------------------------------------------------
|
||||
def fig_simd_vs_scalar(data, sizes, args, metric, out_path):
|
||||
"""左面板 = 每种操作(ADD/MUL/SQRT)的 原生 vs SIMD 延迟柱状对比;
|
||||
右面板 = 对应的 SIMD 加速比(原生延迟 / SIMD 延迟)。
|
||||
|
||||
只取一个 LEN(CSV 里最小的那个): 加速比是"指令级/微架构"特性, 在能驻留私有
|
||||
L2 的小尺寸上测得最干净; 想看跨尺寸的访存行为请用图2。
|
||||
"""
|
||||
if not sizes:
|
||||
print("[图1] 跳过: CSV 里没有样本")
|
||||
return False
|
||||
use_size = sizes[0]
|
||||
rows = [] # (op, native_us, simd_us)
|
||||
for op, simd_impl, native_impl in KERNEL_PAIRS:
|
||||
rs = data.get((simd_impl, use_size))
|
||||
rn = data.get((native_impl, use_size))
|
||||
if not rs or not rn:
|
||||
continue
|
||||
vs = series(rs, metric)
|
||||
vn = series(rn, metric)
|
||||
if vs.size == 0 or vn.size == 0:
|
||||
continue
|
||||
rows.append((op, float(vn.mean()), float(vs.mean())))
|
||||
if not rows:
|
||||
need = ",".join(f"{a},{b}" for _, a, b in KERNEL_PAIRS)
|
||||
print("[图1] 跳过: CSV 里没有 ADD/MUL/SQRT 的 原生+SIMD 成对数据")
|
||||
print(f" 该 CSV 实际包含: {sorted({k[0] for k in data})}")
|
||||
print(" 直接重跑一次即可(run_bench.py 默认已含这些内核变体): python run_bench.py")
|
||||
print(f" 或只补跑内核变体: --impl {need}")
|
||||
return False
|
||||
|
||||
ops = [r[0] for r in rows]
|
||||
native_us = [r[1] for r in rows]
|
||||
simd_us = [r[2] for r in rows]
|
||||
speedup = [(n / s if s > 0 else float("nan")) for n, s in zip(native_us, simd_us)]
|
||||
best = max(speedup)
|
||||
|
||||
fig, (axL, axR) = plt.subplots(1, 2, figsize=(13.5, 6.4))
|
||||
x = np.arange(len(ops), dtype=float)
|
||||
w = 0.36
|
||||
|
||||
# ---- 左: 延迟对比(每组内 左=原生 / 右=SIMD) ----
|
||||
axL.bar(x - w / 2, native_us, w, color=COL_NATIVE, alpha=0.9,
|
||||
label="Native (scalar)")
|
||||
axL.bar(x + w / 2, simd_us, w, color=COL_SIMD, alpha=0.9,
|
||||
label="ViperSIMD (AVX2 x4)")
|
||||
axL.set_xticks(x)
|
||||
axL.set_xticklabels(ops, fontsize=11)
|
||||
axL.set_xlabel("Operation", fontsize=10)
|
||||
axL.set_ylabel(f"Latency per Run (us, {metric_txt(metric)})", fontsize=10)
|
||||
axL.set_title(f"Latency: Native vs ViperSIMD\nLEN={use_size}", fontsize=12)
|
||||
axL.grid(alpha=0.3, axis="y")
|
||||
axL.legend(fontsize=10)
|
||||
hi = max(native_us + simd_us)
|
||||
for xi, y in zip(x - w / 2, native_us):
|
||||
axL.text(xi, y + hi * 0.01, f"{y:,.0f}", ha="center", va="bottom", fontsize=9)
|
||||
for xi, y in zip(x + w / 2, simd_us):
|
||||
axL.text(xi, y + hi * 0.01, f"{y:,.0f}", ha="center", va="bottom", fontsize=9)
|
||||
|
||||
# ---- 右: 比原生快多少倍 ----
|
||||
axR.bar(x, speedup, 0.5, color=COL_SIMD, alpha=0.9)
|
||||
axR.axhline(1.0, color="#555555", lw=1.2, ls="--")
|
||||
axR.text(len(ops) - 0.55, 1.0, "x1 = no gain", va="bottom", ha="right",
|
||||
fontsize=9, color="#555555")
|
||||
axR.set_xticks(x)
|
||||
axR.set_xticklabels(ops, fontsize=11)
|
||||
axR.set_xlabel("Operation", fontsize=10)
|
||||
axR.set_ylabel("Speed-up (native latency / ViperSIMD latency)", fontsize=10)
|
||||
axR.set_title("SIMD Speed-up over Native", fontsize=12)
|
||||
axR.grid(alpha=0.3, axis="y")
|
||||
axR.set_ylim(0, best * 1.30 if best > 0 else 2.0)
|
||||
for xi, sp in zip(x, speedup):
|
||||
axR.text(xi, sp + best * 0.02, f"x{sp:.2f}", ha="center", va="bottom",
|
||||
fontsize=13, fontweight="bold")
|
||||
|
||||
fig.suptitle(f"{args.csv_name}\n"
|
||||
f"bar = mean of all runs · metric = {metric_txt(metric)}"
|
||||
f" · trimmed outliers excluded"
|
||||
f" · latency: lower is better", fontsize=10)
|
||||
fig.tight_layout(rect=(0, 0, 1, 0.90))
|
||||
fig.savefig(out_path, dpi=args.dpi)
|
||||
plt.close(fig)
|
||||
print(f"[图1] Native vs SIMD 已保存: {out_path}")
|
||||
return True
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# 图2: 各实现 Execution Time 折线 + 每次 run 的实测点
|
||||
# ------------------------------------------------------------
|
||||
def fig_exec_time(data, sizes, impls, args, metric, out_path):
|
||||
# 只有一个 LEN 时横轴没东西可展开(折线会退化成孤点), 改用"组号"当横轴:
|
||||
# 每组 = 一次进程内多次 run, 于是单尺寸下依然能看到折线与组间离散。
|
||||
single = len(sizes) == 1
|
||||
if single:
|
||||
xkeys = sorted({g for rec in data.values() for g in rec["groups"]})
|
||||
xlabel = f"Group Index (LEN={sizes[0]}; one group = one process run)"
|
||||
xticklabels = [str(g) for g in xkeys]
|
||||
note = ("line = group mean, dots = individual runs\n"
|
||||
f"single LEN={sizes[0]}: X axis = group index")
|
||||
else:
|
||||
xkeys = list(sizes)
|
||||
xlabel = "Working Set a/b/c/out (LEN)"
|
||||
xticklabels = [human_kib(s) for s in sizes]
|
||||
note = "line = per-size mean, dots = individual runs"
|
||||
|
||||
def samples_at(rec, xi):
|
||||
"""第 xi 个横轴位置上的有效样本(计时值必须 > 0)。"""
|
||||
raw = rec["groups"].get(xkeys[xi]) if single else rec
|
||||
if not raw:
|
||||
return np.array([])
|
||||
v = np.asarray(raw[metric], dtype=float)
|
||||
return v[np.isfinite(v) & (v > 0.0)]
|
||||
|
||||
def rec_of(impl, xi):
|
||||
return data.get((impl, sizes[0])) if single else data.get((impl, xkeys[xi]))
|
||||
|
||||
fig, ax = plt.subplots(figsize=(12.5, 6.8))
|
||||
x = np.arange(len(xkeys), dtype=float)
|
||||
n = max(1, len(impls))
|
||||
allv = []
|
||||
|
||||
for k, impl in enumerate(impls):
|
||||
disp, color = style_for(impl)
|
||||
off = (k - (n - 1) / 2.0) * 0.11 # 各实现的散点横向错开, 避免互相压盖
|
||||
xs, ys = [], []
|
||||
for i in range(len(xkeys)):
|
||||
rec = rec_of(impl, i)
|
||||
if not rec:
|
||||
continue
|
||||
v = samples_at(rec, i)
|
||||
if v.size == 0:
|
||||
continue
|
||||
xs.append(x[i])
|
||||
ys.append(float(v.mean()))
|
||||
ax.scatter(np.full(v.shape, x[i] + off), v, s=16, color=color,
|
||||
alpha=0.35, linewidths=0, zorder=2)
|
||||
allv.extend(v.tolist())
|
||||
if xs:
|
||||
ax.plot(xs, ys, marker="o", ms=8, lw=2.2, color=color,
|
||||
label=f"{disp} (mean)", zorder=3)
|
||||
|
||||
ax.set_xticks(x)
|
||||
ax.set_xticklabels(xticklabels, fontsize=9)
|
||||
ax.set_xlabel(xlabel, fontsize=10)
|
||||
ax.set_ylabel(f"Execution Time per Run (us, {metric_txt(metric)})", fontsize=10)
|
||||
ax.set_title("Execution Time Comparison\n" + note, fontsize=12)
|
||||
|
||||
# 动态范围很大(如 numpy 与 SIMD 差百倍)时自动取对数, 否则小值被压成一条线
|
||||
use_log = args.y_scale == "log"
|
||||
if args.y_scale == "auto" and allv:
|
||||
lo, hi = min(allv), max(allv)
|
||||
use_log = lo > 0 and (hi / lo) > 30.0
|
||||
if use_log:
|
||||
ax.set_yscale("log")
|
||||
ax.set_axisbelow(True)
|
||||
ax.grid(alpha=0.3, which="both")
|
||||
|
||||
if ax.get_legend_handles_labels()[0]:
|
||||
ax.legend(fontsize=9.5, ncol=2)
|
||||
fig.suptitle(f"{args.csv_name} · y-axis: {'log' if use_log else 'linear'}"
|
||||
f" · trimmed outliers excluded"
|
||||
f" · implementations offset horizontally", fontsize=10)
|
||||
fig.tight_layout(rect=(0, 0, 1, 0.94))
|
||||
fig.savefig(out_path, dpi=args.dpi)
|
||||
plt.close(fig)
|
||||
print(f"[图2] Execution Time 折线已保存: {out_path}")
|
||||
return True
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# 图3: Overall Mean Execution Time 对比
|
||||
# ------------------------------------------------------------
|
||||
def fig_overall_mean(data, sizes, impls, args, metric, out_path):
|
||||
labels, means, stds, colors = [], [], [], []
|
||||
for impl in impls:
|
||||
vals = [series(data[(impl, s)], metric) for s in sizes if (impl, s) in data]
|
||||
vals = [v for v in vals if v.size]
|
||||
if not vals:
|
||||
continue
|
||||
v = np.concatenate(vals)
|
||||
disp, color = style_for(impl)
|
||||
labels.append(disp)
|
||||
means.append(float(v.mean()))
|
||||
stds.append(float(v.std(ddof=1)) if v.size > 1 else 0.0)
|
||||
colors.append(color)
|
||||
if not labels:
|
||||
print("[图3] 跳过: 没有有效样本")
|
||||
return False
|
||||
|
||||
fig, ax = plt.subplots(figsize=(11.0, 6.4))
|
||||
xs = np.arange(len(labels), dtype=float)
|
||||
ax.bar(xs, means, 0.6, color=colors, alpha=0.9,
|
||||
yerr=stds, capsize=6, ecolor="#333333")
|
||||
ax.set_xticks(xs)
|
||||
ax.set_xticklabels(labels, fontsize=10)
|
||||
ax.set_ylabel(f"Mean Execution Time per Run (us, {metric_txt(metric)})", fontsize=10)
|
||||
ax.set_title("Overall Mean Execution Time\n"
|
||||
"all valid samples pooled · error bar = std dev"
|
||||
" · in-bar xN.NN = relative to fastest", fontsize=12)
|
||||
ax.grid(alpha=0.3, axis="y")
|
||||
annotate_bars(ax, xs, means, spec=",.0f" if max(means) > 10000 else ".1f")
|
||||
|
||||
# 柱身只标"相对最快者的倍数"(越小越快); 最快的那根直接标"最快"
|
||||
fastest = min(means)
|
||||
for x, y in zip(xs, means):
|
||||
mult = y / fastest
|
||||
ax.text(x, y * 0.55, "fastest" if mult <= 1.0001 else f"x{mult:.2f}",
|
||||
ha="center", va="center", fontsize=9.5, color="white", fontweight="bold")
|
||||
fig.suptitle(f"{args.csv_name}\n"
|
||||
f"per-run work is constant (LEN×ITERS×2),"
|
||||
f" so absolute times are comparable", fontsize=10)
|
||||
fig.tight_layout(rect=(0, 0, 1, 0.90))
|
||||
fig.savefig(out_path, dpi=args.dpi)
|
||||
plt.close(fig)
|
||||
print(f"[图3] Overall Mean Execution Time 已保存: {out_path}")
|
||||
return True
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# 图4: Measured GFLOPS 吞吐对比
|
||||
# ------------------------------------------------------------
|
||||
def fig_gflops(data, sizes, impls, args, metric, out_path):
|
||||
labels, means, stds, colors = [], [], [], []
|
||||
for impl in impls:
|
||||
per_run = []
|
||||
for s in sizes:
|
||||
rec = data.get((impl, s))
|
||||
if not rec:
|
||||
continue
|
||||
v = series(rec, metric)
|
||||
if v.size == 0:
|
||||
continue
|
||||
# 该尺寸单 run 的总运算量 = LEN × ITERS × 2 (读 CSV 的 iters, 兼容降规模)
|
||||
flops_run = float(s) * float(rec["iters"]) * 2.0
|
||||
per_run.append(flops_run / (v * 1e-6) / 1e9) # 每次 run 的 GFLOPS
|
||||
if not per_run:
|
||||
continue
|
||||
v = np.concatenate(per_run)
|
||||
disp, color = style_for(impl)
|
||||
labels.append(disp)
|
||||
means.append(float(v.mean()))
|
||||
stds.append(float(v.std(ddof=1)) if v.size > 1 else 0.0)
|
||||
colors.append(color)
|
||||
if not labels:
|
||||
print("[图4] 跳过: 没有有效样本")
|
||||
return False
|
||||
|
||||
fig, ax = plt.subplots(figsize=(11.0, 6.4))
|
||||
xs = np.arange(len(labels), dtype=float)
|
||||
ax.bar(xs, means, 0.6, color=colors, alpha=0.9,
|
||||
yerr=stds, capsize=6, ecolor="#333333")
|
||||
ax.set_xticks(xs)
|
||||
ax.set_xticklabels(labels, fontsize=10)
|
||||
ax.set_ylabel(f"Throughput (GFLOPS, {metric_txt(metric)})", fontsize=10)
|
||||
ax.set_title("Measured GFLOPS Throughput\n"
|
||||
"GFLOPS = LEN×ITERS×2 / per-run seconds · error bar = std dev"
|
||||
" · in-bar xN.NN = relative to fastest", fontsize=12)
|
||||
ax.grid(alpha=0.3, axis="y")
|
||||
annotate_bars(ax, xs, means, spec=".2f")
|
||||
|
||||
# 柱身只标"相对最快者的倍数"(越大越好); 最快的那根直接标"最快"
|
||||
fastest = max(means)
|
||||
for x, y in zip(xs, means):
|
||||
mult = y / fastest
|
||||
ax.text(x, y * 0.55, "fastest" if mult >= 0.9999 else f"x{mult:.2f}",
|
||||
ha="center", va="center", fontsize=9.5, color="white", fontweight="bold")
|
||||
fig.suptitle(f"{args.csv_name}\n"
|
||||
f"higher is better · GFLOPS computed from each run's own CFG",
|
||||
fontsize=10)
|
||||
fig.tight_layout(rect=(0, 0, 1, 0.90))
|
||||
fig.savefig(out_path, dpi=args.dpi)
|
||||
plt.close(fig)
|
||||
print(f"[图4] GFLOPS 吞吐对比已保存: {out_path}")
|
||||
return True
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
p = argparse.ArgumentParser(description="把 run_bench.py 的 CSV 画成 4 张对比图")
|
||||
p.add_argument("csv", nargs="?", default=None, help="CSV 路径(默认取最新 bench_*.csv)")
|
||||
p.add_argument("--metric", default="wall", choices=["wall", "cpu"],
|
||||
help="耗时口径(默认 wall=QPC 墙钟; cpu=QueryThreadCycleTime)")
|
||||
p.add_argument("--out-dir", default=None, help="输出目录(默认与 CSV 同目录)")
|
||||
p.add_argument("--prefix", default=None, help="输出文件名前缀(默认 CSV 文件名)")
|
||||
p.add_argument("--out", default=None,
|
||||
help="兼容旧用法: 传 .png 路径时, 取其所在目录与文件名作为输出目录/前缀")
|
||||
p.add_argument("--y-scale", default="auto", choices=["auto", "log", "linear"],
|
||||
help="图2 纵轴刻度(默认 auto: 动态范围 >30 倍自动取对数)")
|
||||
p.add_argument("--dpi", type=int, default=130, help="输出 PNG 分辨率(默认 130)")
|
||||
args = p.parse_args(argv)
|
||||
|
||||
# ---- 定位 CSV ----
|
||||
if args.csv:
|
||||
csv_path = Path(args.csv)
|
||||
if not csv_path.is_absolute():
|
||||
csv_path = ROOT / csv_path
|
||||
else:
|
||||
cands = sorted(ROOT.glob("bench_*.csv"), key=lambda q: q.stat().st_mtime)
|
||||
if not cands:
|
||||
print("找不到 bench_*.csv; 请先跑 run_bench.py(默认会自动导出 CSV)",
|
||||
file=sys.stderr)
|
||||
return 2
|
||||
csv_path = cands[-1]
|
||||
if not csv_path.exists():
|
||||
print(f"CSV 不存在: {csv_path}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
# ---- 定位输出目录 / 文件名前缀 ----
|
||||
out_dir = csv_path.parent
|
||||
prefix = csv_path.stem
|
||||
if args.out: # 旧用法: --out some.png
|
||||
op = Path(args.out)
|
||||
out_dir = op.parent if str(op.parent) not in ("", ".") else ROOT
|
||||
if op.suffix.lower() == ".png":
|
||||
prefix = op.stem
|
||||
if args.out_dir:
|
||||
out_dir = Path(args.out_dir)
|
||||
if not out_dir.is_absolute():
|
||||
out_dir = ROOT / out_dir
|
||||
if args.prefix:
|
||||
prefix = args.prefix
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
args.csv_name = csv_path.name
|
||||
|
||||
data = load_csv(csv_path)
|
||||
if not data:
|
||||
print(f"CSV 里没有有效样本: {csv_path}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
sizes = sorted({k[1] for k in data})
|
||||
# ADD/MUL/SQRT 的内核变体只在图1 出现 —— 混进图2/3/4 会把曲线糊成一团
|
||||
kernel_impls = {n for _, simd_n, native_n in KERNEL_PAIRS for n in (simd_n, native_n)}
|
||||
known = [name for name, _, _ in IMPL_STYLE]
|
||||
impls = [n for n in known if any(k[0] == n for k in data)]
|
||||
impls += sorted({k[0] for k in data} - set(known) - kernel_impls) # 未登记的实现也不丢
|
||||
|
||||
print(f"CSV : {csv_path}")
|
||||
print(f"尺寸 : {sizes}")
|
||||
print(f"实现 : {impls}")
|
||||
print(f"口径 : {args.metric} 输出目录: {out_dir} 前缀: {prefix}")
|
||||
|
||||
made = []
|
||||
if fig_simd_vs_scalar(data, sizes, args, args.metric,
|
||||
out_dir / f"{prefix}_1_simd_vs_scalar.png"):
|
||||
made.append(1)
|
||||
if fig_exec_time(data, sizes, impls, args, args.metric,
|
||||
out_dir / f"{prefix}_2_exec_time.png"):
|
||||
made.append(2)
|
||||
if fig_overall_mean(data, sizes, impls, args, args.metric,
|
||||
out_dir / f"{prefix}_3_overall_mean.png"):
|
||||
made.append(3)
|
||||
if fig_gflops(data, sizes, impls, args, args.metric,
|
||||
out_dir / f"{prefix}_4_gflops.png"):
|
||||
made.append(4)
|
||||
|
||||
# ---- 控制台再来一份文字小结, 免得每次都要开图 ----
|
||||
print(f"\n{'impl':<13} {'LEN':>9} {'工作集KiB':>10} {'每run us':>11} "
|
||||
f"{'GFLOPS':>8} {'CPU/墙钟':>9} {'trimmed':>8}")
|
||||
for s in sizes:
|
||||
for impl in impls:
|
||||
rec = data.get((impl, s))
|
||||
if not rec:
|
||||
continue
|
||||
wv = series(rec, "wall")
|
||||
cv = series(rec, "cpu")
|
||||
if wv.size == 0:
|
||||
continue
|
||||
flops_run = float(s) * float(rec["iters"]) * 2.0
|
||||
g = flops_run / (wv * 1e-6) / 1e9
|
||||
ratio = float(np.median(cv / wv[:cv.size])) if cv.size == wv.size else float("nan")
|
||||
print(f"{impl:<13} {s:>9} {s * 32 / 1024:>10.1f} {np.median(wv):>11.1f} "
|
||||
f"{np.mean(g):>8.2f} {ratio:>9.4f} {rec['trimmed']:>8}")
|
||||
print(f"\n共生成 {len(made)} 张图: {made}")
|
||||
return 0 if made else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "ViperSpeedBenchmark",
|
||||
"version": "1.0.0",
|
||||
"description": "同内核多实现测速基准: out[i]=a[i]*b[i]+c[i] (len=16384, iters=65536, 每次run≈2.147e9 flops; 工作集512KiB 常驻私有L2)。Viper 侧第4个命令行参数 mode 可切 8 个内核: FMA/ADD/MUL/SQRT 各一对 SIMD(simd_*_array) 与 原生(scalar_*_array), 用于 SIMD 加速比对照; mode=0 的 FMA SIMD 与 C/C++/Python 对齐",
|
||||
"source_dir": "./App",
|
||||
"build_dir": "./.tpv_build",
|
||||
"compiler": {
|
||||
"cmd": "llc",
|
||||
"flags": [
|
||||
"-filetype=obj"
|
||||
]
|
||||
},
|
||||
"linker": {
|
||||
"cmd": "D:/msys64/mingw64/bin/clang++.exe",
|
||||
"flags": [
|
||||
"-fuse-ld=lld",
|
||||
"-O2",
|
||||
"-lmsvcrt",
|
||||
"-lpthread",
|
||||
"-lmingwex",
|
||||
"-lkernel32",
|
||||
"-Wl,--stack,0x1000000"
|
||||
],
|
||||
"output": "app.exe"
|
||||
},
|
||||
"includes": [
|
||||
"../../includes"
|
||||
],
|
||||
"target": {
|
||||
"triple": "x86_64-pc-windows-gnu",
|
||||
"datalayout": "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
|
||||
},
|
||||
"options": {
|
||||
"slice_level": 3,
|
||||
"sha1_slice_level": 1,
|
||||
"target": "llvm",
|
||||
"strict_mode": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,833 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
run_bench.py - "同内核多实现测速 + 组间差值核验" 总编排脚本
|
||||
(ViperSpeedBenchmark, 2026-09 抗噪强化版)
|
||||
|
||||
噪声治理设计:
|
||||
* 每组(默认 5 组)只启动【一次】子进程: <exe> <runs_per_group=10>。
|
||||
该进程内先做 2 轮满负荷热身, 再连续计时并打印 10 行 RESULT ——
|
||||
组原始数据来自同进程稳态运行, 消除了旧版"每次 run 一次冷进程"的
|
||||
冷页/TLB/启动/频率爬升噪声。
|
||||
* 【线程绑定】每个实现在进程入口调 SetThreadAffinityMask 把自身绑到
|
||||
第一个可用逻辑核(结果以 AFFINITY <core> 行回报), 消除跨核迁移、
|
||||
大小核异构与缓存失效带来的抖动。绑定失败回报 -1, 不致命。
|
||||
* 【双计时】每次 run 同时采集:
|
||||
wall = QPC 墙钟微秒 (含被抢占/被降频的时间, 对调度敏感)
|
||||
cpu = QueryThreadCycleTime 线程 CPU 微秒
|
||||
(只计本线程真正执行过的周期, 对抢占/降频
|
||||
不敏感; TSC 频率由各实现入口自行标定)
|
||||
两种都进 RESULT 行、都做统计、都写 CSV; --metric 选谁当"主指标"决定
|
||||
组均值/总体/差值核验与 PASS/FAIL。
|
||||
注: 不用 GetThreadTimes —— 它的记账粒度跟随调度时钟节拍(本机实测硬地板
|
||||
15.625ms), 几十毫秒的 run 误差 ±40%, 且 Win10 2004+ 起无法用
|
||||
timeBeginPeriod 改善。
|
||||
* 【离群裁剪】每组先按中位数上界裁掉离群点(默认 保留 <= 中位数×1.2 的样本),
|
||||
再算组统计量。裁剪系数由 --trim-ratio 控制, <=0 关闭。
|
||||
* 【统计量可选】--stat mean(默认, 裁剪后算术平均) / median / geomean。
|
||||
双峰分布(一半样本被降频)时 median 的抗噪能力远强于算术平均。
|
||||
|
||||
* 【多尺寸扫描】--sizes 接受逗号分隔的 LEN 列表; 只调 LEN, ITERS 自动配平
|
||||
(ITERS = 2^30 / LEN) 使单 run 恒为 ≈2.147e9 flops, 于是 GFLOPS 跨尺寸可比。
|
||||
LEN 决定工作集(四份缓冲共 LEN*32 字节): 1024=L1 / 16384=L2 / 262144 以上=L3-DRAM。
|
||||
* 【自动导 CSV】默认写成带时间戳的 bench_<YYYYmmdd_HHMMSS>.csv (--csv 指定名字,
|
||||
--no-csv 关闭), 可直接喂给 plot_bench.py 出图。
|
||||
|
||||
每个可执行统一协议(进程开头一次):
|
||||
CFG <len> <iters>
|
||||
AFFINITY <core> # -1 = 绑定失败
|
||||
TSC <hz> # 入口标定出的 TSC 频率; 0 = 标定失败
|
||||
MODE <0|1> # 仅 Viper 输出: 0=simd_fma_array / 1=scalar_fma_array
|
||||
之后每次 run 一行:
|
||||
RESULT <wall_us> <cpu_us> <flops>
|
||||
全部结束后:
|
||||
CHECK <sum>
|
||||
|
||||
实现集合(全部 Viper 变体共用同一个 app.exe, 只差命令行第 4 个参数 mode):
|
||||
viper / viper_scalar FMA (out = a*b + c) mode 0 / 1
|
||||
viper_add / viper_scalar_add ADD (out = a + b) mode 2 / 3
|
||||
viper_mul / viper_scalar_mul MUL (out = a * b) mode 4 / 5
|
||||
viper_sqrt / viper_scalar_sqrt SQRT (out = sqrt(a)) mode 6 / 7
|
||||
c / cpp 手写 C/C++, -O2 -mavx2 -mfma -ffp-contract=fast
|
||||
py Python numpy(就地 ufunc); 无 numpy 则纯 Python 降规模回退
|
||||
每对 `<op>` / `scalar_<op>` 是同一数学内核的"向量化 vs 未向量化"对照 —— 标量版用
|
||||
对应标量指令(vaddsd/vmulsd/vsqrtsd/vfmadd213sd), 逐元素结果与 SIMD 版逐位一致,
|
||||
CHECK 可直接对照; plot_bench.py 图1 就是用它们算 ADD/MUL/SQRT 的 SIMD 加速比。
|
||||
|
||||
统计(每实现): 组均值 / 组内 max-min / 组-总体 / 相邻组差 / 总体均值 /
|
||||
样本标准差 / 最大偏差 / 裁剪剔除数; 差值核验规则同前(容差默认 0.05)。
|
||||
|
||||
首组处理: 首组(组1)默认【仅汇报, 不计入】总体均值/样本标准差/差值核验
|
||||
(规避首组效应: 第一次进程启动的冷启动/频率爬升)。需要计入加 --include-first-group。
|
||||
|
||||
用法示例:
|
||||
python run_bench.py
|
||||
python run_bench.py --impl c,cpp,py --groups 3 --runs-per-group 5
|
||||
python run_bench.py --impl viper,c,cpp,py --tol 0.05 --csv out.csv --flops-check
|
||||
python run_bench.py --metric cpu --stat median # 抗噪口径
|
||||
python run_bench.py --trim-ratio 0 --stat mean # 关闭裁剪
|
||||
python run_bench.py --impl py --py-mode pure # 强制纯 Python 回退
|
||||
"""
|
||||
import argparse
|
||||
import csv
|
||||
import datetime
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
CC_DIR_DEFAULT = "D:/msys64/mingw64/bin"
|
||||
|
||||
DEFAULT_LEN = 16384
|
||||
DEFAULT_SIZES = "16384"
|
||||
TOTAL_FLOPS_TARGET = 1 << 31 # 2147483648 ≈ 2.147e9, 每次 run 恒定的运算量
|
||||
EXPECT_TOTAL_FLOPS = float(TOTAL_FLOPS_TARGET)
|
||||
|
||||
CFG_RE = re.compile(r"^\s*CFG\s+(\d+)\s+(\d+)")
|
||||
AFFINITY_RE = re.compile(r"^\s*AFFINITY\s+(-?\d+)")
|
||||
# 注意: 内层用非捕获分组, 使 RESULT 的第 1/2/3 个数字分别落在 group(1)/group(2)/group(3)
|
||||
NUM = r"[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?"
|
||||
RESULT_RE = re.compile(rf"^\s*RESULT\s+({NUM})\s+({NUM})\s+({NUM})")
|
||||
CHECK_RE = re.compile(rf"^\s*CHECK\s+({NUM})")
|
||||
TSC_RE = re.compile(rf"^\s*TSC\s+({NUM})")
|
||||
# Viper 专有: MODE <0|1> —— 0=simd_fma_array / 1=scalar_fma_array
|
||||
MODE_RE = re.compile(r"^\s*MODE\s+(-?\d+)")
|
||||
|
||||
METRIC_IDX = {"wall": 0, "cpu": 1}
|
||||
METRIC_LABEL = {"wall": "墙钟(QPC)", "cpu": "线程CPU(QueryThreadCycleTime)"}
|
||||
STAT_LABEL = {"mean": "算术平均(裁剪后)", "median": "中位数", "geomean": "几何均值"}
|
||||
|
||||
|
||||
def parse_process_output(text, expected_runs, impl):
|
||||
"""解析单个子进程 stdout -> dict。
|
||||
|
||||
协议: 1 行 CFG + 可选 1 行 AFFINITY + expected_runs 行 RESULT + 1 行 CHECK。
|
||||
RESULT = <wall_us> <cpu_us> <flops>。
|
||||
解析失败抛 ValueError(由调用方转成带 traceback 的失败记录)。
|
||||
"""
|
||||
cfg = None
|
||||
affinity = None
|
||||
tsc_hz = None
|
||||
kernel_mode = None # Viper 专有: 0=SIMD / 1=Scalar; 其它实现无此行
|
||||
samples = [] # (wall_us, cpu_us, flops)
|
||||
checks = []
|
||||
for line in text.splitlines():
|
||||
m = RESULT_RE.match(line)
|
||||
if m:
|
||||
samples.append((float(m.group(1)), float(m.group(2)), float(m.group(3))))
|
||||
continue
|
||||
m = CHECK_RE.match(line)
|
||||
if m:
|
||||
checks.append(float(m.group(1)))
|
||||
continue
|
||||
m = AFFINITY_RE.match(line)
|
||||
if m and affinity is None:
|
||||
affinity = int(m.group(1))
|
||||
continue
|
||||
m = TSC_RE.match(line)
|
||||
if m and tsc_hz is None:
|
||||
tsc_hz = float(m.group(1))
|
||||
continue
|
||||
m = MODE_RE.match(line)
|
||||
if m and kernel_mode is None:
|
||||
kernel_mode = int(m.group(1))
|
||||
continue
|
||||
m = CFG_RE.match(line)
|
||||
if m and cfg is None:
|
||||
cfg = (int(m.group(1)), int(m.group(2)))
|
||||
if cfg is None:
|
||||
raise ValueError(
|
||||
f"[{impl}] stdout 未找到 CFG <len> <iters> 行。stdout 前 20 行:\n"
|
||||
+ "\n".join(text.splitlines()[:20]))
|
||||
if len(samples) != expected_runs:
|
||||
raise ValueError(
|
||||
f"[{impl}] 期望 {expected_runs} 行 RESULT, 实际 {len(samples)} 行"
|
||||
f"(CFG={cfg[0]}x{cfg[1]})。stdout 前 40 行:\n"
|
||||
+ "\n".join(text.splitlines()[:40]))
|
||||
if not checks:
|
||||
raise ValueError(
|
||||
f"[{impl}] stdout 未找到 CHECK 行。stdout 前 40 行:\n"
|
||||
+ "\n".join(text.splitlines()[:40]))
|
||||
return {
|
||||
"cfg": cfg,
|
||||
"affinity": affinity,
|
||||
"tsc_hz": tsc_hz,
|
||||
"kernel_mode": kernel_mode,
|
||||
"samples": samples, # list[(wall_us, cpu_us, flops)]
|
||||
"check": checks[0],
|
||||
}
|
||||
|
||||
|
||||
class BenchError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def sh_ok(cmd, cwd, env=None, timeout=1800):
|
||||
"""执行命令, 成功返回 CompletedProcess; 失败抛 BenchError(带 stderr)。"""
|
||||
try:
|
||||
p = subprocess.run(cmd, cwd=str(cwd), capture_output=True,
|
||||
text=True, errors="replace", timeout=timeout, env=env)
|
||||
except FileNotFoundError as e:
|
||||
raise BenchError(f"找不到可执行文件: {cmd[0]}\n (请确认路径/工具链已安装)")
|
||||
except subprocess.TimeoutExpired:
|
||||
raise BenchError(f"命令超时({timeout}s): {cmd}")
|
||||
return p
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# 编译 / 运行定义
|
||||
# ------------------------------------------------------------
|
||||
# C/C++ 与 Viper 同口径: 允许 FMA 收缩(单趟融合乘加, 单次舍入)。
|
||||
# -ffp-contract=fast 让 a[i]*b[i]+c[i] 生成 vfmadd(而不是 mul+add 两次舍入);
|
||||
# -mfma 显式打开 FMA 指令集(只给 -mavx2 不保证启用 FMA)。
|
||||
# 要回到"两次舍入"的旧口径做 A/B, 把 -ffp-contract=fast 换成 =off 并去掉 -mfma 即可。
|
||||
NATIVE_FLAGS = ["-O2", "-mavx2", "-mfma", "-ffp-contract=fast"]
|
||||
|
||||
|
||||
# Viper 内核实现名 -> (传给 app.exe 的第 4 个参数 mode, 显示名)。
|
||||
# 全部共用同一个 .tpv_build/output/app.exe, 只差这一个 mode 参数 —— 除"跑哪份内核"
|
||||
# 外没有任何差异, 这是 SIMD vs 原生 对照可信的前提(见 App/main.vp 的 KERNEL_* 常量)。
|
||||
# 命名规则: `<op>` = SIMD, `scalar_<op>` = 同操作的标量(原生)对照。
|
||||
VIPER_KERNELS = [
|
||||
("viper", 0, "FMA SIMD (vipersimd.simd_fma_array)"),
|
||||
("viper_scalar", 1, "FMA 原生 (vipersimd.scalar_fma_array)"),
|
||||
("viper_add", 2, "ADD SIMD (vipersimd.simd_add_array)"),
|
||||
("viper_scalar_add", 3, "ADD 原生 (vipersimd.scalar_add_array)"),
|
||||
("viper_mul", 4, "MUL SIMD (vipersimd.simd_mul_array)"),
|
||||
("viper_scalar_mul", 5, "MUL 原生 (vipersimd.scalar_mul_array)"),
|
||||
("viper_sqrt", 6, "SQRT SIMD (vipersimd.simd_sqrt_array)"),
|
||||
("viper_scalar_sqrt", 7, "SQRT 原生 (vipersimd.scalar_sqrt_array)"),
|
||||
]
|
||||
VIPER_MODE = {name: mode for name, mode, _ in VIPER_KERNELS}
|
||||
VIPER_DISP = {name: disp for name, _, disp in VIPER_KERNELS}
|
||||
|
||||
# 默认跑"图1 + 图2/3/4 所需的全部实现": 8 个 Viper 内核变体(FMA/ADD/MUL/SQRT 各一对
|
||||
# SIMD/原生) + C/C++/Python。图1 需要 ADD/MUL/SQRT 的成对数据, 所以默认就带上 ——
|
||||
# 否则跑完默认命令图1 会以"缺数据"整张跳过, 体验很差。
|
||||
# 只要图2/3/4 时, 用 --impl viper,viper_scalar,c,cpp,py 可跳过那 6 个内核变体。
|
||||
DEFAULT_IMPLS = ",".join([n for n, _, _ in VIPER_KERNELS] + ["c", "cpp", "py"])
|
||||
|
||||
|
||||
def build_native(cc_dir, impl, root):
|
||||
"""编译 bench_c.c / bench_cpp.cpp 到 .native_build/, 返回 exe 路径。
|
||||
|
||||
源码或编译选项变化时才重编(用 .flags 标记文件记录上次编译选项)。
|
||||
"""
|
||||
build_dir = root / ".native_build"
|
||||
build_dir.mkdir(parents=True, exist_ok=True)
|
||||
src = root / ("bench_c.c" if impl == "c" else "bench_cpp.cpp")
|
||||
exe = build_dir / ("bench_c.exe" if impl == "c" else "bench_cpp.exe")
|
||||
cc = cc_dir / ("clang.exe" if impl == "c" else "clang++.exe")
|
||||
flags = NATIVE_FLAGS + [
|
||||
"-std=c11" if impl == "c" else "-std=c++17",
|
||||
"-lmsvcrt", "-lmingwex", "-lkernel32", "-lm",
|
||||
]
|
||||
flags_marker = build_dir / (".flags_" + impl)
|
||||
need = (not exe.exists()
|
||||
or not flags_marker.exists()
|
||||
or flags_marker.read_text(encoding="utf-8") != "\n".join(flags)
|
||||
or exe.stat().st_mtime < src.stat().st_mtime)
|
||||
if not need:
|
||||
return exe
|
||||
cmd = [str(cc)] + flags + [str(src), "-o", str(exe)]
|
||||
p = sh_ok(cmd, root)
|
||||
if p.returncode != 0:
|
||||
tail = "\n".join(p.stderr.splitlines()[-25:])
|
||||
raise BenchError(f"编译失败(exit={p.returncode}): {' '.join(cmd)}\n"
|
||||
f"stderr 尾部:\n{tail}")
|
||||
flags_marker.write_text("\n".join(flags), encoding="utf-8")
|
||||
return exe
|
||||
|
||||
|
||||
def iters_for(size):
|
||||
"""给定 LEN, 求出让单 run 恒为 TOTAL_FLOPS_TARGET 的 ITERS。
|
||||
|
||||
必须整除: 否则各尺寸的"每次 run 运算量"就不一致, GFLOPS 横向不可比。
|
||||
取 2 的幂做 LEN 时永远整除(ITERS = 2^30 / LEN)。
|
||||
"""
|
||||
denom = 2 * size
|
||||
if size < 4 or size % 4 != 0:
|
||||
raise BenchError(f"LEN={size} 非法: 需 >=4 且为 4 的倍数(内核按 4-lane ymm 分批)")
|
||||
if TOTAL_FLOPS_TARGET % denom != 0:
|
||||
raise BenchError(f"LEN={size} 无法整除: {TOTAL_FLOPS_TARGET} / (2*{size}) 不是整数")
|
||||
return TOTAL_FLOPS_TARGET // denom
|
||||
|
||||
|
||||
def viper_exe(root):
|
||||
return root / ".tpv_build" / "output" / "app.exe"
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# 统计与核验
|
||||
# ------------------------------------------------------------
|
||||
def mean(vals):
|
||||
return sum(vals) / len(vals)
|
||||
|
||||
|
||||
def sample_stddev(vals, m):
|
||||
if len(vals) < 2:
|
||||
return 0.0
|
||||
s = sum((v - m) ** 2 for v in vals)
|
||||
return math.sqrt(s / (len(vals) - 1))
|
||||
|
||||
|
||||
def median(vals):
|
||||
if not vals:
|
||||
return 0.0
|
||||
s = sorted(vals)
|
||||
n = len(s)
|
||||
mid = n // 2
|
||||
return s[mid] if n % 2 else (s[mid - 1] + s[mid]) / 2.0
|
||||
|
||||
|
||||
def geomean(vals):
|
||||
"""几何均值; 出现 <=0 的样本时退回 0(计时值理论上恒 >0)。"""
|
||||
if not vals:
|
||||
return 0.0
|
||||
if any(v <= 0.0 for v in vals):
|
||||
return 0.0
|
||||
return math.exp(sum(math.log(v) for v in vals) / len(vals))
|
||||
|
||||
|
||||
def pick_stat(name, vals):
|
||||
if not vals:
|
||||
return 0.0
|
||||
if name == "median":
|
||||
return median(vals)
|
||||
if name == "geomean":
|
||||
return geomean(vals)
|
||||
return mean(vals)
|
||||
|
||||
|
||||
def trim_group(vals, trim_ratio):
|
||||
"""按中位数上界裁掉离群点: 保留 v <= median(vals) * trim_ratio。
|
||||
|
||||
返回 (kept, dropped_positions)。trim_ratio <= 0 时等价于不裁剪。
|
||||
至少保留中位数本身, 所以 kept 不会为空。
|
||||
"""
|
||||
if trim_ratio <= 0.0 or not vals:
|
||||
return list(vals), []
|
||||
hi = median(vals) * trim_ratio
|
||||
kept = [v for v in vals if v <= hi]
|
||||
dropped = [i for i, v in enumerate(vals) if v > hi]
|
||||
if not kept:
|
||||
return list(vals), []
|
||||
return kept, dropped
|
||||
|
||||
|
||||
def analyze_impl(groups_samples, tol, drop_first, trim_ratio, stat_name, metric):
|
||||
"""groups_samples: list[list[(wall,cpu,flops)]], 每 list = 一个进程内 N 次 run。
|
||||
|
||||
流程: 取主指标 -> 每组按中位数上界裁剪离群点 -> 用 stat_name 统计。
|
||||
drop_first=True 时, 首组(索引 0)【仅汇报】: 不计入总体/标准差/最大偏差,
|
||||
也不参与差值核验(规避首组效应)。只有一组时自动不排除。
|
||||
"""
|
||||
idx = METRIC_IDX[metric]
|
||||
other_idx = 1 - idx
|
||||
groups = [[s[idx] for s in g] for g in groups_samples]
|
||||
|
||||
drop_first = drop_first and len(groups) > 1
|
||||
|
||||
trimmed = [trim_group(vals, trim_ratio) for vals in groups]
|
||||
stat_groups = trimmed[1:] if drop_first else trimmed
|
||||
|
||||
flat = [v for kept, _ in stat_groups for v in kept]
|
||||
overall = pick_stat(stat_name, flat)
|
||||
overall_std = sample_stddev(flat, overall)
|
||||
max_dev = max((abs(v - overall) for v in flat), default=0.0)
|
||||
n_dropped = sum(len(dropped) for _, dropped in stat_groups)
|
||||
|
||||
group_stats = []
|
||||
for g, (kept, dropped) in enumerate(trimmed, start=1):
|
||||
gm = pick_stat(stat_name, kept)
|
||||
group_stats.append({
|
||||
"idx": g,
|
||||
"raw": groups[g - 1],
|
||||
"kept": kept,
|
||||
"dropped_idx": dropped,
|
||||
"mean": mean(kept),
|
||||
"median": median(kept),
|
||||
"geomean": geomean(kept),
|
||||
"gstat": gm,
|
||||
"spread": (max(kept) - min(kept)) if kept else 0.0,
|
||||
"dev": gm - overall,
|
||||
"in_stat": (not drop_first) or g > 1, # False 仅组1 被排除时
|
||||
})
|
||||
|
||||
adj = [group_stats[k]["gstat"] - group_stats[k - 1]["gstat"]
|
||||
for k in range(1, len(group_stats))]
|
||||
|
||||
def rel(x):
|
||||
return abs(x) / abs(overall) if overall != 0.0 else (0.0 if x == 0.0 else float("inf"))
|
||||
|
||||
# 差值核验只对计入统计的组判定; 首组(含其相邻差)不作为违规依据
|
||||
dev_viol = [gs["idx"] for gs in group_stats if gs["in_stat"] and rel(gs["dev"]) > tol]
|
||||
adj_pairs = [(k, k + 1) for k in range(len(adj))
|
||||
if rel(adj[k]) > tol and not (drop_first and k == 0)]
|
||||
passed = (not dev_viol) and (not adj_pairs)
|
||||
|
||||
# 另一指标的总体值(同一裁剪与统计口径), 只用于汇总对照
|
||||
other_flat = []
|
||||
for i, g in enumerate(groups_samples):
|
||||
if drop_first and i == 0:
|
||||
continue
|
||||
kept, _ = trim_group([s[other_idx] for s in g], trim_ratio)
|
||||
other_flat.extend(kept)
|
||||
overall_other = pick_stat(stat_name, other_flat)
|
||||
|
||||
return {
|
||||
"metric": metric, "other_metric": "cpu" if metric == "wall" else "wall",
|
||||
"stat": stat_name, "trim_ratio": trim_ratio,
|
||||
"overall": overall, "overall_other": overall_other,
|
||||
"overall_std": overall_std, "max_dev": max_dev,
|
||||
"n": len(flat), "n_dropped": n_dropped,
|
||||
"n_stat_groups": len(stat_groups), "drop_first": drop_first,
|
||||
"group_stats": group_stats, "adj": adj,
|
||||
"passed": passed, "dev_viol": dev_viol, "adj_pairs": adj_pairs,
|
||||
"_samples": groups_samples, # 供 CSV / flops-check
|
||||
}
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# 主流程
|
||||
# ------------------------------------------------------------
|
||||
def build_arg_parser():
|
||||
p = argparse.ArgumentParser(
|
||||
description="ViperSpeedBenchmark 编排: 同内核多实现测速 + 组间差值核验"
|
||||
"(每组=单进程内多 run, 线程绑定 + 双计时 + 离群裁剪)")
|
||||
p.add_argument("--groups", type=int, default=5, help="组数(默认 5; 每组=一次进程启动)")
|
||||
p.add_argument("--runs-per-group", type=int, default=10,
|
||||
help="每组连续 run 次数 = 每次进程启动后打印的 RESULT 行数(默认 10)")
|
||||
p.add_argument("--sizes", default=DEFAULT_SIZES,
|
||||
help="逗号分隔的 LEN 列表(多尺寸扫描), 默认 %(default)s。"
|
||||
"每个 LEN 的 ITERS 自动取值, 使单 run 恒为 ≈2.147e9 flops; "
|
||||
"LEN 决定工作集(四份缓冲共 LEN*32 字节), 例如 "
|
||||
"1024=L1 / 16384=L2 / 262144 以上=L3-DRAM")
|
||||
p.add_argument("--impl", default=DEFAULT_IMPLS,
|
||||
help="逗号分隔实现子集, 默认 %(default)s。全部 Viper 变体共用同一个 "
|
||||
"app.exe, 只差命令行第 4 个参数 mode: "
|
||||
"viper/viper_scalar=FMA, viper_add/viper_scalar_add=ADD, "
|
||||
"viper_mul/viper_scalar_mul=MUL, viper_sqrt/viper_scalar_sqrt=SQRT "
|
||||
"(前者 SIMD / 后者原生); 另有 c / cpp / py。"
|
||||
"默认已含全部内核变体(plot_bench.py 图1 需要它们的成对数据)")
|
||||
p.add_argument("--tol", type=float, default=0.05,
|
||||
help="相对容差, 组对总体与相邻组差值判定阈值(默认 0.05=5%%)")
|
||||
p.add_argument("--include-first-group", action="store_true",
|
||||
help="把首组(组1)计入统计与差值核验(默认排除: 组1仅汇报, 规避首组效应)")
|
||||
p.add_argument("--metric", default="wall", choices=["wall", "cpu"],
|
||||
help="主指标(默认 wall=QPC 墙钟; cpu=QueryThreadCycleTime 线程 CPU 时间)")
|
||||
p.add_argument("--stat", default="mean", choices=["mean", "median", "geomean"],
|
||||
help="组/总体统计量(默认 mean=裁剪后算术平均; 双峰分布建议 median)")
|
||||
p.add_argument("--trim-ratio", type=float, default=1.2,
|
||||
help="组内离群裁剪上界 = 组内中位数 × 该系数(默认 1.2); <=0 关闭裁剪")
|
||||
p.add_argument("--csv", default=None,
|
||||
help="CSV 输出路径; 省略时自动写成 bench_<时间戳>.csv")
|
||||
p.add_argument("--no-csv", action="store_true", help="完全不写 CSV")
|
||||
p.add_argument("--cc-dir", default=CC_DIR_DEFAULT, help="clang/clang++ 目录")
|
||||
p.add_argument("--report", default="bench_report.txt", help="日志输出文件")
|
||||
p.add_argument("--py-mode", default=None, choices=["auto", "numpy", "pure"],
|
||||
help="覆盖 bench_py.py 的运行模式(auto/numpy/pure)")
|
||||
p.add_argument("--viper-exe", default=None,
|
||||
help="覆盖 Viper 产物路径(默认 .tpv_build/output/app.exe)")
|
||||
p.add_argument("--flops-check", action="store_true",
|
||||
help="核对 RESULT 行的 flops 字段是否与 CFG 总运算量一致, "
|
||||
"并报告 flops≈2e9(时长≈1s)的行占比及降规模提示")
|
||||
return p
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
for stream in (sys.stdout, sys.stderr):
|
||||
try:
|
||||
stream.reconfigure(encoding="utf-8", errors="replace")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
args = build_arg_parser().parse_args(argv)
|
||||
|
||||
groups_n = max(1, args.groups)
|
||||
runs_n = max(1, args.runs_per_group)
|
||||
tol = args.tol
|
||||
metric = args.metric
|
||||
stat_name = args.stat
|
||||
trim_ratio = args.trim_ratio
|
||||
drop_first = not args.include_first_group # 首组效应规避: 组1仅汇报不计入统计
|
||||
impl_order = [name for name, _, _ in VIPER_KERNELS] + ["c", "cpp", "py"]
|
||||
requested = [x.strip().lower() for x in args.impl.split(",") if x.strip()]
|
||||
if requested == ["all"]:
|
||||
requested = list(impl_order)
|
||||
unknown = [x for x in requested if x not in impl_order]
|
||||
if unknown:
|
||||
print(f"未知实现名: {unknown} (可选: {','.join(impl_order)})", file=sys.stderr)
|
||||
return 2
|
||||
impls = [x for x in impl_order if x in requested]
|
||||
|
||||
# ---- 尺寸列表: LEN 决定工作集(四份缓冲共 LEN*32 字节), ITERS 自动算 ----
|
||||
sizes = []
|
||||
for tok in [t.strip() for t in args.sizes.split(",") if t.strip()]:
|
||||
try:
|
||||
sizes.append(int(tok))
|
||||
except ValueError:
|
||||
print(f"--sizes 里有非整数项: {tok!r}", file=sys.stderr)
|
||||
return 2
|
||||
if not sizes:
|
||||
sizes = [DEFAULT_LEN]
|
||||
try:
|
||||
iters_map = {s: iters_for(s) for s in sizes}
|
||||
except BenchError as e:
|
||||
print(str(e), file=sys.stderr)
|
||||
return 2
|
||||
|
||||
cc_dir = Path(args.cc_dir)
|
||||
vpy_exe = Path(args.viper_exe) if args.viper_exe else viper_exe(ROOT)
|
||||
|
||||
env = dict(os.environ)
|
||||
if args.py_mode:
|
||||
env["VSB_PY_MODE"] = args.py_mode
|
||||
|
||||
lines = []
|
||||
def emit(s=""):
|
||||
lines.append(s)
|
||||
print(s, flush=True)
|
||||
|
||||
stamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
csv_stamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
emit("=" * 78)
|
||||
emit(" ViperSpeedBenchmark - run_bench.py (线程绑定 + 双计时 + 离群裁剪版)")
|
||||
emit(f" 时间 : {stamp}")
|
||||
emit(f" 参数 : groups={groups_n}, runs/group={runs_n}, tol={tol:.1%}")
|
||||
emit(f" 实现 : {','.join(impls)}")
|
||||
emit(f" 负载 : 每次 run ≈ {EXPECT_TOTAL_FLOPS:.3e} flops(LEN×ITERS×2 恒定, 跨尺寸可比)")
|
||||
for s in sizes:
|
||||
emit(f" LEN={s:>8} ITERS={iters_map[s]:>8} 工作集={s * 32 / 1024:>9.1f} KiB")
|
||||
emit(" 采集方式 : 每组启动一次子进程, 进程内 2 轮满热身 + 连续计时 N 次")
|
||||
emit(" 线程绑定 : 子进程入口 SetThreadAffinityMask 绑到首个可用逻辑核(AFFINITY 行回报)")
|
||||
emit(f" 主指标 : {METRIC_LABEL[metric]} (另一指标同时采集, 汇总里对照)")
|
||||
emit(f" 统计量 : {STAT_LABEL[stat_name]}")
|
||||
emit(" 离群裁剪 : " + (f"组内保留 <= 中位数×{trim_ratio:g} 的样本" if trim_ratio > 0
|
||||
else "关闭(--trim-ratio<=0)"))
|
||||
emit(" 统计口径 : " + ("首组(组1)仅汇报不计入统计(规避首组效应)"
|
||||
if drop_first else "全部组计入统计"))
|
||||
emit("=" * 78)
|
||||
|
||||
# ---- 编译 / 检测 ----
|
||||
exe_map = {}
|
||||
compile_notes = []
|
||||
for impl in ("c", "cpp"):
|
||||
if impl not in impls:
|
||||
continue
|
||||
emit(f"[{impl}] 编译 bench_{'c.c' if impl == 'c' else 'cpp.cpp'} -> .native_build/")
|
||||
try:
|
||||
exe_map[impl] = build_native(cc_dir, impl, ROOT)
|
||||
emit(f"[{impl}] 编译成功: {exe_map[impl]}")
|
||||
except BenchError as e:
|
||||
compile_notes.append((impl, str(e)))
|
||||
emit(f"[{impl}] 编译失败: {e}")
|
||||
emit(f"[{impl}] 请确认 clang 工具链可用(--cc-dir 可指定目录), 将跳过该实现。")
|
||||
viper_wanted = [im for im in impls if im in VIPER_MODE]
|
||||
if viper_wanted:
|
||||
if vpy_exe.exists():
|
||||
for name in viper_wanted:
|
||||
exe_map[name] = vpy_exe
|
||||
emit(f"[viper] 找到产物: {vpy_exe}")
|
||||
emit(f" {len(viper_wanted)} 个内核变体共用此 exe, 只差命令行 mode 参数: "
|
||||
+ ", ".join(f"{n}(mode={VIPER_MODE[n]})" for n in viper_wanted))
|
||||
else:
|
||||
emit(f"[viper] 未找到 Viper 产物: {vpy_exe}")
|
||||
emit(" 请先在本工程目录用 tpv 构建 project.vpj(生成 .tpv_build/output/app.exe),")
|
||||
emit(" 本次将跳过全部 Viper 内核变体。")
|
||||
|
||||
# ---- 逐尺寸 × 逐实现跑测(每组=一次进程启动) ----
|
||||
# 按"尺寸优先"展开: 同一尺寸下各实现紧挨着跑, 便于横向对比
|
||||
results = {} # key = (size, impl)
|
||||
prev_size = None
|
||||
for size, impl in [(sz, im) for sz in sizes for im in impls]:
|
||||
iters_n = iters_map[size]
|
||||
if size != prev_size:
|
||||
emit("")
|
||||
emit("#" * 78)
|
||||
emit(f"# 工作尺寸 LEN={size} ITERS={iters_n} 四份缓冲共 {size * 32 / 1024:.1f} KiB")
|
||||
emit("#" * 78)
|
||||
prev_size = size
|
||||
# py 始终可运行(用本解释器), 原生/viper 需先成功编译/检出
|
||||
if impl == "py":
|
||||
pass
|
||||
elif impl not in exe_map:
|
||||
continue
|
||||
emit("")
|
||||
emit("-" * 78)
|
||||
if impl in VIPER_MODE:
|
||||
cmd = [str(exe_map[impl]), str(runs_n), str(size), str(iters_n),
|
||||
str(VIPER_MODE[impl])]
|
||||
display = VIPER_DISP[impl] + " 内核, QPC 计时"
|
||||
elif impl in ("c", "cpp"):
|
||||
cmd = [str(exe_map[impl]), str(runs_n), str(size), str(iters_n)]
|
||||
display = ("C 手写 -O2 -mavx2 -mfma(-ffp-contract=fast) 自动向量化单趟 FMA, QPC"
|
||||
if impl == "c" else
|
||||
"C++ 手写(std::vector) -O2 -mavx2 -mfma(-ffp-contract=fast) 单趟 FMA, QPC")
|
||||
else: # py
|
||||
cmd = [sys.executable, str(ROOT / "bench_py.py"), str(runs_n), str(size), str(iters_n)]
|
||||
display = "Python(numpy 就地 ufunc; 无 numpy 自动纯 Python 降规模回退)"
|
||||
emit(f"实现: {impl} (每组 = 一次进程启动, 进程内满热身 + 连续 {runs_n} 次 run)")
|
||||
emit(f" 方式: {display}")
|
||||
emit(f" 命令: {' '.join(cmd)}")
|
||||
|
||||
groups = [] # list[list[(wall_us, cpu_us, flops)]]
|
||||
meta = {"cfg": None, "check": None, "mode": None, "affinity": None,
|
||||
"tsc_hz": None, "kernel_mode": None}
|
||||
try:
|
||||
for g in range(1, groups_n + 1):
|
||||
p = sh_ok(cmd, ROOT, env=env)
|
||||
out_text = p.stdout
|
||||
if p.returncode != 0:
|
||||
tail = "\n".join(p.stderr.splitlines()[-20:])
|
||||
raise BenchError(
|
||||
f"[{impl}] 组{g} 进程退出码={p.returncode}\n"
|
||||
f"stderr 尾部:\n{tail}\n"
|
||||
f"stdout 前 20 行:\n" + "\n".join(out_text.splitlines()[:20]))
|
||||
try:
|
||||
parsed = parse_process_output(out_text, runs_n, impl)
|
||||
except ValueError as ve:
|
||||
# 附带 stderr 一起展示, 便于定位崩溃原因
|
||||
stderr_tail = "\n".join(p.stderr.splitlines()[-30:])
|
||||
raise BenchError(
|
||||
f"[{impl}] 组{g} 输出解析失败: {ve}\n"
|
||||
f"stderr 尾部:\n{stderr_tail}") from ve
|
||||
groups.append(parsed["samples"])
|
||||
if meta["cfg"] is None:
|
||||
meta["cfg"] = parsed["cfg"]
|
||||
if meta["check"] is None:
|
||||
meta["check"] = parsed["check"]
|
||||
if meta["affinity"] is None:
|
||||
meta["affinity"] = parsed["affinity"]
|
||||
if meta["tsc_hz"] is None:
|
||||
meta["tsc_hz"] = parsed["tsc_hz"]
|
||||
if meta["kernel_mode"] is None:
|
||||
meta["kernel_mode"] = parsed["kernel_mode"]
|
||||
# Viper 内核变体必须真的跑在期望的内核上: 旧版 app.exe 不认命令行第 4
|
||||
# 个参数(或内核不全), 会悄悄跑成别的内核 —— 那等于让 SIMD 数据冒充原生,
|
||||
# 直接污染 SIMD/原生对照。期望值对不上就显式失败, 不给"看似成功"的机会。
|
||||
want_mode = VIPER_MODE.get(impl)
|
||||
if want_mode is not None and parsed["kernel_mode"] != want_mode:
|
||||
raise BenchError(
|
||||
f"[{impl}] 产物回报 MODE={parsed['kernel_mode']!r}, 期望 {want_mode}: "
|
||||
"app.exe 可能是旧版(不识别命令行第 4 个参数 mode)或未包含该内核, "
|
||||
"请重新构建 Viper 工程")
|
||||
if meta["mode"] is None and parsed["cfg"][1] != iters_n:
|
||||
meta["mode"] = "pure-scale"
|
||||
elif meta["mode"] is None:
|
||||
meta["mode"] = "full"
|
||||
wall = [s[0] for s in parsed["samples"]]
|
||||
cpu = [s[1] for s in parsed["samples"]]
|
||||
emit(f"[{impl}] 组{g}/{groups_n} 完成 | 墙钟均值={mean(wall):.1f} us | "
|
||||
f"线程CPU均值={mean(cpu):.1f} us | 墙钟max-min={max(wall) - min(wall):.1f}")
|
||||
st = analyze_impl(groups, tol, drop_first, trim_ratio, stat_name, metric)
|
||||
st["meta"] = meta
|
||||
st["size"] = size
|
||||
results[(size, impl)] = st
|
||||
except Exception as e:
|
||||
# 启动/解析/统计任何异常都要把具体 traceback 打出来, 不再静默跳过
|
||||
err_text = "".join(traceback.format_exception(type(e), e, e.__traceback__))
|
||||
emit(f"[{impl}] 运行失败(详见 traceback): {e}")
|
||||
emit(err_text.rstrip())
|
||||
results[(size, impl)] = {"error": str(e), "traceback": err_text}
|
||||
|
||||
# ---- 差值核验 / flops-check 统计输出 ----
|
||||
csv_rows = []
|
||||
cur_size = None
|
||||
for (size, impl), st in results.items():
|
||||
if size != cur_size:
|
||||
emit("")
|
||||
emit("#" * 78)
|
||||
emit(f"# 结果汇总: 工作尺寸 LEN={size} ITERS={iters_map[size]}"
|
||||
f" 四份缓冲共 {size * 32 / 1024:.1f} KiB")
|
||||
emit("#" * 78)
|
||||
cur_size = size
|
||||
if "error" in st:
|
||||
emit(f" {impl}: 运行失败 —— {st['error']}")
|
||||
continue
|
||||
meta = st.get("meta", {})
|
||||
cfg = meta.get("cfg")
|
||||
cfg_total = (float(cfg[0]) * float(cfg[1]) * 2.0) if cfg else 0.0
|
||||
cfg_txt = f"{cfg[0]}x{cfg[1]}" if cfg else "?"
|
||||
emit("")
|
||||
emit("=" * 78)
|
||||
emit(f"实现 {impl} CFG(len×iters)={cfg_txt} 总运算量/run={cfg_total:.3e} flops"
|
||||
+ (" [纯 Python 降规模]" if meta.get("mode") == "pure-scale" else ""))
|
||||
aff = meta.get("affinity")
|
||||
emit("线程绑定 AFFINITY = "
|
||||
+ (f"CPU#{aff}(SetThreadAffinityMask 成功)"
|
||||
if (aff is not None and aff >= 0) else "未绑定/失败(-1)"))
|
||||
tsc = meta.get("tsc_hz")
|
||||
emit("TSC 标定频率 = "
|
||||
+ (f"{tsc:.4e} Hz" if tsc else "缺失/0(标定失败: 该实现的周期计数未推进)"))
|
||||
km = meta.get("kernel_mode")
|
||||
if km is not None:
|
||||
emit(f"内核 MODE = {km} {VIPER_DISP.get(impl, '(内核名未知)')}")
|
||||
all_samples = [s for g in st.get("_samples", []) for s in g]
|
||||
n_bad = sum(1 for w, c, _ in all_samples if w > 0.0 and c > w * 1.05)
|
||||
if n_bad:
|
||||
emit(f" !! 可疑: {n_bad}/{len(all_samples)} 个样本 cpu_us > wall_us×1.05"
|
||||
" —— 单线程物理上不可能, 请查该实现的 TSC 标定与周期读取")
|
||||
emit(f"CHECK(首次) = {meta.get('check', float('nan')):.17g}")
|
||||
overall = st["overall"]
|
||||
emit(f"主指标 {METRIC_LABEL[st['metric']]} 统计量={STAT_LABEL[st['stat']]}")
|
||||
emit(f"总体统计 overall : {overall:.3f} us "
|
||||
f"(统计 {st['n_stat_groups']}/{groups_n} 组, 有效样本 {st['n']}, "
|
||||
f"裁剪剔除 {st['n_dropped']}"
|
||||
+ ("; 首组仅汇报" if st.get("drop_first") else "") + ")")
|
||||
emit(f"对照 {METRIC_LABEL[st['other_metric']]:<24}: {st['overall_other']:.3f} us")
|
||||
emit(f"样本标准差 sample std : {st['overall_std']:.3f} us")
|
||||
rel_md = (abs(st["max_dev"]) / abs(overall) if overall else 0.0)
|
||||
emit(f"最大偏差 max|raw-overall|: {st['max_dev']:.3f} us ({rel_md:.3%})")
|
||||
if cfg_total:
|
||||
emit(f"实测吞吐(按 CFG, 主指标) : {cfg_total / (overall * 1e-6) / 1e9:.3f} GFLOPS")
|
||||
emit("-" * 78)
|
||||
for gs in st["group_stats"]:
|
||||
raw_txt = ", ".join(f"{v:.1f}" for v in gs["raw"])
|
||||
dev_rel = abs(gs["dev"]) / abs(overall) if overall else 0.0
|
||||
excl = " [首组: 仅汇报, 不计入统计]" if not gs.get("in_stat", True) else ""
|
||||
trim_txt = (f" | 裁剪剔除 {len(gs['dropped_idx'])}/{len(gs['raw'])}"
|
||||
f" 剔除位置={[i + 1 for i in gs['dropped_idx']]}"
|
||||
if gs["dropped_idx"] else " | 裁剪剔除 0")
|
||||
emit(f"[组{gs['idx']:>2}] 统计值={gs['gstat']:>11.2f} us | "
|
||||
f"均值={gs['mean']:>11.2f} | 中位数={gs['median']:>11.2f} | "
|
||||
f"几何均值={gs['geomean']:>11.2f}{trim_txt}")
|
||||
emit(f" 组内差max-min={gs['spread']:>8.2f} | "
|
||||
f"组-总体={gs['dev']:+10.2f} us ({dev_rel:5.2%}){excl}")
|
||||
if gs["idx"] >= 2:
|
||||
k = gs["idx"] - 2
|
||||
if st.get("drop_first") and k == 0:
|
||||
emit(" 相邻组差: n/a(组1 不计入统计)")
|
||||
else:
|
||||
adj_val = st["adj"][k]
|
||||
adj_rel = abs(adj_val) / abs(overall) if overall else 0.0
|
||||
emit(f" 相邻组差 组{gs['idx'] - 1}->组{gs['idx']}: {adj_val:+10.2f} us ({adj_rel:5.2%})")
|
||||
else:
|
||||
emit(f" 相邻组差: n/a(首组)")
|
||||
emit(f" 组内 {runs_n} 次原始(主指标): {raw_txt} us")
|
||||
emit("-" * 78)
|
||||
verdict = "PASS" if st["passed"] else "FAIL"
|
||||
emit(f"差值核验(tol={tol:.1%}): {verdict}"
|
||||
+ (" [组1 排除: 首组仅汇报]" if st.get("drop_first") else ""))
|
||||
if st["passed"]:
|
||||
scope = "统计组(排除首组)" if st.get("drop_first") else "全部组"
|
||||
emit(f" {scope}: |组统计值-总体|/总体 <= tol, 且相邻组|差值|/总体 <= tol")
|
||||
else:
|
||||
if st["dev_viol"]:
|
||||
emit(f" 组对总体超差(违规组): {st['dev_viol']}")
|
||||
if st["adj_pairs"]:
|
||||
emit(f" 相邻组超差(违规对): {['组%d->组%d' % p for p in st['adj_pairs']]}")
|
||||
# CSV 原始行(wall/cpu/flops 全出, 附该样本是否被裁剪剔除)
|
||||
for gi, gsamples in enumerate(st.get("_samples", []), start=1):
|
||||
dropped = set(st["group_stats"][gi - 1]["dropped_idx"])
|
||||
for ri, (wv, cv, fv) in enumerate(gsamples, start=1):
|
||||
csv_rows.append([impl, cfg[0] if cfg else "", cfg[1] if cfg else "",
|
||||
gi, ri, wv, cv, fv, meta.get("check", ""),
|
||||
1 if (ri - 1) in dropped else 0])
|
||||
|
||||
# ---- flops-check ----
|
||||
if args.flops_check:
|
||||
emit("")
|
||||
emit("=" * 78)
|
||||
emit("flops-check 核验(需 --flops-check): 校验 CFG 总运算量≈2.147e9 及")
|
||||
emit("RESULT 行 flops 字段与 us 的换算一致性(flops = CFG_total / 墙钟秒)")
|
||||
emit("-" * 78)
|
||||
any_issue = False
|
||||
for (size, impl), st in results.items():
|
||||
if "error" in st:
|
||||
continue
|
||||
cfg = st.get("meta", {}).get("cfg")
|
||||
if not cfg:
|
||||
emit(f" [LEN={size} {impl}] 无 CFG, 无法核验")
|
||||
any_issue = True
|
||||
continue
|
||||
cfg_total = float(cfg[0]) * float(cfg[1]) * 2.0
|
||||
near = abs(cfg_total - EXPECT_TOTAL_FLOPS) / EXPECT_TOTAL_FLOPS <= 0.05
|
||||
bad = []
|
||||
ok_n = 0
|
||||
near_2e9_n = 0
|
||||
flat = [(w, c, f) for g in st.get("_samples", []) for (w, c, f) in g]
|
||||
for row_w, row_c, row_f in flat:
|
||||
if row_w <= 0:
|
||||
bad.append((row_w, row_f))
|
||||
continue
|
||||
expect_f = cfg_total / (row_w * 1e-6)
|
||||
if abs(row_f - expect_f) / expect_f > 0.01:
|
||||
bad.append((row_w, row_f))
|
||||
else:
|
||||
ok_n += 1
|
||||
if abs(row_f - EXPECT_TOTAL_FLOPS) / EXPECT_TOTAL_FLOPS <= 0.5:
|
||||
near_2e9_n += 1
|
||||
total_n = len(flat)
|
||||
emit(f" [LEN={size} {impl}] CFG={cfg[0]}x{cfg[1]} 单 run 总运算量={cfg_total:.3e} flops"
|
||||
+ (" (≈2.147e9 ✓)" if near else " (≠2e9: 降规模配置, 见 README §8)"))
|
||||
emit(f" RESULT 行 flops 字段与墙钟 us 换算一致: {ok_n}/{total_n}"
|
||||
+ ("" if not bad else f" <<< 不一致示例 {bad[:3]}"))
|
||||
emit(f" flops≈2e9 的行(即单 run 时长≈1.0s): {near_2e9_n}/{total_n} "
|
||||
"(说明: RESULT 的 flops=总运算量/秒, 只有时长恰≈1s 才≈2e9)")
|
||||
if not near:
|
||||
any_issue = True
|
||||
if bad:
|
||||
any_issue = True
|
||||
if not any_issue:
|
||||
emit(" 全部实现 CFG 与 flops 换算均正常。")
|
||||
|
||||
# ---- 汇总 ----
|
||||
emit("")
|
||||
emit("=" * 78)
|
||||
emit(f"汇总: 各实现总体统计值(us) 与 实测 GFLOPS(按各自 CFG); 主指标={METRIC_LABEL[metric]}")
|
||||
cur_size = None
|
||||
for (size, impl), st in results.items():
|
||||
if size != cur_size:
|
||||
emit("")
|
||||
emit(f" ==== LEN={size} (工作集 {size * 32 / 1024:.1f} KiB) ====")
|
||||
cur_size = size
|
||||
if "error" in st:
|
||||
emit(f" {impl:<6}: 失败(见上方 traceback)")
|
||||
continue
|
||||
cfg = st.get("meta", {}).get("cfg")
|
||||
cfg_total = (float(cfg[0]) * float(cfg[1]) * 2.0) if cfg else 0.0
|
||||
|
||||
def gfl(us):
|
||||
return cfg_total / (us * 1e-6) / 1e9 if (cfg_total and us) else float("nan")
|
||||
|
||||
main_us = st["overall"]
|
||||
other_us = st["overall_other"]
|
||||
lab_main = "墙钟" if st["metric"] == "wall" else "CPU "
|
||||
lab_other = "CPU " if st["metric"] == "wall" else "墙钟"
|
||||
aff = st.get("meta", {}).get("affinity")
|
||||
aff_txt = f"CPU#{aff}" if (aff is not None and aff >= 0) else "未绑定"
|
||||
emit(f" {impl:<6}: [{lab_main}] {main_us:>11.3f} us {gfl(main_us):>8.3f} GFLOPS"
|
||||
f" | [{lab_other}] {other_us:>11.3f} us {gfl(other_us):>8.3f} GFLOPS"
|
||||
f" | {aff_txt} | {'PASS' if st['passed'] else 'FAIL'}")
|
||||
if not any(im == "viper" for (_, im) in results):
|
||||
emit(" (viper 已跳过: 请先构建 .tpv_build/output/app.exe)")
|
||||
for impl, note in compile_notes:
|
||||
emit(f" ({impl} 已跳过: 编译失败, 见上方)")
|
||||
emit("=" * 78)
|
||||
|
||||
report = ROOT / args.report
|
||||
report.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
emit(f"\n日志已写入: {report}")
|
||||
|
||||
# CSV 默认自动导出(带时间戳), 便于事后画图; --no-csv 可关闭
|
||||
if not args.no_csv:
|
||||
csv_path = ROOT / (args.csv if args.csv else f"bench_{csv_stamp}.csv")
|
||||
with open(csv_path, "w", newline="", encoding="utf-8-sig") as f:
|
||||
w = csv.writer(f)
|
||||
w.writerow(["impl", "len", "iters", "group", "run",
|
||||
"wall_us", "cpu_us", "flops", "check", "trimmed"])
|
||||
w.writerows(csv_rows)
|
||||
emit(f"原始数据已写入: {csv_path}")
|
||||
emit(f" (出图: python plot_bench.py {csv_path.name})")
|
||||
|
||||
if not results or all("error" in st for st in results.values()):
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,13 @@
|
||||
*.o
|
||||
*.obj
|
||||
*.exe
|
||||
*.img
|
||||
*.iso
|
||||
**/__pycache__/
|
||||
.cache/
|
||||
*.pyc
|
||||
|
||||
# 编译
|
||||
**/output/
|
||||
**/temp/
|
||||
**/.tpv_build/
|
||||
@@ -0,0 +1,143 @@
|
||||
import w32.win32console as w32cmd
|
||||
import stdio
|
||||
import t, c
|
||||
from stdint import *
|
||||
from t import CInt as MyInt
|
||||
from stdint import BYTE as MyByte
|
||||
import main2
|
||||
|
||||
pagecode: t.CDefine = 65001
|
||||
|
||||
# 类型系统收敛测试:typedef 值引用 from-import 别名(阶段1a 别名链)
|
||||
MyWord: t.CTypedef = MyInt
|
||||
MyDword: t.CTypedef = DWORD
|
||||
|
||||
|
||||
class TestStruct():
|
||||
a: t.CInt
|
||||
b: t.CInt
|
||||
|
||||
|
||||
class Obj():
|
||||
val: t.CInt
|
||||
|
||||
|
||||
export def main() -> int:
|
||||
w32cmd.SetConsoleOutputCP(pagecode)
|
||||
w32cmd.SetConsoleCP(pagecode)
|
||||
print("=== Unary * / & (Deref/AddrOf) Test ===")
|
||||
|
||||
# --- Test 1: 标量 & 取地址 + * 解引用 ---
|
||||
x: int = 42
|
||||
px: int* = &x
|
||||
d: int = *px
|
||||
print(f"T1: x={x} *(&x)={d} (expect 42, 42)")
|
||||
if d == 42:
|
||||
print("T1 OK")
|
||||
else:
|
||||
print("T1 FAIL")
|
||||
|
||||
# --- Test 2: 通过指针写回(c.DerefAs 兼容形式) ---
|
||||
#*px = 100
|
||||
|
||||
#k: int**
|
||||
#l = *k
|
||||
c.DerefAs(px, 100)
|
||||
print(f"T2: after c.DerefAs(px, 100) x={x} (expect 100)")
|
||||
if x == 100:
|
||||
print("T2 OK")
|
||||
else:
|
||||
print("T2 FAIL")
|
||||
|
||||
# --- Test 3: 数组元素地址 &arr[i] + * 解引用 ---
|
||||
a: t.CArray[t.CInt, 4]
|
||||
a[0] = 7
|
||||
pa: t.CInt | t.CPtr = &a[0]
|
||||
v3: int = *pa
|
||||
print(f"T3: a[0]={a[0]} *&a[0]={v3} (expect 7, 7)")
|
||||
if v3 == 7:
|
||||
print("T3 OK")
|
||||
else:
|
||||
print("T3 FAIL")
|
||||
|
||||
# --- Test 4: 结构体字段地址 &s.f + * 解引用 ---
|
||||
s: TestStruct
|
||||
s.a = 10
|
||||
pf: t.CInt | t.CPtr = &s.a
|
||||
v4: int = *pf
|
||||
print(f"T4: s.a={s.a} *&s.a={v4} (expect 10, 10)")
|
||||
if v4 == 10:
|
||||
print("T4 OK")
|
||||
else:
|
||||
print("T4 FAIL")
|
||||
|
||||
# --- Test 5: &obj 对象指针(对象变量即指针) ---
|
||||
o: Obj
|
||||
o.val = 99
|
||||
po: Obj | t.CPtr = &o
|
||||
print(f"T5: o.val={o.val} &o->val={po.val} (expect 99, 99)")
|
||||
if po.val == 99:
|
||||
print("T5 OK")
|
||||
else:
|
||||
print("T5 FAIL")
|
||||
|
||||
print("=== Unary * / & Test Done ===")
|
||||
|
||||
# ============================================================
|
||||
# 类型系统收敛测试(阶段1a/1b/2/3)
|
||||
# 验证:正常库分析优先(from-import 别名、库 typedef 动态表),
|
||||
# t/c 库内部特判兜底(CInt/CBool/CPtr 等核心原语保留硬编码)。
|
||||
# ============================================================
|
||||
print("=== Type System Convergence Test ===")
|
||||
|
||||
# --- T6: from-import 别名反查(阶段1a)---
|
||||
# from t import CInt as MyInt → 裸名 MyInt 注解经别名反查 → i32
|
||||
a6: MyInt = 123
|
||||
# MyWord: t.CTypedef = MyInt → typedef 值引用别名 → 链式解析
|
||||
b6: MyWord = 456
|
||||
# from stdint import BYTE as MyByte → 裸名别名 → t.CUnsignedChar
|
||||
m6: MyByte = 65
|
||||
print(f"T6: MyInt={a6} MyWord={b6} MyByte={m6} (expect 123, 456, 65)")
|
||||
if a6 == 123 and b6 == 456 and m6 == 65:
|
||||
print("T6 OK")
|
||||
else:
|
||||
print("T6 FAIL")
|
||||
|
||||
# --- T7: 库 typedef 裸名解析(阶段1b/2)---
|
||||
# 裸名 BYTE/DWORD 走 CTypedef 动态表(stdint.vp 声明权威)
|
||||
bb7: BYTE = 200
|
||||
dd7: DWORD = 0x12345678
|
||||
# typedef 引用 typedef(MyDword = DWORD → CUInt32T)
|
||||
jj7: MyDword = 0xABCD
|
||||
# t/c 核心原语内部特判保留(t.CBool → i1 硬编码兜底)
|
||||
kk7: t.CBool = True
|
||||
print(f"T7: BYTE={bb7} DWORD={dd7} MyDword={jj7} CBool={kk7} (expect 200, 305419896, 43981, 1)")
|
||||
if bb7 == 200 and dd7 == 0x12345678 and jj7 == 0xABCD and kk7 == 1:
|
||||
print("T7 OK")
|
||||
else:
|
||||
print("T7 FAIL")
|
||||
|
||||
# --- T8: __sizeof__ 库 typedef(阶段3 尺寸联动)---
|
||||
s8a: int = BYTE.__sizeof__()
|
||||
s8b: int = DWORD.__sizeof__()
|
||||
s8c: int = MyWord.__sizeof__()
|
||||
s8d: int = t.CBool.__sizeof__()
|
||||
print(f"T8: sizeof(BYTE)={s8a} sizeof(DWORD)={s8b} sizeof(MyWord)={s8c} sizeof(CBool)={s8d} (expect 1, 4, 4, 1)")
|
||||
if s8a == 1 and s8b == 4 and s8c == 4 and s8d == 1:
|
||||
print("T8 OK")
|
||||
else:
|
||||
print("T8 FAIL")
|
||||
|
||||
# --- T9: typedef 联合(BYTE | t.CPtr → 指针包装,* 解引用)---
|
||||
p9: BYTE | t.CPtr = &bb7
|
||||
v9: BYTE = *p9
|
||||
print(f"T9: *(BYTE|CPtr)={v9} (expect 200)")
|
||||
if v9 == 200:
|
||||
print("T9 OK")
|
||||
else:
|
||||
print("T9 FAIL")
|
||||
|
||||
print("=== Type System Convergence Test Done ===")
|
||||
|
||||
main2.m()
|
||||
return 0
|
||||
@@ -0,0 +1,12 @@
|
||||
import t, c
|
||||
import stdio
|
||||
|
||||
|
||||
export def main() -> int:
|
||||
print("main2.m")
|
||||
i: int = 10
|
||||
k: int* = &i
|
||||
stdio.printf("%d\n", *k)
|
||||
a: size_t = len("Hello")
|
||||
stdio.printf("%d\n", a)
|
||||
return 0
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 TermiNexus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,289 @@
|
||||
# Viper Template Project
|
||||
|
||||
[English](#english) | [中文](#中文)
|
||||
|
||||
A minimal, ready-to-use template for bootstrapping a new [Viper](../wiki/01-overview.md) language project on **Windows (x86_64-pc-windows-gnu)**.
|
||||
|
||||
Windows 平台(x86_64-pc-windows-gnu)下 [Viper](../wiki/01-overview.md) 语言新项目的最小可用模板。
|
||||
|
||||
---
|
||||
|
||||
<a id="中文"></a>
|
||||
|
||||
## 中文
|
||||
|
||||
### 这是什么
|
||||
|
||||
`ViperTemplateProject` 是基于 `Viper` 语言的 **Windows 桌面应用模板**。克隆本仓库后,你可以立即得到一个能够:
|
||||
|
||||
- 设置 Windows 控制台为 UTF-8 编码(避免中文乱码)
|
||||
- 打印一行 `你好,世界`
|
||||
- 通过 `llc + clang++`(mingw 工具链)编译为原生 `app.exe`
|
||||
|
||||
的最小 Viper 项目。
|
||||
|
||||
模板对应的语言编译器是 [TransPyC](https://git.gvsds.com/GVSDS/TransPyC);关于 Viper 语言本身的概览,参见 [TransPyC 仓库 wiki](../wiki/01-overview.md)。
|
||||
|
||||
### 目录结构
|
||||
|
||||
```
|
||||
ViperTemplateProject/
|
||||
├── .gitignore # 忽略 build/、*.o、*.exe、缓存等
|
||||
├── README.md # 本文件
|
||||
├── project.vpj # 项目配置(编译器、链接器、目标三元组等)
|
||||
└── App/
|
||||
└── main.py # 入口文件
|
||||
```
|
||||
|
||||
### 前提条件
|
||||
|
||||
| 工具 | 说明 |
|
||||
|------|------|
|
||||
| `python` ≥ 3.10 | 用于运行 TransPyC 编译器 |
|
||||
| `llc` | LLVM 静态编译器,编译 `.ll` → `.o` |
|
||||
| `clang++` | mingw 工具链下的链接驱动(`x86_64-w64-mingw32`) |
|
||||
| `TransPyC` 仓库 | 本编译器,克隆在 `ViperTemplateProject/..` 同一目录下 |
|
||||
|
||||
环境变量建议(PowerShell):
|
||||
|
||||
```powershell
|
||||
$env:PATH = "D:\msys64\mingw64\bin;D:\LLVM\bin;$env:PATH"
|
||||
```
|
||||
|
||||
### 快速开始
|
||||
|
||||
```powershell
|
||||
# 1. 确保目录布局正确
|
||||
# D:\Users\TermiNexus\Desktop\TransPyC\
|
||||
# ├── TransPyC\ # 编译器仓库
|
||||
# └── ViperTemplateProject\ # 本仓库
|
||||
|
||||
cd D:\Users\TermiNexus\Desktop\TransPyC\ViperTemplateProject
|
||||
|
||||
# 2. 编译并运行
|
||||
python ..\TransPyC\Projectrans.py `
|
||||
--project .\project.vpj `
|
||||
--clean-cache=false
|
||||
|
||||
# 3. 产物位于 output\app.exe
|
||||
.\output\app.exe
|
||||
```
|
||||
|
||||
预期输出:
|
||||
|
||||
```
|
||||
你好,世界
|
||||
```
|
||||
|
||||
### 编写你的第一个程序
|
||||
|
||||
打开 `App/main.py`,替换 `print("你好,世界")` 为任意合法 Viper 表达式。例如:
|
||||
|
||||
```python
|
||||
import w32.win32console as w32cmd
|
||||
import t, c
|
||||
|
||||
pagecode: t.CDefine = 65001
|
||||
|
||||
@t.CExport
|
||||
def main() -> int:
|
||||
w32cmd.SetConsoleOutputCP(pagecode)
|
||||
w32cmd.SetConsoleCP(pagecode)
|
||||
|
||||
# 你的代码从这里开始
|
||||
a: t.CInt = 1
|
||||
b: t.CInt = 2
|
||||
print("a + b =", a + b)
|
||||
|
||||
return 0
|
||||
```
|
||||
|
||||
### 修改项目配置
|
||||
|
||||
打开 `project.vpj`,按需调整:
|
||||
|
||||
| 字段 | 含义 | 模板默认值 |
|
||||
|------|------|-----------|
|
||||
| `name` | 项目名(仅显示用) | `Standard Template Project` |
|
||||
| `source_dir` | 源文件根目录 | `./App` |
|
||||
| `temp_dir` | 中间产物目录(`.pyi` / `.stub.ll`) | `./temp` |
|
||||
| `output_dir` | 最终产物目录 | `./output` |
|
||||
| `compiler.cmd` | 汇编器 | `llc` |
|
||||
| `compiler.flags` | 传给 `llc` 的参数 | `["-filetype=obj", "-relocation-model=pic"]` |
|
||||
| `linker.cmd` | 链接器 | `clang++` |
|
||||
| `linker.flags` | 链接器参数 | mingw 标准库 + `kernel32` |
|
||||
| `linker.output` | 产物名 | `app.exe` |
|
||||
| `includes` | 标准库搜索路径 | `["../includes"]` |
|
||||
| `target.triple` | LLVM 目标三元组 | `x86_64-pc-windows-gnu` |
|
||||
| `target.datalayout` | LLVM 数据布局 | mingw 默认 |
|
||||
| `options.target` | 后端目标 | `llvm` |
|
||||
| `options.strict_mode` | 严格类型检查 | `true` |
|
||||
| `options.slice_level` | 切片级别 | `3` |
|
||||
|
||||
### 跨平台使用
|
||||
|
||||
本模板默认针对 **Windows + mingw**。若需在其他平台使用,修改 `project.vpj` 的 `compiler.cmd` / `linker.cmd` / `target.triple`:
|
||||
|
||||
- **Linux (x86_64)**:`triple = "x86_64-unknown-linux-gnu"`,`linker.cmd = "clang++"`
|
||||
- **macOS (x86_64)**:`triple = "x86_64-apple-darwin*"`,`linker.cmd = "clang++"`
|
||||
- **裸机 x86_64 (ViperOS)**:`triple = "x86_64-none-elf"`,`linker.cmd = "ld.lld"`,需要 `isr.s` 启动文件
|
||||
|
||||
### 常见问题
|
||||
|
||||
**Q: 报错 `cannot find includes/os` / `cannot find includes/atom` 之类?**
|
||||
A: 检查 `project.vpj` 中的 `includes` 字段是否指向 `TransPyC/includes`。`../includes` 假设本仓库与 `TransPyC` 仓库同级。
|
||||
|
||||
**Q: 输出 `app.exe` 双击闪退?**
|
||||
A: 在终端中运行 `.\output\app.exe` 而非双击,并确认 mingw 与 LLVM 在 `PATH` 中。
|
||||
|
||||
**Q: 想完全清理构建产物?**
|
||||
A: 使用 `python ..\TransPyC\Projectrans.py --project .\project.vpj --clean-cache` 清理缓存;删除 `output/` 和 `temp/` 即可彻底重置。
|
||||
|
||||
**Q: `StandardTemplateProject` 和本仓库是什么关系?**
|
||||
A: 本仓库 `ViperTemplateProject` 是 `StandardTemplateProject` 的独立仓库版本。如果你仍想保留 `StandardTemplateProject` 放在 `TransPyC` 仓库内,只需把它加入 `TransPyC/.gitignore` 的 `# 排除独立仓库的目录` 小节。
|
||||
|
||||
### 进一步阅读
|
||||
|
||||
- [Viper 语言概览](../wiki/01-overview.md)
|
||||
- [类型系统](../wiki/02-type-system.md)
|
||||
- [OOP 与对象模型](../wiki/06-oop.md)
|
||||
- [自举路线图](../wiki/13-bootstrapping.md)
|
||||
|
||||
---
|
||||
|
||||
<a id="english"></a>
|
||||
|
||||
## English
|
||||
|
||||
### What is this
|
||||
|
||||
`ViperTemplateProject` is a **Windows desktop application template** based on the [Viper](../wiki/01-overview.md) language. After cloning, you immediately have a minimal Viper project that:
|
||||
|
||||
- Sets the Windows console to UTF-8 (avoids mojibake for non-ASCII output)
|
||||
- Prints `你好,世界`
|
||||
- Compiles to a native `app.exe` via `llc + clang++` (mingw toolchain)
|
||||
|
||||
The corresponding compiler is [TransPyC](https://git.gvsds.com/GVSDS/TransPyC). For a Viper language overview, see the [TransPyC wiki](../wiki/01-overview.md).
|
||||
|
||||
### Directory layout
|
||||
|
||||
```
|
||||
ViperTemplateProject/
|
||||
├── .gitignore # ignores build/, *.o, *.exe, caches, etc.
|
||||
├── README.md # this file
|
||||
├── project.vpj # project config (compiler, linker, target triple, ...)
|
||||
└── App/
|
||||
└── main.py # entry point
|
||||
```
|
||||
|
||||
### Prerequisites
|
||||
|
||||
| Tool | Notes |
|
||||
|------|-------|
|
||||
| `python` ≥ 3.10 | runs the TransPyC compiler |
|
||||
| `llc` | LLVM static compiler, `.ll` → `.o` |
|
||||
| `clang++` | mingw toolchain driver (`x86_64-w64-mingw32`) |
|
||||
| `TransPyC` repo | the compiler, cloned as a sibling of `ViperTemplateProject` |
|
||||
|
||||
Suggested `PATH` (PowerShell):
|
||||
|
||||
```powershell
|
||||
$env:PATH = "D:\msys64\mingw64\bin;D:\LLVM\bin;$env:PATH"
|
||||
```
|
||||
|
||||
### Quick start
|
||||
|
||||
```powershell
|
||||
# 1. Make sure the layout is right
|
||||
# D:\Users\TermiNexus\Desktop\TransPyC\
|
||||
# ├── TransPyC\ # compiler repo
|
||||
# └── ViperTemplateProject\ # this repo
|
||||
|
||||
cd D:\Users\TermiNexus\Desktop\TransPyC\ViperTemplateProject
|
||||
|
||||
# 2. Build and run
|
||||
python ..\TransPyC\Projectrans.py `
|
||||
--project .\project.vpj `
|
||||
--clean-cache=false
|
||||
|
||||
# 3. Output is at output\app.exe
|
||||
.\output\app.exe
|
||||
```
|
||||
|
||||
Expected output:
|
||||
|
||||
```
|
||||
你好,世界
|
||||
```
|
||||
|
||||
### Writing your first program
|
||||
|
||||
Open `App/main.py` and replace `print("你好,世界")` with any valid Viper expression. Example:
|
||||
|
||||
```python
|
||||
import w32.win32console as w32cmd
|
||||
import t, c
|
||||
|
||||
pagecode: t.CDefine = 65001
|
||||
|
||||
@t.CExport
|
||||
def main() -> int:
|
||||
w32cmd.SetConsoleOutputCP(pagecode)
|
||||
w32cmd.SetConsoleCP(pagecode)
|
||||
|
||||
# your code goes here
|
||||
a: t.CInt = 1
|
||||
b: t.CInt = 2
|
||||
print("a + b =", a + b)
|
||||
|
||||
return 0
|
||||
```
|
||||
|
||||
### Editing project.vpj
|
||||
|
||||
| Field | Meaning | Template default |
|
||||
|-------|---------|------------------|
|
||||
| `name` | display name | `Standard Template Project` |
|
||||
| `source_dir` | source root | `./App` |
|
||||
| `temp_dir` | intermediate artifacts (`.pyi` / `.stub.ll`) | `./temp` |
|
||||
| `output_dir` | final artifacts | `./output` |
|
||||
| `compiler.cmd` | assembler | `llc` |
|
||||
| `compiler.flags` | flags passed to `llc` | `["-filetype=obj", "-relocation-model=pic"]` |
|
||||
| `linker.cmd` | linker | `clang++` |
|
||||
| `linker.flags` | linker flags | mingw stdlib + `kernel32` |
|
||||
| `linker.output` | output name | `app.exe` |
|
||||
| `includes` | standard library search paths | `["../includes"]` |
|
||||
| `target.triple` | LLVM target triple | `x86_64-pc-windows-gnu` |
|
||||
| `target.datalayout` | LLVM data layout | mingw default |
|
||||
| `options.target` | backend target | `llvm` |
|
||||
| `options.strict_mode` | strict type checking | `true` |
|
||||
| `options.slice_level` | slice level | `3` |
|
||||
|
||||
### Cross-platform use
|
||||
|
||||
This template targets **Windows + mingw** by default. To use on another platform, adjust `compiler.cmd` / `linker.cmd` / `target.triple` in `project.vpj`:
|
||||
|
||||
- **Linux (x86_64)**: `triple = "x86_64-unknown-linux-gnu"`, `linker.cmd = "clang++"`
|
||||
- **macOS (x86_64)**: `triple = "x86_64-apple-darwin*"`, `linker.cmd = "clang++"`
|
||||
- **Bare-metal x86_64 (ViperOS)**: `triple = "x86_64-none-elf"`, `linker.cmd = "ld.lld"`, requires `isr.s` startup
|
||||
|
||||
### FAQ
|
||||
|
||||
**Q: Error like `cannot find includes/os` or `cannot find includes/atom`?**
|
||||
A: Check that the `includes` field in `project.vpj` points to the `TransPyC/includes` directory. `../includes` assumes this repo lives next to `TransPyC`.
|
||||
|
||||
**Q: `app.exe` flashes and closes on double-click?**
|
||||
A: Run `.\output\app.exe` from a terminal, not by double-clicking. Make sure mingw and LLVM are on `PATH`.
|
||||
|
||||
**Q: How to fully clean build artifacts?**
|
||||
A: `python ..\TransPyC\Projectrans.py --project .\project.vpj --clean-cache` clears the cache. To reset everything, also delete `output/` and `temp/`.
|
||||
|
||||
**Q: What is the relationship with `StandardTemplateProject`?**
|
||||
A: This repo `ViperTemplateProject` is the standalone-repo version of `StandardTemplateProject`. If you still want `StandardTemplateProject` to live inside the `TransPyC` repo, add it to the `# 排除独立仓库的目录` section in `TransPyC/.gitignore`.
|
||||
|
||||
### Further reading
|
||||
|
||||
- [Viper language overview](../wiki/01-overview.md)
|
||||
- [Type system](../wiki/02-type-system.md)
|
||||
- [OOP and object model](../wiki/06-oop.md)
|
||||
- [Bootstrapping roadmap](../wiki/13-bootstrapping.md)
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "Standard Template Project",
|
||||
"version": "1.0.0",
|
||||
"source_dir": "./App",
|
||||
"build_dir": "./.tpv_build",
|
||||
"compiler": {
|
||||
"cmd": "llc",
|
||||
"flags": [
|
||||
"-filetype=obj",
|
||||
"-relocation-model=pic"
|
||||
]
|
||||
},
|
||||
"linker": {
|
||||
"cmd": "D:/msys64/mingw64/bin/clang++.exe",
|
||||
"flags": [
|
||||
"-fuse-ld=lld",
|
||||
"-lmsvcrt",
|
||||
"-lucrt",
|
||||
"-lpthread",
|
||||
"-lmingwex",
|
||||
"-lkernel32"
|
||||
],
|
||||
"output": "app.exe"
|
||||
},
|
||||
"includes": [
|
||||
"../../includes"
|
||||
],
|
||||
"target": {
|
||||
"triple": "x86_64-pc-windows-gnu",
|
||||
"datalayout": "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
|
||||
},
|
||||
"options": {
|
||||
"slice_level": 3,
|
||||
"target": "llvm",
|
||||
"strict_mode": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
*.o
|
||||
*.obj
|
||||
*.exe
|
||||
*.img
|
||||
*.iso
|
||||
**/__pycache__/
|
||||
.cache/
|
||||
*.pyc
|
||||
|
||||
# 编译
|
||||
**/output/
|
||||
**/temp/
|
||||
**/.tpv_build/
|
||||
@@ -0,0 +1,315 @@
|
||||
# main.vp — ViperZipfileProject: zipfile module read/write test
|
||||
#
|
||||
# Coverage:
|
||||
# 1) ZipFile("w") writes 4 members (deflate text / deflate large file / stored pseudo-random / empty file)
|
||||
# 2) ZipFile("r") reads back: entry count / name / size / compress_size checks + per-member content compare
|
||||
# (CRC32 verification is done inside zipfile.read)
|
||||
# 3) Corrupt one payload byte of a stored member -> a second read must return None with error ZIP_ERR_CRC
|
||||
import t, c
|
||||
from stdint import *
|
||||
import stdio
|
||||
import stdlib
|
||||
import string
|
||||
import zipfile
|
||||
import w32.win32base as wb
|
||||
import w32.win32file as wf
|
||||
|
||||
|
||||
ZIP_PATH: str = "zip_roundtrip.zip"
|
||||
BIG_LEN: sizet = 100000
|
||||
RND_LEN: sizet = 5000
|
||||
HELLO_TEXT: str = "Hello, Viper Zipfile!\n"
|
||||
|
||||
fail_count: int = 0
|
||||
|
||||
|
||||
def chk(name: str, cond: int):
|
||||
global fail_count
|
||||
if cond:
|
||||
stdio.printf("[PASS] %s\n", name)
|
||||
else:
|
||||
fail_count += 1
|
||||
stdio.printf("[FAIL] %s\n", name)
|
||||
stdio.fflush(None) # keep printed lines visible even on crash (stdout is fully buffered)
|
||||
|
||||
|
||||
def StrLen(s: bytes) -> sizet:
|
||||
n: sizet = 0
|
||||
while s is not None and *s != 0:
|
||||
n += 1
|
||||
s += 1
|
||||
return n
|
||||
|
||||
|
||||
def fill_pattern(buf: bytes, length: sizet, kind: int):
|
||||
for i in range(length):
|
||||
if kind == 1:
|
||||
# 'A'..'G' cycle - highly compressible
|
||||
buf[i] = 65 + (i % 7)
|
||||
else:
|
||||
# pseudo-random - for the ZIP_STORED member
|
||||
buf[i] = (i * 37 + 11) & 0xFF
|
||||
|
||||
|
||||
def WriteZip() -> int:
|
||||
stdio.printf("\n--- 1) write zip (4 members) ---\n")
|
||||
wf.DeleteFileA(ZIP_PATH)
|
||||
w: zipfile.ZipFile* = zipfile.ZipFile(ZIP_PATH, "w", zipfile.ZIP_DEFLATED)
|
||||
if w.ok == 0:
|
||||
stdio.printf("open(w) failed: %s\n", zipfile.error())
|
||||
return 1
|
||||
|
||||
hello: bytes = stdlib.malloc(64)
|
||||
if not hello:
|
||||
stdio.printf("malloc hello failed\n")
|
||||
w.close()
|
||||
return 1
|
||||
string.strcpy(hello, HELLO_TEXT)
|
||||
hello_len: sizet = StrLen(hello)
|
||||
|
||||
big: bytes = stdlib.malloc(BIG_LEN)
|
||||
if not big:
|
||||
stdio.printf("malloc big failed\n")
|
||||
stdlib.free(hello)
|
||||
w.close()
|
||||
return 1
|
||||
fill_pattern(big, BIG_LEN, 1)
|
||||
|
||||
rnd: bytes = stdlib.malloc(RND_LEN)
|
||||
if not rnd:
|
||||
stdio.printf("malloc rnd failed\n")
|
||||
stdlib.free(hello)
|
||||
stdlib.free(big)
|
||||
w.close()
|
||||
return 1
|
||||
fill_pattern(rnd, RND_LEN, 0)
|
||||
|
||||
rc: int = w.write("hello.txt", hello, hello_len, zipfile.ZIP_DEFLATED)
|
||||
chk("write hello.txt (deflate)", rc == zipfile.ZIP_ERR_OK)
|
||||
if rc != zipfile.ZIP_ERR_OK:
|
||||
stdio.printf(" hello err: %s\n", zipfile.error())
|
||||
rc = w.write("big.bin", big, BIG_LEN, zipfile.ZIP_DEFLATED)
|
||||
chk("write big.bin (deflate, 100000B)", rc == zipfile.ZIP_ERR_OK)
|
||||
if rc != zipfile.ZIP_ERR_OK:
|
||||
stdio.printf(" big err: %s\n", zipfile.error())
|
||||
rc = w.write("random.bin", rnd, RND_LEN, zipfile.ZIP_STORED)
|
||||
chk("write random.bin (stored)", rc == zipfile.ZIP_ERR_OK)
|
||||
if rc != zipfile.ZIP_ERR_OK:
|
||||
stdio.printf(" random err: %s\n", zipfile.error())
|
||||
rc = w.write("empty.txt", None, 0, zipfile.ZIP_DEFLATED)
|
||||
chk("write empty.txt (deflate, 0B)", rc == zipfile.ZIP_ERR_OK)
|
||||
if rc != zipfile.ZIP_ERR_OK:
|
||||
stdio.printf(" empty err: %s\n", zipfile.error())
|
||||
rc = w.close()
|
||||
chk("close(w)", rc == zipfile.ZIP_ERR_OK)
|
||||
if rc != zipfile.ZIP_ERR_OK:
|
||||
stdio.printf(" close err: %s\n", zipfile.error())
|
||||
|
||||
# After close, confirm the on-disk size with a raw file handle (independent of zipfile read logic)
|
||||
ph: wb.HANDLE = wf.CreateFileA(
|
||||
ZIP_PATH, wf.GENERIC_READ,
|
||||
wf.FILE_SHARE_READ | wf.FILE_SHARE_WRITE | wf.FILE_SHARE_DELETE,
|
||||
None, wf.OPEN_EXISTING, wf.FILE_ATTRIBUTE_NORMAL, None)
|
||||
if ph == wb.INVALID_HANDLE_VALUE:
|
||||
stdio.printf("probe open failed: err=%u\n", wb.GetLastError())
|
||||
else:
|
||||
pli: wb.LARGE_INTEGER = wb.LARGE_INTEGER()
|
||||
if wf.GetFileSizeEx(ph, t.CPtr(&pli)) != 0:
|
||||
stdio.printf("zip file size on disk = %lld\n", pli.QuadPart)
|
||||
wb.CloseHandle(ph)
|
||||
|
||||
stdlib.free(hello)
|
||||
stdlib.free(big)
|
||||
stdlib.free(rnd)
|
||||
return 0
|
||||
|
||||
|
||||
def CheckReadBack(buf: bytes, expect: bytes, length: sizet) -> int:
|
||||
# buf: decompressed buffer returned by zipfile.read; expect: expected content
|
||||
if buf is None:
|
||||
return 0
|
||||
if length == 0:
|
||||
return 1
|
||||
return int(string.memcmp(buf, expect, length) == 0)
|
||||
|
||||
|
||||
def ReadZip() -> int:
|
||||
stdio.printf("\n--- 2) read-back verification ---\n")
|
||||
r: zipfile.ZipFile* = zipfile.ZipFile(ZIP_PATH, "r")
|
||||
if r.ok == 0:
|
||||
stdio.printf("open(r) failed: %s\n", zipfile.error())
|
||||
return 1
|
||||
|
||||
chk("entry count == 4", r.info_count == 4)
|
||||
for i in range(r.info_count):
|
||||
stdio.printf(" [%d] %s\n", int(i), r.namelist_at(i))
|
||||
|
||||
# ---- hello.txt: deflate text ----
|
||||
info: zipfile.ZipInfo* = r.getinfo("hello.txt")
|
||||
chk("getinfo(hello.txt)", info is not None)
|
||||
hello_expect: bytes = stdlib.malloc(64)
|
||||
string.strcpy(hello_expect, HELLO_TEXT)
|
||||
exp_len: sizet = StrLen(hello_expect)
|
||||
if info is not None:
|
||||
chk("hello file_size == 22", info.file_size == exp_len)
|
||||
chk("hello compress_size > 0", info.compress_size > 0)
|
||||
olen: sizet = 0
|
||||
data: bytes = r.read("hello.txt", &olen)
|
||||
chk("read(hello.txt)", data is not None)
|
||||
chk("hello content match", CheckReadBack(data, hello_expect, olen) == 1)
|
||||
stdlib.free(data)
|
||||
stdlib.free(hello_expect)
|
||||
|
||||
# ---- big.bin: deflate large file ----
|
||||
info = r.getinfo("big.bin")
|
||||
chk("getinfo(big.bin)", info is not None)
|
||||
big_expect: bytes = stdlib.malloc(BIG_LEN)
|
||||
fill_pattern(big_expect, BIG_LEN, 1)
|
||||
if info is not None:
|
||||
chk("big file_size == 100000", info.file_size == BIG_LEN)
|
||||
chk("big compress_size < file_size", info.compress_size < info.file_size)
|
||||
chk("big crc != 0", info.crc32 != 0)
|
||||
olen = 0
|
||||
data = r.read("big.bin", &olen)
|
||||
chk("read(big.bin)", data is not None)
|
||||
chk("big content match", data is not None and olen == BIG_LEN
|
||||
and CheckReadBack(data, big_expect, BIG_LEN) == 1)
|
||||
stdlib.free(data)
|
||||
stdlib.free(big_expect)
|
||||
|
||||
# ---- random.bin: stored ----
|
||||
info = r.getinfo("random.bin")
|
||||
chk("getinfo(random.bin)", info is not None)
|
||||
rnd_expect: bytes = stdlib.malloc(RND_LEN)
|
||||
fill_pattern(rnd_expect, RND_LEN, 0)
|
||||
if info is not None:
|
||||
chk("random file_size == 5000", info.file_size == RND_LEN)
|
||||
chk("random stored: compress_size == file_size", info.compress_size == info.file_size)
|
||||
olen = 0
|
||||
data = r.read("random.bin", &olen)
|
||||
chk("read(random.bin)", data is not None)
|
||||
chk("random content match", data is not None and olen == RND_LEN
|
||||
and CheckReadBack(data, rnd_expect, RND_LEN) == 1)
|
||||
stdlib.free(data)
|
||||
stdlib.free(rnd_expect)
|
||||
|
||||
# ---- empty.txt: empty member ----
|
||||
info = r.getinfo("empty.txt")
|
||||
chk("getinfo(empty.txt)", info is not None)
|
||||
olen = 0
|
||||
data = r.read("empty.txt", &olen)
|
||||
chk("read(empty.txt) ok", data is not None)
|
||||
chk("empty length == 0", olen == 0)
|
||||
stdlib.free(data)
|
||||
|
||||
# ---- Missing member ----
|
||||
olen = 0
|
||||
data = r.read("no_such.txt", &olen)
|
||||
chk("read(missing) returns None", data is None)
|
||||
chk("read(missing) code NOT_FOUND", zipfile.error_code() == zipfile.ZIP_ERR_NOT_FOUND)
|
||||
|
||||
rc: int = r.close()
|
||||
chk("close(r)", rc == zipfile.ZIP_ERR_OK)
|
||||
return 0
|
||||
|
||||
|
||||
def CorruptStoredCheck() -> int:
|
||||
stdio.printf("\n--- 3) CRC check after corrupting a stored member ---\n")
|
||||
r: zipfile.ZipFile* = zipfile.ZipFile(ZIP_PATH, "r")
|
||||
if r.ok == 0:
|
||||
stdio.printf("open(r) failed: %s\n", zipfile.error())
|
||||
return 1
|
||||
info: zipfile.ZipInfo* = r.getinfo("random.bin")
|
||||
chk("getinfo(random.bin)", info is not None)
|
||||
if info is None or info.compress_size < 2:
|
||||
stdio.printf("cannot locate stored member payload\n")
|
||||
r.close()
|
||||
return 1
|
||||
|
||||
# With no extra field in the local header, data starts at header_offset + 30 + name_len
|
||||
start: sizet = sizet(info.header_offset) + 30 + info.name_len
|
||||
flip_pos: sizet = start + info.compress_size - 1
|
||||
|
||||
# Approach: read the whole file into memory -> flip the last stored data byte -> rewrite the whole file (reusing the verified read/write loop)
|
||||
hr: wb.HANDLE = wf.CreateFileA(
|
||||
ZIP_PATH, wf.GENERIC_READ,
|
||||
wf.FILE_SHARE_READ | wf.FILE_SHARE_WRITE | wf.FILE_SHARE_DELETE,
|
||||
None, wf.OPEN_EXISTING, wf.FILE_ATTRIBUTE_NORMAL, None)
|
||||
chk("open R", hr != wb.INVALID_HANDLE_VALUE)
|
||||
if hr != wb.INVALID_HANDLE_VALUE:
|
||||
fsz_li: wb.LARGE_INTEGER = wb.LARGE_INTEGER()
|
||||
if wf.GetFileSizeEx(hr, t.CPtr(&fsz_li)) == 0 or fsz_li.QuadPart <= 0:
|
||||
chk("get file size", 0)
|
||||
wb.CloseHandle(hr)
|
||||
r.close()
|
||||
return 1
|
||||
fsz: sizet = sizet(fsz_li.QuadPart)
|
||||
whole: BYTEPTR = BYTEPTR(stdlib.malloc(fsz))
|
||||
if not whole:
|
||||
wb.CloseHandle(hr)
|
||||
r.close()
|
||||
return 1
|
||||
# Read the whole file
|
||||
done: sizet = 0
|
||||
rd_ok: int = 1
|
||||
while done < fsz:
|
||||
rdgot: ULONG = 0
|
||||
if wf.ReadFile(hr, whole + done, ULONG(fsz - done), &rdgot, None) == 0 or rdgot == 0:
|
||||
rd_ok = 0
|
||||
break
|
||||
done += sizet(rdgot)
|
||||
wb.CloseHandle(hr)
|
||||
chk("read whole file", rd_ok == 1)
|
||||
if rd_ok == 1 and flip_pos < fsz:
|
||||
stdio.printf(" flip stored data byte at %lld (size=%lld)\n", flip_pos, fsz_li.QuadPart)
|
||||
whole[flip_pos] = (whole[flip_pos] ^ 0xFF) & 0xFF
|
||||
# Rewrite the whole file
|
||||
hw: wb.HANDLE = wf.CreateFileA(
|
||||
ZIP_PATH, wf.GENERIC_WRITE,
|
||||
wf.FILE_SHARE_READ | wf.FILE_SHARE_WRITE | wf.FILE_SHARE_DELETE,
|
||||
None, wf.CREATE_ALWAYS, wf.FILE_ATTRIBUTE_NORMAL, None)
|
||||
chk("open W rewrite", hw != wb.INVALID_HANDLE_VALUE)
|
||||
if hw != wb.INVALID_HANDLE_VALUE:
|
||||
wr_ok: int = 1
|
||||
wdone: sizet = 0
|
||||
while wdone < fsz:
|
||||
wrgot: ULONG = 0
|
||||
if wf.WriteFile(hw, whole + wdone, ULONG(fsz - wdone), &wrgot, None) == 0 or wrgot == 0:
|
||||
wr_ok = 0
|
||||
break
|
||||
wdone += sizet(wrgot)
|
||||
wb.CloseHandle(hw)
|
||||
chk("rewrite file with flipped byte", wr_ok == 1)
|
||||
else:
|
||||
chk("flip position in bounds", 0)
|
||||
stdlib.free(whole)
|
||||
r.close()
|
||||
|
||||
z2: zipfile.ZipFile* = zipfile.ZipFile(ZIP_PATH, "r")
|
||||
chk("reopen after corruption", z2.ok == 1)
|
||||
olen: sizet = 0
|
||||
data: bytes = z2.read("random.bin", &olen)
|
||||
chk("corrupted read returns None", data is None)
|
||||
chk("error code == ZIP_ERR_CRC", zipfile.error_code() == zipfile.ZIP_ERR_CRC)
|
||||
stdio.printf(" last error: %s\n", zipfile.error())
|
||||
if data is not None:
|
||||
stdlib.free(data)
|
||||
z2.close()
|
||||
return 0
|
||||
|
||||
|
||||
@t.CExport
|
||||
def main() -> int:
|
||||
stdio.printf("ViperZipfileProject: zipfile module read/write test\n")
|
||||
if WriteZip() != 0:
|
||||
return 1
|
||||
if ReadZip() != 0:
|
||||
return 1
|
||||
if CorruptStoredCheck() != 0:
|
||||
return 1
|
||||
|
||||
wf.DeleteFileA(ZIP_PATH)
|
||||
stdio.printf("\nFailures: %d\n", fail_count)
|
||||
if fail_count == 0:
|
||||
stdio.printf("ALL PASSED\n")
|
||||
return int(fail_count != 0)
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 TermiNexus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,289 @@
|
||||
# Viper Template Project
|
||||
|
||||
[English](#english) | [中文](#中文)
|
||||
|
||||
A minimal, ready-to-use template for bootstrapping a new [Viper](../wiki/01-overview.md) language project on **Windows (x86_64-pc-windows-gnu)**.
|
||||
|
||||
Windows 平台(x86_64-pc-windows-gnu)下 [Viper](../wiki/01-overview.md) 语言新项目的最小可用模板。
|
||||
|
||||
---
|
||||
|
||||
<a id="中文"></a>
|
||||
|
||||
## 中文
|
||||
|
||||
### 这是什么
|
||||
|
||||
`ViperTemplateProject` 是基于 `Viper` 语言的 **Windows 桌面应用模板**。克隆本仓库后,你可以立即得到一个能够:
|
||||
|
||||
- 设置 Windows 控制台为 UTF-8 编码(避免中文乱码)
|
||||
- 打印一行 `你好,世界`
|
||||
- 通过 `llc + clang++`(mingw 工具链)编译为原生 `app.exe`
|
||||
|
||||
的最小 Viper 项目。
|
||||
|
||||
模板对应的语言编译器是 [TransPyC](https://git.gvsds.com/GVSDS/TransPyC);关于 Viper 语言本身的概览,参见 [TransPyC 仓库 wiki](../wiki/01-overview.md)。
|
||||
|
||||
### 目录结构
|
||||
|
||||
```
|
||||
ViperTemplateProject/
|
||||
├── .gitignore # 忽略 build/、*.o、*.exe、缓存等
|
||||
├── README.md # 本文件
|
||||
├── project.vpj # 项目配置(编译器、链接器、目标三元组等)
|
||||
└── App/
|
||||
└── main.py # 入口文件
|
||||
```
|
||||
|
||||
### 前提条件
|
||||
|
||||
| 工具 | 说明 |
|
||||
|------|------|
|
||||
| `python` ≥ 3.10 | 用于运行 TransPyC 编译器 |
|
||||
| `llc` | LLVM 静态编译器,编译 `.ll` → `.o` |
|
||||
| `clang++` | mingw 工具链下的链接驱动(`x86_64-w64-mingw32`) |
|
||||
| `TransPyC` 仓库 | 本编译器,克隆在 `ViperTemplateProject/..` 同一目录下 |
|
||||
|
||||
环境变量建议(PowerShell):
|
||||
|
||||
```powershell
|
||||
$env:PATH = "D:\msys64\mingw64\bin;D:\LLVM\bin;$env:PATH"
|
||||
```
|
||||
|
||||
### 快速开始
|
||||
|
||||
```powershell
|
||||
# 1. 确保目录布局正确
|
||||
# D:\Users\TermiNexus\Desktop\TransPyC\
|
||||
# ├── TransPyC\ # 编译器仓库
|
||||
# └── ViperTemplateProject\ # 本仓库
|
||||
|
||||
cd D:\Users\TermiNexus\Desktop\TransPyC\ViperTemplateProject
|
||||
|
||||
# 2. 编译并运行
|
||||
python ..\TransPyC\Projectrans.py `
|
||||
--project .\project.vpj `
|
||||
--clean-cache=false
|
||||
|
||||
# 3. 产物位于 output\app.exe
|
||||
.\output\app.exe
|
||||
```
|
||||
|
||||
预期输出:
|
||||
|
||||
```
|
||||
你好,世界
|
||||
```
|
||||
|
||||
### 编写你的第一个程序
|
||||
|
||||
打开 `App/main.py`,替换 `print("你好,世界")` 为任意合法 Viper 表达式。例如:
|
||||
|
||||
```python
|
||||
import w32.win32console as w32cmd
|
||||
import t, c
|
||||
|
||||
pagecode: t.CDefine = 65001
|
||||
|
||||
@t.CExport
|
||||
def main() -> int:
|
||||
w32cmd.SetConsoleOutputCP(pagecode)
|
||||
w32cmd.SetConsoleCP(pagecode)
|
||||
|
||||
# 你的代码从这里开始
|
||||
a: t.CInt = 1
|
||||
b: t.CInt = 2
|
||||
print("a + b =", a + b)
|
||||
|
||||
return 0
|
||||
```
|
||||
|
||||
### 修改项目配置
|
||||
|
||||
打开 `project.vpj`,按需调整:
|
||||
|
||||
| 字段 | 含义 | 模板默认值 |
|
||||
|------|------|-----------|
|
||||
| `name` | 项目名(仅显示用) | `Standard Template Project` |
|
||||
| `source_dir` | 源文件根目录 | `./App` |
|
||||
| `temp_dir` | 中间产物目录(`.pyi` / `.stub.ll`) | `./temp` |
|
||||
| `output_dir` | 最终产物目录 | `./output` |
|
||||
| `compiler.cmd` | 汇编器 | `llc` |
|
||||
| `compiler.flags` | 传给 `llc` 的参数 | `["-filetype=obj", "-relocation-model=pic"]` |
|
||||
| `linker.cmd` | 链接器 | `clang++` |
|
||||
| `linker.flags` | 链接器参数 | mingw 标准库 + `kernel32` |
|
||||
| `linker.output` | 产物名 | `app.exe` |
|
||||
| `includes` | 标准库搜索路径 | `["../includes"]` |
|
||||
| `target.triple` | LLVM 目标三元组 | `x86_64-pc-windows-gnu` |
|
||||
| `target.datalayout` | LLVM 数据布局 | mingw 默认 |
|
||||
| `options.target` | 后端目标 | `llvm` |
|
||||
| `options.strict_mode` | 严格类型检查 | `true` |
|
||||
| `options.slice_level` | 切片级别 | `3` |
|
||||
|
||||
### 跨平台使用
|
||||
|
||||
本模板默认针对 **Windows + mingw**。若需在其他平台使用,修改 `project.vpj` 的 `compiler.cmd` / `linker.cmd` / `target.triple`:
|
||||
|
||||
- **Linux (x86_64)**:`triple = "x86_64-unknown-linux-gnu"`,`linker.cmd = "clang++"`
|
||||
- **macOS (x86_64)**:`triple = "x86_64-apple-darwin*"`,`linker.cmd = "clang++"`
|
||||
- **裸机 x86_64 (ViperOS)**:`triple = "x86_64-none-elf"`,`linker.cmd = "ld.lld"`,需要 `isr.s` 启动文件
|
||||
|
||||
### 常见问题
|
||||
|
||||
**Q: 报错 `cannot find includes/os` / `cannot find includes/atom` 之类?**
|
||||
A: 检查 `project.vpj` 中的 `includes` 字段是否指向 `TransPyC/includes`。`../includes` 假设本仓库与 `TransPyC` 仓库同级。
|
||||
|
||||
**Q: 输出 `app.exe` 双击闪退?**
|
||||
A: 在终端中运行 `.\output\app.exe` 而非双击,并确认 mingw 与 LLVM 在 `PATH` 中。
|
||||
|
||||
**Q: 想完全清理构建产物?**
|
||||
A: 使用 `python ..\TransPyC\Projectrans.py --project .\project.vpj --clean-cache` 清理缓存;删除 `output/` 和 `temp/` 即可彻底重置。
|
||||
|
||||
**Q: `StandardTemplateProject` 和本仓库是什么关系?**
|
||||
A: 本仓库 `ViperTemplateProject` 是 `StandardTemplateProject` 的独立仓库版本。如果你仍想保留 `StandardTemplateProject` 放在 `TransPyC` 仓库内,只需把它加入 `TransPyC/.gitignore` 的 `# 排除独立仓库的目录` 小节。
|
||||
|
||||
### 进一步阅读
|
||||
|
||||
- [Viper 语言概览](../wiki/01-overview.md)
|
||||
- [类型系统](../wiki/02-type-system.md)
|
||||
- [OOP 与对象模型](../wiki/06-oop.md)
|
||||
- [自举路线图](../wiki/13-bootstrapping.md)
|
||||
|
||||
---
|
||||
|
||||
<a id="english"></a>
|
||||
|
||||
## English
|
||||
|
||||
### What is this
|
||||
|
||||
`ViperTemplateProject` is a **Windows desktop application template** based on the [Viper](../wiki/01-overview.md) language. After cloning, you immediately have a minimal Viper project that:
|
||||
|
||||
- Sets the Windows console to UTF-8 (avoids mojibake for non-ASCII output)
|
||||
- Prints `你好,世界`
|
||||
- Compiles to a native `app.exe` via `llc + clang++` (mingw toolchain)
|
||||
|
||||
The corresponding compiler is [TransPyC](https://git.gvsds.com/GVSDS/TransPyC). For a Viper language overview, see the [TransPyC wiki](../wiki/01-overview.md).
|
||||
|
||||
### Directory layout
|
||||
|
||||
```
|
||||
ViperTemplateProject/
|
||||
├── .gitignore # ignores build/, *.o, *.exe, caches, etc.
|
||||
├── README.md # this file
|
||||
├── project.vpj # project config (compiler, linker, target triple, ...)
|
||||
└── App/
|
||||
└── main.py # entry point
|
||||
```
|
||||
|
||||
### Prerequisites
|
||||
|
||||
| Tool | Notes |
|
||||
|------|-------|
|
||||
| `python` ≥ 3.10 | runs the TransPyC compiler |
|
||||
| `llc` | LLVM static compiler, `.ll` → `.o` |
|
||||
| `clang++` | mingw toolchain driver (`x86_64-w64-mingw32`) |
|
||||
| `TransPyC` repo | the compiler, cloned as a sibling of `ViperTemplateProject` |
|
||||
|
||||
Suggested `PATH` (PowerShell):
|
||||
|
||||
```powershell
|
||||
$env:PATH = "D:\msys64\mingw64\bin;D:\LLVM\bin;$env:PATH"
|
||||
```
|
||||
|
||||
### Quick start
|
||||
|
||||
```powershell
|
||||
# 1. Make sure the layout is right
|
||||
# D:\Users\TermiNexus\Desktop\TransPyC\
|
||||
# ├── TransPyC\ # compiler repo
|
||||
# └── ViperTemplateProject\ # this repo
|
||||
|
||||
cd D:\Users\TermiNexus\Desktop\TransPyC\ViperTemplateProject
|
||||
|
||||
# 2. Build and run
|
||||
python ..\TransPyC\Projectrans.py `
|
||||
--project .\project.vpj `
|
||||
--clean-cache=false
|
||||
|
||||
# 3. Output is at output\app.exe
|
||||
.\output\app.exe
|
||||
```
|
||||
|
||||
Expected output:
|
||||
|
||||
```
|
||||
你好,世界
|
||||
```
|
||||
|
||||
### Writing your first program
|
||||
|
||||
Open `App/main.py` and replace `print("你好,世界")` with any valid Viper expression. Example:
|
||||
|
||||
```python
|
||||
import w32.win32console as w32cmd
|
||||
import t, c
|
||||
|
||||
pagecode: t.CDefine = 65001
|
||||
|
||||
@t.CExport
|
||||
def main() -> int:
|
||||
w32cmd.SetConsoleOutputCP(pagecode)
|
||||
w32cmd.SetConsoleCP(pagecode)
|
||||
|
||||
# your code goes here
|
||||
a: t.CInt = 1
|
||||
b: t.CInt = 2
|
||||
print("a + b =", a + b)
|
||||
|
||||
return 0
|
||||
```
|
||||
|
||||
### Editing project.vpj
|
||||
|
||||
| Field | Meaning | Template default |
|
||||
|-------|---------|------------------|
|
||||
| `name` | display name | `Standard Template Project` |
|
||||
| `source_dir` | source root | `./App` |
|
||||
| `temp_dir` | intermediate artifacts (`.pyi` / `.stub.ll`) | `./temp` |
|
||||
| `output_dir` | final artifacts | `./output` |
|
||||
| `compiler.cmd` | assembler | `llc` |
|
||||
| `compiler.flags` | flags passed to `llc` | `["-filetype=obj", "-relocation-model=pic"]` |
|
||||
| `linker.cmd` | linker | `clang++` |
|
||||
| `linker.flags` | linker flags | mingw stdlib + `kernel32` |
|
||||
| `linker.output` | output name | `app.exe` |
|
||||
| `includes` | standard library search paths | `["../includes"]` |
|
||||
| `target.triple` | LLVM target triple | `x86_64-pc-windows-gnu` |
|
||||
| `target.datalayout` | LLVM data layout | mingw default |
|
||||
| `options.target` | backend target | `llvm` |
|
||||
| `options.strict_mode` | strict type checking | `true` |
|
||||
| `options.slice_level` | slice level | `3` |
|
||||
|
||||
### Cross-platform use
|
||||
|
||||
This template targets **Windows + mingw** by default. To use on another platform, adjust `compiler.cmd` / `linker.cmd` / `target.triple` in `project.vpj`:
|
||||
|
||||
- **Linux (x86_64)**: `triple = "x86_64-unknown-linux-gnu"`, `linker.cmd = "clang++"`
|
||||
- **macOS (x86_64)**: `triple = "x86_64-apple-darwin*"`, `linker.cmd = "clang++"`
|
||||
- **Bare-metal x86_64 (ViperOS)**: `triple = "x86_64-none-elf"`, `linker.cmd = "ld.lld"`, requires `isr.s` startup
|
||||
|
||||
### FAQ
|
||||
|
||||
**Q: Error like `cannot find includes/os` or `cannot find includes/atom`?**
|
||||
A: Check that the `includes` field in `project.vpj` points to the `TransPyC/includes` directory. `../includes` assumes this repo lives next to `TransPyC`.
|
||||
|
||||
**Q: `app.exe` flashes and closes on double-click?**
|
||||
A: Run `.\output\app.exe` from a terminal, not by double-clicking. Make sure mingw and LLVM are on `PATH`.
|
||||
|
||||
**Q: How to fully clean build artifacts?**
|
||||
A: `python ..\TransPyC\Projectrans.py --project .\project.vpj --clean-cache` clears the cache. To reset everything, also delete `output/` and `temp/`.
|
||||
|
||||
**Q: What is the relationship with `StandardTemplateProject`?**
|
||||
A: This repo `ViperTemplateProject` is the standalone-repo version of `StandardTemplateProject`. If you still want `StandardTemplateProject` to live inside the `TransPyC` repo, add it to the `# 排除独立仓库的目录` section in `TransPyC/.gitignore`.
|
||||
|
||||
### Further reading
|
||||
|
||||
- [Viper language overview](../wiki/01-overview.md)
|
||||
- [Type system](../wiki/02-type-system.md)
|
||||
- [OOP and object model](../wiki/06-oop.md)
|
||||
- [Bootstrapping roadmap](../wiki/13-bootstrapping.md)
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "ViperZipfileProject - zipfile 模块测试",
|
||||
"version": "1.0.0",
|
||||
"source_dir": "./App",
|
||||
"build_dir": "./.tpv_build",
|
||||
"compiler": {
|
||||
"cmd": "llc",
|
||||
"flags": [
|
||||
"-filetype=obj",
|
||||
"-relocation-model=pic"
|
||||
]
|
||||
},
|
||||
"linker": {
|
||||
"cmd": "D:/msys64/mingw64/bin/clang++.exe",
|
||||
"flags": [
|
||||
"-fuse-ld=lld",
|
||||
"-lmsvcrt",
|
||||
"-lucrt",
|
||||
"-lpthread",
|
||||
"-lmingwex",
|
||||
"-lkernel32"
|
||||
],
|
||||
"output": "app.exe"
|
||||
},
|
||||
"includes": [
|
||||
"../../includes"
|
||||
],
|
||||
"target": {
|
||||
"triple": "x86_64-pc-windows-gnu",
|
||||
"datalayout": "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
|
||||
},
|
||||
"options": {
|
||||
"slice_level": 3,
|
||||
"target": "llvm",
|
||||
"strict_mode": true
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user