文章目录
说明
- 测速方式一:测试20次并计时,删掉5次最快和最慢,剩余10次的平均值,即为耗时。
- 测速方式二:使用vs自带的性能计数器。
- 环境:vs2022 Enterprise (64 位) 版本 17.1.5、win10 专业版 (64位) 版本21H1。
- 模式:测速方式一为release、测速方式二为debug,两者都是64位。
取余运算很慢?
有两种取余方法:
int a = 0, b = 0;
std::cin >> a; // 输入999。
// 方法一:使用%运算符。
for (streamoff i = 0; i < 10000; i++) // 平均耗时:22670纳秒。
{
a = a % 10;
}
// 方法二:使用其他运算得出余数。
for (streamoff i = 0; i < 10000; i++) // 平均耗时:22670纳秒。
{
a = a - a / 10 * 10;
}
// 方法三:将方法二中的的除法运算独立出来。
b = a / 10;
for (streamoff i = 0; i < 10000; i++) // 平均耗时:1100纳秒。
{
a = a - b * 10;
}
总结:
- 减、乘、除三次运算的耗时和一次取余运算相同,可见取余远慢于减、乘、除。
- 方法二中,除法运算最耗时,若之前正好已经得到
a / 10
的值,即无需进行除法运算,则快20倍以上。
提前声明for中用于循环的变量,会更快?
// 方法一,提前声明。
long a;
cin >> a; // 输入9。
streamoff i, j, k;
for (i = 0; i < 100; i++) // 平均耗时:534070纳秒。
{
for (j = 0; j < 100; j++)
{
for (k = 0; k < 100; k++)
{
if (a > 2)
a = a + 2;
else
a = a - 2;
}
}
}
// 方法二:在for中声明。
long a;
cin >> a; // 输入9。
for (streamoff i = 0; i < 100; i++) // 平均耗时:532840纳秒。
{
for (streamoff j = 0; j < 100; j++)
{
for (streamoff k = 0; k < 100; k++)
{
if (a > 2)
a = a + 2;
else
a = a - 2;
}
}
}
实验中耗时差距不明显,于是将三个for中的条件都改为I<1000,实验结果分别为:
- 方法一平均耗时595072650纳秒。
- 方法二平均耗时594629127纳秒。
总结:
- 完全不用担心
for
中变量反复创建带来的消耗。而且,从栈的使用方式来看,事先声明反而更慢也是必然情况。
ifstream+ofstream和ctrl+c/v哪个读写速度更快?
分别读取和写入个22个文件,共9.5GB。
string p[22];
streamoff len[22];
char* n[22];
for (streamoff i = 1; i <= 22; i++)
{
p[i - 1] = "D:\\cfg\\" + to_string(i) + ".mp4";
}
// 读取。
for (streamoff i = 0; i <= 21; i++)
{
ifstream f(p[i], ios::binary);
f.seekg(0, ios_base::end);
len[i] = f.tellg();
f.seekg(ios_base::beg);
n[i] = new char[len[i]];
f.read(n[i], len[i]);
f.close();
}
// 写入。
for (streamoff i = 0; i <= 21; i++)
{
ofstream fout("C:\\cfg\\" + to_string(i) + ".mp4", ios::binary);
fout.write(n[i], len[i]);
fout.close();
}
读取22个文件,耗时79秒,速度约120MB/s。
写入22个文件,共耗时90秒,速度约105MB/s。
自己掐表计了个时,跟操作系统的crtl+c/v
,速度一样。
注意:在win10系统中,内存足够时,程序退出后操作系统会保留内存中的数据,这将导致,第二次运行此代码只需极短的时间就能完成22个文件的读取,我测试时,第二次读取仅7秒。在任务管理器的内存界面,可以看到一块标注为“备用”的区域,这就是读取的文件数据。
MoveMemory的速度
分别测试三种情况下移动内存中的数据。
// 方法一:有重叠的情况下,使用MoveMemory。
// 平均耗时:642,996,430纳秒。
char* a = new char[100];
for (streamoff i = 0; i < 100000000; i++)
{
MoveMemory(a, a+10, 20);
}
// 方法二:无重叠的情况下,使用MoveMemory。
// 平均耗时:28,249,6010纳秒。
char* a = new char[100];
for (streamoff i = 0; i < 100000000; i++)
{
MoveMemory(a, a+50, 20);
}
// 方法三:使用for来代替MoveMemory。
// 平均耗时:2347,981,590纳秒。
char* a = new char[100];
for (streamoff i = 0; i < 100000000; i++)
{
for (size_t i = 0; i < 20; i++)
{
*(a + i) = *(a + i + 10);
}
}
可见MoveMemory
的效率还是很高的,而且会分辨内存区是否重叠,用不同的方式移动内存,没有重叠的情况下,速度要快两倍多。
>
和>=
的速度对比
long a, b;
cin >> a;
cin >> b;
// 大于等于的速度。
for (streamoff i = 0; i < 100000000; i++)
{
if (a >= b) // 性能计数器:403毫秒。
a++;
else
b++;
}
// 大于的速度。
for (streamoff i = 0; i < 100000000; i++)
{
if (a > b) // 性能计数器:402毫秒。
a++;
else
b++;
}
总结:
- 虽然在汇编知识中提到会把大于等于转为大于,但测试下来速度差不多。
数组(栈区)和申请内存(堆区)速度比较
[]
符号创建的数组存放在栈区,new
关键字申请的内存则在堆区。哪个快呢?
// 对比一。
char* a = new char[20];
char b[20] = { 0 };
for (streamoff i = 0; i < 100000000; i++)
{
a[0] = 3; // 性能计数器:467毫秒。
a[2] = 4; // 性能计数器:462毫秒。
a[4] = a[0] + a[2]; // 性能计数器:1552毫秒。
}
for (streamoff i = 0; i < 100000000; i++)
{
b[0] = 3; // 性能计数器:458毫秒。
b[2] = 4; // 性能计数器:223毫秒。
b[4] = b[0] + b[2]; // 性能计数器:1160毫秒。
}
总结:
- 很明显,数组容易被寄存器命中,从而大幅增加速度。若不被命中,栈区和堆区速度差不多。
以空间换速度来加速除法
除法是四则运算中最慢的,我想加快它。
char QUICK_DIV10[16];
for (char i = 0; i < 127; i++)
{
QUICK_DIV10[i] = QUICK_DIV10[i] / 10;
}
char a[5] = { 0 };
for (streamoff i = 0; i < 100000000; i++)
{
a[0] = 3;
a[4] = a[0] / 10; // 性能计数器:3263毫秒。
a[4] = QUICK_DIV10[a[0]]; // 性能计数器:934毫秒。
}
总结:
- 这和以前学习oracle11g数据库时发现的增加速度方法差不多,就是用空间换速度。
- 尝试了不同运算符和不同复杂度的运算,发现从数组中获取内容也是有较大消耗,有时会导致耗时更多,得不偿失。
- 还尝试了将
QUICK_DIV10
从char
变为unsigned char
,耗时降低到了917毫秒。