SPL是一种面向结构化数据计算的程序设计语言,集算器是SPL语言的java实现,采用网格式编程形式提供了编码和调试的IDE环境,语法比Java和SQL更为简单易懂,开发效率更高。本文将从集算器的实现原理出发列举一些能够提高计算性能的小技巧。java
SPL里的数值类型有Integer、Long、Double、BigDecimal。其中BigDecimal虽然可以表示任意精度的数据,但计算速度比其它数类型慢不少,占用的内存也大不少,所以在其它数字类型可以知足精度要求时,使用其它数类型代替BigDecimal可以显著提高计算效率。数据库
实际案例中,在使用JDBC读取数据库数据时,有些数据库的JDBC对于低精度数值也返回BigDecimal,这样,在作性能优化时就能够检查一下是否能够转为其它类型,从而提高性能。编程
Java的字符串对象String占用空间较大,一个长度为0的字符串占用40多个字节,而Integer、Long只占用16个字节。同时字符串的比较运算、哈希运算也比Integer、Long慢。数组
另外,数据从硬盘读入生成java对象,其占用的内存大小每每是其占用的硬盘大小的数倍甚至十倍以上(若是硬盘存储使用了压缩技术差距会更大)。这种状况可能直接致使不太大的数据文件在读成java对象时发生内存溢出,这时若是不能减小内存占用量就只能使用外存计算了。而一般外存计算的复杂度远大于内存计算,同时也致使性能会降低不少。性能优化
那么,有没有什么方法可以减小内存占用同时又能提升计算效率呢?网络
一个经常使用的方法就是枚举串序号化,好比下面一个事实表的数据:数据结构
性别、地区这类枚举型的字段,能够创建一个对应表把性别、地区值转换为序号一、二、…,这样事实表中性别字段就能够只保存对应的序号,地区也是同样。转换后数据以下:函数
这样一来,咱们就能够作到减小内存占用,而且提升计算效率,由于数字的比较、分组等操做比字符串的要快不少。在输出结果时,能够根据须要再把序号转化为串,也就是使用序号直接按位置到代码表中找到相应的记录进行替换。性能
序表相似数据库中的表,可是倒是有顺序的。序表数据在内存中用一个连续的数组保存。通常状况下,为序表分配内存时会多预留一些空间来应付可能的增加,以避免每次追加数据时都从新分配内存,不过也不可能预留太多空间而浪费内存。优化
基于这个原来,为序表频繁地追加记录,会致使这个数组长度不断地变长,原先为这个数组分配的空间也要扩大。而扩大内存分配不是一件很简单的事情,须要分配一块更大的空间,而后将原空间内的数据复制过来。寻找空间和复制数据都要占用 CPU 时间,并且经常比运算自己的消耗都大。
所以,若是事先知道行数,一次性把序表建立出来,那只须要在一开始分配一次内存就好了。即使序表中的字段值须要一些步骤才能计算出来,那也应该先new出序表后再去修改记录的字段值,而不要计算一行插入一行。而对于修改记录字段值的方法SPL提供了不少途径。
假设咱们想生成一个20行 2列的斐波那契数列序表,第一列key为行号,即 1,2,3,…;第二列 value 为值。斐波那契数列数列的规则是:第 一、第 2 行取值为 1,从第 3 行起,取值为前两行之和。这个运算须要一步步实现,动态追加数据就是很天然的想法了:
不过,序表一次性产生性能更好,即便计算自己仍然须要一步步实现:
扩充序表,除了一行行追加数据,还有可能会改变数据的结构,增长每行数据中的字段,也就是所谓的列追加。列追加比行追加要更为复杂,序表自己是一个大数组,其中的每一行是一条记录,物理实现上也是一个数组。由于数据结构不多改变,建立序表时不会在生成每行的数组时预留空间,不然内存浪费就太多了(由于每一行都要预留)。基于这种实现原理,若是出现列追加,就会发生前面说的从新分配空间的状况,并且要针对每一行记录进行,再将原记录数据抄过来,能够想见,这个动做的时间成本有多大,甚至常常会远远超过追加那个列后要作的计算。
SPL为序表提供了追加列的功能,这会带来方便性,但在关注性能时却要慎用。不得不用时,也应该如上所述,一次性把须要追加的列都加上,不要一遍遍地追加。对于当时没法计算出字段值的列能够先填成空值,之后再用其它函数去修改字段值。
最多见的状况,从数据库取出的序表后,若是事先知道要再derive出新的一列xxx,那么能够在写SQL时多写一个null as xxx,这样在query时就直接把所需的字段都产生了,不用再作一次derive了。
例如,要从数据表sales中取出字段ORDERDATE,AMOUNT并按ORDERDATE排序,而后追加一列计算AMOUNT的累计值。通常先读出再追加列的天然写法:
而用 SQL 语句先把列生成好的写法:
针对前面两种调整序表结构的优化思路,出发点都是减小new、derive函数中抄字段值的动做。除此以外,SPL还支持对象引用,字段取值能够是另外一条记录。这样,在SPL中,大多数状况不必像SQL那样在新结果集中把字段抄一遍,为了保持原有整条记录一块儿参与运算,只要用引用方式来写就能够了。这样不只性能更好并且空间占用也少。
上面用derive追加AMOUNT累计值的要求能够用new函数实现,new建立一个新序表,SRC字段引用原纪录,CUMULATE字段存储累计值,写法以下:
SPL的网格程序提供了循环语句for和分支语句if来实现复杂的运算逻辑。运行时,因为网格的执行次序是动态解释的,所以大量使用循环,会致使执行的网格过多,在网格的动态解释上就要花费大量的时间。
除了循环语句,SPL还提供了循环函数,能够对付大多数须要使用for语句的场景。对于计算步骤不太复杂,对性能要求高的运算应该尽可能使用循环函数来完成。相似地,能用if 函数的场景也尽可能不要用if语句。
1.2节中列举的计算斐波那契数列的例子能够改写为以下:
其中#表示当前循环到哪条记录,第一条记录对应的#是1,依次递增。value[-1]表示上一条记录的value值,value[-2]表示上前数第二条记录的value值。
eval函数每次执行都要把参数指定的表达式字符串解析成表达式,而后再执行,若是eval函数在循环里执行,过多地把表达式字符串解析成表达式会花费大量的时间,若是表达式字符串不是变的则可使用宏替换代替eval。
把循环里常量的产生放到循环外,也能够对性能优化提供帮助。例如选出北京, 上海, 深圳地区的销售记录,比较“天然”的写法是:
由于SPL的序列是能够被修改的,因此表达式["北京","上海","深圳"]每计算一次都会产生一个新序列。若是像上面这样把["北京","上海","深圳"]放在循环函数select里,那么在执行时将会产生A2长度个序列。若是循环次数多,这些没必要要的运算将消耗大量时间。所以,注重性能的写法应该以下:
警戒循环函数中再有循环函数,这些代码看起来很简单,但几层循环下来,实际计算量会以几何级数放大。这虽然是个常识,但有时也会被忽略,所以能在循环外作的事不要放到循环内。特别地,尤为要警戒在循环内读文件和访问数据库这种超级耗时的动做。
Java在内存不足时性能会急剧降低。因此要及时释放内存,SPL没有删除变量释放内存的语句,只需把变量或单元格的值设为空便可,也能够用clear语句清除一片格子。例子以下:
以=开头单元格是计算格,表达式的返回值会保存在单元格上,以>开头的单元格是执行格,表达式的返回值不会保存。cs.select和cs.join是给游标附加运算,不会产生新的游标因此返回值能够不用保存,A7格为释放读出的PART数据,也能够用clear语句把A1到A5之间的单元格值都清空,只须要把A7格代码替换以下:
for 和if的代码块,能够直接写到同一行上,没有必要像Java那样换一行再写。SPL的网络已经可以清晰地拆分出这些语句了。解释器扫描空白格也须要时间,所以对于含有循环语句的程序,若是循环次数特别多,应该让代码紧凑一些,删除空白的行和列以减小格子数量,从而提升解释器的效率。
下面以获取天天第一条销售记录为例,介绍一下SPL的代码块规则,sales是销售记录游标参数,按ORDERDATE有序。
单元格的代码块为单元格所在行及其正下和左下单元格都为空白格的行,上面例子中A2格for的代码块为[B2:F5]。B2格if的代码块为[C2 : F2],if代码块的下一行和if所在格同列的单元格B3为else,而且B3左面的格子都是空白格,则B3格为B2格的else分支,B3格的代码块为[C3 : F5]。else也能够和对应的if同行,写在if右面的单元格上。