前端性能优化(四)okcoin报价密度性能优化及工作机会推荐(纯javascript高频数据处理性能优化)

本文来源我的知乎 https://zhuanlan.zhihu.com/p/36743871

前言

先交代一下我的近况,已经入职okcoin了,刚入职,感谢前阿里巴巴技术专家铭承(我老板)。走的匆忙,很多阿里的同学都没来得及打个招呼,好在我现在还会翻看钉钉,可以钉钉我。又到了晋升季,祝大家步步高升。下面进入本文主题,这篇文章应该叫《纯javascript高频数据处理性能优化》。

背景介绍

报价密度是用来展示各档位报价数量的,需要在浏览器端做合并。比如报价2.9511的数量1.0200,报价2.9520的数量是1.0333,按照两位小数合并,如果是卖盘则合并结果为报价2.9600的数量为2.0533,如果是买盘则合并结果为报价2.9500的数量为2.0533。按照什么精度来合并用户可以自己选择。由于交易特别活跃,推送的非常频繁,需要在浏览器端做大量计算,直接的结果就是CPU非常高。

优化

我之前写了一篇前端加载优化的文章,链接为 小姜哥:前端性能优化(一)用一张图说明加载优化 , 

 狼叔看了之后说业务梳理更重要,确实是这样的,本次确实做了很多业务梳理方面的优化,原则当然是能不计算就不计算,能少计算就少计算。比如循环外就可以确定的值不要在循环内做计算。比如报价显示20档,当计算获得的数据足够显示时就不要再计算了。

因为场景特殊,这次在纯技术角度的优化所带来的收益也是非常多的,甚至可能超过了业务梳理。

一般来说浏览器端程序出现性能问题都是和DOM操作扯上关系,纯javascript出现严重性能问题是少见的。我至今也就遇到过两次,第一次是在大智慧做行情显示,推送频繁;第二次就是这次在okcoin的报价密度,推送频繁。本文会主要说一些纯技术方面的优化点,有一些是本次做了的,有的是还没做的,优化无止境,一步一步来。

1. try catch的性能

先看例子

// === try catch
var start = window.performance.now()
for (var i = 0, b = ''; i < 500000; i++) {
    var a = b + i;
}
var end = window.performance.now()
console.log(end - start)

var start = window.performance.now()
for (var i = 0, b = ''; i < 500000; i++) {
    try {
        var a = b + i;
    } catch (e) {

    }
}
var end = window.performance.now()
console.log(end - start)

// 输出结果
37.59999999991851
58.79999999998836

从测试结果看try catch确实慢,但是慢的不离谱,慢不了一倍。

我们再来看一个例子。

// === try catch
var start = window.performance.now()
for (var i = 0, b = ''; i < 500000; i++) {
    var a;
    a = (i.toString().split('.')[1] || '').length;
}
var end = window.performance.now()
console.log(end - start)

var start = window.performance.now()
for (var i = 0, b = ''; i < 500000; i++) {
    var a;
    try {
        a = i.toString().split('.')[1].length;
    } catch (e) {
        a = 0;
    }
}
var end = window.performance.now()
console.log(end - start)

// 输出结果
148.00000000011642
3314.400000000256

吓到了吧,慢了20多倍。所以得出结论:try catch要慎用,如果可以把程序写的健壮就不要用try catch来解决问题。

这也是这次优化的大头,之前程序里的加减乘除每个运算使用两个try来做计算小数位的处理,很多走了catch分支,而每一条数据的处理都涉及到多次加减乘除运算,时间耗费巨大。

2. with的性能

先看代码

// === with
var arr = [];
for (var i = 0; i < 50000; i++) {
    arr.push('' + Math.random() * 100000)
}
var start = window.performance.now()
for (var i = 0, value; i < arr.length; i++) {
    value = Math.max(i, arr[i]) 
}
var end = window.performance.now()
console.log(end - start)

var start = window.performance.now()
for (var i = 0, value; i < arr.length; i++) {
    with (Math) {
        value = max(i, arr[i]) 
    }   
}
var end = window.performance.now()
console.log(end - start)

// 输出结果
13.10000000000582
60.20000000001164

性能差四五倍,with这种东西真的不能用,最佳实践早就否定了with。

