您当前的位置:首页 > 电脑百科 > 程序开发 > 编程百科

60亿次for循环,原来这么多东西

时间:2020-09-25 11:30:48  来源:  作者:
60亿次for循环,原来这么多东西

 

作者: Peter 谭老师

转发链接:https://mp.weixin.qq.com/s/Vs8CwdGJ6JUyH_Kp0H5l4Q

起因

  • 有人在思否论坛上向我付费提问

 

60亿次for循环,原来这么多东西

 

  • 当时觉得,这个人问的有问题吧。仔细一看,还是有点东西的

问题重现

  • 编写一段Node.js代码
var http = require('http');
  http.createServer(function (request, response) {
    var num = 0
    for (var i = 1; i < 5900000000; i++) {
        num += i    }    response.end('Hello' + num);
}).listen(8888);
  • 使用nodemon启动服务,用time curl调用这个接口
60亿次for循环,原来这么多东西

 

  • 首次需要7.xxs耗时
  • 多次调用后,问题重现
60亿次for循环,原来这么多东西

 

  • 为什么这个耗时突然变高,由于我是调用的是本机服务,我看CPU使用当时很高,差不多达到100%了.但是我后面发现不是这个问题.

问题排查

  • 排除掉CPU问题,看内存消耗占用。
var http = require('http');
http  .createServer(function(request, response) {    console.log(request.url, 'url');
    let used = process.memoryUsage().heapUsed / 1024 / 1024;
    console.log(
      `The script uses Approximately ${Math.round(used * 100) / 100} MB`,
      'start',
    );    console.time('测试');
    let num = 0;
    for (let i = 1; i < 5900000000; i++) {
      num += i;    }    console.timeEnd('测试');
    used = process.memoryUsage().heapUsed / 1024 / 1024;
    console.log(
      `The script uses approximately ${Math.round(used * 100) / 100} MB`,
      'end',
    );    response.end('Hello' + num);
![](https://imgkr2.cn-bj.ufileos.com/13455121-9d87-42c3-a32e-ea999a2cd09b.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=E3cF2kymC92LifrIC5IOfIZQvnk%253D&Expires=1598883364)
![](https://imgkr2.cn-bj.ufileos.com/1e7b95df-2a48-41c3-827c-3c24b39f4b5b.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=%252FANTTuhgbpIsXslXMc1qCkj2TMU%253D&Expires=1598883362)
  })  .listen(8888);
  • 测试结果:
  • 内存占用和CPU都正常
  • 跟字符串拼接有关,此刻关闭字符串拼接(此时为了快速测试,我把循环次数降到5.9亿次)
60亿次for循环,原来这么多东西

 

  • 发现耗时稳定下来了

定位问题在字符串拼接,先看看字符串拼接的几种方式

  • 一、使用连接符 “+” 把要连接的字符串连起来
var a = 'JAVA'
var b = a + 'script'

* 只连接100个以下的字符串建议用这种方法最方便

  • 二、使用数组的 join 方法连接字符串
var arr = ['hello','java','script']
var str = arr.join("")
  • 比第一种消耗更少的资源,速度也更快
  • 三、使用模板字符串,以反引号( ` )标识
var a = 'java'
var b = `hello ${a}script`
  • 四、使用 JavaScript concat() 方法连接字符串
var a = 'java'
var b = 'script'
var str = a.concat(b)

五、使用对象属性来连接字符串

function StringConnect(){
    this.arr = new Array()
}StringConnect.prototype.append = function(str) {
    this.arr.push(str)
}StringConnect.prototype.toString = function() {
    return this.arr.join("")
}var mystr = new StringConnect()
mystr.append("abc")
mystr.append("def")
mystr.append("g")
var str = mystr.toString()

更换字符串的拼接方式

  • 我把字符串拼接换成了数组的join方式(此时循环5.9亿次)
var http = require('http');
http  .createServer(function(request, response) {    console.log(request.url, 'url');
    let used = process.memoryUsage().heapUsed / 1024 / 1024;
    console.log(
      `The script uses approximately ${Math.round(used * 100) / 100} MB`,
      'start',
    );    console.time('测试');
    let num = 0;
    for (let i = 1; i < 590000000; i++) {
      num += i;    }    const arr = ['Hello'];
    arr.push(num);    console.timeEnd('测试');
    used = process.memoryUsage().heapUsed / 1024 / 1024;
    console.log(
      `The script uses approximately ${Math.round(used * 100) / 100} MB`,
      'end',
    );    response.end(arr.join(''));
  })  .listen(8888);
  • 测试结果,发现接口调用的耗时稳定了(注意此时是5.9亿次循环)
60亿次for循环,原来这么多东西

 

  • 《javascript高级程序设计》中,有一段关于字符串特点的描述,原文大概如下:ECMAScript中的字符串是不可变的,也就是说,字符串一旦创建,他们的值就不能改变。要改变某个变量的保存的的字符串,首先要销毁原来的字符串,然后再用另外一个包含新值的字符串填充该变量

就完了?

  • 用+直接拼接字符串自然会对性能产生一些影响,因为字符串是不可变的,在操作的时候会产生临时字符串副本,+操作符需要消耗时间,重新赋值分配内存需要消耗时间。
  • 但是,我更换了代码后,发现,即使没有字符串拼接,也会耗时不稳定
var http = require('http');
http  .createServer(function(request, response) {
    console.log(request.url, 'url');
    let used = process.memoryUsage().heapUsed / 1024 / 1024;
    console.log(
      `The script uses approximately ${Math.round(used * 100) / 100} MB`,
      'start',
    );
    console.time('测试');
    let num = 0;
    for (let i = 1; i < 5900000000; i++) {
    //   num++;
    }
    const arr = ['Hello'];
    // arr[1] = num;
    console.timeEnd('测试');
    used = process.memoryUsage().heapUsed / 1024 / 1024;
    console.log(
      `The script uses approximately ${Math.round(used * 100) / 100} MB`,
      'end',
    );    response.end('hello');
  })  .listen(8888);
  • 测试结果:
  • 现在我怀疑,不仅仅是字符串拼接的效率问题,更重要的是for循环的耗时不一致
var http = require('http');
http  .createServer(function(request, response) {
    console.log(request.url, 'url');
    let used = process.memoryUsage().heapUsed / 1024 / 1024;
    console.log(
      `The script uses approximately ${Math.round(used * 100) / 100} MB`,
      'start',
    );
    let num = 0;
    console.time('测试');
    for (let i = 1; i < 5900000000; i++) {
    //   num++;
    }
    console.timeEnd('测试');
    const arr = ['Hello'];
    // arr[1] = num;
    used = process.memoryUsage().heapUsed / 1024 / 1024;
    console.log(
      `The script uses approximately ${Math.round(used * 100) / 100} MB`,
      'end',
    );    response.end('hello');
  })  .listen(8888);
  • 测试运行结果:
  • for循环内部的i++其实就是变量不断的重新赋值覆盖
  • 经过我的测试发现,40亿次跟50亿次的区别,差距很大,40亿次的for循环,都是稳定的,但是50亿次就不稳定了.
  • Node.js的EventLoop:
60亿次for循环,原来这么多东西

 

  • 我们目前被阻塞的状态:
  • 我电脑的CPU使用情况

优化方案

  • 遇到了60亿次的循环,像有使用多进程异步计算的,但是本质上没有解决这部分循环代码的调用耗时。
  • 改变策略,拆解单次次数过大的for循环:
var http = require('http');
http  .createServer(function(request, response) {    console.log(request.url, 'url');
    let used = process.memoryUsage().heapUsed / 1024 / 1024;
    console.log(
      `The script uses approximately ${Math.round(used * 100) / 100} MB`,
      'start',
    );    let num = 0;
    console.time('测试');
    for (let i = 1; i < 600000; i++) {
      num++;      for (let j = 0; j < 10000; j++) {
        num++;      }    }    console.timeEnd('测试');
    const arr = ['Hello'];
    console.log(num, 'num');
    arr[1] = num;
    used = process.memoryUsage().heapUsed / 1024 / 1024;
    console.log(
      `The script uses approximately ${Math.round(used * 100) / 100} MB`,
      'end',
    );    response.end(arr.join(''));
  })  .listen(8888);
  • 结果,耗时基本稳定,60亿次循环总共:

推翻字符串的拼接耗时说法

  • 修改代码回最原始的+方式拼接字符串
var http = require('http');
http  .createServer(function(request, response) {    console.log(request.url, 'url');
    let used = process.memoryUsage().heapUsed / 1024 / 1024;
    console.log(
      `The script uses approximately ${Math.round(used * 100) / 100} MB`,
      'start',
    );    let num = 0;
    console.time('测试');
    for (let i = 1; i < 600000; i++) {
      num++;      for (let j = 0; j < 10000; j++) {
        num++;      }    }    console.timeEnd('测试');
    // const arr = ['Hello'];
    console.log(num, 'num');
    // arr[1] = num;
    used = process.memoryUsage().heapUsed / 1024 / 1024;
    console.log(
      `The script uses approximately ${Math.round(used * 100) / 100} MB`,
      'end',
    );    response.end(`Hello` + num);
  })  .listen(8888);
  • 测试结果稳定,符合预期:

总结:

  • 对于单次循环超过一定阀值次数的,用拆解方式,Node.js的运行耗时是稳定,但是如果是循环次数过多,那么就会出现刚才那种情况,阻塞严重,耗时不一样。
  • 为什么?

深度分析问题

  • 遍历60亿次,这个数字是有一些大了,如果是40亿次,是稳定的
  • 这里应该还是跟CPU有一些关系,因为top查看一直是在升高
  • 此处虽然不是真正意义上的内存泄漏,但是我们如果在一个循环中不仅要不断更新i的值到60亿,还要不断更新num的值60亿,内存使用会不断上升,最终出现两份60亿的数据,然后再回收。(因为GC自动垃圾回收,一样会阻塞主线程,多次接口调用后,CPU占用也会升高)
  • 使用for循环拆解后:
 for (let i = 1; i < 60000; i++) {
      num++;
      for (let j = 0; j < 100000; j++) {
        num++;
      }
    }
  • 只要num到60亿即可,解决了这个问题。

哪些场景会遇到这个类似的超大计算量问题:

  • 图片处理
  • 加解密

如果是异步的业务场景,也可以用多进程参与解决超大计算量问题,今天这里就不重复介绍了

作者: Peter 谭老师

转发链接:https://mp.weixin.qq.com/s/Vs8CwdGJ6JUyH_Kp0H5l4Q



Tags:for循环   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
详细说明及例子 :对一组文件中的每一个文件执行某个特定命令普通格式:FOR %variable IN (set) DO command [command-parameters] %variable 指定一个单一字母可替换的参数。 ...【详细内容】
2021-12-27  Tags: for循环  点击:(3)  评论:(0)  加入收藏
作者: Peter 谭老师转发链接:https://mp.weixin.qq.com/s/Vs8CwdGJ6JUyH_Kp0H5l4Q起因 有人在思否论坛上向我付费提问 当时觉得,这个人问的有问题吧。仔细一看,还是有点东西...【详细内容】
2020-09-25  Tags: for循环  点击:(104)  评论:(0)  加入收藏
我们讲解了很多种PHP数组操作的引用场景。今天我们接着分享这方面的知识,说一说对于多维数组,如何根据值搜索,并返回数组的索引值。 学习时间最开始我们习惯于跟随本能写代码,下...【详细内容】
2020-07-20  Tags: for循环  点击:(100)  评论:(0)  加入收藏
JavaScript 语言中的 for 循环用于多次执行代码块,它是 JavaScript 中最常用的一个循环工具,还可用于数组的遍历循环等。我们为什么要使用 for 循环呢?打个比方,例如我们想要...【详细内容】
2020-06-28  Tags: for循环  点击:(45)  评论:(0)  加入收藏
python中的for循环语句怎么写?Python for 循环语句Python for循环可以遍历任何序列的项目,如一个列表或者一个字符串。for循环的语法格式如下:12for iterating_var in sequenc...【详细内容】
2019-10-29  Tags: for循环  点击:(273)  评论:(0)  加入收藏
遍历数组通常用for循环ES5的话也可以使用forEach,ES5具有遍历数组功能的还有map、filter、some、every、reduce、reduceRight等,只不过他们的返回结果不一样。但是使用forea...【详细内容】
2019-08-28  Tags: for循环  点击:(237)  评论:(0)  加入收藏
今天为大家解读python中的for循环,深入探讨它们在底层是如何开展工作的,以及他们为什么会按照自己的方式工作,他们与别的语言中的for循环有什么不同等问题。 循环的问题我们将...【详细内容】
2019-08-21  Tags: for循环  点击:(222)  评论:(0)  加入收藏
▌简易百科推荐
摘 要 (OF作品展示)OF之前介绍了用python实现数据可视化、数据分析及一些小项目,但基本都是后端的知识。想要做一个好看的可视化大屏,我们还要学一些前端的知识(vue),网上有很多比...【详细内容】
2021-12-27  项目与数据管理    Tags:Vue   点击:(1)  评论:(0)  加入收藏
程序是如何被执行的&emsp;&emsp;程序是如何被执行的?许多开发者可能也没法回答这个问题,大多数人更注重的是如何编写程序,却不会太注意编写好的程序是如何被运行,这并不是一个好...【详细内容】
2021-12-23  IT学习日记    Tags:程序   点击:(9)  评论:(0)  加入收藏
阅读收获✔️1. 了解单点登录实现原理✔️2. 掌握快速使用xxl-sso接入单点登录功能一、早期的多系统登录解决方案 单系统登录解决方案的核心是cookie,cookie携带会话id在浏览器...【详细内容】
2021-12-23  程序yuan    Tags:单点登录(   点击:(8)  评论:(0)  加入收藏
下载Eclipse RCP IDE如果你电脑上还没有安装Eclipse,那么请到这里下载对应版本的软件进行安装。具体的安装步骤就不在这赘述了。创建第一个标准Eclipse RCP应用(总共分为六步)1...【详细内容】
2021-12-22  阿福ChrisYuan    Tags:RCP应用   点击:(7)  评论:(0)  加入收藏
今天想简单聊一聊 Token 的 Value Capture,就是币的价值问题。首先说明啊,这个话题包含的内容非常之光,Token 的经济学设计也可以包含诸多问题,所以几乎不可能把这个问题说的清...【详细内容】
2021-12-21  唐少华TSH    Tags:Token   点击:(9)  评论:(0)  加入收藏
实现效果:假如有10条数据,分组展示,默认在当前页面展示4个,点击换一批,从第5个开始继续展示,到最后一组,再重新返回到第一组 data() { return { qList: [], //处理后...【详细内容】
2021-12-17  Mason程    Tags:VUE   点击:(14)  评论:(0)  加入收藏
什么是性能调优?(what) 为什么需要性能调优?(why) 什么时候需要性能调优?(when) 什么地方需要性能调优?(where) 什么时候来进行性能调优?(who) 怎么样进行性能调优?(How) 硬件配...【详细内容】
2021-12-16  软件测试小p    Tags:性能调优   点击:(19)  评论:(0)  加入收藏
Tasker 是一款适用于 Android 设备的高级自动化应用,它可以通过脚本让重复性的操作自动运行,提高效率。 不知道从哪里听说的抖音 app 会导致 OLED 屏幕烧屏。于是就现学现卖,自...【详细内容】
2021-12-15  ITBang    Tags:抖音防烧屏   点击:(23)  评论:(0)  加入收藏
11 月 23 日,Rust Moderation Team(审核团队)在 GitHub 上发布了辞职公告,即刻生效。根据公告,审核团队集体辞职是为了抗议 Rust 核心团队(Core team)在执行社区行为准则和标准上...【详细内容】
2021-12-15  InfoQ    Tags:Rust   点击:(24)  评论:(0)  加入收藏
一个项目的大部分API,测试用例在参数和参数值等信息会有很多相似的地方。我们可以复制API,复制用例来快速生成,然后做细微调整既可以满足我们的测试需求1.复制API:在菜单发布单...【详细内容】
2021-12-14  AutoMeter    Tags:AutoMeter   点击:(20)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条