Nouvelles Du Monde

Développement logiciel : allocateurs spéciaux avec C++17​

Développement logiciel : allocateurs spéciaux avec C++17​

2023-10-02 10:06:00

Dans mon dernier article « Développement logiciel : allocateurs polymorphes avec C++17 » j’ai présenté la théorie des allocateurs polymorphes en C++17. Aujourd’hui, je vais appliquer la théorie.

Publicité


Rainer Grimm travaille depuis de nombreuses années en tant qu’architecte logiciel, responsable d’équipe et de formation. Il aime écrire des articles sur les langages de programmation C++, Python et Haskell, mais aime également intervenir fréquemment lors de conférences spécialisées. Sur son blog Modern C++, il parle intensément de sa passion C++.

Avant de continuer, voici les parties les plus importantes de mon dernier article « Développement de logiciels : allocateurs polymorphes avec C++17 ».

Le programme suivant utilise des allocateurs polymorphes :

// polymorphicAllocator.cpp

#include 
#include 
#include 
#include 

int main() {

    std::array buf1;                         // (1)
    std::pmr::monotonic_buffer_resource pool1{buf1.data(), 
      buf1.size()};
    std::pmr::vector myVec1{&pool1};                    // (3)
    for (int i = 0; i < 5; ++i) {
        myVec1.push_back(i);
    }

    char buf2[200] = {};                                     // (2)
    std::pmr::monotonic_buffer_resource pool2{std::data(buf2), 
      std::size(buf2)};
    std::pmr::vector myVec2{&pool2};
    for (int i = 0; i < 200; ++i) {
        myVec2.push_back(i);
    }

}

Maintenant je veux me lever myVec2 se concentrer. 200 ints sera sur le std::pmr::vector poussé. Ces 200 intça ne rentre pas dans un char buf[200] et donc saute std::pmr::new_delete_resource() en tant qu'allocateur en amont et appelle le global new pour les éléments restants. Je vais maintenant instrumentaliser l'allocateur amont.

Le programme suivant est basé sur le précédent, utilise un allocateur de suivi et rend visibles l'allocation et la désallocation dynamiques de mémoire.

// trackAllocator.cpp

#include 
#include 
#include 
#include 
#include 
#include 

class TrackAllocator : public std::pmr::memory_resource {
    void* do_allocate(std::size_t bytes, 
                      std::size_t alignment) override {
        void* p = std::pmr::new_delete_resource()->
          allocate(bytes, alignment);
        std::cout << 
          std::format("  do_allocate: {:6} bytes at {}n",
                      bytes, p);
        return p;
    }
 
    void do_deallocate(void* p, 
                       std::size_t bytes, 
                       std::size_t alignment) override {
        std::cout << 
          std::format("  do_deallocate: {:4} bytes at {}n", 
                      bytes, p);
        return 
          std::pmr::new_delete_resource()->
            deallocate(p, bytes, alignment);
    }
 
    bool do_is_equal(const std::pmr::memory_resource& other) 
      const noexcept override {
        return std::pmr::new_delete_resource()->is_equal(other);
    }
};


int main() {

    std::cout << 'n';

    TrackAllocator trackAllocator;                         // (1)
    std::pmr::set_default_resource(&trackAllocator);       // (2)

    std::cout << "myVec1n";

    std::array buf1;
    std::pmr::monotonic_buffer_resource pool1{buf1.data(), 
                                              buf1.size()};
    std::pmr::vector myVec1{&pool1};                  // (3)
    for (int i = 0; i < 5; ++i) {
        myVec1.push_back(i);
    }

    std::cout << "myVec2n";

    char buf2[200] = {}; 
    std::pmr::monotonic_buffer_resource pool2{std::data(buf2),
                                             std::size(buf2)};
    std::pmr::vector myVec2{&pool2};                  // (4)
    for (int i = 0; i < 200; ++i) {
        myVec2.push_back(i);
    }

    std::cout << 'n';

}

TrackAllocator est un allocateur pour suivre l'allocation de mémoire et sa libération. Il dérive de la classe d'interface std::pmr::memory_resource à partir duquel toutes les ressources de stockage sont dérivées. TrackAllocator définit les trois fonctions membres requises do_allocate, do_deallocate et do_is_equal. Chaque appel le redirige vers l'appel de std::pmr::new_delete_resource plus loin. std:pmr::new_delete_resource est la ressource de stockage par défaut et appelle le global new et delete sur. La tâche de la fonction membre do_is_equal est de vérifier si les deux ressources mémoire sont égales. Le point intéressant de l'exemple est que j'alloue et désalloue la mémoire dans les fonctions membres do_allocate et do_deallocate visualisière.




Les concepts introduits avec C++20, ainsi que la bibliothèque, les modules et les coroutines Ranges, ont redéfini la façon de créer des applications C++ modernes. Du 7 au 9 novembre 2023 Rainer Grimm vous emmène dans son atelier intensif C++20 : les nouveaux concepts expliqués en détail à jour et aborde les nombreuses fonctions utiles apportées par C++20.

J'instancie cela TrackAllocator (ligne 1) et faites-en la ressource par défaut (2). myVec1 (3) et myVec2 (4) utilisez-le comme allocateur en amont. Cet allocateur intervient lorsque l'allocateur principal est consommé. Cette solution de secours est destinée myVec1 pas nécessaire pour myVec2 mais bien.

Cette sortie montre l'allocation dynamique et la libération de myVec2bis std::pmr::vector à ints peuvent accueillir.



Un allocateur en amont peut également être lié à un conteneur spécifique.

std::pmr::null_resource_allocator est un allocateur spécial. Leur utilisation pour l'allocation donne lieu à un std::bad_alloc-Exception. Cette ressource mémoire garantit que la mémoire n'est pas allouée de manière aléatoire sur le tas :

// nullMemoryResource.cpp

#include 
#include 
#include  
#include 
#include 
#include 

int main() {

    std::cout << 'n';
 
    std::array buf;
    std::pmr::monotonic_buffer_resource pool{buf.data(),
                            buf.size(),                   // (1)
                            std::pmr::null_memory_resource()};
    std::pmr::vector myVec{&pool};      // (2)
    try {
        for (int i = 0; i < 100; ++i) {                   // (3)
            std::cerr << i << " ";
            myVec.emplace_back("A short string");
        }
    }
    catch (const std::bad_alloc& e) {                     // (4)
        std::cerr << 'n' << e.what() << 'n';
    }
    
    std::cout << 'n';
    
}

J'alloue d'abord de la mémoire sur la pile et j'initialise std::pmr::monotonic_buffer_resource avec ça. Ensuite, j'utilise cette ressource mémoire et std::pmr::null_memory_resource en tant qu'allocateur en amont (1). Dans (2) j'en crée un std::pmr::vector. Puisque j'en ai un std::pmr::string la chaîne utilise également la ressource mémoire et son allocateur en amont. Si je std::pmr::vector utiliser, utiliser std::string les répartiteurs globaux new et delete. Enfin, je crée 100 chaînes en (3) et en attrape une en (4). std::bad_alloc-Exception. J'ai eu ce que je méritais : 100 cordes ne rentrent pas dans une seule std::array-Tampon. Après 17 chaînes, le tampon est épuisé.



Le std::pmr::monotonic_buffer possède d'excellentes propriétés. C'est assez rapide et ne libère pas de mémoire. Mon prochain article fournira les chiffres.


(moi)

Vers la page d'accueil



#Développement #logiciel #allocateurs #spéciaux #avec #C17
1696369019

Facebook
Twitter
LinkedIn
Pinterest

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

ADVERTISEMENT