Université de Sherbrooke, régulier et DGL, STR

Vous trouverez ici quelques documents et quelques liens pouvant, je l'espère, vous être utiles. Vous pouvez aussi consulter, parmi les liens divers mis à votre disposition, ceux portant sur les STR, ceux portant sur la multiprogrammation et ceux portant sur l'optimisation.

Les documents qui vous sont fournis ici le sont pour vous rendre service.

Je travaille très fort sur chacun de mes cours. Veuillez ne pas vendre (ou donner) les documents que je vous offre ici à qui que ce soit sans mon consentement. Si des abus surviennent, je vais cesser de rendre ce matériel disponible à toutes et à tous.

Si ces documents vous rendent service, faites-le moi savoir. Mon adresse de courriel est disponible sur la page où on trouve mon horaire.

Petit menu

IFT611/IFT729 INF749

Plan de cours

Plan de cours

Consignes quant au projet de session

Consignes quant au projet de session

Pour IFT611/IFT729, cours s'adressant aux étudiant(e)s de 2e cycle et de fin de 1er cycle, les frais d'impression de notes de cours ne sont pas facturés a priori. Je ne peux pas livrer ces notes de cours en format électronique, pour des raisons contractuelles.

Une procédure suivra pour celles et ceux qui souhaitent obtenir ces documents.

Au moment d'écrire ces lignes, ces notes de cours comprennent :

  • STR – Volume 00, v. 1,05, 77 pages, qui met en place terminologie et vocabulaire, et qui donne des exemples de code prenant en charge des tâches morcelables
  • STR – Volume 01, v. 1,985, 67 pages, qui discute brièvement de conteneurs, d'itérateurs, de tests de « performances », de mythes et de saisies du temps d'exécution
  • STR – Volume 02, v. 3,2, 323 pages, qui discute de multiprogrammation et de synchronisation
  • STR – Volume 03, v. 1,98, 177 pages, qui discute de micro-optimisation et de métaprogrammation

La demande va comme suit :

Qui Volume 00 Volume 01 Volume 02 Volume 03
         
         
         
         
         
         
         
         
         
         
         

 

Pour INF749, cours s'adressant aux étudiant(e)s de 2e cycle, les frais d'impression de notes de cours sont facturés a priori et vous seront livrés dans des cartables en classe.

 

Si vous cherchez des idées de projets, vous trouverez ici quelques exemples des projets qui ont été faits dans ce cours au fil du temps (liste non-exhaustive)

Sur ce site, vous trouverez :

Il arrive que j'aie en classe des étudiant(e)s qui ont déjà suivi des cours avec moi où l'on trouvait un besoin de multiprogrammation ou de programmation générique. Malheureusement, faute de préalables, je ne peux pas supposer que tous et toutes ont les bases nécessaires pour escamoter ces concepts, alors j'implore votre tolérance, surtout pour les deux ou trois premières séances, si vous rencontrez des éléments de matière qui sont, pour vous, du déjà vu.

À propos des séances en classe

Index des séances théoriques
IFT611/IFT729 S00 S01 S02 S03 S04 S05 S06 S07 S08 S09 S10 S11 S12 S13  
INF749 S00 S01 S02 S03 S04 S05 S06 S07 S08 S09 S10 S11 S12 S13 S14

Pendant les premières séances, nous examinerons surtout des concepts et techniques de base, qui seront réinvestis dans les exemples et les illustrations concrètes que proposera le professeur. Ce sont donc des séances plus près de la (saine) programmation au sens large que des STR de manière spécifique; j'espère que vous saurez voir les concepts propres aux STR même si la praxis sera présente dans notre discours.

D'autres approches sont possibles pour un cours de STR, évidemment, mais le choix pédagogique fait par votre chic prof de discuter à la fois de théorie et de pratique demande de mettre sur pied quelques bases au préalable. Je vous remercie de votre tolérance (et, si vous avez apprécié, alors tant mieux!).

Séance IFT611/IFT729 Contenu IFT611/IFT729 Séance INF749 Contenu INF749
S00 IFT611/IFT729, 8 janvier

Au menu :

  • Mise en place d'éléments de vocabulaire
  • Discussion sur la distinction importante entre programmes en temps réel et programmes rapides, du moins dans le cas des STR stricts, et sur les nuances dans le discours quant à la question même de l'idée de temps réel, qui porte plusieurs sens, affectant différemment les gens en fonction de leur domaine d'expertise et de leur milieu de travail. Le tout dans le but de situer les un(e)s et les autres quant au contenu du cours et quant au projet de session qui vous est proposé
  • Présentation du cours et des approches choisies pour y traiter des sujets qui nous y tiennent à coeur
  • Bref tableau de façons de faire que nous utiliserons dans les exemples proposés en classe et dans les notes de cours. Ceci inclut des exemples utilisant entre autres des foncteurs, de la généricité par des templates, un algorithme standard (for_each()) et, pour le plaisir, une λ-expression
  • Bref comparatif de programmes bloquant et non-bloquant
  • Bref comparatif de programmes avec tâche s'exécutant à rythme régulier et à rythme constant

Petit complément : j'ai rédigé un petit comparatif de « performances » pour certaines opérations types applicables à un tableau brut, alloué dynamiquement, et à un vecteur standard. Je ne l'ai pas indiqué en classe encore, mais il s'avère que, lorsqu'il est bien utilisé, le vecteur est presque toujours aussi rapide – ou plus rapide! – que son substrat, le tableau brut, parce que le code sous-jacent est extrêmement sophistiqué et parce que nous, programmeurs de tous les jours, ne prenons pas chaque fois le soin de manipuler nos tableaux comme le font en tout temps les concepteurs du vecteur standard.

Le dire, bien sûr, c'est une chose, mais le démontrer, c'est mieux, alors l'exemple vous attend ici si vous êtes intéressé(e).

Dans les notes de cours, vous trouverez de la matière en lien avec cette séance dans STR – Volume 00 (vocabulaire, donc les pages ≈5..35).

