Имеют ли современные компиляторы C ++ встроенные функции, которые вызываются ровно один раз? - PullRequest
10 голосов
/ 17 августа 2011

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

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 ]

11 голосов
/ 17 августа 2011

Вы должны пометить функции 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
}
7 голосов
/ 17 августа 2011

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

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

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

7 голосов
/ 17 августа 2011

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

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

0 голосов
/ 18 августа 2011

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

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

0 голосов
/ 17 августа 2011

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

...