在 DolphinDB 中启用动态脚本优化

适用版本:DolphinDB 2.00.18 及以上版本

1. 功能概述

动态脚本优化是一种针对 DolphinScript 的脚本执行优化机制,可根据脚本的实际执行情况优化执行路径,适用于循环、条件分支和自定义函数调用等场景,从而提升脚本的执行效率。

DolphinDB 提供运行时函数和全局配置项两种方式控制动态脚本优化。可调用 enableDynamicScriptOptimizationdisableDynamicScriptOptimization 函数,在当前会话中开启或关闭该功能;也可以通过全局配置项 enableDynamicScriptOptimization 统一控制该功能。

入口 作用

函数 enableDynamicScriptOptimization

在当前会话开启动态脚本优化

函数 disableDynamicScriptOptimization

在当前会话关闭动态脚本优化

配置项 enableDynamicScriptOptimization

设置新建会话的初始状态

1.1 适用场景

逐条因子计算、实时风控规则、状态累计指标、逐笔数据处理,以及循环内频繁调用自定义函数的脚本。

1.2 核心价值

  • 零代码开启:对完成第六节兼容性适配的脚本,可在会话中调用开关函数启用优化。

  • 会话级隔离:运行时开关仅影响当前会话,适合调试、性能对比和灰度验证。

  • 循环脚本加速:在已验证场景中,自定义函数调用循环可获得约 2–3 倍加速,循环和条件计算可获得约 1.1–1.7 倍加速。

  • 生产可配置:可通过配置项为新建会话设置统一的初始状态。

  • 兼容性可预期:变量作用域变化的触发条件明确;循环外预先声明变量即可保持清晰、稳定的脚本结构。

2. 会话级生效方式

执行开关函数后,动态脚本优化状态将立即对当前会话生效,后续提交的解释执行脚本将根据当前状态执行。

// 开启当前会话的动态脚本优化
enableDynamicScriptOptimization()

// 关闭当前会话的动态脚本优化
disableDynamicScriptOptimization()

运行时开关不写入配置文件;会话结束后,新会话将按配置项的初始值创建。

3. 生产环境配置

若希望后续新建会话默认启用优化,可在对应服务节点的配置文件中设置同名配置项:

enableDynamicScriptOptimization=true

配置后重启对应服务节点,使后续新建会话按该初始状态运行。运行时函数适合单会话验证;配置项适合为新建会话提供统一的初始状态。

  1. 在测试环境或专用会话中,按第六节完成脚本适配并验证关键结果和耗时。

  2. 通过配置项设置新建会话的初始状态。

  3. 重启服务节点后,在新建会话中执行验证脚本确认状态。

  4. 在业务会话中逐步推广,并持续观察关键脚本的执行时间与结果。

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,workerNum=64

完整的 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. 兼容性问题与脚本适配

开启优化后,只有同时满足以下条件的变量会受到作用域规则影响:

  1. 变量位于 fordo-whilewhile 循环体内;

  2. 变量在 if / else 分支中首次赋值;

  3. 变量在该分支之外继续使用。

对于不满足上述条件的脚本,或已在循环外预先声明的变量,计算逻辑不会因该作用域规则改变而受到影响。仍建议对关键脚本在开启前后执行结果校验。

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 适配建议

  1. 在循环外声明循环后续步骤会使用的变量。

  2. if / else 分支内完成变量赋值和局部计算。

  3. 使用有意义的变量名区分中间计算结果与循环累计结果。

  4. 在开启优化前后,对关键脚本执行结果校验。

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仍显示falsegetConfig 返回的是配置文件中的值;运行时开关只修改当前会话状态。使用 isScriptOptEnabled() 验证当前会话。

Q3:运行时开关的作用范围是什么?只作用于调用函数的当前会话。新建会话按配置项的初始值创建。

Q4:为什么只影响当前会话,仍需要管理员权限?函数的作用范围是当前会话,但服务端将修改会话执行策略的操作限定为管理员。使用配置项为新建会话设置初始状态后,普通用户可以在其会话中执行优化后的脚本。

Q5:如何让新建会话默认开启优化?在服务配置中设置 enableDynamicScriptOptimization=true,重启对应服务节点后,新建会话将按配置项初始化。

Q6:如何处理变量作用域兼容性问题?在循环外预先声明需要在 if / else 分支外使用的变量。详见第六节。

Q7:while循环可以使用吗?可以。DolphinDB 3.00.6 支持 while 语句;本教程的性能测试已覆盖 100 万次 while 循环场景。