为什么“简化”的代码无法向量化?

12

我实现了以下将32个字节的输入转换为大写的代码:

版本1:

void to_upper(char* input) {
    for (int i = 0; i < 32; ++i) { 
        input[i] = (input[i] >= 'a' && input[i] <= 'z') ? input[i] - 32 : input[i]; 
    }
}

版本 2:

void to_upper(char* input) {
    for (int i = 0; i < 32; ++i) { 
        if (input[i] >= 'a' && input[i] <= 'z') {
            input[i] = input[i] - 32; // same for: input[i] -= 32;
        }
    }
}

第一个版本可以自动向量化,第二个版本则不能。这种行为在clang和gcc中是一致的。此外,我也用Rust实现了这两个版本,而且Rust编译器也是这样做的,即第一个版本被自动向量化,第二个版本则没有。

什么因素限制了编译器对第二个版本进行向量化?


3
由于条件赋值的存在,第二个版本存在问题。编译器不允许自行发明赋值语句。 - rustyx
3
听起来很合理,但我对这个解释有两个问题。1)由于编译器所做的所有转换,一个像“input[i] = input[i]”这样的平凡赋值应该是允许的。 2)即使在版本2中添加了一个else分支,我只是调用“else { input[i] = input[i]; }”,向量化仍然不会发生。 - David Frank
版本1在编译成汇编时有效地无分支,编译器应该应用条件移动,而第二个则需要条件分支和跳转。 - Mansoor
你使用了哪些标志?你尝试过使用“-O3”吗? - Mitch
1
从这个例子:https://godbolt.org/z/bzzE7a 看来,添加 input[i] = input[i] 被优化掉了,但仍然导致条件跳转。这个跳转可能是它无法向量化的原因。 - Artyer
1个回答

5
基本上,优化处理在第二个循环中更难识别条件赋值。在第一种情况下,GCC生成了略微不同的中间表示形式,使if-conversion处理能够将代码转换为可向量化形式。
  <bb 3>:
  # i_18 = PHI <i_14(4), 0(2)>
  # ivtmp_24 = PHI <ivtmp_21(4), 32(2)>
  _6 = (sizetype) i_18;
  _7 = input_5(D) + _6;
  _8 = *_7;
  _9 = (unsigned char) _8;
  _10 = _9 + 159;
  _11 = _9 + 224;
  iftmp.0_12 = (char) _11;
  iftmp.0_2 = _10 <= 25 ? iftmp.0_12 : _8;
  *_7 = iftmp.0_2;
  i_14 = i_18 + 1;
  ivtmp_21 = ivtmp_24 - 1;
  if (ivtmp_21 != 0)
    goto <bb 4>;
  else
    goto <bb 5>;

而在第二种情况下,代码包含了不必要的跳转,这会使分析变得复杂并破坏向量化。

  <bb 3>:
  # i_15 = PHI <i_14(6), 0(2)>
  # ivtmp_26 = PHI <ivtmp_25(6), 32(2)>
  _5 = (sizetype) i_15;
  _7 = input_6(D) + _5;
  _8 = *_7;
  _9 = (unsigned char) _8;
  _10 = _9 + 159;
  if (_10 <= 25)
    goto <bb 4>;
  else
    goto <bb 5>;

  <bb 4>:
  _11 = _9 + 224;
  _12 = (char) _11;
  *_7 = _12;

  <bb 5>:
  i_14 = i_15 + 1;
  ivtmp_25 = ivtmp_26 - 1;
  if (ivtmp_25 != 0)
    goto <bb 6>;
  else
    goto <bb 7>;

许多优化过程作为模式匹配器工作,识别和优化常见情况,所以我对这种行为不感到惊讶。您可以尝试在GCC跟踪器中提交错误报告。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接