美文网首页
记一次性能优化

记一次性能优化

作者: iscaster | 来源:发表于2017-02-19 23:53 被阅读0次

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

相关文章

网友评论

      本文标题:记一次性能优化

      本文链接:https://www.haomeiwen.com/subject/obhzittx.html