文章目录
Caché/IRIS
代码优化效率提升十一条 - 持续更新
Caché/IRIS
代码优化效率提升十一条 - 持续更新
- 本篇文章为笔者开发过程中总结一些提升效率方案。
- 本篇将逐条展示优化代码的示例。
- 本篇不涉及
SQL
优化,例如创建索引等。 - 本篇文章将会持续更新。
注:提供新的优化代码方案,请与我联系或底部留言。
准备数据
- 创建测试表结构如下:
Class M.T.Person Extends (%Persistent, %Populate) [ ClassType = persistent, SqlRowIdName = id, SqlTableName = Person ]
{
Property MTName As %String(CAPTION = "名称", POPSPEC = "Name()") [ Aliases = {name}, SqlFieldName = MT_Name ];
Property MTAge As %Integer(CAPTION = "年龄", POPSPEC = "Integer(18,30)") [ Aliases = {age}, SqlFieldName = MT_Age ];
Property MTNo As %String(CAPTION = "身份证号", POPSPEC = "Integer(100000,999999)") [ Aliases = {no}, SqlFieldName = MT_No ];
Property MTMoney As %Integer(CAPTION = "薪水", POPSPEC = "Integer(3000,30000)") [ Aliases = {money}, SqlFieldName = MT_Money ];
}
- 生成
100w
条数据用于测试
USER> w ##class(M.T.Person).Populate(1000000)
1000000
汇总数据使用多维数组
通常我们汇总数据时用临时Global,或者使用进程私有Global,实际上两者效率差不多,如果不跨进程使用时,可以将其替换为多维数组。代码运行效率提升一倍左右。
- 反例
for i = 1 : 1 : 1000000 {
s money = $li(^M.T.PersonD(i), 5)
s ^yx(i) = money
}
s ID = ""
for {
s ID = $o(^yx(ID))
q:(ID = "")
s amt = amt + ^yx(ID)
}
- 正例
s amt = 0
for i = 1 : 1 : 1000000 {
s money = $li(^M.T.PersonD(i), 5)
s yx(i) = money
}
s ID = ""
for {
s ID = $o(yx(ID))
q:(ID = "")
s amt = amt + yx(ID)
}
- 效率测试
ClassMethod OptimizeSummaryData()
{
/* 全局变量 */
s t1 = $zh
k ^yx
s amt = 0
for i = 1 : 1 : 1000000 {
s money = $li(^M.T.PersonD(i), 5)
s ^yx(i) = money
}
s ID = ""
for {
s ID = $o(^yx(ID))
q:(ID = "")
s amt = amt + ^yx(ID)
}
s t2 = $zh
w "全局变量:" _ (t2 - t1),!
w amt,!
/* 进程全局变量 */
s t1 = $zh
k ^||yx
s amt = 0
for i = 1 : 1 : 1000000 {
s money = $li(^M.T.PersonD(i), 5)
s ^||yx(i) = money
}
s ID = ""
for {
s ID = $o(^||yx(ID))
q:(ID = "")
s amt = amt + ^||yx(ID)
}
s t2 = $zh
w "进程全局变量:" _ (t2 - t1),!
w amt,!
/* 多维数组 */
s t1 = $zh
k yx
s amt = 0
for i = 1 : 1 : 1000000 {
s money = $li(^M.T.PersonD(i), 5)
s yx(i) = money
}
s ID = ""
for {
s ID = $o(yx(ID))
q:(ID = "")
s amt = amt + yx(ID)
}
s t2 = $zh
w "多维数组:" _ (t2 - t1),!
w amt,!
}
USER> d ##class(M.Optimize).OptimizeSummaryData()
全局变量:.869142
16506476230
进程全局变量:.783454
16506476230
多维数组:.40313
16506476230
- 测试
100w
数据汇总,可观察多维数组效率最高。 - 本示例仅取单条数据进行汇总,在真实环境中效率会更高。
Global
取数据时需将Global
先赋值变量
因为Global
存在磁盘上,所以每次直接通过Global
取值会多次IO
磁盘,如果将Global
赋值变量,因为变量在内存当中,再通过变量取值。运行效率会更快。
- 反例
for i = 1 : 1 : 1000000 {
s name = $li(^M.T.PersonD(i), 2)
s age = $li(^M.T.PersonD(i), 3)
s no = $li(^M.T.PersonD(i), 4)
s money = $li(^M.T.PersonD(i), 5)
}
- 正例
for i = 1 : 1 : 1000000 {
s data = ^M.T.PersonD(i)
s name = $li(data, 2)
s age = $li(data, 3)
s no = $li(data, 4)
s money = $li(data, 5)
}
- 效率测试
ClassMethod OptimizeGlobalData()
{
#; 直接根据Global取值
s t1 = $zh
for i = 1 : 1 : 1000000 {
s name = $li(^M.T.PersonD(i), 2)
s age = $li(^M.T.PersonD(i), 3)
s no = $li(^M.T.PersonD(i), 4)
s money = $li(^M.T.PersonD(i), 5)
}
s t2 = $zh
w "直接根据Global取值:" _ (t2 - t1),!
#; 将Global赋值变量取值
s t1 = $zh
for i = 1 : 1 : 1000000 {
s data = ^M.T.PersonD(i)
s name = $li(data, 2)
s age = $li(data, 3)
s no = $li(data, 4)
s money = $li(data, 5)
}
s t2 = $zh
w "将Global赋值变量取值:" _ (t2 - t1),!
}
USER>d ##class(M.Optimize).OptimizeGlobalData()
直接根据Global取值:.654173
将Global赋值变量取值:.25341
- 通过示例可观察到,将
Global
赋值变量取值比直接根据Global
取值效率提升更大。
将表达式直接返回
如果方法比较简单,可直接将表达式返回。不必将结果赋值变量,在将变量返回。也可将方法声明为表达式方法,添加关键字[ CodeMode = expression ]
。
- 反例
ClassMethod Return()
{
s date = $h
q date
}
- 正例
ClassMethod Return1()
{
q $h
}
- 效率测试
ClassMethod OptimizeReturn()
{
#; 间接返回值
s t1 = $zh
for i = 1 : 1 : 1000000 {
d ..Return()
}
s t2 = $zh
w "间接返回值:" _ (t2 - t1),!
#; 直接返回值
s t1 = $zh
for i = 1 : 1 : 1000000 {
d ..Return1()
}
s t2 = $zh
w "直接返回值:" _ (t2 - t1),!
}
USER>d ##class(M.Optimize).OptimizeReturn()
间接返回值:.22599
直接返回值:.153412
使用块语法的运行效率要比点语法更快
点语法为旧语法结构,非特殊情况下,请使用块语法。
- 反例
for i = 1 : 1 : 1000000 d
.s index = i
- 正例
for i = 1 : 1 : 1000000 {
s index = i
}
- 效率测试
ClassMethod OptimizeGrammar()
{
#; 点语法
s t1 = $zh
for i = 1 : 1 : 1000000 d
.s index = i
s t2 = $zh
w "点语法:" _ (t2 - t1),!
#; 块语法
s t1 = $zh
for i = 1 : 1 : 1000000 {
s index = i
}
s t2 = $zh
w "块语法:" _ (t2 - t1),!
}
USER>d ##class(M.Optimize).OptimizeGrammar()
点语法:.024101
块语法:.008296
复杂的if
逻辑条件,可以调整顺序,让程序更高效
判断条件应该按照条件出现频率依次排在前面,出现频率最少的应放在最后。
- 反例
for i = 1 : 1 : 1000000 {
if (color = "blue")||(color = "green")||(color = "red") {
}
continue:(color = "blue")||(color = "green")||(color = "red")
}
- 正例
for i = 1 : 1 : 1000000 {
if (color = "red")||(color = "green")||(color = "blue") {
}
continue:(color = "red")||(color = "green")||(color = "blue")
}
- 效率测试
ClassMethod OptimizeLogicalCondition(color)
{
#; 常用条件排最前
s t1 = $zh
for i = 1 : 1 : 1000000 {
if (color = "red")||(color = "green")||(color = "blue") {
}
continue:(color = "red")||(color = "green")||(color = "blue")
}
s t2 = $zh
w "常用条件排最前:" _ (t2 - t1),!
#; 常用条件排最后
s t1 = $zh
for i = 1 : 1 : 1000000 {
if (color = "blue")||(color = "green")||(color = "red") {
}
continue:(color = "blue")||(color = "green")||(color = "red")
}
s t2 = $zh
w "常用条件排最后:" _ (t2 - t1),!
}
USER>d ##class(M.Optimize).OptimizeLogicalCondition("red")
常用条件排最前:.032289
常用条件排最后:.088599
在循环中取不变的配置时,应使用全局变量进行缓存
通常取固定配置时,应该放在循环外面来取。如果必须在循环当中取配置时,可使用全局百分比变量进行缓存,后续判断全局变量如果存在则直接返回。
- 反例
ClassMethod Config1()
{
h 0.001
s config = $zv
q config
}
- 正例
ClassMethod Config()
{
q:($d(%zConfig)) %zConfig
h 0.001
s config = $zv
s %zConfig = config
q config
}
- 效率测试
ClassMethod OptimizeConfig()
{
#; 取一次配置
s t1 = $zh
for i = 1 : 1 : 1000 {
s str = ..Config()
}
s t2 = $zh
w "取一次配置:" _ (t2 - t1),!
#; 每次都取配置
s t1 = $zh
for i = 1 : 1 : 1000 {
s str = ..Config1()
}
s t2 = $zh
w "每次都取配置:" _ (t2 - t1),!
}
USER>d ##class(M.Optimize).OptimizeConfig()
取一次配置:.00034
每次都取配置:1.844951
- 示例中取配置耗时
0.01
,当循环次数越多,耗时越久。用全局变量方式可大大提升效率。
逻辑运算使用双逻辑运算符
单逻辑运算符无论是否满足条件,都会把判断条件测试一遍,双逻辑运算符,满足即退出。所以代码运行效率更快。
- 反例
for i = 1 : 1 : 1000000 {
continue:(color = "red")!(color = "green")!(color = "blue")
}
- 正例
for i = 1 : 1 : 1000000 {
continue:(color = "red")||(color = "green")||(color = "blue")
}
- 效率测试
ClassMethod OptimizeLogicalOperator(color)
{
#; 常用条件排最前
s t1 = $zh
for i = 1 : 1 : 1000000 {
continue:(color = "red")!(color = "green")!(color = "blue")
}
s t2 = $zh
w "常用条件排最前:" _ (t2 - t1),!
#; 常用条件排最后
s t1 = $zh
for i = 1 : 1 : 1000000 {
continue:(color = "red")||(color = "green")||(color = "blue")
}
s t2 = $zh
w "常用条件排最后:" _ (t2 - t1),!
}
USER> d ##class(M.Optimize).OptimizeLogicalOperator("red")
单逻辑运算符:.029142
双逻辑运算符:.017941
USER> d ##class(M.Optimize).OptimizeLogicalOperator("blue")
单逻辑运算符:.030231
双逻辑运算符:.030113
在数组中查找数据,使用内部数组%List
在数据中查找某个值推荐使用%LIST
内部数组格式。
为什么不使用 [
包含运算符,是因为包含运算符无法确切查找某个值,例如 str = "1,111,1111,1111"
,查找ID为11
,str
用逗号分割的字符串里并不包含11
,但是也返回1
,所以并不准确。
- 反例
s array = ##class(%ListOfDataTypes).%New()
d array.Insert("red")
d array.Insert("blue")
d array.Insert("green")
for i = 1 : 1 : 1000000 {
if (array.Find(str)) {
}
}
- 正例
s array = $lb("red","blue","green")
for i = 1 : 1 : 1000000 {
if ($lf(array, str)) {
}
}
- 效率测试
ClassMethod OptimizeArray(str)
{
#; %ListOfDataTypes数组
s t1 = $zh
s array = ##class(%ListOfDataTypes).%New()
d array.Insert("red")
d array.Insert("blue")
d array.Insert("green")
for i = 1 : 1 : 1000000 {
if (array.Find(str)) {
}
}
s t2 = $zh
w "%ListOfDataTypes数组:" _ (t2 - t1),!
#; %List数组
s t1 = $zh
s array = $lb("red","blue","green")
for i = 1 : 1 : 1000000 {
if ($lf(array, str)) {
}
}
s t2 = $zh
w "%List数组:" _ (t2 - t1),!
}
USER>d ##class(M.Optimize).OptimizeArray("green")
%ListOfDataTypes数组:.654175
%List数组:.049307
定义常量时,使用宏速度更快
定义全局常量时,可以使用参数也可以使用宏,由于宏为编译时直接替换代码,所以效率更高。
- 反例
Parameter COLORS = "RED";
- 正例
#define COLORS "RED"
- 效率测试
Parameter COLORS = "RED";
ClassMethod OptimizeParameter()
{
#define COLORS "RED"
#; 取参数常量
s t1 = $zh
for i = 1 : 1 : 1000000 {
s str = ..#COLORS
}
s t2 = $zh
w "取参数常量:" _ (t2 - t1),!
#; 取宏常量
s t1 = $zh
for i = 1 : 1 : 1000000 {
s str = $$$COLORS
}
s t2 = $zh
w "取宏常量:" _ (t2 - t1),!
}
USER> d ##class(M.Optimize).OptimizeParameter()
取参数常量:.024702
取宏常量:.013341
初始化变量时,分开声明效率更快
- 反例
for i = 1 : 1 : 1000000 {
s (index, count, sum, amt) = str
}
- 正例
for i = 1 : 1 : 1000000 {
s index = str
s count = str
s sum = str
s amt = str
}
- 效率测试
ClassMethod OptimizeInitVar(str)
{
#; 分别初始化变量
s t1 = $zh
for i = 1 : 1 : 1000000 {
s index = str
s count = str
s sum = str
s amt = str
}
s t2 = $zh
w "分别初始化变量:" _ (t2 - t1),!
#; 统一初始化变量
s t1 = $zh
for i = 1 : 1 : 1000000 {
s (index, count, sum, amt) = str
}
s t2 = $zh
w "统一初始化变量:" _ (t2 - t1),!
#; 一行初始化变量
s t1 = $zh
for i = 1 : 1 : 1000000 {
s index = str, count = str, sum = str, amt = str
}
s t2 = $zh
w "一行初始化变量:" _ (t2 - t1),!
}
USER>d ##class(M.Optimize).OptimizeInitVar("")
分别初始化变量:.015438
统一初始化变量:.043211
一行初始化变量:.015472
存储数据Global名称越短,效率越高
- 反例
s id = ""
for {
s id = $o(^Mumps.Table.Global.Name.Person(id))
q:(id = "")
s data = ^Mumps.Table.Global.Name.Person(id)
}
- 正例
s id = ""
for {
s id = $o(^Q.1(id))
q:(id = "")
s data = ^Q.1(id)
}
- 效率测试
ClassMethod OptimizeGlobalName()
{
k ^Mumps.Table.Global.Name.Person
k ^Q.1
for i = 1 : 1 : 1000000 {
s ^Mumps.Table.Global.Name.Person(i) = i
s ^Q.1(i) = i
}
/* 长Gloabl名称 */
s t1 = $zh
s id = ""
for {
s id = $o(^Mumps.Table.Global.Name.Person(id))
q:(id = "")
s data = ^Mumps.Table.Global.Name.Person(id)
}
s t2 = $zh
w "长Gloabl名称:" _ (t2 - t1),!
/* 短Gloabl名称 */
s t1 = $zh
s id = ""
for {
s id = $o(^Q.1(id))
q:(id = "")
s data = ^Q.1(id)
}
s t2 = $zh
w "短Gloabl名称:" _ (t2 - t1),!
}
USER>d ##class(M.Optimize).OptimizeGlobalName()
长Gloabl名称:.25337
短Gloabl名称:.233512
USER>d ##class(M.Optimize).OptimizeGlobalName()
长Gloabl名称:.250933
短Gloabl名称:.231979
注:在能保证可读性的情况下,尽量把表定义Global
名称简明扼要。
实践
经过上述学习,来优化以下以下代码吧。此代码300w
条数据,完整输出需要运行大概4
个小时。
试一试仅仅做代码优化效率能提升多少。
ClassMethod XXXExecute(ByRef qHandle As %Binary, startDate As %String, endDate As %String) As %Status
{
Set repid=$I(^CacheTemp)
Set qHandle=$lb(0,repid,0)
Set ind=1
q:startDate="" $$$OK
q:endDate="" $$$OK
k ^||TMPDHCWL($j)
d ..GetAdmByDate(startDate,endDate)
s admRowid="" f s admRowid=$o(^||TMPDHCWL($j,"STADM",admRowid)) q:admRowid="" d
.s typeCode=""
.s patientid=""
.s visitsn=""
.s visittype=""
.s outpatientno=""
.s visittimes=""
.s visitcardno=""
.s typeCode=$p(^||TMPDHCWL($j,"STADM",admRowid),"^",3)
.s patientid=..GetPapmiNo(admRowid)
.s visitsn=admRowid
.s outpatientno=$p(^PAADM(admRowid),"^",81)
.s visittimes=$p(..CountCurrentPat(admRowid),"^",1)
.s wlId="" f s wlId=$o(^DHCWorkLoad(0,"PAADM",admRowid,wlId)) q:wlId="" d
..s expensesn=""
..s arpbl=""
..s BCIRowid=""
..s invId=""
..s receiptno=""
..s ordersn=""
..s orddate=""
..s ordTime=""
..s expensedatetime=""
..s wlType=""
..s transactiontypecode=""
..s transactiontypename=""
..s mEc=""
..s itemtypecode=""
..s itemtypename=""
..s itemcode=""
..s itemname=""
..s accUomId=""
..s itemunit=""
..s itemunitprice=""
..s itemamount=""
..s itemtotalprice=""
..s expensesn=wlId
..s arpbl=$p(^DHCWorkLoad(wlId),"^",20)
..s BCIRowid=$o(^DHCBCI(0,"Bill",arpbl,""))
..s invId=$p(^DHCBCI(BCIRowid),"^",1)
..s receiptno=invId
..s ordersn=$p(^DHCWorkLoad(wlId),"^",21)
..s orddate=$p(^DHCWorkLoad(wlId),"^",5)
..s ordTime=$p(^DHCWorkLoad(wlId),"^",6)
..s expensedatetime=$zd(orddate,3)_" "_$zt(ordTime,1)
..s wlType=$p(^DHCWorkLoad(wlId),"^",47)
..s transactiontypecode=wlType
..s mEc=$p(^DHCWorkLoad(wlId),"^",41)
..s itemtypecode=$p(^DHCTarC("EC",mEc),"^",3)
..s itemtypename=$p(^DHCTarC("EC",mEc),"^",2)
..b ;ywc00000
..s item=$p(^DHCWorkLoad(wlId),"^",22)
..s itemcode=$p($g(^DHCTARI(item)),"^",1)
..s itemname=$p($g(^DHCTARI(item)),"^",2)
..s accUomId=+$p($g(^DHCTARI(item)),"^",3)
..s itemunit=$p($g(^CT("UOM",accUomId)),"^",2)
..s itemunitprice=$p(^DHCWorkLoad(wlId),"^",14)
..s itemamount=$p(^DHCWorkLoad(wlId),"^",15)
..s itemtotalprice=$p(^DHCWorkLoad(wlId),"^",16)
..s extenddata1=""
..s extenddata2=""
..i $p(^PAADM(admRowid),"^",20)="C" d
...s recordstatus="0"
..e d
...s recordstatus="1"
..d OutputRow
k ^||TMPDHCWL($j)
Set qHandle=$lb(0,repid,0)
Quit $$$OK
OutputRow
set Data=$lb(patientid,visitsn,visittype,visitcardno,outpatientno,visittimes,expensesn,receiptno,ordersn,expensedatetime,transactiontypecode,transactiontypename,itemtypecode,itemtypename,itemcode,itemname,itemunit,itemunitprice,itemamount,itemtotalprice,extenddata1,extenddata2,recordstatus)
Set ^CacheTemp(repid,ind)=Data
Set ind=ind+1
quit
}
ClassMethod GetAdmByDate(startDate, endDate, Index = "Dis") As %String
{
;b
k ^||TMPDHCWL($j)
i Index="Prt" d
.d ..GetAdmFromInv(startDate,endDate)
e i Index="Dis" d
.d ...GetAdmFromAdm(startDate,endDate)
e i Index="MriP" d
.d ...GetAdmFromMriP(startDate,endDate)
e i Index="MrInfo" d
.d ..GetAdmFromMrinfo(startDate,endDate)
e i Index="EPR" d
.d ..GetAdmFromEPRLoad(startDate,endDate)
S num1=0
s num2=0
s num3=0
s mFlag=0
s paAdm="" f s paAdm=$o(^||TMPDHCWL($j,"ADM",paAdm)) q:paAdm="" d
.s typeCode=$p(^||TMPDHCWL($j,"ADM",paAdm),"^",3)
.;w typeCode,!
.;q:typeCode'="I"
.i (typeCode="O")||(typeCode="E") d
..;s mFlag=..GetDiagFlagO(paAdm)
..;s num1=num1+1
.e d
..;s mFlag=..GetDiagFlagI(paAdm)
..;s num2=num2+1
.;q:mFlag=0
.;s num3=num3+1
.s ^||TMPDHCWL($j,"STADM",paAdm)=$g(^||TMPDHCWL($j,"ADM",paAdm))
k ^||TMPDHCWL($j,"ADM")
q 1
}
ClassMethod GetAdmFromInv(startDate, endDate) As %String
{
s stDate=$zdh(startDate,3)
s enDate=$zdh(endDate,3)
f date=stDate:1:enDate d
.s invId=0 f s invId=$o(^DHCINVPRT(0,"Date",date,invId)) q:invId="" d
..s time=$p(^DHCINVPRT(invId),"^",20)
..s BCIRowid=0 f s BCIRowid=$o(^DHCBCI(0,"INV",invId,BCIRowid)) q:BCIRowid="" d
...s PBRowid=$p(^DHCBCI(BCIRowid),"^",2)
...s admId=$p(^DHCPB(PBRowid),"^",1)
...s type=$p(^PAADM(admId),"^",2)
...q:type="H"
...s ^||TMPDHCWL($j,"ADM",admId)=date_"^"_time_"^"_type
.s invzyId=0 f s invzyId=$o(^DHCINVPRTZY(0,"DATE",date,invzyId)) q:invzyId="" d
..s admId=$p(^DHCINVPRTZY(invzyId),"^",4)
..s time=$p(^DHCINVPRTZY(invzyId),"^",3)
..s type=$p(^PAADM(admId),"^",2)
..s ^||TMPDHCWL($j,"ADM",admId)=date_"^"_time_"^"_type
q 1
}