3. 计算小数精度

先看代码

// === 计算小数精度
var arr = [];
for (var i = 0; i < 50000; i++) {
    arr.push('' + Math.random() * 100000)
}
var start = window.performance.now()
for (var i = 0, value; i < arr.length; i++) {
    value = arr[i]                    
    var index = value.indexOf('.');
    var len = 0;
    if (index > -1) {
        len = value.length - 1 - index;
    }    
}
var end = window.performance.now()
console.log(end - start)

var start = window.performance.now()
for (var i = 0, value; i < arr.length; i++) {
    value = arr[i]                    
    var index = value.indexOf('.');
    var len = 0;
    if (index > -1) {
        len = value.split('.')[1].length;
    }    
}
var end = window.performance.now()
console.log(end - start)

// 输出结果
3.5000000009313226
20.00000000046566

从测试结果看split的实现方式慢五六倍,也很好理解,对于这个例子split要多创建出来两个字符串和一个数组。这也是本次优化的点之一,结合前文提到的在计算小数精度的时候用到了try catch,所以本次优化在计算小数精度方面比原来提升了几十到上百倍。

4. 将带小数点的字符串分割为整数和小数

先看代码

// === slice vs split
var arr = [];
for (var i = 0; i < 500000; i++) {
    arr.push('' + Math.random() * 100000)
}
var start = window.performance.now()
for (var i = 0, value; i < arr.length; i++) {
    value = arr[i]                    
    var index = value.indexOf('.');
    var a = value.slice(0, index)
    var b = value.slice(index + 1)   
}
var end = window.performance.now()
console.log(end - start)

var start = window.performance.now()
for (var i = 0, value; i < arr.length; i++) {
    value = arr[i]                
    var array = value.split('.')
    var a = array[0]
    var a = array[1]   
}
var end = window.performance.now()
console.log(end - start)

// 测试结果
47.8000000002794
146.8000000002794

从结果看,slice比split还是有优势的,道理应该是操作的内存空间不一样。这是本次没有做优化的,本次优化主要针对于有几倍性能提升的点。

5. 操作Cookie

// === Cookie
var start = window.performance.now()
var cookie = document.cookie
for (var i = 0, value; i < 5000; i++) {
    value = cookie;
}
var end = window.performance.now()
console.log(end - start)

var start = window.performance.now()
for (var i = 0, value; i < 5000; i++) {
    value = document.cookie;
}
var end = window.performance.now()
console.log(end - start)

// 输出结果
0.7999999997991836
623.7999999993917

又吓到了吧,读取cookie原来可以这样慢,七八百倍,这是5000次的结果,看样子是读取cookie的时候会和硬盘扯上关系,我是ssd,机械硬盘有可能更慢。这个例子只是测试了读document.cookie,实际操作又涉及到按照;来split,之后还要循环按照=来split,前面也说过split性能并不优秀。

所以读取cookie的正确姿势是:如果不易变的cookie只做一次读取,用变量来做缓存,如果是容易变的做一个更新机制。这也是本次优化的点,之前每次数据更新会读一次cookie,而如前文所说的,数据推送特别频繁。

6. 数字转化为字符串

先说结论,这个应该算做无差异,应该做的是能不转就不转,能少转就少转。

// === toString vs plus
var arr = [];
for (var i = 0; i < 500000; i++) {
    arr.push(Math.random() * 100000)
}
var start = window.performance.now()
for (var i = 0, value; i < arr.length; i++) {
    value = arr[i].toString()                  
}
var end = window.performance.now()
console.log(end - start)

var start = window.performance.now()
for (var i = 0, value; i < arr.length; i++) {
    value = '' + arr[i]
}
var end = window.performance.now()
console.log(end - start)

// 输出结果
144.80000000004657 
148.19999999948777

7. 字符串转换为数字,new Numbr vs Number

// === new Number vs Number
var arr = [];
for (var i = 0; i < 500000; i++) {
    arr.push('' + Math.random() * 100000)
}
var start = window.performance.now()
for (var i = 0, value; i < arr.length; i++) {
    value = new Number(arr[i]) - new Number('1')                 
}
var end = window.performance.now()
console.log(end - start)
var start = window.performance.now()
for (var i = 0, value; i < arr.length; i++) {
    value = Number(arr[i]) - Number('1');
}
var end = window.performance.now()
console.log(end - start)

