New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
第 120 题:为什么for循环嵌套顺序会影响性能? #235
Comments
为什么 |
循环次数少的放在外层,减少内层变量的操作(初始化,比较,自增等)次数吧。 |
应该是第一个时间少一点,比如,按照每次循环判断来说 (初始化 自增次数类似) 1、i会循环100次,判断i<100 100次 2、i会循环10000次,判断i<100 10000次 虽然判断k<100的次数都是一样的 但是前面两种判断就不一样了,由此可以看见时间长短。 |
两个循环的次数的是一样的,但是 j 与 k 的初始化次数是不一样的
所以相同循环次数,外层越大,越影响性能 |
这里说说我个人的理解:对于这个例子,接下来我们利用三个例子来推算
首先我们单独来比较最内层的循环,也就是循环 10000 次和 100 次的时长 var t1 = new Date().getTime()
for (let k = 0; k < 10000; k++) {
}
var t2 = new Date().getTime()
console.log('first time', t2 - t1)
for (let k = 0; k < 100; k++) {
}
var t3 = new Date().getTime()
console.log('two time', t3 - t2)
单独循环 10000 和 100 的时间是相差无几的,基本可以看成一个等量关系。那么差异肯定是在外层嵌套的两个循环了。 我们再来看看另外一个例子:两层嵌套遍历,外层循环次数是 1000,里层循环次数一个是 10000,另一个是 100 var t1 = new Date().getTime()
for (let j = 0; j < 1000; j++) {
for (let k = 0; k < 10000; k++) {
}
}
var t2 = new Date().getTime()
console.log('first time', t2 - t1)
for (let j = 0; j < 1000; j++) {
for (let k = 0; k < 100; k++) {
}
}
var t3 = new Date().getTime()
console.log('two time', t3 - t2)
差异就出来了,明显看到循环差异在 2-4 ms,可以看到当外层循环的次数一样是,循环的时长取决于里层循环,里层循环次数越多,执行时间越长。 我们再来看看这样一个例子:两层嵌套循环,里层的循环次数都是 1000,外层循环次数一个是 10000,一个是 1000 var t1 = new Date().getTime()
for (let j = 0; j < 10000; j++) {
for (let k = 0; k < 1000; k++) {
}
}
var t2 = new Date().getTime()
console.log('first time', t2 - t1)
for (let j = 0; j < 100; j++) {
for (let k = 0; k < 1000; k++) {
}
}
var t3 = new Date().getTime()
console.log('two time', t3 - t2)
差异就出来了,明显看到循环差异在 2-4 ms,可以看到当内层循环的次数一样是,循环的时长取决于外层循环,外层循环次数越多,执行时间越长。 ====== 以上结论都可以根据算法的时间复杂度计算规则得到 ===================================== |
有没有大佬讲讲for循环嵌套循环相同循环次数,外层越大,越影响性能的原理是什么,比如V8引擎编译机智?或者什么因素的影响,挺好奇的 |
坐等大佬 |
第二循环 变量 | 实例化(次数) | 初始化(次数) | 比较(次数) | 自增(次数) 主要差别是外层两个变量初始化,比较,自增次数全部减少 |
但是我觉得按照这个说法好像不能很好地解释题目三层嵌套的情况 |
从数学的角度分析,已知for循环执行顺序,1:执行变量(仅执行一次)2:执行条件 3:执行代码块区域 4:最后执行++,当执行到第三步时,发现有一个for循环,程序会先执行完内部所有循环 ,之后返回到外部循环。设单次循环执行步骤2和步骤4的时间为T0,内层循环执行代码块区域的时间为T1,内层循环执行的次数为M ,中间层循环执行的次数为N,外层循环执行的次数为X,那么内层循环执行的总次数为MNX,中间层为MX,外层为X,所以循环的总次数为MNX+NX+X,总时间T是执行循环步骤的总时间加上执行代码区域的总时间,即T=(MNX+NX+X)T0+MNXT1,这里代码不变时,T0和T1不变, 总次数MNX不变,(NX+X)越小,所耗费的时间也会越少。 |
你说的两个循环的次数的是一样的,可是我打印出来的a,b的数值是不一样的,这是为什么呢? |
因为你这累加不对:
前两个累加去掉才一样,是 6000 |
(一)var a = 1000100100 = 100 + 1001000 + 100100010000; |
应该不是吧, let是每次循环都会声明一个新的 i 、j 、 K ,所以
|
每一次for循环只会初始化一次而已。后面的循环是不会初始化。 |
|
噢噢,let 每次循环都会初始化,var 是每个for循环只初始化一次 |
我测试了一下 使用var和let的区别,为什么var比let性能差那么多呀?有大佬知道吗? |
内循环多的话,在跳出内部循环的之前,会动态记录和调整转移指,这部分工作需要消耗一定的时间,第二个 内循环跳出的次数多,导致耗时较多。 |
for循环中使用let,js引擎会为每一次循环初始化一个独立作用域和变量。 |
我测试了很多次,发现每次的结果都不尽相同。但是当循环次数到达亿级的时候试了很多次输出的结果没有嵌套的比嵌套的使用时间更长。有大佬知道为什么吗? |
学习了,for的嵌套顺序不同,原来是因为每个值初始化的次数不一样,学习了 |
同问为什么 var 和 let 时间差这么多,一直以为 var 用在循环里面会比较好,哪位大佬来解惑🐰🐰🐰 var t1 = new Date().getTime()
for (var i = 0; i < 100; i++) {
for (var j = 0; j < 1000; j++) {
for (var k = 0; k < 10000; k++) {
}
}
}
var t2 = new Date().getTime()
console.log('first time', t2 - t1)
// first time 1884 var t1 = new Date().getTime()
for (let i = 0; i < 100; i++) {
for (let j = 0; j < 1000; j++) {
for (let k = 0; k < 10000; k++) {
}
}
}
var t2 = new Date().getTime()
console.log('first time', t2 - t1)
// first time 470 var t2 = new Date().getTime()
for (var i = 0; i < 10000; i++) {
for (var j = 0; j < 1000; j++) {
for (var k = 0; k < 100; k++) {
}
}
}
var t3 = new Date().getTime()
console.log('two time', t3 - t2)
// two time 1939 var t2 = new Date().getTime()
for (let i = 0; i < 10000; i++) {
for (let j = 0; j < 1000; j++) {
for (let k = 0; k < 100; k++) {
}
}
}
var t3 = new Date().getTime()
console.log('two time', t3 - t2)
// two time 358 |
限定条件, 变量初始化,赋值都为1次
i 自增: 100, 比较 100, 赋值 1, 初始化 1
a自增: 10000, 比较 10000, 赋值 1, 初始化 1 上面可以看出, 第一种 i,j 自增和初始化明显小雨第二种, 随着循环次数增加,相差也就更多 |
复杂度成指数增长,太可怕了 |
查找栈中数据的位置所需要时间不同,最外层数值越大,所花的时间越多 |
var t1 = new Date().getTime()
for (let i = 0; i < 100; i++) {
for (let j = 0; j < 1000; j++) {
for (let k = 0; k < 10000; k++) {
}
}
}
var t2 = new Date().getTime()
console.log('first time', t2 - t1)
for (let i = 0; i < 10000; i++) {
for (let j = 0; j < 1000; j++) {
for (let k = 0; k < 100; k++) {
}
}
}
var t3 = new Date().getTime()
console.log('two time', t3 - t2) |
我的想法是: |
猜的: 尽管总步数是一致的,词法环境创建的次数也一致,但是每一个词法环境是要维系外层调用关系的(闭包),这导致第二个例子中关系链路更长,分配内存/内部引用重建销毁的成本更高。 可以想象一下,如果让你走同样的步数,要求每走一步都要记住脚印的位置,第一个方案相当于走一步报告一次,而第二个方案相当于走三步报告一次。 |
CPU 干同样一件事情,流水线越长越难优化 |
从汇编执行的指令周期角度考虑
c语言#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int test1(){
clock_t begin,end;
begin = clock();
for(int i=0;i<100;i++){
for(int j=0;j<1000;j++){
for(int k=0;k<10000;k++){
}
}
}
// end time
end = clock();
double cost = (double)(end - begin)/CLOCKS_PER_SEC;
printf("the running time is %lf secs\n", cost);
}
int main (){
test1();
return 1;
} 上面的 test1 c 对应的汇编 117e: c7 45 dc 00 00 00 00 movl $0x0,-0x24(%rbp) i=0
1185: eb 2d jmp 11b4 <test1+0x4b>
1187: c7 45 e0 00 00 00 00 movl $0x0,-0x20(%rbp) j=0
118e: eb 17 jmp 11a7 <test1+0x3e>
1190: c7 45 e4 00 00 00 00 movl $0x0,-0x1c(%rbp) k=0
1197: eb 04 jmp 119d <test1+0x34>
1199: 83 45 e4 01 addl $0x1,-0x1c(%rbp) k++
119d: 83 7d e4 63 cmpl $0x63,-0x1c(%rbp) k-99
11a1: 7e f6 jle 1199 <test1+0x30> k-99<=0 跳 1199
11a3: 83 45 e0 01 addl $0x1,-0x20(%rbp) j++
11a7: 81 7d e0 e7 03 00 00 cmpl $0x3e7,-0x20(%rbp) j-999
11ae: 7e e0 jle 1190 <test1+0x27> j-999<=0 跳 1190
11b0: 83 45 dc 01 addl $0x1,-0x24(%rbp) i++
11b4: 81 7d dc 0f 27 00 00 cmpl $0x270f,-0x24(%rbp) i-9999
11bb: 7e ca jle 1187 <test1+0x1e> i-9999<=0 条 1187 c语言中,也确实
|
您好,我是胡豪,您的邮件我已收到,我会尽快给您回复的~
|
你好,邮件已收到,我现在未上线,一会儿回复你。
|
`
` |
The text was updated successfully, but these errors were encountered: