在 DolphinDB 中启用动态脚本优化
适用版本:DolphinDB 2.00.18 及以上版本
1. 功能概述
动态脚本优化是一种针对 DolphinScript 的脚本执行优化机制,可根据脚本的实际执行情况优化执行路径,适用于循环、条件分支和自定义函数调用等场景,从而提升脚本的执行效率。
DolphinDB 提供运行时函数和全局配置项两种方式控制动态脚本优化。可调用
enableDynamicScriptOptimization 和
disableDynamicScriptOptimization 函数,在当前会话中开启或关闭该功能;也可以通过全局配置项
enableDynamicScriptOptimization 统一控制该功能。
| 入口 | 作用 |
|---|---|
|
在当前会话开启动态脚本优化 |
|
|
在当前会话关闭动态脚本优化 |
|
|
设置新建会话的初始状态 |
1.1 适用场景
逐条因子计算、实时风控规则、状态累计指标、逐笔数据处理,以及循环内频繁调用自定义函数的脚本。
1.2 核心价值
-
零代码开启:对完成第六节兼容性适配的脚本,可在会话中调用开关函数启用优化。
-
会话级隔离:运行时开关仅影响当前会话,适合调试、性能对比和灰度验证。
-
循环脚本加速:在已验证场景中,自定义函数调用循环可获得约 2–3 倍加速,循环和条件计算可获得约 1.1–1.7 倍加速。
-
生产可配置:可通过配置项为新建会话设置统一的初始状态。
-
兼容性可预期:变量作用域变化的触发条件明确;循环外预先声明变量即可保持清晰、稳定的脚本结构。
2. 会话级生效方式
执行开关函数后,动态脚本优化状态将立即对当前会话生效,后续提交的解释执行脚本将根据当前状态执行。
// 开启当前会话的动态脚本优化
enableDynamicScriptOptimization()
// 关闭当前会话的动态脚本优化
disableDynamicScriptOptimization()
运行时开关不写入配置文件;会话结束后,新会话将按配置项的初始值创建。
3. 生产环境配置
若希望后续新建会话默认启用优化,可在对应服务节点的配置文件中设置同名配置项:
enableDynamicScriptOptimization=true
配置后重启对应服务节点,使后续新建会话按该初始状态运行。运行时函数适合单会话验证;配置项适合为新建会话提供统一的初始状态。
-
在测试环境或专用会话中,按第六节完成脚本适配并验证关键结果和耗时。
-
通过配置项设置新建会话的初始状态。
-
重启服务节点后,在新建会话中执行验证脚本确认状态。
-
在业务会话中逐步推广,并持续观察关键脚本的执行时间与结果。
4. 验证当前会话状态
运行时函数不会修改配置文件,因此 getConfig("enableDynamicScriptOptimization")
用于读取配置值,而不是当前会话的运行时状态。
可使用以下函数验证当前会话是否已开启优化。它有意利用第六节所述的变量作用域差异,并封装在函数内,以便在同一会话中重复调用:
def isScriptOptEnabled(){
try{
for(i in 1..1){
if(true){ probeVarX = 1 }
probeResX = probeVarX + 1
}
return false // 未报错:当前会话未开启优化
}catch(ex){
return true // 报错:当前会话已开启优化
}
}
isScriptOptEnabled()
使用示例:
isScriptOptEnabled() // false
enableDynamicScriptOptimization()
isScriptOptEnabled() // true
disableDynamicScriptOptimization()
isScriptOptEnabled() // false
5. 已验证的优化场景与性能表现
以下数据在 3 台机器集群的 computenode 上采集。每个场景均使用确定性输入,并校验开启和关闭优化后的计算结果一致。
5.1 测试集群与方法
| 项目 | 配置 |
|---|---|
|
Server 版本 |
3.00.6 |
|
CPU类型 |
Intel(R) Xeon(R) Silver 4314 CPU @ 2.40GHz |
|
集群拓扑 |
3 个 controller、3 个 datanode、3 个 computenode 和 3 个 agent |
|
数据/计算节点资源 |
每个节点内存上限 256 GB, |
完整的 11 个场景测试脚本请参考 script_for_test。附件会在关闭和开启优化时分别运行各场景并校验结果;如需复现下表的基准数据,可多次执行并取最小耗时。
5.2 性能结果
| 场景 | 关闭优化 | 开启优化 |
|---|---|---|
|
调用链封装在函数体内(100 万次) |
0.93s |
0.28s |
|
循环内并列调用 3 个函数(100 万次) |
1.27s |
0.42s |
|
单层自定义函数调用循环(100 万次) |
0.81s |
0.35s |
|
lambda 表达式循环调用(100 万次) |
0.55s |
0.27s |
|
do-while 循环(100 万次) |
0.26s |
0.15s |
|
while 循环(100 万次) |
0.26s |
0.15s |
|
VWAP 累计计算(50 万条) |
0.27s |
0.18s |
|
矩阵双重循环(1000×1000) |
0.42s |
0.32s |
|
逐条条件判断因子计算(50 万条) |
0.28s |
0.22s |
|
try-catch 循环(50 万次) |
0.12s |
0.10s |
|
for 循环密集计算(100 万次) |
0.34s |
0.30s |
这些场景覆盖函数调用、lambda、for / do-while /
while
控制流、累计计算、数组读写、矩阵循环、条件分支和异常处理。性能会随硬件、数据规模和业务逻辑变化;建议在实际部署环境中以业务脚本复测。
5.3 循环内调用自定义函数
def normalizePrice(x){ return x * 1.01 }
def calcSignal(x){ return x > 100.0 }
def calcScore(x){ return x * x }
n = 1000000
score = 0.0
for(i in 0..(n-1)){
price = normalizePrice(double(i))
signal = calcSignal(price)
if(signal){ score += calcScore(price) }
}
score
5.4 逐条因子计算
n = 500000
prices = 50.0 + double((1..n) % 100)
volumes = 1000 + (1..n) % 10000
result = 0.0
for(i in 0..(n-1)){
if(prices[i] > 100.0){
if(volumes[i] > 5000){ result += prices[i] * volumes[i] * 0.001 }
else { result += prices[i] * volumes[i] * 0.0005 }
} else { result -= prices[i] * volumes[i] * 0.0001 }
}
result
5.5 累计指标计算:VWAP 示例
n = 500000
prices = 50.0 + double((1..n) % 100)
volumes = 1000 + (1..n) % 10000
cumVol = 0.0
cumPV = 0.0
vwap = array(DOUBLE, n)
for(i in 0..(n-1)){
cumVol += volumes[i]
cumPV += prices[i] * volumes[i]
vwap[i] = cumPV / cumVol
}
vwap[n-1]
该类循环同时涉及数组索引、状态累计和数组写入,实测有明显耗时下降。
6. 兼容性问题与脚本适配
开启优化后,只有同时满足以下条件的变量会受到作用域规则影响:
-
变量位于
for、do-while或while循环体内; -
变量在
if/else分支中首次赋值; -
变量在该分支之外继续使用。
对于不满足上述条件的脚本,或已在循环外预先声明的变量,计算逻辑不会因该作用域规则改变而受到影响。仍建议对关键脚本在开启前后执行结果校验。
6.1 受影响写法与适配写法
以下写法中,a 在循环内分支中首次赋值、随后在分支外使用:
for(i in 1..10){
if(true){ a = 1 }
res = a + 1
}
将变量在循环外预先声明即可适配:
a = 0
for(i in 1..10){
if(true){ a = 1 }
res = a + 1
}
res
6.2 适配建议
-
在循环外声明循环后续步骤会使用的变量。
-
在
if/else分支内完成变量赋值和局部计算。 -
使用有意义的变量名区分中间计算结果与循环累计结果。
-
在开启优化前后,对关键脚本执行结果校验。
7. 完整使用示例
// 1. 开启当前会话的优化
enableDynamicScriptOptimization()
// 2. 使用第四节定义的 isScriptOptEnabled() 确认状态
isScriptOptEnabled() // 返回 true
// 3. 执行循环和函数调用密集的业务脚本
def calcFactor(price, volume){ return price * volume * 0.001 }
n = 1000000
prices = 50.0 + double((1..n) % 100)
volumes = 1000 + (1..n) % 10000
factor = array(DOUBLE, n)
for(i in 0..(n-1)){ factor[i] = calcFactor(prices[i], volumes[i]) }
print(sum(factor))
// 4. 需要时关闭当前会话的优化
disableDynamicScriptOptimization()
8. 常见问题
Q1:当前会话开启后,如何确认状态?调用第四节的
isScriptOptEnabled()。它利用已说明的变量作用域差异进行探测,返回 true
表示当前会话已开启优化。
Q2:为什么getConfig仍显示false?getConfig
返回的是配置文件中的值;运行时开关只修改当前会话状态。使用 isScriptOptEnabled() 验证当前会话。
Q3:运行时开关的作用范围是什么?只作用于调用函数的当前会话。新建会话按配置项的初始值创建。
Q4:为什么只影响当前会话,仍需要管理员权限?函数的作用范围是当前会话,但服务端将修改会话执行策略的操作限定为管理员。使用配置项为新建会话设置初始状态后,普通用户可以在其会话中执行优化后的脚本。
Q5:如何让新建会话默认开启优化?在服务配置中设置
enableDynamicScriptOptimization=true,重启对应服务节点后,新建会话将按配置项初始化。
Q6:如何处理变量作用域兼容性问题?在循环外预先声明需要在 if / else
分支外使用的变量。详见第六节。
Q7:while循环可以使用吗?可以。DolphinDB 3.00.6 支持
while 语句;本教程的性能测试已覆盖 100 万次 while 循环场景。