// 输出结果
129
74.40000000060536

是不是发现还是有差异的,最佳实践是有道理的。

8. new Array 和 数组字面量

// === new Array vs []
var arr = [];
for (var i = 0; i < 500000; i++) {
    arr.push('' + Math.random() * 100000)
}
var start = window.performance.now()
for (var i = 0, value; i < arr.length; i++) {
    value = [];               
}
var end = window.performance.now()
console.log(end - start)
var start = window.performance.now()
for (var i = 0, value; i < arr.length; i++) {
    value = new Array()
}
var end = window.performance.now()
console.log(end - start)

// 输出结果为
16.199999999953434
20.199999999953434

最佳实践告诉我们使用字面量是有道理的吧~

9. 用0补位如何生成0

// === 循环拼接0 VS slice
var arr = [];
for (var i = 0; i < 50000; i++) {
    arr.push('' + Math.random() * 100000)
}

var start = window.performance.now()
var zero20 = '000000000000000';
for (var i = 0, value; i < arr.length; i++) {
    value = zero20.slice(0, 4)    
}
var end = window.performance.now()
console.log(end - start)

var start = window.performance.now()
for (var i = 0, value; i < arr.length; i++) {
    value = '';
    for (var j = 0 ; j < 4; j++) {
        value += '0';
    }
}
var end = window.performance.now()
console.log(end - start)

// 输出结果
3.3000000003085006
9.599999999409192

从结果可以看出slice还是有不小的优势的,这也是本次做的。我们知道在显示的的时候需要做一次补0占位的操作,用slice这种更为合适。

10. 向上向下截位

这个对比就不贴出来了,之前的代码太长,下面是新实现的一版向下截位,测试了一下比原来的版本性能好7倍左右。向上截位类似。

var zero20 = '00000000000000000000';
// 向下截位(小数部分向下截位)
function floorTruncation(number, len) {
    number = '' + number;
    var index = number.indexOf('.');
    var precision = index > -1 ? number.length - 1 - index : 0;
    if (precision == len) {
        // 已有精度与目标精度相同
        return number;
    } else if (precision == 0 || len == 0) { 
        // 如果是整数,按照精度进行补零。 如果要求的精度是0则用toFixed直接截断。
        return (+number).toFixed(len)
    }
    // 小数的情况
    return (number + zero20).slice(0, index + 1 + len);
}

其他

还有其他的一些点,这篇文章就不一一列举了,以后有机会再写。

结论

其实上面说的这些点每个拿出来单独执行都非常快,微秒级别的。不同实现之间的差异当然也是微秒级别的。但是在执行次数巨大的情况下就凸显出来差异了。

划重点

前段时间是相对动荡的,有很多人帮助了我,借着这边文章表达一下对他们的感谢。

感谢我的老板铭承。

感谢okcoin的美女HR小姐姐们。

感谢前老板倪欧。

感谢狼叔给我很多新观点。

感谢前公司的前老板们和小伙伴们。

感谢新浪微博的冰哥和吴大师。

感谢美团的斌哥和松哥让我有进美团的机会, 也感谢美团的美女HR小姐姐,美团真的很棒,虽然我没去。

感谢彭老大。

感谢我唐哥

感谢我的好兄弟们和好朋友们。

感谢热情的帮我推荐工作机会的小姐姐们,虽然我基本都没去看。

第二波重点

如果你正在寻找工作我可以帮忙推荐,直接推荐到用人部门的leader手里。可以推荐的公司包括:okcoin、新浪微博、美团、阿里大文娱、猎豹、快手、滴滴、头条等。 请把简历发送到我的邮箱 [email protected] ,请注明自己的基本信息以及想去的公司。也可加我微信lw20170313 ,工作忙,不保证一定回复,不要发简历到微信,见谅。

结束

最后祝大家工作顺利,写出高性能代码,少写bug。

本文有任何疏漏欢迎指正。

有哪些可优化点欢迎指点一下,如果能来okcoin我们一起来做事是极好的,欢迎发简历过来。


猜你喜欢

转载自blog.csdn.net/tt361/article/details/80911486