Repository navigation
Benchmarking
简体中文 | English
本页展示当前实现的基准方法和实测结果。所有成绩来自 Run #7,测试提交为 76dcc7e,运行日期为 2026-10-04。服务端表格统一以毫秒显示,保留六位小数;JSON 保存整数纳秒值。JMH 微基准另标明每次操作的计时单位。
服务端通常每秒更新 20 次,每次更新称为一个 tick,目标间隔约为 50 ms。脚本通过 skript-reflect 调用 System.nanoTime();纳秒是计时单位,不意味着单次测量有纳秒级准确度。调度、JVM 垃圾回收、数据库执行和脚本恢复时机都会影响读数。
当前脚本独占的局部输入可以在后台读取和检查;全局输入仍在主线程跨 tick 分段读取,每段最多 4096 个处理步骤,目标耗时约 2 ms。共享默认变量回到主线程处理。局部结果中的普通值也可以在脚本暂停期间于后台保存,继续执行前恢复局部上下文。
物品、位置等需要服务器 API 的转换共用每 tick 目标预算为 2 ms 的队列。单个耗时很长的转换无法中断,因此预算不是硬上限。全局结果最后仍在主线程发布,这一步不受转换队列预算约束。处理大量数据时,优先使用局部结果和较小分页。
一次读取最多保存 5000 行,更多结果会被拒绝且不保留部分结果。写入语句最多绑定 30000 个值,较大批次会拆成多条语句。拆分本身不保证全部成功或全部回滚。见读取行、写入行和事务。
五种数据库的插件读写、JMH 和报告发布全部通过。环境为 Paper 26.2 build 124、Skript 2.16.2、skript-reflect 2.6.3 和 Java 25。下表用于核对测试覆盖范围,不比较速度。后端是被测数据库;插件读写包含行数曲线、重复用例和新增作用域用例;原始 SQL 对照说明是否运行手写 SQL;CPU/数据库给出作业环境。MongoDB 使用文档命令,不支持 SQL,因此只跳过 SQL 对照。
| 后端 | 插件读写 | 原始 SQL 对照 | CPU/数据库 |
|---|---|---|---|
| SQLite | 已完成 | 已完成 | AMD EPYC 9V74 / SQLite |
| MySQL | 已完成 | 已完成 | AMD EPYC 7763 / mysql:8.4
|
| MariaDB | 已完成 | 已完成 | AMD EPYC 9V45 / mariadb:11.4
|
| PostgreSQL | 已完成 | 已完成 | AMD EPYC 9V74 / postgres:17
|
| MongoDB | 已完成 | 不适用:不支持 SQL | Intel Xeon Platinum 8573C / mongo:8
|
各作业的 CPU 不同,不能按跨数据库的绝对耗时排名。测试用的 bench:: 变量不写入 Skript 的变量文件,以免临时数据的保存队列积压;测试仍包含内存中的变量处理和 ORM 数据库操作。
总耗时从语句开始到脚本恢复运行,包含变量处理、数据库操作及调度等待;生成数据、后续计数、校验和清理不计入。最大 tick 间隔是操作开始到返回后一个 tick 的窗口内,观察器相邻两次执行的最大间隔,不是插件独占主线程的时间。tick 超出量为该间隔减去 50 ms,负数按零处理。每行的操作和请求行数标明数据规模。
这组用例每行只填 id,其余五列为 NULL,输入和结果均为全局变量。每种数据库处理相同的数据结构,横向看读写等待时间与 tick 间隔,可以区分“整次操作等待较久”和“期间某次更新迟到”。每项仅采样一次,不能据此确定稳定的性能差距。
| 后端 | 操作 | 请求行数 | 总耗时 | 最大 tick 间隔 | tick 超出量 |
|---|---|---|---|---|---|
| SQLite | 写入 | 5000 | 738.019777 ms | 51.039040 ms | 1.039040 ms |
| SQLite | 读取 | 5000 | 101.214641 ms | 101.797048 ms | 51.797048 ms |
| MySQL | 写入 | 5000 | 653.115830 ms | 50.743193 ms | 0.743193 ms |
| MySQL | 读取 | 5000 | 99.480507 ms | 100.044641 ms | 50.044641 ms |
| MariaDB | 写入 | 5000 | 627.244008 ms | 50.787066 ms | 0.787066 ms |
| MariaDB | 读取 | 5000 | 75.822950 ms | 76.335655 ms | 26.335655 ms |
| PostgreSQL | 写入 | 5000 | 680.279059 ms | 50.846258 ms | 0.846258 ms |
| PostgreSQL | 读取 | 5000 | 106.822833 ms | 107.407271 ms | 57.407271 ms |
| MongoDB | 写入 | 5000 | 605.432622 ms | 50.700463 ms | 0.700463 ms |
| MongoDB | 读取 | 5000 | 95.601344 ms | 95.916557 ms | 45.916557 ms |
这张表使用相同的只填 id、全局变量用例。10000 行写入耗时/最大 tick 间隔来自较大批次;5000 行重复写入耗时/最大 tick 间隔来自六种行数测试之后再次执行的批次。重复写入应与上一表的首次 5000 行写入比较,不能与 10000 行直接相减作为预热收益;重复运行也不保证缓存已经预热。
| 后端 | 10000 行写入耗时 | 最大 tick 间隔 | 5000 行重复写入耗时 | 重复写入最大 tick 间隔 |
|---|---|---|---|---|
| SQLite | 1416.792130 ms | 50.810946 ms | 786.039922 ms | 50.662504 ms |
| MySQL | 1208.194044 ms | 50.795271 ms | 676.070701 ms | 50.894886 ms |
| MariaDB | 1207.324490 ms | 50.689455 ms | 700.069976 ms | 50.463965 ms |
| PostgreSQL | 1294.077597 ms | 50.748823 ms | 654.585784 ms | 50.558487 ms |
| MongoDB | 1349.871031 ms | 50.522086 ms | 645.084583 ms | 50.512251 ms |
这组对照独立运行,每条路径处理 5000 行,只填 id,使用全局输入和结果。插件写入/读取分别计时 insert many 和 select many;原始 SQL 写入/读取分别计时 execute update 和 execute query,均为完整语句总耗时。SQL 字符串在计时前构造,插件写入则包含输入准备,因此比值不能直接当成 ORM 映射开销。把同一数据库的两条路径放在一起,是为了观察完整调用的成本差异。
**不适用(不支持 SQL)**表示 MongoDB 不接受这组 SQL 语句;它的插件读写仍已完成,缺少 SQL 成绩也不代表零耗时。MySQL 插件路径使用参数化多行插入,其他 JDBC 路径使用驱动批处理。
| 后端 | 插件写入 | 原始 SQL 写入 | 插件读取 | 原始 SQL 读取 |
|---|---|---|---|---|
| SQLite | 749.999370 ms | 50.010275 ms | 94.916647 ms | 75.878837 ms |
| MySQL | 750.014611 ms | 81.068476 ms | 109.862134 ms | 92.715736 ms |
| MariaDB | 699.836833 ms | 49.816977 ms | 81.606404 ms | 69.435926 ms |
| PostgreSQL | 750.100345 ms | 94.180613 ms | 85.262522 ms | 112.546390 ms |
| MongoDB | 699.951095 ms | 不适用(不支持 SQL) | 84.042793 ms | 不适用(不支持 SQL) |
下面展开同一次运行的 MariaDB 结果,使用 AMD EPYC 9V45 和 mariadb:11.4。仍是每行只填 id、全局输入和结果。固定数据库与数据结构,再改变行数,可以观察任务规模与等待时间、tick 延迟的关系;重复 5000 行用于观察运行顺序的影响。
结果说明操作是否成功。10000 行读取超过上限,其耗时衡量拒绝请求和清空结果,不是成功读取 10000 行的成绩。“重复”指其他行数用例结束后再次运行,不保证缓存已预热。
| 操作 | 请求行数 | 总耗时 | 最大 tick 间隔 | tick 超出量 | 结果 |
|---|---|---|---|---|---|
| 写入 | 100 | 100.166930 ms | 52.934261 ms | 2.934261 ms | 成功 |
| 读取 | 100 | 51.661766 ms | 52.577681 ms | 2.577681 ms | 成功 |
| 写入 | 500 | 49.721242 ms | 50.633752 ms | 0.633752 ms | 成功 |
| 读取 | 500 | 55.148990 ms | 56.118816 ms | 6.118816 ms | 成功 |
| 写入 | 1000 | 199.506323 ms | 54.222904 ms | 4.222904 ms | 成功 |
| 读取 | 1000 | 56.909843 ms | 57.477091 ms | 7.477091 ms | 成功 |
| 写入 | 2500 | 299.843110 ms | 50.377625 ms | 0.377625 ms | 成功 |
| 读取 | 2500 | 70.805768 ms | 71.374238 ms | 21.374238 ms | 成功 |
| 写入 | 5000 | 627.244008 ms | 50.787066 ms | 0.787066 ms | 成功 |
| 读取 | 5000 | 75.822950 ms | 76.335655 ms | 26.335655 ms | 成功 |
| 写入 | 10000 | 1207.324490 ms | 50.689455 ms | 0.689455 ms | 成功 |
| 读取 | 10000 | 57.263183 ms | 57.794867 ms | 7.794867 ms | 被拒绝:超过 5000 行读取上限 |
| 写入(重复) | 5000 | 700.069976 ms | 50.463965 ms | 0.463965 ms | 成功 |
| 读取(重复) | 5000 | 83.557357 ms | 84.144273 ms | 34.144273 ms | 成功 |
新增用例填满每行的六列,并比较局部输入/局部结果与全局输入/全局结果。还有混合作用域、替换已有结果,以及物品和位置用例。所有用例在计时前生成数据,每次使用新的主键范围,查询只读取本次范围。生成、校验、计数和清理都在计时窗口外。
每个新增用例预热三次、正式测量十次。表中展示十次正式测量的中位数;预热不参与汇总。每个后端的附件包含 442 条原始记录、34 组汇总,另有 p95 和最大值。p95 用最近秩法计算,十个正式样本时等于最大值,对罕见延迟的判断能力有限。只填 id 的曲线和 SQL 对照各采样一次,不混入这些中位数。
| 用例 | 行数 | 比较目的 |
|---|---|---|
| 六个数值列全部填值:局部与全局 | 100、500、1000、2500、5000 | 固定数据库工作量,观察变量准备和结果保存成本 |
| 局部输入/全局结果、全局输入/局部结果 | 5000 | 分别观察输入与结果作用域的影响 |
| 替换已有局部或全局结果 | 5000 | 计入清除旧结果的工作,校验旧行和旧列被移除 |
| 四个数字、一个物品和一个位置 | 1000 | 观察普通值与主线程对象转换混合时的成本 |
| 带名称和描述的物品、位置,替换局部结果 | 1000 | 覆盖对象属性保留与结果替换 |
下表固定读取 5000 行,每行六个数值列全部有值。操作区分局部或全局结果,主线程处理耗时是本次操作中已计时的主线程处理区间累计耗时中位数;它不是整个 tick 的耗时,也没有覆盖语句的所有零散开销。总耗时和最大 tick 间隔各取十次样本的中位数;tick 超出量由表中的间隔中位数减去 50 ms 后取非负值,不代表十次采样中最严重的延迟。将总耗时与主线程处理放在一起,可以看到工作移到了哪里。局部与全局来自同一次服务端运行,适合在同一数据库内比较。
| 后端 | 操作 | 请求行数 | 总耗时 | 最大 tick 间隔 | tick 超出量 | 主线程处理耗时 |
|---|---|---|---|---|---|---|
| SQLite | 读取到局部变量 | 5000 | 49.925225 ms | 50.208298 ms | 0.208298 ms | 0.018713 ms |
| SQLite | 读取到全局变量 | 5000 | 65.414039 ms | 65.743510 ms | 15.743510 ms | 15.483043 ms |
| MySQL | 读取到局部变量 | 5000 | 49.923762 ms | 50.204954 ms | 0.204954 ms | 0.015720 ms |
| MySQL | 读取到全局变量 | 5000 | 86.510248 ms | 87.054996 ms | 37.054996 ms | 36.381696 ms |
| MariaDB | 读取到局部变量 | 5000 | 50.005949 ms | 50.250288 ms | 0.250288 ms | 0.017637 ms |
| MariaDB | 读取到全局变量 | 5000 | 66.299079 ms | 66.890665 ms | 16.890665 ms | 15.888968 ms |
| PostgreSQL | 读取到局部变量 | 5000 | 49.942947 ms | 50.217965 ms | 0.217965 ms | 0.018167 ms |
| PostgreSQL | 读取到全局变量 | 5000 | 69.211086 ms | 69.465648 ms | 19.465648 ms | 19.242586 ms |
| MongoDB | 读取到局部变量 | 5000 | 49.950872 ms | 50.178014 ms | 0.178014 ms | 0.014090 ms |
| MongoDB | 读取到全局变量 | 5000 | 71.939851 ms | 72.119047 ms | 22.119047 ms | 21.958078 ms |
局部结果保存的主要工作移到后台,但完整读取仍约需一个 tick,因为 Skript 在主线程恢复运行。这里比较当前实现的两种作用域,没有比较不同版本或其他插件。
每行包含四个数字、一个物品和一个位置,共 1000 行。总耗时和最大 tick 间隔为十次样本的中位数,tick 超出量按间隔中位数减去 50 ms 后取非负值。最忙 tick 的处理耗时先取每次操作在同一个 tick 内已计时处理段的累计最大值,再取十次结果的中位数。它属于本次操作,不是整个服务端的 MSPT。把完整等待时间与最忙 tick 放在一起,可以观察分批转换如何减少单个 tick 的工作量,以及因此增加的等待。
| 后端 | 操作 | 请求行数 | 总耗时 | 最大 tick 间隔 | tick 超出量 | 最忙 tick 的处理耗时 |
|---|---|---|---|---|---|---|
| SQLite | 读取到局部变量 | 1000 | 499.969523 ms | 52.032646 ms | 2.032646 ms | 1.947452 ms |
| SQLite | 读取到全局变量 | 1000 | 521.950934 ms | 112.811151 ms | 62.811151 ms | 64.061272 ms |
| MySQL | 读取到局部变量 | 1000 | 699.948048 ms | 52.012222 ms | 2.012222 ms | 1.960147 ms |
| MySQL | 读取到全局变量 | 1000 | 775.946264 ms | 175.476904 ms | 125.476904 ms | 126.033681 ms |
| MariaDB | 读取到局部变量 | 1000 | 449.839210 ms | 52.125599 ms | 2.125599 ms | 1.939399 ms |
| MariaDB | 读取到全局变量 | 1000 | 448.441535 ms | 95.115534 ms | 45.115534 ms | 45.820466 ms |
| PostgreSQL | 读取到局部变量 | 1000 | 599.943042 ms | 52.004495 ms | 2.004495 ms | 1.948226 ms |
| PostgreSQL | 读取到全局变量 | 1000 | 634.938087 ms | 134.294649 ms | 84.294649 ms | 84.902243 ms |
| MongoDB | 读取到局部变量 | 1000 | 499.969330 ms | 52.020147 ms | 2.020147 ms | 1.956798 ms |
| MongoDB | 读取到全局变量 | 1000 | 515.116861 ms | 114.311325 ms | 64.311325 ms | 65.141387 ms |
局部结果的最忙 tick 约承担 2 ms 已计时工作,但整次读取会等待更多 tick。全局结果最终发布仍可能占用几十毫秒,共享转换预算没有覆盖这一步。
同一 MariaDB 作业中,每行六个数值列都有值。操作说明读写与变量作用域;请求行数是处理规模;总耗时和最大 tick 间隔为十次正式测量的中位数,tick 超出量按间隔中位数减去 50 ms 后取非负值。固定环境后同时改变行数与作用域,可以观察变量处理对等待和 tick 间隔的影响。全局写入包含跨 tick 分片读取,较长总耗时不表示这些 tick 都被阻塞。此表的数据内容与只填 id 的表不同,两者不能直接相减作为优化收益。
| 操作 | 请求行数 | 总耗时 | 最大 tick 间隔 | tick 超出量 |
|---|---|---|---|---|
| 局部写入 | 100 | 50.002091 ms | 50.365996 ms | 0.365996 ms |
| 局部读取 | 100 | 49.948891 ms | 50.296366 ms | 0.296366 ms |
| 全局写入 | 100 | 99.999130 ms | 50.318539 ms | 0.318539 ms |
| 全局读取 | 100 | 50.561922 ms | 50.852497 ms | 0.852497 ms |
| 局部写入 | 500 | 50.003658 ms | 50.298183 ms | 0.298183 ms |
| 局部读取 | 500 | 49.969411 ms | 50.278538 ms | 0.278538 ms |
| 全局写入 | 500 | 200.041385 ms | 50.843437 ms | 0.843437 ms |
| 全局读取 | 500 | 53.469929 ms | 53.751575 ms | 3.751575 ms |
| 局部写入 | 1000 | 50.029800 ms | 50.227414 ms | 0.227414 ms |
| 局部读取 | 1000 | 49.985885 ms | 50.250870 ms | 0.250870 ms |
| 全局写入 | 1000 | 400.035662 ms | 50.880191 ms | 0.880191 ms |
| 全局读取 | 1000 | 55.762635 ms | 55.953148 ms | 5.953148 ms |
| 局部写入 | 2500 | 50.027120 ms | 50.249912 ms | 0.249912 ms |
| 局部读取 | 2500 | 49.943913 ms | 50.207857 ms | 0.207857 ms |
| 全局写入 | 2500 | 1000.028616 ms | 50.811210 ms | 0.811210 ms |
| 全局读取 | 2500 | 58.051739 ms | 58.323916 ms | 8.323916 ms |
| 局部写入 | 5000 | 50.068590 ms | 50.309630 ms | 0.309630 ms |
| 局部读取 | 5000 | 50.005949 ms | 50.250288 ms | 0.250288 ms |
| 全局写入 | 5000 | 1845.764052 ms | 50.874572 ms | 0.874572 ms |
| 全局读取 | 5000 | 66.299079 ms | 66.890665 ms | 16.890665 ms |
同次 Run #7 在 AMD EPYC 7763 上使用 JMH 测量,两次独立进程、每次两轮预热和三轮正式测量。用例说明被测工作;每次操作耗时是 JMH 汇总的时间;测量范围说明包含哪些成本。它们用于观察不启动 Paper 时的代码和数据库调用成本,不能解释为游戏服务端上的完整语句耗时。
| 用例 | 每次操作耗时 | 测量范围 |
|---|---|---|
| 5000 行批量插入 | 9.871089 ms/op | 内存 H2、值准备、参数绑定与数据库执行 |
| 计算每条语句的行数上限 | 15.115187 ns/op | 行数上限计算,不访问数据库 |
完整语句和观察器间隔由脚本记录。插件的阶段计时只在基准服务端开启,语句返回后立即读取,避免后续查询覆盖记录。字段是 JSON 中的键名,含义说明计时范围。阶段范围有重叠,不能直接全部相加。
| 字段 | 含义 |
|---|---|
wallNs |
语句开始到脚本恢复,不含后续校验 |
gapNs |
语句开始到返回后一个 tick,观察器的最大执行间隔 |
mainNs |
本次操作中已计时的主线程处理区间累计耗时 |
mainTickMaxNs |
本次操作在同一个 tick 内已计时处理段的累计耗时最大值 |
prepareMainNs、prepareAsyncNs
|
分别在主线程和后台记录的输入准备耗时 |
conversionMainNs |
主线程值转换耗时 |
resultMainNs、resultAsyncNs
|
分别在主线程和后台记录的结果保存耗时 |
executionNs |
异步查询任务的耗时,可能包含输入准备、转换等待、数据库操作和游标读取,不是数据库纯执行时间 |
queueWaitNs |
等待主线程转换队列的时间 |
syncConversions |
已记录的主线程转换次数 |
largestConversionNs |
最慢一次主线程转换的耗时 |
运行页面的 tick-report-* 附件包含 pipeline-results.json、tick-results.json 和环境记录;pipeline-results.json 保存原始样本(含预热)及中位数/p95/最大值,tick-results.json 保存 CI 使用的记录点。下表的报告给出数据库,数据来源链接到本次提交的已发布记录,供核对数值;它不是性能排名。
| 报告 | 数据来源 |
|---|---|
| SQLite | 纳秒记录 |
| MySQL | 纳秒记录 |
| MariaDB | 纳秒记录 |
| PostgreSQL | 纳秒记录 |
| MongoDB | 纳秒记录 |
运行 ./gradlew serverBenchmark 可在本地测试 SQLite;其他后端通过 -Pskriptorm.benchmark.server.type=<类型> 和连接参数指定。结果写入 build/benchmarks/。./gradlew :benchmarks:jmh 运行 JVM 基准,结果写入 benchmarks/build/benchmarks/results.json。各作业记录提交、CPU、JDK、操作系统、数据库镜像、驱动、Paper 和 Skript 版本。
CI 因脚本失败、影响行数或保存行数不符、样本缺失等问题失败;耗时变化只作报告,不作为构建失败条件。基准历史保存在 gh-pages 的 dev/bench/<CPU>/,服务端结果另按后端分组。
- 普通主线程处理段记录实际经过时间,不是 CPU 时间;代码还没有为语句的所有零散开销设置计时。
- 不能将
executionNs与其他阶段直接相加,其中可能已经包含它们。 - 最大 tick 间隔能显示更新延迟,不能给出完整 MSPT(每个 tick 的耗时)分布。内存分配、峰值与长期保留量也需另行测量。
- 空闲服务端不能代表大量在线玩家与实体活动时的表现;短测试也不能排除长期泄漏。
- 通用 JDBC 可以使用本仓库未测过的驱动,现有成绩不代表这些驱动。
- 跨版本对照需要同脚本、同数据、同机器及相同数据库设置;不同 CPU 的 CI 成绩不能直接算成优化百分比。
skript-orm
参考
实用指南
skript-orm (English)
Reference
- Connections
- Tables
- Raw statements
- Writing rows
- Reading rows
- Updating and deleting
- Affected rows
- Errors and waiting
- Transactions
- Types
Practical guides
Wiki 由仓库中的 README 和 docs/ 自动生成。修改文档请到仓库提交,直接编辑 Wiki 的内容会在下次同步时被覆盖。
This wiki is generated from the README files and docs/ in the repository. Please submit changes there; direct wiki edits are overwritten on the next sync.