当数据量达到百万级的时候, 我们的项目遇到了一些问题, 一些报表速度慢的令人难以接受. 设计之初就考虑到报表速度的问题, 但是业务复杂, 需要从不同的数据源下获取信息并且加入若干项用户自定义公式的校验, 要命的是数十个这样的结果要显示在同一个表中. 一直没有更好的办法. 今天这个问题终于浮出了水面.
深夜, 项目组的成员在宾馆的客房里讨论着解决办法. PSM要求大家全力优化自己的代码并且情绪极其激动; 几个成员认为瓶颈不在代码, 而在于数据库的优化和减少业务的复杂度, 用抽取少量数据来代替全量的比对; 其他人没有说话; 我在查看大家写的代码.
减少业务复杂度是不可能的, 我们没有改变需求的权利, 而这也是个很合理的需求, 即使不合理也应该在项目前期提出; 抽取数据的方法也不可行, 我可不能忍受表格上的数据在没有更改数据库的时候每次都有变动; 数据库是要优化, 硬件也要提高, 但至少要在一周之后.
大家的音调越来越高, 之后是半分钟的沉默. 我打破了这个沉默 :"我觉得我们的代码还有很多修改的余地, 单从修改代码结构就可以提高不少的性能, 比如减少函数的调用, 将循环赋值变成顺次硬编码赋值, 用位运算代替各别操作, 还有很多. 但是对一些代码的修改将降低程序的可读性..." 似乎我的话并没有引起多大重视, PSM只是重申了一便要优化代码.
今天, 我在尝试一种可以提高效率的新方法时碰到了如下代码, 我原封不动的复制过来:
// 对 单个"运算符-数值"对进行判断
// 当有一个比较运算符匹配,即可返回结果
boolean result = false ;
int length = fieldContent.length();
if (pair[ 0 ].equals( " > " )) {
if (length > Integer.parseInt(pair[ 1 ].toString())) {
result = true ;
return true ;
} else {
result = false ;
// System.out.println("length error:"+fieldContent);
return false ;
}
} else if (pair[ 0 ].equals( " < " )) {
if (length < Integer.parseInt(pair[ 1 ].toString())) {
result = true ;
return true ;
} else {
result = false ;
return false ;
}
} else if (pair[ 0 ].equals( " = " )) {
if (length == Integer.parseInt(pair[ 1 ].toString())) {
result = true ;
return true ;
} else {
result = false ;
return false ;
}
} else if (pair[ 0 ].equals( " >= " )) {
if (length >= Integer.parseInt(pair[ 1 ].toString())) {
result = true ;
return true ;
} else {
result = false ;
return false ;
}
} else if (pair[ 0 ].equals( " <= " )) {
if (length <= Integer.parseInt(pair[ 1 ].toString())) {
result = true ;
return true ;
} else {
result = false ;
return false ;
}
} else if (pair[ 0 ].equals( " != " )) {
if (length != Integer.parseInt(pair[ 1 ].toString())) {
result = true ; // 反向的,格外注意!
return true ;
} else {
result = false ;
return false ;
}
}
return result;
}
这段代码有些像刚刚开始学习程序设计的学生写的, 很多人都会毫不犹豫的说自己不会写这种蹩脚的代码, 是这样吗? 这段代码的作者是有一年多工作经验的人, 我相信他可以做出一些改正, 问题就在于很多人在敲击键盘的时候只是用手而没有用脑.
这段代码有以下几个问题:
1) 表达罗嗦, result 变量没有任何用处, 冗余的代码浪费了编译时间和硬盘空间, 并且降低了可读性;
2) 两个if连接在一起, if后语句分配的是跳转的内存块, 无用的跳转占用了时间;
3) Integer.parseInt(pair[1].toString()) 频繁使用, 如果平均步数为3, 则每次oneLenCheck方法调用都需要做3次String 到 Integer的转换;
4) 当数组下标频繁变换是, 指针片的切换比直接引用变量占用了更多的时间.
我针对这些问题做了修改, 修改后的代码如下:
// 对 单个"运算符-数值"对进行判断
// 当有一个比较运算符匹配,即可返回结果
int length = fieldContent.length();
int parLength = Integer.parseInt(pair[ 1 ].toString());
String symbol = pair[ 0 ] + "" ;
if ( length > parLength && symbol.equals(">") ) {
return true ;
} else if ( length < parLength && symbol.equals("<") ) {
return true ;
} else if ( length == parLength && symbol.equals("=") ) {
return true ;
} else if ( length >= parLength && symbol.equals(">=") ) {
return true ;
} else if ( length <= parLength && symbol.equals("<=")) {
return true ;
} else if ( length != parLength && symbol.equals("!=") ) {
return true ;
}
return false ;
}
当然, 修改后的代码也并非完美, 没有做到防御性编程, 执行Integer.parseInt(pair[1].toString())时可能抛出异常. 这是其他问题了, 在能够保证数据质量的时候可以放弃异常检测.
性能提升: 进行1000次调用, 修改前的方法平均每次比修改后多0.00001秒, 则对于1,000,000条记录, 可以获得10秒的性能提升. 10秒钟, 哦, 我可以小心的倒杯热水.