Короткий ответ «зависит». Это зависит от множества вещей, включая сложность кода, используемый компилятор и т. Д.
В общем, постоянное распространение (другими словами, «преобразование чего-то, переданного в функцию как константу, в константу сам »для компиляторов не очень сложно. Clang/LLVM делает это очень рано во время компиляции, имея отдельные классы для« значений, которые мы знаем, являются константами »и« значения, которые мы не знаем значения »при создании LLVM- IR («промежуточное представление», слой кода, построенный из исходного кода, который не представляет собой действительный машинный код). Другие компиляторы также будут иметь похожие конструкции, как с помощью IR, так и константами отслеживания, отдельными от непостоянных значений .
Итак, предполагая, что компилятор может «следовать» «код (например, если f
и вызов f
находятся в разных исходных файлах, вряд ли он будет оптимизирован).
Конечно, если вы хотите быть уверенным, что ваш конкретный компилятор делает с ВАШИМ конкретным кодом, вам нужно будет проверить код, сгенерированный компилятором.
// Class definition
class myclass
{
public:
constexpr myclass() noexcept: _value{0}, _option{true} {}
constexpr myclass(int value) noexcept: _value{value}, _option{true} {}
constexpr myclass(int value, bool option) noexcept: _value{value}, _option{option} {}
constexpr int get_value() const noexcept {return _value;}
constexpr int get_option() const noexcept {return _option;}
private:
int _value;
bool _option;
};
// Some function that should be super-optimized
int f(myclass x, myclass y)
{
if (x.get_option() && y.get_option()) {
return x.get_value() + y.get_value();
} else {
return x.get_value() * y.get_value();
}
}
int main()
{
myclass a;
myclass b(1);
myclass c(2, false);
int x = f(a, b);
int y = f(b, c);
return x + y;
}
Это будет генерировать код, который идентичен:
int main()
{
return 3;
}
Однако, если мы изменим код для этого:
#include "myclass.h"
extern int f(myclass x, myclass y);
int main()
{
myclass a;
myclass b(1);
myclass c(2, false);
int x = f(a, b);
int y = f(b, c);
return x + y;
}
и объявить f
в отдельном файле (с -O2
оптимизация), полученный код
define i32 @_Z1f7myclassS_(i64 %x.coerce, i64 %y.coerce) #0 {
entry:
%x.sroa.0.0.extract.trunc = trunc i64 %x.coerce to i32
%y.sroa.0.0.extract.trunc = trunc i64 %y.coerce to i32
%conv.i = and i64 %x.coerce, 1095216660480
%tobool = icmp eq i64 %conv.i, 0
%conv.i12 = and i64 %y.coerce, 1095216660480
%tobool2 = icmp eq i64 %conv.i12, 0
%or.cond = or i1 %tobool, %tobool2
%add = add nsw i32 %y.sroa.0.0.extract.trunc, %x.sroa.0.0.extract.trunc
%mul = mul nsw i32 %y.sroa.0.0.extract.trunc, %x.sroa.0.0.extract.trunc
%retval.0 = select i1 %or.cond, i32 %mul, i32 %add
ret i32 %retval.0
}
и главный:
define i32 @main() #0 {
entry:
%call = tail call i32 @_Z1f7myclassS_(i64 4294967296, i64 4294967297)
%call4 = tail call i32 @_Z1f7myclassS_(i64 4294967297, i64 2)
%add = add nsw i32 %call4, %call
ret i32 %add
}
Как можно видеть, аргумент f
преобразуется в двух 64-битных чисел, а значение option
хранится в верхней половине 64-битового значения. Затем функция f
разбивает 64-битное значение на две части и решает, возвращать ли результат умножения или сложения на основе значения.
Что вы подразумеваете под «различными исходными файлами». Что делать, если 'f' определено в файле заголовка? – Vincent
Если 'f' находится в файле заголовка, он может быть встроен там, где он вызывается. Если 'f' объявлен, но не определен, компилятор не будет иметь никакой опции, кроме как вызвать функцию - и поскольку вызываемая функция не знает, что передается, необходимо проверить входные значения. Я вернусь немного с несколькими примерами, показывая, что я имею в виду. –
LTO позволяет даже в том случае, если он определен в другой единицы перевода! – kukyakya