引言:有位朋友问了一下为什么在for循环中一般i的自增使用 ++i 而不是 i++ ,下列是本次关于这个问题引发的一系列讨论。如有错误欢迎指正。
1 为什么选择++i
众所周知,由于i++ 需要在自增前先保存旧值,所以cpu在执行i++ 时需要先保存i 的旧值在一个临时存储空间,然后再对i 进自增操作。而++i 不需要保存旧值,直接对i 进行自增后将自增后的值进行返回。 由此可知,i++ 在自增时会多一步保存旧值的操作,这导致了运行效率会比++i 稍低。 但在现代编译器中,对c++代码进行编译的时候当发现i++ 的返回值并没有被使用时,编译器会把i++ 优化成++i 。所以如果使用的是c++11以后的标准的话,写成++i 或i++ 都是可以的,就看个人习惯了。 不过在查找资料的过程中我看到了一句话:(出自Quora)
Compiler optimization may save you, but may not be always the case. 编译器优化并不总是能按照你理想的方式去帮助你。
共勉。
2 对于i++和++i的一些测试
对于上述的理论,很自然的验证想法有两个思路,第一个思路是反编译程序后获得汇编代码,然后看在编译器级别是如何进行了代码优化;第二个思路是拿数据进行实际测试。当然由于本人看不懂汇编代码,ida反编译出来的代码顺序我也不是很懂,所以进行实际测试。由于测试存在一定的偶然性和系统误差,如果测出不同的结果欢迎在评论区指正。
2.1 在现代编译器中测试
测试环境:
c++标准:c++11 IDE:Clion(学生优惠) 操作系统:Windows10 CPU:i5-7200U
测试思路:
下面会列举出五个测试函数 第一个测试for循环中++i的执行时间 第二个测试for循环中i++的执行时间 第三个,由于编译器优化方式是检测到i++的返回值并没有被使用,所以将i++优化成了++i,那么我们主动的去使用i++的返回值,这样可以做到阻止编译器优化。所以第三个测试的是阻止编译器优化的i++执行。 第四个作为第三个的对照组,因为第三个测试中多了一步赋值操作,所以在第四个测试中测试多了赋值操作的++i执行时间。 第五个测试for循环中i=i+1的执行时间。
测试规模:
for循环执行3000000000次(即30亿次) 使用long long 类型进行测试
测试代码:
#include <iostream>
#include "bits/stdc++.h"
#include<windows.h>
#include <ctime>
#define MAX 3000000000
void testAddBeforeI();
void testAddAfterI();
void testTemp();
void testTemp2();
void test3();
int main(){
testAddBeforeI();
testAddAfterI();
testTemp();
testTemp2();
test3();
return 0;
}
void testAddBeforeI(){
std::cout << "++i" << std::endl;
long start_time = GetTickCount();
for(long long i= 0; i < MAX; ++i){
}
long end_time = GetTickCount();
std::cout<< "花费时间:"<< end_time - start_time << std::endl;
}
void testAddAfterI(){
std::cout << "i++" << std::endl;
long long temp;
long start_time = GetTickCount();
for(long long i= 0; i < MAX;i++){
}
long end_time = GetTickCount();
std::cout<< "花费时间:"<< end_time - start_time << std::endl;
}
// 阻止编译器优化
void testTemp(){
std::cout << "temp = i++" << std::endl;
long long temp;
long start_time = GetTickCount();
for(long long i= 0; i < MAX; temp = i++){
}
long end_time = GetTickCount();
std::cout<< "花费时间:"<< end_time - start_time << std::endl;
}
void testTemp2(){
std::cout << "temp = ++i" << std::endl;
long long temp;
long start_time = GetTickCount();
for(long long i= 0; i < MAX; temp = ++i){
}
long end_time = GetTickCount();
std::cout<< "花费时间:"<< end_time - start_time << std::endl;
}
void test3(){
std::cout << "i +=i" << std::endl;
long start_time = GetTickCount();
for(long long i= 0; i < MAX; i += 1){
}
long end_time = GetTickCount();
std::cout<< "花费时间:"<< end_time - start_time << std::endl;
}
测试结果:
从图片中可以清晰的看到++i 和 i++ 的执行时间差距不大。i +=1则花费时间稍多。但我个人感觉在误差范围内可以认为三者的执行效率接近。 而temp= i++的情况则稍微比i++执行时间稍长,这在预测范围之内。 但让我不理解的是为什么temp= ++i的执行时间比其他的方式执行效率低得多?经过多次测试基本上可以确定这确实在无法范围以外了。这个问题我无法解决,如果有人能解答我不胜感激。
2.2新的问题
上面的代码是执行了空的for循环进行测试,但笔者在测试的使用尝试将 i 进行累加求和的时候发现了新的问题。
测试环境同2.1 测试思路
将原本的空for换成sum += i 进行累加求和运算。
测试代码:
#include <iostream>
#include "bits/stdc++.h"
#include<windows.h>
#include <ctime>
#define MAX 3000000000
void testAddBeforeI();
void testAddAfterI();
void testTemp();
void testTemp2();
void test3();
int main(){
testAddBeforeI();
testAddAfterI();
testTemp();
testTemp2();
test3();
return 0;
}
void testAddBeforeI(){
std::cout << "++i" << std::endl;
long long sum = 0;
long start_time = GetTickCount();
for(long long i= 0; i < MAX; ++i){
sum += i;
}
long end_time = GetTickCount();
std::cout<< "花费时间:"<< end_time - start_time << std::endl;
std::cout << "求和"<< sum <<std::endl;
}
void testAddAfterI(){
std::cout << "i++" << std::endl;
long long sum = 0;
long start_time = GetTickCount();
for(long long i= 0; i < MAX;i++){
sum += i;
}
long end_time = GetTickCount();
std::cout<< "花费时间:"<< end_time - start_time << std::endl;
std::cout << "求和"<< sum <<std::endl;
}
void testTemp(){
std::cout << "temp = i++" << std::endl;
long long temp;
long long sum = 0;
long start_time = GetTickCount();
for(long long i= 0; i < MAX; temp = i++){
sum += i;
}
long end_time = GetTickCount();
std::cout<< "花费时间:"<< end_time - start_time << std::endl;
std::cout << "求和"<< sum <<std::endl;
}
void testTemp2(){
std::cout << "temp = ++i" << std::endl;
long long temp;
long long sum = 0;
long start_time = GetTickCount();
for(long long i= 0; i < MAX; temp = ++i){
sum += i;
}
long end_time = GetTickCount();
std::cout<< "花费时间:"<< end_time - start_time << std::endl;
std::cout << "求和"<< sum <<std::endl;
}
void test3(){
std::cout << "i +=i" << std::endl;
long start_time = GetTickCount();
long long sum = 0;
for(long long i= 0; i < MAX; i += 1){
sum += i;
}
long end_time = GetTickCount();
std::cout<< "花费时间:"<< end_time - start_time << std::endl;
std::cout << "求和"<< sum <<std::endl;
}
测试结果:
可以从图中看出首先所有求和都是正确的。 其次能明显发现第一组、第二组、第四组和第五组的执行时间几乎没有区别,但令人疑惑的是第三组测试数据却比其他组数据快了非常多。这令我非常不解,也无法给出一个合理的猜想。如果有人能解释,本人不尽感激。
3 总结
在此次for循环中i++ 和++i 的执行效率的探索中我学会了很多新的知识,在解决问题的过程中我也在不断提出新的猜想,然后重复验证。虽然我也没有办法解决所有的问题,这证明了我的知识储备还远远不够。 学习新的知识是令人激动和快乐的,和朋友的不断讨论、逐步逼近答案的过程也令人热血沸腾。欢迎指正和友善的讨论。 感谢流浪工作室的@刘奇翰提出的问题。在和他讨论的过程中也让我认识到了许多不足和欠缺的知识。 不断进步。
|