2016-09-23 97 views
5

Bir C++ derleyicisinin hangi durumlarda döngü füzyonu yapabileceğini ve ne zaman gerçekleştiremediğini anlamaya çalışıyorum.C++ içinde döngü füzyonu (derleyiciye nasıl yardımcı olabilirsiniz?)

Aşağıdaki kod, bir vektördeki tüm değerlerin kare ikilemlerini (f(x) = (2*x)^2) hesaplamak için iki farklı yolun performansını ölçer.

#include <chrono> 
#include <iostream> 
#include <numeric> 
#include <vector> 

constexpr int square(int x) 
{ 
    return x * x; 
} 

constexpr int times_two(int x) 
{ 
    return 2 * x; 
} 

// map ((^2) . (^2)) $ [1,2,3] 
int manual_fusion(const std::vector<int>& xs) 
{ 
    std::vector<int> zs; 
    zs.reserve(xs.size()); 
    for (int x : xs) 
    { 
     zs.push_back(square(times_two(x))); 
    } 
    return zs[0]; 
} 

// map (^2) . map (^2) $ [1,2,3] 
int two_loops(const std::vector<int>& xs) 
{ 
    std::vector<int> ys; 
    ys.reserve(xs.size()); 
    for (int x : xs) 
    { 
     ys.push_back(times_two(x)); 
    } 

    std::vector<int> zs; 
    zs.reserve(ys.size()); 
    for (int y : ys) 
    { 
     zs.push_back(square(y)); 
    } 
    return zs[0]; 
} 

template <typename F> 
void test(F f) 
{ 
    const std::vector<int> xs(100000000, 42); 

    const auto start_time = std::chrono::high_resolution_clock::now(); 
    const auto result = f(xs); 
    const auto end_time = std::chrono::high_resolution_clock::now(); 

    const auto elapsed = end_time - start_time; 
    const auto elapsed_us = std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count(); 
    std::cout << elapsed_us/1000 << " ms - " << result << std::endl; 
} 

int main() 
{ 
    test(manual_fusion); 
    test(two_loops); 
} 

iki halkanın, bir döngü ile bir versiyonu olarak takes about twice as much time GCC ve Clang için bile -O3 versiyon.

Derleyicinin two_loops'u ikinci döngüde yerinde çalışmaya gerek kalmadan manual_fusion hızına yükseltmesine olanak sağlamanın bir yolu var mı? Sormamın sebebi ise FunctionalPlus numaralı kütüphaneme fplus::enumerate(fplus::transform(f, xs)); gibi daha hızlı bir şekilde zincirleme çağrısı yapmaktır.

+1

için push_back etmektir. Bu ys' üzerinde doğrudan çalışın (değiştir) ' –

+0

Çok teşekkür ederim, [işe yarıyor] (http://ideone.com/I17kdT)! Derleyicinin tahsisatı en iyi duruma getirme imkânı olduğunu düşünüyor musunuz? –

+1

Ben öyle düşünmüyorum. Bunu yapmak için çok fazla tahmin olurdu (bir tahmin zaten bu optimizasyonu yasakladı) –

cevap

1

Sen aşağıdaki gibi two_loops işlevini değiştirmek deneyebilirsiniz:

int two_loops(const std::vector<int>& xs) 
{ 
    std::vector<int> zs; 
    zs.reserve(xs.size()); 
    for (int x : xs) 
    { 
     zs.push_back(times_two(x)); 
    } 

    for (int i=0 : i<zs.size(); i++) 
    { 
     zs[i] = (square(zs[i])); 
    } 
    return zs[0]; 
} 

nokta İkinci versiyonda iki tahsisleri sahip iki kez bellek ayrılırken önlemek ve başka vektör

+0

Teşekkür ederim. Bu çalışıyor. Ama tahsisatı kaldırmadan bir olasılık umuyordum. Nedeni, kütüphanemde [FunctionalPlus] (https://github.com/Dobiasd/FunctionalPlus) 'fplus :: numaralandır (fplus :: transform (f, xs)); Sorumu buna göre uzattım. –

+0

Zincirli aramalar yapmak istiyorsanız, daha uzun yürütme süreleri olan fiyatı ödemeniz gerekecektir. En azından, tahsislerden ve push_backlerden kaçınabilirsiniz. İlk çağrı vektörü oluşturacak ve sonraki çağrılar vektör içeriklerini değiştirecektir. – Trifon

+0

İçeriği değiştirmek maalesef FunctionalPlus'ta kullandığım yaklaşıma uymuyor. –