Commit b2f3e1ce authored by CHAMONT David's avatar CHAMONT David
Browse files

Cours complet

parent 9368f3ab
......@@ -6,12 +6,10 @@ auto compose( Function1 f1, Function2 f2 )
{ return [f1,f2]( auto val ){ return f1(f2(val)) ; } ; }
int square( int i ) { return i*i ; }
int inc( int i ) { return i+1 ; }
void display( int i ) { std::cout<<i<<std::endl ; }
int main()
{
std::array<int,5> table { 1, 2, 3, 4, 5 } ;
auto f = compose(inc,square) ;
for ( auto element : table )
std::cout<<f(element)<<std::endl ;
std::for_each(table.begin(),table.end(),compose(display,square)) ;
}
#include <cmath>
#include <iostream>
#include <optional>
template< typename OutputType, typename InputType >
auto raise( OutputType(&f)(InputType) )
{
return [f]( std::optional<InputType> input ) -> std::optional<OutputType>
{
if (input) { return f(input.value()) ; }
else { return std::nullopt ; }
} ;
}
template< typename OutputType, typename InputType >
auto raise( std::optional<OutputType>(&f)(InputType) )
{
return [f]( std::optional<InputType> input ) -> std::optional<OutputType>
{
if (input) { return f(input.value()) ; }
else { return std::nullopt ; }
} ;
}
std::optional<double> mysqrt( double d )
{
if (d<0) { return std::nullopt ; }
else { return std::sqrt(d) ; }
}
double square( double d )
{ return d*d ; }
template< typename A >
std::ostream & operator<<( std::ostream & os, std::optional<A> const & opt )
{
if (opt) { return os<<opt.value() ; }
else { return os<<"nothing" ; }
}
int main()
{
std::cout<<raise(mysqrt)(mysqrt(10))<<std::endl ;
std::cout<<raise(mysqrt)(mysqrt(-10))<<std::endl ;
}
\ No newline at end of file
......@@ -1167,7 +1167,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.7"
"version": "3.9.12"
},
"livereveal": {
"scroll": true
......
......@@ -672,7 +672,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.7"
"version": "3.9.12"
},
"livereveal": {
"scroll": true
......
......@@ -883,7 +883,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.7"
"version": "3.9.12"
},
"livereveal": {
"scroll": true
......
......@@ -570,7 +570,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.5"
"version": "3.9.12"
}
},
"nbformat": 4,
......
......@@ -57,7 +57,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.7"
"version": "3.9.12"
}
},
"nbformat": 4,
......
# Programming styles
Topics more focused on the standard library, new best practices, and programming styles.
#### Morning 1 (1h30) : [Core guidelines](1-CoreGuidelines/README.ipynb)
Coding instructions by Bjarne Stroustrup and Herb Sutter, as well as a library to facilitate their implementation.
#### Morning 2 (1h30) : [Template meta programming](2-TemplateMetaProgramming/README.ipynb)
To use the compiler as a code generator: general principles, type manipulation, SFINAE, expression templates.
#### Afternoon 1 (1h30) : [Functional programming](3-FunctionalProgramming/README.ipynb)
Lambda functions, algebraic types and monadic types, ranges.
#### Afternoon 2 (1h30) : [Concurrent programming](4-ConcurrentProgramming/README.ipynb)
Threads, synchronization, shared data, mutexes and locks, asynchronous calls.
---
© *CNRS 2022*
*This document was created by David Chamont. It is available under the [Licence Creative Commons - Attribution - No commercial use - Shared under the conditions 4.0 International](http://creativecommons.org/licenses/by-nc-sa/4.0/)*
\ No newline at end of file
......@@ -24,11 +24,11 @@
"## Motivation\n",
"\n",
"\n",
"There are frequent situations where one need some `<` operator for a home-made class. Typically, if you want to make a `std::vector` of such objects, and sort it. But also if you want to use it as a key for some `std::set` or `std::map`.\n",
"There are frequent situations where one needs `<` operator for a home-made class. Typically, if you want to make a `std::vector` of such objects, and sort it. But also if you want to use it as a key for e.g. `std::set` or `std::map`.\n",
"\n",
"For completeness, one will also add `>`, `>=`, and `<=`, implemented as a reuse of either `<` and `==`, or `<` and `>`.\n",
"For completeness, one should also add `>`, `>=`, and `<=`, implemented reusing either `<` and `==`, or `<` and `>`.\n",
"\n",
"Those operators will be defined as an external functions, optionally friends, so that left and right arguments will be similarly convertibles.\n",
"Those operators should be defined as free functions, optionally friends, so that left and right arguments will be similarly convertible.\n",
"\n",
"Much boilerplate code to write. Too much."
]
......@@ -44,9 +44,9 @@
"source": [
"## Idea\n",
"\n",
"C++20 introduce the new *spaceship* operator : `<=>`. Well, the real official name is *three-way comparison operator*.\n",
"C++20 introduces the *spaceship* operator : `<=>`. Well, the real official name is *three-way comparison operator*.\n",
"\n",
"It is provided by default for all predefined types, and return *something* which can be compared to `0` (such as the `std::strcmp`), meaning *lower than* if this *something* is lower than `0`, *greater than* if it is greater than `0`, and *equivalent* if it is equal `0`:"
"It is provided by default for all predefined types, and returns *something* which can be compared to `0` (similar to `std::strcmp`), meaning *lower than* if this *something* is lower than `0`, *greater than* if it is greater than `0`, and *equivalent* if it is equal `0`:"
]
},
{
......@@ -91,9 +91,9 @@
"source": [
"## Different kinds of ordering\n",
"\n",
"The real return type of `<=>` for integers is `std::strong_ordering` : whatever the values, you will always get `true` for exactly one test among `<0`, `==0`, and `>0`.\n",
"The real return type of `<=>` for integers is `std::strong_ordering`: whatever the values, you will always get `true` for exactly one test among `<0`, `==0`, and `>0`.\n",
"\n",
"On the contrary, the return type of `<=>` for floating point numbers is `std::partial_ordering`, because sometimes the tree tests may return `false`, typically if one one the number is `NaN`."
"On the contrary, the return type of `<=>` for floating point numbers is `std::partial_ordering`, because sometimes all tests may return `false`, typically if one number is `NaN`."
]
},
{
......@@ -136,11 +136,9 @@
}
},
"source": [
"The type `std::strong_ordering` is convertible to `std::partial_ordering`, but not the contrary.\n",
"Between the two, we have a class `std::weak_ordering`, where `==0` mean that the two compared values are equivalent from a ranking point of view, but not necessarily *equal*: in a given expression, one cannot substitutes one value for the other and be sure to have the same result.\n",
"\n",
"Between the two, we have a class `std::weak_ordering`, where `==0` mean that the two compared values are equivalent from a ranking point of view, but not necessarily *equal*. In a given expression, one cannot subsitutes one value for the other and be sure to have the same result.\n",
"\n",
"I am not aware of some predefined type whose `<=>` would return an instance of ``std::weak_ordering`, but it may make sense for some home-made class, such as the following."
"I am not aware of some predefined type whose `<=>` would return an instance of `std::weak_ordering`, but it may make sense for some home-made class, such as the following."
]
},
{
......@@ -171,8 +169,8 @@
" {\n",
" unsigned n, d ;\n",
" \n",
" std::weak_ordering operator<=>( Rational const & other ) const\n",
" { return (n*other.d)<=>(other.n*d) ; }\n",
" friend std::weak_ordering operator<=>( Rational const & lhs, Rational const & rhs )\n",
" { return (lhs.n*rhs.d)<=>(lhs.n*rhs.d) ; }\n",
" } ;\n",
" \n",
"std::ostream & operator<<( std::ostream & os, std::weak_ordering cmp )\n",
......@@ -211,11 +209,11 @@
}
},
"source": [
"Despites the `<=>` for `unsigned` returns some instance of `std::strong_ordering`, we prefer here to cast it to `std::weak_ordering`. This way, we emphasize that if `a<=>b` is equal to `0`, it only means that `a` and `b` are logically equivalent, but may lead to different results in some expressions.\n",
"Despites `<=>` for `unsigned` returning a `std::strong_ordering`, we prefer here to convert it to `std::weak_ordering`. This way, we emphasize that if `a<=>b` is equal to `0`, it only means that `a` and `b` are logically equivalent, but may lead to different results in other expressions (e.g. printing them).\n",
"\n",
"As one can check in the code, we have defined `<=>`, and the compiler has added, for free, some default implementations for `<`, `>`, `<=` and `=>`. Of course, one can overload them with home-made implementations.\n",
"As one can see from the code, we have defined only `<=>`, but `<`, `>`, `<=` and `=>` work as well. The compiler can rephrase any use of the latter operators in terms of {\\it <=>}. Of course, one can also provide own implementations.\n",
"\n",
"It has NOT added a default implementation for `==` and `!=`. Those operators are generally expected to mean *equal*, rather than *equivalent*. If `<=>` does not enable a strong order, the operator `==`generally deserves a separate implementation."
"It is NOT able to rephrase `==` and `!=` in terms of `<=>`. Those operators are generally expected to mean *equal*, rather than *equivalent*. If `<=>` does not enable a strong order, it is generally advised not to define `==`."
]
},
{
......@@ -229,7 +227,7 @@
"source": [
"## Default `<=>` implementation\n",
"\n",
"One can ask the compiler to provide some default implementation for `<=>`. Logically enough, it will compare the first member variable of the two objects, and goes on to the next member variable as long as the current ones are equivalent.\n",
"One can ask the compiler to provide some default implementation for `<=>` and/or `==`. Logically enough, it will compare the first member variable of the two objects, and goes on to the next member variable as long as the current ones are equivalent.\n",
"\n",
"In the previous example, that would not be relevant, because it will compare the numerators first, and conclude that `3/6` is greater than `2/3`:"
]
......@@ -255,11 +253,12 @@
],
"source": [
"#include <iostream>\n",
"#include <compare>\n",
"\n",
"struct Rational\n",
" {\n",
" unsigned n, d ;\n",
" std::strong_ordering operator<=>( Rational const & other ) const = default ;\n",
" friend std::strong_ordering operator<=>( Rational const &, Rational const & ) = default ;\n",
" } ;\n",
" \n",
"std::ostream & operator<<( std::ostream & os, std::weak_ordering cmp )\n",
......@@ -337,9 +336,9 @@
}
},
"source": [
"We see above that the definition of `<=>` has been completed by the compiler with a default implementation of `<` (among others), which has been used by `std::set` so to sorts its elements.\n",
"We see above that the definition of `<=>` has been provided, and the use of `<` by `std::set` to sorts its elements has been rewritten by the compiler in terms of {\\it <=>}.\n",
"\n",
"Worth to note : if you use the *default* implementation of `<=>`, you will also get additional `==` and `!=`. Not sure thi sspecial behavior is such a good idea..."
"Worth to note : if {\\it <=>} is defaulted and no {\\it ==} is defined, then the compiler also provides a defaulted {\\it ==}. I guess that in most cases, the default implementation of `<=>` will return some instance of `std::strong_ordering`, and it makes sense to also get `==`."
]
},
{
......@@ -351,10 +350,10 @@
}
},
"source": [
"## What to retain\n",
"## Summary\n",
"\n",
"- Defining `<=>` brings you a free default implementation of `<`, `>`, `<=`, and `>=`.\n",
"- The standard library defines few different kinds of order (strong, weak and partial).\n",
"- Defining `<=>` allows you to use `<`, `>`, `<=`, and `>=` as well.\n",
"- The standard library defines a few different kinds of order (strong, weak and partial).\n",
"- Do not confuse equivalence (`(a<=>b)==0`) with equality (`a==b`)."
]
},
......@@ -493,7 +492,8 @@
"\n",
"- https://blog.tartanllama.xyz/spaceship-operator/\n",
"- https://iq.opengenus.org/spaceship-operator-cpp/\n",
"- https://www.jonathanmueller.dev/talk/meetingcpp2019/"
"- https://www.jonathanmueller.dev/talk/meetingcpp2019/\n",
"- https://quuxplusone.github.io/blog/2021/10/22/hidden-friend-outlives-spaceship/"
]
},
{
......@@ -506,7 +506,7 @@
},
"source": [
"© *CNRS 2022* \n",
"*Assembled and written by David Chamont, this work is made available according to the terms of the* \n",
"*Assembled and written by David Chamont, with corrections from Bernhard Manfred Gruber, this work is made available according to the terms of the* \n",
"[*Creative Commons License - Attribution - NonCommercial - ShareAlike 4.0 International*](http://creativecommons.org/licenses/by-nc-sa/4.0/)"
]
}
......
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"# Measuring compilation time\n",
"\n",
"UNDER WORK"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Globally, with linux `time` command\n",
"\n",
"In the Linux world, information about the overall execution time of an application can be obtained by simply preceding the application name with the `time` command."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"%%file tmp.chrono.1.h\n",
"\n",
"#include <vector>\n",
"#include <cstdlib>\n",
"#include <cassert>\n",
"#include <iostream>"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"%%file tmp.chrono.2.h\n",
"\n",
"std::vector<double> generate( int size )\n",
" {\n",
" std::vector<double> datas(size) ;\n",
" for ( double & data : datas )\n",
" { data = std::rand()/(RAND_MAX+1.) ; }\n",
" return datas ;\n",
" }"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"%%file tmp.chrono.3.h\n",
"\n",
"double analyse( std::vector<double> const & datas, int power )\n",
" {\n",
" double res = 0 ;\n",
" for ( double data : datas )\n",
" {\n",
" double val = 1 ; \n",
" for ( int j=0 ; j<power ; ++j )\n",
" val *= data ;\n",
" res += val ;\n",
" }\n",
" return res ;\n",
" }"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"%%file tmp.chrono.cpp\n",
"\n",
"#include \"tmp.chrono.1.h\"\n",
"#include \"tmp.chrono.2.h\"\n",
"#include \"tmp.chrono.3.h\"\n",
"\n",
"int main( int argc, char * argv[] )\n",
" {\n",
" assert(argc==3) ;\n",
" int size {atoi(argv[1])} ;\n",
" int power {atoi(argv[2])} ;\n",
"\n",
" auto datas = generate(size) ;\n",
" auto res = analyse(datas,power) ;\n",
" std::cout<<res<<std::endl ; \n",
" }"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"%%file tmp.chrono.sh\n",
"\n",
"rm -f tmp.chrono.exe\n",
"g++ -std=c++17 -O3 tmp.chrono.cpp -o tmp.chrono.exe\n",
"time ./tmp.chrono.exe $*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"!bash -l ./tmp.chrono.sh 1024 100000"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Details of the `time` display:\n",
"- `real` : the elapsed time seen in real life.\n",
"- `user` : the execution time spent in the user code.\n",
"- `sys` : the execution time spent in system calls."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Repeated time measurement\n",
"\n",
"When monitoring the execution time, especially a small code, and especially when running on a non-reserved dedicated machine :\n",
"- **run your program many times** and compute the mean execution time,\n",
"- **ensure each single run is long enough** so that the processor pipelines get filled and you go well beyond the initial computing latency.\n",
"\n",
"Also, be aware that if your data size is larger than the CPU cache, this may reduce I/O throughput, thus make your program I/ bound. In such a case, optimizing the computation instructions will not improve your global execution time."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Below, we run the program once, so to check the result. Then we run it 10 times, measuring the execution time with a GNU flavor of `time`, and redirect the results into a python script, which will finally compute the mean time. We ask a power of `100000`, to be sure the arithmetic intensity is high, and we limit the size of the array to `1024`, below the typical cache size."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"%%file tmp.chrono.sh\n",
"\n",
"rm -f tmp.chrono.exe\n",
"g++ -std=c++17 -O3 tmp.chrono.cpp -o tmp.chrono.exe\n",
"./tmp.chrono.exe $*\n",
"\n",
"rm -f tmp.chrono.py\n",
"echo \"t = 0\" >> tmp.chrono.py\n",
"for i in 0 1 2 3 4 5 6 7 8 9\n",
"do \\time -f \"t += %U\" -a -o ./tmp.chrono.py ./tmp.chrono.exe $* >> /dev/null\n",
"done\n",
"echo \"print('(~ {:.3f} s)'.format(t/10.))\" >> tmp.chrono.py\n",
"python3 tmp.chrono.py"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"!bash -l tmp.chrono.sh 1024 100000"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Internal time measurement with `std::chrono`\n",
"\n",
"When trying to speed-up a big real world application, one needs to known the execution time of the subparts of the application, so to focus his efforts where it is worth. **It is highly advised to learn how to use a profiling tool such as [`perf`](https://perf.wiki.kernel.org/index.php/Main_Page)**."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Meanwhile, some internal information can be collected thanks to `std::chrono`. One of the interesting aspects of `std::chrono` is the work done on time units. Below, we measure the execution time of `generate` and displays it in milliseconds."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"%%file tmp.chrono.2.h\n",
"\n",
"#include <chrono>\n",
" \n",
"std::vector<double> generate( int size )\n",
" {\n",
" using namespace std::chrono ;\n",
" auto t1 {steady_clock::now()} ; \n",
"\n",
" std::vector<double> datas(size) ; \n",
" for ( double & data : datas )\n",
" { data = std::rand()/(RAND_MAX+1.) ; }\n",
"\n",
" auto t2 {steady_clock::now()} ;\n",
" auto dt {duration_cast<microseconds>(t2-t1).count()} ;\n",
" std::cout<<\"(generate: \"<<dt<<\" us)\"<<std::endl ;\n",
" \n",
" return datas ;\n",
" }"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"!rm -f tmp.chrono.exe && g++ -std=c++17 -I./ tmp.chrono.cpp -o tmp.chrono.exe"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"!./tmp.chrono.exe 1024 100000"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Comparing two codes with [QuickBench](https://www.quick-bench.com/)\n",
"\n",
"If you do not care about the absolute computation time, but want to compare two (or more) alternative implementations, you may like the online tool [QuickBench](https://www.quick-bench.com/), powered by [Google Benchmark](https://github.com/google/benchmark)."
]
},
{
"cell_type": "markdown",
"metadata": {