任何东西只要够深,都是一把刀(面试版)

14,037 阅读7分钟

前言

标题无能,请各位看官放下你们的屠刀,扎心的往下看,看到情不自禁处请手下留情。

引言

您瞧清楚了,我是互联网公司技术开发一枚,渴了喝咖啡,饿了吃外卖,晕了上医院的那种。房贷没还清,车子自己买,人家休假我加班。还有最重要的是,我是一条单身狗。不要想着单身贵族这种身份,你爸和你妈的一巴掌就能让你认清现实。

我要讲的故事就四字:性能优化

我知道,此时此刻看到这里的朋友一定会掌心出汗、眉头紧锁,用一种带着危险的眼神死盯着手机屏幕。来吧,我不怕。性能优化都没搞好,还想着去找对象?

我在前年遇到的面试题

面试官:你为性能优化做了哪些事情?

答: 懒加载缓存离线包并行化

面试官: 那离线包是如何设计的?

答: 就是将首页做一份快照,交给native童鞋在App打包的时候放入包的本地路径。

面试官:那你对懒加载或者缓存有什么具体的实施方案吗?

答: 有啊。骨架屏、SSR。

面试官: 骨架屏和SSR有什么各自优缺点?

答: 骨架屏适合在页面加载延迟毕竟高的情况下使用,但是骨架屏的灰色豆腐块需求视觉切图,这个图是需要缓存在客户端的。如果大范围使用骨架屏,可能会导致客户端缓存过多的图片,反而得不偿失吧。SSR实施成本会高一些,如果不是特别重要的页面不建议使用。 当网络环境很差劲的情况下,依然还是要做降级处理的,也就是客户端渲染。

面试官: 针对首屏白屏问题你有具体的解决方案吗?

我内心OS: 内心开始MMP了,合着我前面说的那些就不是解决方案了?

答: 方法有不少,但具体还是要看公司的环境了。毕竟不是所有手段都能用上。 但首屏很重要的一个点是做好缓存。例如利用好http缓存。。。

小结一下

那时候,面试几乎都是靠面试官抽一鞭子然后我回答一哆嗦。 回答性能优化方面的问题,也基本上是那老四样。 可问题就在于,这些答案换个人来也能说。说白了,你说出去的东西并不是真正属于你的东西。 然后,职位岗位略高一点,你就没机会了。

发生在昨天的面试故事

面试官:性能监控平台是如何搭建的?

答: 先确认性能指标。例如首屏渲染时间、页面白屏时间、秒卡率、FPS、平均请求时间等。

(被打断,因为面试官get到了他感兴趣的点)

面试官: 你们的 FPS 是怎么计算的?

答: FPSFrames Per Second,每秒显示帧数)。一般 FPS60 以上,页面流畅,不卡顿。但事实上并非如此,比如你在打游戏(例如吃鸡、王者荣耀),虽然 FPS 低于60,但我们觉得很流畅,并不卡顿。

FPS 低于 60 并不意味着卡顿,那 FPS 高于 60 也非一定不卡顿。比如前 60 帧渲染很快(10ms 渲染 1 帧),后面的 3 帧渲染很慢( 20ms 渲染 1 帧),这样平均起来 FPS 为95,高于 60 的标准。这种情况会不会卡顿呢?明显是卡顿的。

所以卡顿与否的关键点在于单帧渲染耗时是否过长。

但难点在于,在浏览器上,我们没办法拿到单帧渲染耗时的接口。所以我们只能拿 FPS 来计算,只要 FPS 保持稳定,且值比较低,就没问题。我们给它定的标准是连续 3 帧不低于 20 FPS,且保持稳定。

(面试官眼睛开始有光了。)

面试官: 那你就写一下吧。

// **开始在线写代码。**
// 以h5 为例

/*
利用 requestAnimationFrame 在一秒内执行 60 次(在不卡顿的情况下)这一点,
假设页面加载用时 X ms,这期间 requestAnimationFrame 执行了 N 次,
则帧率为1000* N/X,也就是FPS。
*/

