Вы когда-нибудь слышали о Движении с инвариантным кодом цикла ?
Это этап оптимизации от компилятора, который перемещает код из тела циклов, когда это возможно.
Например, с помощью следующего кода:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv) {
for (int i = 0; i < argc; ++i) {
if (argc < 100) {
printf("%d\n", atoi(argv[1]));
}
}
}
Clang генерирует следующий IR:
define i32 @main(i32 %argc, i8** nocapture %argv) nounwind {
%1 = icmp sgt i32 %argc, 0
br i1 %1, label %.lr.ph, label %._crit_edge
.lr.ph: ; preds = %0
%2 = icmp slt i32 %argc, 100
%3 = getelementptr inbounds i8** %argv, i64 1
br i1 %2, label %4, label %._crit_edge
; <label>:4 ; preds = %4, %.lr.ph
%i.01.us = phi i32 [ %9, %4 ], [ 0, %.lr.ph ]
%5 = load i8** %3, align 8, !tbaa !0
%6 = tail call i64 @strtol(i8* nocapture %5, i8** null, i32 10) nounwind
%7 = trunc i64 %6 to i32
%8 = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([4 x i8]* @.str, i64 0, i64 0), i32 %7) nounwind
%9 = add nsw i32 %i.01.us, 1
%exitcond = icmp eq i32 %9, %argc
br i1 %exitcond, label %._crit_edge, label %4
._crit_edge: ; preds = %4, %.lr.ph, %0
ret i32 0
}
Что можно перевести обратно на C:
int main(int argc, char** argv) {
if (argc == 0) { return 0; }
if (argc >= 100) { return 0; }
for (int i = 0; i < argc; ++i) {
printf("%d\n", atoi(argv[1]));
}
return 0;
}
Вывод: не беспокойтесь о микрооптимизациях, если профилировщик не обнаружит, что они не такие микро, как вы думали.
EDIT:
Правка радикально изменила вопрос (боже, я ненавижу это: р) LICM больше не применяется, и эти две функции имеют разные функции.
Вывод, однако, остается идентичным. Одна проверка if
в цикле for
не меняет фундаментальную сложность вашего кода (помните, что условие цикла также проверяется на каждой итерации ...).