放弃 (2):代码按顺序执行

放弃 (2):代码按顺序执行

2025 南京大学《操作系统原理》
放弃 (2):代码按顺序执行

让我们来看看编译器

虚拟化:进程只需要看到自己和操作系统

  • Determinism: 除了系统调用,没人能 “干涉” 程序的状态

编译器:按照 “系统调用” 优化程序

  • 语句/指令不需要按程序声明的那样执行
    • 典型的优化:死代码消除 (dead code elimination)
  • 只要保证优化前后的程序在系统调用层面上等价,可以任意调换/删除语句
    • 但这和 non-determinism 是矛盾的
      • load 可能会读到来自其他线程写入的值
      • 如果依赖这个假设编程,编译器会教你做人的 😊
2025 南京大学《操作系统原理》
放弃 (2):代码按顺序执行

一个聪明的例子

while (!flag);
  • 这样就可以实现线程的等待了
  • “等另一个线程举起旗子,我再继续”?
2025 南京大学《操作系统原理》
放弃 (2):代码按顺序执行

一个聪明的例子

while (!flag);
  • 这样就可以实现线程的等待了
  • “等另一个线程举起旗子,我再继续”?

聪明,但不如编译器聪明 😭

2025 南京大学《操作系统原理》
放弃 (2):代码按顺序执行

求和 (再次出现)

#define N 100000000
long sum = 0;

void T_sum() { for (int i = 0; i < N; i++) sum++; }

int main() {
    create(T_sum);
    create(T_sum);
    join();
    printf("sum = %ld\n", sum);
}

如果添加编译优化?

  • -O1: 100000000 (nn) 😱😱
  • -O2: 200000000 (2n2n) 😱😱😱
2025 南京大学《操作系统原理》
放弃 (2):代码按顺序执行

编译器做了什么?

T_sum 的行为是对 sum 做 n 次自增

  • 等价的改写 1
    • t = load(sum);
    • while (n--) t++;
    • store(sum, t);
  • 等价的改写 2
    • t = load(sum);
    • store(sum, t + n);
  • 编译优化假设 determinism 是绝对必要的
    • 否则程序的性能就不能看了
2025 南京大学《操作系统原理》
放弃 (2):代码按顺序执行

控制编译器优化的行为 🌶️

方法 1:插入 “不可优化” 的代码块

while (!flag) {
    asm volatile ("" ::: "memory");
}

方法 2:标记变量 load/store 为不可优化

int volatile flag;
while (!flag);

以上都不是《操作系统》课推荐的方法

  • Don't play with shared memory.
2025 南京大学《操作系统原理》