记一次性能优化

有那么一句名言,过早优化是万恶之源。指的就是在开发过程中,不用太早考虑性能问题,而是要优先实现功能和保持代码清晰,简洁。更深的原因,是对个人的判断的极端不信任。我们觉得重要的往往不重要,我们觉得不重要的事后才发现很重要。事前的判断,往往会错得离谱。所以,在软件工程中,就放弃了事前对可能的性能占用情况的评估,而是在实现运行中,通过软件工具,来分析性能瓶颈,即:profiling.

我最近就遇到一个profiling的情况。

最初是因为有那么一个bug。导致js后台处理进程,一小时左右,会自动退出。经过分析,我发现这是我所使用的kafka连接库产生的问题,这个问题不好修复,但是我可以使用trick绕过它。处理之后,进程不会报错退出了,但是,在运行3-4小时后,进程的处理性能会出现巨大的下降,而且内存占用会不停的增长,不停的刷新占用的上限。

很明显,这个js进程存在内存泄漏的情况。

但是内存泄漏是哪里产生的咧?

由于之前没有做过profiling,无法判断是旧有的代码就有问题,还是新加入的代码导致的。所以只能全局的去考虑。

最开始,我使用了nodejs自带的profiling工具进行过一些cpu性能分析

https://nodejs.org/en/docs/guides/simple-profiling/

后来我又找到了更直观的node-inspector

https://github.com/node-inspector/node-inspector

通过node-inspector分析,所能了解的也不多。只是看到closure,也就是闭包,有占到26%的内存,是最大的一项。

这么说,是我代码中的closure导致的内存泄漏。我又搜索了相关资料,了解了一下closure导致内存泄漏的常见形式。给我启发最大的是这么一篇:

http://point.davidglasser.net/2013/06/27/surprising-javascript-memory-leak.html

在里面提到js v8一个惊人的细节。在同一作用域定义的闭包,会共享上下文(context),经典的触发代码如下:

var str = new Array(1000000).join('*');

var doSomethingWithStr = function () {

  if (str === 'something')

    console.log("str was something");

  };

  doSomethingWithStr();

  var logIt = function () {

    console.log('interval');

  }

  setInterval(logIt, 100);

};

setInterval(run, 1000);

在这段代码中logIt和doSomethingWithStr共享上下文,引用了str,导致str不被会gc清理,从而内存泄漏。

我又看了一下代码,代码中并没有使用setInteval函数,但使用的Rxjs,Rxjs的Observable.subscribe函数会不会有和setInteval函数类似的效果。我立即写了段代码测试了一下。

var subscription = null

var sequence = null

function run() {

  var str = new Array(1000000).join('*');

  if (subscription != null) {

    subscription.dispose()

  }

var sequence = Rx.Observable.interval(200)

var subscription = sequence.subscribe(

  function() {

    console.log("str[0]=", str[0])

  })

}

setInterval(run, 500);

使用工具查看,发现这段代码会导致内存占用迅速上升,即存在内存泄漏。

经过试验后,我找到了初步的解决方案。

var subscription = null

var sequence = null

function run() {

  var str = new Array(1000000).join('*');

  if (subscription != null) {

    subscription.dispose()

  }

  var sequence = Rx.Observable.interval(200).take(3)

  var subscription = sequence.subscribe(

  function() {

    console.log("str[0]=", str[0])

  } , function(err) {

    console.log("err=", err)

  } , function(err) {

    str=null

    console.log("completed")

  } )

}

setInterval(run, 500);

即在消息流结束的时候,将str对象置为null,来防止str对象的泄漏。本以为这就是最佳方案。无意中我又发现,哪怕没有在onCompleted函数,只要流结束,也不会内存泄漏。


我觉得这个rxjs的一个bug。在dispose后,不就是应该该把要清理都清理了,是吧?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 工厂模式类似于现实生活中的工厂可以产生大量相似的商品,去做同样的事情,实现同样的效果;这时候需要使用工厂模式。简单...
    舟渔行舟阅读 7,814评论 2 17
  • 单例模式 适用场景:可能会在场景中使用到对象,但只有一个实例,加载时并不主动创建,需要时才创建 最常见的单例模式,...
    Obeing阅读 2,092评论 1 10
  • 函数声明和函数表达式有什么区别 (*)解析器会率先读取函数声明,并使其在执行任何代码之前可以访问;函数表达式则必须...
    coolheadedY阅读 399评论 0 1
  • 一、数组 数组是一个有序列表,所以有下标. 并且数组在js中可以存在任意类型的数据.并且同一个数组中可以存放不同的...
    空谷悠阅读 519评论 0 1
  • 相关知识点 数据类型、运算、对象、function、继承、闭包、作用域、原型链、事件、RegExp、JSON、Aj...
    sandisen阅读 11,389评论 7 175