2016-03-21 8 views
0

std :: thread ve std :: async arasında bir test kodu hazırladım. 4 çekirdekli CentOs 7 kutusu (gcc 4.8.5), Sürüm 1 ilestd :: thread std :: async, BÜYÜK performans kazancı sağlar. Nasıl mümkün olabilir?

#include <iostream> 
#include <mutex> 
#include <fstream> 
#include <string> 
#include <memory> 
#include <thread> 
#include <future> 
#include <functional> 
#include <boost/noncopyable.hpp> 
#include <boost/lexical_cast.hpp> 
#include <boost/filesystem.hpp> 
#include <boost/date_time/posix_time/posix_time.hpp> 
#include <boost/asio.hpp> 

namespace fs = boost::filesystem; 
namespace pt = boost::posix_time; 
namespace as = boost::asio; 
class Log : private boost::noncopyable 
{ 
public: 
    void LogPath(const fs::path& filePath) { 
     boost::system::error_code ec; 
     if(fs::exists(filePath, ec)) { 
      fs::remove(filePath); 
     } 
     this->ofStreamPtr_.reset(new fs::ofstream(filePath)); 
    }; 

    void WriteLog(std::size_t i) { 
     assert(*this->ofStreamPtr_); 
     std::lock_guard<std::mutex> lock(this->logMutex_); 
     *this->ofStreamPtr_ << "Hello, World! " << i << "\n"; 
    }; 

private: 
    std::mutex logMutex_; 
    std::unique_ptr<fs::ofstream> ofStreamPtr_; 
}; 

int main(int argc, char *argv[]) { 
    if(argc != 2) { 
     std::cout << "Wrong argument" << std::endl; 
     exit(1); 
    } 
    std::size_t iter_count = boost::lexical_cast<std::size_t>(argv[1]); 

    Log log; 
    log.LogPath("log.txt"); 

    std::function<void(std::size_t)> func = std::bind(&Log::WriteLog, &log, std::placeholders::_1); 

    auto start_time = pt::microsec_clock::local_time(); 
    ////// Version 1: use std::thread ////// 
// { 
//  std::vector<std::shared_ptr<std::thread> > threadList; 
//  threadList.reserve(iter_count); 
//  for(std::size_t i = 0; i < iter_count; i++) { 
//   threadList.push_back(
//    std::make_shared<std::thread>(func, i)); 
//  } 
// 
//  for(auto it: threadList) { 
//   it->join(); 
//  } 
// } 

// pt::time_duration duration = pt::microsec_clock::local_time() - start_time; 
// std::cout << "Version 1: " << duration << std::endl; 

    ////// Version 2: use std::async ////// 
    start_time = pt::microsec_clock::local_time(); 
    { 
     for(std::size_t i = 0; i < iter_count; i++) { 
      auto result = std::async(func, i); 
     } 
    } 

    duration = pt::microsec_clock::local_time() - start_time; 
    std::cout << "Version 2: " << duration << std::endl; 

    ////// Version 3: use boost::asio::io_service ////// 
// start_time = pt::microsec_clock::local_time(); 
// { 
//  as::io_service ioService; 
//  as::io_service::strand strand{ioService}; 
//  { 
//   for(std::size_t i = 0; i < iter_count; i++) { 
//    strand.post(std::bind(func, i)); 
//   } 
//  } 
//  ioService.run(); 
// } 

// duration = pt::microsec_clock::local_time() - start_time; 
// std::cout << "Version 3: " << duration << std::endl; 


} 

yaklaşık 100 x yavaş diğer uygulamaları ile karşılaştırılır (STD :: iplik kullanılarak) dönüştürülmüştür.

 
Iteration Version1 Version2 Version3 
100  0.0034s 0.000051s 0.000066s 
1000  0.038s  0.00029s 0.00058s 
10000  0.41s  0.0042s 0.0059s 
100000 throw  0.026s  0.061s 

Neden dişli sürüm çok yavaş? Her iş parçacığının Log::WriteLog işlevini tamamlamak için uzun sürmeyeceğini düşündüm.

+0

Benim görüşüme göre çok fazla iş parçacığı (cpu çekirdeklerinden daha fazlası) tetikliyorsunuz ve hepsi de cpu zamanı ve içerik geçişi için rekabet ediyorlar, yavaştır. Zaman uyumsuzluğu durumunda, çalışma zamanı kodunuzu yeterli sayıda iş parçacığı üzerinde etkin bir şekilde yönetir ve yürütür ve gerektiğinde işlemci zamanını verir. – Saleem

+0

iş parçacığı _very_ genişliyor. çekirdek sayısından daha fazla bir şey, performansı düşürür (kilitlerle/IO tarafından engellenen konuları göz ardı eder). Bu yüzden iplik havuzu tavsiye edilir. –

+0

Kodunuzun 100000 yinelemeyle başarısız olması, yeterince büyük bir ipucudur. Bir iş parçacığı, pahalı bir işletim sistemi nesnesidir ve bunları oluşturma ve bunları yeniden parçalama maliyetini ödersiniz. Eğer iş parçacığı tarafından yapılan iş miktarı bu kadar küçükse, o zaman kesinlikle yükü görürsünüz. Bir std :: async uygulaması bu maliyeti amorti edebilir, bir threadpool kullanarak standart bir tekniktir. Kaba bir kılavuz, bir iş parçacığının en az 100 mikrosaniye çalışması gerektiğidir, bir uyumsuzluk işlevi bir saniyeden fazla sürmemelidir. –

cevap

2

Bu işlev hiçbir zaman çağrılmayabilir. Sen Sürüm 2'de bir std::launch politikasını geçmezken, bu yüzden the default behavior of std::async (vurgu mayın) güvenmek:

async(std::launch::async | std::launch::deferred, f, args...) ile aynı şekilde davranır. Başka bir deyişle, f başka bir evre 'da çalıştırılabilir veya sonuç için std::future sorgulandığında eşzamanlı olarak çalıştırılabilir.

deneyin bu küçük değişiklikle sizin kriter yeniden çalıştırmayı: Alternatif

auto result = std::async(std::launch::async, func, i); 

, size iş parçacığı tümü üzerinde join() diyoruz nasıl benzer ikinci bir döngü içinde her std::future üzerinde result.wait() diyebiliriz Versiyon 1'de. Bu, std::future'un değerlendirilmesini zorlar.

Bu referans noktasında büyük, ilgisiz bir sorun olduğunu unutmayın. func, fonksiyon çağrısının tam süresi boyunca hemen bir kilit alır ve bu da paralelliğin imkansız olmasını sağlar. Burada iplik kullanmanın bir avantajı yoktur - bir seri uygulamadan çok daha yavaş (iplik oluşturma ve kilitleme nedeniyle) olacağından şüpheleniyorum.

+0

Evet, haklısınız. Başlatma politikasını belirtmeden, bu kod asla çalışmaz ... Kodumu std :: launch :: async kullanacak şekilde değiştirdim ve Sürüm 1 ile etkili bir performans sergiliyor. (10000 iterrasyon için 0.29s) Not: '' std :: lock_guard''' kaldırılarak performans kazanımları –

+0

Paralel kodun bir akıl yürütme kontrolü olarak seri uygulamaya karşı karşılaştırılmasını öneririm. "Std :: fstream" iş parçacığı güvenli olmadığı için, "std :: lock_guard" öğesini kesinlikle kaldırmamalısınız. Ayrıca, bu, orijinal sorunuzu cevapladıysa, lütfen cevabı kabul etmeyi düşünün. –