/*但不同户客户端差异很大,需要考虑兼容性。
在这里我们定义 fpsCompatibility 表示兼容性方面的处理,在浏览器不支持
requestAnimationFrame 时,利用 setTimeout 来模拟实现。
在 fpsLoop 里面完成 FPS 的计算。
最后通过遍历 fpsList 来判断是否连续三次 fps 小于20。
*/
const fpsCompatibility = function () {

  return (

    window.requestAnimationFrame ||

    window.webkitRequestAnimationFrame ||

    function (callback) {

      window.setTimeout(callback, 1000 / 60);

    }

  );

}();

const fpsConfig = {

  lastTime: performance.now(), // performance 是一个浏览器提供的API

  lastFameTime: performance.now(),

  frame: 0

}

const fpsList = [];

const fpsLoop = function () {

  const first = performance.now();
  
  const diff = (first - fpsConfig.lastFameTime);

  fpsConfig.lastFameTime = first;

  const fps = Math.round(1000 / diff);

  fpsConfig.frame = fpsConfig.frame + 1;

  if (first > 1000 + fpsConfig.lastTime) {

    const fps = Math.round((fpsConfig.frame * 1000) / (first - fpsConfig.lastTime));

    fpsList.push(fps);
    
    console.log(`time: ${new Date()} fps is:`, fps);

    fpsConfig.frame = 0;

    fpsConfig.lastTime = first;

  };

  fpsCompatibility(fpsLoop);

}

fpsLoop();

function checkFPS(fpsList, below = 20, last = 3) {
  let count = 0;

  for (let i = 0; i < fpsList.length; i++) {
    if (fpsList[i] && fpsList[i] < below) {
      count++;
    } else {
      count = 0
    }

    if (count >= last) {
      return true
    }
  }

  return false

}

checkFPS(fpsList);



// 如果连续判断 3次 FPS 都小于20,就认为是卡顿。

面试官: 嗯,可以。 那像这些性能指标的描述内容你们是如何上报的?

答:手动埋点自动化采集可视化埋点

面试官:如何设计一个性能SDK,有思路吗?

答: 性能SDK设计,一个是接入设计,另一个是SDK运行设计

  • sdk接入设计
    • 可以把之前首屏、白屏、卡顿采集的脚本封装进去。并让脚本自动运行。
    • 做好SDK使用/帮助文档,提高易用性
    • 整一个性能分析助手,能快速定位一些简单的基础问题
  • SDK运行设计
    • 兼容性问题,用原生JavaScript去写性能指标采集,实现跨端采集
    • 容错机制,例如try catch捕获,然后把异常上报
    • 测试sdk性能,可根据用户实际使用情况来确认机型分布

面试官: 你刚说的异常上报,你的上报策略是如何设计的?

答: 在采集性能指标后,最好还是先对数据异常进行过滤。不过上报策略设计,分几个部分吧。

  • 日志数据过滤
    • 先对数据异常进行过滤
    • 异常数据包括计算错误,合法的异常值、最大值、最小值什么的。
  • 数据抽样策略
    • 上报的数据是全量还是抽样,需要根据日活来决定。
    • 一般日活10W以下,可以选择全量。日活1000W的APP,那就需要抽样了。
  • 上报机型选择
    • 强网情况直接上报,弱网情况先将日志存储在本地,等待强网环境下再上报。
    • 其它的还有APP启动时上报,批量数据上报等……

小结

约十次性能优化十次是什么鬼? 再约就是继续性能优化是什么鬼? 再后来的日子里,我还是和面试官们成为了朋友,困扰我很久的问题也终于从 “面试官们” 那得到了答案。

她是喜欢草莓味的咖啡奶茶,但她更想吃草莓味的冰淇淋。特价牌子的旁边就写着草莓冰淇淋的价格,而我却视而不见。人家指着那你都看不到,听到想点便宜的就暗自窃喜,你真当人家傻呢?

我把“她”换成“面试官”重新说一段。

面试官是喜欢问你对性能优化做了什么事情,但面试官更想知道你做到了而别人没做到的事情。老四样的旁边就写着适用于怎样的场景,而我却视而不见。人家指着那你都看不懂,听到想听简单的就暗自窃喜,你真当面试官傻呢?

所以,请相信,相亲从第一面就开始交锋了。先输了的那个往往就失去了主动权。如果你自嗨了,那你就失去了足够敏锐的观察力。人性都一样,对喜欢的东西容易不设防,对不喜欢的东西会倍加警惕。

最后,任何东西只要够深,都是一把刀。 性能优化也不例外。

(祝,君一切安好。)