我们拿一个算法的代码实现来举例子,首先我们写一个求阶乘的子函数,这里我偷懒让 ChatGPT 帮忙生成了一个:
// 阶乘函数
intfactorial_iterative(int n) {
int result = 1;
// 从1乘到n
for (int i = 1; i <= n; ++i) {
result *= i;
}
return result;
}
// 示例
int main() {
int result_iterative = factorial_iterative(5);
printf("5的阶乘是: %dn", result_iterative);
return 0;
}
这种简单的迭代算法的优点是比较容易理解,一眼就可以看出程序员想干什么。
但这样写出来的程序缺点也很大,就是运行效率非常低,我们在算法编写中最怕的就是for 循环,因为这里面会存在大量的比较和跳转,同时最容易产生一些代码被无效的循环执行。
这些缺点有的会被编译器的优化措施给规避掉,比如编译器可以把一些需要内存访问的变量先放到寄存器中,等计算完结果后,再把结果从寄存器中转移到内存中,因为 CPU 读取寄存器比读取内存可快多了。
但是编译器也不是万能的,有些优化他就做不到。比如,我们改成下面展开的样子,超标量的流水线就开始起作用了。
// 阶乘函数
intfactorial_iterative(int n) {
int result0 = 1, result1 = 1, result2 = 1,result3 = 1;
// 从1乘到n
for (int i = 1; i < n; i += 4) {
result0 *= i;
result1 *= i + 1;
result2 *= i + 2;
result3 *= i + 3;
}
return (result0 * result1 * result2 * result3);
}
首先,我们假设开启了编译器优化,编译器已经把所有内存访问的变量在函数开始都归置到了寄存器中,那么这时候我们可以看到,4 个 result 的乘法语句是相互独立的,他们的计算过程不依赖于其他 3 个语句的计算结果。
这就好比安排了四个人,给他们算 4 个单独的式子,假设他们计算能力相同,于是他们会在同一段时间后跑到黑板上来互相乘一下算个总的结果。
而如果我们只是简单的做循环展开,不增加新的寄存器变量,也就是不加人的情况下是怎么样的呢?
// 阶乘函数
intfactorial_iterative(int n) {
int result = 1;
// 从1乘到n
for (int i = 1; i < n; i += 4) {
result *= i;
result *= i + 1;
result *= i + 2;
result *= i + 3;
}
return (result * result * result * result);
}
这里只放了一个聪明的孩子做算式,不过你看他要做的 4 个算式,其中后一个算式总要用到前一个算式的结果,他即便再聪明也得一个一个的算。
这就是超标量流水线的用处,当然展开多少还需要我们自己衡量,本质上也是用空间换时间,另外寄存器可是稀缺资源。
-
处理器
+关注
关注
68文章
19404浏览量
230758 -
mcu
+关注
关注
146文章
17313浏览量
352218 -
代码
+关注
关注
30文章
4821浏览量
68893 -
编译器
+关注
关注
1文章
1642浏览量
49238
发布评论请先 登录
相关推荐
评论