Séances
IFT611/729 |
Contenu
IFT611/729 |
Séances INF749 |
Contenu INF749 |
S00 –
IFT611/729, 6 janvier |
Le mots de la semaine : introduction,
vocabulaire et bases. On s'entend sur
quelques termes et façons de faire qui nous suivront toute la session
Au menu :
- Présentation du plan de cours
- Présentation du projet de session
et de quelques exemples tirés
de sessions antérieures
- Mise en place de certains éléments de vocabulaire, au sens pertinent
pour ce cours :
- Synchrone (sens faible)
- Synchrone (sens strict)
- Asynchrone
- Bloquant
- Non-bloquant
- Temps réel
- Contraintes de temps réel (CRIB :
constant, régulier, immédiat, bref)
- 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 aparté sur la part du technique, la part du conceptuel et
l'organisation du cours
- 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.
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 jetez un coup d'oeil à ../../Sources/comparatif_vecteur_tableau.html si vous êtes intéressé(e).
De même, si vous êtes curieuse / curieux de comparer la vitesse à
l'exécution d'une fonction et d'un foncteur
à tâche équivalente, voir
../../Sources/Comparaison-Fonctions-Foncteurs.html
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 frugalité. É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,
7 janvier |
Le mots de la semaine : introduction,
vocabulaire et bases. On s'entend sur
quelques termes et façons de faire qui nous suivront toute la session
Au menu :
- Présentation du plan de cours
- Présentation du projet de session
et de quelques exemples tirés
de sessions antérieures
- Mise en place de certains éléments de vocabulaire, au sens pertinent
pour ce cours :
- Synchrone (sens faible)
- Synchrone (sens strict)
- Asynchrone
- Bloquant
- Non-bloquant
- Temps réel
- Contraintes de temps réel (CRIB :
constant, régulier, immédiat, bref)
- 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 aparté sur la part du technique, la part du conceptuel et
l'organisation du cours
- 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.
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 jetez un coup d'oeil à ../../Sources/comparatif_vecteur_tableau.html si vous êtes intéressé(e).
De même, si vous êtes curieuse / curieux de comparer la vitesse à
l'exécution d'une fonction et d'un foncteur
à tâche équivalente, voir
../../Sources/Comparaison-Fonctions-Foncteurs.html
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 frugalité. É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/729, 13 janvier |
Le mots de la semaine : saines
pratiques et hygiène de programmation. On
établit des façons de faire, et on examine les nuances entre copie
(complexité typiquement linéaire, temps souvent indéterministe) et
mouvement (complexité typiquement constante, temps souvent déterministe),
tout en introduisant quelques
idiomes fort utiles.
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)
- L'affectation (de copie) et
l'idiome d'affectation sécuritaire
- Le destructeur
-
L'opérateur [] dans ses déclinaisons
const et non-const
-
La méthode push_back() et
l'implémentation d'un algorithme de croissance (qui reste à faire)
-
Opérateurs d'égalité (==,
!=)
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 suit
(notez que nous discuterons de
grow() à la séance
S02) :
#include <cstddef> // pour std::size_t
#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:
[[nodiscard]] size_type size() const noexcept {
return nelems;
}
[[nodiscard]] size_type capacity() const noexcept {
return cap;
}
[[nodiscard]] bool empty() const noexcept {
return !size();
}
private:
[[nodiscard]] bool full() const noexcept {
return size() == capacity();
}
public:
using iterator = pointer;
using const_iterator = const_pointer;
[[nodiscard]] iterator begin() noexcept {
return elems;
}
[[nodiscard]] const_iterator begin() const noexcept {
return elems;
}
[[nodiscard]] const_iterator cbegin() const noexcept {
return begin();
}
[[nodiscard]] iterator end() noexcept {
return begin() + size();
}
[[nodiscard]] const_iterator end() const noexcept {
return begin() + size();
}
[[nodiscard]] const_iterator cend() const noexcept {
return end();
}
Tableau() = default;
Tableau(size_type n, const_reference init)
: elems{ new value_type[n] }, nelems{ n }, cap{ 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 : 64; // hum
auto p = new value_type[new_cap];
std::copy(begin(), end(), p);
delete[] elems;
cap = new_cap;
elems = p;
}
};
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.
|
S01 – INF749,
14 janvier |
Le mots de la semaine : saines
pratiques et hygiène de programmation. On
établit des façons de faire, et on examine les nuances entre copie
(complexité typiquement linéaire, temps souvent indéterministe) et
mouvement (complexité typiquement constante, temps souvent déterministe),
tout en introduisant quelques
idiomes fort utiles.
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)
- L'affectation (de copie) et
l'idiome d'affectation sécuritaire
- Le destructeur
-
L'opérateur [] dans ses déclinaisons
const et non-const
-
La méthode push_back() et
l'implémentation d'un algorithme de croissance (qui reste à faire)
-
Opérateurs d'égalité (==,
!=)
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 suit
(notez que nous discuterons de
grow() à la séance
S02) :
#include <cstddef> // pour std::size_t
#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:
[[nodiscard]] size_type size() const noexcept {
return nelems;
}
[[nodiscard]] size_type capacity() const noexcept {
return cap;
}
[[nodiscard]] bool empty() const noexcept {
return !size();
}
private:
[[nodiscard]] bool full() const noexcept {
return size() == capacity();
}
public:
using iterator = pointer;
using const_iterator = const_pointer;
[[nodiscard]] iterator begin() noexcept {
return elems;
}
[[nodiscard]] const_iterator begin() const noexcept {
return elems;
}
[[nodiscard]] const_iterator cbegin() const noexcept {
return begin();
}
[[nodiscard]] iterator end() noexcept {
return begin() + size();
}
[[nodiscard]] const_iterator end() const noexcept {
return begin() + size();
}
[[nodiscard]] const_iterator cend() const noexcept {
return end();
}
Tableau() = default;
Tableau(size_type n, const_reference init)
: elems{ new value_type[n] }, nelems{ n }, cap{ 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 : 64; // hum
auto p = new value_type[new_cap];
std::copy(begin(), end(), p);
delete[] elems;
cap = new_cap;
elems = p;
}
};
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.
|
S02 – IFT611/729,
20 janvier |
Le mots de la semaine :
sélection à coût nul d'algorithmes. Premier contact avec les traits
et leurs applications.
Au menu :
La classe Tableau avec constructeur et
affectation de mouvement suit, et il en va de même pour le test qui met
en relief l'impact du
mouvement sur la vitesse d'un programme :
#include <cstddef> // pour std::size_t
#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:
[[nodiscard]] size_type size() const noexcept {
return nelems;
}
[[nodiscard]] size_type capacity() const noexcept {
return cap;
}
[[nodiscard]] bool empty() const noexcept {
return !size();
}
private:
[[nodiscard]] bool full() const noexcept {
return size() == capacity();
}
public:
using iterator = pointer;
using const_iterator = const_pointer;
[[nodiscard]] iterator begin() noexcept {
return elems;
}
[[nodiscard]] const_iterator begin() const noexcept {
return elems;
}
[[nodiscard]] const_iterator cbegin() const noexcept {
return begin();
}
[[nodiscard]] iterator end() noexcept {
return begin() + size();
}
[[nodiscard]] const_iterator end() const noexcept {
return begin() + size();
}
[[nodiscard]] 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;
}
Tableau(Tableau &&autre) noexcept
: elems{ std::exchange(autre.elems, nullptr) },
nelems{ std::exchange(autre.nelems, 0) },
cap{ std::exchange(autre.cap, 0) } {
}
Tableau& operator=(Tableau &&autre) noexcept {
Tableau{ std::move(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 : 128;
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);
}
};
#include <vector>
#include <numeric>
#include <chrono>
#include <iostream>
#include <utility>
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 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 = 10'000'000 };
vector<double> v(N);
iota(begin(v), end(v), 1.0);
auto [r0,dt0] = tester([v]() mutable {
v = f(v);
return v.back();
});
auto [r1,dt1] = tester([v]() mutable {
g(v);
return v.back();
});
auto [r2,dt2] = tester([v]() mutable {
v = f(std::move(v));
return v.back();
});
cout << "v = f(v) : " << duration_cast<microseconds>(dt0)
<< endl;
cout << "g(v) : " << duration_cast<microseconds>(dt1)
<< endl;
cout << "v = f(std::move(v)) : " << duration_cast<microseconds>(dt2)
<< endl;
}
Notez que notre examen d'implémentations génériques d'un tableau
suivra sous peu; nous avons quelques menus détails à explorer auparavant.
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,
21 janvier |
Le mots de la semaine :
sélection à coût nul d'algorithmes. Premier contact avec les traits
et leurs applications.
Au menu :
La classe Tableau avec constructeur et
affectation de mouvement suit, et il en va de même pour le test qui met
en relief l'impact du
mouvement sur la vitesse d'un programme :
#include <cstddef> // pour std::size_t
#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:
[[nodiscard]] size_type size() const noexcept {
return nelems;
}
[[nodiscard]] size_type capacity() const noexcept {
return cap;
}
[[nodiscard]] bool empty() const noexcept {
return !size();
}
private:
[[nodiscard]] bool full() const noexcept {
return size() == capacity();
}
public:
using iterator = pointer;
using const_iterator = const_pointer;
[[nodiscard]] iterator begin() noexcept {
return elems;
}
[[nodiscard]] const_iterator begin() const noexcept {
return elems;
}
[[nodiscard]] const_iterator cbegin() const noexcept {
return begin();
}
[[nodiscard]] iterator end() noexcept {
return begin() + size();
}
[[nodiscard]] const_iterator end() const noexcept {
return begin() + size();
}
[[nodiscard]] 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;
}
Tableau(Tableau &&autre) noexcept
: elems{ std::exchange(autre.elems, nullptr) },
nelems{ std::exchange(autre.nelems, 0) },
cap{ std::exchange(autre.cap, 0) } {
}
Tableau& operator=(Tableau &&autre) noexcept {
Tableau{ std::move(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 : 128;
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);
}
};
#include <vector>
#include <numeric>
#include <chrono>
#include <iostream>
#include <utility>
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 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 = 10'000'000 };
vector<double> v(N);
iota(begin(v), end(v), 1.0);
auto [r0,dt0] = tester([v]() mutable {
v = f(v);
return v.back();
});
auto [r1,dt1] = tester([v]() mutable {
g(v);
return v.back();
});
auto [r2,dt2] = tester([v]() mutable {
v = f(std::move(v));
return v.back();
});
cout << "v = f(v) : " << duration_cast<microseconds>(dt0)
<< endl;
cout << "g(v) : " << duration_cast<microseconds>(dt1)
<< endl;
cout << "v = f(std::move(v)) : " << duration_cast<microseconds>(dt2)
<< endl;
}
Notez que notre examen d'implémentations génériques d'un tableau
suivra sous peu; nous avons quelques menus détails à explorer auparavant.
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/729, 27 janvier |
Le mots de la semaine :
sélection à coût nul d'algorithmes et résilience.
Notre exploration des traits
et de leurs applications se poursuit; nous revenons sur le tableau
d'entiers pour en faire un tableau générique, et nous examinons les
conséquences de ce geste sur la résilience de notre code.
- Q01
- Bref retour sur Q00
- Quelques mots sur cette
optimisation qu'on
nomme le
Function Inlining et nuances quant aux capacités des
compilateurs
- Idée de qualité d'implémentation (Quality of Implementation,
QoI)
- Enjeux comme le Whole Program Optimization ou le Link Time Optimization
- Quelques mots sur les
concepts
- Quelques mots sur la préconstruction
- 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()
- Poursuite de l'examen des itérateurs et des traits, en lien avec le
calcul de la moyenne
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.
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().
J'ai utilisé la technique du tag dispatching, mais il existe
d'autres techniques pour y arriver :
#include <iterator>
using namespace std; // bof
//
// j'ai appelé la fonction distance_ pour éviter les
// conflits de nom avec std::distance
//
template <class It>
auto distance_(It debut, It fin, forward_iterator_tag) {
typename iterator_traits<It>::difference_type n{};
for(; debut != fin; ++debut)
++n;
return n;
}
template <class It>
auto distance_(It debut, It fin, random_access_iterator_tag) {
return fin - debut;
}
template <class It>
auto distance_(It debut, It fin) {
return distance_(
debut, fin, typename iterator_traits<
It
>::iterator_category{}
);
}
On pourrait appliquer d'autres mécanismes, par exemple
std::nable_if ou des
concepts.
Dans ce cas bien précis, le recours à
if constexpr pourrait être moins plaisant,
du fait qu'il n'y a pas de « cas par défaut » alors la question de ce qui devrait faire partie du else final
se poserait (il faudrait probablement combler ce cas – l'escamoter, vraiment avec un
static_assert ou
quelque chose de semblable). C'est normal : il existe une diversité d'outils pour une diversité de cas d'utilisation, après tout.
N'oubliez pas de livrer L00
aujourd'hui! |
S03 – INF749,
28 janvier |
Le mots de la semaine :
sélection à coût nul d'algorithmes et résilience.
Notre exploration des traits
et de leurs applications se poursuit; nous revenons sur le tableau
d'entiers pour en faire un tableau générique, et nous examinons les
conséquences de ce geste sur la résilience de notre code.
- Q01
- Bref retour sur Q00
- Quelques mots sur cette
optimisation qu'on
nomme le
Function Inlining et nuances quant aux capacités des
compilateurs
- Idée de qualité d'implémentation (Quality of Implementation,
QoI)
- Enjeux comme le Whole Program Optimization ou le Link Time Optimization
- Quelques mots sur les
concepts
- Quelques mots sur la préconstruction
- 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()
- Poursuite de l'examen des itérateurs et des traits, en lien avec le
calcul de la moyenne
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.
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().
J'ai utilisé la technique du tag dispatching, mais il existe
d'autres techniques pour y arriver :
#include <iterator>
using namespace std; // bof
//
// j'ai appelé la fonction distance_ pour éviter les
// conflits de nom avec std::distance
//
template <class It>
auto distance_(It debut, It fin, forward_iterator_tag) {
typename iterator_traits<It>::difference_type n{};
for(; debut != fin; ++debut)
++n;
return n;
}
template <class It>
auto distance_(It debut, It fin, random_access_iterator_tag) {
return fin - debut;
}
template <class It>
auto distance_(It debut, It fin) {
return distance_(
debut, fin, typename iterator_traits<
It
>::iterator_category{}
);
}
On pourrait appliquer d'autres mécanismes, par exemple
std::nable_if ou des
concepts.
Dans ce cas bien précis, le recours à
if constexpr pourrait être moins plaisant,
du fait qu'il n'y a pas de « cas par défaut » alors la question de ce qui devrait faire partie du else final
se poserait (il faudrait probablement combler ce cas – l'escamoter, vraiment avec un
static_assert ou
quelque chose de semblable). C'est normal : il existe une diversité d'outils pour une diversité de cas d'utilisation, après tout.
N'oubliez pas de livrer L00
aujourd'hui! |
S04 –
IFT611/729, 3 février |
Le mots de la semaine : gestion
déterministe des ressources. Dans les
STR,
traditionnellement, l'allocation de ressources (mémoire et autres) tend à
être reléguée au rang des opérations indéterministes en temps, à juste
titre, donc devant être réalisées a priori, n'étant pas à propos pour les
tronçons assujettis à des contraintes
TR.
Cependant, lorsque nous
connaissons le contexte, il est parfois possible de faire de petits
miracles.
Au menu :
Pour le code du tableau générique tel que vu en classe, voici :
#include <cstddef>#include <algorithm>
#include <initializer_list>
#include <utility>
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:
[[nodiscard]] size_type size() const noexcept {
return nelems;
}
[[nodiscard]] size_type capacity() const noexcept {
return cap;
}
[[nodiscard]] bool empty() const noexcept {
return !size();
}
private:
[[nodiscard]] bool full() const noexcept {
return size() == capacity();
}
public:
using iterator = pointer;
using const_iterator = const_pointer;
[[nodiscard]] iterator begin() noexcept {
return elems;
}
[[nodiscard]] const_iterator begin() const noexcept {
return elems;
}
[[nodiscard]] const_iterator cbegin() const noexcept {
return begin();
}
[[nodiscard]] iterator end() noexcept {
return begin() + size();
}
[[nodiscard]] const_iterator end() const noexcept {
return begin() + size();
}
[[nodiscard]] 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;
}
}
//
// Notez que le constructeur ci-dessous peut bénéficier
// du recours à enable_if pour éviter certaines ambiguïtés
//
Tableau(size_type n, const_reference init)
: elems{ new value_type[n] }, nelems{ n }, cap{ 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;
}
}
//
// Notez que le constructeur ci-dessous peut bénéficier
// du recours à enable_if pour éviter certaines ambiguïtés
//
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)
: 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 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 : 128; // 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{ std::exchange(autre.elems, nullptr) },
nelems{ std::exchange(autre.nelems, 0) },
cap{ std::exchange(autre.cap, 0) } {
}
Tableau& operator=(Tableau &&autre) noexcept {
delete[] elems;
elems = std::exchange(autre.elems, nullptr);
nelems = std::exchange(autre.nelems, 0);
cap = std::exchange(autre.cap, 0);
return *this;
}
};
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:
[[nodiscard]] size_type size() const noexcept {
return nelems;
}
[[nodiscard]] size_type capacity() const noexcept {
return cap;
}
[[nodiscard]] bool empty() const noexcept {
return !size();
}
private:
[[nodiscard]] bool full() const noexcept {
return size() == capacity();
}
public:
using iterator = pointer;
using const_iterator = const_pointer;
[[nodiscard]] iterator begin() noexcept {
return elems.get();
}
[[nodiscard]] const_iterator begin() const noexcept {
return elems.get();
}
[[nodiscard]] const_iterator cbegin() const noexcept {
return elems.get();
}
[[nodiscard]] iterator end() noexcept {
return begin() + size();
}
[[nodiscard]] const_iterator end() const noexcept {
return begin() + size();
}
[[nodiscard]] const_iterator cend() const noexcept {
return begin() + size();
}
Tableau() = default;
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)
: elems{ new value_type[n] }, nelems{ n } , cap{ 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 = make_unique<value_type[]>{ size() };
std::copy(debut, fin, begin());
}
template <class U>
Tableau(const Tableau<U> &autre)
: elems{ new value_type[autre.size()] },
nelems{ autre.size() }, cap{ 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 : 128;
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
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,
4 février |
Le mots de la semaine : gestion
déterministe des ressources. Dans les
STR,
traditionnellement, l'allocation de ressources (mémoire et autres) tend à
être reléguée au rang des opérations indéterministes en temps, à juste
titre, donc devant être réalisées a priori, n'étant pas à propos pour les
tronçons assujettis à des contraintes
TR.
Cependant, lorsque nous
connaissons le contexte, il est parfois possible de faire de petits
miracles.
Au menu :
Pour le code du tableau générique tel que vu en classe, voici :
#include <cstddef>#include <algorithm>
#include <initializer_list>
#include <utility>
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:
[[nodiscard]] size_type size() const noexcept {
return nelems;
}
[[nodiscard]] size_type capacity() const noexcept {
return cap;
}
[[nodiscard]] bool empty() const noexcept {
return !size();
}
private:
[[nodiscard]] bool full() const noexcept {
return size() == capacity();
}
public:
using iterator = pointer;
using const_iterator = const_pointer;
[[nodiscard]] iterator begin() noexcept {
return elems;
}
[[nodiscard]] const_iterator begin() const noexcept {
return elems;
}
[[nodiscard]] const_iterator cbegin() const noexcept {
return begin();
}
[[nodiscard]] iterator end() noexcept {
return begin() + size();
}
[[nodiscard]] const_iterator end() const noexcept {
return begin() + size();
}
[[nodiscard]] 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;
}
}
//
// Notez que le constructeur ci-dessous peut bénéficier
// du recours à enable_if pour éviter certaines ambiguïtés
//
Tableau(size_type n, const_reference init)
: elems{ new value_type[n] }, nelems{ n }, cap{ 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;
}
}
//
// Notez que le constructeur ci-dessous peut bénéficier
// du recours à enable_if pour éviter certaines ambiguïtés
//
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)
: 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 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 : 128; // 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{ std::exchange(autre.elems, nullptr) },
nelems{ std::exchange(autre.nelems, 0) },
cap{ std::exchange(autre.cap, 0) } {
}
Tableau& operator=(Tableau &&autre) noexcept {
delete[] elems;
elems = std::exchange(autre.elems, nullptr);
nelems = std::exchange(autre.nelems, 0);
cap = std::exchange(autre.cap, 0);
return *this;
}
};
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:
[[nodiscard]] size_type size() const noexcept {
return nelems;
}
[[nodiscard]] size_type capacity() const noexcept {
return cap;
}
[[nodiscard]] bool empty() const noexcept {
return !size();
}
private:
[[nodiscard]] bool full() const noexcept {
return size() == capacity();
}
public:
using iterator = pointer;
using const_iterator = const_pointer;
[[nodiscard]] iterator begin() noexcept {
return elems.get();
}
[[nodiscard]] const_iterator begin() const noexcept {
return elems.get();
}
[[nodiscard]] const_iterator cbegin() const noexcept {
return elems.get();
}
[[nodiscard]] iterator end() noexcept {
return begin() + size();
}
[[nodiscard]] const_iterator end() const noexcept {
return begin() + size();
}
[[nodiscard]] const_iterator cend() const noexcept {
return begin() + size();
}
Tableau() = default;
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)
: elems{ new value_type[n] }, nelems{ n } , cap{ 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 = make_unique<value_type[]>{ size() };
std::copy(debut, fin, begin());
}
template <class U>
Tableau(const Tableau<U> &autre)
: elems{ new value_type[autre.size()] },
nelems{ autre.size() }, cap{ 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 : 128;
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
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/729, 10 février |
Au menu :
- Q03
- 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++ :
- 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)
|
S05 – INF749,
11 février |
Au menu :
- Q03
- 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++ :
- 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)
|
S06 –
IFT611/729, 17 février
19 février
19 h par Teams |
Au menu :
- Q04
- Retour sur Q03
- Retour sur Q02, en particulier :
- À la veille de la pause de mi-session, on se fait plaisir :
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)
|
S06 – INF749,
18 février |
Au menu :
- Q04
- Retour sur Q03
- Retour sur Q02, en particulier :
- À la veille de la pause de mi-session, on se fait plaisir :
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)
|
24
février |
Cours suspendus dans le but de faire place aux examens périodiques pour
les cours qui en tiennent
|
25 février |
Cours suspendus dans le but de faire place aux examens périodiques pour
les cours qui en tiennent
|
3 mars |
Relâche. Reposez-vous un peu, vous en avez sûrement
besoin!
|
4 mars |
Relâche. Reposez-vous un peu, vous en avez sûrement
besoin!
|
S07 – IFT611/729, 10 mars |
Au menu :
- Q04
- Q05
- Retour sur le dossier de la gestion de l'allocation
dynamique de mémoire :
- Implémenter un conteneur tirant profit des mécanismes distinguant
allocation et initialisation
- Rédiger un conteneur générique du calibre de
std::vector, donc générique, robuste et rapide
- Algorithmes standards sur de la mémoire brute et différences avec
algorithmes standards usuels
- Opérations modifiant la structure d'un tel conteneur (insert(),
erase()) : subtilités et enjeux
- Introduction aux allocateurs depuis C++ 11
- Introduction aux
allocateurs pmr de
C++ 17
- 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
|
S07 – INF749,
11 mars |
Au menu :
- Q04
- Q05
- Retour sur le dossier de la gestion de l'allocation
dynamique de mémoire :
- Implémenter un conteneur tirant profit des mécanismes distinguant
allocation et initialisation
- Rédiger un conteneur générique du calibre de
std::vector, donc générique, robuste et rapide
- Algorithmes standards sur de la mémoire brute et différences avec
algorithmes standards usuels
- Opérations modifiant la structure d'un tel conteneur (insert(),
erase()) : subtilités et enjeux
- Introduction aux allocateurs depuis C++ 11
- Introduction aux
allocateurs pmr de
C++ 17
- 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
|
S08 –
IFT611/729, 17 mars |
Au menu :
|
S08 – INF749,
18 mars |
Au menu :
|
S09 –
IFT611/729, 24 mars |
Au menu :
|
S09 – INF749,
25 mars |
Au menu :
|
S10 –
IFT611/729, 31 mars |
Attention : exceptionnellement (raisons familiales, et santé et –
surtout – météorologiques), cette séance se tiendra à distance, par
Teams.
Au menu :
- Présentations de projets :
- Antoine Yapeti et Cédric Laliberté : « Présentation du
KwoiiiBoard »
- Q09 (car Q08
est les présentations des projets, pour celles et ceux qui le font)
- Collecte d'ordures et
STR : exemple concret
de Metronome
Il existe d'autres langages accompagnés de moteurs de collecte
d'ordures visant à réduire la latence plutôt qu'à accroître le
débit. Par exemple, certains usagers du langage
Go rapportent des pauses maximales de l'ordre de
7,1 ms, ce qui peut être acceptable pour certains
STR (probablement pas pour du TR strict, cependant,
comme l'indique aussi ceci)
Si le temps le permet :
Si vous souhaitez le code un peu ésotérique impliquant des calculs sur des listes de types, le voici :
#include <type_traits>
using namespace std;
/*
template <class T, T val> struct integral_constant {
using type = T;
static constexpr type value = val;
constexpr auto operator()() const noexcept { return value; }
};
template <bool B> struct bool_constant : integral_constant<bool, B> {};
struct true_type : bool_constant<true> {};
struct false_type : bool_constant<false> {};
*/
template <auto V> struct constant {
using type = decltype(V);
static constexpr type value = V;
constexpr auto operator()() const noexcept { return value; }
};
template <class ...>
struct type_list;
using flottants = type_list<float, double, long double>;
template <class> struct longueur;
template <class T, class ... Q> struct longueur<type_list<T, Q...>> {
enum { value = 1 + longueur<type_list<Q...>>::value };
};
template <> struct longueur<type_list<>> {
enum { value = 0 };
};
template <int N> struct int_ : constant<N> {};
template <class T, class U>
struct somme_ : int_<T::value + U::value> {};
template <class T, class U>
struct produit_ : int_<T::value * U::value> {};
template <class TL, template <class, class> class F, class Init>
struct cumuler;
template <class T, class ... Q, template <class, class> class F, class Init>
struct cumuler<type_list<T, Q...>, F, Init>
: F<T, cumuler<type_list<Q...>, F, Init>> {
};
template <template <class, class> class F, class Init>
struct cumuler<type_list<>, F, Init> : Init {
};
template <class TL> using somme = cumuler<TL, somme_, int_<0>>;
template <class TL> using produit = cumuler<TL, produit_, int_<1>>;
//
//template <class> struct somme;
//template <class T, class ... Q>
// struct somme<type_list<T, Q...>>
// : int_<T::value + somme<type_list<Q...>>::value> {
// };
//template <> struct somme <type_list<>> : int_<0> {};
//
//template <class> struct produit;
//template <class T, class ... Q>
//struct produit<type_list<T, Q...>>
// : int_<T::value * produit<type_list<Q...>>::value> {
//};
//template <> struct produit <type_list<>> : int_<1> {};
int main() {
using prems = type_list<int_<2>, int_<3>, int_<5>, int_<7>, int_<11>>;
static_assert(somme<prems>::value == 28);
static_assert(produit<prems>::value == 2310);
using trois = integral_constant<int, 3>;
static_assert(trois{}() == 3);
static_assert(longueur<flottants>::value == 3);
}
|
S10 – INF749,
1 avril |
Au menu :
- Q09 (car Q08 est les présentations des projets,
S11)
- Collecte d'ordures et
STR : exemple concret
de Metronome
Il existe d'autres langages accompagnés de moteurs de collecte
d'ordures visant à réduire la latence plutôt qu'à accroître le
débit. Par exemple, certains usagers du langage
Go rapportent des pauses maximales de l'ordre de
7,1 ms, ce qui peut être acceptable pour certains
STR (probablement pas pour du TR strict, cependant,
comme l'indique aussi ceci)
Si le temps le permet :
Toujours si le temps le permet, pour vous divertir :
-
SSO
et SOO
- Programmer à coût zéro à l'exécution (sorte de cadeau non-Haskell – désolé! – mais destiné aux
Haskelliennes et les Haskelliens parmi vous) :
Si vous souhaitez le code un peu ésotérique impliquant des calculs sur des listes de types, le voici :
#include <type_traits>
using namespace std;
/*
template <class T, T val> struct integral_constant {
using type = T;
static constexpr type value = val;
constexpr auto operator()() const noexcept { return value; }
};
template <bool B> struct bool_constant : integral_constant<bool, B> {};
struct true_type : bool_constant<true> {};
struct false_type : bool_constant<false> {};
*/
template <auto V> struct constant {
using type = decltype(V);
static constexpr type value = V;
constexpr auto operator()() const noexcept { return value; }
};
template <class ...>
struct type_list;
using flottants = type_list<float, double, long double>;
template <class> struct longueur;
template <class T, class ... Q> struct longueur<type_list<T, Q...>> {
enum { value = 1 + longueur<type_list<Q...>>::value };
};
template <> struct longueur<type_list<>> {
enum { value = 0 };
};
template <int N> struct int_ : constant<N> {};
template <class T, class U>
struct somme_ : int_<T::value + U::value> {};
template <class T, class U>
struct produit_ : int_<T::value * U::value> {};
template <class TL, template <class, class> class F, class Init>
struct cumuler;
template <class T, class ... Q, template <class, class> class F, class Init>
struct cumuler<type_list<T, Q...>, F, Init>
: F<T, cumuler<type_list<Q...>, F, Init>> {
};
template <template <class, class> class F, class Init>
struct cumuler<type_list<>, F, Init> : Init {
};
template <class TL> using somme = cumuler<TL, somme_, int_<0>>;
template <class TL> using produit = cumuler<TL, produit_, int_<1>>;
//
//template <class> struct somme;
//template <class T, class ... Q>
// struct somme<type_list<T, Q...>>
// : int_<T::value + somme<type_list<Q...>>::value> {
// };
//template <> struct somme <type_list<>> : int_<0> {};
//
//template <class> struct produit;
//template <class T, class ... Q>
//struct produit<type_list<T, Q...>>
// : int_<T::value * produit<type_list<Q...>>::value> {
//};
//template <> struct produit <type_list<>> : int_<1> {};
int main() {
using prems = type_list<int_<2>, int_<3>, int_<5>, int_<7>, int_<11>>;
static_assert(somme<prems>::value == 28);
static_assert(produit<prems>::value == 2310);
using trois = integral_constant<int, 3>;
static_assert(trois{}() == 3);
static_assert(longueur<flottants>::value == 3);
}
|
S11 –
IFT611/729,
7 avril |
Au menu :
- Présentations de projets :
- Alexandre Ehrhard, Loïs Montessuit, Sophie Trouillot et Karl Plourde : « Version numérique
immersive de la Coinche »
- Hermann Ngandeu Ngotcho, « IDS from scratch »
- Vincent Beaudoin et Mikael Demers, « Equalizer
portable »
Si le temps le permet :
Toujours si le temps le permet, pour vous divertir :
-
SSO
et SOO
- Programmer à coût zéro à l'exécution (sorte de cadeau non-Haskell – désolé! – mais destiné aux
Haskelliennes et les Haskelliens parmi vous) :
N'oubliez pas de livrer L02
aujourd'hui!
Je mettrai vieil examen (celui de 2017)
sur le site Moodle associé au cours, pour vous donner un aperçu du
format et du style de question.
Si vous souhaitez le code un peu ésotérique impliquant des calculs sur des listes de types, le voici :
#include <type_traits>
using namespace std;
/*
template <class T, T val> struct integral_constant {
using type = T;
static constexpr type value = val;
constexpr auto operator()() const noexcept { return value; }
};
template <bool B> struct bool_constant : integral_constant<bool, B> {};
struct true_type : bool_constant<true> {};
struct false_type : bool_constant<false> {};
*/
template <auto V> struct constant {
using type = decltype(V);
static constexpr type value = V;
constexpr auto operator()() const noexcept { return value; }
};
template <class ...>
struct type_list;
using flottants = type_list<float, double, long double>;
template <class> struct longueur;
template <class T, class ... Q> struct longueur<type_list<T, Q...>> {
enum { value = 1 + longueur<type_list<Q...>>::value };
};
template <> struct longueur<type_list<>> {
enum { value = 0 };
};
template <int N> struct int_ : constant<N> {};
template <class T, class U>
struct somme_ : int_<T::value + U::value> {};
template <class T, class U>
struct produit_ : int_<T::value * U::value> {};
template <class TL, template <class, class> class F, class Init>
struct cumuler;
template <class T, class ... Q, template <class, class> class F, class Init>
struct cumuler<type_list<T, Q...>, F, Init>
: F<T, cumuler<type_list<Q...>, F, Init>> {
};
template <template <class, class> class F, class Init>
struct cumuler<type_list<>, F, Init> : Init {
};
template <class TL> using somme = cumuler<TL, somme_, int_<0>>;
template <class TL> using produit = cumuler<TL, produit_, int_<1>>;
//
//template <class> struct somme;
//template <class T, class ... Q>
// struct somme<type_list<T, Q...>>
// : int_<T::value + somme<type_list<Q...>>::value> {
// };
//template <> struct somme <type_list<>> : int_<0> {};
//
//template <class> struct produit;
//template <class T, class ... Q>
//struct produit<type_list<T, Q...>>
// : int_<T::value * produit<type_list<Q...>>::value> {
//};
//template <> struct produit <type_list<>> : int_<1> {};
int main() {
using prems = type_list<int_<2>, int_<3>, int_<5>, int_<7>, int_<11>>;
static_assert(somme<prems>::value == 28);
static_assert(produit<prems>::value == 2310);
using trois = integral_constant<int, 3>;
static_assert(trois{}() == 3);
static_assert(longueur<flottants>::value == 3);
}
|
S11 – INF749,
8 avril |
Au menu :
- Séance où vous présenterez vos projets de session.
Si ce n'est pas terminé, c'est pas dramatique, mais on veut qu'il y
ait une réflexion et une analyse derrière ce qui sera présenté
- La séance débutera par une présentation de David Viens, spécialiste de
l'audio temps réel et l'un des principaux artisans derrière Plogue Arts et
Technologie : https://www.plogue.com/
N'oubliez pas de livrer L02
aujourd'hui!
Je mettrai vieil examen (celui de 2017)
sur le site Moodle associé au cours, pour vous donner un aperçu du
format et du style de question.
|
S12 –
IFT611/729,
24 avril |
Chic examen final plein d'amour, de 8 h 30 à
11 h 30 au
D3-2037
|
S12 – INF749,
15 avril |
Chic examen final plein d'amour
|
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'évolution du langage
C++.
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.