sauf si vous utilisez une ancienne bibliothèque standard, std::function
aura des optimisations pour les objets de petite fonction (dont le vôtre est un). Vous ne verrez aucune réduction de performance. Les personnes qui vous disent de ne pas utiliser std::function
pour des raisons de performances sont les mêmes personnes qui «optimisent» le code avant de mesurer les goulots d'étranglement.
Écrivez le code qui exprime l'intention. SI il devient un goulot d'étranglement de la performance (il ne sera pas) alors regardez-le changer.
J'ai déjà travaillé sur un système de prix financiers à terme. Quelqu'un a décidé qu'il fonctionnait trop lentement (64 cœurs, plusieurs boîtes de serveurs, des centaines de milliers d'algorithmes discrets fonctionnant en parallèle dans un DAG massif). Nous l'avons donc profilé.
Qu'avons-nous trouvé?
Le traitement a pris presque pas de temps du tout. Le programme passait 99% de son temps à convertir des doubles en chaînes et en chaînes à doubler aux limites de l'E/S, où nous devions communiquer avec un bus de messages.
L'utilisation d'un lambda à la place d'un std::function
pour les rappels n'aurait fait aucune différence.
Écrivez un code élégant. Exprimez clairement votre intention. Compiler avec optimisations. Marvel que le compilateur fait son travail et transforme vos 100 lignes de C++ en 5 instructions de code machine.
Une démonstration simple:
#include <functional>
// external function forces an actual function call
extern void other_func(const char* p);
// try to force std::function to call polymorphically
void test_it(const std::function<void(const char*)>& f, const char* p)
{
f(p);
}
int main()
{
// make our function object
auto f = std::function<void(const char*)>([](const char* p) { other_func(p); });
const char* const data[] = {
"foo",
"bar",
"baz"
};
// call it in a tight loop
for(auto p : data) {
test_it(f, p);
}
}
compilez avec clang pomme, -O2:
Résultat:
.globl _main
.align 4, 0x90
_main: ## @main
Lfunc_begin1:
.cfi_startproc
.cfi_personality 155, ___gxx_personality_v0
.cfi_lsda 16, Lexception1
## BB#0: ## %_ZNKSt3__18functionIFvPKcEEclES2_.exit.i
#
# the normal stack frame stuff...
#
pushq %rbp
Ltmp13:
.cfi_def_cfa_offset 16
Ltmp14:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Ltmp15:
.cfi_def_cfa_register %rbp
pushq %r15
pushq %r14
pushq %rbx
subq $72, %rsp
Ltmp16:
.cfi_offset %rbx, -40
Ltmp17:
.cfi_offset %r14, -32
Ltmp18:
.cfi_offset %r15, -24
movq [email protected](%rip), %rbx
movq (%rbx), %rbx
movq %rbx, -32(%rbp)
leaq -80(%rbp), %r15
movq %r15, -48(%rbp)
#
# take the address of std::function's vtable... we'll need it (once)
#
leaq __ZTVNSt3__110__function6__funcIZ4mainE3$_0NS_9allocatorIS2_EEFvPKcEEE+16(%rip), %rax
#
# here's the tight loop...
#
movq %rax, -80(%rbp)
leaq L_.str(%rip), %rdi
movq %rdi, -88(%rbp)
Ltmp3:
#
# oh look! std::function's call has been TOTALLY INLINED!!
#
callq __Z10other_funcPKc
Ltmp4:
LBB1_2: ## %_ZNSt3__110__function6__funcIZ4mainE3$_0NS_9allocatorIS2_EEFvPKcEEclEOS6_.exit
## =>This Inner Loop Header: Depth=1
#
# notice that the loop itself uses more instructions than the call??
#
leaq L_.str1(%rip), %rax
movq %rax, -88(%rbp)
movq -48(%rbp), %rdi
testq %rdi, %rdi
je LBB1_1
## BB#3: ## %_ZNKSt3__18functionIFvPKcEEclES2_.exit.i.1
## in Loop: Header=BB1_2 Depth=1
#
# destructor called once (constant time, therefore irrelevant)
#
movq (%rdi), %rax
movq 48(%rax), %rax
Ltmp5:
leaq -88(%rbp), %rsi
callq *%rax
Ltmp6:
## BB#4: ## in Loop: Header=BB1_2 Depth=1
leaq L_.str2(%rip), %rax
movq %rax, -88(%rbp)
movq -48(%rbp), %rdi
testq %rdi, %rdi
jne LBB1_5
#
# the rest of this function is exception handling. Executed at most
# once, in exceptional circumstances. Therefore, irrelevant.
#
LBB1_1: ## in Loop: Header=BB1_2 Depth=1
movl $8, %edi
callq ___cxa_allocate_exception
movq [email protected](%rip), %rcx
addq $16, %rcx
movq %rcx, (%rax)
Ltmp10:
movq [email protected](%rip), %rsi
movq [email protected](%rip), %rdx
movq %rax, %rdi
callq ___cxa_throw
Ltmp11:
jmp LBB1_2
LBB1_9: ## %.loopexit.split-lp
Ltmp12:
jmp LBB1_10
LBB1_5: ## %_ZNKSt3__18functionIFvPKcEEclES2_.exit.i.2
movq (%rdi), %rax
movq 48(%rax), %rax
Ltmp7:
leaq -88(%rbp), %rsi
callq *%rax
Ltmp8:
## BB#6:
movq -48(%rbp), %rdi
cmpq %r15, %rdi
je LBB1_7
## BB#15:
testq %rdi, %rdi
je LBB1_17
## BB#16:
movq (%rdi), %rax
callq *40(%rax)
jmp LBB1_17
LBB1_7:
movq -80(%rbp), %rax
leaq -80(%rbp), %rdi
callq *32(%rax)
LBB1_17: ## %_ZNSt3__18functionIFvPKcEED1Ev.exit
cmpq -32(%rbp), %rbx
jne LBB1_19
## BB#18: ## %_ZNSt3__18functionIFvPKcEED1Ev.exit
xorl %eax, %eax
addq $72, %rsp
popq %rbx
popq %r14
popq %r15
popq %rbp
retq
Peut-on cesser d'argumenter sur la performance maintenant s'il vous plaît?
'auto printHello (F f) -> decltype (f (" "), void())' –
@PiotrSkotnicki Bien que l'erreur ne soit pas nécessairement moins obscure. –
Vous pouvez également utiliser '' à la place d'un modèle ... 'void printHello (std :: fonction f)' –