请选择 进入手机版 | 继续访问电脑版

干货丨时序数据库DolphinDB即时编译(JIT)详解

[复制链接]
谢世民 发表于 2021-1-1 18:33:52 | 显示全部楼层 |阅读模式 打印 上一主题 下一主题
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次所需要的时间。
  1. 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。对比三种方法的耗时:
  1. 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 的标识即可:
  1. @jitdef myFunc(/* arguments */) {  /* implementation */}
复制代码
用户在调用此函数时,DolphinDB会将函数的代码实时编译为呆板码后执行。
3.2 支持的语句
现在DolphinDB支持在JIT中使用以下几种语句:


  • 赋值语句,比方:
  1. @jitdef func() {  y = 1}
复制代码
请注意,multiple assign现在是不支持的,比方:
  1. @jitdef func() {  a, b = 1, 2}func()
复制代码
运行以上语句会抛出异常。


  • return语句,比方:
  1. @jitdef func() {  return 1}
复制代码


  • if-else语句,比如:
  1. @jitdef myAbs(x) {  if(x > 0) return x  else return -x}
复制代码


  • do-while语句,比方:
  1. @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}
复制代码


  • for语句,比方:
  1. @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的使用:
  1. @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函数:
  1. @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
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

发布主题

专注素材教程免费分享
全国免费热线电话

18768367769

周一至周日9:00-23:00

反馈建议

27428564@qq.com 在线QQ咨询

扫描二维码关注我们

Powered by Discuz! X3.4© 2001-2013 Comsenz Inc.( 蜀ICP备2021001884号-1 )