ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

用 V 语言驱动多语言 ORM 基准与 JS DOM Canvas 图表:js_dom_draw_benchmark_chart 示例全解析

用 V 语言驱动多语言 ORM 基准与 JS DOM Canvas 图表:js_dom_draw_benchmark_chart 示例全解析 用 V 语言驱动多语言 ORM 基准与 JS DOM Canvas 图表js_dom_draw_benchmark_chart 示例全解析【免费下载链接】vSimple, fast, safe, compiled language for developing maintainable software. Compiles itself in 1s with zero library dependencies. Supports automatic C V translation. https://vlang.io项目地址: https://gitcode.com/GitHub_Trending/v/v本篇指南围绕 V 语言仓库 examples/js_dom_draw_benchmark_chart 示例展开它同时运行一个基于 V veb ORMSQLite的 Web 基准服务、一个基于 TypeScript TypeORM 的对照基准服务以及一个用 V 编写的veb图表服务——后者会把 V ORM 与 TypeScript ORM 的 insert/select/update 单次耗时收集起来并通过 V 编译到 JS DOMv -b js_browser的代码在canvas上叠加绘制对比折线。读完本文你将掌握该示例的完整启动方法、三个服务的分工与端口约定、V veb/ORM 基准接口的实现模式以及如何新增一组新的基准项使其自动出现在图表中。示例的整体架构与数据流这个示例不是一个单体程序而是由三个可独立运行的进程协同构成仓库中的目录布局清晰对应了各自职责组件目录语言/框架端口V ORM 基准服务v_veb_ormV veb ORM SQLite内存库4000TypeScript 基准服务typescript_vanilla_typeormTypeScript TypeORM sqlite33000图表展示服务chartV veb V→JSjs.dom3001数据流的方向非常直观用户在浏览器访问http://localhost:3001/图表服务的首页路由通过http.get()分别向http://localhost:4000/sqlite-memory/${count}与http://localhost:3000/sqlite-memory/${count}发起请求见 chart/main.v两个后端各自返回一个包含insert/select/update三个int数组的 JSON数组中的每个元素是一次数据库操作的耗时单位为纳秒。图表服务把这些数据渲染成 HTML 表格并把 JSON 字符串作为自定义属性注入页面中的多个canvas元素最后页面加载draw.js——由 draw.js.v 编译得到的 JS在浏览器端读取 canvas 属性并绘制多色折线。运行前的环境准备顶层 README.md 明确要求本机已安装 Node.js因为其中一个对照端是 TypeScript/TypeORM 服务需要npm安装依赖。以下步骤假设你的当前目录已经位于仓库根目录即/data/web/disk1/git_repo/GitHub_Trending/v/vcd examples/js_dom_draw_benchmark_chart进入示例目录后按 README 的顺序依次启动三个进程即可。启动第一个端TypeScript TypeORM 基准服务TypeScript 端在 typescript_vanilla_typeorm 目录下README 给出的启动命令是npm i --prefix typescript_vanilla_typeorm npm run start:dev --prefix typescript_vanilla_typeorm第一步安装依赖第二步进入开发模式。查看 package.json可以发现start:dev实际是借助nodemon监听*.ts变化每次变更后自动执行npm run start即先执行tsc编译产物进入dist/再执行node server.js。该服务的依赖中同时包含typeorm、sqlite3、reflect-metadata、typescript^4.7.4与types/node。TS 端核心代码 展示了与 V 端对齐的测法import { performance } from perf_hooks; import { Entity, Column, PrimaryGeneratedColumn, DataSource } from typeorm; Entity(benchmark) export class Task { PrimaryGeneratedColumn() id?: number; Column(text) title!: string; Column(text) status!: string; } export const appDataSource new DataSource({ type: sqlite, database: :memory:, dropSchema: true, entities: [Task], synchronize: true, // create a new table logging: false, });对照 V 端的Task结构v_veb_orm/main.v 中标记为[table: benchmark]并含title、status两列两个端完全共用同一张benchmark表结构这是二者可对比的前提。sqlite_memory()函数使用 Node 的performance.now()计时并把毫秒差值乘上1000000转成纳秒后Math.floor取整压入数组得到与 V 端一致的度量口径。最后返回结构相同的Responsetype Response { insert: number[]; select: number[]; update: number[]; };结合 chart/main.v 中请求http://localhost:3000/sqlite-memory/${benchmark_loop_length}的逻辑可以推断server.js负责把 TypeORM 的sqlite_memory(count)以/sqlite-memory/:count路由暴露在 3000 端口上返回与 V 端字段一一对应的 JSON。启动第二个端V veb ORM 基准服务README 给出的命令是v run v_veb_orm/顶层 README 原文写的是v run v_veb_orm/src/但按当前仓库实际结构入口 main.v 直接位于v_veb_orm/目录下因此v run v_veb_orm/才是可运行命令。V 端入口 用veb.run_atApp, Context监听 4000 端口并注册了以下路由路由实现状态GET /hello-world返回纯文本hello world用于连通性验证GET /sqlite-memory/:count完整实现 insert/select/update 计时GET /sqlite-file/:count预留空实现返回空数组GET /postgres/:count预留空实现返回空数组GET /mysql/:count预留空实现返回空数组/sqlite-memory/:count的核心逻辑是每次请求建立一个:memory:内存数据库并create table Task随后循环count次用time.new_stopwatch()对单次 ORM 操作计时把耗时纳秒级依次收集到insert_stopwatchs、select_stopwatchs、update_stopwatchs三个数组中最后drop table清理并返回 JSON[/sqlite-memory/:count] pub fn (mut app App) sqlite_memory(mut ctx Context, count string) veb.Result { count_int : count.int() mut insert_stopwatchs : []int{} mut select_stopwatchs : []int{} mut update_stopwatchs : []int{} mut sw : time.new_stopwatch() mut db : sqlite.connect(:memory:) or { panic(err) } sql db { create table Task } or { panic(err) } task_model : Task{ title: a status: done } // inserts for i : 0; i count_int; i { sw.start() sql db { insert task_model into Task } or { panic(err) } sw.stop() insert_stopwatchs int(sw.end - sw.start) } // selects for i : 0; i count_int; i { sw.start() result : sql db { select from Task } or { []Task{} } sw.stop() eprintln(result) select_stopwatchs int(sw.end - sw.start) } // updates for i : 0; i count_int; i { sw.start() sql db { update Task set title b, status finish where id i } or { panic(err) } sw.stop() update_stopwatchs int(sw.end - sw.start) } sql db { drop table Task } or { panic(err) } response : Response{ insert: insert_stopwatchs select: select_stopwatchs update: update_stopwatchs } return ctx.json(response) }注意这里的返回类型Response只包含三个整型数组结构体字段注释与 TS 端完全一致确保图表端可以用同一个json.decode目标结构体解析两边的响应。图表请求时传入的count来自 chart/main.v 中声明的常量benchmark_loop_length 20即默认循环 20 次、返回 20 个采样点。启动第三个端V 图表服务两个基准服务都就绪后最后启动图表v run chart/启动成功后浏览器访问http://localhost:3001/页面标题为 “Is V orm still fast?”并附带当前 V 版本号取自v.util.version.full_v_version(true)。若按 chart/README.md 的提示控制台会出现类似[veb] Running app on http://localhost:3001/的消息。图表服务的静态资源与 JS 动态编译chart/main.v 的main()除了启动veb.runApp, Context外还完成了三件静态资源注册工作mut app : App{} app.serve_static(/favicon.ico, favicon.ico) or { panic(err) } app.serve_static(/draw.js, draw.js) or { panic(err) } app.mount_static_folder_at(os.resource_abs_path(.), /) or { panic(err) }其中最有意思的是before_request钩子每次请求进入前都会用os.execute_or_panic(v -b js_browser draw.js.v )现场把 draw.js.v 重新编译成draw.js也就是说这个示例把 V 编译到浏览器 JSjs_browser目标的整条链路直接嵌进了 Web 服务的生命周期中改完绘制代码刷新页面即生效无需手工执行编译步骤。首页路由拉取两个基准端的数据并聚合GET /路由chart/main.v负责整页聚合它遍历[insert, select, update]三种语句类别分别调用insert_framework_benchmark_times()、select_framework_benchmark_times()、update_framework_benchmark_times()每个函数内部并行发起 V 端与 TypeScript 端两个 HTTP 请求并组装成FrameworkPlatform结构体struct FrameworkPlatform { mut: v_sqlite_memory []int // v_sqlite_file []int typescript_sqlite_memory []int } fn (framework_platform FrameworkPlatform) to_map() map[string][]int { mut mapa : map[string][]int{} mapa[v_sqlite_memory] framework_platform.v_sqlite_memory // mapa[v_sqlite_file] framework_platform.v_sqlite_file mapa[typescript_sqlite_memory] framework_platform.typescript_sqlite_memory return mapa }结构体注释掉的v_sqlite_file表明设计上允许扩展更多数据源当前只启用v_sqlite_memoryV与typescript_sqlite_memoryTypeScript两个序列。聚合过程中arrays.max用来计算每组数据的最大耗时max_benchmark用于让折线图按最慢者等比缩放json.encode负责把耗时序列转成注入 HTML 的字符串。随后路由调用gen_table_info()生成统计表格并通过return $veb.html()渲染同目录模板templates/controller/get/all/task.html。模板注入Canvas 与统计表的编排task.html 是 veb 的控制器模板对insert/select/update三种语句各生成一个卡片卡片左侧是从 0 到max_benchmark均匀分成 10 档、标注纳秒数值的 Y 轴刻度每个刻度为int(number * (max_benchmark/10))ns右侧是一个尺寸为720 × 480的canvas。关键的数据传递方式是自定义 HTML 属性canvas idcanvas_${orm_stmt_kind}_id inserts_from_framework{from_framework[orm_stmt_kind]} max_benchmark{max_benchmark[orm_stmt_kind]} styleborder: 1px solid grey width720 height480 /canvasinserts_from_framework携带的是完整的 JSON 字符串如{v_sqlite_memory:[...],typescript_sqlite_memory:[...]}max_benchmark则是该类别下的最大耗时浏览器端 JS 直接从属性中取回数据避免了额外的 XHR 请求。每个卡片下方还渲染一张对比表列依次为max.最大耗时、10% max.最高的 10% 样本平均、min.最小耗时、10% min.最低的 10% 样本平均单元格内容由服务端gen_table_info()预先算好并格式化为“x.xx ms (Nx faster)”。行首的名称用 chart/main.v 中定义的chart_colors : [gray, red, orange, purple, ...]上色与后面折线颜色一一对应。页面底部加载script typetext/javascript srcdraw.js/script把绘制工作完全交给 V 编译出的 JS。draw.js.v用 V 写 Canvas 折线绘制draw.js.v 是整个示例“JS DOM 绘制”主题的落点。它通过import js.dom直接操作浏览器 API先用document.getElementById(canvas_insert_id)等分别取得三类 canvas再对每个 canvas 调用getContext(2d)。由于 V 的 JS 绑定中存在强类型接口代码里用一个match分支把ctx收窄成JS.CanvasRenderingContext2Dcanvas_elem[insert] document.getElementById(canvas_insert_id.str)? // ... ctx : canvas[orm_stmt_kind].getContext(2d.str, js_undefined())? mut context : JS.CanvasRenderingContext2D{} match ctx { JS.CanvasRenderingContext2D { context ctx } else { panic(can not get 2d context) } }随后从 canvas 属性读回数据getAttribute(inserts_from_framework)取出 JSONgetAttribute(max_benchmark)取出比例基准并用JS.JSON.parse反序列化回结构体FrameworkPlatform。当前仓库的 V→JS 实现还无法直接遍历对象的键值对源码注释写明 “Waiting for v implement for loop getting key and value of object in v.js”因此代码显式按字段遍历obj.v_sqlite_memory与obj.typescript_sqlite_memorymut obj : FrameworkPlatform{} obj JS.JSON.parse(tos(inserts_from_framework)) mut attribute_int_values : []int{} //* v framework for variable in obj.v_sqlite_memory { attribute_int_values variable } state.draw_bench_chart(gray, attribute_int_values, tos(max_benchmark).int())? attribute_int_values [] //* typescript framework for variable in obj.typescript_sqlite_memory { attribute_int_values variable } state.draw_bench_chart(red, attribute_int_values, tos(max_benchmark).int())?DrawState结构体缓存了当前画笔状态context、是否绘制中、当前 x/ydraw_bench_chart()把每个采样点按max_time归一化映射到 480px 高的画布y max_height - max_height/max_time * value再逐段moveTo/lineTo/stroke连成折线。因此同一张 canvas 上会出现两条叠加曲线灰色对应 V ORM红色对应 TypeScript ORM。统计口径从纳秒采样到 “x faster”chart/main.v 的gen_table_info()体现了这个示例在展示层所做的统计加工值得单独拆解单位换算两端上报的单次耗时均为纳秒表格展示时统一除以1_000_000用math.round_sig(x, 2)保留两位有效数字显示为毫秒并在模板 Y 轴上保留纳秒刻度去极值平均为避免个别抖动样本污染结论代码对每组的耗时数组排序后trim(len/10)分别截掉最高/最低的 10% 样本再求均值得到10% max.其余 90% 中最差水平与10% min.90% 中的最佳水平两类指标相对加速比遍历各组找出全局最大值作为分母算出max_fast/min_fast/...等整型倍率最终拼出x.xx ms (Nx faster)的单元格文案用同一基准横向比较不同框架。如何扩展一组新的 Benchmarkchart/README.md 专门写了新增基准的步骤其内部引用的路径基于当时的目录结构下面给出的是对照当前仓库实际布局整理后的流程第一步在 V 基准服务中添加路由。打开 v_veb_orm/main.v仿照/sqlite-memory/:count写一个返回Response结构体 JSON 的路由例如[/sqlite-memory/:count] pub fn (mut app App) sqlite_memory(mut ctx Context, count int) veb.Result { mut insert_stopwatchs : []int{} mut select_stopwatchs : []int{} mut update_stopwatchs : []int{} mut sw : time.new_stopwatch() mut db : sqlite.connect(:memory:) or { panic(err) } sql db { create table Task } or { panic(err) } task_model : Task{ title: a status: done } for i : 0; i count; i { sw.start() sql db { insert task_model into Task } or { []Task{} } sw.stop() insert_stopwatchs int(sw.end - sw.start) } sql db { drop table Task } or { panic(err) } response : Response{ insert: insert_stopwatchs select:select_stopwatchs update: update_stopwatchs } return ctx.json(response) }示例片段中的count int写法展示了 ORM 路由参数声明的一种形式当前仓库实际代码里用count string加count.int()转换两种皆可。字段前的select:是为了转义 V 关键字select。第二步在图表服务的 chart/main.v 中补一个取数函数用http.get拉取新基准端的数据并json.decode成FrameworkBenchmarkResponsefn typescript_sqlite_memory() ?FrameworkBenchmarkResponse { url : http://localhost:3000/sqlite-memory/${benchmark_loop_length} res : http.get(url) or { panic(err) } framework_benchmark_response : json.decode(FrameworkBenchmarkResponse, res.body)! return framework_benchmark_response }第三步接线与上色。把新函数挂进insert_framework_benchmark_times()、select_framework_benchmark_times()、update_framework_benchmark_times()三个聚合函数并在FrameworkPlatform中增加对应字段to_map()会自动把它变成图表数据的一个序列若希望新序列在表格与折线中拥有独立的展示色还需在chart_colors中补上对应颜色。仓库中预置的sqlite_file、postgres、mysql路由与结构体注释里的v_sqlite_file字段正是为这套扩展路径预留的插槽。其他运行方式与注意事项Docker 运行图表服务chart/README.md 提供了容器化方案chart/Dockerfile 基于官方镜像thevlang/vlang:alpine-dev先v up更新到最新编译器再v -prod main.v做生产编译。镜像构建后按下列命令运行并映射 3001 端口sudo docker build -t name . sudo docker run --name container name --interactive --tty --publish 3001:3001 name进程启动顺序与依赖图表首页在渲染时会主动请求 4000 与 3000 端口因此应先把 V 端与 TypeScript 端跑起来再启动 chart否则取数函数会因连接失败而走error分支页面拿不到可绘制的数据。V→JS 编译目标示例依赖 V 的js_browser目标before_request中每次请求执行v -b js_browser draw.js.v。因此本地运行要求所用的 V 版本支持该编译目标若使用 Docker 镜像则可在容器内经v up获得一致的新版本编译环境。开发与生产两种姿势TypeScript 端npm run start:dev借助 nodemon 实现改动自动重编译并重启Node.js 为硬性前置条件chart 端则通过 veb 的before_request钩子在每次访问时重新生成draw.js两种方式都做到了“改完即生效”。该示例完整覆盖了从“用 veb ORM 写带计时的基准服务”“跨语言 HTTP 汇总 JSON 数据”“veb 模板注入 canvas 属性”到“用 V 编译 JS DOM 代码在 Canvas 上叠画多框架曲线”的一整条链路无论把它当作 V 的 ORM/veb 实战范例还是当作 V→JS DOM 编程的参考都值得在仓库中直接v run起来观察它的运行效果。【免费下载链接】vSimple, fast, safe, compiled language for developing maintainable software. Compiles itself in 1s with zero library dependencies. Supports automatic C V translation. https://vlang.io项目地址: https://gitcode.com/GitHub_Trending/v/v创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表