DolphinDB是高性能分布式时序数据库,内置了丰富的盘算功能和强大多范式编程语言。为了能够提高DolphinDB脚本的执行效率,从1.01版本开始,DolphinDB支持即时编译(JIT)。
1 JIT简介
即时编译(英文: Just-in-time compilation, 缩写: JIT),又译实时编译或实时编译,是动态编译的一种形式,可提高步伐运行效率。
通常步伐有两种运行方式:编译执行息争释执行。编译执行在步伐执行前全部翻译为呆板码,特点是运行效率较高,以C/C++为代表。解释执行是由解释器对步伐逐句解释并执行,机动性较强,但是执行效率较低,以Python为代表。
即时编译融合了两者的优点,在运行时将代码翻译为呆板码,可以到达与静态编译语言相近的执行效率。Python的第三方实现PyPy通过JIT显着改善了解释器的性能。绝大多数的Java实现都依赖JIT以提高代码的运行效率。
2 JIT在DolphinDB中的作用
DolphinDB的编程语言是解释执行,运行步伐时首先对步伐举行语法分析生成语法树,然后递归执行。在不能使用向量化的情况下,解释成本会比较高。这是由于DolphinDB底层由C++实现,脚本中的一次函数调用会转化为多次C++内的虚拟函数调用。for循环,while循环和if-else等语句中,由于要反复调用函数,十分耗时,在某些场景下不能满意实时性的需求。
DolphinDB中的即时编译功能显著提高了for循环,while循环和if-else等语句的运行速度,特别适合于无法使用向量化运算但又对运行速度有极高要求的场景,比方高频因子盘算、实时流数据处置惩罚等。
在下面的例子中,我们对比使用和不使用JIT的情况下,do-while循环盘算1到1000000之和100次所需要的时间。
- def sum_without_jit(v) { s = 0l i = 1 n = size(v) do { s += v[i] i += 1 } while(i = t10) { // [t10, t1] if(cur == -1) cur = 0 } else if(s > t20) { // [t20, t10) cur = 0 } else if(s >= t2) { // [t2, t20] if(cur == 1) cur = 0 } else { // (-inf, t2) cur = -1 } output[idx] = cur idx += 1 } return output}
复制代码 把@jit去掉,得到不使用JIT的自界说函数calculate_without_jit。对比三种方法的耗时:
- n = 10000000t1= 60t10 = 50t20 = 30t2 = 20signal = rand(100.0, n)timer(100) (iif(signal >t1, 1h, iif(signal < t10, 0h, 00h)) - iif(signal t20, 0h, 00h))).ffill().nullFill(0h) // 41092.019 mstimer(100) calculate_with_jit(calculate, signal, size(signal), t1, t10, t20, t2) // 17075.127 mstimer(100) calculate_without_jit(signal, size(signal), t1, t10, t20, t2) // 1404406.413 ms
复制代码 本例中,使用JIT的速度向量化运算的2.4倍,是不消JIT的82倍。这里JIT的速度比向量化运算还要快,是因为向量化运算中调用了许多次DolphinDB的内置函数,产生了许多中间效果,
涉及到多次内存分配以及虚拟函数调用,而JIT生成的代码则没有这些额外的开销。
另外一种情况是,某些盘算无法使用向量化,比如盘算期权隐含颠簸率(implied volatility)时,需要使用牛顿法,无法使用向量化运算。这种情况下如果需要满意一定的实时性,可以选择使用DolphinDB的插件,亦可使用JIT。两者的区别在于,在任何场景下都可以使用插件,但是需要使用C++语言编写,比较复杂;JIT的编写相对而言较为容易,但是适用的场景较为有限。JIT的运行速度与使用C++插件的速度非常靠近。
3 如安在DolphinDB中使用JIT
3.1 使用方法
DolphinDB现在仅支持对用户自界说函数举行JIT。只需在用户自界说函数之前的一行添加 @jit 的标识即可:
- @jitdef myFunc(/* arguments */) { /* implementation */}
复制代码 用户在调用此函数时,DolphinDB会将函数的代码实时编译为呆板码后执行。
3.2 支持的语句
现在DolphinDB支持在JIT中使用以下几种语句:
请注意,multiple assign现在是不支持的,比方:
- @jitdef func() { a, b = 1, 2}func()
复制代码 运行以上语句会抛出异常。
- @jitdef func() { return 1}
复制代码
- @jitdef myAbs(x) { if(x > 0) return x else return -x}
复制代码
- @jitdef mySqrt(x) { diff = 0.0000001 guess = 1.0 guess = (x / guess + guess) / 2.0 do { guess = (x / guess + guess) / 2.0 } while(abs(guess * guess - x) >= diff) return guess}
复制代码
- @jitdef mySum(vec) { s = 0 for(i in vec) { s += i } return s}
复制代码 DolphinDB支持在JIT中以上语句的任意嵌套。
3.3 支持的运算符和函数
现在DolphinDB支持在JIT中使用以下的运算符:add(+), sub(-), multiply(*), divide(/), and(&&), or(||), bitand(&), bitor(|), bitxor(^), eq(==), neq(!=), ge(>=), gt(>), le( 0.00001); return (high + low) /2;}@jitdef test_jit(future_price, strike, ttm, risk_rate, b_rate, option_price, is_call) { n = size(future_price) ret = array(DOUBLE, n, n) i = 0 do { ret = ImpliedVolatility(future_price, strike, ttm, risk_rate, b_rate, option_price, is_call) i += 1 } while(i < n) return ret}n = 100000future_price=take(rand(10.0,1)[0], n)strike_price=take(rand(10.0,1)[0], n)strike=take(rand(10.0,1)[0], n)input_ttm=take(rand(10.0,1)[0], n)risk_rate=take(rand(10.0,1)[0], n)b_rate=take(rand(10.0,1)[0], n)vol=take(rand(10.0,1)[0], n)input_vol=take(rand(10.0,1)[0], n)multi=take(rand(10.0,1)[0], n)is_call=take(rand(10.0,1)[0], n)ttm=take(rand(10.0,1)[0], n)option_price=take(rand(10.0,1)[0], n)timer(10) test_jit(future_price, strike, ttm, risk_rate, b_rate, option_price, is_call) // 2621.73 mstimer(10) test_non_jit(future_price, strike, ttm, risk_rate, b_rate, option_price, is_call) // 302714.74 ms[/code] 上面的例子中,ImpliedVolatility会调用GBlackScholes函数。函数test_non_jit可通过把test_jit界说之前的@jit去掉以获取。JIT版本test_jit运行速度是非JIT版本test_non_jit的115倍。
5.2 盘算 Greeks
量化金融中经常使用Greeks举行风险评估,下面以Charm为例展示JIT的使用:
- @jitdef myMax(a,b){ if(a>b){ return a }else{ return b }}@jitdef NormDist(x) { return cdfNormal(0, 1, x);}@jitdef ND(x) { return (1.0/sqrt(2*pi)) * exp(-(x*x)/2.0)}@jitdef CalculateCharm(future_price, strike_price, input_ttm, risk_rate, b_rate, vol, multi, is_call) { day_year = 245.0; d1 = (log(future_price/strike_price) + (b_rate + (vol*vol)/2.0) * input_ttm) / (myMax(vol,0.00001) * sqrt(input_ttm)); d2 = d1 - vol * sqrt(input_ttm); if (is_call) { return -exp((b_rate - risk_rate) * input_ttm) * (ND(d1) * (b_rate/vol/sqrt(input_ttm) - d2/2.0/input_ttm) + (b_rate-risk_rate) * NormDist(d1)) * future_price * multi / day_year; } else { return -exp((b_rate - risk_rate) * input_ttm) * (ND(d1) * (b_rate/vol/sqrt(input_ttm) - d2/2.0/input_ttm) - (b_rate-risk_rate) * NormDist(-d1)) * future_price * multi / day_year; }}@jitdef test_jit(future_price, strike_price, input_ttm, risk_rate, b_rate, vol, multi, is_call) { n = size(future_price) ret = array(DOUBLE, n, n) i = 0 do { ret[i] = CalculateCharm(future_price[i], strike_price[i], input_ttm[i], risk_rate[i], b_rate[i], vol[i], multi[i], is_call[i]) i += 1 } while(i < n) return ret}def ND_validate(x) { return (1.0/sqrt(2*pi)) * exp(-(x*x)/2.0)}def NormDist_validate(x) { return cdfNormal(0, 1, x);}def CalculateCharm_vectorized(future_price, strike_price, input_ttm, risk_rate, b_rate, vol, multi, is_call) { day_year = 245.0; d1 = (log(future_price/strike_price) + (b_rate + pow(vol, 2)/2.0) * input_ttm) / (max(vol, 0.00001) * sqrt(input_ttm)); d2 = d1 - vol * sqrt(input_ttm); return iif(is_call,-exp((b_rate - risk_rate) * input_ttm) * (ND_validate(d1) * (b_rate/vol/sqrt(input_ttm) - d2/2.0/input_ttm) + (b_rate-risk_rate) * NormDist_validate(d1)) * future_price * multi / day_year,-exp((b_rate - risk_rate) * input_ttm) * (ND_validate(d1) * (b_rate/vol/sqrt(input_ttm) - d2/2.0/input_ttm) - (b_rate-risk_rate) * NormDist_validate(-d1)) * future_price * multi / day_year)}n = 1000000future_price=rand(10.0,n)strike_price=rand(10.0,n)strike=rand(10.0,n)input_ttm=rand(10.0,n)risk_rate=rand(10.0,n)b_rate=rand(10.0,n)vol=rand(10.0,n)input_vol=rand(10.0,n)multi=rand(10.0,n)is_call=rand(true false,n)ttm=rand(10.0,n)option_price=rand(10.0,n)timer(10) test_jit(future_price, strike_price, input_ttm, risk_rate, b_rate, vol, multi, is_call) // 1834.342 mstimer(10) test_none_jit(future_price, strike_price, input_ttm, risk_rate, b_rate, vol, multi, is_call) // 224099.805 mstimer(10) CalculateCharm_vectorized(future_price, strike_price, input_ttm, risk_rate, b_rate, vol, multi, is_call) // 3117.761 ms
复制代码 上面是一个更加复杂的例子,涉及到更多的函数调用和更复杂的盘算,JIT版本比非JIT版本快121倍左右,比向量化版本快0.7倍左右。
5.3 盘算止损点 (stoploss)
在这篇知乎专栏中,我们展示了如何使用DolphinDB举行技能信号回测,下面我们用JIT来实现此中的stoploss函数:
- @jitdef stoploss_JIT(ret, threshold) { n = ret.size() i = 0 curRet = 1.0 curMaxRet = 1.0 indicator = take(true, n) do { indicator[i] = false curRet *= (1 + ret[i]) if(curRet > curMaxRet) { curMaxRet = curRet } drawDown = 1 - curRet / curMaxRet; if(drawDown >= threshold) { i = n // break is not supported for now } i += 1 } while(i < n) return indicator}def stoploss_no_JIT(ret, threshold) { n = ret.size() i = 0 curRet = 1.0 curMaxRet = 1.0 indicator = take(true, n) do { indicator[i] = false curRet *= (1 + ret[i]) if(curRet > curMaxRet) { curMaxRet = curRet } drawDown = 1 - curRet / curMaxRet; if(drawDown >= threshold) { i = n // break is not supported for now } i += 1 } while(i < n) return indicator}def stoploss_vectorization(ret, threshold){ cumret = cumprod(1+ret) drawDown = 1 - cumret / cumret.cummax() firstCutIndex = at(drawDown >= threshold).first() + 1 indicator = take(false, ret.size()) if(isValid(firstCutIndex) and firstCutIndex < ret.size()) indicator[firstCutIndex:] = true return indicator}ret = take(0.0008 -0.0008, 1000000)threshold = 0.10timer(10) stoploss_JIT(ret, threshold) // 58.674 mstimer(10) stoploss_no_JIT(ret, threshold) // 14622.142 mstimer(10) stoploss_vectorization(ret, threshold) // 151.884 ms
复制代码 stoploss这个函数实际上只需要找到drawdown大于threshold的第一天,不需要把cumprod和cummax全部盘算出来,因此用JIT实现的版本比向量化版本快了1.5倍左右,比非JIT版本快248倍左右。
如果数据中最后一天才要stoploss,那么JIT版本的速度会和向量化一样,但是远远比非JIT版本快。
6. 未来
在后续的版本中,我们操持逐步支持以下功能:
- 在for, do-while语句中支持break和continue。
- 支持dictionary等数据布局,支持string等数据类型。
- 支持更多的数学和统计类函数。
- 增强类型推导功能,能够识别更多DolphinDB内置函数返回值的数据类型。
- 支持在自界说函数中为输入参数,返回值和局部变量声明数据类型。
7 总结
DolphinDB推出了即时编译执行自界说函数的功能,显著提高了for循环,while循环和if-else等语句的运行速度,特别适合于无法使用向量化运算但又对运行速度有极高要求的场景,比方高频因子盘算、实时流数据处置惩罚等。
来源:https://blog.csdn.net/qq_41996852/article/details/112002998
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |