2015-07-19 1 views
1

J'ai un code série que je voudrais paralléliser en utilisant Cilk Plus; la boucle principale appelle une fonction de traitement de façon répétée sur différents ensembles de données, de sorte que les itérations sont indépendantes les unes des autres, sauf pour l'utilisation d'une ressource non thread-thread, qui est encapsulée dans une classe (par exemple nts) bibliothèque qui prend un nom de fichier et fait des E/S dessus.Comment organiser un pool de ressources non thread-safe dans Cilk Plus (une ressource par travailleur)?

Si j'utilisais OpenMP, je voudrais créer un pool de ressources qui contient autant de ressources que je threads, et accéder à ces ressources selon le TID:

std::vector<nts> nts_pool; 
for (std::size_t i{0}; i < omp_get_num_threads(); ++i) 
    nts_pool.push_back(nts{}); 

nts_pool[omp_get_thread_num()].do_stuff(); // from inside the task 

En utilisant Cilk De plus, je pouvais faire autant en utilisant les API __cilkrts_get_nworkers() et __cilkrts_get_worker_number(), mais à partir de plusieurs messages sur les forums Intel, j'ai compris que cela est considéré comme une mauvaise solution au problème, et la bonne solution serait d'utiliser un hyperobjet titulaire.

Maintenant, la solution de support semble bien, sauf que je veux vraiment avoir seulement autant de vues créées que j'ai des threads de travail. Autrement dit, pour 3 threads de travail, je voudrais avoir 3 objets et pas plus. La justification est que, comme je le dis, la ressource est fournie par une bibliothèque tierce, est très coûteuse à construire, et je devrai traiter les fichiers résultants par la suite, donc le moins sera le mieux. Malheureusement, j'ai découvert qu'au lieu de faire une vue par travailleur et de la garder jusqu'à une synchronisation, les détenteurs créent et détruisent d'une manière ou d'une autre des vues selon la logique que je ne comprends pas, et il ne semble pas y avoir de un moyen d'influencer ce comportement.

Est-il possible de faire en sorte que les détenteurs se comportent comme je le souhaite, et sinon, quelle serait la solution idiomatique de Cilk Plus à mon problème?

Voici le programme que je l'habitude d'enquêter sur les titulaires, notez qu'il crée jusqu'à 50 vues sur ma machine de test au cours d'une course, qui sont attribués et détruits apparemment au hasard:

#include <iostream> 
#include <atomic> 

#include <cilk/cilk.h> 
#include <cilk/holder.h> 
#include <cilk/reducer_ostream.h> 
#include <cilk/cilk_api.h> 

cilk::reducer_ostream *hyper_cout; 

class nts { 
public: 
    nts() : tag_{std::to_string(++id_)} { 
     *hyper_cout << "NTS constructor: " << tag_ << std::endl; 
    } 
    ~nts() { 
     *hyper_cout << "NTS destructor: " << tag_ << std::endl; 
    } 
    void print_tag() { 
     *hyper_cout << "NTS tag: " << tag_ << std::endl; 
    } 
    static void is_lock_free() { 
     *hyper_cout << "Atomic is lockfree: " << id_.is_lock_free() << std::endl; 
    } 
private: 
    const std::string tag_; 
    static std::atomic_size_t id_; 
}; 

std::atomic_size_t nts::id_{0}; 

class nts_holder { 
public: 
    void print_tag() { nts_().print_tag(); } 
private: 
    cilk::holder<nts> nts_; 
}; 

int main() { 

    __cilkrts_set_param("nworkers", "4"); 

    cilk::reducer_ostream cout{std::cout}; 
    hyper_cout = &cout; 

    *hyper_cout << "Workers: " << __cilkrts_get_nworkers() << std::endl; 
    nts::is_lock_free(); 

    nts_holder ntsh; 
    ntsh.print_tag(); 

    for (std::size_t i{0}; i < 1000; ++i) { 
     cilk_spawn [&]() { 
      ntsh.print_tag(); 
     }(); 
    } 

    cilk_sync; 

    return 0; 

} 

Répondre

1

Vous avez raison les détenteurs sont une solution tentante mais inefficace à ce problème particulier. Si votre programme est correct en utilisant le tableau des emplacements avec un emplacement par travailleur, il n'y a vraiment rien de mal à utiliser les API __cilkrts_get_nworkers() et __cilkrts_get_worker_number() dans ce cas. Nous décourageons leur utilisation en général; préférant écrire du code Cilk Plus qui ne tient pas compte du nombre de travailleurs parce qu'il évolue généralement mieux de cette façon. Cependant, il y a des cas, y compris celui-ci, où la création d'un emplacement par travailleur est la meilleure stratégie.

+0

Merci beaucoup pour votre réponse claire. dans ce cas je vais aller chercher ma solution de slots! Cependant, j'apprécierais que vous puissiez également clarifier pourquoi les détenteurs se comportent comme ils le font actuellement. Cela me semble particulièrement contre-intuitif, puisque la mise en œuvre la plus facile qui me vient à l'esprit est un éventail de machines à sous ... :-) –

+0

La raison principale pour laquelle les détenteurs se comportent comme ils le font est historique: les détenteurs sont construits sur le dessus mécanisme réducteur, qui préserve les exigences d'une opération associative, même si l'opération en question est typiquement une opération non-op. Comme vous l'avez découvert, ce n'est souvent pas une propriété utile, mais il peut être utile, par exemple, de détecter qu'une valeur n'a pas changé et peut donc être utilisée sans la recalculer. Nous étudions des mécanismes plus efficaces (c'est-à-dire, slot-per-worker) pour les réducteurs et les détenteurs lorsque la commutativité est autorisée. –