编译器获取您的代码,将其拆分为非常简单的指令,然后以它认为最佳的方式重新组合和排列它们。
The code
int i = 1;
int x = ++i + ++i;
由以下指令组成:
1. store 1 in i
2. read i as tmp1
3. add 1 to tmp1
4. store tmp1 in i
5. read i as tmp2
6. read i as tmp3
7. add 1 to tmp3
8. store tmp3 in i
9. read i as tmp4
10. add tmp2 and tmp4, as tmp5
11. store tmp5 in x
但是,尽管这是我编写的编号列表,但只有少数排序依赖关系这里: 1->2->3->4->5->10->11 和 1->6->7->8->9->10->11 必须保持其相对顺序。除此之外,编译器可以自由地重新排序,也许还可以消除冗余。
例如,您可以按如下方式对列表进行排序:
1. store 1 in i
2. read i as tmp1
6. read i as tmp3
3. add 1 to tmp1
7. add 1 to tmp3
4. store tmp1 in i
8. store tmp3 in i
5. read i as tmp2
9. read i as tmp4
10. add tmp2 and tmp4, as tmp5
11. store tmp5 in x
为什么编译器可以这样做呢?因为增量的副作用没有排序。但现在编译器可以简化:例如,4 中有一个死存储:该值立即被覆盖。另外,tmp2 和 tmp4 实际上是同一件事。
1. store 1 in i
2. read i as tmp1
6. read i as tmp3
3. add 1 to tmp1
7. add 1 to tmp3
8. store tmp3 in i
5. read i as tmp2
10. add tmp2 and tmp2, as tmp5
11. store tmp5 in x
现在与 tmp1 相关的所有内容都是死代码:它从未被使用过。并且 i 的重读也可以被消除:
1. store 1 in i
6. read i as tmp3
7. add 1 to tmp3
8. store tmp3 in i
10. add tmp3 and tmp3, as tmp5
11. store tmp5 in x
看,这段代码短得多。优化器很高兴。程序员不是,因为 i 只增加了一次。哎呀。
让我们看看编译器可以做的其他事情:让我们回到原始版本。
1. store 1 in i
2. read i as tmp1
3. add 1 to tmp1
4. store tmp1 in i
5. read i as tmp2
6. read i as tmp3
7. add 1 to tmp3
8. store tmp3 in i
9. read i as tmp4
10. add tmp2 and tmp4, as tmp5
11. store tmp5 in x
编译器可以像这样重新排序:
1. store 1 in i
2. read i as tmp1
3. add 1 to tmp1
4. store tmp1 in i
6. read i as tmp3
7. add 1 to tmp3
8. store tmp3 in i
5. read i as tmp2
9. read i as tmp4
10. add tmp2 and tmp4, as tmp5
11. store tmp5 in x
然后再次注意到 i 被读取了两次,因此消除其中之一:
1. store 1 in i
2. read i as tmp1
3. add 1 to tmp1
4. store tmp1 in i
6. read i as tmp3
7. add 1 to tmp3
8. store tmp3 in i
5. read i as tmp2
10. add tmp2 and tmp2, as tmp5
11. store tmp5 in x
这很好,但它可以更进一步:它可以重用 tmp1:
1. store 1 in i
2. read i as tmp1
3. add 1 to tmp1
4. store tmp1 in i
6. read i as tmp1
7. add 1 to tmp1
8. store tmp1 in i
5. read i as tmp2
10. add tmp2 and tmp2, as tmp5
11. store tmp5 in x
那么就可以消除6中i的重读:
1. store 1 in i
2. read i as tmp1
3. add 1 to tmp1
4. store tmp1 in i
7. add 1 to tmp1
8. store tmp1 in i
5. read i as tmp2
10. add tmp2 and tmp2, as tmp5
11. store tmp5 in x
现在 4 是一个死店:
1. store 1 in i
2. read i as tmp1
3. add 1 to tmp1
7. add 1 to tmp1
8. store tmp1 in i
5. read i as tmp2
10. add tmp2 and tmp2, as tmp5
11. store tmp5 in x
现在 3 和 7 可以合并为一条指令:
1. store 1 in i
2. read i as tmp1
3+7. add 2 to tmp1
8. store tmp1 in i
5. read i as tmp2
10. add tmp2 and tmp2, as tmp5
11. store tmp5 in x
删除最后一个临时的:
1. store 1 in i
2. read i as tmp1
3+7. add 2 to tmp1
8. store tmp1 in i
10. add tmp1 and tmp1, as tmp5
11. store tmp5 in x
现在您就得到了 Visual C++ 给出的结果。
请注意,在两个优化路径中,只要指令没有因不执行任何操作而被删除,就保留了重要的顺序依赖性。