Bu yanıt, std :: function çağrılarının çalışma süresi maliyeti için daha anlamlı bir kriter olduğuna inandığım mevcut cevaplar kümesine katkıda bulunmayı amaçlamaktadır.
Std :: fonksiyon mekanizması sağladığı için tanınmalıdır: Çağrılabilir herhangi bir varlık uygun imzalı bir std :: fonksiyonuna dönüştürülebilir. Bir yüzeye z = f (x, y) ile tanımlanan bir işleve uyan bir kitaplığınız olduğunu varsayalım, a'yı kabul etmek için yazabilirsiniz std::function<double(double,double)>
ve kitaplık kullanıcısı herhangi bir çağrılabilir varlığı buna dönüştürebilir; sıradan bir işlev, bir sınıf örneği yöntemi veya bir lambda veya std :: bind tarafından desteklenen herhangi bir şey olsun.
Şablon yaklaşımlarının aksine, bu, farklı durumlar için kütüphane işlevini yeniden derlemeye gerek kalmadan çalışır; buna göre, her ek durum için çok az derlenmiş kod gereklidir. Bunu gerçekleştirmek her zaman mümkün olmuştur, ancak bazı garip mekanizmalar gerektiriyordu ve kütüphane kullanıcısının çalışması için işlevlerinin etrafında bir adaptör inşa etmesi gerekiyordu. std :: function , yeni ve çok güçlü bir özellik olan tüm durumlar için ortak bir çalışma zamanı çağrı arabirimi elde etmek için gereken adaptörü otomatik olarak oluşturur .
Benim görüşüme göre, bu performans açısından std :: işlevi için en önemli kullanım durumudur: Bir kez inşa edildikten sonra birçok kez bir std :: işlevini arama maliyetiyle ilgileniyorum ve derleyicinin gerçekte çağrılan işlevi bilerek çağrıyı optimize edemediği bir durum olabilir (yani uygun bir kıyaslama elde etmek için uygulamayı başka bir kaynak dosyada gizlemeniz gerekir).
Aşağıdaki testi OP'lere benzer şekilde yaptım; ancak ana değişiklikler:
- Her vaka 1 milyar kez döngüye girer, ancak std :: function nesneleri yalnızca bir kez oluşturulur. Gerçek std :: fonksiyon çağrıları (belki optimize edildiğinde değil) oluştururken 'operatör yeni' denilen çıkış koduna bakarak buldum.
- İstenmeyen optimizasyonu önlemek için test iki dosyaya bölünmüştür
- Benim durumlarım: (a) işlev satır içi (b) işlevi sıradan bir işlev işaretçisi tarafından geçirilir (c) işlevi std :: olarak sarılmış uyumlu bir işlevdir işlevi (d) işlevi bir std ile uyumlu yapılan uyumsuz bir işlevdir :: bağlama, std :: işlevi olarak sarılmış
Aldığım sonuçlar:
vaka (a) (satır içi) 1,3 ns
diğer tüm durumlar: 3,3 ns.
Durum (d) biraz daha yavaş olma eğilimindedir, ancak fark (yaklaşık 0,05 nsn) gürültüde emilir.
Sonuç olarak, std :: işlevi, gerçek işleve basit bir 'bağlama' uyarlaması olsa bile, bir işlev işaretçisi kullanarak karşılaştırılabilir ek yük (çağrı zamanında) olabilir. Satır içi diğerlerinden 2 ns daha hızlıdır, ancak bu beklenen bir değiş tokuştur çünkü satır içi çalışma zamanında 'kablo bağlantılı' tek durumdur.
Johan-lundberg kodunu aynı makinede çalıştırdığımda, döngü başına yaklaşık 39 nsec görüyorum, ancak std :: fonksiyonunun gerçek yapıcısı ve yıkıcısı da dahil olmak üzere döngüde çok daha fazlası var, ki bu muhtemelen oldukça yüksek çünkü yeni ve sil.
-O2 gcc 4.8.1, x86_64 hedefine (çekirdek i5).
Not, derleyicinin çağrıldığı işlevleri genişletmesini önlemek için kodun iki dosyaya bölünür (amaçlanan durum hariç).
----- ilk kaynak dosya --------------
#include <functional>
// simple funct
float func_half( float x ) { return x * 0.5; }
// func we can bind
float mul_by( float x, float scale ) { return x * scale; }
//
// func to call another func a zillion times.
//
float test_stdfunc( std::function<float(float)> const & func, int nloops ) {
float x = 1.0;
float y = 0.0;
for(int i =0; i < nloops; i++ ){
y += x;
x = func(x);
}
return y;
}
// same thing with a function pointer
float test_funcptr( float (*func)(float), int nloops ) {
float x = 1.0;
float y = 0.0;
for(int i =0; i < nloops; i++ ){
y += x;
x = func(x);
}
return y;
}
// same thing with inline function
float test_inline( int nloops ) {
float x = 1.0;
float y = 0.0;
for(int i =0; i < nloops; i++ ){
y += x;
x = func_half(x);
}
return y;
}
----- ikinci kaynak dosya -------------
#include <iostream>
#include <functional>
#include <chrono>
extern float func_half( float x );
extern float mul_by( float x, float scale );
extern float test_inline( int nloops );
extern float test_stdfunc( std::function<float(float)> const & func, int nloops );
extern float test_funcptr( float (*func)(float), int nloops );
int main() {
using namespace std::chrono;
for(int icase = 0; icase < 4; icase ++ ){
const auto tp1 = system_clock::now();
float result;
switch( icase ){
case 0:
result = test_inline( 1e9);
break;
case 1:
result = test_funcptr( func_half, 1e9);
break;
case 2:
result = test_stdfunc( func_half, 1e9);
break;
case 3:
result = test_stdfunc( std::bind( mul_by, std::placeholders::_1, 0.5), 1e9);
break;
}
const auto tp2 = high_resolution_clock::now();
const auto d = duration_cast<milliseconds>(tp2 - tp1);
std::cout << d.count() << std::endl;
std::cout << result<< std::endl;
}
return 0;
}
İlgilenenler için, derleyicinin 'mul_by' 'nin bir şamandıra (float) gibi görünmesini sağlamak için oluşturduğu adaptör - bind (mul_by, _1,0.5) olarak oluşturulan işlev çağrıldığında bu' denir ':
movq (%rdi), %rax ; get the std::func data
movsd 8(%rax), %xmm1 ; get the bound value (0.5)
movq (%rax), %rdx ; get the function to call (mul_by)
cvtpd2ps %xmm1, %xmm1 ; convert 0.5 to 0.5f
jmp *%rdx ; jump to the func
(bu yüzden bağlamada 0.5f yazsaydım biraz daha hızlı olabilirdi ...) 'x' parametresinin% xmm0 olarak geldiğini ve orada kaldığını unutmayın.
Test_stdfunc çağrılmadan önce işlevin oluşturulduğu alandaki kod aşağıdadır: c ++ filt ile çalıştırın:
movl $16, %edi
movq $0, 32(%rsp)
call operator new(unsigned long) ; get 16 bytes for std::function
movsd .LC0(%rip), %xmm1 ; get 0.5
leaq 16(%rsp), %rdi ; (1st parm to test_stdfunc)
movq mul_by(float, float), (%rax) ; store &mul_by in std::function
movl $1000000000, %esi ; (2nd parm to test_stdfunc)
movsd %xmm1, 8(%rax) ; store 0.5 in std::function
movq %rax, 16(%rsp) ; save ptr to allocated mem
;; the next two ops store pointers to generated code related to the std::function.
;; the first one points to the adaptor I showed above.
movq std::_Function_handler<float (float), std::_Bind<float (*(std::_Placeholder<1>, double))(float, float)> >::_M_invoke(std::_Any_data const&, float), 40(%rsp)
movq std::_Function_base::_Base_manager<std::_Bind<float (*(std::_Placeholder<1>, double))(float, float)> >::_M_manager(std::_Any_data&, std::_Any_data const&, std::_Manager_operation), 32(%rsp)
call test_stdfunc(std::function<float (float)> const&, int)
std::function
Yalnızca gerçekten de çağrılabilir nesnelerin heterojen bir koleksiyonuna ihtiyacınız varsa kullanın (yani çalışma zamanında başka hiçbir ayrımcı bilgi mevcut değildir).