Есть ли в современных C++ компиляторах встроенные функции, которые вызываются ровно один раз?

Как, скажем, мой заголовочный файл:

class A
{
    void Complicated();
}

И мой исходный файл

void A::Complicated()
{
    ...really long function...
}

Могу ли я разделить исходный файл на

void DoInitialStuff(pass necessary vars by ref or value)
{
    ...
}
void HandleCaseA(pass necessary vars by ref or value)
{
    ...
}
void HandleCaseB(pass necessary vars by ref or value)
{
    ...
}
void FinishUp(pass necessary vars by ref or value)
{
    ...
}
void A::Complicated()
{
    ...
    DoInitialStuff(...);
    switch ...
        HandleCaseA(...)
        HandleCaseB(...)
    ...
    FinishUp(...)
}

Полностью для удобства чтения и без какого-либо страха воздействия с точки зрения производительности?

5 ответов

Решение

Вы должны отметить функции static чтобы компилятор знал, что они локальны для этого модуля перевода.

Без static компилятор не может предположить (за исключением LTO / WPA), что функция вызывается только один раз, поэтому с меньшей вероятностью встроит ее.

Демонстрация с использованием страницы LLVM Try Out.

Тем не менее, код для удобочитаемости в первую очередь, микрооптимизации (а такая подстройка является микрооптимизацией) должен идти только после измерения производительности.


Пример:

#include <cstdio>

static void foo(int i) {
  int m = i % 3;
  printf("%d %d", i, m);
}

int main(int argc, char* argv[]) {
  for (int i = 0; i != argc; ++i) {
    foo(i);
  }
}

Производит с static:

; ModuleID = '/tmp/webcompile/_27689_0.bc'
target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64"
target triple = "x86_64-unknown-linux-gnu"

@.str = private constant [6 x i8] c"%d %d\00"     ; <[6 x i8]*> [#uses=1]

define i32 @main(i32 %argc, i8** nocapture %argv) nounwind {
entry:
  %cmp4 = icmp eq i32 %argc, 0                    ; <i1> [#uses=1]
  br i1 %cmp4, label %for.end, label %for.body

for.body:                                         ; preds = %for.body, %entry
  %0 = phi i32 [ %inc, %for.body ], [ 0, %entry ] ; <i32> [#uses=3]
  %rem.i = srem i32 %0, 3                         ; <i32> [#uses=1]
  %call.i = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([6 x i8]* @.str, i64 0, i64 0), i32 %0, i32 %rem.i) nounwind ; <i32> [#uses=0]
  %inc = add nsw i32 %0, 1                        ; <i32> [#uses=2]
  %exitcond = icmp eq i32 %inc, %argc             ; <i1> [#uses=1]
  br i1 %exitcond, label %for.end, label %for.body

for.end:                                          ; preds = %for.body, %entry
  ret i32 0
}

declare i32 @printf(i8* nocapture, ...) nounwind

Без static:

; ModuleID = '/tmp/webcompile/_27859_0.bc'
target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64"
target triple = "x86_64-unknown-linux-gnu"

@.str = private constant [6 x i8] c"%d %d\00"     ; <[6 x i8]*> [#uses=1]

define void @foo(int)(i32 %i) nounwind {
entry:
  %rem = srem i32 %i, 3                           ; <i32> [#uses=1]
  %call = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([6 x i8]* @.str, i64 0, i64 0), i32 %i, i32 %rem) ; <i32> [#uses=0]
  ret void
}

declare i32 @printf(i8* nocapture, ...) nounwind

define i32 @main(i32 %argc, i8** nocapture %argv) nounwind {
entry:
  %cmp4 = icmp eq i32 %argc, 0                    ; <i1> [#uses=1]
  br i1 %cmp4, label %for.end, label %for.body

for.body:                                         ; preds = %for.body, %entry
  %0 = phi i32 [ %inc, %for.body ], [ 0, %entry ] ; <i32> [#uses=3]
  %rem.i = srem i32 %0, 3                         ; <i32> [#uses=1]
  %call.i = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([6 x i8]* @.str, i64 0, i64 0), i32 %0, i32 %rem.i) nounwind ; <i32> [#uses=0]
  %inc = add nsw i32 %0, 1                        ; <i32> [#uses=2]
  %exitcond = icmp eq i32 %inc, %argc             ; <i1> [#uses=1]
  br i1 %exitcond, label %for.end, label %for.body

for.end:                                          ; preds = %for.body, %entry
  ret i32 0
}

Зависит от псевдонимов (указатели на эту функцию) и длины функции (большая функция, встроенная в ветвь, может выбросить другую ветвь из кэша, что приведет к снижению производительности).

Пусть компилятор беспокоится об этом, вы беспокоитесь о своем коде:)

Сложная функция, вероятно, будет иметь скорость, в которой преобладают операции внутри функции; накладные расходы при вызове функции не будут заметны, даже если они не встроены.

Вы не имеете большого контроля над встраиванием функции, лучший способ узнать это попробовать и выяснить.

Оптимизатор компилятора может быть более эффективным с более короткими фрагментами кода, поэтому вы можете обнаружить, что он работает быстрее, даже если он не встроен.

Я предлагаю вам создать вспомогательный класс, чтобы разбить вашу сложную функцию на вызовы методов, во многом как вы предлагали, но без долгой, скучной и нечитаемой задачи передачи аргументов каждой из этих небольших функций. Передайте эти аргументы только один раз, сделав их членами-переменными вспомогательного класса.

На этом этапе не сосредотачивайтесь на оптимизации, убедитесь, что ваш код читабелен и у вас все будет хорошо в 99% случаев.

Если вы разделите ваш код на логические группы, компилятор сделает то, что считает лучшим: если он короткий и простой, компилятор должен его встроить, и результат будет таким же. Однако, если код сложен, выполнение дополнительного вызова функции может на самом деле быть быстрее, чем выполнение всей встроенной работы, поэтому вы оставляете компилятору возможность сделать это тоже. Помимо всего этого, логически разделенный код может быть намного проще для разработчика и позволяет избежать будущих ошибок.

Другие вопросы по тегам