Truc important : bien que je ne l'aie pas mentionné explicitement pendant la séance, ce cours mettra l'accent sur plusieurs facettes de la conception et du développement de STR, dont :

  • Le respect en tout temps des contraintes fixées a priori.. Nous en avons discuté quelque peu en classe (itérer à rythme régulier ou constant, s'exécuter de manière brève, démarrer immédiatement – faible latence)
  • La résilience. Un STR étant fréquemment un système dont dépend l'intégrité des gens et du matériel, sa résilience tend à être une caractéristique plus que souhaitable
  • La concision. étant souvent destinés à opérer des systèmes embarqués ou à s'exécuter sur du matériel plus... humble, mais aux caractéristiques d'exécution plus prévisibles que ne le sont celles du matériel très sophistiqué mis à notre disposition aujourd'hui, il n'est pas rare qu'un STR doive occuper un espace réduit en mémoire

Ne vous étonnez donc pas de voir ces thématiques apparaître de manière récurrente dans notre discours.

S00 INF749, 9 janvier

Au menu :

  • Mise en place d'éléments de vocabulaire
  • Discussion sur la distinction importante entre programmes en temps réel et programmes rapides, du moins dans le cas des STR stricts, et sur les nuances dans le discours quant à la question même de l'idée de temps réel, qui porte plusieurs sens, affectant différemment les gens en fonction de leur domaine d'expertise et de leur milieu de travail. Le tout dans le but de situer les un(e)s et les autres quant au contenu du cours et quant au projet de session qui vous est proposé
  • Présentation du cours et des approches choisies pour y traiter des sujets qui nous y tiennent à coeur
  • Bref tableau de façons de faire que nous utiliserons dans les exemples proposés en classe et dans les notes de cours. Ceci inclut des exemples utilisant entre autres des foncteurs, de la généricité par des templates, un algorithme standard (for_each()) et, pour le plaisir, une λ-expression
  • Bref comparatif de programmes bloquant et non-bloquant
  • Bref comparatif de programmes avec tâche s'exécutant à rythme régulier et à rythme constant

Petit complément : j'ai rédigé un petit comparatif de « performances » pour certaines opérations types applicables à un tableau brut, alloué dynamiquement, et à un vecteur standard. Je ne l'ai pas indiqué en classe encore, mais il s'avère que, lorsqu'il est bien utilisé, le vecteur est presque toujours aussi rapide – ou plus rapide! – que son substrat, le tableau brut, parce que le code sous-jacent est extrêmement sophistiqué et parce que nous, programmeurs de tous les jours, ne prenons pas chaque fois le soin de manipuler nos tableaux comme le font en tout temps les concepteurs du vecteur standard.

Le dire, bien sûr, c'est une chose, mais le démontrer, c'est mieux, alors l'exemple vous attend ici si vous êtes intéressé(e).

Dans les notes de cours, vous trouverez de la matière en lien avec cette séance dans STR – Volume 00 (vocabulaire, donc les pages ≈5..35).

Truc important : bien que je ne l'aie pas mentionné explicitement pendant la séance, ce cours mettra l'accent sur plusieurs facettes de la conception et du développement de STR, dont :

  • Le respect en tout temps des contraintes fixées a priori.. Nous en avons discuté quelque peu en classe (itérer à rythme régulier ou constant, s'exécuter de manière brève, démarrer immédiatement – faible latence)
  • La résilience. Un STR étant fréquemment un système dont dépend l'intégrité des gens et du matériel, sa résilience tend à être une caractéristique plus que souhaitable
  • La concision. étant souvent destinés à opérer des systèmes embarqués ou à s'exécuter sur du matériel plus... humble, mais aux caractéristiques d'exécution plus prévisibles que ne le sont celles du matériel très sophistiqué mis à notre disposition aujourd'hui, il n'est pas rare qu'un STR doive occuper un espace réduit en mémoire

Ne vous étonnez donc pas de voir ces thématiques apparaître de manière récurrente dans notre discours.

S01 IFT611/IFT729, 15 janvier

Au menu, d'une manière teintée par notre besoin d'avoir une approche appropriée pour le développement de STR :

Mise en place d'un premier conteneur (un tableau d'entiers) implémenté selon les conventions du langage C++, en portant une attention particulière aux considérations de robustesse et d'efficacité, incluant :

  • Quelques types internes et publics
  • Quelques méthodes clés (size(), empty(), capacity())
  • Les accès aux itérateurs (begin() et end(), en déclinaison const et non-const; cbegin() et cend())
  • Quelques constructeurs (défaut, paramétrique, copie, mouvement)
  • L'affectation (de copie, de mouvement)
  • Le destructeur

Munis des idées et techniques mises de l'avant dans cette séance, vous devriez être en mesure de comprendre les exemples dans STR – Volume 01 (voir le code des cas sous étude). Profitez de l'opportunité pour expérimenter avec ces propositions de tests, pour les modifier, pour essayer de comprendre les métriques que vous parviendrez à en tirer, etc.

Le code utilisé pour la classe Tableau, de même que le test qui met en relief l'impact du mouvement sur la vitesse d'un programme, suit :

#include <cstddef>
#include <algorithm>

class Tableau {
public:
   using value_type = int;
   using size_type = std::size_t;
   using pointer = value_type*;
   using const_pointer = const value_type*;
   using reference = value_type&;
   using const_reference = const value_type&;
private:
   pointer elems {};
   size_type nelems {},
             cap {};
public:
   size_type size() const noexcept {
      return nelems;
   }
   size_type capacity() const noexcept {
      return cap;
   }
   bool empty() const noexcept {
      return !size();
   }
private:
   bool full() const noexcept {
      return size() == capacity();
   }
public:
   using iterator = pointer;
   using const_iterator = const_pointer;
   iterator begin() noexcept {
      return elems;
   }
   const_iterator begin() const noexcept {
      return elems;
   }
   const_iterator cbegin() const noexcept {
      return begin();
   }
   iterator end() noexcept {
      return begin() + size();
   }
   const_iterator end() const noexcept {
      return begin() + size();
   }
   const_iterator cend() const noexcept {
      return end();
   }
   Tableau() = default;
   Tableau(size_type n, const_reference init)
      : cap{ n }, nelems{ n }, elems{ new value_type[n] } {
      std::fill(begin(), end(), init);
   }
   Tableau(const Tableau &autre)
      : elems{ new value_type[autre.size()] },
        nelems{ autre.size() }, cap{ autre.size() } {
      std::copy(autre.begin(), autre.end(), begin());
   }
   ~Tableau() {
      delete [] elems;
   }
   void swap(Tableau &autre) noexcept {
      using std::swap;
      swap(elems, autre.elems);
      swap(nelems, autre.nelems);
      swap(cap, autre.cap);
   }
   Tableau& operator=(const Tableau &autre) {
      Tableau{ autre }.swap(*this);
      return *this;
   }
   reference operator[](size_type n) noexcept {
      return elems[n];
   }
   const_reference operator[](size_type n) const noexcept {
      return elems[n];
   }
   void push_back(const_reference val) {
      if (full()) grow();
      elems[size()] = val;
      ++nelems;
   }
private:
   void grow() {
      const size_type new_cap = capacity()? capacity() * 2 : 42; // hum
      auto p = new value_type[new_cap];
      std::copy(begin(), end(), p);
      delete[] elems;
      cap = new_cap;
      elems = p;
   }
public:
   bool operator==(const Tableau &autre) const noexcept {
      return size() == autre.size() &&
             std::equal(begin(), end(), autre.begin());
   }
   bool operator!=(const Tableau &autre) const noexcept {
      return !(*this == autre);
   }
   Tableau(Tableau &&autre) noexcept
      : elems{ autre.elems }, nelems{ autre.nelems }, cap{ autre.cap } {
      autre.elems = {};
      autre.nelems = {};
      autre.cap = {};
   }
   Tableau& operator=(Tableau &&autre) noexcept {
      delete[] elems;
      elems = autre.elems;
      nelems = autre.nelems;
      cap = autre.cap;
      autre.elems = {};
      autre.nelems = {};
      autre.cap = {};
      return *this;
   }
};


#include <vector>
#include <numeric>
#include <chrono>
#include <iostream>
using namespace std;
using namespace std::chrono;

template <class F, class ... Args>
   auto tester(F f, Args &&... args) {
      auto pre = high_resolution_clock::now();
      auto res = f(std::forward<Args>(args)...);
      auto post = high_resolution_clock::now();
      return make_pair(res, post - pre);
   }

vector<double> f(vector<double> v) {
   transform(begin(v), end(v), begin(v), [](double x) { return sqrt(x); });
   return v;
}

void g(vector<double> &v) {
   transform(begin(v), end(v), begin(v), [](double x) { return sqrt(x); });
}

int main() {
   enum { N = 50'000'000 };
   vector<double> v(N);
   iota(begin(v), end(v), 1.0);
   auto r0 = tester([v]() mutable {
      v = f(v);
      return v.back();
   });
   auto r1 = tester([v]() mutable {
      g(v);
      return v.back();
   });
   auto r2 = tester([v]() mutable {
      v = f(std::move(v));
      return v.back();
   });
   cout << "v = f(v)            : " << duration_cast<microseconds>(r0.second).count()
        << " us." << endl;
   cout << "g(v)                : " << duration_cast<microseconds>(r1.second).count()
        << " us." << endl;
   cout << "v = f(srd::move(v)) : " << duration_cast<microseconds>(r2.second).count()
        << " us." << endl;
}

Dans les notes de cours, comme mentionné dans le paragraphe précédent, vous trouverez de la matière en lien avec cette séance dans STR – Volume 01.

Complément culturel : même les compilateurs ont des bogues (quoiqu'ils en aient sans doute moins que vous ne le croyez parfois). Pour en savoir plus sur un bogue du compilateur sous-jacent à Visual Studio, décrit (avec sa solution) par Stephan T. Lavavej, voir http://channel9.msdn.com/Series/C9-Lectures-Stephan-T-Lavavej-Core-C- (en particulier la partie de ).

S01 INF749, 16 janvier

Au menu, d'une manière teintée par notre besoin d'avoir une approche appropriée pour le développement de STR :

Mise en place d'un premier conteneur (un tableau d'entiers) implémenté selon les conventions du langage C++, en portant une attention particulière aux considérations de robustesse et d'efficacité, incluant :

  • Quelques types internes et publics
  • Quelques méthodes clés (size(), empty(), capacity())
  • Les accès aux itérateurs (begin() et end(), en déclinaison const et non-const; cbegin() et cend())
  • Quelques constructeurs (défaut, paramétrique, copie, mouvement)
  • L'affectation (de copie, de mouvement)
  • Le destructeur

Munis des idées et techniques mises de l'avant dans cette séance, vous devriez être en mesure de comprendre les exemples dans STR – Volume 01 (voir le code des cas sous étude). Profitez de l'opportunité pour expérimenter avec ces propositions de tests, pour les modifier, pour essayer de comprendre les métriques que vous parviendrez à en tirer, etc.

Le code utilisé pour la classe Tableau, de même que le test qui met en relief l'impact du mouvement sur la vitesse d'un programme, suit :

#include <cstddef>
#include <algorithm>

class Tableau {
public:
   using value_type = int;
   using size_type = std::size_t;
   using pointer = value_type*;
   using const_pointer = const value_type*;
   using reference = value_type&;
   using const_reference = const value_type&;
private:
   pointer elems {};
   size_type nelems {},
             cap {};
public:
   size_type size() const noexcept {
      return nelems;
   }
   size_type capacity() const noexcept {
      return cap;
   }
   bool empty() const noexcept {
      return !size();
   }
private:
   bool full() const noexcept {
      return size() == capacity();
   }
public:
   using iterator = pointer;
   using const_iterator = const_pointer;
   iterator begin() noexcept {
      return elems;
   }
   const_iterator begin() const noexcept {
      return elems;
   }
   const_iterator cbegin() const noexcept {
      return begin();
   }
   iterator end() noexcept {
      return begin() + size();
   }
   const_iterator end() const noexcept {
      return begin() + size();
   }
   const_iterator cend() const noexcept {
      return end();
   }
   Tableau() = default;
   Tableau(size_type n, const_reference init)
      : cap{ n }, nelems{ n }, elems{ new value_type[n] } {
      std::fill(begin(), end(), init);
   }
   Tableau(const Tableau &autre)
      : elems{ new value_type[autre.size()] },
        nelems{ autre.size() }, cap{ autre.size() } {
      std::copy(autre.begin(), autre.end(), begin());
   }
   ~Tableau() {
      delete [] elems;
   }
   void swap(Tableau &autre) noexcept {
      using std::swap;
      swap(elems, autre.elems);
      swap(nelems, autre.nelems);
      swap(cap, autre.cap);
   }
   Tableau& operator=(const Tableau &autre) {
      Tableau{ autre }.swap(*this);
      return *this;
   }
   reference operator[](size_type n) noexcept {
      return elems[n];
   }
   const_reference operator[](size_type n) const noexcept {
      return elems[n];
   }
   void push_back(const_reference val) {
      if (full()) grow();
      elems[size()] = val;
      ++nelems;
   }
private:
   void grow() {
      const size_type new_cap = capacity()? capacity() * 2 : 42; // hum
      auto p = new value_type[new_cap];
      std::copy(begin(), end(), p);
      delete[] elems;
      cap = new_cap;
      elems = p;
   }
public:
   bool operator==(const Tableau &autre) const noexcept {
      return size() == autre.size() &&
             std::equal(begin(), end(), autre.begin());
   }
   bool operator!=(const Tableau &autre) const noexcept {
      return !(*this == autre);
   }
   Tableau(Tableau &&autre) noexcept
      : elems{ autre.elems }, nelems{ autre.nelems }, cap{ autre.cap } {
      autre.elems = {};
      autre.nelems = {};
      autre.cap = {};
   }
   Tableau& operator=(Tableau &&autre) noexcept {
      delete[] elems;
      elems = autre.elems;
      nelems = autre.nelems;
      cap = autre.cap;
      autre.elems = {};
      autre.nelems = {};
      autre.cap = {};
      return *this;
   }
};


#include <vector>
#include <numeric>
#include <chrono>
#include <iostream>
using namespace std;
using namespace std::chrono;

template <class F, class ... Args>
   auto tester(F f, Args &&... args) {
      auto pre = high_resolution_clock::now();
      auto res = f(std::forward<Args>(args)...);
      auto post = high_resolution_clock::now();
      return make_pair(res, post - pre);
   }

vector<double> f(vector<double> v) {
   transform(begin(v), end(v), begin(v), [](double x) { return sqrt(x); });
   return v;
}

void g(vector<double> &v) {
   transform(begin(v), end(v), begin(v), [](double x) { return sqrt(x); });
}

int main() {
   enum { N = 50'000'000 };
   vector<double> v(N);
   iota(begin(v), end(v), 1.0);
   auto r0 = tester([v]() mutable {
      v = f(v);
      return v.back();
   });
   auto r1 = tester([v]() mutable {
      g(v);
      return v.back();
   });
   auto r2 = tester([v]() mutable {
      v = f(std::move(v));
      return v.back();
   });
   cout << "v = f(v)            : " << duration_cast<microseconds>(r0.second).count()
        << " us." << endl;
   cout << "g(v)                : " << duration_cast<microseconds>(r1.second).count()
        << " us." << endl;
   cout << "v = f(srd::move(v)) : " << duration_cast<microseconds>(r2.second).count()
        << " us." << endl;
}

Pour le code du tableau générique tel que vu en classe, voici :

#include <cstddef>
#include <algorithm>
#include <initializer_list>
template <class T>
class Tableau {
public:
   using value_type = T;
   using size_type = std::size_t;
   using pointer = value_type*;
   using const_pointer = const value_type*;
   using reference = value_type&;
   using const_reference = const value_type&;
private:
   pointer elems {};
   size_type nelems {},
             cap {};
public:
   size_type size() const noexcept {
      return nelems;
   }
   size_type capacity() const noexcept {
      return cap;
   }
   bool empty() const noexcept {
      return !size();
   }
private:
   bool full() const noexcept {
      return size() == capacity();
   }
public:
   using iterator = pointer;
   using const_iterator = const_pointer;
   iterator begin() noexcept {
      return elems;
   }
   const_iterator begin() const noexcept {
      return elems;
   }
   const_iterator cbegin() const noexcept {
      return begin();
   }
   iterator end() noexcept {
      return begin() + size();
   }
   const_iterator end() const noexcept {
      return begin() + size();
   }
   const_iterator cend() const noexcept {
      return end();
   }
   Tableau() = default;
   Tableau(std::initializer_list<value_type> lst)
      : elems{ new value_type[lst.size()] },
        nelems{ lst.size() }, cap{ lst.size() } {
      try {
         std::copy(lst.begin(), lst.end(), begin());
      } catch(...) {
         delete[] elems;
         throw;
      }
   }
   Tableau(size_type n, const value_type &init)
      : cap{ n }, nelems{ n }, elems{ new value_type[n] } {
      try {
         std::fill(begin(), end(), init);
      } catch(...) {
         delete[] elems;
         throw;
      }
   }
   Tableau(const Tableau &autre)
      : elems{ new value_type[autre.size()] },
        nelems{ autre.size() }, cap{ autre.size() } {
      try {
         std::copy(autre.begin(), autre.end(), begin());
      } catch(...) {
         delete[] elems;
         throw;
      }
   }
   template <class It>
      Tableau(It debut, It fin)
         : nelems{ std::distance(debut, fin) } {
         cap = size();
         elems = new value_type[size()];
         try {
            std::copy(debut, fin, begin());
         } catch(...) {
            delete[] elems;
            throw;
         }
      }
   template <class U>
      Tableau(const Tableau<U> &autre)
         : cap{ autre.size() }, nelems{ autre.size() },
           elems{ new value_type[autre.size()] } {
         try {
            std::copy(autre.begin(), autre.end(), begin());
         } catch(...) {
            delete[] elems;
            throw;
         }
      }
   template <class U>
      Tableau& operator=(const Tableau<U> &autre) {
         Tableau{ autre }.swap(*this);
         return *this;
      }
   ~Tableau() {
      delete[] elems;
   }
   void swap(Tableau &autre) noexcept {
      using std::swap;
      swap(elems, autre.elems);
      swap(nelems, autre.nelems);
      swap(cap, autre.cap);
   }
   Tableau& operator=(const Tableau &autre) {
      Tableau{ autre }.swap(*this);
      return *this;
   }
   reference operator[](size_type n) noexcept {
      return elems[n];
   }
   const_reference operator[](size_type n) const noexcept {
      return elems[n];
   }
   void push_back(const_reference val) {
      if (full()) grow();
      elems[size()] = val;
      ++nelems;
   }
private:
   void grow() {
      const size_type new_cap = capacity() ? capacity() * 2 : 42; // hum
      auto p = new value_type[new_cap];
      try {
         std::copy(begin(), end(), p);
         delete[]elems;
         cap = new_cap;
         elems = p;
      } catch(...) {
         delete [] p;
         throw;
      }
   }
public:
   bool operator==(const Tableau &autre) const  {
      return size() == autre.size() &&
             std::equal(begin(), end(), autre.begin());
   }
   bool operator!=(const Tableau &autre) const  {
      return !(*this == autre);
   }
   Tableau(Tableau &&autre) noexcept
      : elems{ autre.elems }, nelems{ autre.nelems }, cap{ autre.cap } {
      autre.elems = {};
      autre.nelems = {};
      autre.cap = {};
   }
   Tableau& operator=(Tableau &&autre) noexcept {
      delete[] elems;
      elems = autre.elems;
      nelems = autre.nelems;
      cap = autre.cap;
      autre.elems = {};
      autre.nelems = {};
      autre.cap = {};
      return *this;
   }
};

Dans les notes de cours, comme mentionné dans le paragraphe précédent, vous trouverez de la matière en lien avec cette séance dans STR – Volume 01.

Complément culturel : même les compilateurs ont des bogues (quoiqu'ils en aient sans doute moins que vous ne le croyez parfois). Pour en savoir plus sur un bogue du compilateur sous-jacent à Visual Studio, décrit (avec sa solution) par Stephan T. Lavavej, voir http://channel9.msdn.com/Series/C9-Lectures-Stephan-T-Lavavej-Core-C- (en particulier la partie de ).

S02 – IFT611/IFT729, 22 janvier

Au menu :

  • Q00
  • Compléter l'implémentation du tableau d'entiers débuté à S01 :
    • méthode push_back() et implémentation d'un algorithme de croissance

Notez que notre examen d'implémentations génériques d'un tableau suivra sous peu; nous avons quelques menus détails à explorer auparavant.

  • Survol rapide des traits
  • Petite excursion dans le monde des itérateurs : catégories, opérations, forces, faiblesses, usages
  • Poursuite de l'examen des itérateurs, en lien avec la fonction distance()
  • L'exemple amusant d'un trait est_convertible
  • Poursuite de l'examen des itérateurs, en lien avec le calcul de la moyenne

La fonction distance() est un cas d'espèce intéressant, tiré du standard lui-même, qui exploite les traits et les catégories pour réaliser une optimisation presque obscène. J'aime bien faire le tour de cette approche, qui peut être appliquée à bien d'autres trucs, avec des gens qui, comme vous, doivent écrire des programmes dont les performances sont sans compromis.

Les plus curieuses et les plus curieux remarqueront que les fonctions advance(), next() et prev() appliquent les mêmes techniques d'optimisation que sa cousine distance().

Dans les notes de cours, vous trouverez de la matière en lien avec cette séance dans STR – Volume 00, annexes 00 et 01, mais vous en trouverez surtout dans les liens ci-dessus.

S02 INF749, 23 janvier

Au menu :

La fonction distance() est un cas d'espèce intéressant, tiré du standard lui-même, qui exploite les traits et les catégories pour réaliser une optimisation presque obscène. J'aime bien faire le tour de cette approche, qui peut être appliquée à bien d'autres trucs, avec des gens qui, comme vous, doivent écrire des programmes dont les performances sont sans compromis.

Les plus curieuses et les plus curieux remarqueront que les fonctions advance(), next() et prev() appliquent les mêmes techniques d'optimisation que sa cousine distance().

Dans les notes de cours, vous trouverez de la matière en lien avec cette séance dans STR – Volume 00, annexes 00 et 01, mais vous en trouverez surtout dans les liens ci-dessus.

S03 – IFT611/IFT729, 29 janvier

Au menu :

Pour le code du tableau générique tel que vu en classe, voici :

#include <cstddef>
#include <algorithm>
#include <initializer_list>
template <class T>
class Tableau {
public:
   using value_type = T;
   using size_type = std::size_t;
   using pointer = value_type*;
   using const_pointer = const value_type*;
   using reference = value_type&;
   using const_reference = const value_type&;
private:
   pointer elems {};
   size_type nelems {},
             cap {};
public:
   size_type size() const noexcept {
      return nelems;
   }
   size_type capacity() const noexcept {
      return cap;
   }
   bool empty() const noexcept {
      return !size();
   }
private:
   bool full() const noexcept {
      return size() == capacity();
   }
public:
   using iterator = pointer;
   using const_iterator = const_pointer;
   iterator begin() noexcept {
      return elems;
   }
   const_iterator begin() const noexcept {
      return elems;
   }
   const_iterator cbegin() const noexcept {
      return begin();
   }
   iterator end() noexcept {
      return begin() + size();
   }
   const_iterator end() const noexcept {
      return begin() + size();
   }
   const_iterator cend() const noexcept {
      return end();
   }
   Tableau() = default;
   Tableau(std::initializer_list<value_type> lst)
      : elems{ new value_type[lst.size()] },
        nelems{ lst.size() }, cap{ lst.size() } {
      try {
         std::copy(lst.begin(), lst.end(), begin());
      } catch(...) {
         delete[] elems;
         throw;
      }
   }
   Tableau(size_type n, const value_type &init)
      : cap{ n }, nelems{ n }, elems{ new value_type[n] } {
      try {
         std::fill(begin(), end(), init);
      } catch(...) {
         delete[] elems;
         throw;
      }
   }
   Tableau(const Tableau &autre)
      : elems{ new value_type[autre.size()] },
        nelems{ autre.size() }, cap{ autre.size() } {
      try {
         std::copy(autre.begin(), autre.end(), begin());
      } catch(...) {
         delete[] elems;
         throw;
      }
   }
   template <class It>
      Tableau(It debut, It fin)
         : nelems{ std::distance(debut, fin) } {
         cap = size();
         elems = new value_type[size()];
         try {
            std::copy(debut, fin, begin());
         } catch(...) {
            delete[] elems;
            throw;
         }
      }
   template <class U>
      Tableau(const Tableau<U> &autre)
         : cap{ autre.size() }, nelems{ autre.size() },
           elems{ new value_type[autre.size()] } {
         try {
            std::copy(autre.begin(), autre.end(), begin());
         } catch(...) {
            delete[] elems;
            throw;
         }
      }
   template <class U>
      Tableau& operator=(const Tableau<U> &autre) {
         Tableau{ autre }.swap(*this);
         return *this;
      }
   ~Tableau() {
      delete[] elems;
   }
   void swap(Tableau &autre) noexcept {
      using std::swap;
      swap(elems, autre.elems);
      swap(nelems, autre.nelems);
      swap(cap, autre.cap);
   }
   Tableau& operator=(const Tableau &autre) {
      Tableau{ autre }.swap(*this);
      return *this;
   }
   reference operator[](size_type n) noexcept {
      return elems[n];
   }
   const_reference operator[](size_type n) const noexcept {
      return elems[n];
   }
   void push_back(const_reference val) {
      if (full()) grow();
      elems[size()] = val;
      ++nelems;
   }
private:
   void grow() {
      const size_type new_cap = capacity() ? capacity() * 2 : 42; // hum
      auto p = new value_type[new_cap];
      try {
         std::copy(begin(), end(), p);
         delete[]elems;
         cap = new_cap;
         elems = p;
      } catch(...) {
         delete [] p;
         throw;
      }
   }
public:
   bool operator==(const Tableau &autre) const  {
      return size() == autre.size() &&
             std::equal(begin(), end(), autre.begin());
   }
   bool operator!=(const Tableau &autre) const  {
      return !(*this == autre);
   }
   Tableau(Tableau &&autre) noexcept
      : elems{ autre.elems }, nelems{ autre.nelems }, cap{ autre.cap } {
      autre.elems = {};
      autre.nelems = {};
      autre.cap = {};
   }
   Tableau& operator=(Tableau &&autre) noexcept {
      delete[] elems;
      elems = autre.elems;
      nelems = autre.nelems;
      cap = autre.cap;
      autre.elems = {};
      autre.nelems = {};
      autre.cap = {};
      return *this;
   }
};

Dans les notes de cours, vous trouverez de la matière en lien avec cette séance dans STR – Volume 00, annexes 00 et 01, mais vous en trouverez surtout dans les liens ci-dessus.

S03 INF749, 30 janvier

Au menu :

Pour le code (plus simple, tout aussi efficace) que l'on obtient si on ajoute un unique_ptr à notre tableau générique de la séance S01, voici :

#include <cstddef>
#include <algorithm>
#include <initializer_list>
#include <memory>

template <class T>
class Tableau {
public:
   using value_type = T;
   using size_type = std::size_t;
   using pointer = value_type*;
   using const_pointer = const value_type*;
   using reference = value_type&;
   using const_reference = const value_type&;
private:
   std::unique_ptr<value_type[]> elems;
   size_type nelems,
      cap;
public:
   size_type size() const noexcept {
      return nelems;
   }
   size_type capacity() const noexcept {
      return cap;
   }
   bool empty() const noexcept {
      return !size();
   }
private:
   bool full() const noexcept {
      return size() == capacity();
   }
public:
   using iterator = pointer;
   using const_iterator = const_pointer;
   iterator begin() noexcept {
      return elems.get();
   }
   const_iterator begin() const noexcept {
      return elems.get();
   }
   const_iterator cbegin() const noexcept {
      return elems.get();
   }
   iterator end() noexcept {
      return begin() + size();
   }
   const_iterator end() const noexcept {
      return begin() + size();
   }
   const_iterator cend() const noexcept {
      return begin() + size();
   }
   Tableau() noexcept : elems{}, nelems{}, cap{} {
   }
   Tableau(std::initializer_list<value_type> lst)
      : elems{ new value_type[lst.size()] },
        nelems{ lst.size() }, cap{ lst.size() } {
      std::copy(lst.begin(), lst.end(), begin());
   }
   Tableau(size_type n, const value_type &init)
      : cap{ n }, nelems{ n }, elems{ new value_type[n] } {
      std::fill(begin(), end(), init);
   }
   Tableau(const Tableau &autre)
      : elems{ new value_type[autre.size()] },
        nelems{ autre.size() }, cap{ autre.size() } {
      std::copy(autre.begin(), autre.end(), begin());
   }
   template <class It>
   Tableau(It debut, It fin)
      : nelems{ std::distance(debut, fin) } {
      cap = size();
      elems = unique_ptr<T>{ new value_type[size()] };
      std::copy(debut, fin, begin());
   }
   template <class U>
   Tableau(const Tableau<U> &autre)
      : cap{ autre.size() }, nelems{ autre.size() },
        elems{ new value_type[autre.size()] } {
      std::copy(autre.begin(), autre.end(), begin());
   }
   template <class U>
   Tableau& operator=(const Tableau<U> &autre) {
      Tableau{ autre }.swap(*this);
      return *this;
   }
   ~Tableau() = default;
   void swap(Tableau &autre) noexcept {
      using std::swap;
      swap(elems, autre.elems);
      swap(nelems, autre.nelems);
      swap(cap, autre.cap);
   }
   Tableau& operator=(const Tableau &autre) {
      Tableau{ autre }.swap(*this);
      return *this;
   }
   reference operator[](size_type n) noexcept {
      return elems[n];
   }
   const_reference operator[](size_type n) const noexcept {
      return elems[n];
   }
   void push_back(const_reference val) {
      if (full()) grow();
      elems[size()] = val;
      ++nelems;
   }
private:
   void grow() {
      using namespace std;
      const size_type new_cap = capacity() ? capacity() * 2 : 42; // hum
      unique_ptr<value_type[]> p{ new value_type[new_cap] };
      copy(begin(), end(), p);
      cap = new_cap;
      swap(p, elems);
   }
public:
   bool operator==(const Tableau &autre) const  {
      return size() == autre.size() &&
         std::equal(begin(), end(), autre.begin());
   }
   bool operator!=(const Tableau &autre) const  {
      return !(*this == autre);
   }
   Tableau(Tableau &&autre) = default;
   Tableau& operator=(Tableau &&autre) = default;
};

Dans les notes de cours, vous trouverez de la matière en lien avec cette séance dans STR – Volume 00, annexes 00 et 01, mais vous en trouverez surtout dans les liens ci-dessus.

S04 IFT611/IFT729, 5 février

Au menu :

Pour le code (plus simple, tout aussi efficace) que l'on obtient si on ajoute un unique_ptr à notre tableau générique, voici :

#include <cstddef>
#include <algorithm>
#include <initializer_list>
#include <memory>

template <class T>
class Tableau {
public:
   using value_type = T;
   using size_type = std::size_t;
   using pointer = value_type*;
   using const_pointer = const value_type*;
   using reference = value_type&;
   using const_reference = const value_type&;
private:
   std::unique_ptr<value_type[]> elems;
   size_type nelems,
      cap;
public:
   size_type size() const noexcept {
      return nelems;
   }
   size_type capacity() const noexcept {
      return cap;
   }
   bool empty() const noexcept {
      return !size();
   }
private:
   bool full() const noexcept {
      return size() == capacity();
   }
public:
   using iterator = pointer;
   using const_iterator = const_pointer;
   iterator begin() noexcept {
      return elems.get();
   }
   const_iterator begin() const noexcept {
      return elems.get();
   }
   const_iterator cbegin() const noexcept {
      return elems.get();
   }
   iterator end() noexcept {
      return begin() + size();
   }
   const_iterator end() const noexcept {
      return begin() + size();
   }
   const_iterator cend() const noexcept {
      return begin() + size();
   }
   Tableau() noexcept : elems{}, nelems{}, cap{} {
   }
   Tableau(std::initializer_list<value_type> lst)
      : elems{ new value_type[lst.size()] },
        nelems{ lst.size() }, cap{ lst.size() } {
      std::copy(lst.begin(), lst.end(), begin());
   }
   Tableau(size_type n, const value_type &init)
      : cap{ n }, nelems{ n }, elems{ new value_type[n] } {
      std::fill(begin(), end(), init);
   }
   Tableau(const Tableau &autre)
      : elems{ new value_type[autre.size()] },
        nelems{ autre.size() }, cap{ autre.size() } {
      std::copy(autre.begin(), autre.end(), begin());
   }
   template <class It>
   Tableau(It debut, It fin)
      : nelems{ std::distance(debut, fin) } {
      cap = size();
      elems = unique_ptr<T>{ new value_type[size()] };
      std::copy(debut, fin, begin());
   }
   template <class U>
   Tableau(const Tableau<U> &autre)
      : cap{ autre.size() }, nelems{ autre.size() },
        elems{ new value_type[autre.size()] } {
      std::copy(autre.begin(), autre.end(), begin());
   }
   template <class U>
   Tableau& operator=(const Tableau<U> &autre) {
      Tableau{ autre }.swap(*this);
      return *this;
   }
   ~Tableau() = default;
   void swap(Tableau &autre) noexcept {
      using std::swap;
      swap(elems, autre.elems);
      swap(nelems, autre.nelems);
      swap(cap, autre.cap);
   }
   Tableau& operator=(const Tableau &autre) {
      Tableau{ autre }.swap(*this);
      return *this;
   }
   reference operator[](size_type n) noexcept {
      return elems[n];
   }
   const_reference operator[](size_type n) const noexcept {
      return elems[n];
   }
   void push_back(const_reference val) {
      if (full()) grow();
      elems[size()] = val;
      ++nelems;
   }
private:
   void grow() {
      using namespace std;
      const size_type new_cap = capacity() ? capacity() * 2 : 42; // hum
      unique_ptr<value_type[]> p{ new value_type[new_cap] };
      copy(begin(), end(), p);
      cap = new_cap;
      swap(p, elems);
   }
public:
   bool operator==(const Tableau &autre) const  {
      return size() == autre.size() &&
         std::equal(begin(), end(), autre.begin());
   }
   bool operator!=(const Tableau &autre) const  {
      return !(*this == autre);
   }
   Tableau(Tableau &&autre) = default;
   Tableau& operator=(Tableau &&autre) = default;
};
  • Premiers pas dans la gestion intelligente de la mémoire :
    • survol des mécanismes inhérents au langage C (std::malloc() et std::free()) et des implications de leurs signatures respectives
    • spécialisation des services de C++ :
      • de manière globale, avec pour exemple un système primitif de détection de fuites
      • de manière no-throw
      • de manière positionnelle... Ne remplacez pas la spécialisation existante dans ce cas, je vous en prie!
      • avec assistants pour mémoire spécialisée
      • sous forme de méthodes d'instance
    • les arénas, qui permettent de gérer des zones de mémoire choisies

L'optique poursuivie est :

  • Explorer la question de la résilience des programmes plus en détail
  • Voir comment il est possible de contrôler finement les mécanismes d'allocation (choisir le lieu, la façon, les stratégies, etc.)
  • Montrer comment il est possible d'en arriver à des mécanismes d'allocation dynamique de mémoire qui soient déterministes (quand le contexte s'y prête)
S04 INF749, 6 février

Au menu :

  • Q02
  • Retour sur Q02, avec une brève discussion des options que cette question soulève
  • Brève discussion de l'importance d'échouer tôt, du moins lorsqu'on échoue :
    • privilégier un plantage à la compilation, avec static_assert, si cela s'avère possible, sinon
    • privilégier un plantage en laboratoire, peut-être avec assert(), si le problème détecté est dynamique et irrécupérable,ou
    • privilégier un plantage en laboratoire, en levant une exception, si le problème détecté est dynamique mais nous semble mener à une situation que le client ne peut ignorer et d'où le code client est susceptible de récupérer; et enfin
    • retourner des codes d'erreur pour les situations problématiques que le client peut ignorer sans que cela n'entraîne de conséquences trop fâcheuses
      • notez que les exceptions sont la manière correcte de signaler un problème dans un constructeur (à moins que la situation ne soit irrécupérable – voir ci-dessus), ceux-ci n'ayant pas de valeur de retour, mais
      • notez aussi que les exceptions ne sont pas du tout convenables dans un destructeur, ceci rendant le programme inutilisable

Notez que les diapositives électroniques utilisées pour décrire (entre autres) les approches au traitement des erreurs dans les cas où les exceptions ne sont pas souhaitées sont disponibles sur Amsterdam-2016-PR-The-Exception-Situation.pdf. Elles sont en anglais; c'est le matériel sous-jacent à une conférence que j'ai donné en novembre 2016 à Amsterdam.

  • Premiers pas dans la gestion intelligente de la mémoire :
    • survol des mécanismes inhérents au langage C (std::malloc() et std::free()) et des implications de leurs signatures respectives
    • spécialisation des services de C++ :
      • de manière globale, avec pour exemple un système primitif de détection de fuites
      • de manière no-throw
      • de manière positionnelle... Ne remplacez pas la spécialisation existante dans ce cas, je vous en prie!
      • avec assistants pour mémoire spécialisée
      • sous forme de méthodes d'instance
    • les arénas, qui permettent de gérer des zones de mémoire choisies
  • Survol de considérations d'alignement en mémoire

L'optique poursuivie est :

  • Explorer la question de la résilience des programmes plus en détail
  • Voir comment il est possible de contrôler finement les mécanismes d'allocation (choisir le lieu, la façon, les stratégies, etc.)
  • Montrer comment il est possible d'en arriver à des mécanismes d'allocation dynamique de mémoire qui soient déterministes (quand le contexte s'y prête)
S05 IFT611/IFT729, 12 février

Au menu :

  • Survol de considérations d'alignement en mémoire
  • Initialiser un objet : ce qui n'a pas été dit à la séance S04... parce que j'ai oublié!
  • Q03 – le document vous est cette fois offert sous forme électronique, à faire individuellement mais hors de la classe (pour vous permettre d'utiliser un compilateur et un débogueur dans votre démarche) : STR--Q03.pdf
  • Retour sur Q02 et introduction de constexpr
  • Brève discussion de l'importance d'échouer tôt, du moins lorsqu'on échoue :
    • privilégier un plantage à la compilation, avec static_assert, si cela s'avère possible, sinon
    • privilégier un plantage en laboratoire, peut-être avec assert(), si le problème détecté est dynamique et irrécupérable,ou
    • privilégier un plantage en laboratoire, en levant une exception, si le problème détecté est dynamique mais nous semble mener à une situation que le client ne peut ignorer et d'où le code client est susceptible de récupérer; et enfin
    • retourner des codes d'erreur pour les situations problématiques que le client peut ignorer sans que cela n'entraîne de conséquences trop fâcheuses
      • notez que les exceptions sont la manière correcte de signaler un problème dans un constructeur (à moins que la situation ne soit irrécupérable – voir ci-dessus), ceux-ci n'ayant pas de valeur de retour, mais
      • notez aussi que les exceptions ne sont pas du tout convenables dans un destructeur, ceci rendant le programme inutilisable

Notez que les diapositives électroniques utilisées pour décrire (entre autres) les approches au traitement des erreurs dans les cas où les exceptions ne sont pas souhaitées sont disponibles sur Amsterdam-2016-PR-The-Exception-Situation.pdf. Elles sont en anglais; c'est le matériel sous-jacent à une conférence que j'ai donné en novembre 2016 à Amsterdam

S05 INF749, 13 février

Séance spéciale : puisque plusieurs d'entre vous devront s'absenter pour des obligations scolaires cette semaine, et puisque nous avançons à bon train, nous tiendrons une « séance à pratique ».

Votre mandat sera :

  • De réaliser, à l'aide du compilateur de votre choix, Q03 que vous devrez m'envoyer par courriel avant le début de la séance S06 (le libellé est disponible sur STR--Q03.pdf)
  • De vous servir un verre de quelque chose d'agréable et de regarder attentivement cette présentation de 2011 (mais toujours d'actualité) portant sur les impacts de l'antémémoire (de la Cache) sur l'exécution des programmes, par le jeune retraité Scott Meyers : https://www.youtube.com/watch?v=WDIkqP4JbkE (les diapositives sont disponibles sur http://www.aristeia.com/TalkNotes/ACCU2011_CPUCaches.pdf si vous êtes intéressé(e)s)
  • Suivant cette présentation, pour fins de discussion en classe, imaginez la situation suivante :
    • Vous souhaitez réaliser un tri fusion (Merge Sort) des éléments d'un tableau de grande taille. En résumé, cela implique :
      • Séparer le tableau en deux parties de taille à peu près égales (sans copier les données; on souhaite un tri en place)
      • Trier les parties de gauche et de droite
      • Les fusionner une fois triées
    • Vous souhaitez réaliser le tri des parties de gauche et de droite en parallèle, pour accélérer le traitement. Supposons que les fils d'exécution (les threads) qui prendront en charge les exécutions parallèles soient déjà lancés ou que leur lancement soit à coût nul (c'est bien sûr faux, mais l'objectif est de simplifier le modèle).
    • Que devrez-vous faire pour éviter l'impact dommageable d'un faux partage sur votre algorithme?
S06 IFT611/IFT729, 19 février

Au menu :

S06 INF749, 20 février

Au menu :

  • Retour sur Q02 et introduction de constexpr
  • Q04
  • Brève discussion de l'importance d'échouer tôt, du moins lorsqu'on échoue :
    • privilégier un plantage à la compilation, avec static_assert, si cela s'avère possible, sinon
    • privilégier un plantage en laboratoire, peut-être avec assert(), si le problème détecté est dynamique et irrécupérable,ou
    • privilégier un plantage en laboratoire, en levant une exception, si le problème détecté est dynamique mais nous semble mener à une situation que le client ne peut ignorer et d'où le code client est susceptible de récupérer; et enfin
    • retourner des codes d'erreur pour les situations problématiques que le client peut ignorer sans que cela n'entraîne de conséquences trop fâcheuses
      • notez que les exceptions sont la manière correcte de signaler un problème dans un constructeur (à moins que la situation ne soit irrécupérable – voir ci-dessus), ceux-ci n'ayant pas de valeur de retour, mais
      • notez aussi que les exceptions ne sont pas du tout convenables dans un destructeur, ceci rendant le programme inutilisable

Notez que les diapositives électroniques utilisées pour décrire (entre autres) les approches au traitement des erreurs dans les cas où les exceptions ne sont pas souhaitées sont disponibles sur Amsterdam-2016-PR-The-Exception-Situation.pdf. Elles sont en anglais; c'est le matériel sous-jacent à une conférence que j'ai donné en novembre 2016 à Amsterdam

S07 IFT611/IFT729, 26 février

Au menu :

C'est la semaine des intras, mais nous tenons une séance de cours au même local que d'habitude car nous aimons tellement notre cours, n'est-ce pas?

S07 INF749, 27 février

Au menu :

 

5 mars

Relâche au campus principal. Reposez-vous un peu, vous en avez sûrement besoin!

S08 INF749, 6 mars

Au menu :

12 mars

Je serai à la rencontre du WG21 à Jacksonville; voir ../../Sujets/Orthogonal/wg21-2018-Jacksonville.html si vous voulez suivre mes aventures.

S09 INF749, 13 mars

Je serai à la rencontre du WG21 à Jacksonville; voir ../../Sujets/Orthogonal/wg21-2018-Jacksonville.html si vous voulez suivre mes aventures.

S08 IFT611/IFT729, 19 mars

Au menu :

  • Rapport de voyage, en particulier les enjeux qui touchent directement notre cours :
    • Annotation [[no_unique_address]], utile en particulier dans le cas des classes terminales
    • Annotations [[likely]] et [[unlikely]]
    • Contrats
    • Destroying operator delete
    • <span>
    • Itérateurs constexpr
    • simd<T> pour le Parallelism TS v2
    • Static Reflection TS
    • Coroutines et transfert de contrôle symétrique
  • Aussi intéressants, mais d'un autre ordre :
    • SG15
    • SG16
    • Dates et fuseaux horaires
    • Down with typename!
    • Destructeurs constexpr
    • Travaux sur la syntaxe concise des concepts
    • λ et capture variadique
    • Affectation déstructurante (Structured Bindings) et accessibilité
    • J'ai appris que les qualifications d'accès voyagent dans le temps :
struct A {
protected:
  struct B {
  };
};
struct X : A::B, A {
};
int main() {
  X x;
}
    • Cas analogues au problème de l'arrêt
#include <cstdio>
#include <cstdlib>
void f() {
  struct X {
    ~X() {
      std::puts("unwound");
      std::exit(0);
    }
  } x;
  throw 0;
}
int main(int argc, char**) {
  try {
    f();
  } catch (int) {
    std::puts("caught");
  }
}
    • Quelques horreurs (toujours amusantes), par exemple :
struct A {
   int n = A{}.n;
}; 
    • ... ou encore :
struct A { int x, y; };
A passthrough(A a) { return a; }
int main(void) {
   A a;
   a.x = 0;
   return passthrough(a).x; // Oups! UB!
}
  • Q06
  • Introduction aux problèmes d'entrées/ sorties dans les STR (sujet sur lequel nous reviendrons) :
    • entrées ininterruptibles (TR strict)
    • contrainte d'immédiateté (basse latence)
    • contrainte de brièveté
    • combiner indéterminisme des entrées/ sorties et consommation de données à rythme constant
    • distinguer temps constant et temps constant amorti (quand cela compte)
    • réduire le blocage
  • Examen d'une stratégie interruptible de compression de données, incluant :
    • le concept général de fonction morcelable (une espèce de fonction interruptible_foreach())
S10 INF749, 20 mars

Au menu :

  • Rapport de voyage, en particulier les enjeux qui touchent directement notre cours :
    • Annotation [[no_unique_address]], utile en particulier dans le cas des classes terminales
    • Annotations [[likely]] et [[unlikely]]
    • Contrats
    • Destroying operator delete
    • <span>
    • Itérateurs constexpr
    • simd<T> pour le Parallelism TS v2
    • Static Reflection TS
    • Coroutines et transfert de contrôle symétrique
  • Aussi intéressants, mais d'un autre ordre :
    • SG15
    • SG16
    • Dates et fuseaux horaires
    • Down with typename!
    • Destructeurs constexpr
    • Travaux sur la syntaxe concise des concepts
    • λ et capture variadique
    • Affectation déstructurante (Structured Bindings) et accessibilité
    • J'ai appris que les qualifications d'accès voyagent dans le temps :
struct A {
protected:
  struct B {
  };
};
struct X : A::B, A {
};
int main() {
  X x;
}
    • Cas analogues au problème de l'arrêt
#include <cstdio>
#include <cstdlib>
void f() {
  struct X {
    ~X() {
      std::puts("unwound");
      std::exit(0);
    }
  } x;
  throw 0;
}
int main(int argc, char**) {
  try {
    f();
  } catch (int) {
    std::puts("caught");
  }
}
    • Quelques horreurs (toujours amusantes), par exemple :
struct A {
   int n = A{}.n;
}; 
    • ... ou encore :
struct A { int x, y; };
A passthrough(A a) { return a; }
int main(void) {
   A a;
   a.x = 0;
   return passthrough(a).x; // Oups! UB!
}

Cours écourté pour cause de gastro...

S09 IFT611/IFT729, 26 mars

Au menu :

S11 INF749, 27 mars

Au menu :

Nous nous dirigeons vers un examen des ordonnanceurs statiques, reposant sur des tâches périodiques connues a priori, de même que de ce que sont les conditions d'« ordonnançabilité ».

Si vous êtes curieuses ou curieux, un projet (pas vraiment nettoyé, mais qui peut être amusant à consulter) est disponible ici. étant donné la nature novatrice et expérimentale de cette chose, je vous invite à me communiquer tous les bogues que vous y trouverez.

Dans les notes de cours, vous trouverez de la matière en lien avec cette séance dans STR – Volume 00, pages ≈37..43 pour les ordonnanceurs TR et dans STR – Volume 04, pages ≈48..58 et ≈158..174 pour les entrées/ sorties, et pages ≈134..157 pour les exécutifs. Pour les techniques d'optimisation, examinez STR – Volume 03, pp. ≈109..119

2 avril

Lundi de Pâques, congé universitaire

S12 INF749, 3 avril

Au menu :

Les volumes 04 et 05 des notes de cours sont en chantier; je les mets quand même à votre disposition :

S10 IFT611/IFT729, 4 avril 18 h

Au menu :

Nous nous dirigeons vers un examen des ordonnanceurs statiques, reposant sur des tâches périodiques connues a priori, de même que de ce que sont les conditions d'« ordonnançabilité ».

Si vous êtes curieuses ou curieux, un projet (pas vraiment nettoyé, mais qui peut être amusant à consulter) est disponible ici. étant donné la nature novatrice et expérimentale de cette chose, je vous invite à me communiquer tous les bogues que vous y trouverez.

Dans les notes de cours, vous trouverez de la matière en lien avec cette séance dans STR – Volume 00, pages ≈37..43 pour les ordonnanceurs TR et dans STR – Volume 04, pages ≈48..58 et ≈158..174 pour les entrées/ sorties, et pages ≈134..157 pour les exécutifs. Pour les techniques d'optimisation, examinez STR – Volume 03, pp. ≈109..119

S13 INF749, 10 avril

Au menu : cours dont vous êtes les héros

Bravo; ce fut fort intéressant. Vous êtes chouettes!

Le minitest Q09 est disponible ici : STR--Q09.pdf

S11 IFT611/IFT729, 9 avril

Au menu :

Les volumes 04 et 05 des notes de cours sont en chantier; je les mets quand même à votre disposition :

S14 INF749, 17 avril

Chic examen final plein d'amour

S12 IFT611/IFT729, 16 avril

Au menu :

  • Un invité spécial pour les premières 75 minutes
  • L'équipe Follow My Journey composée de Vincent Desrosiers, Victor Filion, Xavier Bourgeois-Vézina, Simon Turcotte nous parlera de son projet : « Le projet Follow My Journey a pour but de fournir une plateforme de voyage dans un contexte de partage d'un journal de voyage (voir le journal du voyage de Patrice sur son site du cours), incluant le suivi du voyage sur une carte. Le trajet du voyage sera alors présenté sur la carte et pourra alors être utilisé pour suivre en temps réel le déplacement et l'avancement du voyage en plus de fournir d'autres fonctionnalités qui seront présentées durant la présentation »
  • L'équipe composée de Simon Adam et Iannick Langevin nous présentera un « Sniffer » de paquets qui écrit le contenu de tous les paquets passant sur un réseau dans un fichier
  • Hugo Boisselle-Morin nous parlera de Sodium, un traceur GPS qui permet de faire un suivi temps réel sur le Web

... un imprévu que l'on nomme, au choix, « verglas », ou encore « météo de m.... » selon vos dispositions. Évidemment, votre période d'examens finaux débute demain (le 17 avril), ce qui limite nos options.

Voici, conséquemment, comment nous nous adapterons :

  • Si vous aviez manifesté(e) l'intention de faire une présentation en classe, et si vous souhaitez toujours procéder, je vous accueillerai la semaine prochaine (23 avril) avant ou (peu) après l'examen
  • J'ai mis à votre disposition le minitest Q09  que vous trouverez ici : STR--Q09.pdf
  • Je vous souhaite de ne pas vous péter la marboulette sur la glace
   
S13 IFT611/IFT729, 23 avril

Chic examen final plein d'amour, de 9h à 12h au local D7-3021

   

Code des cas sous étude dans les notes de cours

Les sections qui suivent proposent du code ou des exemples proposés en classe, le tout dans le but de vous permettre d'étudier la chose, de critiquer, de commenter, de questionner et d'expérimenter à loisir. Notez que certains des exemples ci-dessous doivent être retouchés à la lueur de l'avènement du standard C++ 11.

Sources des exemples du document STR – Volume 01
cible

Cliquez sur cette cible pour obtenir le code de la chaîne pascal simplifiée

cible

Cliquez sur cette cible pour obtenir le code de la chaîne pascal avec itérateurs

cible

Cliquez sur cette cible pour obtenir le code du test 0.0

cible

Cliquez sur cette cible pour obtenir le code du test 0.0b

cible

Cliquez sur cette cible pour obtenir le code du test 0.1

cible

Cliquez sur cette cible pour obtenir le code du test 1.0

cible

Cliquez sur cette cible pour obtenir le code du test 1.1

cible

Cliquez sur cette cible pour obtenir le code du test 1.2

cible

Cliquez sur cette cible pour obtenir le code du test 1.3

cible

Cliquez sur cette cible pour obtenir le code du test 2.0

cible

Cliquez sur cette cible pour obtenir le code du test 2.1

cible

Cliquez sur cette cible pour obtenir le code du test 2.2

cible

Cliquez sur cette cible pour obtenir le code du test 3.0

En lien avec l'exercice de compression de données

Cliquez sur cette cible pour obtenir le projet bmp00, réalisant une compression RLE sur des bitmaps dans un temps raisonnable. Ce projet n'est pas un STR puisqu'il cherche à réaliser rapidement et correctement une tâche mais n'offre aucune garantie de non-dépassement d'un seuil de temps – il n'y a aucun plafond mesurable dans le temps d'exécution de l'algorithme de compression dans ce programme, donc aucune contrainte de temps dont le respect serait déterministe.

L'idée d'offrir d'abord une version opérationnelle d'un algorithme donné, sans se préoccuper de contraintes TR, est que cette version est en général plus simple que les versions offrant des garanties TR strictes.

Sur le plan pédagogique, cela donne aussi une excuse à votre professeur pour mettre en place des bases conceptuelles clés du code moderne, pour que toutes et tous comprennent ses exemples, sans aller jusqu'à donner un cours général de techniques de programmation contemporaines. J'essaie de mettre en place des bases conceptuelles et techniques communes sans trop me répéter étant donné la nature bigarrée mais techniquement très solide de la clientèle de ce cours.

Cliquez sur cette cible pour obtenir le projet bmp_morcele, réalisant une compression RLE sur des bitmaps dans un temps raisonnable et de manière à ne pas dépasser un certain seuil de tolérance au temps.

Cette version compresse des données RGB brutes selon une approche RLE et peut, contrairement aux deux précédentes, être considérée TR (au sens usuel, pas au sens strict, mais principalement à cause de la plateforme) dans la mesure où le seuil indiquant d'arrêter tiendrait compte du pire cas possible (calculé a priori) pour une séquence de compression RLE donnée. Aller jusque là demanderait toutefois une meilleure connaissance du contexte et obscurcirait quelque peu le propos. Le code du projet est du code de test, montrant qu'il est possible de faire en sorte que l'algorithme de compression s'interrompe « en cours de traitement ».

Quelques suggestions d'exercices pour vous faire la main :

  • Ajoutez un traitement arbitraire (que vous pouvez simuler par une attente active basée sur un délai aléatoire) dans la boucle qui invoque la fonction compressant une séquence selon une approche RLE. Présumez que cette boucle doive itérer fois par seconde, donc que chaque itération de la boucle prenne au pire seconde, et faites en sorte que la compression n'utilise que le temps restant (s'il y en a). Le temps accordé à l'algorithme de compression sera donc un seuil variable plutôt que constant
  • Transformez l'algorithme de compression pour qu'il puisse être interrompu pendant une séquence RLE plutôt qu'entre chaque séquence RLE. Cette version sera globalement plus lente que la version proposée par le professeur mais sera plus près d'une version déterministe, donc moins à risque de déborder des contraintes TR imposées par le contexte. Vous voudrez utiliser un foncteur plutôt qu'une fonction pour réaliser cet exercice
  • L'algorithme qui transforme une séquence de bytes bruts en vecteur de valeurs RGB est lent. Transformez-le de manière à ce qu'il soit interruptible
  • Plus costaud : transformez la fonction qui compresse un bitmap de manière à ce qu'elle soit interruptible. Ceci sera plus facile à réaliser si l'algorithme transformant une séquence de bytes bruts en séquence RGB est lui-même interruptible. Considérez la fonction consommant un bitmap du flux comme était une opération indivisible (ceci vous permettra de vous concentrer sur l'essentiel)
  • Pour que les algorithmes soient plus déterministes, utilisez des tableaux bruts ou des vecteurs préalablement dimensionnés comme conteneurs de destination pour les algorithmes de transformation de séquence de bytes en séquence RGB et de compression RLE. Analysez la solution que vous proposez pour montrer en quoi elle est préférable à la version antérieure et en quoi elle est moins intéressante (évitez les banalités, il y a une réelle réflexion à faire ici)

Cliquez sur cette cible pour la description du format bitmap.

Cliquez sur cette cible pour la description de la compression RLE. Pour un peu de pédagogie sur RLE, voir http://hbfs.wordpress.com/2009/04/14/ad-hoc-compression-methods-rle/

En lien avec l'exécutif

Pour un texte sur le concept, voir ../../Sujets/TempsReel/executifs.html

Cliquez sur cette cible pour obtenir les sources d'un projet contenant une ébauche fonctionnelle d'exécutif de simulation. Ce projet a été conçu avec Visual Studio 2017, mais les sources sont essentiellement portables (le fichier source non portable est concis, et je pense que vous n'aurez pas de peine à l'identifier).

Un exécutif est un système monoprogrammé qui, avec discipline et avec soin, permet d'atteindre des performances prévisibles et des comportements se rapprochant de ceux qu'on retrouve dans les systèmes multiprogrammés, mais sans payer le prix associés outils de synchronisation. Rien n'est parfait (la mécanique ne peut être répartie sur plusieurs coeurs qu'au prix d'efforts manuels) mais l'idée peut être stimulante et repose sur des techniques qu'on enseigne peu à dans nos institutions aujourd'hui.

En lien avec les séances sous QNX

Pour une image frappante, je vous invite à comparer le schéma des états d'un processus qu'on trouve sur le Wiki à ce sujet avec celui que met de l'avant QNX (document d'origine). C'est divertissant.

Notez que l'aide en ligne pour QNX est de très bonne qualité, pour ce système en particulier comme pour les STR en général. La racine pour la version la plus récente de cette aide (à ma connaissance, du moins) est http://www.qnx.com/developers/docs/6.5.0/index.jsp

Pour les séances avec QNX, vous trouverez le code source des illustrations à travers les liens suivants (ce ne sont que de petits exemples pour vous démarrer, sans plus) :

Le lien le plus important est sans doute celui offrant de l'aide sur la bibliothèque standard de la platreforme, soit http://www.qnx.com/developers/docs/6.5.0/topic/com.qnx.doc.neutrino_lib_ref/about.html. Pour la messagerie synchrone, portez une attention particulière au trio d'opérations MsgSend(), MsgReceive() et MsgReply().

De la documentation touffue vous est offerte sur les SETR et sur QNX dans la section à cet effet : http://www.qnx.com/developers/docs/6.5.0/topic/com.qnx.doc.neutrino/bookset.html

À propos du développement TR pour processeurs à plusieurs coeurs, la section http://www.qnx.com/developers/docs/6.5.0/topic/com.qnx.doc.multicore_en/bookset.html peut vous intéresser.

En lien avec les entrées/ sorties

Les sources du code de Double-Buffering et du code du serveur d'entrées/ sorties non-bloquantes sont mises à votre disposition. Désolé de ne pas avoir fait une meilleure présentation, le temps m'a manqué.

Petite question analytique

Un appareil de type « photo radar » a pour rôle de détecter le mouvement d'un véhicule dans un espace contrôlé et, si la vitesse de déplacement du véhicule en question dépasse un certain seuil, de provoquer une capture d'image qui sera analysée pour détecter un ensemble de caractéristiques (couleur, marque, symboles sur la plaque d'immatriculation, etc.).

En bref, un capteur physique (le radar) saisit les mouvements d'objets dans un espace et les signale à un module d'analyse de première ligne capable de reconnaître un déplacement suspect et de provoquer une demande de capture d'image de la part d'une caméra à haute définition. Une fois l'image capturée, celle-ci est transmise à un module analytique qui en sortira les principales caractéristiques recherchées, puis les intégrera à une base de données. Ensuite, un module produisant des contraventions en fonction des besoins pourra, de manière automatique, faire son travail.

La question est en trois volets :

Résultats des minitests

Les résultats des minitests distribués au cours de la session vont comme suit. Notez que la moyenne globale indiquée ici tient compte du fait que je ne retiens que les huit meilleurs résultats sur dix. Notez aussi que les présentations en classe sont susceptibles de mener au remplacement d'un minitest moins bien réussi, ce qui fait que les moyennes en fin de compte sera probablement un peu meilleure que celles indiquées ici.

IFT611/IFT729

INF749

QXX

Séance

QXX

Séance

Q00

S02

Q00

S02

Q01

S03

Q01

S03

Q02

S04

Q02

S04

Q03

S05

Q03

S05

Q04

S06

Q04

S06

Q05

S07

Q05

S07

Q06

S08

Q06

S08

Q07

S10

Q07

S12

Q08

S11

Q08

S12

Q09

S12

Q09

S13

 :
 :

Résultats des livrables

Les résultats de l'évaluation des livrables remis au cours de la session vont comme suit. Ici, représente le nombre de projets livrés.

IFT611/IFT729 INF749
LXX Remise à la séance LXX Remise à la séance
L00

S03

L00

S03

L01

S08

L01

S08

L02

S12

L02

S12

 :
 :
 :
 :

Valid XHTML 1.0 Transitional

CSS Valide !