commit 6cc2e76b2aa940176b132513c93cece000119da3 Author: Alexis Pereda Date: Wed Oct 6 21:08:28 2021 +0200 thesis version diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..32c8519 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,88 @@ +cmake_minimum_required(VERSION 3.1) + +project(thesis) + +set(LATEX_COMPILER_FLAGS "-interaction=batchmode -file-line-error -shell-escape" + CACHE STRING "Flags passed to latex.") +include(UseLATEX.cmake) + +# Global +set(LATEX_USE_SYNCTEX ON) +set(imgdir img) + +file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/pdf) +file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/buildfig) +file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/figures) + +# Functions +macro(subdirlist result curdir) + file(GLOB children RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${curdir}/*) + foreach(child ${children}) + if(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${child}) + subdirlist(${result} ${child}) + list(APPEND ${result} ${child}) + endif() + endforeach() +endmacro() + +set(imgdirs ${imgdir}) +subdirlist(imgdirs ${imgdir}) + +# Document +set(modes oneside twoside print genfigures) +set(srcdir src) +set(stydir sty) + +file(GLOB_RECURSE src RELATIVE ${CMAKE_SOURCE_DIR} ${srcdir}/*.tex) +file(GLOB_RECURSE bib RELATIVE ${CMAKE_SOURCE_DIR} ${srcdir}/*.bib) +file(GLOB_RECURSE sty RELATIVE ${CMAKE_SOURCE_DIR} ${stydir}/*.sty) + +set(latex_document_options) +set(custom_command_options ALL) + +foreach(mode ${modes}) + set(maintex main_${mode}) + + execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink + ${CMAKE_CURRENT_BINARY_DIR}/${srcdir}/${maintex}.tex + ${CMAKE_CURRENT_BINARY_DIR}/${maintex}.tex + ) + + add_latex_document( + ${srcdir}/main_${mode}.tex + INPUTS ${src} ${lst} ${sty} + BIBFILES ${bib} + IMAGE_DIRS ${imgdirs} + USE_BIBLATEX + USE_GLOSSARY + ${latex_document_options} + ) + + if(NOT ${mode} STREQUAL genfigures) + set(synctex_file ${CMAKE_PROJECT_NAME}_${mode}.synctex.gz) + add_custom_target(pdf_${synctex_file} ${custom_command_options} + DEPENDS ${maintex}.pdf + COMMAND ${CMAKE_COMMAND} -E copy ${maintex}.synctex.gz pdf/${synctex_file} + ) + + set(pdf_file ${CMAKE_PROJECT_NAME}_${mode}.pdf) + add_custom_target(pdf_${pdf_file} ${custom_command_options} + DEPENDS ${maintex}.pdf + COMMAND ${CMAKE_COMMAND} -E copy ${maintex}.pdf pdf/${pdf_file} + ) + + add_custom_target(pdf_${CMAKE_PROJECT_NAME}_${mode} DEPENDS pdf_${synctex_file} pdf_${pdf_file}) + endif() + + set(latex_document_options EXCLUDE_FROM_ALL) + set(custom_command_options) +endforeach() + +add_custom_target(generate_figures + DEPENDS main_genfigures_pdf +) + +add_custom_command(TARGET generate_figures POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_BINARY_DIR}/buildfig/*.pdf + ${CMAKE_CURRENT_BINARY_DIR}/figures +) diff --git a/README.md b/README.md new file mode 100644 index 0000000..015d87f --- /dev/null +++ b/README.md @@ -0,0 +1,109 @@ +# About + +Thesis title: using template metaprogramming to design active libraries for assisted parallelisation. + +(French: *application de la métaprogrammation template à la conception de bibliothèques actives +de parallélisation assitée*) + +## Download + +- [Computer version](https://phd.pereda.fr/assets/thesis/alexis_pereda_thesis.pdf); +- [Print version](https://phd.pereda.fr/assets/thesis/alexis_pereda_thesis_print.pdf). + +## Abstract + +
+Hardware performance has been increasing through the addition of computing cores rather than through +increasing their frequency since the early 2000s. +This means that parallel programming is no longer optional should you need to make the best use of +the hardware at your disposal. +Yet many programs are still written sequentially: parallel programming introduces numerous +difficulties. +Amongst these, it is notably hard to determine whether a sequence of a program can be executed in +parallel, i.e. preserving its behaviour as well as its overall result. +Even knowing that it is possible to parallelise a piece of code, doing it correctly is another +problem. +In this thesis, we present two approaches to make writing parallel software easier. + +We present an active library (using C++ template metaprogramming to operate during the compilation +process) whose purpose is to analyse and parallelise loops. +To do so, it builds a representation of each processed loop using expression templates through an +embedded language. +This allows to know which variables are used and how they are used. +For the case of arrays, which are common within loops, it also acquires the index functions. +The analysis of this information enables the library to identify which instructions in the loop can +be run in parallel. +Interdependent instructions are detected by knowing the variables and their access mode for each +instruction. +Given a group of interdependent instructions and the known index functions, the library decides if +the instructions can be run in parallel or not. +We want this library to help developers writing loops that will be automatically parallelised +whenever possible and run sequentially as without the library otherwise. +Another focus is to provide this to serve as a framework to integrate new methods for parallelising +programs and extended analysis rules. + +We introduce another active library that aims to help developers by assisting them in writing +parallel software instead of fully automating it. +This library uses algorithmic skeletons to let the developer describe its algorithms with both its +sequential and parallel parts by assembling atomic execution patterns such as a series of tasks or a +parallel execution of a repeated task. +This description includes the data flow, that is how parameters and function returns are +transmitted. +Usually, this is automatically set by the algorithmic skeleton library, however it gives the +developer greater flexibility and it makes it possible, amongst other things, for our library to +automatically transmit special parameters that must not be shared between parallel tasks. +One feature that this allows is to ensure repeatability from one execution to another even for +stochastic algorithms. +Considering the distribution of tasks on the different cores, we even reduce the number of these +non-shared parameters. +Once again, this library provides a framework at several levels. +Low-level extensions consist of the implementation of new execution patterns to be used to build +skeletons. +Another low-level axis is the integration of new execution policies that decide how tasks are +distributed on the available computing cores. +High-level additions will be libraries using ours to offer ready-to-use algorithmic skeletons for +various fields. +
+ +Keywords: template metaprogramming; assisted parallelisation; automatic parallelisation; active libraries; algorithmic skeletons; repeatability. + +## Related publications + +- "Repeatability with Random Numbers Using Algorithmic Skeletons", ESM 2020 (https://hal.archives-ouvertes.fr/hal-02980472); +- "Modeling Algorithmic Skeletons for Automatic Parallelization Using Template Metaprogramming", HPCS 2019 (IEEE) [10.1109/HPCS48598.2019.9188128](https://doi.org/10.1109/HPCS48598.2019.9188128); +- "Processing Algorithmic Skeletons at Compile-Time", ROADEF 2020 (https://hal.archives-ouvertes.fr/hal-02573660); +- "Algorithmic Skeletons Using Template Metaprogramming", ICAST 2019; +- "Parallel Algorithmic Skeletons for Metaheuristics", ROADEF 2019 (https://hal.archives-ouvertes.fr/hal-02059533); +- "Static Loop Parallelization Decision Using Template Metaprogramming", HPCS 2018 (IEEE) [10.1109/HPCS.2018.00159](https://doi.org/10.1109/HPCS.2018.00159). + +## Related projects + +- [AlSk](https://phd.pereda.fr/dev/alsk), an algorithmic skeletons active library; +- [pfor](https://phd.pereda.fr/dev/pfor), an automatic parallelisation active library; +- [ROSA](https://phd.pereda.fr/dev/rosa), an algorithmic skeletons collection for [OR](https://en.wikipedia.org/wiki/Operations_research) algorithms; +- [TMP](https://phd.pereda.fr/dev/tmp), template metaprogramming library used to implement this library. + +## Usage + +To produce the `Makefile`: +```bash +mkdir build +cd build +cmake .. +``` + +Compilation has been tested with `texlive-full` version 2020.20210202-3. + +To build the project: +``` +make +``` + +Be patient, it takes *some* time. + +Make can be run with these arguments: +- `pdf_thesis_oneside`: to build the "computer" version, including dynamic figures (default); +- `pdf_thesis_twoside`: same as oneside but better for double-page display; +- `pdf_thesis_print`: printing version (no dynamic figures, double-page and blank pages where required). + +PDF files are generated in `build/pdf/`. diff --git a/UseLATEX.cmake b/UseLATEX.cmake new file mode 100644 index 0000000..bb1fd9e --- /dev/null +++ b/UseLATEX.cmake @@ -0,0 +1,2074 @@ +# File: UseLATEX.cmake +# CMAKE commands to actually use the LaTeX compiler +# Version: 2.7.0 +# Author: Kenneth Moreland +# +# Copyright 2004, 2015 Sandia Corporation. +# Under the terms of Contract DE-AC04-94AL85000, there is a non-exclusive +# license for use of this work by or on behalf of the U.S. Government. +# +# This software is released under the BSD 3-Clause License. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# +# The following function is defined: +# +# add_latex_document( +# [BIBFILES ] +# [INPUTS ] +# [IMAGE_DIRS ] +# [IMAGES ] +# [CONFIGURE ] +# [DEPENDS ] +# [MULTIBIB_NEWCITES ] +# [USE_BIBLATEX] +# [USE_INDEX] +# [INDEX_NAMES ] +# [USE_GLOSSARY] [USE_NOMENCL] +# [FORCE_PDF] [FORCE_DVI] [FORCE_HTML] +# [TARGET_NAME ] +# [INCLUDE_DIRECTORIES ] +# [EXCLUDE_FROM_ALL] +# [EXCLUDE_FROM_DEFAULTS]) +# Adds targets that compile . The latex output is placed +# in LATEX_OUTPUT_PATH or CMAKE_CURRENT_BINARY_DIR if the former is +# not set. The latex program is picky about where files are located, +# so all input files are copied from the source directory to the +# output directory. This includes the target tex file, any tex file +# listed with the INPUTS option, the bibliography files listed with +# the BIBFILES option, and any .cls, .bst, .clo, .sty, .ist, and .fd +# files found in the current source directory. Images found in the +# IMAGE_DIRS directories or listed by IMAGES are also copied to the +# output directory and converted to an appropriate format if necessary. +# Any tex files also listed with the CONFIGURE option are also processed +# with the CMake CONFIGURE_FILE command (with the @ONLY flag). Any file +# listed in CONFIGURE but not the target tex file or listed with INPUTS +# has no effect. DEPENDS can be used to specify generated files that are +# needed to compile the latex target. +# +# The following targets are made. The name prefix is based off of the +# base name of the tex file unless TARGET_NAME is specified. If +# TARGET_NAME is specified, then that name is used for the targets. +# +# name_dvi: Makes .dvi +# name_pdf: Makes .pdf using pdflatex. +# name_safepdf: Makes .pdf using ps2pdf. If using the +# default program arguments, this will ensure all fonts +# are embedded and no lossy compression has been +# performed on images. +# name_ps: Makes .ps +# name_html: Makes .html +# name_auxclean: Deletes .aux and other auxiliary files. +# This is sometimes necessary if a LaTeX error occurs +# and writes a bad aux file. Unlike the regular clean +# target, it does not delete other input files, such as +# converted images, to save time on the rebuild. +# +# Unless the EXCLUDE_FROM_ALL option is given, one of these targets +# is added to the ALL target and built by default. Which target is +# determined by the LATEX_DEFAULT_BUILD CMake variable. See the +# documentation of that variable for more details. +# +# Unless the EXCLUDE_FROM_DEFAULTS option is given, all these targets +# are added as dependencies to targets named dvi, pdf, safepdf, ps, +# html, and auxclean, respectively. +# +# USE_BIBLATEX enables the use of biblatex/biber as an alternative to +# bibtex. Bibtex remains the default if USE_BIBLATEX is not +# specified. +# +# If the argument USE_INDEX is given, then commands to build an index +# are made. If the argument INDEX_NAMES is given, an index file is +# generated for each name in this list. See the LaTeX package multind +# for more information about how to generate multiple indices. +# +# If the argument USE_GLOSSARY is given, then commands to +# build a glossary are made. If the argument MULTIBIB_NEWCITES is +# given, then additional bibtex calls are added to the build to +# support the extra auxiliary files created with the \newcite command +# in the multibib package. +# +# INCLUDE_DIRECTORIES provides a list of directories in which LaTeX +# should look for input files. It accepts both files relative to the +# binary directory and absolute paths. +# +# History: +# +# 2.7.0 Add INCLUDE_DIRECTORIES parameters. (Thanks to Eric Dönges.) +# +# 2.6.1 Fix issue with detecting long undefined reference warnings that +# LaTeX "helpfully" split across lines (and which fowled up our +# regex). +# +# 2.6.0 Skip image conversion targets that are not used when a force option +# is given. This helps prevent errors for missing conversion programs +# that are not needed. (Thanks to Martin Wetzel.) +# +# 2.5.0 Parse biber output for warnings. +# +# For regular bibtex, you get warnings about undefined references +# when you run latex. However, when using biber, biber itself prints +# out the said warning and latex sees nothing. Thus, when using biber +# the regular output is now suppressed and the log file is scanned +# for potential issues. +# +# 2.4.9 Use biblatex.cfg file if it exists and the USE_BIBLATEX option is ON. +# +# 2.4.8 Fix synctex issue with absolute paths not being converted. +# +# 2.4.7 Fix some issues with spaces in the path of the working directory where +# LaTeX is executed. +# +# 2.4.6 Fix parse issue with older versions of CMake. +# +# 2.4.5 Fix issues with files and paths containing spaces. +# +# 2.4.4 Improve error reporting message when LaTeX fails. +# +# When LaTeX fails, delete the output file, which is invalid. +# +# Add warnings for "missing characters." These usually mean that a +# non-ASCII character is in the document and will not be printed +# correctly. +# +# 2.4.3 Check for warnings from the natbib package. When using natbib, +# warnings for missing bibliography references look different. So +# far, natbib seems to be quiet unless something is important, so +# look for all natbib warnings. (We can change this later if +# necessary.) +# +# 2.4.2 Fix an issue where new versions of ImageMagick expect the order of +# options in command line execution of magick/convert. (See, for +# example, http://www.imagemagick.org/Usage/basics/#why.) +# +# 2.4.1 Add ability to dump LaTeX log file when using batch mode. Batch +# mode suppresses most output, often including error messages. To +# make sure critical error messages get displayed, show the full log +# on failures. +# +# 2.4.0 Remove "-r 600" from the default PDFTOPS_CONVERTER_FLAGS. The -r flag +# is available from the Poppler version of pdftops, but not the Xpdf +# version. +# +# Fix an issue with the flags for the different programs not being +# properly separated. +# +# Fix an issue on windows where the = character is not allowed for +# ps2pdf arguments. +# +# Change default arguments for latex and pdflatex commands. Makes the +# output more quiet and prints out the file/line where errors occur. +# (Thanks to Nikos Koukis.) +# +# After a LaTeX build, check the log file for warnings that are +# indicative of problems with the build. +# +# Remove support for latex2html. Instead, use the htlatex program. +# This is now part of TeX Live and most other distributions. It also +# behaves much more like the other LaTeX programs. Also fixed some +# nasty issues with the htlatex arguments. +# +# 2.3.2 Declare LaTeX input files as sources for targets so that they show +# up in IDEs like QtCreator. +# +# Fix issue where main tex files in subdirectories were creating +# invalid targets for building HTML. Just disable the HTML targets in +# this case. +# +# 2.3.1 Support use of magick command instead of convert command for +# ImageMagick 7. +# +# 2.3.0 Add USE_BIBLATEX option to support the biblatex package, which +# requires using the program biber as a replacement for bibtex +# (thanks to David Tracey). +# +# 2.2.1 Add STRINGS property to LATEX_DEFAULT_BUILD to make it easier to +# select the default build in the CMake GUI. +# +# 2.2.0 Add TARGET_NAME option. +# +# 2.1.1 Support for finding bmp, ppm, and other image files. +# +# 2.1.0 Fix an error where the pdf target and others were defined multiple +# times if UseLATEX.cmake was included multiple times. +# +# Added INDEX_NAMES option to support multiple indexes in a single +# document from the multind package (thanks to Dan Lipsa). +# +# 2.0.0 First major revision of UseLATEX.cmake updates to more recent features +# of CMake and some non-backward compatible changes. +# +# Changed all function and macro names to lower case. CMake's identifiers +# are case insensitive, but the convention moved from all upper case to +# all lower case somewhere around the release of CMake 2. (The original +# version of UseLATEX.cmake predates that.) +# +# Remove condition matching in if statements. They are no longer necessary +# and are even discouraged (because else clauses get confusing). +# +# Use "new" features available in CMake such as list and argument parsing. +# +# Remove some code that has been deprecated for a while. +# +# Mark variables for compiler and converter executables as advanced to +# match the more conventional CMake behavior. +# +# Changed how default builds are specified and add the ability to force +# a particular build. +# +# Made the base targets (pdf, dvi, etc.) global. add_latex_document +# always mangles its target names and these base targets depend on +# the targets with mangled names. +# +# 1.10.5 Fix for Window's convert check (thanks to Martin Baute). +# +# 1.10.4 Copy font files to binary directory for packages that come with +# their own fonts. +# +# 1.10.3 Check for Windows version of convert being used instead of +# ImageMagick's version (thanks to Martin Baute). +# +# 1.10.2 Use htlatex as a fallback when latex2html is not available (thanks +# to Tomasz Grzegurzko). +# +# 1.10.1 Make convert program mandatory only if actually used (thanks to +# Julien Schueller). +# +# 1.10.0 Added NO_DEFAULT and DEFAULT_PS options. +# Fixed issue with cleaning files for LaTeX documents originating in +# a subdirectory. +# +# 1.9.6 Fixed problem with LATEX_SMALL_IMAGES. +# Strengthened check to make sure the output directory does not contain +# the source files. +# +# 1.9.5 Add support for image types not directly supported by either latex +# or pdflatex. (Thanks to Jorge Gerardo Pena Pastor for SVG support.) +# +# 1.9.4 Fix issues with filenames containing multiple periods. +# +# 1.9.3 Hide some variables that are now cached but should not show up in +# the ccmake list of variables. +# +# 1.9.2 Changed MACRO declarations to FUNCTION declarations. The better +# FUNCTION scoping will hopefully avoid some common but subtle bugs. +# This implicitly increases the minimum CMake version to 4.6 (although +# I honestly only test it with the latest 4.8 version). +# +# Since we are updating the minimum CMake version, I'm going to start +# using the builtin LIST commands that are now available. +# +# Favor using pdftops from the Poppler package to convert from pdf to +# eps. It does a much better job than ImageMagick or ghostscript. +# +# 1.9.1 Fixed typo that caused the LATEX_SMALL_IMAGES option to fail to +# activate. +# +# 1.9.0 Add support for the multibib package (thanks to Antonio LaTorre). +# +# 1.8.2 Fix corner case when an argument name was also a variable containing +# the text of an argument. In this case, the CMake IF was matching +# the argument text with the contents of the variable with the same +# argument name. +# +# 1.8.1 Fix problem where ps2pdf was not getting the appropriate arguments. +# +# 1.8.0 Add support for synctex. +# +# 1.7.7 Support calling xindy when making glossaries. +# +# Improved make clean support. +# +# 1.7.6 Add support for the nomencl package (thanks to Myles English). +# +# 1.7.5 Fix issue with bibfiles being copied two different ways, which causes +# Problems with dependencies (thanks to Edwin van Leeuwen). +# +# 1.7.4 Added the DEFAULT_SAFEPDF option (thanks to Raymond Wan). +# +# Added warnings when image directories are not found (and were +# probably not given relative to the source directory). +# +# 1.7.3 Fix some issues with interactions between makeglossaries and bibtex +# (thanks to Mark de Wever). +# +# 1.7.2 Use ps2pdf to convert eps to pdf to get around the problem with +# ImageMagick dropping the bounding box (thanks to Lukasz Lis). +# +# 1.7.1 Fixed some dependency issues. +# +# 1.7.0 Added DEPENDS options (thanks to Theodore Papadopoulo). +# +# 1.6.1 Ported the makeglossaries command to CMake and embedded the port +# into UseLATEX.cmake. +# +# 1.6.0 Allow the use of the makeglossaries command. Thanks to Oystein +# S. Haaland for the patch. +# +# 1.5.0 Allow any type of file in the INPUTS lists, not just tex file +# (suggested by Eric Noulard). As a consequence, the ability to +# specify tex files without the .tex extension is removed. The removed +# function is of dubious value anyway. +# +# When copying input files, skip over any file that exists in the +# binary directory but does not exist in the source directory with the +# assumption that these files were added by some other mechanism. I +# find this useful when creating large documents with multiple +# chapters that I want to build separately (for speed) as I work on +# them. I use the same boilerplate as the starting point for all +# and just copy it with different configurations. This was what the +# separate ADD_LATEX_DOCUMENT method was supposed to originally be for. +# Since its external use is pretty much deprecated, I removed that +# documentation. +# +# 1.4.1 Copy .sty files along with the other class and package files. +# +# 1.4.0 Added a MANGLE_TARGET_NAMES option that will mangle the target names. +# +# Fixed problem with copying bib files that became apparent with +# CMake 2.4. +# +# 1.3.0 Added a LATEX_OUTPUT_PATH variable that allows you or the user to +# specify where the built latex documents to go. This is especially +# handy if you want to do in-source builds. +# +# Removed the ADD_LATEX_IMAGES macro and absorbed the functionality +# into ADD_LATEX_DOCUMENT. The old interface was always kind of +# clunky anyway since you had to specify the image directory in both +# places. It also made supporting LATEX_OUTPUT_PATH problematic. +# +# Added support for jpeg files. +# +# 1.2.0 Changed the configuration options yet again. Removed the NO_CONFIGURE +# Replaced it with a CONFIGURE option that lists input files for which +# configure should be run. +# +# The pdf target no longer depends on the dvi target. This allows you +# to build latex documents that require pdflatex. Also added an option +# to make the pdf target the default one. +# +# 1.1.1 Added the NO_CONFIGURE option. The @ character can be used when +# specifying table column separators. If two or more are used, then +# will incorrectly substitute them. +# +# 1.1.0 Added ability include multiple bib files. Added ability to do copy +# sub-tex files for multipart tex files. +# +# 1.0.0 If both ps and pdf type images exist, just copy the one that +# matches the current render mode. Replaced a bunch of STRING +# commands with GET_FILENAME_COMPONENT commands that were made to do +# the desired function. +# +# 0.4.0 First version posted to CMake Wiki. +# + +if(__USE_LATEX_INCLUDED) + return() +endif() +set(__USE_LATEX_INCLUDED TRUE) + +############################################################################# +# Find the location of myself while originally executing. If you do this +# inside of a macro, it will recode where the macro was invoked. +############################################################################# +set(LATEX_USE_LATEX_LOCATION ${CMAKE_CURRENT_LIST_FILE} + CACHE INTERNAL "Location of UseLATEX.cmake file." FORCE + ) + +############################################################################# +# Generic helper functions +############################################################################# + +include(CMakeParseArguments) + +function(latex_list_contains var value) + set(input_list ${ARGN}) + list(FIND input_list "${value}" index) + if(index GREATER -1) + set(${var} TRUE PARENT_SCOPE) + else() + set(${var} PARENT_SCOPE) + endif() +endfunction(latex_list_contains) + +# Match the contents of a file to a regular expression. +function(latex_file_match variable filename regexp default) + # The FILE STRINGS command would be a bit better, but I'm not totally sure + # the match will always be to a whole line, and I don't want to break things. + file(READ ${filename} file_contents) + string(REGEX MATCHALL "${regexp}" + match_result ${file_contents} + ) + if(match_result) + set(${variable} "${match_result}" PARENT_SCOPE) + else() + set(${variable} "${default}" PARENT_SCOPE) + endif() +endfunction(latex_file_match) + +# A version of GET_FILENAME_COMPONENT that treats extensions after the last +# period rather than the first. To the best of my knowledge, all filenames +# typically used by LaTeX, including image files, have small extensions +# after the last dot. +function(latex_get_filename_component varname filename type) + set(result) + if("${type}" STREQUAL "NAME_WE") + get_filename_component(name ${filename} NAME) + string(REGEX REPLACE "\\.[^.]*\$" "" result "${name}") + elseif("${type}" STREQUAL "EXT") + get_filename_component(name ${filename} NAME) + string(REGEX MATCH "\\.[^.]*\$" result "${name}") + else() + get_filename_component(result ${filename} ${type}) + endif() + set(${varname} "${result}" PARENT_SCOPE) +endfunction(latex_get_filename_component) + +############################################################################# +# Functions that perform processing during a LaTeX build. +############################################################################# +function(latex_execute_latex) + if(NOT LATEX_WORKING_DIRECTORY) + message(SEND_ERROR "Need to define LATEX_WORKING_DIRECTORY") + endif() + + if(NOT LATEX_FULL_COMMAND) + message(SEND_ERROR "Need to define LATEX_FULL_COMMAND") + endif() + + if(NOT LATEX_OUTPUT_FILE) + message(SEND_ERROR "Need to define LATEX_OUTPUT_FILE") + endif() + + if(NOT LATEX_LOG_FILE) + message(SEND_ERROR "Need to define LATEX_LOG_FILE") + endif() + + set(full_command_original "${LATEX_FULL_COMMAND}") + + # Chose the native method for parsing command arguments. Newer versions of + # CMake allow you to just use NATIVE_COMMAND. + if (CMAKE_VERSION VERSION_GREATER 3.8) + set(separate_arguments_mode NATIVE_COMMAND) + else() + if (WIN32) + set(separate_arguments_mode WINDOWS_COMMAND) + else() + set(separate_arguments_mode UNIX_COMMAND) + endif() + endif() + + # Preps variables for use in execute_process. + # Even though we expect LATEX_WORKING_DIRECTORY to have a single "argument," + # we also want to make sure that we strip out any escape characters that can + # foul up the WORKING_DIRECTORY argument. + separate_arguments(LATEX_FULL_COMMAND UNIX_COMMAND "${LATEX_FULL_COMMAND}") + separate_arguments(LATEX_WORKING_DIRECTORY_SEP UNIX_COMMAND "${LATEX_WORKING_DIRECTORY}") + + execute_process( + COMMAND ${LATEX_FULL_COMMAND} + WORKING_DIRECTORY "${LATEX_WORKING_DIRECTORY_SEP}" + RESULT_VARIABLE execute_result + OUTPUT_VARIABLE ignore + ERROR_VARIABLE ignore + ) + + if(NOT ${execute_result} EQUAL 0) + # LaTeX tends to write a file when a failure happens. Delete that file so + # that LaTeX will run again. + file(REMOVE "${LATEX_WORKING_DIRECTORY}/${LATEX_OUTPUT_FILE}") + + message("\n\nLaTeX command failed") + message("${full_command_original}") + message("Log output:") + file(READ "${LATEX_WORKING_DIRECTORY}/${LATEX_LOG_FILE}" log_output) + message("${log_output}") + message(FATAL_ERROR "Executed LaTeX, but LaTeX returned an error.") + endif() +endfunction(latex_execute_latex) + +function(latex_makeglossaries) + # This is really a bare bones port of the makeglossaries perl script into + # CMake scripting. + message("**************************** In makeglossaries") + if(NOT LATEX_TARGET) + message(SEND_ERROR "Need to define LATEX_TARGET") + endif() + + set(aux_file ${LATEX_TARGET}.aux) + + if(NOT EXISTS ${aux_file}) + message(SEND_ERROR "${aux_file} does not exist. Run latex on your target file.") + endif() + + latex_file_match(newglossary_lines ${aux_file} + "@newglossary[ \t]*{([^}]*)}{([^}]*)}{([^}]*)}{([^}]*)}" + "@newglossary{main}{glg}{gls}{glo}" + ) + + latex_file_match(istfile_line ${aux_file} + "@istfilename[ \t]*{([^}]*)}" + "@istfilename{${LATEX_TARGET}.ist}" + ) + string(REGEX REPLACE "@istfilename[ \t]*{([^}]*)}" "\\1" + istfile ${istfile_line} + ) + + string(REGEX MATCH ".*\\.xdy" use_xindy "${istfile}") + if(use_xindy) + message("*************** Using xindy") + if(NOT XINDY_COMPILER) + message(SEND_ERROR "Need to define XINDY_COMPILER") + endif() + else() + message("*************** Using makeindex") + if(NOT MAKEINDEX_COMPILER) + message(SEND_ERROR "Need to define MAKEINDEX_COMPILER") + endif() + endif() + + foreach(newglossary ${newglossary_lines}) + string(REGEX REPLACE + "@newglossary[ \t]*{([^}]*)}{([^}]*)}{([^}]*)}{([^}]*)}" + "\\1" glossary_name ${newglossary} + ) + string(REGEX REPLACE + "@newglossary[ \t]*{([^}]*)}{([^}]*)}{([^}]*)}{([^}]*)}" + "${LATEX_TARGET}.\\2" glossary_log ${newglossary} + ) + string(REGEX REPLACE + "@newglossary[ \t]*{([^}]*)}{([^}]*)}{([^}]*)}{([^}]*)}" + "${LATEX_TARGET}.\\3" glossary_out ${newglossary} + ) + string(REGEX REPLACE + "@newglossary[ \t]*{([^}]*)}{([^}]*)}{([^}]*)}{([^}]*)}" + "${LATEX_TARGET}.\\4" glossary_in ${newglossary} + ) + + if(use_xindy) + latex_file_match(xdylanguage_line ${aux_file} + "@xdylanguage[ \t]*{${glossary_name}}{([^}]*)}" + "@xdylanguage{${glossary_name}}{english}" + ) + string(REGEX REPLACE + "@xdylanguage[ \t]*{${glossary_name}}{([^}]*)}" + "\\1" + language + ${xdylanguage_line} + ) + # What crazy person makes a LaTeX index generator that uses different + # identifiers for language than babel (or at least does not support + # the old ones)? + if(${language} STREQUAL "frenchb") + set(language "french") + elseif(${language} MATCHES "^n?germanb?$") + set(language "german") + elseif(${language} STREQUAL "magyar") + set(language "hungarian") + elseif(${language} STREQUAL "lsorbian") + set(language "lower-sorbian") + elseif(${language} STREQUAL "norsk") + set(language "norwegian") + elseif(${language} STREQUAL "portuges") + set(language "portuguese") + elseif(${language} STREQUAL "russianb") + set(language "russian") + elseif(${language} STREQUAL "slovene") + set(language "slovenian") + elseif(${language} STREQUAL "ukraineb") + set(language "ukrainian") + elseif(${language} STREQUAL "usorbian") + set(language "upper-sorbian") + endif() + if(language) + set(language_flags "-L ${language}") + else() + set(language_flags "") + endif() + + latex_file_match(codepage_line ${aux_file} + "@gls@codepage[ \t]*{${glossary_name}}{([^}]*)}" + "@gls@codepage{${glossary_name}}{utf}" + ) + string(REGEX REPLACE + "@gls@codepage[ \t]*{${glossary_name}}{([^}]*)}" + "\\1" + codepage + ${codepage_line} + ) + if(codepage) + set(codepage_flags "-C ${codepage}") + else() + # Ideally, we would check that the language is compatible with the + # default codepage, but I'm hoping that distributions will be smart + # enough to specify their own codepage. I know, it's asking a lot. + set(codepage_flags "") + endif() + + message("${XINDY_COMPILER} ${MAKEGLOSSARIES_COMPILER_ARGS} ${language_flags} ${codepage_flags} -I xindy -M ${glossary_name} -t ${glossary_log} -o ${glossary_out} ${glossary_in}" + ) + exec_program(${XINDY_COMPILER} + ARGS ${MAKEGLOSSARIES_COMPILER_ARGS} + ${language_flags} + ${codepage_flags} + -I xindy + -M ${glossary_name} + -t ${glossary_log} + -o ${glossary_out} + ${glossary_in} + OUTPUT_VARIABLE xindy_output + ) + message("${xindy_output}") + + # So, it is possible (perhaps common?) for aux files to specify a + # language and codepage that are incompatible with each other. Check + # for that condition, and if it happens run again with the default + # codepage. + if("${xindy_output}" MATCHES "^Cannot locate xindy module for language (.+) in codepage (.+)\\.$") + message("*************** Retrying xindy with default codepage.") + exec_program(${XINDY_COMPILER} + ARGS ${MAKEGLOSSARIES_COMPILER_ARGS} + ${language_flags} + -I xindy + -M ${glossary_name} + -t ${glossary_log} + -o ${glossary_out} + ${glossary_in} + ) + endif() + + else() + message("${MAKEINDEX_COMPILER} ${MAKEGLOSSARIES_COMPILER_ARGS} -s ${istfile} -t ${glossary_log} -o ${glossary_out} ${glossary_in}") + exec_program(${MAKEINDEX_COMPILER} ARGS ${MAKEGLOSSARIES_COMPILER_ARGS} + -s ${istfile} -t ${glossary_log} -o ${glossary_out} ${glossary_in} + ) + endif() + + endforeach(newglossary) +endfunction(latex_makeglossaries) + +function(latex_makenomenclature) + message("**************************** In makenomenclature") + if(NOT LATEX_TARGET) + message(SEND_ERROR "Need to define LATEX_TARGET") + endif() + + if(NOT MAKEINDEX_COMPILER) + message(SEND_ERROR "Need to define MAKEINDEX_COMPILER") + endif() + + set(nomencl_out ${LATEX_TARGET}.nls) + set(nomencl_in ${LATEX_TARGET}.nlo) + + exec_program(${MAKEINDEX_COMPILER} ARGS ${MAKENOMENCLATURE_COMPILER_ARGS} + ${nomencl_in} -s "nomencl.ist" -o ${nomencl_out} + ) +endfunction(latex_makenomenclature) + +function(latex_correct_synctex) + message("**************************** In correct SyncTeX") + if(NOT LATEX_TARGET) + message(SEND_ERROR "Need to define LATEX_TARGET") + endif() + + if(NOT GZIP) + message(SEND_ERROR "Need to define GZIP") + endif() + + if(NOT LATEX_SOURCE_DIRECTORY) + message(SEND_ERROR "Need to define LATEX_SOURCE_DIRECTORY") + endif() + + if(NOT LATEX_BINARY_DIRECTORY) + message(SEND_ERROR "Need to define LATEX_BINARY_DIRECTORY") + endif() + message("${LATEX_BINARY_DIRECTORY}") + message("${LATEX_SOURCE_DIRECTORY}") + + set(synctex_file ${LATEX_BINARY_DIRECTORY}/${LATEX_TARGET}.synctex) + set(synctex_file_gz ${synctex_file}.gz) + + if(EXISTS ${synctex_file_gz}) + + message("Making backup of synctex file.") + configure_file(${synctex_file_gz} ${synctex_file}.bak.gz COPYONLY) + + message("Uncompressing synctex file.") + exec_program(${GZIP} + ARGS --decompress ${synctex_file_gz} + ) + + message("Reading synctex file.") + file(READ ${synctex_file} synctex_data) + + message("Replacing output paths with input paths.") + foreach(extension tex cls bst clo sty ist fd) + # Relative paths + string(REGEX REPLACE + "(Input:[0-9]+:)([^/\n][^\n]\\.${extension}*)" + "\\1${LATEX_SOURCE_DIRECTORY}/\\2" + synctex_data + "${synctex_data}" + ) + + # Absolute paths + string(REGEX REPLACE + "(Input:[0-9]+:)${LATEX_BINARY_DIRECTORY}([^\n]*\\.${extension})" + "\\1${LATEX_SOURCE_DIRECTORY}\\2" + synctex_data + "${synctex_data}" + ) + endforeach(extension) + + message("Writing synctex file.") + file(WRITE ${synctex_file} "${synctex_data}") + + message("Compressing synctex file.") + exec_program(${GZIP} + ARGS ${synctex_file} + ) + + else() + + message(SEND_ERROR "File ${synctex_file_gz} not found. Perhaps synctex is not supported by your LaTeX compiler.") + + endif() + +endfunction(latex_correct_synctex) + +function(latex_check_important_warnings) + # Check for biber warnings/errors if that was run + set(bib_log_file ${LATEX_TARGET}.blg) + if(EXISTS ${bib_log_file}) + file(READ ${bib_log_file} bib_log) + if(bib_log MATCHES "INFO - This is Biber") + message("\nChecking ${bib_log_file} for Biber warnings/errors.") + + string(REGEX MATCHALL + "[A-Z]+ - [^\n]*" + biber_messages + "${bib_log}") + + set(found_error) + foreach(message ${biber_messages}) + if(NOT message MATCHES "^INFO - ") + set(found_error TRUE) + message("${message}") + endif() + endforeach(message) + + if(found_error) + latex_get_filename_component(log_file_path ${bib_log_file} ABSOLUTE) + message("\nConsult ${log_file_path} for more information on Biber output.") + else() + message("No known important Biber output found.") + endif(found_error) + else() # Biber output not in log file + message("Skipping biber checks (biber not used)") + endif() + else() # No bib log file + message("Skipping bibliography checks (not run)") + endif() + + set(log_file ${LATEX_TARGET}.log) + + message("\nChecking ${log_file} for important warnings.") + if(NOT LATEX_TARGET) + message(SEND_ERROR "Need to define LATEX_TARGET") + endif() + + if(NOT EXISTS ${log_file}) + message("Could not find log file: ${log_file}") + return() + endif() + + set(found_error) + + file(READ ${log_file} log) + + # Check for declared LaTeX warnings + string(REGEX MATCHALL + "\nLaTeX Warning:[^\n]*" + latex_warnings + "${log}") + if(latex_warnings) + set(found_error TRUE) + message("\nFound declared LaTeX warnings.") + foreach(warning ${latex_warnings}) + string(STRIP "${warning}" warning_no_newline) + message("${warning_no_newline}") + endforeach(warning) + endif() + + # Check for natbib warnings + string(REGEX MATCHALL + "\nPackage natbib Warning:[^\n]*" + natbib_warnings + "${log}") + if(natbib_warnings) + set(found_error TRUE) + message("\nFound natbib package warnings.") + foreach(warning ${natbib_warnings}) + string(STRIP "${warning}" warning_no_newline) + message("${warning_no_newline}") + endforeach(warning) + endif() + + # Check for overfull + string(REGEX MATCHALL + "\nOverfull[^\n]*" + overfull_warnings + "${log}") + if(overfull_warnings) + set(found_error TRUE) + message("\nFound overfull warnings. These are indicative of layout errors.") + foreach(warning ${overfull_warnings}) + string(STRIP "${warning}" warning_no_newline) + message("${warning_no_newline}") + endforeach(warning) + endif() + + # Check for invalid characters + string(REGEX MATCHALL + "\nMissing character:[^\n]*" + invalid_character_warnings + "${log}") + if(invalid_character_warnings) + set(found_error TRUE) + message("\nFound invalid character warnings. These characters are likely not printed correctly.") + foreach(warning ${invalid_character_warnings}) + string(STRIP "${warning}" warning_no_newline) + message("${warning_no_newline}") + endforeach(warning) + endif() + + if(found_error) + latex_get_filename_component(log_file_path ${log_file} ABSOLUTE) + message("\nConsult ${log_file_path} for more information on LaTeX build.") + else() + message("No known important warnings found.") + endif(found_error) +endfunction(latex_check_important_warnings) + +############################################################################# +# Helper functions for establishing LaTeX build. +############################################################################# + +function(latex_needit VAR NAME) + if(NOT ${VAR}) + message(SEND_ERROR "I need the ${NAME} command.") + endif() +endfunction(latex_needit) + +function(latex_wantit VAR NAME) + if(NOT ${VAR}) + message(STATUS "I could not find the ${NAME} command.") + endif() +endfunction(latex_wantit) + +function(latex_setup_variables) + set(LATEX_OUTPUT_PATH "${LATEX_OUTPUT_PATH}" + CACHE PATH "If non empty, specifies the location to place LaTeX output." + ) + + find_package(LATEX) + + find_program(XINDY_COMPILER + NAME xindy + PATHS ${MIKTEX_BINARY_PATH} /usr/bin + ) + + find_package(UnixCommands) + + find_program(PDFTOPS_CONVERTER + NAMES pdftops + DOC "The pdf to ps converter program from the Poppler package." + ) + + find_program(HTLATEX_COMPILER + NAMES htlatex + PATHS ${MIKTEX_BINARY_PATH} + /usr/bin + ) + + mark_as_advanced( + LATEX_COMPILER + PDFLATEX_COMPILER + BIBTEX_COMPILER + BIBER_COMPILER + MAKEINDEX_COMPILER + XINDY_COMPILER + DVIPS_CONVERTER + PS2PDF_CONVERTER + PDFTOPS_CONVERTER + LATEX2HTML_CONVERTER + HTLATEX_COMPILER + ) + + latex_needit(LATEX_COMPILER latex) + latex_wantit(PDFLATEX_COMPILER pdflatex) + latex_wantit(HTLATEX_COMPILER htlatex) + latex_needit(BIBTEX_COMPILER bibtex) + latex_wantit(BIBER_COMPILER biber) + latex_needit(MAKEINDEX_COMPILER makeindex) + latex_wantit(DVIPS_CONVERTER dvips) + latex_wantit(PS2PDF_CONVERTER ps2pdf) + latex_wantit(PDFTOPS_CONVERTER pdftops) + + set(LATEX_COMPILER_FLAGS "-interaction=batchmode -file-line-error" + CACHE STRING "Flags passed to latex.") + set(PDFLATEX_COMPILER_FLAGS ${LATEX_COMPILER_FLAGS} + CACHE STRING "Flags passed to pdflatex.") + set(HTLATEX_COMPILER_TEX4HT_FLAGS "html" + CACHE STRING "Options for the tex4ht.sty and *.4ht style files.") + set(HTLATEX_COMPILER_TEX4HT_POSTPROCESSOR_FLAGS "" + CACHE STRING "Options for the text4ht postprocessor.") + set(HTLATEX_COMPILER_T4HT_POSTPROCESSOR_FLAGS "" + CACHE STRING "Options for the t4ht postprocessor.") + set(HTLATEX_COMPILER_LATEX_FLAGS ${LATEX_COMPILER_FLAGS} + CACHE STRING "Flags passed from htlatex to the LaTeX compiler.") + set(LATEX_SYNCTEX_FLAGS "-synctex=1" + CACHE STRING "latex/pdflatex flags used to create synctex file.") + set(BIBTEX_COMPILER_FLAGS "" + CACHE STRING "Flags passed to bibtex.") + set(BIBER_COMPILER_FLAGS "" + CACHE STRING "Flags passed to biber.") + set(MAKEINDEX_COMPILER_FLAGS "" + CACHE STRING "Flags passed to makeindex.") + set(MAKEGLOSSARIES_COMPILER_FLAGS "" + CACHE STRING "Flags passed to makeglossaries.") + set(MAKENOMENCLATURE_COMPILER_FLAGS "" + CACHE STRING "Flags passed to makenomenclature.") + set(DVIPS_CONVERTER_FLAGS "-Ppdf -G0 -t letter" + CACHE STRING "Flags passed to dvips.") + if(NOT WIN32) + set(PS2PDF_CONVERTER_FLAGS "-dMaxSubsetPct=100 -dCompatibilityLevel=1.3 -dSubsetFonts=true -dEmbedAllFonts=true -dAutoFilterColorImages=false -dAutoFilterGrayImages=false -dColorImageFilter=/FlateEncode -dGrayImageFilter=/FlateEncode -dMonoImageFilter=/FlateEncode" + CACHE STRING "Flags passed to ps2pdf.") + else() + # Most windows ports of ghostscript utilities use .bat files for ps2pdf + # commands. bat scripts interpret "=" as a special character and separate + # those arguments. To get around this, the ghostscript utilities also + # support using "#" in place of "=". + set(PS2PDF_CONVERTER_FLAGS "-dMaxSubsetPct#100 -dCompatibilityLevel#1.3 -dSubsetFonts#true -dEmbedAllFonts#true -dAutoFilterColorImages#false -dAutoFilterGrayImages#false -dColorImageFilter#/FlateEncode -dGrayImageFilter#/FlateEncode -dMonoImageFilter#/FlateEncode" + CACHE STRING "Flags passed to ps2pdf.") + endif() + set(PDFTOPS_CONVERTER_FLAGS "" + CACHE STRING "Flags passed to pdftops.") + mark_as_advanced( + LATEX_COMPILER_FLAGS + PDFLATEX_COMPILER_FLAGS + HTLATEX_COMPILER_TEX4HT_FLAGS + HTLATEX_COMPILER_TEX4HT_POSTPROCESSOR_FLAGS + HTLATEX_COMPILER_T4HT_POSTPROCESSOR_FLAGS + HTLATEX_COMPILER_LATEX_FLAGS + LATEX_SYNCTEX_FLAGS + BIBTEX_COMPILER_FLAGS + BIBER_COMPILER_FLAGS + MAKEINDEX_COMPILER_FLAGS + MAKEGLOSSARIES_COMPILER_FLAGS + MAKENOMENCLATURE_COMPILER_FLAGS + DVIPS_CONVERTER_FLAGS + PS2PDF_CONVERTER_FLAGS + PDFTOPS_CONVERTER_FLAGS + ) + + # Because it is easier to type, the flags variables are entered as + # space-separated strings much like you would in a shell. However, when + # using a CMake command to execute a program, it works better to hold the + # arguments in semicolon-separated lists (otherwise the whole string will + # be interpreted as a single argument). Use the separate_arguments to + # convert the space-separated strings to semicolon-separated lists. + separate_arguments(LATEX_COMPILER_FLAGS) + separate_arguments(PDFLATEX_COMPILER_FLAGS) + separate_arguments(HTLATEX_COMPILER_LATEX_FLAGS) + separate_arguments(LATEX_SYNCTEX_FLAGS) + separate_arguments(BIBTEX_COMPILER_FLAGS) + separate_arguments(BIBER_COMPILER_FLAGS) + separate_arguments(MAKEINDEX_COMPILER_FLAGS) + separate_arguments(MAKEGLOSSARIES_COMPILER_FLAGS) + separate_arguments(MAKENOMENCLATURE_COMPILER_FLAGS) + separate_arguments(DVIPS_CONVERTER_FLAGS) + separate_arguments(PS2PDF_CONVERTER_FLAGS) + separate_arguments(PDFTOPS_CONVERTER_FLAGS) + + # Not quite done. When you call separate_arguments on a cache variable, + # the result is written to a local variable. That local variable goes + # away when this function returns (which is before any of them are used). + # So, copy these variables with local scope to cache variables with + # global scope. + set(LATEX_COMPILER_ARGS "${LATEX_COMPILER_FLAGS}" CACHE INTERNAL "") + set(PDFLATEX_COMPILER_ARGS "${PDFLATEX_COMPILER_FLAGS}" CACHE INTERNAL "") + set(HTLATEX_COMPILER_ARGS "${HTLATEX_COMPILER_LATEX_FLAGS}" CACHE INTERNAL "") + set(LATEX_SYNCTEX_ARGS "${LATEX_SYNCTEX_FLAGS}" CACHE INTERNAL "") + set(BIBTEX_COMPILER_ARGS "${BIBTEX_COMPILER_FLAGS}" CACHE INTERNAL "") + set(BIBER_COMPILER_ARGS "${BIBER_COMPILER_FLAGS}" CACHE INTERNAL "") + set(MAKEINDEX_COMPILER_ARGS "${MAKEINDEX_COMPILER_FLAGS}" CACHE INTERNAL "") + set(MAKEGLOSSARIES_COMPILER_ARGS "${MAKEGLOSSARIES_COMPILER_FLAGS}" CACHE INTERNAL "") + set(MAKENOMENCLATURE_COMPILER_ARGS "${MAKENOMENCLATURE_COMPILER_FLAGS}" CACHE INTERNAL "") + set(DVIPS_CONVERTER_ARGS "${DVIPS_CONVERTER_FLAGS}" CACHE INTERNAL "") + set(PS2PDF_CONVERTER_ARGS "${PS2PDF_CONVERTER_FLAGS}" CACHE INTERNAL "") + set(PDFTOPS_CONVERTER_ARGS "${PDFTOPS_CONVERTER_FLAGS}" CACHE INTERNAL "") + + find_program(IMAGEMAGICK_CONVERT + NAMES magick convert + DOC "The convert program that comes with ImageMagick (available at http://www.imagemagick.org)." + ) + mark_as_advanced(IMAGEMAGICK_CONVERT) + + if(DEFINED ENV{LATEX_DEFAULT_BUILD}) + set(default_build $ENV{LATEX_DEFAULT_BUILD}) + else() + set(default_build pdf) + endif() + + set(LATEX_DEFAULT_BUILD "${default_build}" CACHE STRING + "Choose the default type of LaTeX build. Valid options are pdf, dvi, ps, safepdf, html" + ) + set_property(CACHE LATEX_DEFAULT_BUILD + PROPERTY STRINGS pdf dvi ps safepdf html + ) + + option(LATEX_USE_SYNCTEX + "If on, have LaTeX generate a synctex file, which WYSIWYG editors can use to correlate output files like dvi and pdf with the lines of LaTeX source that generates them. In addition to adding the LATEX_SYNCTEX_FLAGS to the command line, this option also adds build commands that \"corrects\" the resulting synctex file to point to the original LaTeX files rather than those generated by UseLATEX.cmake." + OFF + ) + + option(LATEX_SMALL_IMAGES + "If on, the raster images will be converted to 1/6 the original size. This is because papers usually require 600 dpi images whereas most monitors only require at most 96 dpi. Thus, smaller images make smaller files for web distribution and can make it faster to read dvi files." + OFF) + if(LATEX_SMALL_IMAGES) + set(LATEX_RASTER_SCALE 16 PARENT_SCOPE) + set(LATEX_OPPOSITE_RASTER_SCALE 100 PARENT_SCOPE) + else() + set(LATEX_RASTER_SCALE 100 PARENT_SCOPE) + set(LATEX_OPPOSITE_RASTER_SCALE 16 PARENT_SCOPE) + endif() + + # Just holds extensions for known image types. They should all be lower case. + # For historical reasons, these are all declared in the global scope. + set(LATEX_DVI_VECTOR_IMAGE_EXTENSIONS .eps CACHE INTERNAL "") + set(LATEX_DVI_RASTER_IMAGE_EXTENSIONS CACHE INTERNAL "") + set(LATEX_DVI_IMAGE_EXTENSIONS + ${LATEX_DVI_VECTOR_IMAGE_EXTENSIONS} + ${LATEX_DVI_RASTER_IMAGE_EXTENSIONS} + CACHE INTERNAL "" + ) + + set(LATEX_PDF_VECTOR_IMAGE_EXTENSIONS .pdf CACHE INTERNAL "") + set(LATEX_PDF_RASTER_IMAGE_EXTENSIONS .jpeg .jpg .png CACHE INTERNAL "") + set(LATEX_PDF_IMAGE_EXTENSIONS + ${LATEX_PDF_VECTOR_IMAGE_EXTENSIONS} + ${LATEX_PDF_RASTER_IMAGE_EXTENSIONS} + CACHE INTERNAL "" + ) + + set(LATEX_OTHER_VECTOR_IMAGE_EXTENSIONS .ai .dot .svg CACHE INTERNAL "") + set(LATEX_OTHER_RASTER_IMAGE_EXTENSIONS + .bmp .bmp2 .bmp3 .dcm .dcx .ico .gif .pict .ppm .tif .tiff + CACHE INTERNAL "") + set(LATEX_OTHER_IMAGE_EXTENSIONS + ${LATEX_OTHER_VECTOR_IMAGE_EXTENSIONS} + ${LATEX_OTHER_RASTER_IMAGE_EXTENSIONS} + CACHE INTERNAL "" + ) + + set(LATEX_VECTOR_IMAGE_EXTENSIONS + ${LATEX_DVI_VECTOR_IMAGE_EXTENSIONS} + ${LATEX_PDF_VECTOR_IMAGE_EXTENSIONS} + ${LATEX_OTHER_VECTOR_IMAGE_EXTENSIONS} + CACHE INTERNAL "" + ) + set(LATEX_RASTER_IMAGE_EXTENSIONS + ${LATEX_DVI_RASTER_IMAGE_EXTENSIONS} + ${LATEX_PDF_RASTER_IMAGE_EXTENSIONS} + ${LATEX_OTHER_RASTER_IMAGE_EXTENSIONS} + CACHE INTERNAL "" + ) + set(LATEX_IMAGE_EXTENSIONS + ${LATEX_DVI_IMAGE_EXTENSIONS} + ${LATEX_PDF_IMAGE_EXTENSIONS} + ${LATEX_OTHER_IMAGE_EXTENSIONS} + CACHE INTERNAL "" + ) +endfunction(latex_setup_variables) + +function(latex_setup_targets) + if(NOT TARGET pdf) + add_custom_target(pdf) + endif() + if(NOT TARGET dvi) + add_custom_target(dvi) + endif() + if(NOT TARGET ps) + add_custom_target(ps) + endif() + if(NOT TARGET safepdf) + add_custom_target(safepdf) + endif() + if(NOT TARGET html) + add_custom_target(html) + endif() + if(NOT TARGET auxclean) + add_custom_target(auxclean) + endif() +endfunction(latex_setup_targets) + +function(latex_get_output_path var) + set(latex_output_path) + if(LATEX_OUTPUT_PATH) + get_filename_component( + LATEX_OUTPUT_PATH_FULL "${LATEX_OUTPUT_PATH}" ABSOLUTE + ) + if("${LATEX_OUTPUT_PATH_FULL}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") + message(SEND_ERROR "You cannot set LATEX_OUTPUT_PATH to the same directory that contains LaTeX input files.") + else() + set(latex_output_path "${LATEX_OUTPUT_PATH_FULL}") + endif() + else() + if("${CMAKE_CURRENT_BINARY_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") + message(SEND_ERROR "LaTeX files must be built out of source or you must set LATEX_OUTPUT_PATH.") + else() + set(latex_output_path "${CMAKE_CURRENT_BINARY_DIR}") + endif() + endif() + set(${var} ${latex_output_path} PARENT_SCOPE) +endfunction(latex_get_output_path) + +function(latex_add_convert_command + output_path + input_path + output_extension + input_extension + flags + ) + set(require_imagemagick_convert TRUE) + set(convert_flags "") + if(${input_extension} STREQUAL ".eps" AND ${output_extension} STREQUAL ".pdf") + # ImageMagick has broken eps to pdf conversion + # use ps2pdf instead + if(PS2PDF_CONVERTER) + set(require_imagemagick_convert FALSE) + set(converter ${PS2PDF_CONVERTER}) + set(convert_flags -dEPSCrop ${PS2PDF_CONVERTER_ARGS}) + else() + message(SEND_ERROR "Using postscript files with pdflatex requires ps2pdf for conversion.") + endif() + elseif(${input_extension} STREQUAL ".pdf" AND ${output_extension} STREQUAL ".eps") + # ImageMagick can also be sketchy on pdf to eps conversion. Not good with + # color spaces and tends to unnecessarily rasterize. + # use pdftops instead + if(PDFTOPS_CONVERTER) + set(require_imagemagick_convert FALSE) + set(converter ${PDFTOPS_CONVERTER}) + set(convert_flags -eps ${PDFTOPS_CONVERTER_ARGS}) + else() + message(STATUS "Consider getting pdftops from Poppler to convert PDF images to EPS images.") + set(convert_flags ${flags}) + endif() + else() + set(convert_flags ${flags}) + endif() + + if(require_imagemagick_convert) + if(IMAGEMAGICK_CONVERT) + string(TOLOWER ${IMAGEMAGICK_CONVERT} IMAGEMAGICK_CONVERT_LOWERCASE) + if(${IMAGEMAGICK_CONVERT_LOWERCASE} MATCHES "system32[/\\\\]convert\\.exe") + message(SEND_ERROR "IMAGEMAGICK_CONVERT set to Window's convert.exe for changing file systems rather than ImageMagick's convert for changing image formats. Please make sure ImageMagick is installed (available at http://www.imagemagick.org). If you have a recent version of ImageMagick (7.0 or higher), use the magick program instead of convert for IMAGEMAGICK_CONVERT.") + else() + set(converter ${IMAGEMAGICK_CONVERT}) + # ImageMagick requires a special order of arguments where resize and + # arguments of that nature must be placed after the input image path. + add_custom_command(OUTPUT ${output_path} + COMMAND ${converter} + ARGS ${input_path} ${convert_flags} ${output_path} + DEPENDS ${input_path} + ) + endif() + else() + message(SEND_ERROR "Could not find convert program. Please download ImageMagick from http://www.imagemagick.org and install.") + endif() + else() # Not ImageMagick convert + add_custom_command(OUTPUT ${output_path} + COMMAND ${converter} + ARGS ${convert_flags} ${input_path} ${output_path} + DEPENDS ${input_path} + ) + endif() +endfunction(latex_add_convert_command) + +# Makes custom commands to convert a file to a particular type. +function(latex_convert_image + output_files_var + input_file + output_extension + convert_flags + output_extensions + other_files + ) + set(output_file_list) + set(input_dir ${CMAKE_CURRENT_SOURCE_DIR}) + latex_get_output_path(output_dir) + + latex_get_filename_component(extension "${input_file}" EXT) + + # Check input filename for potential problems with LaTeX. + latex_get_filename_component(name "${input_file}" NAME_WE) + set(suggested_name "${name}") + if(suggested_name MATCHES ".*\\..*") + string(REPLACE "." "-" suggested_name "${suggested_name}") + endif() + if(suggested_name MATCHES ".* .*") + string(REPLACE " " "-" suggested_name "${suggested_name}") + endif() + if(NOT suggested_name STREQUAL name) + message(WARNING "Some LaTeX distributions have problems with image file names with multiple extensions or spaces. Consider changing ${name}${extension} to something like ${suggested_name}${extension}.") + endif() + + string(REGEX REPLACE "\\.[^.]*\$" ${output_extension} output_file + "${input_file}") + + latex_list_contains(is_type ${extension} ${output_extensions}) + if(is_type) + if(convert_flags) + latex_add_convert_command(${output_dir}/${output_file} + ${input_dir}/${input_file} ${output_extension} ${extension} + "${convert_flags}") + set(output_file_list ${output_dir}/${output_file}) + else() + # As a shortcut, we can just copy the file. + add_custom_command(OUTPUT ${output_dir}/${input_file} + COMMAND ${CMAKE_COMMAND} + ARGS -E copy ${input_dir}/${input_file} ${output_dir}/${input_file} + DEPENDS ${input_dir}/${input_file} + ) + set(output_file_list ${output_dir}/${input_file}) + endif() + else() + set(do_convert TRUE) + # Check to see if there is another input file of the appropriate type. + foreach(valid_extension ${output_extensions}) + string(REGEX REPLACE "\\.[^.]*\$" ${output_extension} try_file + "${input_file}") + latex_list_contains(has_native_file "${try_file}" ${other_files}) + if(has_native_file) + set(do_convert FALSE) + endif() + endforeach(valid_extension) + + # If we still need to convert, do it. + if(do_convert) + latex_add_convert_command(${output_dir}/${output_file} + ${input_dir}/${input_file} ${output_extension} ${extension} + "${convert_flags}") + set(output_file_list ${output_dir}/${output_file}) + endif() + endif() + + set(${output_files_var} ${output_file_list} PARENT_SCOPE) +endfunction(latex_convert_image) + +# Adds custom commands to process the given files for dvi and pdf builds. +# Adds the output files to the given variables (does not replace). +function(latex_process_images dvi_outputs_var pdf_outputs_var) + latex_get_output_path(output_dir) + set(dvi_outputs) + set(pdf_outputs) + foreach(file ${ARGN}) + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${file}") + latex_get_filename_component(extension "${file}" EXT) + set(convert_flags) + + # Check to see if we need to downsample the image. + latex_list_contains(is_raster "${extension}" + ${LATEX_RASTER_IMAGE_EXTENSIONS}) + if(LATEX_SMALL_IMAGES) + if(is_raster) + set(convert_flags -resize ${LATEX_RASTER_SCALE}%) + endif() + endif() + + # Make sure the output directory exists. + latex_get_filename_component(path "${output_dir}/${file}" PATH) + make_directory("${path}") + + # Do conversions for dvi. + if(NOT LATEX_FORCE_PDF) + latex_convert_image(output_files "${file}" .eps "${convert_flags}" + "${LATEX_DVI_IMAGE_EXTENSIONS}" "${ARGN}") + list(APPEND dvi_outputs ${output_files}) + endif () + + # Do conversions for pdf. + if(NOT LATEX_FORCE_DVI AND NOT LATEX_FORCE_HTML) + if(is_raster) + latex_convert_image(output_files "${file}" .png "${convert_flags}" + "${LATEX_PDF_IMAGE_EXTENSIONS}" "${ARGN}") + list(APPEND pdf_outputs ${output_files}) + else() + latex_convert_image(output_files "${file}" .pdf "${convert_flags}" + "${LATEX_PDF_IMAGE_EXTENSIONS}" "${ARGN}") + list(APPEND pdf_outputs ${output_files}) + endif() + endif() + else() + message(WARNING "Could not find file ${CMAKE_CURRENT_SOURCE_DIR}/${file}. Are you sure you gave relative paths to IMAGES?") + endif() + endforeach(file) + + set(${dvi_outputs_var} ${dvi_outputs} PARENT_SCOPE) + set(${pdf_outputs_var} ${pdf_outputs} PARENT_SCOPE) +endfunction(latex_process_images) + +function(latex_copy_globbed_files pattern dest) + file(GLOB file_list ${pattern}) + foreach(in_file ${file_list}) + latex_get_filename_component(out_file ${in_file} NAME) + configure_file(${in_file} ${dest}/${out_file} COPYONLY) + endforeach(in_file) +endfunction(latex_copy_globbed_files) + +function(latex_copy_input_file file) + latex_get_output_path(output_dir) + + if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${file}) + latex_get_filename_component(path ${file} PATH) + file(MAKE_DIRECTORY ${output_dir}/${path}) + + latex_list_contains(use_config ${file} ${LATEX_CONFIGURE}) + if(use_config) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/${file} + ${output_dir}/${file} + @ONLY + ) + add_custom_command(OUTPUT ${output_dir}/${file} + COMMAND ${CMAKE_COMMAND} + ARGS ${CMAKE_BINARY_DIR} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${file} + ) + else() + add_custom_command(OUTPUT ${output_dir}/${file} + COMMAND ${CMAKE_COMMAND} + ARGS -E copy ${CMAKE_CURRENT_SOURCE_DIR}/${file} ${output_dir}/${file} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${file} + ) + endif() + else() + if(EXISTS ${output_dir}/${file}) + # Special case: output exists but input does not. Assume that it was + # created elsewhere and skip the input file copy. + else() + message("Could not find input file ${CMAKE_CURRENT_SOURCE_DIR}/${file}") + endif() + endif() +endfunction(latex_copy_input_file) + +############################################################################# +# Commands provided by the UseLATEX.cmake "package" +############################################################################# + +function(latex_usage command message) + message(SEND_ERROR + "${message}\n Usage: ${command}(\n [BIBFILES ...]\n [INPUTS ...]\n [IMAGE_DIRS ...]\n [IMAGES \n [CONFIGURE ...]\n [DEPENDS ...]\n [MULTIBIB_NEWCITES] \n [USE_BIBLATEX] [USE_INDEX] [USE_GLOSSARY] [USE_NOMENCL]\n [FORCE_PDF] [FORCE_DVI] [FORCE_HTML]\n [TARGET_NAME] \n [EXCLUDE_FROM_ALL]\n [EXCLUDE_FROM_DEFAULTS])" + ) +endfunction(latex_usage command message) + +# Parses arguments to add_latex_document and ADD_LATEX_TARGETS and sets the +# variables LATEX_TARGET, LATEX_IMAGE_DIR, LATEX_BIBFILES, LATEX_DEPENDS, and +# LATEX_INPUTS. +function(parse_add_latex_arguments command latex_main_input) + set(options + USE_BIBLATEX + USE_INDEX + USE_GLOSSARY + USE_NOMENCL + FORCE_PDF + FORCE_DVI + FORCE_HTML + EXCLUDE_FROM_ALL + EXCLUDE_FROM_DEFAULTS + # Deprecated options + USE_GLOSSARIES + DEFAULT_PDF + DEFAULT_SAFEPDF + DEFAULT_PS + NO_DEFAULT + MANGLE_TARGET_NAMES + ) + set(oneValueArgs + TARGET_NAME + ) + set(multiValueArgs + BIBFILES + MULTIBIB_NEWCITES + INPUTS + IMAGE_DIRS + IMAGES + CONFIGURE + DEPENDS + INDEX_NAMES + INCLUDE_DIRECTORIES + ) + cmake_parse_arguments( + LATEX "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + # Handle invalid and deprecated arguments + if(LATEX_UNPARSED_ARGUMENTS) + latex_usage(${command} "Invalid or deprecated arguments: ${LATEX_UNPARSED_ARGUMENTS}") + endif() + if(LATEX_USE_GLOSSARIES) + latex_usage(${command} "USE_GLOSSARIES option removed in version 1.6.1. Use USE_GLOSSARY instead.") + endif() + if(LATEX_DEFAULT_PDF) + latex_usage(${command} "DEFAULT_PDF option removed in version 2.0. Use FORCE_PDF option or LATEX_DEFAULT_BUILD CMake variable instead.") + endif() + if(LATEX_DEFAULT_SAFEPDF) + latex_usage(${command} "DEFAULT_SAFEPDF option removed in version 2.0. Use LATEX_DEFAULT_BUILD CMake variable instead.") + endif() + if(LATEX_DEFAULT_DVI) + latex_usage(${command} "DEFAULT_DVI option removed in version 2.0. Use FORCE_DVI option or LATEX_DEFAULT_BUILD CMake variable instead.") + endif() + if(LATEX_NO_DEFAULT) + latex_usage(${command} "NO_DEFAULT option removed in version 2.0. Use EXCLUDE_FROM_ALL instead.") + endif() + if(LATEX_MANGLE_TARGET_NAMES) + latex_usage(${command} "MANGLE_TARGET_NAMES option removed in version 2.0. All LaTeX targets use mangled names now.") + endif() + + # Capture the first argument, which is the main LaTeX input. + latex_get_filename_component(latex_target ${latex_main_input} NAME_WE) + set(LATEX_MAIN_INPUT ${latex_main_input} PARENT_SCOPE) + set(LATEX_TARGET ${latex_target} PARENT_SCOPE) + + # Propagate the result variables to the caller + foreach(arg_name ${options} ${oneValueArgs} ${multiValueArgs}) + set(var_name LATEX_${arg_name}) + set(${var_name} ${${var_name}} PARENT_SCOPE) + endforeach(arg_name) +endfunction(parse_add_latex_arguments) + +function(add_latex_targets_internal) + latex_get_output_path(output_dir) + + if(LATEX_USE_SYNCTEX) + set(synctex_flags ${LATEX_SYNCTEX_ARGS}) + else() + set(synctex_flags) + endif() + + # The commands to run LaTeX. They are repeated multiple times. + set(latex_build_command + ${LATEX_COMPILER} ${LATEX_COMPILER_ARGS} ${synctex_flags} ${LATEX_MAIN_INPUT} + ) + if(LATEX_COMPILER_ARGS MATCHES ".*batchmode.*") + # Wrap command in script that dumps the log file on error. This makes sure + # errors can be seen. + set(latex_build_command + ${CMAKE_COMMAND} + -D LATEX_BUILD_COMMAND=execute_latex + -D LATEX_WORKING_DIRECTORY="${output_dir}" + -D LATEX_FULL_COMMAND="${latex_build_command}" + -D LATEX_OUTPUT_FILE="${LATEX_TARGET}.dvi" + -D LATEX_LOG_FILE="${LATEX_TARGET}.log" + -P "${LATEX_USE_LATEX_LOCATION}" + ) + endif() + set(pdflatex_build_command + ${PDFLATEX_COMPILER} ${PDFLATEX_COMPILER_ARGS} ${synctex_flags} ${LATEX_MAIN_INPUT} + ) + if(PDFLATEX_COMPILER_ARGS MATCHES ".*batchmode.*") + # Wrap command in script that dumps the log file on error. This makes sure + # errors can be seen. + set(pdflatex_build_command + ${CMAKE_COMMAND} + -D LATEX_BUILD_COMMAND=execute_latex + -D LATEX_WORKING_DIRECTORY="${output_dir}" + -D LATEX_FULL_COMMAND="${pdflatex_build_command}" + -D LATEX_OUTPUT_FILE="${LATEX_TARGET}.pdf" + -D LATEX_LOG_FILE="${LATEX_TARGET}.log" + -P "${LATEX_USE_LATEX_LOCATION}" + ) + endif() + + if(LATEX_INCLUDE_DIRECTORIES) + # The include directories needs to start with the build directory so + # that the copied files can be found. It also needs to end with an + # empty directory so that the standard system directories are included + # after any specified. + set(LATEX_INCLUDE_DIRECTORIES . ${LATEX_INCLUDE_DIRECTORIES} "") + + # CMake separates items in a list with a semicolon. Lists of + # directories on most systems are separated by colons, so we can do a + # simple text replace. On Windows, directories are separated by + # semicolons, but we replace them with the $ generator + # expression to make sure CMake treats it as a single string. + if(CMAKE_HOST_WIN32) + string(REPLACE ";" "$" TEXINPUTS "${LATEX_INCLUDE_DIRECTORIES}") + else() + string(REPLACE ";" ":" TEXINPUTS "${LATEX_INCLUDE_DIRECTORIES}") + endif() + + # Set the TEXINPUTS environment variable + set(latex_build_command + ${CMAKE_COMMAND} -E env TEXINPUTS=${TEXINPUTS} ${latex_build_command}) + set(pdflatex_build_command + ${CMAKE_COMMAND} -E env TEXINPUTS=${TEXINPUTS} ${pdflatex_build_command}) + endif() + + if(NOT LATEX_TARGET_NAME) + # Use the main filename (minus the .tex) as the target name. Remove any + # spaces since CMake cannot have spaces in its target names. + string(REPLACE " " "_" LATEX_TARGET_NAME ${LATEX_TARGET}) + endif() + + # Some LaTeX commands may need to be modified (or may not work) if the main + # tex file is in a subdirectory. Make a flag for that. + get_filename_component(LATEX_MAIN_INPUT_SUBDIR ${LATEX_MAIN_INPUT} DIRECTORY) + + # Set up target names. + set(dvi_target ${LATEX_TARGET_NAME}_dvi) + set(pdf_target ${LATEX_TARGET_NAME}_pdf) + set(ps_target ${LATEX_TARGET_NAME}_ps) + set(safepdf_target ${LATEX_TARGET_NAME}_safepdf) + set(html_target ${LATEX_TARGET_NAME}_html) + set(auxclean_target ${LATEX_TARGET_NAME}_auxclean) + + # Probably not all of these will be generated, but they could be. + # Note that the aux file is added later. + set(auxiliary_clean_files + ${output_dir}/${LATEX_TARGET}.aux + ${output_dir}/${LATEX_TARGET}.bbl + ${output_dir}/${LATEX_TARGET}.blg + ${output_dir}/${LATEX_TARGET}-blx.bib + ${output_dir}/${LATEX_TARGET}.glg + ${output_dir}/${LATEX_TARGET}.glo + ${output_dir}/${LATEX_TARGET}.gls + ${output_dir}/${LATEX_TARGET}.idx + ${output_dir}/${LATEX_TARGET}.ilg + ${output_dir}/${LATEX_TARGET}.ind + ${output_dir}/${LATEX_TARGET}.ist + ${output_dir}/${LATEX_TARGET}.log + ${output_dir}/${LATEX_TARGET}.out + ${output_dir}/${LATEX_TARGET}.toc + ${output_dir}/${LATEX_TARGET}.lof + ${output_dir}/${LATEX_TARGET}.xdy + ${output_dir}/${LATEX_TARGET}.synctex.gz + ${output_dir}/${LATEX_TARGET}.synctex.bak.gz + ${output_dir}/${LATEX_TARGET}.dvi + ${output_dir}/${LATEX_TARGET}.ps + ${output_dir}/${LATEX_TARGET}.pdf + ) + + set(image_list ${LATEX_IMAGES}) + + # For each directory in LATEX_IMAGE_DIRS, glob all the image files and + # place them in LATEX_IMAGES. + foreach(dir ${LATEX_IMAGE_DIRS}) + if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${dir}) + message(WARNING "Image directory ${CMAKE_CURRENT_SOURCE_DIR}/${dir} does not exist. Are you sure you gave relative directories to IMAGE_DIRS?") + endif() + foreach(extension ${LATEX_IMAGE_EXTENSIONS}) + file(GLOB files ${CMAKE_CURRENT_SOURCE_DIR}/${dir}/*${extension}) + foreach(file ${files}) + latex_get_filename_component(filename ${file} NAME) + list(APPEND image_list ${dir}/${filename}) + endforeach(file) + endforeach(extension) + endforeach(dir) + + latex_process_images(dvi_images pdf_images ${image_list}) + + set(make_dvi_command + ${CMAKE_COMMAND} -E chdir ${output_dir} + ${latex_build_command}) + set(make_pdf_command + ${CMAKE_COMMAND} -E chdir ${output_dir} + ${pdflatex_build_command} + ) + + set(make_dvi_depends ${LATEX_DEPENDS} ${dvi_images}) + set(make_pdf_depends ${LATEX_DEPENDS} ${pdf_images}) + foreach(input ${LATEX_MAIN_INPUT} ${LATEX_INPUTS}) + list(APPEND make_dvi_depends ${output_dir}/${input}) + list(APPEND make_pdf_depends ${output_dir}/${input}) + if(${input} MATCHES "\\.tex$") + # Dependent .tex files might have their own .aux files created. Make + # sure these get cleaned as well. This might replicate the cleaning + # of the main .aux file, which is OK. + string(REGEX REPLACE "\\.tex$" "" input_we ${input}) + list(APPEND auxiliary_clean_files + ${output_dir}/${input_we}.aux + ${output_dir}/${input}.aux + ) + endif() + endforeach(input) + + set(all_latex_sources ${LATEX_MAIN_INPUT} ${LATEX_INPUTS} ${image_list}) + + if(LATEX_USE_GLOSSARY) + foreach(dummy 0 1) # Repeat these commands twice. + set(make_dvi_command ${make_dvi_command} + COMMAND ${CMAKE_COMMAND} -E chdir ${output_dir} + ${CMAKE_COMMAND} + -D LATEX_BUILD_COMMAND=makeglossaries + -D LATEX_TARGET=${LATEX_TARGET} + -D MAKEINDEX_COMPILER=${MAKEINDEX_COMPILER} + -D XINDY_COMPILER=${XINDY_COMPILER} + -D MAKEGLOSSARIES_COMPILER_ARGS=${MAKEGLOSSARIES_COMPILER_ARGS} + -P ${LATEX_USE_LATEX_LOCATION} + COMMAND ${CMAKE_COMMAND} -E chdir ${output_dir} + ${latex_build_command} + ) + set(make_pdf_command ${make_pdf_command} + COMMAND ${CMAKE_COMMAND} -E chdir ${output_dir} + ${CMAKE_COMMAND} + -D LATEX_BUILD_COMMAND=makeglossaries + -D LATEX_TARGET=${LATEX_TARGET} + -D MAKEINDEX_COMPILER=${MAKEINDEX_COMPILER} + -D XINDY_COMPILER=${XINDY_COMPILER} + -D MAKEGLOSSARIES_COMPILER_ARGS=${MAKEGLOSSARIES_COMPILER_ARGS} + -P ${LATEX_USE_LATEX_LOCATION} + COMMAND ${CMAKE_COMMAND} -E chdir ${output_dir} + ${pdflatex_build_command} + ) + endforeach(dummy) + endif() + + if(LATEX_USE_NOMENCL) + foreach(dummy 0 1) # Repeat these commands twice. + set(make_dvi_command ${make_dvi_command} + COMMAND ${CMAKE_COMMAND} -E chdir ${output_dir} + ${CMAKE_COMMAND} + -D LATEX_BUILD_COMMAND=makenomenclature + -D LATEX_TARGET=${LATEX_TARGET} + -D MAKEINDEX_COMPILER=${MAKEINDEX_COMPILER} + -D MAKENOMENCLATURE_COMPILER_ARGS=${MAKENOMENCLATURE_COMPILER_ARGS} + -P ${LATEX_USE_LATEX_LOCATION} + COMMAND ${CMAKE_COMMAND} -E chdir ${output_dir} + ${latex_build_command} + ) + set(make_pdf_command ${make_pdf_command} + COMMAND ${CMAKE_COMMAND} -E chdir ${output_dir} + ${CMAKE_COMMAND} + -D LATEX_BUILD_COMMAND=makenomenclature + -D LATEX_TARGET=${LATEX_TARGET} + -D MAKEINDEX_COMPILER=${MAKEINDEX_COMPILER} + -D MAKENOMENCLATURE_COMPILER_ARGS=${MAKENOMENCLATURE_COMPILER_ARGS} + -P ${LATEX_USE_LATEX_LOCATION} + COMMAND ${CMAKE_COMMAND} -E chdir ${output_dir} + ${pdflatex_build_command} + ) + endforeach(dummy) + endif() + + if(LATEX_BIBFILES) + set(suppress_bib_output) + if(LATEX_USE_BIBLATEX) + if(NOT BIBER_COMPILER) + message(SEND_ERROR "I need the biber command.") + endif() + set(bib_compiler ${BIBER_COMPILER}) + set(bib_compiler_flags ${BIBER_COMPILER_ARGS}) + + if(NOT BIBER_COMPILER_ARGS MATCHES ".*-q.*") + # Only suppress bib output if the quiet option is not specified. + set(suppress_bib_output TRUE) + endif() + + if(LATEX_USE_BIBLATEX_CONFIG) + list(APPEND auxiliary_clean_files ${output_dir}/biblatex.cfg) + list(APPEND make_dvi_depends ${output_dir}/biblatex.cfg) + list(APPEND make_pdf_depends ${output_dir}/biblatex.cfg) + endif() + else() + set(bib_compiler ${BIBTEX_COMPILER}) + set(bib_compiler_flags ${BIBTEX_COMPILER_ARGS}) + endif() + if(LATEX_MULTIBIB_NEWCITES) + # Suppressed bib output currently not supported for multibib + foreach (multibib_auxfile ${LATEX_MULTIBIB_NEWCITES}) + latex_get_filename_component(multibib_target ${multibib_auxfile} NAME_WE) + set(make_dvi_command ${make_dvi_command} + COMMAND ${CMAKE_COMMAND} -E chdir ${output_dir} + ${bib_compiler} ${bib_compiler_flags} ${multibib_target}) + set(make_pdf_command ${make_pdf_command} + COMMAND ${CMAKE_COMMAND} -E chdir ${output_dir} + ${bib_compiler} ${bib_compiler_flags} ${multibib_target}) + set(auxiliary_clean_files ${auxiliary_clean_files} + ${output_dir}/${multibib_target}.aux) + endforeach (multibib_auxfile ${LATEX_MULTIBIB_NEWCITES}) + else() + set(full_bib_command + ${bib_compiler} ${bib_compiler_flags} ${LATEX_TARGET}) + if(suppress_bib_output) + set(full_bib_command + ${CMAKE_COMMAND} + -D LATEX_BUILD_COMMAND=execute_latex + -D LATEX_WORKING_DIRECTORY="${output_dir}" + -D LATEX_FULL_COMMAND="${full_bib_command}" + -D LATEX_OUTPUT_FILE="${LATEX_TARGET}.bbl" + -D LATEX_LOG_FILE="${LATEX_TARGET}.blg" + -P "${LATEX_USE_LATEX_LOCATION}" + ) + endif() + set(make_dvi_command ${make_dvi_command} + COMMAND ${CMAKE_COMMAND} -E chdir ${output_dir} + ${full_bib_command}) + set(make_pdf_command ${make_pdf_command} + COMMAND ${CMAKE_COMMAND} -E chdir ${output_dir} + ${full_bib_command}) + endif() + + foreach (bibfile ${LATEX_BIBFILES}) + list(APPEND make_dvi_depends ${output_dir}/${bibfile}) + list(APPEND make_pdf_depends ${output_dir}/${bibfile}) + endforeach (bibfile ${LATEX_BIBFILES}) + else() + if(LATEX_MULTIBIB_NEWCITES) + message(WARNING "MULTIBIB_NEWCITES has no effect without BIBFILES option.") + endif() + endif() + + if(LATEX_USE_INDEX) + if(LATEX_INDEX_NAMES) + set(INDEX_NAMES ${LATEX_INDEX_NAMES}) + else() + set(INDEX_NAMES ${LATEX_TARGET}) + endif() + foreach(idx_name ${INDEX_NAMES}) + set(make_dvi_command ${make_dvi_command} + COMMAND ${CMAKE_COMMAND} -E chdir ${output_dir} + ${latex_build_command} + COMMAND ${CMAKE_COMMAND} -E chdir ${output_dir} + ${MAKEINDEX_COMPILER} ${MAKEINDEX_COMPILER_ARGS} ${idx_name}.idx) + set(make_pdf_command ${make_pdf_command} + COMMAND ${CMAKE_COMMAND} -E chdir ${output_dir} + ${pdflatex_build_command} + COMMAND ${CMAKE_COMMAND} -E chdir ${output_dir} + ${MAKEINDEX_COMPILER} ${MAKEINDEX_COMPILER_ARGS} ${idx_name}.idx) + set(auxiliary_clean_files ${auxiliary_clean_files} + ${output_dir}/${idx_name}.idx + ${output_dir}/${idx_name}.ilg + ${output_dir}/${idx_name}.ind) + endforeach() + else() + if(LATEX_INDEX_NAMES) + message(WARNING "INDEX_NAMES has no effect without USE_INDEX option.") + endif() + endif() + + set(make_dvi_command ${make_dvi_command} + COMMAND ${CMAKE_COMMAND} -E chdir ${output_dir} + ${latex_build_command} + COMMAND ${CMAKE_COMMAND} -E chdir ${output_dir} + ${latex_build_command}) + set(make_pdf_command ${make_pdf_command} + COMMAND ${CMAKE_COMMAND} -E chdir ${output_dir} + ${pdflatex_build_command} + COMMAND ${CMAKE_COMMAND} -E chdir ${output_dir} + ${pdflatex_build_command}) + + # Need to run one more time to remove biblatex' warning + # about page breaks that have changed. + if(LATEX_USE_BIBLATEX) + set(make_dvi_command ${make_dvi_command} + COMMAND ${CMAKE_COMMAND} -E chdir ${output_dir} + ${latex_build_command}) + set(make_pdf_command ${make_pdf_command} + COMMAND ${CMAKE_COMMAND} -E chdir ${output_dir} + ${pdflatex_build_command}) + endif() + + if(LATEX_USE_SYNCTEX) + if(NOT GZIP) + message(SEND_ERROR "UseLATEX.cmake: USE_SYNTEX option requires gzip program. Set GZIP variable.") + endif() + set(make_dvi_command ${make_dvi_command} + COMMAND ${CMAKE_COMMAND} + -D LATEX_BUILD_COMMAND=correct_synctex + -D LATEX_TARGET=${LATEX_TARGET} + -D GZIP=${GZIP} + -D "LATEX_SOURCE_DIRECTORY=${CMAKE_CURRENT_SOURCE_DIR}" + -D "LATEX_BINARY_DIRECTORY=${output_dir}" + -P ${LATEX_USE_LATEX_LOCATION} + ) + set(make_pdf_command ${make_pdf_command} + COMMAND ${CMAKE_COMMAND} + -D LATEX_BUILD_COMMAND=correct_synctex + -D LATEX_TARGET=${LATEX_TARGET} + -D GZIP=${GZIP} + -D "LATEX_SOURCE_DIRECTORY=${CMAKE_CURRENT_SOURCE_DIR}" + -D "LATEX_BINARY_DIRECTORY=${output_dir}" + -P ${LATEX_USE_LATEX_LOCATION} + ) + endif() + + # Check LaTeX output for important warnings at end of build + set(make_dvi_command ${make_dvi_command} + COMMAND ${CMAKE_COMMAND} -E chdir ${output_dir} + ${CMAKE_COMMAND} + -D LATEX_BUILD_COMMAND=check_important_warnings + -D LATEX_TARGET=${LATEX_TARGET} + -P ${LATEX_USE_LATEX_LOCATION} + ) + set(make_pdf_command ${make_pdf_command} + COMMAND ${CMAKE_COMMAND} -E chdir ${output_dir} + ${CMAKE_COMMAND} + -D LATEX_BUILD_COMMAND=check_important_warnings + -D LATEX_TARGET=${LATEX_TARGET} + -P ${LATEX_USE_LATEX_LOCATION} + ) + + # Capture the default build. + string(TOLOWER "${LATEX_DEFAULT_BUILD}" default_build) + + if((NOT LATEX_FORCE_PDF) AND (NOT LATEX_FORCE_DVI) AND (NOT LATEX_FORCE_HTML)) + set(no_force TRUE) + endif() + + # Add commands and targets for building pdf outputs (with pdflatex). + if(LATEX_FORCE_PDF OR no_force) + if(LATEX_FORCE_PDF) + set(default_build pdf) + endif() + + if(PDFLATEX_COMPILER) + add_custom_command(OUTPUT ${output_dir}/${LATEX_TARGET}.pdf + COMMAND ${make_pdf_command} + DEPENDS ${make_pdf_depends} + ) + add_custom_target(${pdf_target} + DEPENDS ${output_dir}/${LATEX_TARGET}.pdf + SOURCES ${all_latex_sources} + ) + if(NOT LATEX_EXCLUDE_FROM_DEFAULTS) + add_dependencies(pdf ${pdf_target}) + endif() + endif() + endif() + + # Add commands and targets for building dvi outputs. + if(LATEX_FORCE_DVI OR LATEX_FORCE_HTML OR no_force) + if(LATEX_FORCE_DVI) + if((NOT default_build STREQUAL dvi) AND + (NOT default_build STREQUAL ps) AND + (NOT default_build STREQUAL safepdf)) + set(default_build dvi) + endif() + endif() + + add_custom_command(OUTPUT ${output_dir}/${LATEX_TARGET}.dvi + COMMAND ${make_dvi_command} + DEPENDS ${make_dvi_depends} + ) + add_custom_target(${dvi_target} + DEPENDS ${output_dir}/${LATEX_TARGET}.dvi + SOURCES ${all_latex_sources} + ) + if(NOT LATEX_EXCLUDE_FROM_DEFAULTS) + add_dependencies(dvi ${dvi_target}) + endif() + + if(DVIPS_CONVERTER) + add_custom_command(OUTPUT ${output_dir}/${LATEX_TARGET}.ps + COMMAND ${CMAKE_COMMAND} -E chdir ${output_dir} + ${DVIPS_CONVERTER} ${DVIPS_CONVERTER_ARGS} -o ${LATEX_TARGET}.ps ${LATEX_TARGET}.dvi + DEPENDS ${output_dir}/${LATEX_TARGET}.dvi) + add_custom_target(${ps_target} + DEPENDS ${output_dir}/${LATEX_TARGET}.ps + SOURCES ${all_latex_sources} + ) + if(NOT LATEX_EXCLUDE_FROM_DEFAULTS) + add_dependencies(ps ${ps_target}) + endif() + if(PS2PDF_CONVERTER) + # Since both the pdf and safepdf targets have the same output, we + # cannot properly do the dependencies for both. When selecting safepdf, + # simply force a recompile every time. + add_custom_target(${safepdf_target} + ${CMAKE_COMMAND} -E chdir ${output_dir} + ${PS2PDF_CONVERTER} ${PS2PDF_CONVERTER_ARGS} ${LATEX_TARGET}.ps ${LATEX_TARGET}.pdf + DEPENDS ${ps_target} + ) + if(NOT LATEX_EXCLUDE_FROM_DEFAULTS) + add_dependencies(safepdf ${safepdf_target}) + endif() + endif() + endif() + endif() + + if(LATEX_FORCE_HTML OR no_force) + if (LATEX_FORCE_HTML) + set(default_build html) + endif() + + if(HTLATEX_COMPILER AND LATEX_MAIN_INPUT_SUBDIR) + message(STATUS + "Disabling HTML build for ${LATEX_TARGET_NAME}.tex because the main file is in subdirectory ${LATEX_MAIN_INPUT_SUBDIR}" + ) + # The code below to run HTML assumes that LATEX_TARGET.tex is in the + # current directory. I have tried to specify that LATEX_TARGET.tex is + # in a subdirectory. That makes the build targets correct, but the + # HTML build still fails (at least for htlatex) because files are not + # generated where expected. I am getting around the problem by simply + # disabling HTML in this case. If someone really cares, they can fix + # this, but make sure it runs on many platforms and build programs. + elseif(HTLATEX_COMPILER) + # htlatex places the output in a different location + set(HTML_OUTPUT "${output_dir}/${LATEX_TARGET}.html") + add_custom_command(OUTPUT ${HTML_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E chdir ${output_dir} + ${HTLATEX_COMPILER} ${LATEX_MAIN_INPUT} + "${HTLATEX_COMPILER_TEX4HT_FLAGS}" + "${HTLATEX_COMPILER_TEX4HT_POSTPROCESSOR_FLAGS}" + "${HTLATEX_COMPILER_T4HT_POSTPROCESSOR_FLAGS}" + ${HTLATEX_COMPILER_ARGS} + DEPENDS + ${output_dir}/${LATEX_TARGET}.tex + ${output_dir}/${LATEX_TARGET}.dvi + VERBATIM + ) + add_custom_target(${html_target} + DEPENDS ${HTML_OUTPUT} ${dvi_target} + SOURCES ${all_latex_sources} + ) + if(NOT LATEX_EXCLUDE_FROM_DEFAULTS) + add_dependencies(html ${html_target}) + endif() + endif() + endif() + + # Set default targets. + if("${default_build}" STREQUAL "pdf") + add_custom_target(${LATEX_TARGET_NAME} DEPENDS ${pdf_target}) + elseif("${default_build}" STREQUAL "dvi") + add_custom_target(${LATEX_TARGET_NAME} DEPENDS ${dvi_target}) + elseif("${default_build}" STREQUAL "ps") + add_custom_target(${LATEX_TARGET_NAME} DEPENDS ${ps_target}) + elseif("${default_build}" STREQUAL "safepdf") + add_custom_target(${LATEX_TARGET_NAME} DEPENDS ${safepdf_target}) + elseif("${default_build}" STREQUAL "html") + add_custom_target(${LATEX_TARGET_NAME} DEPENDS ${html_target}) + else() + message(SEND_ERROR "LATEX_DEFAULT_BUILD set to an invalid value. See the documentation for that variable.") + endif() + + if(NOT LATEX_EXCLUDE_FROM_ALL) + add_custom_target(_${LATEX_TARGET_NAME} ALL DEPENDS ${LATEX_TARGET_NAME}) + endif() + + set_directory_properties(. + ADDITIONAL_MAKE_CLEAN_FILES "${auxiliary_clean_files}" + ) + + add_custom_target(${auxclean_target} + COMMENT "Cleaning auxiliary LaTeX files." + COMMAND ${CMAKE_COMMAND} -E remove ${auxiliary_clean_files} + ) + add_dependencies(auxclean ${auxclean_target}) +endfunction(add_latex_targets_internal) + +function(add_latex_targets latex_main_input) + latex_get_output_path(output_dir) + parse_add_latex_arguments(ADD_LATEX_TARGETS ${latex_main_input} ${ARGN}) + + add_latex_targets_internal() +endfunction(add_latex_targets) + +function(add_latex_document latex_main_input) + latex_get_output_path(output_dir) + if(output_dir) + parse_add_latex_arguments(add_latex_document ${latex_main_input} ${ARGN}) + + latex_copy_input_file(${LATEX_MAIN_INPUT}) + + foreach (bib_file ${LATEX_BIBFILES}) + latex_copy_input_file(${bib_file}) + endforeach (bib_file) + + if (LATEX_USE_BIBLATEX AND EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/biblatex.cfg) + latex_copy_input_file(biblatex.cfg) + set(LATEX_USE_BIBLATEX_CONFIG TRUE) + endif() + + foreach (input ${LATEX_INPUTS}) + latex_copy_input_file(${input}) + endforeach(input) + + latex_copy_globbed_files(${CMAKE_CURRENT_SOURCE_DIR}/*.cls ${output_dir}) + latex_copy_globbed_files(${CMAKE_CURRENT_SOURCE_DIR}/*.bst ${output_dir}) + latex_copy_globbed_files(${CMAKE_CURRENT_SOURCE_DIR}/*.clo ${output_dir}) + latex_copy_globbed_files(${CMAKE_CURRENT_SOURCE_DIR}/*.sty ${output_dir}) + latex_copy_globbed_files(${CMAKE_CURRENT_SOURCE_DIR}/*.ist ${output_dir}) + latex_copy_globbed_files(${CMAKE_CURRENT_SOURCE_DIR}/*.fd ${output_dir}) + + add_latex_targets_internal() + endif() +endfunction(add_latex_document) + +############################################################################# +# Actually do stuff +############################################################################# + +if(LATEX_BUILD_COMMAND) + set(command_handled) + + if("${LATEX_BUILD_COMMAND}" STREQUAL execute_latex) + latex_execute_latex() + set(command_handled TRUE) + endif() + + if("${LATEX_BUILD_COMMAND}" STREQUAL makeglossaries) + latex_makeglossaries() + set(command_handled TRUE) + endif() + + if("${LATEX_BUILD_COMMAND}" STREQUAL makenomenclature) + latex_makenomenclature() + set(command_handled TRUE) + endif() + + if("${LATEX_BUILD_COMMAND}" STREQUAL correct_synctex) + latex_correct_synctex() + set(command_handled TRUE) + endif() + + if("${LATEX_BUILD_COMMAND}" STREQUAL check_important_warnings) + latex_check_important_warnings() + set(command_handled TRUE) + endif() + + if(NOT command_handled) + message(SEND_ERROR "Unknown command: ${LATEX_BUILD_COMMAND}") + endif() + +else() + # Must be part of the actual configure (included from CMakeLists.txt). + latex_setup_variables() + latex_setup_targets() +endif() diff --git a/img/alsk/exptt.pdf b/img/alsk/exptt.pdf new file mode 100644 index 0000000..e185638 Binary files /dev/null and b/img/alsk/exptt.pdf differ diff --git a/img/alsk/lineartt.pdf b/img/alsk/lineartt.pdf new file mode 100644 index 0000000..e9412b9 Binary files /dev/null and b/img/alsk/lineartt.pdf differ diff --git a/img/alsk/rt_graspels_dj38_24_20_20_par.pdf b/img/alsk/rt_graspels_dj38_24_20_20_par.pdf new file mode 100644 index 0000000..98848e1 Binary files /dev/null and b/img/alsk/rt_graspels_dj38_24_20_20_par.pdf differ diff --git a/img/alsk/rt_graspels_dj38_24_20_20_seq.pdf b/img/alsk/rt_graspels_dj38_24_20_20_seq.pdf new file mode 100644 index 0000000..ed253a4 Binary files /dev/null and b/img/alsk/rt_graspels_dj38_24_20_20_seq.pdf differ diff --git a/img/alsk/rt_graspels_qa194_12_20_20_speedup.pdf b/img/alsk/rt_graspels_qa194_12_20_20_speedup.pdf new file mode 100644 index 0000000..d97f8bd Binary files /dev/null and b/img/alsk/rt_graspels_qa194_12_20_20_speedup.pdf differ diff --git a/img/alsk/rt_graspels_qa194_16_20_20_speedup.pdf b/img/alsk/rt_graspels_qa194_16_20_20_speedup.pdf new file mode 100644 index 0000000..98d98bd Binary files /dev/null and b/img/alsk/rt_graspels_qa194_16_20_20_speedup.pdf differ diff --git a/img/alsk/rt_graspels_qa194_20_20_20_speedup.pdf b/img/alsk/rt_graspels_qa194_20_20_20_speedup.pdf new file mode 100644 index 0000000..deccc8f Binary files /dev/null and b/img/alsk/rt_graspels_qa194_20_20_20_speedup.pdf differ diff --git a/img/alsk/rt_graspels_qa194_24_20_20_par.pdf b/img/alsk/rt_graspels_qa194_24_20_20_par.pdf new file mode 100644 index 0000000..ecb1aaf Binary files /dev/null and b/img/alsk/rt_graspels_qa194_24_20_20_par.pdf differ diff --git a/img/alsk/rt_graspels_qa194_24_20_20_seq.pdf b/img/alsk/rt_graspels_qa194_24_20_20_seq.pdf new file mode 100644 index 0000000..31b45c6 Binary files /dev/null and b/img/alsk/rt_graspels_qa194_24_20_20_seq.pdf differ diff --git a/img/alsk/rt_graspels_qa194_4_20_20_speedup.pdf b/img/alsk/rt_graspels_qa194_4_20_20_speedup.pdf new file mode 100644 index 0000000..9b8d84d Binary files /dev/null and b/img/alsk/rt_graspels_qa194_4_20_20_speedup.pdf differ diff --git a/img/alsk/rt_graspels_qa194_4_v12_20_par.pdf b/img/alsk/rt_graspels_qa194_4_v12_20_par.pdf new file mode 100644 index 0000000..c803dbf Binary files /dev/null and b/img/alsk/rt_graspels_qa194_4_v12_20_par.pdf differ diff --git a/img/alsk/rt_graspels_qa194_4_v16_20_par.pdf b/img/alsk/rt_graspels_qa194_4_v16_20_par.pdf new file mode 100644 index 0000000..4e08e2c Binary files /dev/null and b/img/alsk/rt_graspels_qa194_4_v16_20_par.pdf differ diff --git a/img/alsk/rt_graspels_qa194_4_v1_20_par.pdf b/img/alsk/rt_graspels_qa194_4_v1_20_par.pdf new file mode 100644 index 0000000..85d9de8 Binary files /dev/null and b/img/alsk/rt_graspels_qa194_4_v1_20_par.pdf differ diff --git a/img/alsk/rt_graspels_qa194_4_v20_20_par.pdf b/img/alsk/rt_graspels_qa194_4_v20_20_par.pdf new file mode 100644 index 0000000..3519f6d Binary files /dev/null and b/img/alsk/rt_graspels_qa194_4_v20_20_par.pdf differ diff --git a/img/alsk/rt_graspels_qa194_4_v2_20_par.pdf b/img/alsk/rt_graspels_qa194_4_v2_20_par.pdf new file mode 100644 index 0000000..67a8bd3 Binary files /dev/null and b/img/alsk/rt_graspels_qa194_4_v2_20_par.pdf differ diff --git a/img/alsk/rt_graspels_qa194_4_v30_20_par.pdf b/img/alsk/rt_graspels_qa194_4_v30_20_par.pdf new file mode 100644 index 0000000..0bca816 Binary files /dev/null and b/img/alsk/rt_graspels_qa194_4_v30_20_par.pdf differ diff --git a/img/alsk/rt_graspels_qa194_4_v40_20_par.pdf b/img/alsk/rt_graspels_qa194_4_v40_20_par.pdf new file mode 100644 index 0000000..798c485 Binary files /dev/null and b/img/alsk/rt_graspels_qa194_4_v40_20_par.pdf differ diff --git a/img/alsk/rt_graspels_qa194_4_v4_20_par.pdf b/img/alsk/rt_graspels_qa194_4_v4_20_par.pdf new file mode 100644 index 0000000..8a71b6e Binary files /dev/null and b/img/alsk/rt_graspels_qa194_4_v4_20_par.pdf differ diff --git a/img/alsk/rt_graspels_qa194_4_v50_20_par.pdf b/img/alsk/rt_graspels_qa194_4_v50_20_par.pdf new file mode 100644 index 0000000..3743c9e Binary files /dev/null and b/img/alsk/rt_graspels_qa194_4_v50_20_par.pdf differ diff --git a/img/alsk/rt_graspels_qa194_4_v8_20_par.pdf b/img/alsk/rt_graspels_qa194_4_v8_20_par.pdf new file mode 100644 index 0000000..0e69743 Binary files /dev/null and b/img/alsk/rt_graspels_qa194_4_v8_20_par.pdf differ diff --git a/img/alsk/rt_graspels_qa194_8_20_20_speedup.pdf b/img/alsk/rt_graspels_qa194_8_20_20_speedup.pdf new file mode 100644 index 0000000..95d6e1c Binary files /dev/null and b/img/alsk/rt_graspels_qa194_8_20_20_speedup.pdf differ diff --git a/img/alsk/rt_graspels_qa194_all_20_20_speedup_1.pdf b/img/alsk/rt_graspels_qa194_all_20_20_speedup_1.pdf new file mode 100644 index 0000000..75607e4 Binary files /dev/null and b/img/alsk/rt_graspels_qa194_all_20_20_speedup_1.pdf differ diff --git a/img/alsk/rt_graspels_qa194_all_20_20_speedup_10.pdf b/img/alsk/rt_graspels_qa194_all_20_20_speedup_10.pdf new file mode 100644 index 0000000..b57fdd8 Binary files /dev/null and b/img/alsk/rt_graspels_qa194_all_20_20_speedup_10.pdf differ diff --git a/img/alsk/rt_graspels_qa194_all_20_20_speedup_12.pdf b/img/alsk/rt_graspels_qa194_all_20_20_speedup_12.pdf new file mode 100644 index 0000000..82634b3 Binary files /dev/null and b/img/alsk/rt_graspels_qa194_all_20_20_speedup_12.pdf differ diff --git a/img/alsk/rt_graspels_qa194_all_20_20_speedup_14.pdf b/img/alsk/rt_graspels_qa194_all_20_20_speedup_14.pdf new file mode 100644 index 0000000..277980d Binary files /dev/null and b/img/alsk/rt_graspels_qa194_all_20_20_speedup_14.pdf differ diff --git a/img/alsk/rt_graspels_qa194_all_20_20_speedup_16.pdf b/img/alsk/rt_graspels_qa194_all_20_20_speedup_16.pdf new file mode 100644 index 0000000..b996fb7 Binary files /dev/null and b/img/alsk/rt_graspels_qa194_all_20_20_speedup_16.pdf differ diff --git a/img/alsk/rt_graspels_qa194_all_20_20_speedup_18.pdf b/img/alsk/rt_graspels_qa194_all_20_20_speedup_18.pdf new file mode 100644 index 0000000..e394c02 Binary files /dev/null and b/img/alsk/rt_graspels_qa194_all_20_20_speedup_18.pdf differ diff --git a/img/alsk/rt_graspels_qa194_all_20_20_speedup_2.pdf b/img/alsk/rt_graspels_qa194_all_20_20_speedup_2.pdf new file mode 100644 index 0000000..16f11ae Binary files /dev/null and b/img/alsk/rt_graspels_qa194_all_20_20_speedup_2.pdf differ diff --git a/img/alsk/rt_graspels_qa194_all_20_20_speedup_4.pdf b/img/alsk/rt_graspels_qa194_all_20_20_speedup_4.pdf new file mode 100644 index 0000000..3e39918 Binary files /dev/null and b/img/alsk/rt_graspels_qa194_all_20_20_speedup_4.pdf differ diff --git a/img/alsk/rt_graspels_qa194_all_20_20_speedup_6.pdf b/img/alsk/rt_graspels_qa194_all_20_20_speedup_6.pdf new file mode 100644 index 0000000..bde003c Binary files /dev/null and b/img/alsk/rt_graspels_qa194_all_20_20_speedup_6.pdf differ diff --git a/img/alsk/rt_graspels_qa194_all_20_20_speedup_8.pdf b/img/alsk/rt_graspels_qa194_all_20_20_speedup_8.pdf new file mode 100644 index 0000000..c44a445 Binary files /dev/null and b/img/alsk/rt_graspels_qa194_all_20_20_speedup_8.pdf differ diff --git a/img/alsk/rt_graspels_qa194_v12_20_20_par.pdf b/img/alsk/rt_graspels_qa194_v12_20_20_par.pdf new file mode 100644 index 0000000..051edc1 Binary files /dev/null and b/img/alsk/rt_graspels_qa194_v12_20_20_par.pdf differ diff --git a/img/alsk/rt_graspels_qa194_v16_20_20_par.pdf b/img/alsk/rt_graspels_qa194_v16_20_20_par.pdf new file mode 100644 index 0000000..ea3349a Binary files /dev/null and b/img/alsk/rt_graspels_qa194_v16_20_20_par.pdf differ diff --git a/img/alsk/rt_graspels_qa194_v20_20_20_par.pdf b/img/alsk/rt_graspels_qa194_v20_20_20_par.pdf new file mode 100644 index 0000000..0efc57c Binary files /dev/null and b/img/alsk/rt_graspels_qa194_v20_20_20_par.pdf differ diff --git a/img/alsk/rt_graspels_qa194_v4_20_20_par.pdf b/img/alsk/rt_graspels_qa194_v4_20_20_par.pdf new file mode 100644 index 0000000..7671b13 Binary files /dev/null and b/img/alsk/rt_graspels_qa194_v4_20_20_par.pdf differ diff --git a/img/alsk/rt_graspels_qa194_v8_20_20_par.pdf b/img/alsk/rt_graspels_qa194_v8_20_20_par.pdf new file mode 100644 index 0000000..4f9bcdb Binary files /dev/null and b/img/alsk/rt_graspels_qa194_v8_20_20_par.pdf differ diff --git a/img/alsk/rt_graspels_qa194_var_els_iter_max_par_legend.pdf b/img/alsk/rt_graspels_qa194_var_els_iter_max_par_legend.pdf new file mode 100644 index 0000000..a9305df Binary files /dev/null and b/img/alsk/rt_graspels_qa194_var_els_iter_max_par_legend.pdf differ diff --git a/img/alsk/rt_graspels_qa194_var_grasp_par_legend.pdf b/img/alsk/rt_graspels_qa194_var_grasp_par_legend.pdf new file mode 100644 index 0000000..a32551c Binary files /dev/null and b/img/alsk/rt_graspels_qa194_var_grasp_par_legend.pdf differ diff --git a/img/gnx/placeholder b/img/gnx/placeholder new file mode 100644 index 0000000..e69de29 diff --git a/img/mp/placeholder b/img/mp/placeholder new file mode 100644 index 0000000..b88f8cf --- /dev/null +++ b/img/mp/placeholder @@ -0,0 +1 @@ +-- only to force git to track parent directory -- diff --git a/img/par/raymarching.png b/img/par/raymarching.png new file mode 100644 index 0000000..80c51dc Binary files /dev/null and b/img/par/raymarching.png differ diff --git a/img/pfor/fixediv.pdf b/img/pfor/fixediv.pdf new file mode 100644 index 0000000..a35a5de Binary files /dev/null and b/img/pfor/fixediv.pdf differ diff --git a/img/pfor/fixedv.pdf b/img/pfor/fixedv.pdf new file mode 100644 index 0000000..e2e3f3c Binary files /dev/null and b/img/pfor/fixedv.pdf differ diff --git a/img/pfor/rt_cores.pdf b/img/pfor/rt_cores.pdf new file mode 100644 index 0000000..92d445c Binary files /dev/null and b/img/pfor/rt_cores.pdf differ diff --git a/img/pfor/rt_par_1.pdf b/img/pfor/rt_par_1.pdf new file mode 100644 index 0000000..7698ef8 Binary files /dev/null and b/img/pfor/rt_par_1.pdf differ diff --git a/img/pfor/rt_par_10.pdf b/img/pfor/rt_par_10.pdf new file mode 100644 index 0000000..74b1404 Binary files /dev/null and b/img/pfor/rt_par_10.pdf differ diff --git a/img/pfor/rt_par_12.pdf b/img/pfor/rt_par_12.pdf new file mode 100644 index 0000000..2b74ed2 Binary files /dev/null and b/img/pfor/rt_par_12.pdf differ diff --git a/img/pfor/rt_par_14.pdf b/img/pfor/rt_par_14.pdf new file mode 100644 index 0000000..10f4ff6 Binary files /dev/null and b/img/pfor/rt_par_14.pdf differ diff --git a/img/pfor/rt_par_16.pdf b/img/pfor/rt_par_16.pdf new file mode 100644 index 0000000..a0483b2 Binary files /dev/null and b/img/pfor/rt_par_16.pdf differ diff --git a/img/pfor/rt_par_18.pdf b/img/pfor/rt_par_18.pdf new file mode 100644 index 0000000..bc20175 Binary files /dev/null and b/img/pfor/rt_par_18.pdf differ diff --git a/img/pfor/rt_par_2.pdf b/img/pfor/rt_par_2.pdf new file mode 100644 index 0000000..ea72862 Binary files /dev/null and b/img/pfor/rt_par_2.pdf differ diff --git a/img/pfor/rt_par_4.pdf b/img/pfor/rt_par_4.pdf new file mode 100644 index 0000000..69fe85d Binary files /dev/null and b/img/pfor/rt_par_4.pdf differ diff --git a/img/pfor/rt_par_6.pdf b/img/pfor/rt_par_6.pdf new file mode 100644 index 0000000..75ea55b Binary files /dev/null and b/img/pfor/rt_par_6.pdf differ diff --git a/img/pfor/rt_par_8.pdf b/img/pfor/rt_par_8.pdf new file mode 100644 index 0000000..6a8b800 Binary files /dev/null and b/img/pfor/rt_par_8.pdf differ diff --git a/img/pfor/rt_seq.pdf b/img/pfor/rt_seq.pdf new file mode 100644 index 0000000..f6b0ae3 Binary files /dev/null and b/img/pfor/rt_seq.pdf differ diff --git a/img/pfor/rt_speedup.pdf b/img/pfor/rt_speedup.pdf new file mode 100644 index 0000000..815d546 Binary files /dev/null and b/img/pfor/rt_speedup.pdf differ diff --git a/merge b/merge new file mode 100755 index 0000000..de431e8 --- /dev/null +++ b/merge @@ -0,0 +1,92 @@ +#!/bin/bash + +[ ! -d .git ] && exit 1 + +opt=$1; shift + +chapters="par gnx mp pfor alsk" +subdirs="alg fig lst" +acronyms= +commands= + +for subdir in $(echo ${subdirs}); do + rm -rf "src/${subdir}/*" +done + +rm -f 'src/tikz/*' + +# gather chapter sources +chn=0 + +cp '../remerciements/src/thanks.tex' "src/${chn}_1_thanks.tex" + +for chapter in $(echo ${chapters}); do + rm -rf "src/${chapter}" + cp -rf "../${chapter}/src/${chapter}" 'src' + chn=$((chn+1)) + + if [ "${opt}" != 'contents' ]; then + cp "../${chapter}/src/${chapter}.tex" "src/${chn}_${chapter}.tex" + fi + + rm -rf "img/${chapter}" + cp -rf "../${chapter}/img/${chapter}" 'img' + + cp "../${chapter}/src/tikz/"* 'src/tikz/' + + for subdir in $(echo ${subdirs}); do + dir="../${chapter}/src/${subdir}/${chapter}" + if [ -d "${dir}" ]; then + cp -rf "${dir}" "src/${subdir}/" + fi + done + + acronymsfile="../${chapter}/src/acronyms.tex" + if [ -f "${acronymsfile}" ]; then + acronyms="${acronyms} ${acronymsfile}" + fi + + commandsfile="../${chapter}/src/commands.tex" + if [ -f "${commandsfile}" ]; then + commands="${commands} ${commandsfile}" + fi +done + +# merge all acronyms files +letter=A +previous= +while read line; do + current=$(echo "${line}"|cut -d'{' -f2|cut -d'}' -f1) + curletter=$(echo "${current}"|cut -c1) + if [ "${curletter}" != "${letter}" ]; then + echo + letter="${curletter}" + fi + if [ "${current}" = "${previous}" ]; then + echo>&2 "[acronyms] incoherent duplicate for {${current}}" + fi + previous="${current}" + echo "${line}" +done<<<$(sort -u ${acronyms}|grep -Ev '^$'|sed 's,\\,\\\\,g')>src/acronyms.tex + +# merge all commands files +declare -A arrcmds +for cmds in ${commands}; do + chapter=$(echo "${cmds}"|cut -d'/' -f2) + echo "%{{{ ${chapter} \"" + while read line; do + cmd=$(sed -re 's/[^{]*\{([^}]+)\}.*/\1/'<<<"${line}") + if [ -n "${cmd}" ]; then + if [ ${arrcmds[${cmd}]+x} ]; then + if [ ${arrcmds[${cmd}]} != ${line} ]; then + echo>&2 "[commands] incoherent duplicate for ${cmd}" + fi + echo -n "% " + else + arrcmds[${cmd}]="${line}" + fi + echo "${line}" + fi + done<<<$(sed 's,\\,\\\\,g' ${cmds}) + echo "%}}}" +done>src/commands.tex diff --git a/src/0_0_titlepage.tex b/src/0_0_titlepage.tex new file mode 100644 index 0000000..0612c9c --- /dev/null +++ b/src/0_0_titlepage.tex @@ -0,0 +1,71 @@ +\date{01/07/2021} +\def\university{Université Clermont Auvergne} +\def\unived{École doctorale des sciences pour l'ingénieur} + +\def\jury{% +Alexandre \textsc{Guitton} & Professeur des universités & Université Clermont Auvergne & Président \\ +Joël \textsc{Falcou} & Maître de conférences HDR & Université Paris-Saclay & Rapporteur \\ +Françoise \textsc{Baude} & Professeur des universités & Université Côte d'Azur & Rapportrice \\ +Éric \textsc{Innocenti} & Maître de conférences & Université de Corse & Examinateur \\ +Bruno \textsc{Bachelet} & Maître de conférences HDR & Université Clermont Auvergne & Directeur de thèse\\ +David \textsc{Hill} & Professeur des universités & Université Clermont Auvergne & Directeur de thèse\\ +} +\def\juryinvited{% +Claude \textsc{Mazel} & Maître de conférences & Université Clermont Auvergne & Invité \\ +Jian-Jin \textsc{Li} & Maître de conférences & Université Clermont Auvergne & Invitée \\ +} + +\makeatletter +\begin{titlepage} + \newgeometry{width=180mm} + \setlength\parskip{1.5ex} + \begin{center} + % Academic + \textsc{\large\university} + + {\large\unived} + + \vspace{5ex} + + % Author + {\large Thèse présentée par} + + {\Large\@author} + + \vspace{5ex} + + % Ph.D + en vue de l'obtention du grade de + + {\LARGE Docteur d'université} + + {\Large Spécialité : informatique} + + \vspace{8ex} + + % Title + \rule[2ex]{\textwidth}{1pt} + {\LARGE\@title} + \rule[0ex]{\textwidth}{1pt} + + \vspace{8ex} + + % Defense + {\large Soutenue publiquement le \@date{} devant le jury composé de :} + % {\large devant le jury composé de :} + + \vspace{5ex} + + \vfill + + % Jury + \bgroup + \def\arraystretch{1.3} + \begin{tabular}{l >{\it}l l l} + \jury + \juryinvited + \end{tabular} + \egroup + \end{center} +\end{titlepage} +\makeatother diff --git a/src/0_1_thanks.tex b/src/0_1_thanks.tex new file mode 100644 index 0000000..ff075ef --- /dev/null +++ b/src/0_1_thanks.tex @@ -0,0 +1,46 @@ +\begin{thesisthanks} +Je remercie avant tout mes directeurs de thèse, Bruno Bachelet et David Hill. +Bruno pour avoir proposé ce sujet de thèse (spécialement pour la partie \og métaprogrammation \fg) +et m'avoir choisi pour travailler dessus, mais surtout pour son aide, sa disponibilité, sa +patience et sa confiance indéfectible durant ces quelques années. +David pour avoir rendu cette thèse possible et pour son aide indispensable quant à certaines +difficultés... scientifiques et administratives. +Je remercie Claude Mazel, co-encadrant qui n'aura eu de cesse de me vitupérer pour d'importantes +vétilles, aussi bien pour ses relectures que pour les nombreuses discussions que nous avons eues, +évidemment toujours expressément au sujet de la thèse. + +Je remercie également Joël Falcou, Françoise Baude, Éric Innocenti, Alexandre Guitton ainsi que +Jian-Jin Li pour avoir fait partie de mon jury de thèse, et en particulier Joël et Françoise pour +leur travail de rapporteurs. + +\cutout r (-4.5em,3.3ex)\Shapepar{\hexagonshape} +Ce document a été écrit en utilisant \LaTeX{} et en profitant de nombreux paquets sans lesquels ce +travail de rédaction aurait été bien moins appréciable. +Je tiens donc à remercier toute personne ayant participé au développement de ces outils, et plus +généralement à l'expansion du logiciel libre. + +\indent Je remercie aussi l'ensemble des personnes travaillant au LIMOS et à l'ISIMA. Loïc, +notamment pour m'avoir épargné, de force quelques fois, certaines besognes en faveur de l'avancement +de la thèse ; Yves-Jean qui a toujours pris le temps de discuter de mathématiques, y compris +quelquefois utiles à mes travaux ; Fréd avec qui j'ai pu attendre Kaamelott ; l'équipe technique et +le personnel administratif qui m'ont facilité bien des démarches. +\hfill\break\indent +Merci aux quelques docteurs -- ou presque -- que j'ai cotoyé durant cette thèse pour l'avoir en +partie animée. +David et Kévin pour les multiples discussions autour de problèmes variés et les +\textit{occasionnelles} parties de billard ; Théo, futur artiste qui m'a évité bien des soliloques ; +et bien sûr Cyrille avec qui j'ai pu explorer différents domaines de l'informatique, des autres +sciences et de tant d'autres thèmes. + +\begin{CJK}{UTF8}{min} +ICAST2019を開催した熊本大学とそれにかかわった人たちに感謝します。 +特に岸田先生には滞在中お世話になり、初めての日本訪問を楽しむことができました。 +\end{CJK} + +Merci au théorème fondamental de l'ingénierie logicielle, à savoir que tout problème peut être résolu +en ajoutant un niveau d'indirection, d'être vrai, au moins dans le cadre des développements +effectués durant cette thèse. + +J'étends enfin ces remerciements à mes proches, nommément Gwénaëlle qui a dû accepter un emploi du +temps chaotique, et m'a malgré cela soutenu inlassablement. +\end{thesisthanks} diff --git a/src/0_2_abstract.tex b/src/0_2_abstract.tex new file mode 100644 index 0000000..118c822 --- /dev/null +++ b/src/0_2_abstract.tex @@ -0,0 +1,133 @@ +%{{{ résumé " +\begin{abstract} +%{{{ +L'écriture de programmes parallèles, par opposition aux programmes \og classiques \fg{} séquentiels +et n'utilisant donc qu'un processeur, est devenue une nécessité. +En effet, si jusqu'au début du millénaire la puissance de calcul des ordinateurs dépendait +principalement de la fréquence du processeur, elle est maintenant liée au nombre de cœurs de calcul +qui sont de plus en plus nombreux. +Pourtant, à cause des difficultés introduites par la parallélisation, la plupart des programmes sont +toujours écrits de manière \og classique \fg. +En particulier, il peut être compliqué de déterminer, étant donné une sous partie d'un programme, si +la parallélisation est possible, c'est-à-dire si elle n'introduira pas un changement de comportement +du programme. +Cependant, même en sachant précisément ce qui peut être parallélisé, le faire correctement est aussi +une tâche difficile. +Cette thèse présente deux approches pour simplifier l'écriture de programmes parallèles. + +Nous proposons une bibliothèque active -- par métaprogrammation template, elle agit durant la +compilation -- qui acquiert des informations à propos d'une portion de programme, correspondant à +une boucle, au moyen de patrons d'expression. +Celles-ci sont utilisées pour analyser les instructions et identifier lesquelles peuvent être +exécutées en parallèle. +Cette analyse repose sur deux niveaux de connaissance : l'ensemble des variables utilisées en +distinguant les accès en lecture de ceux en écriture, et, puisqu'il s'agit souvent de tableaux, des +fonctions d'indice. +Les variables et leur mode d'accès permettent de savoir quelles instructions sont interdépendantes +tandis que les fonctions d'indice nous servent à déterminer, pour un groupe d'instructions, s'il +est possible de procéder à une exécution parallèle des itérations de la boucle. +L'objectif de cette bibliothèque est de proposer un cadre, à la fois pour les développeurs afin +d'écrire des boucles qui seront automatiquement exécutées en parallèle si cela est possible, mais +aussi à un niveau plus élevé pour intégrer de nouvelles méthodes de parallélisation ou d'autres +règles à utiliser pour l'analyse. + +Nous proposons également une seconde bibliothèque, active elle aussi, orientée sur la +parallélisation assistée en utilisant la technique des squelettes algorithmiques comme interface +pour le développeur. +Celle-ci permet de représenter des algorithmes complets comme des assemblages de motifs d'exécution +: séquence de tâches ; exécution répétée d'une tâche en parallèle ; ... +En utilisant cette connaissance, nous pouvons mettre en place des choix dans la manière de répartir +les tâches exécutées en parallèle sur les différents processeurs. +Par ailleurs, nous avons choisi d'expliciter l'expression de la transmission des données entre les +tâches, contrairement à ce qui est habituellement fait. +Grâce à cela, la bibliothèque automatise notamment la transmission de paramètres qui ne doivent pas +être partagés par des tâches parallèles. +Cela nous permet en particulier de garantir la répétabilité des exécutions y compris lorsque, par +exemple, les tâches utilisent des nombres pseudo-aléatoires. +En tenant compte de la politique d'exécution choisie et des nombres de processeurs possibles, nous +réduisons la quantité nécessaire de ces paramètres ne devant pas être partagés. +Ainsi, cette seconde bibliothèque propose elle aussi un cadre de programmation à plusieurs niveaux. +Celle-ci est extensible au niveau de ses politiques d'exécution ou des motifs pour la construction +des squelettes algorithmiques. +On peut l'utiliser pour définir une variété de squelettes algorithmiques, lesquels serviront ensuite +à un développeur pour écrire des programmes dont la parallélisation sera facilitée. +%}}} + +\vfill +\noindent\textbf{Mots-clés} : + métaprogrammation template ; + parallélisation assistée ; + parallélisation automatique ; + bibliothèques actives ; + squelettes algorithmiques ; + répétabilité. +\end{abstract} +%}}} + +%{{{ abstract " +\begin{otherlanguage}{english} +\begin{abstract} +%{{{ +Hardware performance has been increasing through the addition of computing cores rather than through +increasing their frequency since the early 2000s. +This means that parallel programming is no longer optional should you need to make the best use of +the hardware at your disposal. +Yet many programs are still written sequentially: parallel programming introduces numerous +difficulties. +Amongst these, it is notably hard to determine whether a sequence of a program can be executed in +parallel, i.e. preserving its behaviour as well as its overall result. +Even knowing that it is possible to parallelise a piece of code, doing it correctly is another +problem. +In this thesis, we present two approaches to make writing parallel software easier. + +We present an active library (using C++ template metaprogramming to operate during the compilation +process) whose purpose is to analyse and parallelise loops. +To do so, it builds a representation of each processed loop using expression templates through an +embedded language. +This allows to know which variables are used and how they are used. +For the case of arrays, which are common within loops, it also acquires the index functions. +The analysis of this information enables the library to identify which instructions in the loop can +be run in parallel. +Interdependent instructions are detected by knowing the variables and their access mode for each +instruction. +Given a group of interdependent instructions and the known index functions, the library decides if +the instructions can be run in parallel or not. +We want this library to help developers writing loops that will be automatically parallelised +whenever possible and run sequentially as without the library otherwise. +Another focus is to provide this to serve as a framework to integrate new methods for parallelising +programs and extended analysis rules. + +We introduce another active library that aims to help developers by assisting them in writing +parallel software instead of fully automating it. +This library uses algorithmic skeletons to let the developer describe its algorithms with both its +sequential and parallel parts by assembling atomic execution patterns such as a series of tasks or a +parallel execution of a repeated task. +This description includes the data flow, that is how parameters and function returns are +transmitted. +Usually, this is automatically set by the algorithmic skeleton library, however it gives the +developer greater flexibility and it makes it possible, amongst other things, for our library to +automatically transmit special parameters that must not be shared between parallel tasks. +One feature that this allows is to ensure repeatability from one execution to another even for +stochastic algorithms. +Considering the distribution of tasks on the different cores, we even reduce the number of these +non-shared parameters. +Once again, this library provides a framework at several levels. +Low-level extensions consist of the implementation of new execution patterns to be used to build +skeletons. +Another low-level axis is the integration of new execution policies that decide how tasks are +distributed on the available computing cores. +High-level additions will be libraries using ours to offer ready-to-use algorithmic skeletons for +various fields. +%}}} + +\vfill +\noindent\textbf{Keywords}: + template metaprogramming; + assisted parallelisation; + automatic parallelisation; + active libraries; + algorithmic skeletons; + repeatability. +\end{abstract} +\end{otherlanguage} +%}}} diff --git a/src/0_3_preamble.tex b/src/0_3_preamble.tex new file mode 100644 index 0000000..d4d7831 --- /dev/null +++ b/src/0_3_preamble.tex @@ -0,0 +1,98 @@ +\chapterx*{Avant-propos} + +Ce document présente les travaux effectués durant ma thèse. +Les projets correspondants sont accessibles à l'adresse \url{https://phd.pereda.fr/dev}. +Parmi ces projets se trouvent les deux bibliothèques principales : +\begin{itemize} + \item \url{https://phd.pereda.fr/dev/pfor} qui est présentée dans \acref{ch:pfor} ; + \item \url{https://phd.pereda.fr/dev/alsk} qui est présentée dans \acref{ch:alsk}. +\end{itemize} + +De nombreux extraits de code source sont étudiés. +Ceux-ci sont la plupart du temps simplifiés pour se concentrer sur les points intéressants. +En général, le langage de programmation est indiqué et, en particulier pour le C et le C++, le +standard à partir duquel le code est valide. +Ces indications sont +\tikz[remember picture,anchor=base,inner sep=0pt]{\node(tikz preamble anchor 0){en dessous à droite};} +des extraits de code. + +\begin{cppcode} + // source code +\end{cppcode} +\mincpp{\tikz[overlay,remember picture]{ + \node(tikz preamble anchor 1)[draw=green!30!black!70,very thick,dashed,rectangle, + rounded corners=1mm,minimum width=5.5em,minimum height=4ex, shift={(-1.2em,+.6ex)}]{}; + \draw[draw=green!30!black!70,ultra thick,->,>=stealth,opacity=.5] + ([shift={(3ex,-.5mm)}]tikz preamble anchor 0.south) to[bend right] ([xshift=-.5mm]tikz preamble anchor 1.west); +}14} + +Certaines figures sont dynamiques\footnote{Sauf si le document a été compilé spécifiquement pour +impression.}. +Dans ce cas se trouvera sous la figure concernée le symbole $\triangleright$ suivi d'indications +spécifiques. + +\begin{ocg}{Exemples d'OCG}{ocgexamples}{1} +\begin{multicols}{2} +\begin{ocg}{Exemple TikZ}{ocgexample0}{1} +\begin{figure}[H] + \def\prefix{tikz-preamble-ocg-example0} + \centering + \ocgcase{\prefix}{\prefix-off}{on}{% + \begin{tikzpicture}% + \node[common/drawfill=black,circle,minimum size=12mm,show ocg={\prefix-on}]{Test}; + \end{tikzpicture}% + }% + \ocgcase{\prefix}{\prefix-on}{off}{% + \begin{tikzpicture}% + \node[common/drawfill=green!35!black,circle,minimum size=12mm]{\checkmark}; + \end{tikzpicture}% + }% + \\ + {% + \small $\triangleright$ cliquer sur le disque \og Test \fg pour tester% + }% +\end{figure} +\end{ocg} +\columnbreak +\begin{ocg}{Exemple avec liens}{ocgexample1}{1} +\begin{figure}[H] + \def\prefix{tikz-preamble-ocg-example1} + \centering + \ocmdcase{\prefix-0}{% + \begin{tikzpicture}% + \node[common/drawfill=black,rectangle,rounded corners,minimum width=12mm,minimum height=12mm]{\num{0}}; + \end{tikzpicture}% + }% + \foreach \i in {1,...,5} {% + \pgfmathtruncatemacro{\ii}{\i*\i}% + \ocmdcase{\prefix-\i}{% + \begin{tikzpicture}% + \node[common/drawfill=black,rectangle,rounded corners,minimum width=12mm,minimum height=12mm]{\num{\ii}}; + \end{tikzpicture}% + }% + }% + \\ + {% + \small $\triangleright$ afficher le carré de :% + \ocgradio{\prefix}{\prefix-0}{0}{on}% + \foreach \i in {1,...,5} {% + \ocgradio{\prefix}{\prefix-\i}{\i}{off}% + }% + }% +\end{figure} +\end{ocg} +\end{multicols} +\end{ocg} + +Si le symbole $\triangleright$ apparaît mais que rien ne se passe lors du clic, le lecteur utilisé +n'est pas compatible. +Les lecteurs testés sont les suivants : +\begin{itemize} + \item Evince ; + \item Okular ; + \item Adobe Acrobat Reader DC. +\end{itemize} + +Quoi qu'il arrive, la figure affichée par défaut est celle dont il est question au sein du texte. +Les éléments dynamiques peuvent apporter une aide à la compréhension ou des éléments supplémentaires +mais ne sont en aucun cas indispensables à la lecture. diff --git a/src/0_3_preamble_print.tex b/src/0_3_preamble_print.tex new file mode 100644 index 0000000..a4db8de --- /dev/null +++ b/src/0_3_preamble_print.tex @@ -0,0 +1,33 @@ +\chapterx*{Avant-propos} + +Ce document présente les travaux effectués durant ma thèse. +Les projets correspondants sont accessibles à l'adresse \url{https://phd.pereda.fr/dev}. +Parmi ces projets se trouvent les deux bibliothèques principales : +\begin{itemize} + \item \url{https://phd.pereda.fr/dev/pfor} qui est présentée dans \acref{ch:pfor} ; + \item \url{https://phd.pereda.fr/dev/alsk} qui est présentée dans \acref{ch:alsk}. +\end{itemize} + +De nombreux extraits de code source sont étudiés. +Ceux-ci sont la plupart du temps simplifiés pour se concentrer sur les points intéressants. +En général, le langage de programmation est indiqué et, en particulier pour le C et le C++, le +standard à partir duquel le code est valide. +Ces indications sont +\tikz[remember picture,anchor=base,inner sep=0pt]{\node(tikz preamble anchor 0){en dessous à droite};} +des extraits de code. + +\begin{cppcode} + // source code +\end{cppcode} +\mincpp{\tikz[overlay,remember picture]{ + \node(tikz preamble anchor 1)[draw=green!30!black!70,very thick,dashed,rectangle, + rounded corners=1mm,minimum width=5.5em,minimum height=4ex, shift={(-1.2em,+.6ex)}]{}; + \draw[draw=green!30!black!70,ultra thick,->,>=stealth,opacity=.5] + ([shift={(3ex,-.5mm)}]tikz preamble anchor 0.south) to[bend right] ([xshift=-.5mm]tikz preamble anchor 1.west); +}14} + +Ce document a été compilé spécifiquement pour impression. +La version numérique comporte des figures dynamiques. +Celles-ci ont toujours un rendu par défaut qui est le seul dont il est question au sein du texte. +Les éléments dynamiques peuvent apporter une aide à la compréhension ou des éléments supplémentaires +mais ne sont en aucun cas indispensables à la lecture. diff --git a/src/0_intro.tex b/src/0_intro.tex new file mode 100644 index 0000000..3e5c779 --- /dev/null +++ b/src/0_intro.tex @@ -0,0 +1,170 @@ +\chapterx*{Introduction} + +%{{{ introduction " +L'informatique est employée dans de nombreux domaines, que ce soit comme sujet d'étude ou comme +outil d'application, notamment en sciences, y compris dans le cadre de démonstrations mathématiques. +L'évolution de celle-ci repose de manière croissante sur la puissance de calcul qu'apportent les +ordinateurs. +Or, jusqu'au début des années \num{2000}, la puissance d'un ordinateur dépendait sensiblement de la +fréquence du processeur qui effectue les calculs, laquelle a été confrontée à une limite physique au +delà de laquelle les fuites de courants étaient trop importantes, allant même jusqu'à faire fondre +les processeurs~\autocite{ref:namsungkim2003}. + +Depuis, pour outrepasser cette limite, les processeurs se voient pourvus de cœurs de calcul de plus +en plus nombreux. +En dehors de notions élémentaires, l'utilisation de l'informatique comme outil pour résoudre des +problèmes nécessite surtout des connaissances dans le domaine du problème. +Mais l'utilisation concurrente de plusieurs cœurs de calcul introduit des difficultés propres à +l'informatique et donc une expertise que n'ont pas la plupart de ses utilisateurs, et qu'ils ne +devraient pas être contraints à maîtriser. +Ces difficultés couvrent des sujets variés, incluant la connaissance du fonctionnement du matériel, +de son interaction avec les couches logicielles les plus basses jusqu'à des principes propres à la +programmation concurrente : gestion de la communication entre les éléments concurrents ; +synchronisation du travail effectué ; implémentation de patrons de conception dédiés ; maintien de +la répétabilité malgré l'utilisation de nombres pseudo-aléatoires ; ... +Du fait de ces difficultés, une majorité de programmes sont développés sans bénéficier réellement du +potentiel du matériel à disposition. +%}}} + +%{{{ solutions existantes " +Des solutions ont naturellement été étudiées, permettant d'abord l'utilisation des mêmes primitives +indépendamment de l'architecture exacte sur laquelle est déployé le programme, là où initialement +chaque système fournissait sa propre interface. +Des instructions spécifiques, mettant en place tout ce qui est nécessaire à la parallélisation, ont +été introduites, notamment au sein de certains compilateurs. +Cette technique va jusqu'à la création de langages dédiés exposant des syntaxes adaptées. +Tout cela a participé à assister les développeurs dans l'écriture de programmes s'exécutant en +parallèle sur plusieurs processeurs. + +Il s'est également agit de proposer l'automatisation de la génération de programmes parallèles à +partir de leur écriture séquentielle classique. +Différentes techniques existent, dont, à nouveau, l'exploitation des compilateurs pour analyser le +code source et produire, sans que soit nécessaire une intervention de la part du développeur, un +programme dont l'exécution est +parallèle~\autocite{ref:zima1988,ref:blume1995,ref:ahmad1997,ref:fonseca2016}. +Similairement, des langages de programmation se sont mis à intégrer des constructions orientées vers +la parallélisation et plus ou moins transparentes pour le +développeur~\autocite{ref:roscoe1988,ref:loveman1993,ref:chamberlain2007,ref:rieger2019}. + +Autant pour l'implémentation d'outils simplifiant la mise en œuvre de la parallélisation que pour +son automatisation, de nombreuses bibliothèques logicielles sont disponibles et permettent +d'apporter à l'existant -- langages et compilateurs -- de nouvelles +possibilités~\autocite{ref:kuchen2002a,ref:chan2004,ref:falcou2008,ref:videau2018,ref:ernstsson2018}. +Il est même possible, dans certains cas, qu'une bibliothèque logicielle (indépendante du +compilateur) agisse pratiquement aussi profondément que le peut une extension de compilateur (à +l'inverse, spécifique à celui-ci). +Ces bibliothèques, dites actives~\autocite{ref:veldhuizen1998a}, ouvrent la voie à de nouvelles +façons de proposer des outils aux développeurs. +%}}} + +%{{{ besoin " +Les solutions aux problèmes de la parallélisation basées sur des bibliothèques logicielles actives +sont relativement peu nombreuses par rapport aux autres approches. +Elles sont plus contraignantes à produire qu'une extension de compilateur. +Il est notamment difficile d'en écrire une qui ne cause effectivement pas un surcoût rédhibitoire en +temps d'exécution, en mémoire utilisée ou encore en temps de compilation, comparé à une +implémentation manuelle ou à ce qui se fait au niveau des compilateurs. +Selon le cadre dans lequel la bibliothèque active est conçue, il peut également être +particulièrement difficile de déboguer. + +Le C++ et ses templates, supportant une forme de métaprogrammation à la base de ce qui permet +l'élaboration de bibliothèques actives au sein de ce langage, est connu pour cela. +En partie parce que ces templates n'ont pas été pensé initialement pour cela (ils servent avant tout +à la dimension générique du langage), il est difficile de suivre ce qu'ils causent durant la +compilation. +Malgré cette limite, qui tend par ailleurs à s'amoindrir avec les évolutions du langage, cette forme +de métaprogrammation est très employée car elle permet, lorsqu'elle est bien utilisée, d'atteindre +des résultats très proches de ce que l'on obtiendrait en écrivant soi-même le code produit. +%}}} + +%{{{ proposition " +Avec cette thèse, nous voulons proposer principalement deux nouveaux outils. +Le premier est un outil de parallélisation automatique, avec une bibliothèque active détectant +elle-même ce qui peut validement être exécuté en parallèle et qui génère le code correspondant. +La détection doit reposer sur des mécanismes extensibles afin de permettre l'ajout de nouvelles +règles, déterminant, à partir des accès aux variables, ce qu'il est possible de faire de chaque +instruction. +Similairement, la génération de code doit avoir la capacité de fonctionner indépendamment de la +méthode de parallélisation sous-jacente. +Pour cela, il faut donc voir la manière de paralléliser comme une simple configuration qui peut être +indiquée à la bibliothèque plutôt que comme un élément qui lui est central. +À l'inverse, le fonctionnement de la bibliothèque ne doit pas affecter négativement et +significativement la performance du programme par rapport à ce qui peut être accompli similairement +\og à la main \fg. +Cela doit en particulier être vrai lorsque son utilisation aboutit à un programme non parallélisé. + +Le second outil relève, quant à lui, de la parallélisation assistée, laquelle laisse au moins à la +charge du développeur le soin de décider quelles sections du programme doivent être parallélisées +avec une deuxième bibliothèque, également active. +Pour ceci, nous proposons une nouvelle vision des squelettes algorithmiques avec davantage de +contrôle de la part du développeur. +Un but est de permettre une utilisation très flexible des squelettes algorithmiques, par exemple sur +l'aspect de la transmission des paramètres entre les différentes parties d'un squelette. +Un autre but est l'analyse des squelettes algorithmiques durant la compilation pour en déduire, par +exemple, des optimisations possibles de celui-ci ou des schémas efficaces d'exécution parallèle. +La parallélisation, particulièrement lorsqu'elle est combinée à l'utilisation de nombres +pseudo-aléatoires, rend plus difficile la préservation de la répétabilité d'un programme, +c'est-à-dire la capacité à exécuter plusieurs fois ce programme dans les mêmes conditions en +obtenant systématiquement le même résultat. +Sans répétabilité, la capacité de débogage est illusoire, et pour cette raison, nous voulons +proposer un moyen de garantir cette répétabilité grâce aux squelettes algorithmiques. +%}}} + +%{{{ structure " +Ce document présente les travaux effectués au titre de cette thèse. +Pour ce faire, il est scindé en \num{5} chapitres. +\Acref{ch:par} traite de la parallélisation en partant des notions élémentaires matérielles et +logicielles et en allant jusqu'aux concepts spécifiques de plus haut niveau qui permettent de +résoudre des problèmes introduits par cette discipline. +Ce chapitre détaille ensuite différentes couches supérieures qui simplifient l'application de la +parallélisation, principalement sur les deux aspects que sont la parallélisation automatique et la +parallélisation assistée. + +\Acref{ch:gnx} introduit un paradigme de programmation nommé \og généricité \fg, en particulier en +C++. +En effet, la généricité de ce langage fonctionnant durant la compilation grâce, entre autres, aux +templates, est à la base de la métaprogrammation template. +Y sont donc traitées la création et l'utilisation de templates, ainsi que leur spécialisation et les +contraintes de sélection associées à celles-ci. +Ce chapitre aborde aussi des problématiques spécifiques à cette généricité et les solutions qui y +répondent. + +\Acref{ch:mp} détaille ensuite la métaprogrammation, notamment celle du C++ avec la notion de +template, les travaux effectués pour cette thèse reposant tous sur cette possibilité du langage C++. +En utilisant les notions de généricité du chapitre précédent, il présente l'implémentation de +différents algorithmes dont le déroulement s'effectue durant la compilation du programme. +Il progresse sur ce sujet jusqu'aux patrons d'expression, une technique en métaprogrammation +template permettant la représentation et l'utilisation, durant la compilation, de portions de code +source. + +\Acref{ch:pfor} présente une bibliothèque active en C++ et utilisant la métaprogrammation template +pour la parallélisation automatique de boucles. +Cette proposition est expliquée en commençant par le détail des tests effectués par la bibliothèque +pour déterminer l'indépendance d'instructions et leur capacité à être exécutées en parallèle sans +créer de conflit d'accès aux données. +Ensuite, le chapitre traite de la détection des données nécessaires à l'application de ces tests, +qui repose sur des patrons d'expression, et qui permet d'acquérir une vision fine des instructions. +La génération du code éventuellement parallèle, incluant donc l'exécution des tests durant la +compilation, est subséquemment exposée. +Pour finir, l'efficacité de l'utilisation de cette bibliothèque est évaluée au moyen de mesures de +temps de compilation et d'exécution. +Cela est effectué pour différents cas et pour des codes utilisant la bibliothèque par rapport à des +codes équivalents ne l'utilisant pas. + +Le \hyperref[ch:alsk]{cinquième et dernier chapitre} présente une seconde bibliothèque active, +utilisant également la métaprogrammation template du C++, cette fois-ci orientée vers la +parallélisation assistée au moyen de squelettes algorithmiques. +Ce chapitre introduit un problème de \gls{RO} et des métaheuristiques utilisées pour le résoudre. +Ces métaheuristiques sont ensuite utilisées pour illustrer les concepts généraux de squelettes +algorithmiques ainsi que ceux propres à notre proposition. +Le chapitre se poursuit avec l'étude de la répartition des tâches à exécuter en parallèle en +fonction du squelette algorithmique et de sa structure. +Le problème de la répétabilité des exécutions parallèles en tenant compte, notamment, de +l'utilisation de nombres pseudo-aléatoires est ensuite traité. +Après les premières sections qui présentent chacune un aspect de la conception des squelettes +algorithmiques en utilisant cette bibliothèque, ce chapitre montre comment ceux-ci peuvent être +utilisés en pratique, notamment par le biais d'une syntaxe alternative définissant un langage +embarqué au sein du C++. +Cette bibliothèque est testée pour résoudre des instances de \gls{TSP} et est comparée en temps +d'exécution avec des implémentations équivalentes ne l'utilisant pas. +%}}} diff --git a/src/1_par.tex b/src/1_par.tex new file mode 100644 index 0000000..ac2d456 --- /dev/null +++ b/src/1_par.tex @@ -0,0 +1,8 @@ +\chapterx{par}{Parallélisation automatique et assistée} + +\inputsrc{0_intro} +\inputsrc{1_general} +\inputsrc{2_comp} +\inputsrc{3_auto} +\inputsrc{4_assist} +\inputsrc{5_conclusion} diff --git a/src/2_gnx.tex b/src/2_gnx.tex new file mode 100644 index 0000000..6aa99d7 --- /dev/null +++ b/src/2_gnx.tex @@ -0,0 +1,17 @@ +\chapterx{gnx}{Généricité en C++} + +\inputsrc{0_intro} +\inputsrc{1_cpp} +\inputsrc{2_ccpp} +\inputsrc{3_syntax} +\inputsrc{4_tad} +\inputsrc{5_spe} +\inputsrc{6_sfinae} +\inputsrc{7_concepts} +\inputsrc{8_inst} +\inputsrc{9_overload} +\inputsrc{a_auto} +\inputsrc{b_forward} +\inputsrc{c_pack} +\inputsrc{d_ifcx} +\inputsrc{e_conclusion} diff --git a/src/3_mp.tex b/src/3_mp.tex new file mode 100644 index 0000000..1c1b959 --- /dev/null +++ b/src/3_mp.tex @@ -0,0 +1,6 @@ +\chapterx{mp}{Métaprogrammation template} + +\inputsrc{0_intro} +\inputsrc{1_types} +\inputsrc{2_mpt} +\inputsrc{3_conclusion} diff --git a/src/4_pfor.tex b/src/4_pfor.tex new file mode 100644 index 0000000..1825b74 --- /dev/null +++ b/src/4_pfor.tex @@ -0,0 +1,9 @@ +\chapterx{pfor}{Analyse et parallélisation automatique de boucles} + +\inputsrc{0_intro} +\inputsrc{1_relwork} +\inputsrc{2_conditions} +\inputsrc{3_detection} +\inputsrc{4_generation} +\inputsrc{5_results} +\inputsrc{6_conclusion} diff --git a/src/5_alsk.tex b/src/5_alsk.tex new file mode 100644 index 0000000..f6cf76a --- /dev/null +++ b/src/5_alsk.tex @@ -0,0 +1,11 @@ +\chapterx{alsk}{Parallélisation et répétabilité par les squelettes algorithmiques} + +\inputsrc{0_intro} +\inputsrc{1_relwork} +\inputsrc{2_app} +\inputsrc{3_concept} +\inputsrc{4_exec} +\inputsrc{5_repeat} +\inputsrc{6_usage} +\inputsrc{7_results} +\inputsrc{8_conclusion} diff --git a/src/6_conclusion.tex b/src/6_conclusion.tex new file mode 100644 index 0000000..d18b51e --- /dev/null +++ b/src/6_conclusion.tex @@ -0,0 +1,219 @@ +\chapterx*{Conclusion} + +%{{{ introduction " +La programmation parallèle induit de nombreuses difficultés par rapport à la programmation +séquentielle classique. +En premier lieu, il faut identifier les portions de code source qui peuvent être exécutées en +parallèle sans invalider leur comportement, c'est-à-dire en conservant le même déroulement lors de +leur exécution jusqu'à l'obtention du même résultat. +Lorsque ces portions de code sont identifiées, la mise en place d'une stratégie de parallélisation +demande un nouvel effort. +De plus, une évolution du programme, même mineure, peut nécessiter une maintenance coûteuse +puisqu'il faut évaluer à nouveau s'il est possible de l'exécuter en parallèle et mettre à jour le +code en conséquence. +Un programme efficacement parallélisé, s'il utilise par exemple des nombres pseudo-aléatoires, peut +ne plus être répétable d'une exécution à l'autre. +D'autre part, en matière de vérification, il est précieux de pouvoir tester les résultats d'un +programme parallèle par rapport à son équivalent exécuté séquentiellement. +Cette thèse présente des outils conçus pour répondre à différents problèmes introduits par la +programmation parallèle, dont la répétabilité de codes stochastiques faisant usage de nombres +pseudo-aléatoires. + +Afin de pouvoir expliquer la théorie et le fonctionnement des propositions faites dans cette thèse, +celle-ci explique les rudiments de la programmation parallèle ainsi que les différentes abstractions +et automatisations qui existent. +Deux chapitres sont ensuite dédiés à la généricité, notamment en C++, et à la métaprogrammation, +notamment celle dite template du C++. +Ils détaillent en particulier les fonctionnalités permises par ces paradigmes et qui servent ensuite +dans l'implémentation des bibliothèques actives que sont les outils proposés. +%}}} + +%{{{ pfor " +La première bibliothèque, présentée dans \acref{ch:pfor}, propose une interface pour automatiser la +parallélisation d'instructions au sein d'une boucle. +Celle-ci emploie les patrons d'expressions pour acquérir une représentation des instructions de la +boucle sous la forme d'un \gls{ASA}. +Cet \gls{ASA} détaille jusqu'aux opérations sur les indices d'accès aux tableaux -- appelées +fonctions d'indice -- dont les opérandes sont alors connus dès la compilation. +Grâce à cela, la bibliothèque vérifie si tous les accès aux données permettent une exécution +parallèle, et ce selon les caractéristiques des fonctions d'indice : +\begin{enumerate} + \item si toutes sont affines, la bibliothèque répond, avec une spécificité et une sensibilité + parfaite, en calculant l'existence de solutions à un ensemble d'équations diophantiennes ; + \item sinon, si toutes sont injectives, elle répond, avec une sensibilité imparfaite, en utilisant + un test simplifié ; + \item sinon la bibliothèque suppose par défaut que les instructions ne peuvent être parallélisées. +\end{enumerate} +Cette séquence de tests garantit une spécificité parfaite et empêche donc la parallélisation d'un +code qui ne peut l'être sainement. + +Ces tests sont appliqués à des groupes d'instructions qui sont formés de sorte que deux instructions +quelconques venant de groupes différents sont indépendantes quant aux données qu'elles utilisent. +De cette manière, les instructions qui ne doivent être exécutées en parallèle peuvent être +maintenues séparées de celles pouvant être exécutées en parallèle si elles sont indépendantes. +Ainsi, la non-parallélisabilité de ces premières instructions ne gêne pas la parallélisabilité des +dernières, alors qu'une analyse sur l'ensemble complet aurait logiquement rapporté une réponse +négative quant à la possibilité d'exécuter les instructions en parallèle. + +À partir des fonctions d'indices seules, la bibliothèque détermine automatiquement si celles-ci sont +affines. +Quant aux autres propriétés telles que l'injectivité, elles peuvent être indiquées voire déduites. +C'est par exemple le cas de l'injectivité si la fonction d'indice est strictement croissante ou +strictement décroissante. + +Une fois que les ensembles d'instructions parallélisables et non parallélisables sont ainsi définis, +la bibliothèque reproduit le code représenté par l'\gls{ASA} en intégrant ce qui est nécessaire pour +que soient exécutées en parallèles les instructions concernées. +Pour cela, il est possible d'utiliser différents générateurs. +La bibliothèque propose une parallélisation avec OpenMP ou en utilisant des \en{threads} \gls{POSIX} +ou encore une génération des instructions en utilisant la technique du déroulement de boucle. + +La bibliothèque a été testée en comparant ses performances avec des programmes équivalents écrits +dans les meilleures règles de l'art, de façon \og artisanale \fg (et donc sans l'utiliser). +Les temps de compilation, bien qu'il y ait certainement encore des améliorations possibles sur cet +aspect, sont raisonnables et permettent une utilisation au sein de projets. +Les temps d'exécution montrent que l'abstraction apportée par la bibliothèque n'implique que des +surcoûts minimes. +%}}} + +%{{{ alsk " +La seconde proposition faite dans cette thèse est une autre bibliothèque active ayant pour objectif +la parallélisation assistée par les squelettes algorithmiques. +Contrairement à la première proposition, les portions du programme qui peuvent être exécutées en +parallèle sont intrinsèquement liées au squelette algorithmique que définit le développeur. + +Cette bibliothèque dispose de sa propre manière de concevoir des squelettes algorithmiques. +Celle-ci repose sur une séparation initiale de deux concepts : la structure et les liens. +La structure du squelette est une composition d'autres structures et d'os, les éléments structurels +atomiques introduits dans cette thèse. +Chaque os correspond à un motif d'exécution, par exemple l'exécution séquentielle de plusieurs +tâches ou l'exécution parallèle d'une même tâche répétée, suivie d'une autre pour sélectionner le +meilleur résultat produit. +Quant aux liens, il s'agit d'une description des transferts de données entre les différentes tâches +à exécuter. +Cela se définit en utilisant des paramètres spéciaux, lesquels indiquent à la bibliothèque ce par +quoi ils doivent être remplacés (un paramètre de la tâche appelante, la valeur de retour d'une +autre, ...). + +La structure du squelette permet de savoir quels éléments peuvent être exécutés en parallèle. +Il est donc par exemple possible de déterminer le nombre de niveaux de parallélisation dont on +dispose pour un squelette donné. +Pour cela, une bibliothèque annexe d'outils pour la métaprogrammation template est utilisée. +Celle-ci comporte des algorithmes pour parcourir des listes et des arbres de types, et un squelette +algorithmique peut être transformé en arbre (et un arbre en liste si nécessaire). + +En utilisant, notamment, l'information du nombre de niveaux parallélisables, la bibliothèque permet +l'optimisation de la répartition des tâches sur les différents \en{threads} selon plusieurs +politiques d'exécution : \en{thread pool} ; répartition équilibrée ; répartition équilibrée +n'utilisant que le premier niveau ; ... +Dans le cas du \en{thread pool}, l'équilibrage de la charge entre les différents \en{threads} est +automatique et dynamique, au coût d'une synchronisation à effectuer pour l'accès aux tâches à +exécuter. +La répartition équilibrée proposée suppose une durée similaire dans l'exécution des différentes +tâches et les distribue aux \en{threads} de manière à ce que chacun en ait, à une près, le même +nombre. +Cette hypothèse semble raisonnable en particulier pour des os répétant une même tâche plusieurs +fois, os fréquemment utilisés dans l'implémentation de métaheuristiques. + +Grâce aux connaissances apportées par le choix d'une politique d'exécution et au contrôle permis par +les liens, cette thèse propose une solution au problème de la perte de répétabilité lors de +l'exécution parallèle d'un programme utilisant des nombres pseudo-aléatoires. +Cette solution permet plus généralement le maintien de la répétabilité lors de l'utilisation de +données qui doivent n'être utilisées que par un unique \en{thread}. +Ces données doivent donc être associées aux tâches qui partagent le \en{thread} qui les exécute. +Une solution triviale consiste à fournir à chaque tâche ses propres données. +Lorsqu'il s'agit de nombres pseudo-aléatoires, cela signifie qu'il faut déterminer une séquence +indépendante pour chaque tâche, ce qui devient coûteux, entre autres en mémoire, lorsque le nombre +de tâches augmente et que le statut initial du générateur est conséquent (proche de +\SI{2.5}{\kibi\octet} par exemple pour un état initial de Mersenne Twister MT19937). +En tenant compte de la distribution des tâches sur les \en{threads}, et ce pour différentes +quantités de \en{threads}, on détermine quelles tâches sont toujours exécutées par un \en{thread} +commun (indépendamment du nombre de \en{threads}) et on leur associe une séquence de nombres +pseudo-aléatoires commune (état initial partagé). +Ceci permet de garantir la répétabilité non seulement d'une exécution à l'autre, mais également +lorsque le nombre de \en{threads} varie. +Les tâches ayant besoin de nombres pseudo-aléatoires peuvent alors en recevoir un automatiquement au +moyen d'un paramètre spécial que les liens permettent d'utiliser. + +Cette bibliothèque a été utilisée pour résoudre des instances de \gls{TSP} par la métaheuristique +\graspels{}. +Les temps d'exécution sont comparés à ceux obtenus avec une implémentation manuelle d'un \graspels{} +et correspondent à des exécutions parallèles et à des exécutions séquentielles (afin de valider que, +même dans ce cas, la bibliothèque n'induit pas de surcoût). +Dans tous les cas, les temps obtenus sont analogues. +%}}} + +\chapterx*{Limites et perspectives} + +%{{{ perspectives " +%{{{ pfor " +Nous avons bien conscience que notre première proposition, orientée sur l'analyse et la +parallélisation automatique de boucles, est incomplète dans la mesure où de multiples boucles +imbriquées ne sont pas détectées. +Pour le moment, l'utilisation de multiples boucles imbriquées signifie qu'une seule sera traitée +pour la parallélisation. +Ce fait n'est pas gênant si l'on considère les architectures matérielles multi-cœurs actuelles. +La bibliothèque ne propose pas d'outil pour mettre en place une parallélisation automatique sur +plusieurs niveaux car elle ne permet pas, à ce stade, l'utilisation de l'indice d'un niveau à un +niveau inférieur. +C'est une piste de recherche que nous envisageons d'explorer à l'avenir. + +De plus, il sera intéressant de tester différentes méthodes pour appliquer la parallélisation. +Notamment l'utilisation d'un \en{thread pool} afin de voir si l'introduction de sections critiques +induit un coût négligeable par rapport au gain supposé apporté par le fait d'éviter la recréation de +\en{threads} à chaque nouvelle boucle parallélisée. + +Au niveau de l'analyse et particulièrement des tests permettant de déterminer la parallélisabilité +des instructions, l'implémentation du modèle polyédral est également une piste. +Celle-ci peut notamment être utile au support de multiples boucles imbriquées, mais peut +éventuellement également servir à augmenter la sensibilité du test pour des boucles à un seul niveau +lorsque les fonctions d'indice ne sont ni affines ni injectives. + +Une autre piste d'amélioration consiste en l'application de transformations sur le code source +traité. +Actuellement, celui-ci est analysé pour sa parallélisabilité, et s'il ne passe pas le test, il est +exécuté séquentiellement. +Il est possible, en modifiant astucieusement les instructions, de conserver le comportement du +programme en éliminant des dépendances, augmentant donc potentiellement sa parallélisabilité. +%}}} + +%{{{ alsk " +Par rapport à la seconde proposition, axée sur la parallélisation assistée par l'utilisation de +squelettes algorithmiques, le travail effectué était principalement centré sur l'abstraction +apportée par la bibliothèque de squelettes algorithmiques, au détriment de l'optimisation des +différentes politiques d'exécution fournies. +Ces politiques d'exécution ne sont donc pas implémentées de manière optimale. +Une personne dont la parallélisation est le domaine d'expertise pourrait améliorer cela. +En outre, il serait très intéressant de tester la piste évoquée dans +\acref{subsubsec:alsk/exec/impl/static}, à savoir la préparation, dès la compilation, de la séquence +complète des tâches qu'exécutera chaque \en{thread} afin d'éviter un défaut actuel de la politique +d'exécution répartissant les tâches de manière équilibrée : les \en{threads} des niveaux parallèles, +à l'exception du tout premier, sont créés de multiples fois. + +Une autre idée est de permettre l'ajustement du poids des tâches à exécuter afin de ne plus supposer +un temps d'exécution homogène. +En utilisant ces poids, une distribution équilibrée des tâches sur les différents \en{threads} peut, +peut-être, être aussi efficace en termes d'équilibrage de charge que ce que peut accomplir +dynamiquement un \en{thread pool}. +De plus, une exécution, partielle ou sur des données réduites, du programme permettrait +éventuellement la génération automatique de ces poids. + +Les os proposés au sein de cette thèse répondent aux besoins levés par l'applicatif en \gls{RO} que +nous avons utilisé. +Néanmoins, des motifs tels que le \en{pipeline}, sont manquants pour une utilisation réellement +générale. +Certains de ces nouveaux os pourraient nécessiter l'ajout de primitives au sein des exécuteurs. + +Le système de liens apporte des avantages intéressants, mais également une syntaxe plus lourde pour +le développeur qui, en utilisant d'autres bibliothèques de squelettes algorithmiques, pourrait +préférer une valeur par défaut. +L'\gls{EDSL} introduit en partie cette possibilité de valeur par défaut et même de déduction de +liens, mais cela manque aux couches plus basses. + +Ce système de liens permet par ailleurs de connaître exactement quelles tâches nécessitent +l'utilisation, par exemple, de nombres pseudo-aléatoires. +En utilisant cette information, il est possible de réduire encore le nombre de \gls{PRNG} devant +être créés pour garantir la répétabilité du programme. +%}}} +%}}} + diff --git a/src/acronyms.tex b/src/acronyms.tex new file mode 100644 index 0000000..6ee048a --- /dev/null +++ b/src/acronyms.tex @@ -0,0 +1,80 @@ +\newacronym{ABI}{ABI}{\en{Application Binary Interface}} +\newacronym{API}{API}{\en{Application Programming Interface}} +\newacronym{ASA}{ASA}{arbre syntaxique abstrait} +\newacronym{AST}{AST}{\en{Abstract Syntax Tree}} +\newacronym{AVX}{AVX}{\en{Advanced Vector eXtensions}} + +\newacronym{BLP}{BLP}{\en{Bit-Level Parallelism}} + +\newacronym{C++ AMP}{C++ AMP}{C++ {\en{Accelerated Massive Parallelism}}} +\newacronym{CPP}{CPP}{\en{C PreProcessor}} +\newacronym{CPU}{CPU}{\en{Central Processing Unit}} +\newacronym{CTAD}{CTAD}{\en{Class Template Argument Deduction}} +\newacronym{CUDA}{CUDA}{\en{Compute Unified Device Architecture}} + +\newacronym{DSL}{DSL}{\en{Domain Specific Language}} +\newacronym{DSP}{DSP}{\en{Digital Signal Processor}} + +\newacronym{EBO}{EBO}{\en{Empty Base Optimization}} +\newacronym{EDSL}{EDSL}{\en{Embedded Domain Specific Language}} +\newacronym{ELS}{ELS}{\en{Evolutionary Local Search}} +\newacronym{ET}{ET}{\en{Expression Templates}} + +\newacronym{FIFO}{FIFO}{\en{First In, First Out}} +\newacronym{FPGA}{FPGA}{\en{Field-Programmable Gate Array}} + +\newacronym{GPGPU}{GPGPU}{\en{General-Purpose computing on \gls{GPU}}} +\newacronym{GPPL}{GPPL}{\en{General Purpose Programming Language}} +\newacronym{GPU}{GPU}{\en{Graphics Processing Unit}} +\newacronym{GRASP}{GRASP}{\en{Greedy Randomized Adaptive Search Procedure}} + +\newacronym{HPC}{HPC}{\en{High Performance Computing}} + +\newacronym{ID}{ID}{\en{Instruction Decode}} +\newacronym{IEEE}{IEEE}{\en{Institute of Electrical and Electronics Engineers}} +\newacronym{IF}{IF}{\en{Instruction Fetch}} +\newacronym{ILP}{ILP}{\en{Instruction-Level Parallelism}} +\newacronym{ILS}{ILS}{\en{Iterative Local Search}} +\newacronym{IPC}{IPC}{\en{Inter-Process Communication}} + +\newacronym{MA}{MA}{\en{Memory Access}} +\newacronym{MIMD}{MIMD}{\en{Multiple-Instruction stream -- Multiple-Data stream}} +\newacronym{MISD}{MISD}{\en{Multiple-Instruction stream -- Single-Data stream}} +\newacronym{MMX}{MMX}{\en{multimedia extensions}} +\newacronym{MPI}{MPI}{\en{Message Passing Interface}} +\newacronym{MPT}{MPT}{métaprogrammation template} + +\newacronym{NUMA}{NUMA}{\en{Non-Uniform Memory Access}} + +\newacronym{OpenCL}{OpenCL}{\en{Open Computing Language}} +\newacronym{OpenMP}{OpenMP}{\en{Open Multi-Processing}} + +\newacronym{PEPS}{PEPS}{Premier Entré, Premier Sorti} +\newacronym{PGCD}{PGCD}{plus grand commun diviseur} +\newacronym{POO}{POO}{Programmation Orientée Objet} +\newacronym{POSIX}{POSIX}{\en{Portable Operating System Interface}} +\newacronym{PRNG}{PRNG}{\en{Pseudorandom Number Generator}} + +\newacronym{RAII}{RAII}{\en{Resource Acquisition Is Initialisation}} +\newacronym{RAW}{RAW}{\en{Read After Write}} +\newacronym{RO}{RO}{Recherche Opérationnelle} +\newacronym{RRID}{RRID}{\en{Resource Release Is Destruction}} + +\newacronym{SFINAE}{SFINAE}{\en{Substitution Failure Is Not An Error}} +\newacronym{SIMD}{SIMD}{\en{Single-Instruction stream -- Multiple-Data stream}} +\newacronym{SISD}{SISD}{\en{Single-Instruction stream -- Single-Data stream}} +\newacronym{SMP}{SMP}{\en{Symmetric MultiProcessing}} +\newacronym{SSE}{SSE}{\en{Streaming \gls{SIMD} Extension}} + +\newacronym{TAD}{TAD}{\en{Template Argument Deduction}} +\newacronym{TBB}{TBB}{\en{Threading Building Blocks}} +\newacronym{TMP}{TMP}{\en{template metaprogramming}} +\newacronym{TSP}{TSP}{\en{Travelling Salesman Problem}} + +\newacronym{UAL}{UAL}{Unité Arithmétique et Logique} +\newacronym{UMA}{UMA}{\en{Uniform Memory Access}} +\newacronym{UVF}{UVF}{Unité de calcul en Virgule Flottante} + +\newacronym{WAR}{WAR}{\en{Write After Read}} +\newacronym{WAW}{WAW}{\en{Write After Write}} +\newacronym{WB}{WB}{\en{WriteBack}} diff --git a/src/alg/alsk/app/els.tex b/src/alg/alsk/app/els.tex new file mode 100644 index 0000000..a1d02ba --- /dev/null +++ b/src/alg/alsk/app/els.tex @@ -0,0 +1,20 @@ +\begin{algorithmic} + \Function{ELS}{$P, S, N, M$} + \State $S \gets \Call{rechercheLocaleInitiale}{P, S}$ + \State $S^* \gets S$ + + \For{$i = 1..N$} + \For{$j = 1..M$} + \State $S_j \gets \Call{mutation}{S}$ + \State $S_j \gets \Call{rechercheLocale}{P, S_j}$ + \EndFor + \State $S' \gets \Call{sélection1}{\{S_1, S_2, \dots, S_{M}\}}$ + \State $S^* \gets \Call{sélection2}{S^*, S'}$ + + \If{\Call{critèreAcceptation}{$S'$}} + \State $S \gets S'$ + \EndIf + \EndFor + \State \Return $S^*$ + \EndFunction +\end{algorithmic} diff --git a/src/alg/alsk/app/grasp.tex b/src/alg/alsk/app/grasp.tex new file mode 100644 index 0000000..6f7ce85 --- /dev/null +++ b/src/alg/alsk/app/grasp.tex @@ -0,0 +1,10 @@ +\begin{algorithmic} + \Function{GRASP}{$N, P$} + \For{$i = 1..N$} + \State $S_i \gets \Call{heuristiqueConstructive}{P}$ + \State $S_i \gets \Call{rechercheLocale}{P, S_i}$ + \EndFor + \State $S^* \gets \Call{sélection}{\{S_1, S_2, \dots, S_{N}\}}$ + \State \Return $S^*$ + \EndFunction +\end{algorithmic} diff --git a/src/alg/alsk/app/greedy.tex b/src/alg/alsk/app/greedy.tex new file mode 100644 index 0000000..20a798c --- /dev/null +++ b/src/alg/alsk/app/greedy.tex @@ -0,0 +1,12 @@ +\begin{algorithmic} + \Function{glouton}{$P$} + \State $S \gets \emptyset$ + \State Construire la liste $L$ des éléments insérables dans $S$ à partir de $P$ + \While{$L \neq \emptyset$} + \State Évaluer le coût d'insertion de chaque élément de $L$ + \State Retirer de $L$ l'élément de coût minimal + \State Insérer cet élément dans $S$ + \EndWhile + \State \Return $S$ + \EndFunction +\end{algorithmic} diff --git a/src/alg/alsk/app/ils.tex b/src/alg/alsk/app/ils.tex new file mode 100644 index 0000000..a647e1b --- /dev/null +++ b/src/alg/alsk/app/ils.tex @@ -0,0 +1,17 @@ +\begin{algorithmic} + \Function{ILS}{$P, S, N$} + \State $S \gets \Call{rechercheLocaleInitiale}{P, S}$ + \State $S^{*} \gets S$ + + \For{$i = 1..N$} + \State $S' \gets \Call{mutation}{S}$ + \State $S' \gets \Call{rechercheLocale}{P, S'}$ + \State $S^{*} \gets \Call{sélection}{S^{*}, S'}$ + + \If{\Call{critèreAcceptation}{$S'$}} + \State $S \gets S'$ + \EndIf + \EndFor + \State \Return $S^{*}$ + \EndFunction +\end{algorithmic} diff --git a/src/alg/par/assist/barrier.tex b/src/alg/par/assist/barrier.tex new file mode 100644 index 0000000..0e6bc86 --- /dev/null +++ b/src/alg/par/assist/barrier.tex @@ -0,0 +1,16 @@ +\begin{algorithmic}[1] + \Procedure{threadPrincipal}{} + \State $promesse\_type\;promesse$ + \State $future\_type\;future$ + \State $thread\_type\;thread(\textsc{travail}, promesse)$ + \State \Comment{travail précédant la barrière de synchronisation} + \State $\Call{obtenirResultat}{future}$ \Comment{la valeur retournée n'importe pas} + \State \Comment{travail réalisé après la barrière de synchronisation} + \EndProcedure + + \Procedure{travail}{$promesse$} + \State \Comment{travail précédant la barrière de synchronisation} + \State $promesse \gets \epsilon$ \Comment{la valeur donnée n'importe pas} + \State \Comment{travail réalisé après la barrière de synchronisation} + \EndProcedure +\end{algorithmic} diff --git a/src/alg/par/assist/futurepromise.tex b/src/alg/par/assist/futurepromise.tex new file mode 100644 index 0000000..b38d4f2 --- /dev/null +++ b/src/alg/par/assist/futurepromise.tex @@ -0,0 +1,16 @@ +\begin{algorithmic}[1] + \Procedure{threadPrincipal}{} + \State $promesse\_type\;promesse$ \Comment{\og promesse \fg{} qui sera donnée au second \en{thread}} + \State $future\_type\;future \gets \Call{obtenirFuture}{promesse}$ \Comment{construction de la + \og future \fg{} correspondante} + \State $thread\_type\;thread(\textsc{travail}, promesse)$ + \State \Comment{le \en{thread} principal peut travailler indépendament ici} + \State $resultat \gets \Call{obtenirResultat}{future}$ \Comment{synchronisation entre les deux + \en{threads}} + \EndProcedure + + \Procedure{travail}{$promesse$} + \State \Comment{production d'un résultat} + \State $promesse \gets resultat$ + \EndProcedure +\end{algorithmic} diff --git a/src/alg/par/assist/pthread.tex b/src/alg/par/assist/pthread.tex new file mode 100644 index 0000000..8f5e236 --- /dev/null +++ b/src/alg/par/assist/pthread.tex @@ -0,0 +1,15 @@ +\begin{algorithmic}[1] + \State $m \gets PTHREAD\_MUTEX\_INITIALIZER$ + + \Procedure{threadPrincipal}{} + \State $thread\_id \gets \Call{pthread\_create}{\textsc{travail}}$ + \State $\Call{travail}{}$ \Comment{cet appel est exécuté dans le \en{thread} principal} + \State $\Call{pthread\_join}{thread\_id}$ + \EndProcedure + + \Procedure{travail}{} + \State $\Call{pthread\_mutex\_lock}{m}$ + \State ... \Comment{modification d'une variable partagée} + \State $\Call{pthread\_mutex\_unlock}{m}$ + \EndProcedure +\end{algorithmic} diff --git a/src/alg/par/assist/stdthread.tex b/src/alg/par/assist/stdthread.tex new file mode 100644 index 0000000..efc36ce --- /dev/null +++ b/src/alg/par/assist/stdthread.tex @@ -0,0 +1,13 @@ +\begin{algorithmic}[1] + \State $mutex\_type\;m$ \Comment{le constructeur initialise correctement} + \Procedure{threadPrincipal}{} + \State $thread\_type\;thread(\textsc{travail})$ + \Comment{le constructeur appelle \texttt{pthread\_create}} + \State $\Call{travail}{}$ + \EndProcedure \Comment{le destructeur de $thread\_type$ appelle \texttt{ptread\_join}} + + \Procedure{travail}{} + \State $mutex\_locker\_type\;lock(m)$ \Comment{le constructeur prend le \en{mutex}...} + \State \Comment{modification d'une variable partagée} + \EndProcedure \Comment{... et le destructeur le libère} +\end{algorithmic} diff --git a/src/alg/par/auto/ast.tex b/src/alg/par/auto/ast.tex new file mode 100644 index 0000000..8a342fc --- /dev/null +++ b/src/alg/par/auto/ast.tex @@ -0,0 +1,9 @@ +\begin{algorithmic}[1] + \State $i = 10$ + \State $r = 0$ + \While{$i \neq 0$} + \State $r = r+i$ + \State $i = i-1$ + \EndWhile + \State \Return $r$ +\end{algorithmic} diff --git a/src/alg/par/auto/depcontrol.tex b/src/alg/par/auto/depcontrol.tex new file mode 100644 index 0000000..eb4971f --- /dev/null +++ b/src/alg/par/auto/depcontrol.tex @@ -0,0 +1,5 @@ +\begin{algorithmic}[1] + \If{$R_1 = A$} + \State $R_2 \gets B$ \Comment{L'exécution de cette instruction dépend de l'instruction $R_1 = A$} + \EndIf +\end{algorithmic} diff --git a/src/alg/par/auto/depraw.tex b/src/alg/par/auto/depraw.tex new file mode 100644 index 0000000..bbca109 --- /dev/null +++ b/src/alg/par/auto/depraw.tex @@ -0,0 +1,4 @@ +\begin{algorithmic}[1] + \State $R_1 \gets A$ \Comment{Une ressource utilisée en écriture...} + \State $R_2 \gets R_1$ \Comment{... puis en lecture} +\end{algorithmic} diff --git a/src/alg/par/auto/depwar.tex b/src/alg/par/auto/depwar.tex new file mode 100644 index 0000000..0882d83 --- /dev/null +++ b/src/alg/par/auto/depwar.tex @@ -0,0 +1,4 @@ +\begin{algorithmic}[1] + \State $A \gets R_1$ \Comment{Une ressource est utilisée en lecture...} + \State $R_1 \gets B$ \Comment{... puis en écriture} +\end{algorithmic} diff --git a/src/alg/par/auto/depwar_solved.tex b/src/alg/par/auto/depwar_solved.tex new file mode 100644 index 0000000..f3e368c --- /dev/null +++ b/src/alg/par/auto/depwar_solved.tex @@ -0,0 +1,5 @@ +\begin{algorithmic}[1] + \State $V \gets R_1$ \Comment{Introduction d'une nouvelle variable} + \State $A \gets V$ \Comment{Cette instruction ne dépend plus de la suivante} + \State $R_1 \gets B$ +\end{algorithmic} diff --git a/src/alg/par/auto/depwaw.tex b/src/alg/par/auto/depwaw.tex new file mode 100644 index 0000000..bb7bf37 --- /dev/null +++ b/src/alg/par/auto/depwaw.tex @@ -0,0 +1,4 @@ +\begin{algorithmic}[1] + \State $R_1 \gets A$ \Comment{Une ressource utilisée en écriture...} + \State $R_1 \gets B$ \Comment{... puis à nouveau en écriture} +\end{algorithmic} diff --git a/src/alg/par/comp/barrier.tex b/src/alg/par/comp/barrier.tex new file mode 100644 index 0000000..8334eaf --- /dev/null +++ b/src/alg/par/comp/barrier.tex @@ -0,0 +1,17 @@ +\begin{algorithmic}[1] + \Procedure{barrière}{} \Comment{procédure exécutée par les $N_t$ \en{threads}} + \State $\Call{P}{m}$ \Comment{sémaphore utilisé comme \en{mutex} pour protéger $N_b$} + \State $N_b \gets N_b + 1$ \Comment{$N_b$ compte le nombre de \en{thread} arrivés} + \If{$N_b = N_t$} \Comment{Le dernier \en{thread} exécutera la première branche} + \State $N_b \gets N_b - 1$ + \Repeat + \State $\Call{V}{b}$ \Comment{libération des autres \en{threads}} + \State $N_b \gets N_b - 1$ + \Until{$N_b = 0$} + \State $\Call{V}{m}$ + \Else \Comment{pour les $N_t-1$ premiers \en{threads}} + \State $\Call{V}{m}$ + \State $\Call{P}{b}$ \Comment{bloqué jusqu'à l'arrivée du dernier \en{thread}} + \EndIf + \EndProcedure +\end{algorithmic} diff --git a/src/alg/par/comp/barrier2.tex b/src/alg/par/comp/barrier2.tex new file mode 100644 index 0000000..5530e66 --- /dev/null +++ b/src/alg/par/comp/barrier2.tex @@ -0,0 +1,13 @@ +\begin{algorithmic}[1] + \Procedure{thread1}{} \Comment{procédure exécutée par l'un des deux \en{threads}} + \State ... + \State $\Call{V}{a}$ + \State $\Call{P}{b}$ + \EndProcedure + + \Procedure{thread2}{} \Comment{procédure exécutée par l'autre \en{thread}} + \State ... + \State $\Call{P}{a}$ + \State $\Call{V}{b}$ + \EndProcedure +\end{algorithmic} diff --git a/src/alg/par/comp/mutex.tex b/src/alg/par/comp/mutex.tex new file mode 100644 index 0000000..e6b64e1 --- /dev/null +++ b/src/alg/par/comp/mutex.tex @@ -0,0 +1,8 @@ +\begin{algorithmic}[1] + \State \Comment{Le sémaphore $m$ existe et a été initialisé à 1} + \Procedure{}{} \Comment{procédure exécutée par de multiples \en{threads}} + \State $\Call{P}{m}$ + \State ... \Comment{section critique} + \State $\Call{V}{m}$ + \EndProcedure +\end{algorithmic} diff --git a/src/alg/pfor/conditions/groupdependent.tex b/src/alg/pfor/conditions/groupdependent.tex new file mode 100644 index 0000000..4ec0eab --- /dev/null +++ b/src/alg/pfor/conditions/groupdependent.tex @@ -0,0 +1,19 @@ +\begin{algorithmic} + \State entrée : un ensemble d'instructions $P = \{I_1, ..., I_n\}$ + \State sortie : l'ensemble des sous-ensembles d'instructions dépendantes + \Function{GrouperInstructionsDépendantes}{$P$} + \State $G \gets \emptyset$ + \ForAll{$I_k \in P$} + \State $P_a \gets \{I_k\}$ + \State $P_c \gets P_a$ + \ForAll{$P_b \in G$} + \If{not \Call{TestBernstein}{$P_a$, $P_b$}} + \State $G \gets G \setminus \{P_b\}$ + \State $P_c \gets P_c \cup P_b$ + \EndIf + \EndFor + \State $G \gets G \cup \{P_c\}$ + \EndFor + \State \Return $G$ + \EndFunction +\end{algorithmic} diff --git a/src/alsk/0_intro.tex b/src/alsk/0_intro.tex new file mode 100644 index 0000000..8300bff --- /dev/null +++ b/src/alsk/0_intro.tex @@ -0,0 +1,30 @@ +\section{Introduction} + +La parallélisation automatique permet au développeur de ne pas se préoccuper du tout de la +problématique de parallélisation au sein de son programme. +En contrepartie, il perd une partie du contrôle dont il pourrait disposer sur sa parallélisation. +Ce chapitre traite d'une solution de parallélisation assistée utilisant le concept de squelettes +algorithmiques~\autocite{ref:cole1989}. + +L'objectif principal est de proposer une bibliothèque +active\footnote{\url{https://phd.pereda.fr/dev/alsk}} permettant à un développeur de décrire ses +algorithmes en spécifiant lui-même quelles parties peuvent être exécutées en parallèles, et de +quelle manière elles doivent l'être en assemblant un ensemble de motifs d'exécution fournis. + +Ce chapitre présente d'abord la recherche existante autour des squelettes algorithmiques puis +introduit un ensemble d'outils classiques de \gls{RO} ainsi qu'un problème de \gls{RO}. +Ceux-ci sont utilisés comme application aux travaux présentés dans ce chapitre car ils ont des +propriétés intéressantes pour mettre en évidence des problématiques liées à la parallélisation et +donc les solutions proposées, notamment pour le problème de la répétabilité. +Ce chapitre se poursuit avec la présentation de notre conception des squelettes algorithmiques, en +détaillant en particulier leur structure et la possibilité de décrire les transmissions de données +entre les différentes parties de l'algorithmique représenté. +Ensuite, le chapitre explique comment se fait la répartition des tâches de l'algorithme afin de le +rendre parallèle et expose différentes stratégies pour ce faire. +Après cela, nous abordons le problème de la répétabilité, notamment pour le cas de l'utilisation de +nombres pseudo-aléatoires, et des méthodes pour optimiser la procédure initiale en tenant compte de +la stratégie de répartition des tâches adoptée et du degré de parallélisation. +Ce chapitre introduit ensuite un \gls{EDSL} servant d'interface pour simplifier l'utilisation de la +bibliothèque. +Enfin, il se termine sur une étude des performances obtenues en utilisant la bibliothèque en la +confrontant à des codes écrits sans l'utiliser. diff --git a/src/alsk/1_relwork.tex b/src/alsk/1_relwork.tex new file mode 100644 index 0000000..6e12d76 --- /dev/null +++ b/src/alsk/1_relwork.tex @@ -0,0 +1,160 @@ +\section{Travaux connexes} + +Les squelettes algorithmiques, introduits par \autocite{ref:cole1989}, proposent une solution de +parallélisation assistée. +L'objectif des squelettes est de masquer l'implémentation parallèle d'un algorithme en fournissant +une interface permettant au développeur de choisir parmi des patrons de conception celui qui +correspond à son besoin. +Un avantage des squelettes par rapport à des interfaces plus simples, comme un ensemble de fonctions +correspondant directement aux différents patrons, réside dans leur capacité à être +composés~\autocite{ref:benoit2005}. + +\autocite{ref:cole1989} les définit comme des fonctions d'ordre supérieur, c'est-à-dire des +fonctions capables d'utiliser des fonctions comme paramètres ou retournant d'autres fonctions. +L'exemple de la fonction d'ordre supérieur \og map \fg{} y est donné ainsi : +\begin{align*} + map : (a \to b) \to ([a] \to [b]). +\end{align*} + +Il s'agit d'une fonction acceptant une autre fonction en paramètre et retournant une nouvelle +fonction. +Cette dernière agit sur un vecteur d'éléments du type de l'unique paramètre de la fonction unaire en +entrée ($a$) et retournant un vecteur d'éléments du même type que ce qui est retourné par la +fonction en entrée ($b$). +Pour produire le vecteur de type $[b]$, la fonction en entrée est appliquée sur chaque élément du +vecteur de type $[a]$. +Des fonctions d'ordre supérieur ont été présentées dans \acref{ch:mp}, +\cref{subsubsec:mp/mpt/et/curry}. + +De nombreux travaux ont proposé des modèles et implémentations de squelettes algorithmiques dans +différents langages de programmation depuis l'introduction de ce concept. +Ceux-ci proposent généralement des patrons classiques~\autocite{ref:campbell1996} : parmi ceux +orientés vers la parallélisation de données, on trouve \en{map}, \en{zip}, \en{reduce} ; et parmi +ceux travaillant sur la parallélisation de tâches, il existe \en{farm}, \en{pipeline}, \en{divide +and conquer}~\autocite{ref:kuchen2002a}. + +Cette section illustre certains patrons. +Si l'on considère, dans un premier temps, le patron \en{fork-join} qui consiste simplement en +l'exécution de deux tâches $T_0$ et $T_1$ en parallèle, on peut utiliser la représentation faite +dans \acref{fig:alsk/relwork/forkjoin}. +Dans celle-ci, les tâches exécutées sont représentées par des blocs et nommées $T_i$ où $i$ est un +entier en l'absence duquel toutes les tâches $T$ effectuent la même chose. +Les zones englobant plusieurs tâches indiquent une exécution parallèle de celles-ci. + +\begin{figure} + \inputfig{relwork/forkjoin} + {Patron d'exécution parallèle \en{fork-join}} +\end{figure} + +Le patron \en{map} a déjà été abordé dans l'introduction de ce chapitre et correspond à l'exécution +de $n$ instances d'une tâche $T$ sur $n$ données, comme illustré par \acref{fig:alsk/relwork/map}. +En remplaçant l'ensemble défini de données par un flux, un \en{map} devient un \en{farm}. + +\begin{figure}[!hb] + \inputfig{relwork/map} + {Patron d'exécution parallèle \en{map}} +\end{figure} + +À nouveau comparable à \en{map}, la fonction d'ordre supérieure \en{zip} peut être définie ainsi : +\begin{align*} + zip : ((a, b) \to c) \to ([a], [b] \to [c]). +\end{align*} + +Cela correspond donc à l'application d'une fonction d'arité \num{2} sur chaque élément de deux +vecteurs de données de même cardinalité ($\vert [a] \vert = \vert [b] \vert$) pour produire un +unique vecteur. +Il est possible de généraliser le principe à des arités quelconques. + +L'application d'une fonction de projection (\texttt{min}, \texttt{sum}, ...) sur un ensemble de +données pour le réduire à une seule est appelée réduction, et le patron associé \en{reduce} ou plus +rarement \en{fold}. +Il est très souvent appliqué après un \en{map}, ce qui correspond alors au patron +\en{map-reduce}.% ref? illustré par \acref{fig:alsk/relwork/map}. + +Le \en{pipeline} permet l'exécution parallèle de plusieurs tâches $T_0$, $T_1$, ... $T_n$ par +lesquelles les données en entrée passent successivement (\cref{fig:alsk/relwork/pipeline}). +Le principe de \og diviser pour régner \fg, en anglais \en{divide and conquer}, est de répartir le +travail initial entre les travailleurs par séparation successives de l'ensemble de données en +entrée. +Après cette étape, les résultats sont fusionnés successivement jusqu'à obtenir un résultat final. +Il est possible de diviser les données jusqu'à atteindre un niveau atomique (sans fixer le nombre de +travailleurs) ou de définir la profondeur maximale (limitant le nombre de travailleurs). + +\begin{figure} + \inputfig{relwork/pipeline} + {Patron d'exécution parallèle \en{pipeline}} +\end{figure} + +Les structures de contrôle classiques (branches conditionnelles, boucles, ...) peuvent également +être proposées comme squelette. +Il existe d'autres patrons, mais ceux qui ont été présentés ci-dessus et les combinaisons +concevables entre ceux-ci sont plus que suffisants pour le propos de cette thèse. + +La bibliothèque C++ \gls{TBB} propose des fonctions correspondant à divers patrons parallèles tels +que \cppinline{parallel_for}, \cppinline{parallel_reduce}, \cppinline{parallel_pipeline}, ... +Ces fonctions sont conçues de manière similaire à la bibliothèque standard du langage (génériques +par template, utilisation d'itérateurs). + +L'exécution est parallélisée et équilibrée dynamiquement, et \gls{TBB} détecte l'utilisation +imbriquée de plusieurs de ses fonctions. +Cette fonctionnalité permet de distinguer cette bibliothèque d'un simple ensemble de fonctions et la +rapproche des squelettes algorithmiques. + +De manière similaire, le langage de programmation Cilk++ implémente des mots-clés pour faciliter +l'exécution parallèle ainsi qu'une structure de contrôle utilisant la syntaxe de la boucle +\cppinline{for} du C++, \cppinline{cilk_for}. +Ainsi, bien que cela ne soit pas un outil dont la conception repose spécifiquement sur les +squelettes algorithmiques (à l'instar de \gls{TBB}), les éléments proposés s'en rapprochent. + +En revanche, \autocite{ref:rieger2019} propose un langage, Musket, fondamentalement basé sur le +concept de squelettes algorithmiques. +Celui-ci est un \gls{DSL}, dont la syntaxe est volontairement proche de celle du C++, l'objectif +étant de permettre la génération de code C++ parallèle. +Des types de données spécifiques, permettant la parallélisation, sont fournis (des types primitifs +jusqu'aux matrices). +Les motifs de parallélisation proposés sont \en{map}, \en{reduce}, \en{zip} et \en{shift partition}. + +Les auteurs de Musket ont détaillé les raisons pour lesquelles ils ont choisi de développer un +langage plutôt qu'une bibliothèque~\autocite{ref:wrede2020}. +Celles-ci entraînent un surcoût en temps d'exécution par rapport à une implémentation manuelle ou +une génération de code comme peut le faire le compilateur d'un langage. +D'autre part, les bibliothèques étant contraintes à respecter la syntaxe du langage hôte, il est +plus facile de proposer une meilleure lisibilité du code au sein d'un \gls{DSL}. +Enfin, cette dernière contrainte apporte un autre argument au choix d'un langage dédié : cela permet +davantage de flexibilité dans les transformations appliquées sur le code pour produire un programme +parallèle. + +De nombreuses bibliothèques de squelettes algorithmiques +ont été proposées~\autocite{ref:aldinucci2009,ref:ciechanowicz2009,ref:leyton2010,ref:legaux2013,ref:ernstsson2018,ref:philippe2019}. +\autocite{ref:striegnitz2000} a montré que le langage C++ permettait probablement l'implémentation +de bibliothèques de squelettes algorithmiques. +Plus tard, \autocite{ref:falcou2006a} l'a prouvé et a également montré que l'utilisation des +templates permettait d'obtenir un moindre surcoût en temps d'exécution. +La différence notable entre une bibliothèque \og classique \fg{} et ce que permettent les templates +en C++ est la possibilité de réellement agir durant la compilation, les rendant \og actives +\fg~\autocite{ref:veldhuizen1998a}. +Enfin, il est possible de proposer un \gls{DSL} au sein de certains langages existants, comme c'est +le cas du C++ : on parle alors d'un \gls{EDSL}~\autocite{ref:saidani2009}. +Cela permet de mitiger la scission entre langage et bibliothèque en termes de performances et de +flexibilité, au moins dans le cadre du C++. + +Les travaux effectués durant cette thèse sur les squelettes algorithmiques ont ainsi été orientés +vers le C++, et plus spécifiquement vers l'implémentation de bibliothèques faisant usage de la +métaprogrammation template de ce langage. +Les inconvénients des bibliothèques par rapport à la création d'un langage ou même par rapport à +l'implémentation d'une extension pour un compilateur sont amoindris par l'utilisation de la +métaprogrammation. +En revanche, une bibliothèque apporte des avantages que nous avons jugé importants. +Premièrement, elles s'inscrivent dans un cadre qui peut déjà être connu par le développeur, et dans +le cas du langage C++, qui est très vastement utilisé, c'est particulièrement le cas. +L'implémentation d'un nouveau langage nécessite également la mise en place des nombreuses +optimisations déjà présentes et à venir sur les compilateurs de langages existants, alors qu'une +bibliothèque profitera de celles-ci automatiquement. +Certes, un nouveau langage peut, plutôt que d'être compilé directement vers de l'assembleur, générer +du code dans un autre langage bénéficiant de ces optimisations~\autocite{ref:rieger2019}, cependant, +cela requiert une maintenance parallèle des deux langages. +Il existe également le cas des extensions pour un compilateur, mais celles-ci doivent alors être +implémentées pour chaque compilateur, ou bien imposer aux utilisateurs de n'utiliser qu'un +sous-ensemble des compilateurs disponibles. +Une bibliothèque dont l'implémentation ne repose que sur le standard du langage ne crée aucune de +ces contraintes. diff --git a/src/alsk/2_app.tex b/src/alsk/2_app.tex new file mode 100644 index 0000000..b307a89 --- /dev/null +++ b/src/alsk/2_app.tex @@ -0,0 +1,227 @@ +\section{Application} + +L'objectif de cette section est d'introduire les problèmes sur lesquels nous avons appliqué notre +solution. +Nous avons choisi de travailler sur la résolution de problèmes de \gls{RO}. +Ceux-ci offrent plusieurs niveaux de parallélisation qui peuvent être entrelacés avec des niveaux +non parallélisables, ce qui nous a intéressé pour valider notre représentation globale d'un +algorithme. +L'utilisation intensive de nombres pseudo-aléatoires dans ce domaine nous a également permis +d'éprouver notre solution en ce qui concerne l'obtention de résultats répétables. + +Le voyageur de commerce (\gls{TSP})~\autocite{ref:robinson1949} est un problème très classique de +\gls{RO}. +Une instance de ce problème consiste en un graphe $G = (V, A, c)$ où $V$ est un ensemble de sommets, +$A$ un ensemble d'arêtes et $c$ une fonction de coût qui associe une distance minimale à un arc de +$A$~\autocite{ref:dantzig1954}. +Une solution peut être exprimée comme un vecteur $s$ de sommets contenant une fois chaque sommet de +$V$, sa valeur $c_s$ étant alors donnée par \acref{eq:alsk/app/tspvalue}, où $(a, b)$ correspond à +l'arête entre le sommet $a$ et le sommet $b$. + +\begin{align} + c_s = \sum_{i=2}^{\vert V \vert}{c((s[i-1], s[i]))} + \label{eq:alsk/app/tspvalue} +\end{align} + +L'objectif est alors de trouver un cycle qui minimise $c_s$. +\Acref{fig:alsk/app/tspinstance} est un exemple d'instance de \gls{TSP} avec \num{10} sommets pour +lequel nous considérerons que les coûts sont les distances entre les deux sommets concernés +(précisément la norme euclidienne). +Deux solutions sont montrées dans \acref{fig:alsk/app/tspsolution}, celle de droite étant optimale. + +Les algorithmes peuvent être évalués selon un critère de complexité, temporelle ou spatiale. +Par extension, il est possible d'associer une complexité à un problème comme étant la meilleure de +celles des algorithmes possibles permettant de le résoudre. +Afin de catégoriser les problèmes en fonction de leur complexité, des classes de complexité ont été +définies. +Il existe par exemple la classe P dont les problèmes peuvent être résolus en temps +polynomial, et sont donc considérés comme \og faciles \fg. +En revanche, pour les problèmes de classe NP, ce n'est que la vérification d'une solution qui peut +être faite en temps polynomial. +Un problème NP-complet est un problème de classe NP qui est au moins aussi difficile que tous les +autres problèmes de la classe NP et le \gls{TSP} est NP-complet : s'il est possible de vérifier une +solution en temps polynomial, en trouver une est difficile. +Cette catégorie de problèmes peut être traitée en \gls{RO} par des heuristiques et des +métaheuristiques. + +\newcommand*{\pointstotspstart}[2]{% + \hypersetup{linkcolor=black}% + \ocgmakeprefixes{#1}% + \edef\prefixP{\ocgprefix-pointer}% + \actionsocg{\prefixP}{}{}{#2}% + \tikz[remember picture,baseline=-.5ex]{\coordinate(tsp start anchor);}% + \begin{ocg}{\prefixP}{\prefixP}{0}% + \foreach \tspstart in {0,...,9} {% + \ifthenelse{\tspstart=0}{\def\state{1}}{\def\state{0}}% + \ocgcase{\prefixI}{\prefixI-\tspstart}{\state}{\tikz[remember picture,overlay]{% + \path[->,>=stealth,thick,dashed] (tsp start anchor) edge[bend right] (tsp node \tspstart);% + }}% + }% + \end{ocg}% +} +\renewcommand*{\pointstotspstart}[2]{#2}%disabled + +Le principe d'une heuristique est de trouver rapidement une solution au détriment de sa qualité. +Un exemple d'heuristique est l'algorithme glouton (\cref{alg:alsk/app/greedy}). +Appliqué au \gls{TSP}, celui-ci accepte en entrée une représentation du problème $P$ contenant +notamment l'ensemble des sommets. +Une solution est alors construite progressivement en lui ajoutant le sommet jugé optimal à chaque +étape. +\Acref{fig:alsk/app/tspgreedysolution} présente la solution obtenue par l'application de cet +algorithme sur l'instance de la \acref{fig:alsk/app/tspinstance} en démarrant du +\pointstotspstart{app/tspgreedysolution}{point grisé}. +Il existe des variantes non déterministes, par exemple en considérant les $n > 1$ meilleurs ajouts +possibles à chaque étape et en ajoutant l'un d'eux au hasard. + +\begin{algorithm} + \inputalg{app/greedy} + {Algorithme glouton} +\end{algorithm} + +\begin{figure} + \inputfig{app/tspinstance} + {Instance de \glsxtrshort{TSP}} +\end{figure} + +\begin{figure} + \inputfig{app/tspsolution} + {Solutions de l'instance de \glsxtrshort{TSP} de \acref{fig:alsk/app/tspinstance}} +\end{figure} + +\begin{figure} + \ocgfigRP{Solution TSP (glouton)} + {app/tsptikzrp}{app/tspgreedysolution} + {Solution d'une instance de \glsxtrshort{TSP} par un algorithme glouton} + {\tspstart}{0/on,1/off,2/off,3/off,4/off,5/off,6/off,7/off,8/off,9/off} + {circle,minimum size=5pt,inner sep=0pt}{tsp node \tspstart} + {cliquer sur un sommet pour choisir le premier} +\end{figure} + +Une métaheuristique est une heuristique générique pouvant s'appliquer à différents problèmes sans +changements importants de l'algorithme. +Les exemples sont nombreux~\autocite{ref:toussaint2010} : recuit simulé, recherche tabou, recherche +à voisinage variable, ... + +Ceux qui vont nous intéresser particulièrement, étant utilisés comme exemples dans la suite de ce +chapitre, sont : +\begin{itemize} + \item le \gls{GRASP} ; + \item l'\gls{ILS} ; + \item l'\gls{ELS}. +\end{itemize} + +Le \gls{GRASP}~\autocite{ref:feo1989} (\cref{alg:alsk/app/grasp}) consiste en la création de +multiples solutions à différents endroits de l'espace des solutions (l'intérêt est amoindri si les +solutions sont trop proches) suivie de l'amélioration par modifications successives (des +déplacements \og locaux \fg dans l'espace des solutions). +La création est accomplie par une heuristique constructive, par exemple un algorithme glouton non +déterministe, et l'amélioration par une recherche locale. +Une recherche locale consiste en l'exploration partielle ou complète du voisinage d'une solution +dans l'espace des solutions. +Le voisinage d'une solution est l'ensemble des solutions que l'on peut obtenir en appliquant une +transformation sur celle-ci, aussi appelée mutation. +% Une illustration commune de ceci est représentée dans \acref{fig:alsk/app/localmin}. +% Dans celle-ci, la courbe est l'espace des solutions, et lorsque l'on cherche la solution dont la +% valeur est minimale, une recherche locale sert à \og descendre \fg afin de trouver un minimum local, +% voire un minimum global. + +% \begin{figure} + % \inputfig{app/localmin} + % {Recherche locale au sein d'un espace de solutions quelconque} +% \end{figure} + +\begin{algorithm} + \inputalg{app/grasp} + {\Glsxtrshort{GRASP}} +\end{algorithm} + +Les transformations qu'il est possible de faire dépendent du problème : dans le cadre du \gls{TSP}, +il existe par exemple l'échange qui consiste en l'échange aléatoire de deux sommets dans la +solution (voir \acref{fig:alsk/app/tspswap}). +La descente est un exemple de recherche locale qui va remplacer la solution par son meilleur voisin +jusqu'à ce qu'il n'existe pas de voisin au moins aussi intéressant. + +\begin{figure} + \ocgfigRP{Illustration d'échange de sommets d'une solution de TSP} + {app/tsptikzrpii}{app/tspswap} + {Échange de deux sommets d'une solution d'une instance de \glsxtrshort{TSP}} + {\swapI}{0/off,1/off,2/off,3/off,4/on,5/off,6/off,7/off,8/off,9/off} + {circle,minimum size=5pt,inner sep=0pt}{tsp node \swapI,tsp node \swapI'} + {cliquer sur un sommet pour choisir un des sommets échangés} +\end{figure} + +Les différentes itérations de création (par heuristique constructive (HC)) et amélioration d'une +solution (par recherche locale (RL)) sont indépendantes et peuvent donc être exécutées en parallèle. +La sélection (S) de la solution doit en revanche être accomplie de manière séquentielle. + +\Acref{fig:alsk/app/grasp} représente un \gls{GRASP} en tenant compte de cela. +Les tâches à exécuter sont représentées par des cercles tandis que les triangles indiquent une +section (ouverte par le triangle qui sépare le flot d'exécution, et fermée par le triangle qui les +réunit) dont les flots d'exécutions peuvent être exécutés en parallèle. + +\begin{figure} + \inputfig{app/grasp} + {Schéma d'un \glsxtrshort{GRASP}} +\end{figure} + +L'\gls{ILS}~\autocite{ref:lourenco2003} (\cref{alg:alsk/app/ils}) améliore une solution $S$ plutôt +que d'en créer une. +Dans un premier temps, la solution $S$ donnée en paramètre est améliorée au moyen d'une recherche +locale (initiale afin de la différencier de la recherche locale utilisée ensuite). +L'amélioration de cette solution est ensuite effectuée en itérant $N$ fois l'amélioration par +recherche locale d'une mutation de la solution courante $S$. +À chaque itération, la solution courante est mise à jour pour prendre la nouvelle solution créée ou +non en fonction d'un critère d'acceptation. +Si cette solution est, par ailleurs, meilleure que la meilleure solution $S^{*}$ retenue +jusqu'alors, elle la remplace. + +\begin{algorithm} + \inputalg{app/ils} + {\Glsxtrshort{ILS}} +\end{algorithm} + +Chaque itération de cet algorithme dépendant du résultat de l'itération précédente, il n'est pas +possible de le paralléliser. +\Acref{fig:alsk/app/ils} représente cela par l'utilisation de carrés pour la boucle. +Les tâches de mutation (M), de recherche locale (RL), de sélection (S) et de vérification du critère +d'acceptation (A) sont ainsi exécutées séquentiellement à chaque itération, elles aussi exécutées en +séquence. + +\begin{figure} + \inputfig{app/ils} + {Schéma d'un \glsxtrshort{ILS}} +\end{figure} + +Enfin, l'\gls{ELS}~\autocite{ref:wolf2007} (\cref{alg:alsk/app/els}) fonctionne de manière similaire +à l'\gls{ILS}. +On retrouve la recherche locale initiale et une boucle principale permettant d'améliorer une +solution $S$ donnée en paramètre. +À chaque itération, $M$ solutions sont générées par une mutation suivie d'une recherche locale et la +meilleure, si elle vérifie le critère d'acceptation, est conservée pour l'itération suivante. + +Cet algorithme est plus intéressant que le précédent quant à la parallélisation. +Bien qu'il possède une boucle externe non parallélisable, la boucle interne peut l'être. +\Acref{fig:alsk/app/els} présente ainsi une structure externe très semblable à +\acref{fig:alsk/app/ils} dont la séquence \og mutation, recherche locale \fg{} est remplacée par une +tâche plus complexe introduisant une boucle parallélisable. + +\begin{figure} + \inputfig{app/els} + {Schéma d'un \glsxtrshort{ELS}} +\end{figure} + +Ces métaheuristiques peuvent également être combinées : \autocite{ref:prins2009a} a proposé +\graspils{} et \graspels{}, des métaheuristiques \gls{GRASP} dont la recherche locale est, +respectivement, \gls{ILS} et \gls{ELS}. +C'est en particulier le \graspels{} qui va servir d'exemple dans ce chapitre pour illustrer +l'utilisation des squelettes algorithmiques que nous proposons\footnote{Une bibliothèque +implémentant des algorithmes de \gls{RO}, notamment le \graspels{}, fait partie des travaux +effectués durant la thèse (\url{https://phd.pereda.fr/dev/rosa}).}. +Sa structure générale possède deux niveaux de boucles parallélisables entrecoupés par un niveau de +boucle devant être exécuté séquentiellement, ce qui en fait une application intéressante pour la +validation de notre modèle. + +\begin{algorithm} + \inputalg{app/els} + {\Glsxtrshort{ELS}} +\end{algorithm} diff --git a/src/alsk/3_concept.tex b/src/alsk/3_concept.tex new file mode 100644 index 0000000..4a751f3 --- /dev/null +++ b/src/alsk/3_concept.tex @@ -0,0 +1,7 @@ +\section{Conception} + +\inputsrc{concept/0_intro} +\inputsrc{concept/1_struct} +\inputsrc{concept/2_links} +\inputsrc{concept/3_inst} +\inputsrc{concept/4_parheight} diff --git a/src/alsk/4_exec.tex b/src/alsk/4_exec.tex new file mode 100644 index 0000000..649a0df --- /dev/null +++ b/src/alsk/4_exec.tex @@ -0,0 +1,8 @@ +\section{Politique d'exécution} +\label{sec:alsk/exec} + +\inputsrc{exec/0_intro} +\inputsrc{exec/1_bones} +\inputsrc{exec/2_ident} +\inputsrc{exec/3_executor} +\inputsrc{exec/4_impl} diff --git a/src/alsk/5_repeat.tex b/src/alsk/5_repeat.tex new file mode 100644 index 0000000..dd85202 --- /dev/null +++ b/src/alsk/5_repeat.tex @@ -0,0 +1,7 @@ +\section{Répétabilité} +\label{sec:alsk/repeatability} + +\inputsrc{repeat/0_intro} +\inputsrc{repeat/1_repeat} +\inputsrc{repeat/2_opti} +\inputsrc{repeat/3_results} diff --git a/src/alsk/6_usage.tex b/src/alsk/6_usage.tex new file mode 100644 index 0000000..2ae6cb6 --- /dev/null +++ b/src/alsk/6_usage.tex @@ -0,0 +1,4 @@ +\section{Utilisation} + +\inputsrc{usage/1_edsl} +\inputsrc{usage/2_impl} diff --git a/src/alsk/7_results.tex b/src/alsk/7_results.tex new file mode 100644 index 0000000..e89ae92 --- /dev/null +++ b/src/alsk/7_results.tex @@ -0,0 +1,298 @@ +\section{Performances} + +%{{{ Introduction " +Cette section présente des mesures de temps d'exécution de programmes utilisant la bibliothèque +présentée dans ce chapitre. +Toutes les mesures ont été effectuées sur une machine dotée d'un Intel Xeon E7-8890 v3, cadencé à +\SI{2.5}{\GHz} et ayant \num{72} cœurs physiques (\num{18} processeurs ayant chacun \num{4} cœurs +physiques). +Les programmes sont compilés en utilisant GCC (\texttt{g++}) 8.2.0 avec notamment le pack +d'optimisations \texttt{O2}. +Les valeurs données dans ce document sont obtenues par une moyenne sur \num{20} exécutions en +utilisant des états initiaux différents pour les \gls{PRNG} (les mêmes \num{20} états sont utilisés +à chaque fois). +Lorsque cela est pertinent (notamment, suffisamment visible), l'intervalle de confiance à +\SI{99}{\percent} est affiché. +Pour ce qui est des exécutions, nous avons réglé l'affinité du processus de sorte que pour chaque +processeur, au maximum un cœur soit utilisé. +Cela limite donc à \num{18} cœurs pour protéger d'un biais de mesure potentiel dû à l'utilisation +simultanée de plusieurs cœurs d'un même processeur. + +Un objectif important que devait atteindre cette bibliothèque est d'être compétitive, en termes de +temps d'exécution par rapport à une solution écrite directement par un développeur. +En effet, une abstraction est moins intéressante si elle implique un fort surcoût. +Pour cette raison, nous avons utilisé le langage C++ et la métaprogrammation template qui +permettent, lorsqu'ils sont correctement utilisés, de s'approcher d'une implémentation manuelle en +ce qui concerne le temps d'exécution. + +Pour évaluer cela, nous avons comparé les temps d'exécution de programmes écrits en utilisant la +bibliothèque (et en utilisant différentes politiques d'exécution) par rapport aux temps d'exécution +de programmes équivalents écrits sans utiliser cette bibliothèque. +Ces programmes implémentent un \graspels{} dans le but de résoudre des instances de \gls{TSP} de +\num{38} sommets et de \num{194} sommets, ils utilisent donc des nombres pseudo-aléatoires. +Afin de s'assurer de la pertinence des temps mesurés, nous avons donc fait en sorte de garantir la +répétabilité des programmes écrits sans utiliser la bibliothèque. +Pour les versions l'utilisant, la répétabilité est obtenue sans effort. + +Dans cette section, nous utiliserons trois variables $N$, $O$ et $I$ qui correspondront +respectivement au nombre d'itérations (parallélisables) du \gls{GRASP}, au nombre d'itérations (non +parallélisables) de la boucle extérieure de l'\gls{ELS} et au nombre d'itérations (parallélisables) +de la boucle intérieure de l'\gls{ELS}. + +Toutes les mesures sont effectuées pour quatre politiques d'exécutions différentes lorsque la +bibliothèque est utilisée : +\begin{itemize} + \item \og firstlevel \fg, qui ne parallélise que le premier niveau pouvant être exécuté en + parallèle ; + \item \og staticpool \fg, qui utilise un \en{thread pool} en associant à une tâche un + \en{thread} en fonction de son identifiant et en équilibrant les multiples niveaux parallèles + comme décrit dans \acref{sec:alsk/exec} ; + \item \og dynamicpool \fg, qui utilise un \en{thread pool} utilisé de manière classique ; + \item \og thread \fg, qui applique l'équilibrage des multiples niveaux parallèles de + \acref{sec:alsk/exec} en créant des \en{threads} dynamiquement. +\end{itemize} +%}}} + +%{{{ Sequential " +Bien que l'objectif principal de cette bibliothèque soit d'aider à l'écriture de programmes +parallèles, elle peut également être utilisée pour produire des programmes séquentiels en +choisissant une politique d'exécution appropriée. +Cela peut par exemple être utile à des fins de débugage, mais aussi pour effectuer des mesures de +performance dans certains domaines scientifiques où les comparaisons sont généralement effectuées +sur des programmes séquentiels. +Nous avons donc dans un premier temps mesuré les temps d'exécution de programmes séquentiels. + +\Acref{fig:alsk/results/rt_graspels_dj38_24_20_20_seq,fig:alsk/results/rt_graspels_qa194_24_20_20_seq} +montrent les temps d'exécution de programmes séquentiels avec $N = 24$, $O = 20$ et $I = 20$. +Les étiquettes préfixées de \og hw \fg{} correspondent aux programmes écrits sans utiliser la +bibliothèque tandis que celles préfixées de \og sk \fg{} indiquent ceux l'utilisant. +L'étiquette \og hw\_seq \fg{} est associée au programme écrit pour une exécution séquentielle. +Quant à \og hw\_par \fg{}, il s'agit d'un programme écrit pour une exécution parallèle, bien que +l'exécution sera effectivement séquentielle puisqu'un seul cœur est affecté. + +Quelle que soit la politique d'exécutions utilisée pour les versions employant les squelettes +algorithmiques, l'exécution sera également obligatoirement séquentielle en pratique puisqu'un seul +cœur est affecté comme pour \og hw\_par \fg{}. + +\Acref{fig:alsk/results/rt_graspels_dj38_24_20_20_seq} correspond aux temps d'exécution pour une +instance de \gls{TSP} ayant \num{38} sommets et il s'agit d'une instance ayant \num{194} sommets +pour \acref{fig:alsk/results/rt_graspels_qa194_24_20_20_seq}. +Pour une petite instance (\num{38} sommets), on observe de légères variations, cependant la courte +durée d'exécution ne permet pas de conclure clairement, sinon que l'utilisation de la bibliothèque +n'engendre pas de surcoût significatif même pour des tâches courtes. + +Les temps mesurés pour la seconde instance (\num{194} sommets) sont particulièrement proches pour +les programmes écrits sans et avec la bibliothèque à l'exception des politiques d'exécution \og +staticpool \fg{} et \og thread \fg. +Ainsi, à nouveau, on observe que la politique d'exécution séquentielle permet d'obtenir des +exécutions de durées quasiment identiques à ce que l'on peut atteindre sans utiliser la +bibliothèque. +À l'inverse, l'utilisation d'une autre politique d'exécution dans un contexte séquentiel (parce +qu'un seul cœur est disponible) peut en revanche être coûteux. +En effet, celles-ci peuvent avoir besoin de mettre en place des mécanismes, lesquels ne sont +généralement pas optimisés pour le cas d'une exécution séquentielle. + +Afin de garantir la répétabilité y compris lorsqu'un seul \en{thread} est possible, l'implémentation +des politiques d'exécution doit dans ce cas également produire un comportement cohérent, privant +celle-ci d'éventuelles optimisations. +Cela explique par ailleurs pourquoi la bibliothèque n'utilise pas automatiquement la politique +d'exécution séquentielle lorsque le nombre de cœurs alloués est de \num{1}. +Néanmoins, dans le cas où la répétabilité n'a pas besoin d'être assurée au point d'avoir le même +déroulement entre une exécution séquentielle et une exécution parallèle, alors il est avisé de la +part du développeur d'utiliser la politique d'exécution séquentielle lorsqu'il n'y a qu'un cœur. + +\begin{figure} + \centering + \includegraphics{img/alsk/rt_graspels_dj38_24_20_20_seq.pdf} + \caption{Temps d'exécution séquentielle d'un \graspels{} pour une instance de \glsxtrshort{TSP} de \num{38} sommets} + \label{fig:alsk/results/rt_graspels_dj38_24_20_20_seq} +\end{figure} + +\begin{figure} + \centering + \includegraphics{img/alsk/rt_graspels_qa194_24_20_20_seq.pdf} + \caption{Temps d'exécution séquentielle d'un \graspels{} pour une instance de \glsxtrshort{TSP} de \num{194} sommets} + \label{fig:alsk/results/rt_graspels_qa194_24_20_20_seq} +\end{figure} +%}}} + +%{{{ Parallel " +%{{{ var cores " +\Acref{fig:alsk/results/rt_graspels_dj38_24_20_20_par,fig:alsk/results/rt_graspels_qa194_24_20_20_par} +présentent les temps d'exécution de programmes parallèles avec $N = 24$, $O = 20$ et $I = 20$. +La première étiquette, \og hw\_par \fg, correspond au programme écrit sans utiliser la +bibliothèque pour une exécution parallèle. +Les étiquettes préfixées par \og sk \fg{} correspondent à des programmes écrits en utilisant la +bibliothèque et en utilisant différentes politiques d'exécution. +La seconde partie de chaque étiquette indique quelle politique d'exécution est utilisée. + +Pour une instance de \gls{TSP} ayant \num{38} sommets, on obtient les résultats présentés par +\acref{fig:alsk/results/rt_graspels_dj38_24_20_20_par}. +On observe globalement des performances similaires et surtout une décroissance stable du temps +d'exécution avec l'augmentation du nombre de cœurs. +La politique d'exécution \og staticpool \fg{} semble particulièrement efficace. +Cela se justifie par l'absence de sections critiques (contrairement à ce que l'on a avec \og +dynamicpool \fg) et la réutilisation des \en{threads} créés (contrairement à ce que fait \og +thread \fg). +La répartition des tâches est également un peu meilleure que celle de \og firstlevel \fg{} pour les +dernières itérations de la boucle principale du \gls{GRASP} lorsqu'il y a un reste à la division de +$N$ par le nombre de cœurs disponibles. + +\begin{figure} + \centering + \includegraphics{img/alsk/rt_graspels_dj38_24_20_20_par.pdf} + \caption{Temps d'exécution parallèle d'un \graspels{} pour une instance de \glsxtrshort{TSP} de \num{38} sommets} + \label{fig:alsk/results/rt_graspels_dj38_24_20_20_par} +\end{figure} + +Lorsque l'on travaille sur une instance de \gls{TSP} avec \num{194} sommets, on observe les +résultats présentés dans \acref{fig:alsk/results/rt_graspels_qa194_24_20_20_par}. +Ils sont dans l'ensemble équivalents aux précédents, ce qui confirme une certaine indépendance des +performances obtenues par rapport à la taille des données traitées. + +\begin{figure} + \centering + \includegraphics{img/alsk/rt_graspels_qa194_24_20_20_par.pdf} + \caption{Temps d'exécution parallèle d'un \graspels{} pour une instance de \glsxtrshort{TSP} de \num{194} sommets} + \label{fig:alsk/results/rt_graspels_qa194_24_20_20_par} +\end{figure} +%}}} + +%{{{ var grasp_n " +En faisant varier $N$ de \num{4} à \num{20} par pas de \num{4}, on obtient les courbes de +\acref{fig:alsk/results/rt_graspels_qa194_4:20_20_20_par}. +En particulier pour $N=4$ (\cref{fig:alsk/results/rt_graspels_qa194_4_20_20_par}), $N=8$ +(\cref{fig:alsk/results/rt_graspels_qa194_8_20_20_par}) et $N=12$ +(\cref{fig:alsk/results/rt_graspels_qa194_12_20_20_par}) pour lesquels c'est très visible, on +observe une limite dans l'accélération obtenue pour \og firstlevel \fg. +Ce résultat est logique puisque la politique d'exécution utilisée dans ce cas ne parallélisant que +le premier niveau possible, si celui-ci correspond à une boucle de $k$ itérations l'accélération +maximale que l'on peut obtenir est de $k$. +Les autres politiques d'exécution se comportent de la même manière, indépendamment du nombre +d'itérations du \gls{GRASP}, ce qui est attendu puisque toutes les trois sont capables de +paralléliser de multiples niveaux et donc de tirer profit des itérations parallélisables de +l'\gls{ELS} (au nombre fixe de $I=20$). + +\begin{figure} + \centering + \foreach \n in {4,8,...,20} { + \begin{subfigure}[b]{.49\textwidth} + \centering + \includegraphics{img/alsk/rt_graspels_qa194_v\n_20_20_par.pdf} + \caption{$N = \n$} + \label{fig:alsk/results/rt_graspels_qa194_\n_20_20_par} + \end{subfigure} + \hfill + } + \begin{subfigure}[b]{.49\textwidth} + \centering + \hspace{2em} + \includegraphics{img/alsk/rt_graspels_qa194_var_grasp_par_legend.pdf} + \vspace{12ex} + \end{subfigure} + \caption{Temps d'exécution parallèle d'un \graspels{} pour une instance de \glsxtrshort{TSP} de \num{194} sommets selon + le nombre d'itérations du \glsxtrshort{GRASP}} + \label{fig:alsk/results/rt_graspels_qa194_4:20_20_20_par} +\end{figure} +%}}} + +%{{{ var els iter max " +Nous avons voulu vérifier l'effet de la variation du nombre d'itérations de la boucle centrale non +parallélisable (la boucle extérieure de l'\gls{ELS} dont le nombre d'itérations est $O$). +Les courbes présentées dans \acref{fig:alsk/results/rt_graspels_qa194_4_1:50_20_par} correspondent à +des mesures de temps d'exécution pour $N=4$ et $O$ variant entre $1$ et $50$. +Ces courbes permettent d'observer que d'une manière globale, le comportement reste similaire. + +\begin{figure} + \centering + \foreach \n in {1,4,8,40,50} { + \begin{subfigure}[b]{.49\textwidth} + \centering + \includegraphics{img/alsk/rt_graspels_qa194_4_v\n_20_par.pdf} + \caption{$O = \n$} + \label{fig:alsk/results/rt_graspels_qa194_4_\n_20_par} + \end{subfigure} + \hfill + } + \begin{subfigure}[b]{.49\textwidth} + \centering + \hspace{2em} + \includegraphics{img/alsk/rt_graspels_qa194_var_els_iter_max_par_legend.pdf} + \vspace{12ex} + \end{subfigure} + \caption{Temps d'exécution parallèle d'un \graspels{} pour une instance de \glsxtrshort{TSP} de \num{194} sommets selon + le nombre d'itérations non parallélisables de l'\glsxtrshort{ELS}} + \label{fig:alsk/results/rt_graspels_qa194_4_1:50_20_par} +\end{figure} +%}}} + +%{{{ speedup " +Enfin, sur la base des données précédentes, +\acref{fig:alsk/results/rt_graspels_qa194_4_20_20_speedup,fig:alsk/results/rt_graspels_qa194_20_20_20_speedup} +présentent l'accélération obtenue pour les différentes politiques d'exécution (ainsi que pour une +parallélisation sans utiliser la bibliothèque) en fonction du nombre de cœurs alloués. +\Acref{fig:alsk/results/rt_graspels_qa194_4_20_20_speedup} correspond à l'exécution d'un \graspels{} +pour lequel $N=4$, c'est-à-dire que la boucle principale du \gls{GRASP} effectue $4$ itérations qui +peuvent être parallélisées. +Les valeurs de $O$ et $I$ sont conservées à la valeur par défaut utilisée durant cette section, à +savoir $20$ pour les deux. +Lorsque $N=4$, on observe en particulier que la parallélisation en utilisant une politique +d'exécution ne traitant qu'un niveau est limitée en accélération à la valeur atteinte lorsque le +nombre de cœurs alloués égale $N$, ce qui n'est pas surprenant. +Par ailleurs, l'accélération obtenue en utilisant la politique d'exécution par \en{thread pool} \og +statique \fg{} semble être généralement meilleure que les autres. +Dans tous les cas, on observe que l'accélération croît effectivement lorsque le nombre de cœurs +alloués croît. + +\begin{figure} + \ocgfigIG{Accélération en fonction du nombre de coeurs alloués} + {results/rt_graspels_qa194_4_20_20_speedup}{rt_graspels_qa194_}{_20_20_speedup.pdf} + {Accélération en fonction du nombre de cœurs alloués (\graspels{} avec $N=4$, \glsxtrshort{TSP} de \num{194} sommets)} + {$N$ :}{\N}{4/on,8/off,12/off,16/off} +\end{figure} + +\Acref{fig:alsk/results/rt_graspels_qa194_20_20_20_speedup} correspond quant à elle à l'exécution +d'un \graspels{} avec $N=20$. +Le comportement global est conservé et l'accélération obtenue est proportionnelle au nombre de cœurs +alloués. +Pour la politique d'exécution \og sk\_firstlevel \fg{}, l'accélération obtenue n'atteint logiquement +plus un plafond puisque le nombre de cœurs alloués n'atteint pas, durant ces mesures, la valeur de +$N$. + +\begin{figure} + \centering + \includegraphics{img/alsk/rt_graspels_qa194_20_20_20_speedup.pdf} + \caption{Accélération en fonction du nombre de cœurs alloués (\graspels{} avec $N=20$, + \glsxtrshort{TSP} de \num{194} sommets)} + \label{fig:alsk/results/rt_graspels_qa194_20_20_20_speedup} +\end{figure} + +\Acref{fig:alsk/results/rt_graspels_qa194_all_20_20_speedup_1,fig:alsk/results/rt_graspels_qa194_all_20_20_speedup_16} +montrent l'accélération obtenue en fonction de la valeur de $N$ pour un nombre de cœurs alloués +défini, respectivement \num{1} et \num{18}. +Dans \acref{fig:alsk/results/rt_graspels_qa194_all_20_20_speedup_1}, on observe donc le comportement +des politiques d'exécution lorsqu'elles sont utilisées pour une exécution séquentielle. +Cela confirme les remarques précédentes à propos de la politique \og sk\_staticpool \fg{} qui +apparaît comme étant la moins efficace dans ce contexte précis. + +\begin{figure} + \centering + \includegraphics{img/alsk/rt_graspels_qa194_all_20_20_speedup_1.pdf} + \caption{Accélération en fonction de $N$ pour \num{1} cœur alloué (\graspels{}, \glsxtrshort{TSP} de \num{194} sommets)} + \label{fig:alsk/results/rt_graspels_qa194_all_20_20_speedup_1} +\end{figure} + +En revanche, dans \acref{fig:alsk/results/rt_graspels_qa194_all_20_20_speedup_16} on vérifie que +pour toute valeur de $N$ cette politique d'exécution est meilleure. +Cela semble indiquer que le fait d'effectuer une partie du travail durant la compilation et de +déterminer à l'avance à quel \en{thread} affecter chaque tâche affecte favorablement les +performances du programme. + +\begin{figure} + \ocgfigIG{Accélération en fonction du nombre d'itérations} + {results/rt_graspels_qa194_all_20_20_speedup_16}{rt_graspels_qa194_all_20_20_speedup_}{.pdf} + {Accélération en fonction de $N$ pour \num{18} cœurs alloués (\graspels{}, \glsxtrshort{TSP} de \num{194} sommets)} + {nombre de cœurs :}{\T}{2/off,4/off,6/off,8/off,10/off,12/off,14/off,16/off,18/on} +\end{figure} +%}}} +%}}} diff --git a/src/alsk/8_conclusion.tex b/src/alsk/8_conclusion.tex new file mode 100644 index 0000000..5593d22 --- /dev/null +++ b/src/alsk/8_conclusion.tex @@ -0,0 +1,74 @@ +\section{Conclusion} + +Ce chapitre nous a permis d'explorer le concept de squelette algorithmique. +Ceux-ci permettent de représenter un algorithme pour qu'un programme puisse en faire usage afin de +produire un exécutable dont l'exécution exploite mieux le matériel à sa disposition. +Un atout des squelettes algorithmiques est d'isoler l'ensemble des détails de la parallélisation du +code de l'utilisateur. + +Nous avons dans un premier temps introduit un problème sur lequel appliquer des squelettes +algorithmiques, sélectionné dans le domaine de la \gls{RO}. +Celui-ci permet d'illustrer les caractéristiques des différents éléments composant les squelettes +algorithmiques. +En particulier, la métaheuristique \graspels{} exhibe trois couches de boucles dont deux pour +lesquelles la parallélisation est possible. + +Nous avons proposé une bibliothèque tirant profit de la métaprogrammation du langage C++ depuis +l'acquisition du squelette algorithmique jusqu'à la génération du programme. +Nous présentons d'abord les concepts fondamentaux de cette bibliothèque : la structure du squelette +et les liens notamment, mais aussi la notion d'os, de muscles et de corps. +La structure d'un squelette décrit l'algorithme par une composition de motifs d'exécution (les os). +Les liens décrivent les flux de données entre les muscles. +Ce système fait en grande partie la spécificité de notre bibliothèque. + +L'exécution d'un algorithme est conditionnée par une politique d'exécution. +Ceci permet de séparer logiquement les motifs d'exécution de la manière dont le programme doit être +exécuté : sur \gls{CPU} ou bien \gls{GPU} ; avec une quantité de \en{threads} définie ; en +orchestrant statiquement ou dynamiquement la répartition des tâches ; ... +Différentes politiques d'exécution ont alors été exposées pour en illustrer le fonctionnement. + +Grâce au mécanisme des liens et au fonctionnement interne de la bibliothèque (qui permet par exemple +l'attribution d'un identifiant unique à toute tâche parallèle), nous avons proposé un moyen de +garantir la répétabilité de l'exécution d'un programme stochastique. +Cette répétabilité est assurée pour un nombre quelconque de \en{threads}, y compris par rapport à +une exécution séquentielle, ce qui est précieux pour la vérification des résultats. +L'intérêt de cette solution initiale réside dans son autonomie : elle consiste en l'affectation à +toute tâche parallèle de son contexte propre. +Ceci est faisable à la main, mais fastidieux et source d'erreur, pour un nombre de \en{threads} +spécifique. +Si ce dernier change, l'affectation doit être revue. + +De plus, nous avons étudié différents moyens permettant d'améliorer cette solution. +En effet, en supposant une parallélisation infinie, toute tâche parallèle doit posséder un contexte +unique. +Or, en tenant compte des limites techniques du matériel, nous avons montré comment réduire +significativement le nombre de contextes distincts requis, par exemple en n'assurant la répétabilité +que jusqu'à une limite supérieure du nombre de cœurs. + +Afin de fournir une abstraction supplémentaire aux concepts internes de la bibliothèque, nous +proposons un \gls{EDSL}. +Celui-ci a pour vocation principale de montrer qu'il est possible de masquer la complexité de +l'interface brute. +Cette itération permet la définition de squelettes et de corps et permet aussi de configurer à la +volée les paramètres de l'algorithme. + +L'application à la \gls{RO} est utilisée au long de ce chapitre : cela offre un exemple dont les +caractéristiques nous ont permis d'introduire, notamment, les problématiques de la parallélisation +sur plusieurs niveaux et le traitement des nombres pseudo-aléatoires. +Notre bibliothèque a finalement été employée pour résoudre des instances de \gls{TSP} afin de +comparer les performances durant l'exécution du programme entre une version écrite \og à la main +\fg{} et une version générée automatiquement par un squelette algorithmique. + +Cette bibliothèque offre différents points d'entrée. +Au niveau le plus interne, un développeur peut concevoir de nouveaux os (donc de nouveaux motifs +d'exécution). +Un expert en parallélisation peut implémenter une nouvelle politique d'exécution. +Du point de vue des utilisateurs de cette bibliothèque, deux catégories peuvent être distinguées : +ceux qui vont décrire un ensemble de squelettes prêts à être utilisés au sein d'une nouvelle +bibliothèque et les utilisateurs finaux qui ajoutent les muscles à ces squelettes pour pouvoir les +exécuter. + +La répétabilité est garantie par la bibliothèque sous certaines conditions (principalement +l'utilisation correcte du système de liens). +L'utilisateur peut fournir lui-même les états qui seront utilisés pour la génération de nombres +aléatoires, et il reste ainsi à sa charge de valider la qualité scientifique de ceux-ci. diff --git a/src/alsk/concept/0_intro.tex b/src/alsk/concept/0_intro.tex new file mode 100644 index 0000000..6a2881a --- /dev/null +++ b/src/alsk/concept/0_intro.tex @@ -0,0 +1,39 @@ +Un squelette algorithmique est le résultat de l'assemblage de plusieurs composants détaillés dans +cette section. +Dans \acref{fig:alsk/app/grasp,fig:alsk/app/ils,fig:alsk/app/els}, les cercles représentent les +tâches devant être exécutées et qui sont fournies par le développeur utilisateur. +Celles-ci sont communément appelées des muscles et sont, dans notre implémentation, de simples +fonctions ou, plus exactement, tout ce qui peut être utilisé comme une fonction. +En C++, grâce à la surcharge de la fonction membre \cppinline{operator()}, il est possible pour une +instance d'une classe de se comporter comme une fonction. +On peut parler de +fonctionoïdes\footnote{\url{https://isocpp.org/wiki/faq/pointers-to-members\#functionoids}}, terme +que nous utiliserons dans ce document pour regrouper l'ensemble des éléments ressemblant à une +fonction. + +Dans ces \acref{fig:alsk/app/grasp,fig:alsk/app/ils,fig:alsk/app/els}, les arêtes liant les muscles +représentent les transitions entre ceux-ci. +En particulier, cela représente les transferts de données. +En général, les bibliothèques de squelettes algorithmique font le choix d'imposer à leurs +utilisateurs comment les données sont transmises en définissant implicitement la nature du +transfert. +Pour un patron d'exécution \en{pipeline}, cela peut, par exemple, se traduire par l'utilisation de +la valeur retournée par le premier muscle comme premier argument du deuxième, et ainsi de suite. +Ce choix peut être fait pour simplifier l'interface, mais, en conséquence, celle-ci est moins +flexible. +Nous avons choisi de laisser le développeur définir lui-même les contraintes qui décrivent comment +les données sont transférées et que nous avons appelées des liens. +Ce mécanisme permet en outre l'intégration d'autres fonctionnalités qui seront présentées dans +\acref{sec:alsk/repeatability}. + +Enfin, la structure générale du squelette correspond à la manière d'assembler les différents +patrons. +Dans notre cas, les patrons, séquentiels ou parallèles, sont les unités structurelles minimales. +Pour cette raison, nous les avons nommés \og os \fg. +Tout squelette est composé uniquement d'os, mais la construction d'un squelette peut utiliser, de +manière récursive, un assemblage sans distinction d'os et de squelettes. + +Cette section a pour but de présenter notre implémentation de la structure des squelettes, dans un +premier temps, puis, dans un second temps, des liens que ces squelettes utilisent. +Pour cela, nous utilisons l'exemple du \graspels{}, introduit dans la section précédente. +Notre objectif sera de représenter cet exemple avec des squelettes algorithmiques. diff --git a/src/alsk/concept/1_struct.tex b/src/alsk/concept/1_struct.tex new file mode 100644 index 0000000..2a6a0a9 --- /dev/null +++ b/src/alsk/concept/1_struct.tex @@ -0,0 +1,124 @@ +\subsection{Structure} +\label{subsec:alsk/concept/struct} + +Comme cela a été dit, les os sont les éléments de base de la construction d'un squelette. +Ils possèdent des caractéristiques, rendues accessibles par une spécialisation d'un template de +classe de \en{traits} (voir \acref{subsec:mp/types/reflexion}), qui permettent par exemple de savoir +si celui-ci peut exécuter des tâches en parallèle. +Un os fournit son implémentation par la spécialisation d'un template de classe dédié, dont +l'interface principale est l'\cppinline{operator()}. + +Cette implémentation est propre à chaque os puisqu'elle dépend du comportement que celui-ci +représente. +Elle dépend également de paramètres extrinsèques comme le mode d'exécution. +À ce jour, la bibliothèque ne supporte que deux modes : séquentiel et parallèle ; mais il est +possible d'étendre cette capacité en créant de nouveaux \en{tags} (voir +\acref{subsubsec:mp/mpt/et/sepdata}) et les implémentations d'os associées. + +Ainsi, un os tel que \cppinline{Serial}, dont la nature est d'exécuter en séquence plusieurs tâches, +aura une implémentation unique indépendante du mode d'exécution (qui ne peut pour le moment qu'être +séquentiel ou parallèle). +En revanche, un os représentant une ferme de tâches devra s'adapter et donc proposer deux +implémentations distinctes. +Il est techniquement possible de n'en proposer qu'une seule qui fonctionnerait pour un nombre +quelconque de \en{threads} (incluant \num{1}), cependant le programme séquentiel généré serait moins +efficace : la généricité sur le nombre de \en{threads} implique au moins l'existence de tests qui +n'apparaîtraient pas dans un code dédié. + +\Acref{lst:alsk/concept/struct/minimal} définit la structure d'un squelette minimal (composé d'un +seul os, \texttt{Serial}) qui exécutera \num{4} tâches \cppinline{Task1} à \cppinline{Task4} en +série. +Le template \cppinline{S} (pour structure) permet de stocker l'os et les muscles associés pour une +utilisation durant la compilation. +La structure d'un squelette étant faite pour être utilisée ultérieurement (et possiblement dans +différents contextes), il est utile de la nommer, comme on le fait pour des variables. +S'agissant d'un type ou d'un template, \cppinline{using} peut être utilisé (voir +\acref{lst:alsk/concept/struct/minimal_param}). + +\begin{listing} + \inputlst{concept/struct/minimal} + {Structure d'un squelette composé d'un seul os} +\end{listing} + +D'autre part, les muscles utilisés (\texttt{Task1} à \texttt{Task4}) n'influent aucunement sur la +structure et sont habituellement inconnus au stade de la conception d'un squelette. +Ainsi, il est souhaitable de paramétrer cette structure sur les muscles qui y seront insérés comme +dans \acref{lst:alsk/concept/struct/minimal_param}. + +\begin{listing} + \inputlst{concept/struct/minimal_param} + {Structure paramétrée d'un squelette composé d'un seul os} +\end{listing} + +Lorsqu'un squelette est complet, c'est-à-dire que l'on dispose de la structure avec les liens +(prochaine section) et les muscles, il est possible de produire une implémentation de celui-ci. +Cette implémentation fournit un \cppinline{operator()} (dont les spécificités seront expliquées dans +la section sur les liens), ce qui en fait donc un fonctionoïde. +Pour cette raison, l'implémentation produite par un squelette peut être utilisée comme muscle d'un +autre squelette. +La bibliothèque tire profit de cette caractéristique pour, au sein de la définition d'une structure +de squelette, traiter une autre structure comme un muscle. +Grâce à cela, il est possible de définir des squelettes plus complexes, eux-mêmes composés de +squelettes. + +La structure d'un \gls{GRASP} (\cref{fig:alsk/app/grasp}) peut alors être définie par un patron +exécutant plusieurs fois une même tâche et sélectionnant le meilleur résultat obtenu (os nommé +\texttt{FarmSel}). +La tâche principale (par opposition à la tâche de sélection) est une séquence +de deux tâches (os \texttt{Serial}) (\cref{lst:alsk/concept/struct/grasp}). +Dans les codes, écrits en anglais, les noms des tâches des schémas sont adaptés. +Ainsi l'heuristique constructive HC devient le type \cppinline{CH}, la recherche locale RL devient +\cppinline{LS} et enfin la tâche de sélection S devient \cppinline{Sel}. + +\begin{listing} + \inputlst{concept/struct/grasp} + {Structure du squelette d'un \glsxtrshort{GRASP}} +\end{listing} + +Cette construction est mieux schématisée par un arbre (\cref{fig:alsk/concept/struct/treegrasp}). +Celui-ci peut-être obtenu à partir de la représentation du \gls{GRASP} de +\acref{fig:alsk/app/grasp}. +Chaque branche de l'arbre correspond directement à un patron utilisé dans le \gls{GRASP}, et chaque +feuille correspond à un muscle. +Un nœud $A$ est l'enfant d'un nœud $B$ si les éléments homologues $A'$ et $B'$ au sein du squelette +sont liés par le fait que $B'$ possède $A'$ comme tâche à exécuter. +Cette représentation souligne l'aspect \og patron d'expressions \fg{} qu'arborent les squelettes +algorithmiques. +Elle est également plus compacte, en particulier parce qu'elle ne répète aucune information. + +\begin{figure} + \inputfig{concept/struct/treegrasp} + {Arbre représentant la structure d'un GRASP} +\end{figure} + +Ainsi, le \graspels{} peut être schématisé de manière différente par +\acref{fig:alsk/concept/struct/graspels,fig:alsk/concept/struct/treegraspels}, mais le schéma de +cette dernière figure est plus compact, car les éléments parallèles ne sont pas répétés, et aussi +plus proche de la représentation que la bibliothèque a de cet algorithme. +La définition de la structure du \graspels{} peut être obtenue en définissant une structure de +l'\gls{ELS}, formant le cœur du \graspels{}, et en l'utilisant comme un muscle qui remplace la +recherche locale dans la structure du \gls{GRASP} (\cref{lst:alsk/concept/struct/graspels}). +\begin{listing}[!b] + \inputlst{concept/struct/graspels} + {Structure du squelette d'un \graspels{}} +\end{listing} +À noter que pour alléger les extraits de code et les figures, le critère d'acceptation (tâche A) ne +sera dorénavant pas spécifié au sein de l'\gls{ELS}. +Il s'agit simplement d'une tâche devant être effectuée en série, et donc d'un os \cppinline{Serial}. + +\begin{figure} + \inputfig{concept/struct/graspels} + {Schéma d'un \graspels{}} +\end{figure} + +\begin{figure} + \inputfig{concept/struct/treegraspels} + {Arbre représentant la structure d'un \graspels{}} +\end{figure} + +S'il est aussi possible de définir directement la structure globale du \graspels{}, la méthode +présentée est préférable puisqu'elle offre une meilleure extensibilité. +En effet, en définissant plusieurs petites structures (\gls{GRASP}, \gls{ELS}, \gls{ILS}, ...), +celles-ci peuvent être réutilisées pour construire d'autres structures (\graspils{}, \graspels{}, +...) +sans la répétition de code qu'aurait induit la création sans intermédiaires de ces dernières. diff --git a/src/alsk/concept/2_links.tex b/src/alsk/concept/2_links.tex new file mode 100644 index 0000000..6ccf052 --- /dev/null +++ b/src/alsk/concept/2_links.tex @@ -0,0 +1,104 @@ +\subsection{Transmission des données} +\label{subsec:alsk/concept/links} + +Classiquement, les squelettes algorithmiques imposent leur flux de données. +Le développeur doit donc adapter ses muscles pour que leur interface (dans le cas des fonctions, +leur signature : type de retour et types des paramètres) soit compatible. +Cela permet de proposer un outil plus simple d'utilisation puisqu'il y a moins d'information à +fournir par le développeur pour décrire un squelette. +Nous avons cependant fait le choix de ne pas définir de liens (mécanisme de transmission des +données) par défaut entre les différents éléments d'un squelette car cela permet une plus grande +liberté pour l'utilisateur. +De plus, comme le montre \acref{sec:alsk/repeatability}, cela permet de proposer des fonctionnalités +supplémentaires. +Ce mécanisme apporte beaucoup au piment des squelettes algorithmiques proposés au sein de cette +thèse. + +La définition complète d'un squelette (sans ses muscles) nécessite donc l'information des données en +entrée et en sortie de chaque futur muscle. +Chaque partie pouvant être exécutée étant un fonctionoïde, la description du type de retour et des +types des paramètres est facilement faite par la syntaxe C++ décrivant le type d'une fonction : +\cppinline{R(P1, P2, ..., Pn)} où \cppinline{R} est le type de retour et \cppinline{P1} à +\cppinline{Pn} sont les types des $n$ paramètres. + +Suivant une syntaxe très proche de celle utilisée pour décrire la structure, +\acref{lst:alsk/concept/links/minimal} définit les types de données qu'utilisent les différents +fonctionoïdes impliqués dans un squelette qui exécute en séquence deux tâches. +Le template \cppinline{L} (pour lien) permet de stocker, pour un os donné (ici \texttt{Serial}), à +la fois la signature de la fonction qu'il définit et cette même information pour chacun de ses +muscles. +De manière similaire à ce qui peut être accompli avec le template \cppinline{S} lors de la +définition d'une structure de squelette, il est possible d'utiliser une instance du template +\cppinline{L} en substitution d'un muscle. + +\begin{listing} + \inputlst{concept/links/minimal} + {Liens d'un squelette composé d'un seul os} +\end{listing} + +Cet exemple est cependant erroné car il n'apporte pas l'information attendue par le système : les +liens entre les différents paramètres. +Pour le \gls{GRASP}, il s'agit (voir \acref{fig:alsk/concept/links/grasp}) : +\begin{itemize} + \item de connecter le premier paramètre ($P_0$), le problème, aux muscles HC et RL (exécutés par + l'os \cppinline{Serial}) ; + \item de connecter le second paramètre ($P_1$), un \gls{PRNG}, aux muscles HC et RL ; + \item de connecter la sortie du muscle HC ($R_0$) à l'entrée du muscle RL ; + \item et enfin de connecter les $n$ sorties des $n$ exécutions de RL au muscle de sélection. +\end{itemize} + +\begin{figure} + \inputfig{concept/links/grasp} + {Transmission de données au sein d'un \glsxtrshort{GRASP}} +\end{figure} + +Cette description peut être transcrite en C++ comme dans \acref{lst:alsk/concept/links/grasp}. +Deux templates spéciaux sont utilisés : le template \cppinline{P} qui, avec un paramètre template +représentant un entier $i$, permet d'indiquer un argument qui doit être remplacé par le +$(i+1)$\textsuperscript{ème} paramètre de l'appelant ; le template \cppinline{R}, qui utilise de +même un paramètre template représentant un entier $j$, correspond à la valeur retournée par le +$(j+1)$\textsuperscript{ème} muscle de l'os dans lequel ce template est utilisé. + +\begin{listing} + \inputlst{concept/links/grasp} + {Liens du squelette d'un \glsxtrshort{GRASP}} +\end{listing} + +Ainsi, la ligne~\ref{line:alsk/concept/links/grasp:ls} indique que le muscle RL prendra en premier +argument le premier argument donné à l'os \texttt{Serial}, comme deuxième argument la valeur +retournée par le muscle HC (dont l'indice parmi les muscles appelées par l'os est \num{0}, d'où +\cppinline{R<0>}), et comme dernier argument le deuxième de l'appelant. +La valeur retournée par le muscle n'est pas contrôlée par le système de liens et doit donc être le +véritable type de retour. + +La ligne~\ref{line:alsk/concept/links/grasp:serial} décrit les liens d'un os plutôt que ceux d'un +muscle. +Le fonctionnement ne diffère que pour ce qui est retourné qui peut alors être choisi. +Dans ce cas, il s'agit de la valeur retournée par le second muscle de cet os, c'est-à-dire le muscle +RL. + +La ligne~\ref{line:alsk/concept/links/grasp:sel} concerne le muscle Select de l'os \texttt{FarmSel}. +Dans ce cas particulier, le fonctionnement interne de l'os, qui impose la comparaison de solutions, +contraint les arguments qui seront donnés à ce muscle, empêchant partiellement l'utilisation des +templates \cppinline{P} et \cppinline{R}. +L'os fournit lui-même les deux solutions à comparer, et ce autant de fois que nécessaire pour +déterminer la solution qui doit finalement être conservée. +En revanche, si le muscle requiert des arguments supplémentaires (par exemple un \gls{PRNG}), +ceux-ci peuvent être transmis au moyen des templates \cppinline{P} et \cppinline{R}. + +Enfin, la ligne~\ref{line:alsk/concept/links/grasp:farmsel} décrit les liens de l'os +\texttt{FarmSel}. +Deux particularités sont importantes à noter ici. +D'abord, pour la même raison qui fait que le muscle de sélection ne peut pas entièrement contrôler +ses arguments, il n'est pas possible de choisir ce qui est retourné par cet os (contrairement à l'os +\texttt{Serial} qui le permet). +Ensuite, parce qu'il s'agit de l'os le plus externe (cet os est la racine de l'arbre correspondant), +il ne possède pas de fonction appelante à ce stade, rendant invalides l'utilisation des templates +\cppinline{P} et \cppinline{R} pour définir des arguments. +S'il est ensuite utilisé comme muscle d'un autre squelette, dans ce nouveau contexte il sera +possible d'utiliser ces templates pour établir des liens. + +Comme cela avait été fait pour la structure, cette définition des liens est nommée et paramétrée. +Cette fois-ci, ce sont les types des paramètres, c'est-à-dire les types des données manipulées, qui +n'ont pas besoin d'être connus. +Cela permet également la réutilisation de cette définition de liens au sein d'une autre définition. diff --git a/src/alsk/concept/3_inst.tex b/src/alsk/concept/3_inst.tex new file mode 100644 index 0000000..9386633 --- /dev/null +++ b/src/alsk/concept/3_inst.tex @@ -0,0 +1,139 @@ +\subsection{Instanciation du squelette} + +En combinant la structure et les liens associés, on obtient un squelette +(\cref{lst:alsk/concept/inst/grasp}). +Le template \cppinline{BuildSkeleton} construit un arbre exprimant le squelette complet à partir de +sa structure et des liens auxquels sont transmis respectivement les muscles (premier pack de +paramètres) et les types de paramètres des fonctionoïdes (second pack de paramètres). +À ce stade, il est possible de produire un squelette en fixant une partie de ces informations. +Dans cet exemple, toutes restent inconnues, faisant de \cppinline{GraspSkel} un template ayant pour +paramètres l'ensemble des types des paramètres des muscles, ainsi que ces derniers. + +\begin{listing} + \inputlst{concept/inst/grasp} + {Squelette d'un \glsxtrshort{GRASP}} +\end{listing} + +Plutôt que de décrire séparément la structure et les liens, le développeur pourrait se passer de +cette étape de construction et écrire directement le squelette final. +Cependant, cette méthode par assemblage permet de simplifier les éléments à définir en masquant les +détails d'implémentation de la structure de données correspondant au squelette (chaque nœud de +l'arbre comporte des données qui sont plus lourdes à écrire que la structure et les liens +séparément). +Il s'agit donc d'une abstraction de la représentation interne réelle qui permet au développeur +d'exprimer plus aisément un squelette. + +L'étape d'assemblage permet ainsi à la bibliothèque de modifier la structure des squelettes sans +changer son interface. +Cela peut être utilisé pour ajouter au sein des nœuds du squelette des informations déduites à +partir de la structure ou des liens. +Enfin, durant cette étape, le squelette peut être optimisé. +Soient deux algorithmes X et Y dont la structure est telle que représentée, respectivement, par +\acref{fig:alsk/concept/inst/tree_a} et \acref{fig:alsk/concept/inst/tree_b}. +Il est possible d'utiliser l'algorithme Y comme muscle au sein de l'algorithme X (à l'instar de ce +qui est accompli pour construire le \graspels{}, par exemple) en remplacement de la tâche \og B \fg +(\cref{fig:alsk/concept/inst/tree_ab}). +\begin{figure} + \centering + \begin{subfigure}[t]{.49\textwidth} + \inputfig{concept/inst/tree_a} + {Algorithme X} + \end{subfigure}% + ~ + \begin{subfigure}[t]{.49\textwidth} + \inputfig{concept/inst/tree_b} + {Algorithme Y} + \end{subfigure} + \caption{Arbres représentant la structure de deux algorithmes} +\end{figure} +On observe alors un os \texttt{Serial} exécutant un autre os \texttt{Serial}. +Étant donné le fonctionnement de cet os, \acref{fig:alsk/concept/inst/tree_ab_optimized} montre une +structure équivalente dans son fonctionnement et réduisant le nombre d'intermédiaires. +Cette optimisation automatique peut être accomplie durant la compilation, au moment de l'assemblage +de la structure et des liens, par métaprogrammation~\autocite{ref:pereda2020}. + +\begin{figure} + \centering + \begin{subfigure}[t]{.49\textwidth} + \inputfig{concept/inst/tree_ab} + {Résultat brut de l'assemblage} + \end{subfigure}% + ~ + \begin{subfigure}[t]{.49\textwidth} + \inputfig{concept/inst/tree_ab_optimized} + {Arbre optimisé} + \end{subfigure} + \caption{Arbres représentant la structure d'un algorithme issu d'un assemblage} +\end{figure} + +La séparation de la définition de la structure et des liens permet également d'imaginer des +extensions à la bibliothèque pour automatiser, par exemple, la génération des liens à partir de la +structure. +Lorsque les liens d'un squelette sont définis, ceux-ci comportent entre autres l'information des os +qui seront utilisés. +Un outil au niveau du compilateur (extension ou programme agissant avant le compilateur) peut alors +analyser cette description et générer le template de la structure. +Une limite de ce procédé réside dans le mécanisme d'affectation automatique de noms aux paramètres +représentant les muscles. +Sans une compréhension globale de l'algorithme représenté, il n'est pas possible de nommer +correctement ces muscles. +Par ailleurs, au sein du langage lui-même, il n'est pas possible de générer un template, mais +seulement des types, et c'est pourquoi la première solution proposée repose sur un outil externe. +Cela peut tout de même être utilisé pour générer une structure déjà pourvue de muscles. + +Si au contraire la structure est déjà définie et les liens inconnus, il est possible de proposer la +génération automatique de liens. +Celle-ci reposera sur des motifs établis, et le résultat sera alors similaire à ce que font par +défaut les autres solutions de squelettes algorithmiques en ce qui concerne la manière dont les +données sont transmises. +Par exemple, il est possible d'indiquer qu'en cas d'os \cppinline{serial} exécutant les $n$ tâches +$(T_0, \dots, T_{n-1})$, il s'agit d'accepter en paramètres ceux de la première tâche ($T_0$), puis +pour chaque tâche $T_i$ ($i \geqslant 1$) de prendre pour seul paramètre ce qui est retourné par la +tâche $T_{i-1}$. +Enfin, le retour global sera lié à celui de la dernière tâche, $T_{n-1}$. + +Ces motifs peuvent cependant être fournis par le développeur (au maximum un par os). +Ce faisant, un développeur voulant se contraindre à une forme pré-définie, par exemple pour réduire +la quantité de code à produire lors de l'écriture d'un nouveau squelette, en a la possibilité. +Celle-ci reste non-intrusive puisque dans tous les cas, les liens peuvent être redéfinis localement +afin de les adapter à un cas particulier. + +Un squelette peut être complété en lui fournissant les types des paramètres et les muscles dont il a +besoin. +Pour l'\gls{ELS} (\cref{lst:alsk/concept/inst/elsbody}), les types des paramètres sont, dans l'ordre +: le type représentant une instance du problème ; celui d'une instance d'une solution ; celui +d'un générateur de nombres pseudo-aléatoires. +Pour illustrer qu'il s'agit bien de types spécifiques au problème que l'on souhaite résoudre (pour +le problème et la solution), un espace de noms est utilisé. + +% TODO p23 claude.pdf +\begin{listing} + \inputlst{concept/inst/elsbody} + {Corps d'un \glsxtrshort{ELS}} +\end{listing} + +Les muscles doivent être des fonctionoïdes. +En C++ cela inclut les fonctions et les instances de classes surchargeant au moins un +\cppinline{operator()}. +Dans le cas des fonctions comme \cppinline{selectMin} (\cref{lst:alsk/concept/inst/selectmin}), une +indirection est nécessaire afin de construire un type à partir de celle-ci. +L'implémentation en C++14 ne peut être accomplie avec cette syntaxe légère qu'à l'aide d'une macro +(\cppinline{FN}), mais à partir de C++17 il est possible d'utiliser un template directement. +Lorsqu'il s'agit déjà d'un type (comme c'est le cas des muscles \cppinline{Descent} et +\cppinline{Move2Opt} dans \acref{lst:alsk/concept/inst/elsbody}), celui-ci peut directement être +utilisé. + +\begin{listing} + \inputlst{concept/inst/selectmin} + {Fonction de sélection d'une solution à un \glsxtrshort{TSP}} +\end{listing} + +En utilisant le même principe, \acref{lst:alsk/concept/inst/graspelsbody} fournit les types et +muscles nécessaires au \graspels{} pour l'instancier. +Cette instanciation produit un type (\cppinline{GRASPxELS}) dédié à la résolution d'instances de +\gls{TSP} par l'utilisation de la métaheuristique \graspels{}. + +\begin{listing} + \inputlst{concept/inst/graspelsbody} + {Corps d'un \graspels{}} +\end{listing} diff --git a/src/alsk/concept/4_parheight.tex b/src/alsk/concept/4_parheight.tex new file mode 100644 index 0000000..7e82310 --- /dev/null +++ b/src/alsk/concept/4_parheight.tex @@ -0,0 +1,79 @@ +\subsection{Hauteur parallèle du squelette} + +Afin d'illustrer ce qu'il est possible de faire, exclusivement durant la compilation, avec une telle +information, nous proposons de calculer la \og hauteur parallèle \fg{} de l'arbre, c'est-à-dire le +nombre de niveaux parallélisables. +Ces explications sont naturellement transposables à d'autres applications comme l'optimisation de +squelettes assemblés. + +Comme expliqué dans \acref{ch:mp}, l'écriture est récursive en métaprogrammation pure puisqu'il +s'agit exclusivement de programmation fonctionnelle. +Cependant, afin d'assister le développeur, nous avons mis en place une bibliothèque +annexe\footnote{\url{https://phd.pereda.fr/dev/tmp}} permettant l'écriture de métaprogrammes d'une +manière plus impérative. + +Cette bibliothèque annexe est utilisée dans l'écriture de certaines parties de la bibliothèque que +présente ce chapitre, mais nous ne présenterons que son interface, l'implémentation étant fondée +uniquement sur des principes expliqués dans \acref{ch:mp}. + +Une solution triviale pour obtenir cette hauteur parallèle est basée sur l'utilisation de la +métafonction \cppinline{treeAccumulate} (\cref{lst:alsk/concept/parheight/treeaccumulate}). +Cette métafonction accepte trois paramètres : l'arbre \cppinline{T}, la métafonction \cppinline{F} à +appliquer et une valeur par défaut \cppinline{A} à utiliser (notamment pour le cas d'un arbre vide). +Le principe général de \cppinline{TreeAccumulate} (à laquelle \cppinline{treeAccumulate} fait appel +pour extraire une valeur) est d'appeler la métafonction \cppinline{F} en paramètre en lui donnant +comme arguments le nœud courant \cppinline{N} (racine de l'arbre en cours) ainsi que le résultat de +celle-ci pour chacun des nœuds fils conservés dans le pack \cppinline{Ns} (précisément, pour chacun +des arbres dont les nœuds fils sont les racines). +Ces résultats sont donc obtenus par un appel récursif à \cppinline{TreeAccumulate}. + +\begin{listing} + \inputlst{concept/parheight/treeaccumulate} + {Définition de la métafonction \texttt{treeAccumulate}} +\end{listing} + +Dans \acref{lst:alsk/concept/parheight/calcheight}, nous définissons donc comme hauteur par défaut +la valeur \num{0} (nommée \cppinline{CalcHeightDefault}). +\begin{listing}[t] + \inputlst{concept/parheight/calcheight} + {Utilisation de la métafonction \texttt{treeAccumulate} pour calculer la hauteur parallèle d'un + squelette} +\end{listing} +La métafonction \cppinline{CalcHeight} sera appelée par \cppinline{treeAccumulate} pour chaque nœud +de l'arbre avec pour premier argument le nœud courant, \cppinline{P}, et pour autres arguments +l'ensemble des résultats de cette métafonction pour les nœuds fils. +Deux cas doivent alors être distingués : s'il s'agit d'une feuille (cas \cppinline{Leaf}) la +hauteur correspond à \num{0} (un muscle est séquentiel) ; s'il s'agit au contraire d'un nœud interne +(cas \cppinline{Branch}), la hauteur parallèle correspond à la hauteur maximale des nœuds +fils, augmentée de \num{1} si le nœud courant est capable de paralléliser son exécution. +La métafonction \cppinline{TreeFromSkeleton} est fournie par la bibliothèque (ainsi que sa +réciproque \cppinline{SkeletonFromTree}) pour simplifier le traitement des squelettes en permettant +leur conversion en arbres. + +Pour mieux se rendre compte de l'aspect impératif apporté par ces métafonctions, étudions ce même +problème, résolu sans l'aide de la métafonction \cppinline{treeAccumulate}. +Cette solution alternative est implémentée dans \acref{lst:alsk/concept/parheight/parheightalt}. +Cette fois-ci, les métafonctions \cppinline{checkParallelityEach} et \cppinline{sumEach}, utilisées +comme arguments aux métafonctions fournies, ne sont pas écrites (elles sont faciles à écrire et ne +présentent pas d'intérêt pour l'explication). + +\begin{listing} + \inputlst{concept/parheight/parheightalt} + {Calcul alternatif de la hauteur parallèle d'un squelette} +\end{listing} + +En premier lieu, le squelette est converti en arbre. +À partir de cet arbre, l'ensemble des chemins de la racine jusqu'à chaque feuille est construit avec +la métafonction \cppinline{TreeAllRTLPaths}. +Ainsi, \cppinline{RtlPaths} est un vecteur de vecteurs de nœuds de l'arbre. +Chacun de ces nœuds est transformé en un booléen pour produire un vecteur de vecteurs de booléens, +\cppinline{Parallel}. +Ce booléen sera vrai si le nœud correspond à un os correspondant à une exécution parallèle. +Enfin, chaque vecteur contenu dans le vecteur principal est transformé en un entier correspondant au +nombre de booléens vrais qu'il contient. +Grâce à ces transformations, nous avons alors un vecteur, \cppinline{ParallelC}, contenant un entier +pour chaque chemin possible de la racine de l'arbre à une de ses feuilles, lequel correspond aux +nombres d'os autorisant du parallélisme qui existent sur ce chemin. +Pour terminer, une projection de ce vecteur en un unique entier, par accumulation en conservant le +maximum de tous les nombres, est obtenu grâce à la métafonction \cppinline{accumulate}. +La valeur par défaut intervient lorsque le vecteur \cppinline{ParallelC} est vide. diff --git a/src/alsk/exec/0_intro.tex b/src/alsk/exec/0_intro.tex new file mode 100644 index 0000000..0db6dd9 --- /dev/null +++ b/src/alsk/exec/0_intro.tex @@ -0,0 +1,56 @@ +Jusqu'à présent, nous avons vu comment un squelette peut être assemblé par un développeur et comment +lui adjoindre des muscles pour définir les tâches effectives à exécuter, formant ainsi un corps. +Le squelette est composé d'une structure formée d'os et de liens indiquant la nature des flux de +données entre les muscles. +Deux points sont restés indéfinis : l'implémentation des os, c'est-à-dire l'application du motif +d'exécution censé être décrit par l'os, et la stratégie à adopter pour l'exécution des muscles, +c'est-à-dire la manière de répartir le travail sur les processeurs à disposition. + +Les politiques d'exécution sont fournies au moyen de classes, des exécuteurs, présentant une +interface qui peut être utilisée dans l'implémentation des os. +Comme représenté par \acref{fig:alsk/exec/intro/diagram}, un os n'exécute pas directement lui-même +ses muscles. +Cette indirection est nécessaire pour dissocier les os de la politique à suivre pour l'exécution des +muscles qui lui sont associés. + +Nous profitons de ceci pour masquer une difficulté inhérente au travail que doit effectuer notre +bibliothèque : l'appel aux fonctions en respectant les liens fournis pour la transmission des +paramètres. +Dans \acref{fig:alsk/exec/intro/diagram}, le \og système interne \fg correspond à un template dont +le but principal est d'appliquer les liens. +Pour cela sont utilisés différents n-uplets (dont l'ensemble de paramètres en entrée et l'ensemble +des valeurs retournées) au sein desquels sont sélectionnés les bons éléments, dans le bon ordre, +pour être utilisés dans un appel de fonctionoïde. +Le fonctionoïde dont il s'agit correspond alors soit directement à un fonctionoïde fourni par le +développeur, soit au résultat de l'implémentation d'un sous-squelette. +Ces deux aspects (l'implémentation automatique, si nécessaire, et la gestion de l'appel effectif en +fournissant le bon ensemble d'arguments) ne semblent pas intéressant à présenter en détail et seront +donc simplement regroupés sous le nom de système interne. + +Dans un premier temps, pour chaque muscle d'un os, ce dernier génère les instances (étape \num{1} de +la figure) du système interne qui lui sont dédiées. +Ces instances sont ensuite utilisées, conjointement avec d'éventuels autres arguments, pour faire +appel aux primitives procurées par une politique d'exécution (étape \num{2}). +En effet, chaque politique d'exécution doit rendre publique une interface commune de fonctions qui +pourront être utilisées pour exprimer comment doivent être exécutés des ensembles de tâches. +Par exemple, on pourra demander une exécution parallèle de multiples tâches ou bien une exécution +parallèle d'une même tâche répétée plusieurs fois. + +Les politiques d'exécution utilisent l'instance du système interne (étape \num{3}), fournie par +l'os, qui permet d'exécuter un muscle (indiqué par l'os) simplement : l'implémentation du muscle, si +celui-ci correspond en fait à un autre os (étape \num{4}), ainsi que la gestion des liens sont +automatisés, comme expliqué ci-avant. +L'implémentation de l'étape \num{4}, qui arrive donc uniquement lorsque le muscle à exécuter est un +os, utilise la partie \og implémentation \fg{} de l'os pour produire un muscle. + +\begin{figure} + \inputfig{exec/intro/diagram} + {Diagramme d'interaction des os avec les exécuteurs} +\end{figure} + +Cette section présente les éléments importants de ce système, en commençant par l'écriture de la +spécialisation du template dédié à l'implémentation des os pour certains d'entre eux afin +d'identifier les fonctions qui doivent être pourvues par un exécuteur. +Ensuite, elle expose le principe général des exécuteurs pour terminer par des exemples concrets de +ce qu'un développeur peut écrire pour gérer lui-même, si nécessaire, le procédé à suivre durant +l'exécution. diff --git a/src/alsk/exec/1_bones.tex b/src/alsk/exec/1_bones.tex new file mode 100644 index 0000000..3f8d742 --- /dev/null +++ b/src/alsk/exec/1_bones.tex @@ -0,0 +1,99 @@ +\subsection{Implémentation des os} + +Les os représentent des motifs d'exécution (une série de tâches, une répétition parallèle d'une +tâche, ...). +Pour que la composition de squelettes, basée sur les os, fonctionne, il faut que ces os puissent se +comporter comme des muscles. +C'est-à-dire qu'il faut qu'un os puisse être exécuté comme une fonction. + +Pour cela, la définition d'un os se fait en plusieurs parties, comme cela a été introduit dans +\acref{subsec:alsk/concept/struct}. +La première est un template de classe, comme dans \acref{lst:alsk/exec/bones/farm} par exemple pour +\cppinline{Farm}, qui correspond à la partie \og squelette \fg{} de l'os dans +\acref{fig:alsk/exec/intro/diagram}. +Celui-ci est paramétré au minimum par la signature de la fonction qui sera associée à l'os +(\cppinline{Signature}) et par l'ensemble des muscles et de leurs signatures : le pack avec +\cppinline{Task} et \cppinline{TaskLinks}, respectivement le muscle et la signature associée qui +seront exécutés par \cppinline{Farm}. +Pour un os disposant de plusieurs tâches, ce pack est répété autant de fois qu'il y a de tâches. + +\begin{listing} + \inputlst{exec/bones/farm} + {Os \cppinline{Farm}} +\end{listing} + +La classe générée doit permettre de définir l'état de l'os, par exemple pour le cas de +\cppinline{Farm} il y aura, en plus de l'instance de la tâche \cppinline{task}, le nombre de fois +\cppinline{n} que la tâche \cppinline{task} devra être exécutée. +L'instance de \cppinline{Task} est utile principalement lorsqu'elle correspond elle-même à un autre +os, lequel a donc également besoin d'un état. + +La partie \og \en{traits} \fg{} de l'os dans \acref{fig:alsk/exec/intro/diagram} est également +importante pour le bon fonctionnement de la bibliothèque. +Chaque os doit spécialiser un template de classe de \en{traits} (\cppinline{SkeletonTraits}) pour +notamment indiquer si l'os possède un comportement obligatoirement séquentiel ou non. +\Acref{lst:alsk/exec/bones/farmtraits} montre cela pour l'os \cppinline{Farm} qui peut, si la +politique d'exécution le permet, être exécuté en parallèle. + +\begin{listing} + \inputlst{exec/bones/farmtraits} + {Spécialisation du template de classe de \en{traits} pour l'os \cppinline{Farm}} +\end{listing} + +Enfin, la partie \og implémentation \fg{} de l'os dans \acref{fig:alsk/exec/intro/diagram} est ce +qui permet d'utiliser l'os comme une fonction. +Cela se fait par un ensemble de spécialisations d'un autre template (\cppinline{Impl}), lesquelles +définissent le comportement de l'os en fonction de certains critères. +Essentiellement, chacune de ces spécialisations consiste en un fonctionoïde dont +l'\cppinline{operator()} accepte les paramètres attendus par la signature de fonction affectée à +l'os, et retourne, de même, le type retourné indiqué dans cette signature. + +Pour le cas d'os simples comme \cppinline{Farm}, il existe des primitives dans l'interface des +politiques d'exécution correspondant directement au patron à respecter. +Ainsi, que ce soit pour le cas séquentiel (voir \acref{lst:alsk/exec/bones/farmseq}) ou le cas +parallèle (voir \acref{lst:alsk/exec/bones/farmpar}), un appel de fonction suffit. +Dans le premier cas, il s'agit d'exécuter de manière séquentielle de multiples fois l'unique tâche. +Dans le second cas, l'exécution est faite en parallèle. + +\begin{listing} + \inputlst{exec/bones/farmseq} + {Implémentation de l'os \cppinline{Farm} pour le cas séquentiel} +\end{listing} + +\Acref{lst:alsk/exec/bones/farmseq,lst:alsk/exec/bones/farmpar} diffèrent par rapport au critère +représenté par le deuxième paramètre du template \cppinline{Impl} : un \en{tag} indiquant si la +spécialisation concerne une exécution séquentielle (\cppinline{tag::Sequential}) ou une exécution +parallèle (\cppinline{tag::Parallel}). +Ainsi, dans le premier cas on appellera la primitive \cppinline{executeSequential} de la politique +d'exécution, tandis que dans le second cas il s'agira d'utiliser \cppinline{executeParallel}. +À noter que la politique d'exécution peut tout à fait procéder à une exécution séquentielle y +compris dans le second cas, notamment dans le cas évident où il s'agit d'une politique d'exécution +séquentielle. + +\begin{listing} + \inputlst{exec/bones/farmpar} + {Implémentation de l'os \cppinline{Farm} pour le cas parallèle} +\end{listing} + +La classe dont hérite la spécialisation du template \cppinline{Impl} (à laquelle on accède au +travers du type \cppinline{This}) fournit la partie \og squelette \fg{} de l'os, grâce notamment au +pack \cppinline{TTask} contenant les muscles et leurs signatures. +En l'utilisant, précisément la partie \og liens \fg{} que l'on obtient depuis ce squelette, on +définit \cppinline{Task} qui sert d'interface avec le \og système interne \fg{} : le template +\cppinline{Execute} est le point d'entrée de ce système. +Ce type \cppinline{Task} est donc fourni à la primitive de la politique d'exécution comme argument +template. + +Un os plus complexe peut utiliser plusieurs primitives, c'est le cas par exemple de +\cppinline{While}. +Celui-ci exécute le muscle permettant de déterminer s'il faut continuer, puis, le cas échéant, la +tâche principale. +L'ensemble des primitives des politiques d'exécution que nous avons déterminées comme nécessaires +pour l'écriture des os dont dispose la bibliothèque comporte ainsi l'exécution : +\begin{itemize} + \item d'une tâche ; + \item d'une tâche répétée (\cppinline{executeSequential}) ; + \item d'une tâche répétée en parallèle (\cppinline{executeParallel}) ; + \item d'une tâche répétée en parallèle suivi d'une réduction ; + \item d'un ensemble de tâches en parallèle. +\end{itemize} diff --git a/src/alsk/exec/2_ident.tex b/src/alsk/exec/2_ident.tex new file mode 100644 index 0000000..51e4554 --- /dev/null +++ b/src/alsk/exec/2_ident.tex @@ -0,0 +1,70 @@ +\subsection{Identification des tâches} +\label{subsec:alsk/exec/ident} + +Comme expliqué, les muscles ne sont pas directement utilisables. +S'il s'agit d'un fonctionoïde, rien n'est à faire avant de pouvoir l'exécuter, mais, en revanche, +s'il s'agit d'un os, il est nécessaire dans un premier temps de l'implémenter pour obtenir un +fonctionoïde. +Ainsi, c'est finalement dans les deux cas un l'appel à un fonctionoïde qui devra être fait. + +Pour procéder à cet appel, il faut correctement positionner les différents paramètres (indiqués par +le template \cppinline{P} lors de la définition des liens) et résultats (indiqués par le template +\cppinline{R}) dans la liste des arguments du fonctionoïde. +Ceci est automatiquement effectué par le système interne et repose, en simplifiant un peu, sur de +l'expansion de packs templates. + +Cette étape permet en outre de produire des méta-informations sur le squelette. +L'une de ces données, dont nous nous servirons par la suite, est un identifiant numérique associé à +chaque tâche, muscle et os. +Cet identifiant possède comme propriété d'être unique dans un contexte parallèle quelconque : deux +tâches concurrentes ne peuvent partager le même identifiant. +En d'autres termes, si deux tâches s'exécutent obligatoirement en séquence (c'est-à-dire s'il +existe une dépendance entre elles) elles auront un identifiant identique, sinon celui-ci sera +différent. + +Il est possible de parvenir à cette affectation en déterminant quels os sont parallélisables. +Lorsqu'un os séquentiel est instancié, il transmet son propre identifiant à chacun de ses muscles, +tandis qu'un os pouvant être parallèle leur donne des identifiants distincts. +Le calcul de l'identifiant dans ce dernier cas dépend alors de l'existence de niveaux de +parallélisation plus profonds. + +Afin d'expliquer notre méthode, nous allons prendre l'exemple du \graspels{} représenté dans +\acref{fig:alsk/exec/ident/graspels} et annoté avec les ensembles desquels sont issus les +identifiants associés à chaque nœud. +\begin{figure}[!b] + \inputfig{exec/ident/graspels} + {Identification des tâches d'un squelette de \graspels{}} +\end{figure} +L'identifiant de départ, affecté à l'os racine \texttt{farmsel\textsubscript{1}}, est \num{0}. +Celui-ci est transmis tel quel au muscle \texttt{Sel\textsubscript{1}} puisque celui-ci ne peut pas +être exécuté en parallèle d'une autre tâche. +Pour déterminer les identifiants donnés aux $a$ instances de \texttt{serial\textsubscript{1}}, il +est nécessaire de parcourir l'arbre à partir de ce nœud afin de trouver un éventuel autre os +parallélisable. +Dans ce cas, il y en a effectivement un, \texttt{farmsel\textsubscript{2}}, exécutant $b$ tâches +parallèles. + +Lorsqu'un cas de parallélisation à plusieurs niveaux est ainsi trouvé, par exemple lorsqu'un nœud +$P_1$, correspondant à un os parallélisable, possède un nœud descendant $P_2$, correspondant aussi à +un os parallélisable, le nombre de tâches pouvant être exécutées en parallèle correspond au produit +des nombres de tâches que vont exécuter $P_1$ et $P_2$. + +Ainsi, au niveau de \texttt{farmsel\textsubscript{1}}, le nombre de tâches exécutées en parallèle +est de $a \times b$ : chaque \texttt{serial\textsubscript{1}} permettra l'exécution parallèle de +$b$ tâches. +Les $a$ identifiants donnés aux instances de \texttt{serial\textsubscript{1}} sont donc générés à +partir de celui de \texttt{farmsel\textsubscript{1}} (\num{0}) et espacés de $b$ et sont donc +$\{0,b,2\,b,\dots,(a-1)\,b\}$. + +Toutes les tâches d'un même niveau de parallélisation partagent le même identifiant, donc, pour une +instance donnée de \texttt{serial\textsubscript{1}}, les instances correspondantes des tâches qui ne +sont exécutées qu'une seule fois dans cette instance (\texttt{HC}, \texttt{serial\textsubscript{2}}, +\texttt{RLI}, \texttt{itersel}, \texttt{farmsel\textsubscript{2}}, \texttt{Sel\textsubscript{2}} et +\texttt{Sel\textsubscript{3}}) auront un identifiant unique (appartenant à l'ensemble ci-avant). + +L'affectation au niveau de \texttt{farmsel\textsubscript{2}} suit le même principe que pour le +premier os parallélisable, mais puisqu'aucun descendant ne permet la parallélisation, le pas utilisé +pour espacer les identifiants est \num{1}. +Ainsi, pour l'instance de \texttt{farmsel\textsubscript{2}} d'identifiant \num{0}, ce seront les +nombres de \num{0} à $b-1$, pour celui d'identifiant $b$, de $b$ à $2b-1$, et ainsi de suite +jusqu'à celui d'identifiant $(a-1)\,b$ qui produira les valeurs de $(a-1)\,b$ à $a\,b-1$. diff --git a/src/alsk/exec/3_executor.tex b/src/alsk/exec/3_executor.tex new file mode 100644 index 0000000..3f0b066 --- /dev/null +++ b/src/alsk/exec/3_executor.tex @@ -0,0 +1,158 @@ +\subsection{Exécuteur} +\label{subsec:alsk/exec/executor} + +Le rôle rempli par les exécuteurs est donc d'implémenter une politique d'exécution, c'est-à-dire une +manière d'exécuter les muscles d'un squelette sur les différents cœurs qui sont affectés. +Pour cela, nous avons vu qu'un ensemble de schémas de parallélisation doivent être fournis. +Ces différentes primitives seront présentées pour des cas concrets par la suite. +Nous allons dans un premier temps nous intéresser aux raisons de fournir différentes politiques +d'exécutions. + +Considérons un squelette dont l'os extérieur est un \cppinline{Farm} exécutant $N \in +\mathbb{N}^{*}$ fois sa tâche. +Nous supposons que les instances de cette tâche ont toutes le même temps d'exécution $D$. +\Acref{fig:alsk/exec/executor/sequential} schématise l'exécution séquentielle d'un programme : un +même \en{thread} traite les tâches l'une après l'autre. +Le temps total d'exécution sera donc égal à $N \times D$. + +\begin{figure} + \inputfig{exec/executor/sequential} + {Exécution séquentielle de $N$ tâches} +\end{figure} + +Si l'on dispose de $K \in \mathbb{N}^{*}$ cœurs, notamment lorsque $K > 1$, il faut décider du +nombre de \en{threads} à créer. +La valeur optimale dépend de ce que fait le programme, et nous n'allons pas discuter de ce problème +ici. +Le développeur utilisant cette bibliothèque peut définir lui-même cette valeur, et nous allons pour +cette section seulement considérer le nombre $T \in \mathbb{N}^{*}$ de \en{threads} qu'il est +possible de créer. + +Supposons que l'on dispose au maximum de $T$ \en{threads} pour exécuter $N$ tâches ($N, T \in +\mathbb{N}^{*}$), il existe $n \in \mathbb{N}$ et $r \in \llbracket 0, T \llbracket$ tel que +$N = n\,T + r$. +Une répartition équilibrée consiste alors à donner à $r$ \en{threads} $n+1$ tâches à traiter et +$n$ aux $T-r$ autres \en{threads}. +Lorsque $T > N$, on a $n = 0$ et $r = N$ et il y a donc $T-N$ \en{threads} auxquels sont affectées +\num{0} tâches. +Dans ce cas, seulement $N$ \en{threads} sont créés. + +Selon la valeur de $r$, deux cas sont possibles. +Si $r = 0$ alors les $T$ \en{threads} exécutent $n = \frac{N}{T}$ tâches et le temps total +d'exécution devient $n \times D$, c'est-à-dire $\frac{1}{T} \times (N \times D)$, permettant donc +une accélération théorique de $T$. +Sinon, si $r \neq 0$ alors il existe au moins un \en{thread} qui exécute $n+1$ tâches où $n = +\left\lfloor \frac{N}{T} \right\rfloor$, donc $n = \frac{N-r}{T}$. +Le temps total d'exécution, toujours pour des durées égales entre toutes les tâches, devient +$(n+1) \times D$, c'est-à-dire $\frac{N+T-r}{T} \times D = \frac{N+T-r}{N\,T} \times (N \times D)$. +L'accélération théorique est alors égale à $\frac{N\,T}{N+T-r}$. +Elle tend vers $T$ quand $r$ s'approche de $T$ et est minimale lorsque $r = 1$. + +Par exemple, si l'on dispose de $K = 6$ cœurs et que l'on s'autorise à créer $T = 6$ \en{threads} +pour exécuter $N = 15$ tâches, on aura alors, comme représenté sur +\acref{fig:alsk/exec/executor/firstlevel}, $n = \left\lfloor \frac{15}{6} \right\rfloor$ donc $n = +2$ tâches exécutées au minimum par chacun des \en{threads}, et $r = 15 - 6 \times 2$ soit $r = 3$ +\en{threads} qui en exécutent une de plus. +Ainsi, \num{3} \en{threads} exécutent \num{3} tâches tandis que les \num{3} derniers n'exécutent que +\num{2} tâches. + +\begin{figure} + \ocgfigII{Exécution parallèle de tâches (1 niveau)} + {exec/executor/firstlevel} + {Exécution parallèle de \num{15} tâches sur \num{6} \en{threads}} + {tâches :}{\N}{12/off,13/off,14/off,15/on} + {; \en{threads} :}{\T}{5/off,6/on,7/off} +\end{figure} + +Si les tâches ne sont pas équilibrées en temps d'exécution, une telle procédure, sans connaissance +\textit{a priori} du déséquilibre (par exemple sous forme de poids attribué à chaque tâche), +produira potentiellement une répartition peu efficace sur les \en{threads}. +Si la connaissance \textit{a priori} ne peut être acquise, une répartition dynamique basée sur le +concept de \en{thread pool} permet généralement de répondre au besoin. +Nous verrons cependant que cette solution possède des inconvénients et induit un surcoût inévitable. + +Sur l'exemple précédent, il est possible de libérer le \en{thread} \num{5} en déplaçant les tâches +qui lui sont affectées sur les \en{threads} \num{3} et \num{4} sans que cela n'augmente le temps +d'exécution total (toujours dans l'hypothèse d'un temps d'exécution constant $D$). +Cela est représenté par \acref{fig:alsk/exec/executor/firstleveldense}. +En général, cette altération est possible lorsqu'il existe un reste $r \neq 0$ (de sorte qu'au moins +un \en{thread} soit inoccupé à la dernière ligne) et que $n \leqslant T-r-1$ (de sorte que les $n$ +tâches affectées au \en{thread} $T$ puissent être attribuées aux \en{threads} $r+1$ à $T-1$). +Pour cet exemple, appliquer cette modification permet seulement de consommer effectivement au +mieux un \en{thread} de moins, le laissant ainsi libre pour d'autres applications. +En pratique, cela peut également changer le temps d'exécution, l'améliorant (par exemple en +permettant de bénéficier d'une meilleure localité des données) ou le détériorant (par exemple si +certaines tâches se retrouvent bloquées en attente d'une ressource). +Ces variations étant très liées au programme concerné, c'est une décision qui doit être prise au cas +par cas, en profilant l'exécution. + +\begin{figure} + \ocgfigIB{Exécution parallèle dense de tâches (1 niveau)} + {exec/executor/firstleveldense} + {Exécution parallèle plus dense de \num{15} tâches sur \num{6} \en{threads}} + {tâches/\en{threads} :}{\N}{\T}{8/4/off,12/4/off,15/5/on} +\end{figure} + +Jusqu'ici, nous avons seulement envisagé un niveau de parallélisation possible. +Or, et c'est le cas justement du \graspels{} qui nous sert d'exemple, de nombreux algorithmes +offrent plusieurs niveaux de parallélisation. +\Acref{fig:alsk/exec/executor/firstleveldetails} reprend \acref{fig:alsk/exec/executor/firstlevel} +en détaillant les tâches. +On considère alors que chacune des $N = 15$ tâches exécute en fait $M = 2$ tâches internes qui sont +indépendantes et peuvent donc être exécutées en parallèle. +L'accélération théorique obtenue est de $\frac{15\;\!\times\;\!2\;\!\times D}{3\;\!\times\;\!2 +\times D} = 5$ en utilisant \num{6} \en{threads}. +Il apparaît alors que la solution proposée ci-avant semble pouvoir être améliorée. + +\begin{figure} + \ocgfigII{Exécution parallèle de tâches (2 niveaux)} + {exec/executor/firstleveldetails} + {Exécution parallèle de \num{15} tâches exécutant \num{2} tâches internes sur \num{6} \en{threads}} + {tâches :}{\N}{12/off,13/off,14/off,15/on} + {; \en{threads} :}{\T}{5/off,6/on,7/off} +\end{figure} + +En effet, en ayant connaissance du niveau de parallélisation interne, il est possible d'obtenir une +répartition des tâches semblable à ce que présente \acref{fig:alsk/exec/executor/multilevel}. +Ainsi, l'accélération théorique obtenue devient $\frac{15\;\!\times\;\!2\;\!\times D}{5\;\!\times D} += 6$. + +\begin{figure} + \ocgfigIB{Exécution parallèle dense de tâches (2 niveaux)} + {exec/executor/multilevel} + {Exécution parallèle améliorée de \num{15} tâches exécutant \num{2} tâches internes sur \num{6} \en{threads}} + {tâches/\en{threads} :}{\N}{\T}{5/2/off,6/4/off,10/4/off,9/6/off,15/6/on} +\end{figure} + +Pour généraliser, en partant de la répartition proposée initialement pour le premier niveau +(\cref{fig:alsk/exec/executor/firstlevel}), il faut améliorer la répartition des tâches \og +restantes \fg, c'est-à-dire celles qui ne complètent pas la dernière ligne, le cas échéant. +Il faut donc intervenir lorsque $r \neq 0$. +Il s'agit alors de déterminer combien de \en{threads} peuvent utiliser chacune des tâches restantes +pour l'exécution de leurs tâches internes. +Pour un équilibrage simple (où l'on affecte à toutes ces tâches autant que possible le même nombre +de \en{threads}), on peut déterminer $T^r$, le nombre de \en{threads} minimal par tâche, et un reste +$r' \in \llbracket 0, r\llbracket$, le nombre de tâches qui disposeront d'un \en{thread} +supplémentaire, ainsi : $T = r\,T^r + r'$. % TODO? cite hpcs2019 +Il faut donc affecter $T^r+1$ \en{threads} à $r'$ tâches et $T^r$ \en{threads} à $r-r'$ tâches, ce +qui utilise effectivement $(T^r+1)\,r' + T^r\,(r-r') = r\,T^r + r'$ \en{threads}, c'est-à-dire $T$. +Chaque tâche $i$ restante ayant à sa disposition $T^r_i > 1$ \en{threads} pourra procéder au même +calcul pour répartir ses tâches internes. + +Durant la compilation, un exécuteur peut détecter l'existence d'un niveau parallélisable au sein +d'une branche de l'arbre que représente le squelette. +En utilisant cette information, il lui est possible de ne pas affecter plusieurs \en{threads} à une +tâche si celle-ci ne peut pas en tirer profit. +La répartition des valeurs $T^r_i$ peut ainsi être adaptée en tenant compte de ce critère. +En utilisant en complément une information partiellement dynamique comme le nombre de tâches +exécutées par une branche de l'arbre, l'exécuteur peut équilibrer encore davantage les valeurs +$T^r_i$. + +En pratique, il n'est pas toujours judicieux d'utiliser une politique d'exécution parallélisant +plusieurs niveaux. +Prenons par exemple un cas où les deux premiers niveaux sont séparés par un os séquentiel répétant +l'exécution de sa tâche principale. +Si cet os séquentiel possède un très grand nombre d'itérations, le deuxième niveau parallélisable +sera instancié autant de fois, et causera, selon la politique d'exécution, la création de très +nombreux \en{threads}, la surcharge d'une \en{thread pool}, ... +Il n'existe pas une politique d'exécution idéale qui pourrait être utilisée en toutes circonstances. diff --git a/src/alsk/exec/4_impl.tex b/src/alsk/exec/4_impl.tex new file mode 100644 index 0000000..9641cdf --- /dev/null +++ b/src/alsk/exec/4_impl.tex @@ -0,0 +1,119 @@ +\subsection{Implémentation d'un exécuteur} + +%{{{ +Nous allons aborder ici le cas de deux politiques d'exécution : celui du \en{thread pool} et celui +de l'orchestration statique tenant compte de multiples niveaux de parallélisme. +Ces deux manières de procéder permettent d'explorer les deux aspects opposés d'un exécuteur : le +traitement effectué durant la compilation et la capacité à utiliser des informations dynamiques, +connues uniquement lors de l'exécution du programme. +%}}} + +%{{{ +\subsubsection{\en{Thread pool}} + +Le \en{thread pool} repose sur une file de tâches partagée par un ensemble de \en{threads} dont le +rôle est de traiter les tâches. +Tous les accès à cette file doivent être protégés par un \en{mutex} (voir +\acref{subsubsec:par/comp/thread/sync}). +Un exécuteur doit donc pouvoir accéder à un état partagé, lequel comporte la file de tâches et +implémente le \en{thread pool} en ayant une interface permettant, principalement, de soumettre des +tâches (\cref{fig:alsk/exec/impl/threadpool}). + +Au sein du \en{thread pool}, dès que l'un des \en{threads} du \en{pool} est disponible (parce qu'il +n'a pas encore traité de tâche ou qu'il en a terminé une), il essaie d'acquérir le \en{mutex} pour +utiliser la file. +Si la file n'est pas vide, le \en{thread} retire la plus ancienne tâche pour l'exécuter. +Sinon, il se met en attente et sera réveillé lorsque la file recevra une nouvelle tâche, après que +les éventuels autres \en{threads} auparavant en attente auront eux-mêmes été débloqués. + +\begin{figure} + \inputfig{exec/impl/threadpool} + {Schéma d'un \en{thread pool}} +\end{figure} + +Ce fonctionnement permet au \en{thread pool} d'équilibrer automatiquement et dynamiquement la charge +des \en{threads}. +En revanche, l'accès à la file des tâches, à laquelle accèdent tous les \en{threads}, cause des +sections critiques lors de l'exécution si de multiples \en{threads} deviennent disponibles +ou que des tâches doivent être soumises concomitamment. +Cela signifie que de très nombreuses tâches relativement courtes causeront un goulot d'étranglement, +affectant les performances et l'accélération qui peut être obtenue. + +À cela s'ajoute la perte de certaines optimisations due au fonctionnement interne d'un \en{thread +pool}. +Pour pouvoir conserver et utiliser les tâches, qui sont des fonctionoïdes, il faut que celles-ci +soient de même type car le conteneur (la file) est homogène. +Cela passe par l'effacement du type réel du fonctionoïde, ce qui induit une indirection dynamique de +l'appel. + +Certains exécuteurs peuvent avoir besoin d'un état partagé (pour le \en{thread pool}, il s'agit de +la file des tâches et de l'ensemble des \en{threads}). +Cet état est défini par une spécialisation d'un template de classe propre à chaque exécuteur. +Une instance de ce type est ensuite rendue accessible aux fonctions qui doivent être implémentées +par l'exécuteur (précisément, une référence sur une instance unique). +Cette séparation entre l'exécuteur lui-même et son état permet à la bibliothèque d'éviter +l'existence d'une référence commune de l'exécuteur et simplifie certains détails d'implémentation +internes. + +L'une de ces fonctions membres de la classe de l'exécuteur est \cppinline{executeParallel} et +correspond à l'exécution d'une tâche $n$ fois en parallèle. +Une définition simplifiée est donnée dans \acref{lst:alsk/exec/impl/threadpoolpar}. +On peut y observer l'accès à l'état de l'exécuteur (\cppinline{state}). +Les \og futures \fg{} (voir \acref{subsec:par/assist/abstractions}) sont créées par la fonction +\cppinline{run} servant à soumettre une nouvelle tâche à exécuter. +Ces \og futures \fg{} permettent de synchroniser la terminaison des tâches pour cette exécution. + +\begin{listing} + \inputlst{exec/impl/threadpoolpar} + {Implémentation rudimentaire de la fonction membre \cppinline{executeParallel} (par \en{thread pool})} +\end{listing} + +Pour cela, il faut attendre la réalisation de chaque \og future \fg. +Cependant, dans un contexte général où plusieurs niveaux de parallélisation peuvent exister, une +tâche peut elle-même ajouter un ou plusieurs autres éléments à la file des tâches. +Sans action particulière pour gérer ce cas, cela engendre un blocage du système. +Pour cela, si le \en{pool} possède $T$ \en{threads}, il suffit par exemple que $T$ tâches $A^i$, $i +\in \llbracket0, T\llbracket$ aient à exécuter elles-mêmes plus d'une (mettons $k$) tâche $a^i_j$, +$j \in \llbracket0, k\llbracket$. +Les $T$ tâches $A^i$ pouvant s'exécuter en même temps, il est possible qu'elles occupent déjà les +$T$ \en{threads} du \en{pool}. +Chacun des $T$ \en{threads} exécute une tâche $A^i$ et ajoute à la file des tâches ses $a^i_j$ +tâches puis se met en attente de leur terminaison. +Or, sans un mécanisme de préemption, puisqu'aucun \en{thread} n'est alors disponible, aucune des +tâches $a^i_j$ ne va s'accomplir et le système sera bloqué. +Pour cette raison, l'attente des \og futures \fg{} est effectué par une fonction \textit{ad hoc} du +\en{thread pool} (\cppinline{state.executor.wait}) qui résout ce problème en faisant travailler le +\en{thread} lorsque cela est nécessaire avant d'attendre les \og futures \fg. +Il s'agit de déterminer, avant de se mettre en attente, si la file de tâches ne contient aucune +tâche dont dépendrait une des \og futures \fg{} à attendre, ce qui peut être vérifié en pratique en +s'assurant que la file des tâches est vide. +Si elle ne l'est pas, le \en{thread} courant doit alors étendre sa durée de vie au sein du \en{pool} +et traiter une nouvelle tâche avant d'effectuer à nouveau le test susmentionné. +%}}} + +%{{{ +\subsubsection{Multi-niveau statique} +\label{subsubsec:alsk/exec/impl/static} + +Par \og statique \fg, pour une politique d'exécution, il est entendu que chaque tâche est associée à +un \en{thread} (ou l'équivalent s'il ne s'agit pas de parallélisation par \en{threads}) de manière +stable et dès la compilation. +Ceci offre différents avantages, notamment lorsqu'il s'agira de s'intéresser à la répétabilité dans +\acref{sec:alsk/repeatability}. + +En restant sur le concept de \en{pool}, un ensemble de \en{threads} peut être créé à l'avance et une +projection de l'identifiant de chaque tâche (voir \acref{subsec:alsk/exec/ident}) sur un numéro de +\en{thread} permet de faire une orchestration statique. +Alternativement, il est possible de produire dynamiquement le numéro de \en{thread} au sein de +l'exécuteur (la répartition reste définie durant la compilation). +Ces deux méthodes offrent différents avantages sur les caractéristiques de répartition des tâches, +ce qui nous sera à nouveau utile pour la répétabilité. + +Les \en{threads} peuvent également être créés au besoin. +Pour aller plus loin, il serait possible de créer les \en{threads} en définissant dès la compilation +leur déroulement au moment où l'on conçoit la fonction qu'ils exécuteront. +Celle-ci comporterait les appels de chaque tâche devant être traitée par un \en{thread} donné en +suivant une orchestration définie (par exemple celle représentée par +\acref{fig:alsk/exec/executor/multilevel}) et en utilisant des barrières de synchronisation lorsque +nécessaire. +%}}} diff --git a/src/alsk/repeat/0_intro.tex b/src/alsk/repeat/0_intro.tex new file mode 100644 index 0000000..f1ae316 --- /dev/null +++ b/src/alsk/repeat/0_intro.tex @@ -0,0 +1,68 @@ +Le fonctionnement de la recherche scientifique, du moins dans la forme actuelle de la méthode +scientifique expérimentale, repose sur plusieurs principes : la réfutabilité, la non-contradiction +et la reproductibilité~\autocite{ref:popper2005}. +Une expérience mène à des résultats, et ceux-ci doivent être interprétés pour obtenir une +conclusion. +En effectuant cela une seule fois, la conclusion est dépendante de nombreux paramètres : +variations dues à des effets aléatoires ou non considérés ; erreurs de manipulation, de mesure ; +biais auxquels peuvent être sujets les chercheurs ; fraude... +En faisant reproduire l'expérience, dans des contextes différents, par des personnes différentes, il +est possible de réduire les effets de ces paramètres et donc de valider la qualité du travail si la +même conclusion scientifique est atteinte. + +Ce critère de reproductibilité n'est cependant pas respecté systématiquement, et, dans certains +domaines, il a même été observé qu'un grand nombre de publications présentent des expériences ne +pouvant pas être reproduites~\autocite{ref:ioannidis2005}, au point que le problème est connu sous +le nom de \og crise de la reproductibilité \fg. +Le domaine de la recherche en informatique n'est pas épargné, mais celui-ci est inquiété par un +problème supplémentaire : le manque de répétabilité. + +Les deux concepts, reproductibilité et répétabilité, sont quelquefois +confondus~\autocite{ref:drummond2009} mais il est nécessaire de bien les distinguer. +Alors que la reproductibilité ne s'intéresse qu'à l'obtention d'une même conclusion à partir de +plusieurs itérations d'une expérience, celle-ci pouvant donc être faite dans des conditions +différentes, la répétabilité signifie l'obtention d'un même résultat lorsque les conditions sont +identiques. +En informatique, ces conditions représentent le contexte d'exécution. +Elles incluent le matériel, le système d'exploitation ou encore les bibliothèques logicielles +utilisées. + +La répétabilité est indispensable afin de pouvoir raisonner sur le comportement d'un programme lors +de son exécution : sans cela, il n'est par exemple même plus possible de déboguer et c'est tout +l'intérêt de l'automatisation informatique qui est remis en cause. +Dans le cas de programmes séquentiels, elle est en général assez facile à garantir, mais la +parallélisation a introduit de nouveaux problèmes. +Par exemple, l'exécution dynamique (voir \acref{subsubsec:par/auto/hardware/instruction}) peut +causer des variations dans les résultats numériques en nombres flottants à cause de la non +associativité des opérations associées, pouvant donc rendre l'exécution d'un programme non +répétable. +D'autres optimisations matérielles peuvent aboutir à cette non répétabilité, mais celles-ci ne +seront pas traitées dans cette thèse, qui propose des solutions logicielles au problème de la +répétabilité induite par l'introduction de la parallélisation. + +Les nombres aléatoires sont utilisés dans de nombreux domaines dont celui de la \gls{RO}. +Plus exactement, ce sont des nombres pseudo-aléatoires qui sont généralement utilisés : cela +signifie qu'ils sont générés par un algorithme déterministe, et qu'il est donc possible d'exécuter +plusieurs fois un programme avec la même séquence de nombres. +Pour cette raison, l'usage de nombre pseudo-aléatoires ne pose généralement pas de problème de +répétabilité. +Cependant, ce n'est plus le cas lorsqu'ils sont utilisés au sein d'un programme exécuté en +parallèle~\autocite{ref:hill2013}. +\Acref{fig:alsk/repeat/intro/unique_stream} présente ce qu'il peut arriver lors de l'utilisation +d'un flux de nombres pseudo-aléatoires partagé entre deux \en{threads} : selon l'exécution, les +nombres obtenus par un \en{thread} ne sont pas les mêmes. + +\begin{figure} + \inputfig{repeat/intro/unique_stream} + {Deux partages possibles par deux \en{threads} des nombres issus d'une séquence pseudo-aléatoire} +\end{figure} + +Il est possible d'assurer la répétabilité de plusieurs manières. +Celle-ci pourrait être limitée à l'obtention d'un résultat identique pour un degré de parallélisme +donné qui ferait alors partie du contexte d'exécution. +Cela ne suffirait cependant pas pour obtenir un programme scientifiquement valide, ce qui requiert +une répétabilité par rapport à une exécution séquentielle~\autocite{ref:hill2015}. +Pour cela, il est possible de considérer le degré de parallélisme comme ne faisant pas partie du +contexte d'exécution, la répétabilité devant donc en être indépendante. + +Cette section présente une solution, intégrée à notre bibliothèque de squelettes algorithmiques. diff --git a/src/alsk/repeat/1_repeat.tex b/src/alsk/repeat/1_repeat.tex new file mode 100644 index 0000000..8aa5a2f --- /dev/null +++ b/src/alsk/repeat/1_repeat.tex @@ -0,0 +1,143 @@ +\subsection{Garantir la répétabilité} + +L'accès à des nombres pseudo-aléatoires au sein de chaque tâche peut être fait : +\begin{enumerate} + \item en partageant un flux de nombres pseudo-aléatoires ; + \item en créant un flux par \en{thread} ; + \item en créant un flux par tâche exécutée en parallèle. +\end{enumerate} + +%{{{ première solution " +La première solution requiert un mécanisme de \en{mutex} et causera de manière pratiquement certaine +le résultat présenté dans \acref{fig:alsk/repeat/intro/unique_stream}. +En effet, même protégé par un \en{mutex} (qui, au passage, ralentit l'exécution du programme en +créant une section critique, qui diminue l'accélération qu'il est possible d'obtenir), l'accès au +flux de nombres pseudo-aléatoires n'est soumis à aucune restriction d'ordre. +Ainsi, un \en{thread} peut accéder en premier au flux durant une exécution mais pas durant une autre +exécution. +%}}} + +%{{{ deuxième solution " +La deuxième solution, en revanche, permet, sous certaines conditions, une répétabilité pour un degré +de parallélisme défini. +Ces conditions ne concernent que les tâches faisant usage de nombres pseudo-aléatoires (et par +extension celles dont elles sont dépendantes, mais ce n'est pas le propos ici). +Le terme de \og tâche \fg{} dans le paragraphe suivant est restreint à cette définition. + +% TODO thread pool citation --- move +D'une part, il faut que les tâches soient, d'une exécution à une autre, toujours exécutées par le +même \en{thread} (en identifiant un \en{thread} par le flux de nombres pseudo-aléatoires qui lui est +associé). +Ceci est difficile à garantir lorsque l'on utilise certains mécanismes d'orchestration comme le +\en{thread pool}~\autocite{ref:schmidt1998} où l'affectation d'une tâche à un \en{thread} est +décidée durant l'exécution du programme en fonction de l'ordre d'arrivée des tâches et de la +disponibilité des \en{threads}. +D'autre part, il faut que les tâches exécutées par un \en{thread} le soient toujours dans le même +ordre. +Sans cela, il est évident que les nombres obtenus vont différer entre les exécutions. + +De par le fonctionnement de notre bibliothèque, il est possible de conserver une affectation aux +\en{threads} et un ordre d'exécution précis pour chaque tâche (en particulier celles utilisant des +nombres pseudo-aléatoires), ce qui valide ces pré-requis lorsqu'une politique d'exécution compatible +est utilisée. +Cependant, comme nous visons une répétabilité indépendante du degré de parallélisme, cette solution +ne convient pas, puisqu'il s'agit d'associer un flux à un \en{thread}. +En utilisant un nombre variable de \en{threads}, certaines tâches qui auraient obtenu des nombres +depuis un flux obtiendraient des nombres différents depuis un autre flux, comme le montre l'exemple +de \acref{fig:alsk/repeat/repeat/parallel_streams} avec \num{1} et \num{2} \en{threads}. + +\begin{figure} + \inputfig{repeat/repeat/parallel_streams} + {Différence entre \num{1} et \num{2} \en{threads} ayant chacun leur séquence de nombres + pseudo-aléatoires} +\end{figure} +%}}} + +%{{{ troisième solution " +En fournissant un flux de nombres pseudo-aléatoires à chaque tâche, dernière des trois solutions, le +nombre de \en{threads} utilisés n'est plus pris en compte, rendant la répétabilité valable pour un +nombre quelconque de \en{threads}. +Pour la validité scientifique des résultats produits (et non seulement la répétabilité), il est en +plus nécessaire de construire des flux indépendants~\autocite{ref:hill2013}. + +Garantir la répétabilité, au niveau logiciel, dans le cadre de l'utilisation de nombres +pseudo-aléatoires au sein d'un programme exécuté en parallèle peut donc être accompli en créant un +nombre suffisant (c'est-à-dire au moins égal au nombre de tâches) de flux indépendants et en les +distribuant de sorte à ce qu'une tâche donnée accède toujours au même flux. +Le premier point est trivial dans notre cadre, puisque le nombre de tâches est connu, sans quoi une +limite haute devrait être définie. +La distribution des flux requiert deux mécanismes : l'identification des tâches et un moyen de +transmettre des informations à celles-ci. + +L'identification des tâches est déjà accomplie par la bibliothèque en interne (voir +\acref{subsec:alsk/exec/ident}). +Cette identification garantit que deux tâches ayant un même identifiant ont une dépendance et sont +donc nécessairement exécutées l'une après l'autre. +Grâce à cela, nous réduisons le nombre de flux nécessaire au nombre de tâches \og parallèles \fg +plutôt qu'au nombre de tâches total comme indiqué ci-avant. + +Le second mécanisme nécessaire est la transmission de données aux tâches. +Notre bibliothèque propose un système de liens (voir \acref{subsec:alsk/concept/links}) décrivant +les flux de données et fournissant deux templates de types particuliers permettant de représenter +les paramètres et les résultats des fonctions. +Ainsi, \cppinline{P} où \cppinline{i} est un entier naturel désigne le paramètre de la fonction +appelante d'indice \cppinline{i} et \cppinline{R} indique le résultat du muscle d'indice +\cppinline{i}. +Un troisième template s'ajoute à ceux-ci et permet la substitution de données contextuelles, +dépendantes de l'identifiant du muscle. +Concrètement, il s'agit d'un template nommé \cppinline{C} et qui fonctionne de manière similaire : +\cppinline{C} correspond à la donnée contextuelle d'indice \cppinline{i}. + +Le principe de cette nouvelle catégorie est de fournir un ou plusieurs éléments d'un n-uplet de +données aux tâches le requérant. +Le n-uplet est instancié autant de fois qu'il y a de tâches ($a \times b$ dans l'exemple du +\graspels{}), afin que chacune puisse avoir accès à une instance propre. +Ce n-uplet peut être défini par le développeur, ce qui permet donc son utilisation pour des besoins +plus étendus que le support de la répétabilité tel que nous le proposons. + +Le n-uplet fourni par défaut comporte ainsi deux informations : l'identifiant de la tâche et un +\gls{PRNG}. +Ainsi, \cppinline{C<0>} permet d'accéder à l'identifiant de la tâche tandis que \cppinline{C<1>} +permet d'obtenir une instance propre de \gls{PRNG}. + +Cependant, si une valeur numérique est adaptée pour la sélection d'une donnée dans un n-uplet, et +qu'elle est également compréhensible pour les deux templates \cppinline{P} et \cppinline{R}, elle +n'est en revanche pas efficace pour dénoter ce à quoi elle correspond dans ce cas. +En effet, si au sein du n-uplet par défaut, \num{1} correspond à l'indice du \gls{PRNG}, ce n'est +pas nécessairement toujours sa position s'il existe ce paramètre au sein d'un autre n-uplet +possible. +Dans tous les cas, il s'agit d'une notation très peu explicite, propice à l'introduction d'erreurs +et nécessitant donc probablement des commentaires à chaque utilisation. + +Plusieurs solutions sont possibles, par exemple en créant des valeurs nommées (\cppinline{constexpr +unsigned RNGId;}) pour les utiliser comme arguments du template \cppinline{C}. +Une seconde solution est de créer des alias pour des instances de ce template. +Ces deux solutions ne s'excluent pas, et nous avons fait le choix de proposer par défaut la seconde. +Ainsi, \cppinline{RNG} permet de signifier qu'un \gls{PRNG} est attendu comme paramètre. + +\Acref{lst:alsk/concept/links/grasp} (illustré par \acref{fig:alsk/concept/links/grasp}), présenté +comme code utilisant le système des liens lors de son introduction, correspond à la première +solution : un unique \gls{PRNG} partagé par toutes les tâches. +Le paramètre de type \gls{PRNG} provient de la fonction appelante et est transmis aux muscles. +Il est d'une part nécessaire d'utiliser un \en{mutex} au sein des muscles, augmentant le couplage +entre le code utilisateur et la parallélisation, mais d'autre part, comme nous l'avons vu, cela +induit une absence de répétabilité. + +À la place, en utilisant les paramètres contextuels du système de liens (dans ce cas +\cppinline{RNG}), on obtient \acref{lst:alsk/repeat/repeat/grasp} (illustré par +\acref{fig:alsk/repeat/repeat/grasp}). +La différence principale à observer dans ce nouveau code est que seuls les muscles utilisant +réellement les nombres pseudo-aléatoires ont le paramètre \cppinline{RNG}. +Chaque instance de ces muscles recevra automatiquement, grâce au système interne de la bibliothèque, +un flux de nombres pseudo-aléatoires adapté à sa situation. + +\begin{listing} + \inputlst{repeat/repeat/grasp} + {Liens du squelette d'un \glsxtrshort{GRASP} avec un paramètre contextuel} +\end{listing} + +\begin{figure}[h] + \inputfig{repeat/repeat/grasp} + {Transmission de données au sein d'un \glsxtrshort{GRASP} en utilisant les paramètres contextuels} +\end{figure} +%}}} diff --git a/src/alsk/repeat/2_opti.tex b/src/alsk/repeat/2_opti.tex new file mode 100644 index 0000000..a292a34 --- /dev/null +++ b/src/alsk/repeat/2_opti.tex @@ -0,0 +1,263 @@ +\subsection{Réduire le nombre de \glsxtrshort{PRNG} nécessaires} + +%{{{ +\subsubsection{Principe général} + +Créer un flux de nombres pseudo-aléatoires pour chaque tâche pouvant être exécutée en parallèle +d'une autre est une solution fonctionnelle. +Il peut s'agir, selon la politique d'exécution sélectionnée, de la seule envisageable pour garantir +la répétabilité indépendamment du nombre de \en{threads}. + +Cependant, en considérant les capacités réelles des machines sur lesquelles un programme est +exécuté, il est possible de faire mieux. +En effet, le nombre de cœurs des machines croît mais n'est pas infini. +De plus, en pratique, un programme possède une durée de vie limitée~\autocite{ref:tamai1992}. +Ainsi, il est logique de supposer une limite maximale du nombre de cœurs auxquels aura accès une +application lors de son développement. +Si le programme vient à être maintenu suffisamment longtemps pour que cette limite s'avère trop +basse, il reste possible de la mettre à jour, au coût d'une nouvelle compilation. + +Grâce à cette hypothèse, l'utilisateur peut inférer, à partir de la limite théorique de cœurs, un +nombre maximal $T$ de \en{threads} que le programme pourra utiliser. +C'est jusqu'à cette limite que notre bibliothèque devra assurer la répétabilité. +Lorsqu'un nombre arbitrairement grand de \en{threads} est possible, toute tâche parallélisable doit +être considérée comme exécutée en parallèle de toutes les autres. +Ceci contraint à associer à chacune un \gls{PRNG} propre. + +En revanche, si le nombre de \en{threads} est limité il peut arriver que certaines tâches soient, +selon la politique d'exécution, toujours exécutées par un même \en{thread}. +Dans ce cas, plutôt que d'associer à chaque tâche parallélisable un \gls{PRNG}, il s'agit de +déterminer les groupes de tâches qui sont toujours exécutées ensemble afin d'associer un \gls{PRNG} +à chaque groupe. + +Le cas général consiste alors à déterminer pour chaque quantité possible de \en{thread} $t$ +(typiquement dans l'intervalle $\llbracket 1, T\rrbracket$) les ensembles de tâches exécutées en +séquence pour finalement les combiner et en déduire les ensembles de tâches qui seront toujours +exécutées par le même \en{thread} quelque soit le nombre de \en{threads}. + +Afin d'expliquer le principe, considérons l'exemple suivant : \num{9} tâches $m_0$ à $m_8$ seront +réparties sur \num{1} à \num{4} \en{threads}. +La politique d'exécution utilisée pour cet exemple a pour objectif d'équilibrer la charge des +\en{threads} en leur affectant autant que possible le même nombre de tâches. +Ainsi, lorsque le nombre de \en{threads} alloués est de \num{2}, un ensemble $S_2$ de deux ensembles +de tâches est déduit et contient l'ensemble des tâches de $m_0$ à $m_4$ et l'ensemble des tâches de +$m_5$ à $m_8$. +\Acref{eq:alsk/repeat/opti/tasksets} décrit les ensembles $S_i$, où $i$, le nombre de \en{threads} +associés, prend ses valeurs dans l'intervalle $\llbracket1, 4\rrbracket$. + +\begin{equation} + \begin{aligned} + S_1 &= \{\{m_0,m_1,m_2,m_3,m_4,m_5,m_6,m_7,m_8\}\}\\ + S_2 &= \{\{m_0,m_1,m_2,m_3,m_4\},\{m_5,m_6,m_7,m_8\}\}\\ + S_3 &= \{\{m_0,m_1,m_2\},\{m_3,m_4,m_5\},\{m_6,m_7,m_8\}\}\\ + S_4 &= \{\{m_0,m_1,m_2\},\{m_3,m_4\},\{m_5,m_6\},\{m_7,m_8\}\}\\ + \end{aligned} + \label{eq:alsk/repeat/opti/tasksets} +\end{equation} + +En identifiant quelles tâches sont toujours exécutées ensemble, c'est-à-dire par un \en{thread} +commun et donc de manière séquentielle, quelle que soit la quantité de \en{threads} alloués, on +obtient l'ensemble $S$ de \acref{eq:alsk/repeat/opti/tasksetsmerged}. + +\begin{equation} + S = \{\{m_0,m_1,m_2\},\{m_3,m_4\},\{m_5\},\{m_6\},\{m_7,m_8\}\} + \label{eq:alsk/repeat/opti/tasksetsmerged} +\end{equation} + +Chacun des ensembles contenus dans cet ensemble $S$ contient donc des tâches qui ne seront jamais +exécutées en parallèle. +Dans cet exemple, il est donc possible de créer en tout \num{5} \gls{PRNG} $g_0$ à $g_4$ pour les +affecter à chacun des \num{5} sous-ensembles. +C'est-à-dire que $g_0$ sera utilisé par les tâches $m_0$, $m_1$ et $m_2$ ; $g_1$ par les tâches +$m_3$ et $m_4$ ; $g_2$ par la tâche $m_5$ ; $g_3$ par la tâche $m_6$ ; et enfin $g_4$ par les deux +dernières tâches $m_7$ et $m_8$. +Ainsi, quelque que soit le nombre de \en{threads} $t \in \llbracket 1, 4\rrbracket$, un même +\gls{PRNG} ne pourra jamais être utilisé par deux tâches qui s'exécutent en parallèle. + +Une méthode efficace pour déterminer $S$ est illustrée par +\acref{fig:alsk/repeat/opti/mergingtasksets}. +\begin{figure} + \inputfig{repeat/opti/mergingtasksets} + {Représentation de la fusion d'ensembles de tâches exécutées en séquence} +\end{figure} +Les quatre premières lignes correspondent à une représentation alternative des ensembles $S_1$ à +$S_4$ (\cref{eq:alsk/repeat/opti/tasksets}) et la dernière ligne représente le résultat de leur +fusion, $S$ (\cref{eq:alsk/repeat/opti/tasksetsmerged}). +Tels que sont construits les ensembles, il semble être suffisant de cumuler les \og séparations +\fg{} de chacun des ensembles $S_1$ à $S_4$ pour obtenir $S$. +En construisant les ensembles de ces séparations, leur union décrit alors l'ensemble des séparations +de $S$. +Dans cet exemple, ces ensembles de séparations, de $S'_1$ à $S'_4$, ressembleraient à $S'_1 = +\emptyset$, $S'_2 = \{(m_3,m_4)\}$, $S'_3 = \{(m_1,m_2),(m_4,m_5)\}$ et $S'_4 = +\{(m_1,m_2),(m_3,m_4),(m_5,m_6)\}$ (un couple $(m_i,m_{i+1})$ indiquant donc qu'une séparation +existe entre les tâches $i$ et $i+1$) et leur union donne +$S' = \{(m_1,m_2),(m_3,m_4),(m_4,m_5),(m_5,m_6)\}$. +%}}} + +%{{{ +\subsubsection{Démonstration} +\label{subsubsec:alsk/repeat/opti/demo} + +Afin de démontrer ceci, notons $N$ un entier non nul représentant le nombre de tâches. +Notons alors $I = \llbracket0, N\llbracket$ et $t$ le nombre de \en{threads} qu'il est possible +d'utiliser. + +\begin{thmdef} + on appellera subdivision de $I$ en $t$ parties tout $(t+1)$-uplet $s = (c_0, \dots, c_t)$ tel que + \[ + c_0 = 0 < c_1 < \dots < c_t = N. + \] +\end{thmdef} + +\begin{thmrq} + en notant $I_k = \llbracket a_k, a_{k+1}\llbracket$, on observe que la famille + $(I_k)_{0 \leqslant k \leqslant t-1}$ est une partition de $I$, c'est-à-dire + \begin{align*} + \bigcup_{k=0}^{t-1} I_k = I\\ + \forall i, j \in \llbracket0, t\llbracket, i \neq j \implies I_i \cap I_j = \emptyset. + \end{align*} +\end{thmrq} +\begin{thmrq} + on utilisera néanmoins le terme de subdivision et non celui de partition pour exprimer le fait que + les $I_k$ sont des intervalles. + Notons $\Sigma$ l'ensemble des subdivisions de $I$ en un nombre quelconque de parties, $e$ + l'application de $\Sigma$ dans $P(I)$ (l'ensemble des parties de $I$) qui à $s = (a_0, \dots, a_t) + \in \Sigma$ associe $e(s) = \{a_0, \dots, a_t\} \in P(I)$. + Cette application, qui est clairement bijective, permet d'induire sur $\Sigma$ une relation + d'ordre partielle définie par : + \[ + \forall (s, s') \in \Sigma^2, s \leqslant s' \iff e(s) \subset e(s'). + \] +\end{thmrq} +\begin{thmrq} + les autres relations de comparaison ($<$, $\geqslant$, $>$) se déduisent naturellement de la + définition précédente (par exemple $\forall (s, s') \in \Sigma^2, s < s' \iff e(s) \nsubseteq + e(s')$). +\end{thmrq} +\begin{thmrq} + si $s_1 > s_0$, on dit que $s_1$ est plus fine que $s_0$. +\end{thmrq} + +\begin{thmdef} + on dira que $m$ est un majorant de $S \subset \Sigma$, un ensemble de subdivisions d'un intervalle + entier, si et seulement si $\forall s \in S, s \leqslant m$. +\end{thmdef} + +\begin{thmex} + en prenant l'intervalle entier $I = \llbracket0, 6\rrbracket$ et $S = \{(0, 3, 4, 6), (0, 2, 3, + 6)\}$, les majorants de $S$ sont $(0, 1, 2, 3, 4, 5, 6)$, $(0, 2, 3, 4, 5, 6)$, $(0, 1, 2, 3, 4, + 6)$ et $(0, 2, 3, 4, 6)$. +\end{thmex} + +\begin{thmdef} + on appellera borne supérieure d'un ensemble de subdivisions le plus petit de ses majorants. +\end{thmdef} + +\begin{thmex} + en poursuivant avec l'exemple précédent, $(0, 2, 3, 4, 6)$ est la borne supérieure de $S$. +\end{thmex} + +Définissons maintenant dans $\Sigma$ une loi de composition interne $\vee$ définie par +\[ + \forall (s, s') \in \Sigma^2, s \vee s' = e^{-1}(e(s) \cup e(s')). +\] + +\begin{thmrq} + en termes simples, $s \vee s'$ s'obtient en ordonnant la liste réunissant les termes de $s$ et + $s'$. +\end{thmrq} + +\begin{thmex} + ainsi, $(0, 2, 6) \vee (0, 2, 3, 4) = (0, 2, 3, 4, 6)$. +\end{thmex} + +\begin{thmlemme} + $\vee$ est associative. +\end{thmlemme} + +\begin{thmproof} + l'associativité de $\vee$ découle de l'associativité de l'union et de la bijectivité de $e$. +\end{thmproof} + +\begin{thm} + soit $S = \{s_1, \dots, s_n\} \subset \Sigma$ un ensemble de subdivisions d'un intervalle entier. + La borne supérieure de $S$ est $b = \bigvee_{i=1}^n s_i$. +\end{thm} + +\begin{thmproof} + par définition, on a $e(b) = \bigcup_{i=1}^n e(s_i)$. + Donc, $\forall j \in \llbracket1, n\rrbracket, e(s_j) \subset e(b)$. + Il en découle que $\forall j \in \llbracket1, n\rrbracket, s_j \leqslant b$, et donc que $b$ est + un majorant de $S$. + + Supposons alors qu'il existe $b' \in \Sigma$, tel que $b'$ soit un majorant de $S$, avec $b' < b$. + Pour tout $s_i \in S$, notons plus précisément $s_i = (c_{i,0}, \dots, c_{i,t_i})$ et $b' = (c'_0, + \dots, c'_{p'})$. + Puisque $b' < b$, on a $e(b') \nsubseteq e(b)$, donc il existe $s_k \in S$ et $c_{k,l} \in e(s_k)$ + tel que $c_{k,l} \in e(b)$ et $c_{k,l} \notin e(b')$. + + Par conséquent, $\forall j \in \llbracket1, p'\rrbracket$, $c'_j \neq c_{k,l}$, et on ne peut donc + pas avoir $s_k \leqslant b'$, ce qui est une contradiction avec le fait que $b'$ soit un majorant + de $S$. + + L'hypothèse $b' < b$ est donc absurde et, en conclusion, $b$ est le plus petit majorant de $S$, + c'est-à-dire sa borne supérieure. +\end{thmproof} +%}}} + +%{{{ +\subsubsection{Conclusion} + +La conclusion de cette démonstration est qu'une subdivision en sous-ensembles de tâches qui, quel +que soit le nombre de \en{threads} utilisés, seront exécutées par un même \en{thread}, peut être +trouvée en appliquant et en généralisant la méthode algorithmique expliquée précédemment et +schématisée dans \acref{fig:alsk/repeat/opti/mergingtasksets}. +Si la subdivision trouvée, notée $s$, comporte $p$ parties, alors il faudra associer à chaque +identifiant de tâche $id$ un identifiant de contexte $idx \in \llbracket 0, p-1\rrbracket$, toutes +les tâches ayant le même identifiant de contexte $idx$ étant exécutées par un même \en{thread}. + +La conversion d'un identifiant $id$ d'une tâche en un identifiant de contexte $idx$ est une +opération qui nécessite de trouver le premier élément de $s$ supérieur à $id$, $idx$ étant alors +égal à sa position dans $s$ moins un. +Par exemple, si $s = (0, 2, 4, 5, 6, 9)$ (cela correspond à +\acref{eq:alsk/repeat/opti/tasksetsmerged}), \acref{tab:alsk/repeat/opti/ctxid} liste les +conversions d'indice. +En terme de code, $s$ peut être conservé sous la forme d'un \cppinline{std::set} (un ensemble +ordonné) et la conversion peut être effectuée par la fonction définie dans +\acref{lst:alsk/repeat/opti/ctxid} (complexité globale linéaire en la taille de $s$). + +\begin{table} + \centering + \caption{Conversions d'identifiants de tâche en identifiants de contexte} + \label{tab:alsk/repeat/opti/ctxid} + + \begin{tabular}{|l|l|l|l|} + identifiant de tâche & borne supérieure & position & identifiant de contexte \\ + 0 & 2 & 1 & 0 \\ + 1 & 2 & 1 & 0 \\ + 2 & 2 & 1 & 0 \\ + 3 & 4 & 2 & 1 \\ + 4 & 4 & 2 & 1 \\ + 5 & 5 & 3 & 2 \\ + 6 & 6 & 4 & 3 \\ + 7 & 9 & 5 & 4 \\ + 8 & 9 & 5 & 4 \\ + \end{tabular} +\end{table} + +\begin{listing} + \inputlst{repeat/opti/ctxid} + {Conversion d'un identifiant de tâche en identifiant de contexte} +\end{listing} + +Tout exécuteur, devant fournir cette fonction, doit construire la subdivision $s$ correspondant à ce +qu'il devra exécuter et aux contraintes données par l'utilisateur quant aux nombres de \en{threads} +devant être supportés pour la répétabilité. +Cette subdivision découle de $\bigvee_{t \in \mathbf{T}} s_t$ (notation introduite dans +\acref{subsubsec:alsk/repeat/opti/demo}), où $\mathbf{T}$ est l'ensemble des nombres de \en{threads} +possibles et les $s_t$ sont les subdivisions obtenues pour chaque valeur $t \in \mathbf{T}$. +En C++, l'opération $\vee$ est implémentée par une simple union des \cppinline{std::set}. +En pratique, le résultat de l'union est automatiquement obtenu en insérant au fur et à mesure les +éléments de la subdivision : le fonctionnement de \cppinline{std::set} (qui représente la notion +d'ensemble) fait que si l'élément est déjà présent, il n'est pas ajouté à nouveau. +%}}} diff --git a/src/alsk/repeat/3_results.tex b/src/alsk/repeat/3_results.tex new file mode 100644 index 0000000..66598ec --- /dev/null +++ b/src/alsk/repeat/3_results.tex @@ -0,0 +1,52 @@ +\subsection{Évaluation du nombre de \glsxtrshort{PRNG} créés} + +Grâce à la technique expliquée précédemment, le nombre de \gls{PRNG} en particulier, mais plus +généralement le nombre de n-uplets pour le contexte, qu'il est nécessaire d'instancier est réduit. +\Acref{fig:alsk/repeat/opti/lineartt} montre les quantités nécessaires sans et avec optimisation +selon différentes politiques d'exécution pour une valeur de $T = 64$ et pour $N$ variant entre +\num{1} et \num{1000}. +Les deux politiques d'exécution utilisées ont le même objectif (parallélisation multi-niveau du +squelette algorithmique) mais diffèrent sur leur manière de répartir les tâches sur les \en{threads} +à leur disposition, l'une correspondant globalement à la méthode décrite dans +\acref{subsec:alsk/exec/executor}, l'autre optant pour une alternative \og gloutonne \fg{} donnant +théoriquement des résultats similaires lorsque la durée des tâches est elle aussi similaire. +On observe que le choix d'une politique d'exécution peut donc également être motivé par le nombre de +\gls{PRNG} que celle-ci implique. + +\begin{figure} + \centering + \includegraphics{img/alsk/lineartt.pdf} + \caption{Nombre de \glsxtrshort{PRNG} pour deux politiques d'exécution avec \num{1} à \num{1000} + tâches et pour un nombre de \en{threads} dans $\llbracket1, 64\rrbracket$} + \label{fig:alsk/repeat/opti/lineartt} +\end{figure} + +L'ensemble des nombres de \en{threads} pour lesquels il faut garantir la répétabilité est de +définition libre : l'utilisateur peut indiquer par exemple spécifiquement $\{3, 111\}$. +En pratique, l'ensemble des valeurs de \num{1} jusqu'à une borne supérieure semble assez commun. +Cependant, un autre motif est fréquent : l'ensemble des puissances de \num{2} jusqu'à une limite. +En utilisant de tels ensembles, le nombre d'instances du n-uplet contextuel décroît encore. +\Acref{fig:alsk/repeat/opti/exptt} montre les quantités nécessaires de ce n-uplet sans et avec +optimisation pour une même politique d'exécution pour une valeur de $N \in \llbracket1, +1000\rrbracket$ et $T = 64$. +Dans un des deux cas, $\mathbf{T} = \llbracket1, 64\rrbracket$, tandis que dans l'autre cas, +$\mathbf{T} = 2^{\llbracket0, 6\rrbracket}$. +Avec la seconde définition de $\mathbf{T}$, on observe que le nombre de \gls{PRNG} semble posséder +une valeur maximale indépendante de $N$. + +\begin{figure} + \centering + \includegraphics{img/alsk/exptt.pdf} + \caption{Nombre de \glsxtrshort{PRNG} pour une politique d'exécution avec \num{1} à \num{1000} tâches et + pour un nombre de \en{threads} dans $\llbracket1, 64\rrbracket$ et $2^{\llbracket0, + 6\rrbracket}$} + \label{fig:alsk/repeat/opti/exptt} +\end{figure} + +Il est encore possible d'améliorer ces résultats. +Notamment, lorsque les différents paramètres spécifiques au contexte (\cppinline{C}) ne sont pas +utilisés du tout, il semble évident qu'aucune instance du n-uplet contextuel n'est nécessaire. +Or, grâce au système de liens, dont l'information est connue durant la compilation, la détection des +conditions de ce critère est justement permise. +Plus généralement, il est possible d'analyser l'arbre du squelette pour ignorer dans le calcul toute +branche dont aucune feuille (les muscles) ne fait usage d'un paramètre contextuel. diff --git a/src/alsk/usage/1_edsl.tex b/src/alsk/usage/1_edsl.tex new file mode 100644 index 0000000..71dd20b --- /dev/null +++ b/src/alsk/usage/1_edsl.tex @@ -0,0 +1,239 @@ +\subsection{Langage dédié} +\label{subsec:alsk/usage/edsl} + +%{{{ +L'interface proposée par notre bibliothèque, telle que présentée jusqu'ici, requiert la définition +dans un premier temps, d'une part, de la structure du squelette, et d'autre part des liens qui +seront ensuite combinés à la structure pour obtenir un squelette. +Dans un second temps, les muscles sont ajoutés pour former un corps qui pourra être implémenté selon +une politique d'exécution et enfin exécuté. +Cette manière de faire offre l'avantage de séparer naturellement la structure et les liens, de sorte +que leur réutilisation au sein d'autres squelettes est directe. + +Cette section introduit un \gls{EDSL}, proposé par la bibliothèque, comme alternative à cette +première interface. +Le principe général de ce langage dédié est de construire les corps à partir des muscles, la +structure du squelette étant déterminée par la manière de combiner ces muscles au moyen de fonctions +dédiées. + +Sans cet \gls{EDSL}, pour construire un \gls{GRASP} il faut définir sa structure (voir +\acref{lst:alsk/concept/struct/grasp}) et ses liens (voir \acref{lst:alsk/concept/links/grasp}) pour +les combiner (voir \acref{lst:alsk/concept/inst/grasp}) et enfin associer à ce squelette des muscles +(voir \acref{lst:alsk/concept/inst/graspelsbody}) pour former un corps. +En utilisant cet \gls{EDSL}, toutes ces étapes se traduisent par ce qui est fait dans +\acref{lst:alsk/usage/edsl/inlinegrasp} +Les types \cppinline{Init}, \cppinline{LS} et \cppinline{Select} correspondent à des muscles. + +\begin{listing} + \inputlst{usage/edsl/inlinegrasp} + {Définition d'un \glsxtrshort{GRASP} utilisant un \glsxtrshort{EDSL}} +\end{listing} + +Cette section a pour objectif d'expliquer les différents éléments de cet \gls{EDSL} (le template +\cppinline{link} ou encore les opérateurs utilisés, par exemple). +Au sein de ce langage, les opérandes (dans le code : \cppinline{init}, \cppinline{ls} et +\cppinline{select}) représenteront les muscles qui seront utilisés (respectivement \cppinline{Init}, +\cppinline{LS} et \cppinline{Select}). +Puisqu'il ne s'agit pas directement de ces muscles mais de variables les représentant au sein du +langage, le terme de pseudo-muscle sera employé pour les désigner. +Ces opérandes seront utilisés dans des expressions qui permettront de : les annoter afin de définir +les liens (ce à quoi sert le template \cppinline{link}) ; les combiner pour obtenir la +représentation du futur squelette ; ... +De la même manière que pour le cas des pseudo-muscles, le terme de pseudo-corps correspondra à +la représentation d'un corps dans l'\gls{EDSL}. +Plus généralement, le préfixe \og pseudo- \fg{} sera ajouté, dans cette section, devant un terme +pour indiquer qu'il s'agit d'une représentation au sein du langage dédié. +Dans \acref{lst:alsk/usage/edsl/inlinegrasp}, on définit donc précisément un pseudo-corps de +\gls{GRASP}. +%}}} + +Les opérandes terminaux (pseudo-muscles) sont créés à partir d'un muscle. +\Acref{lst:alsk/usage/edsl/makeoperand} présente la syntaxe la plus simple créant un opérande à +partir du muscle \cppinline{LocalSearch}. + +\begin{listing} + \inputlst{usage/edsl/makeoperand} + {Définition d'un opérande à partir d'un muscle} +\end{listing} + +Dès lors que des pseudo-muscles sont définis, ils peuvent être utilisés pour créer des +pseudo-corps. +À noter que ce qui est généré est bien directement un pseudo-corps, l'accès au squelette +(précisément à la structure et aux liens) se fait au travers de ce pseudo-corps, sujet abordé +ultérieurement. + +Pour chaque os est fournie une fonction spécifique et éventuellement un ensemble d'opérateurs +propres pour la construction de pseudo-corps à partir d'opérandes du langage (c'est-à-dire soit +un pseudo-muscle, soit un pseudo-corps). +Par exemple, l'os \cppinline{Serial} fournit la fonction \cppinline{serial} et l'opérateur +équivalent \cppinline{operator,} tandis que l'os \cppinline{Farm} met à disposition la fonction +\cppinline{farm} et l'opérateur unaire équivalent \cppinline{operator*}. +\Acref{lst:alsk/usage/edsl/structures} présente la définition de pseudo-corps à un seul os pour +quelques structures. + +\begin{listing} + \inputlst{usage/edsl/structures} + {Définition de pseudo-corps à un seul os pour différentes structures} +\end{listing} + +Certains os n'étant capables de produire que des comportements séquentiels, ils peuvent être +construits à partir de leur homologue parallélisable au moyen d'une fonction indiquant qu'il s'agit +de la version séquentielle qui doit être produite. +Ainsi, l'os correspondant à $n$ exécutions en séquence d'une tâche (\cppinline{Loop}) s'obtient, +comme montré dans \acref{lst:alsk/usage/edsl/loop}, à partir d'un pseudo-corps (utilisant l'os +\cppinline{Farm}), lui-même créé de la même manière qu'à la +ligne~\ref{line:alsk/usage/edsl/structures:farm} de \acref{lst:alsk/usage/edsl/structures}. + +\begin{listing} + \inputlst{usage/edsl/loop} + {Définition d'un pseudo-corps utilisant l'os \cppinline{Loop}} +\end{listing} + +Lors de la création de pseudo-corps, il est possible d'associer des liens à un opérande (à nouveau, +pseudo-muscle ou pseudo-corps). +Pour cela, plusieurs outils sont fournis : un pour associer les liens dès la création d'un +pseudo-muscle et un autre pour associer les liens \textit{a posteriori} qui peut donc s'appliquer +sur un pseudo-corps quelconque. +Les signatures qui sont utilisées comme arguments fonctionnent à l'identique de ce qui est expliqué +dans \acref{subsec:alsk/concept/links}. +Par exemple, pour définir, dans ce langage, les mêmes liens pour la tâche de recherche locale à la +ligne~\ref{line:alsk/concept/links/grasp:ls} de \acref{lst:alsk/concept/links/grasp}, +il est possible d'utiliser les syntaxes présentées par +\acref{lst:alsk/usage/edsl/links,lst:alsk/usage/edsl/indirlinks}. + +\begin{listing} + \inputlst{usage/edsl/links} + {Affectation directe de liens à un pseudo-corps} +\end{listing} + +\begin{listing} + \inputlst{usage/edsl/indirlinks} + {Affectation indirecte de liens à un pseudo-corps} +\end{listing} + +L'affectation directe, soit par la syntaxe de construction d'opérande (comme pour +\cppinline{direct}), soit par la syntaxe alternative (comme pour \cppinline{alt}), peut être +utilisée lorsque l'on sait à l'avance comment le pseudo-corps produit sera utilisé. + +La syntaxe indirecte permet de définir localement la signature à employer pour un pseudo-corps +existant (et ayant éventuellement déjà une signature associée) : dans +\acref{lst:alsk/usage/edsl/indirlinks}, on affecte à \cppinline{localSearch} de nouveaux liens (la +variable \cppinline{indirect} conservant le resultat de cette opération). + +Certaines circonstances permettent la déduction d'une partie de la signature d'un pseudo-corps. +C'est le cas lorsque l'on produit celui d'un \cppinline{FarmSel} comme dans +\acref{lst:alsk/usage/edsl/farmsel}. +Ce mécanisme permet de réduire le nombre de cas où l'application explicite de liens est nécessaire. + +\begin{listing} + \inputlst{usage/edsl/farmsel} + {Définition d'un pseudo-corps utilisant l'os \cppinline{FarmSel}} +\end{listing} + +Pour la création de \cppinline{farmSel}, un premier pseudo-corps est construit à partir du +pseudo-muscle \cppinline{task}, en utilisant l'\cppinline{operator*}, et correspond donc à une +\cppinline{Farm} (on aurait d'ailleurs pu écrire \cppinline{farm(task)} au lieu de +\cppinline{*task}). +À ce pseudo-corps est associé la signature \cppinline{Auto(Problem const&)} : un paramètre doit être +attendu (de type \cppinline{Problem const&}) et le type de retour (\cppinline{Auto}) sera +automatiquement remplacé. + +En effet, ce pseudo-corps de \cppinline{Farm} est ensuite utilisé pour construire un +pseudo-corps de \cppinline{FarmSel} par l'utilisation de l'\cppinline{operator->*}. +Or le squelette \cppinline{FarmSel} contraint le type de retour : le type de retour global est le +même que celui de l'opération de sélection (ici, \cppinline{select}). +Cela permet l'inférence de ce type de retour dans le cas des pseudo-corps, et donc l'utilisation de +ce type \cppinline{Auto}. +Une syntaxe alternative à l'\cppinline{operator->*} est permise par une fonction membre +\cppinline{select} acceptant en paramètre le pseudo-corps de sélection. +Dans les deux cas, le pseudo-corps produit est celui d'un \cppinline{FarmSel} dont la signature +est par défaut telle que son type de retour soit celui du pseudo-corps de sélection et ses +paramètres ceux du pseudo-corps de \cppinline{Farm}. +Ainsi, si les signatures sont respectivement \cppinline{FarmRet(FarmArgs...)} et +\cppinline{SelectRet(SelectArgs...)}, la signature résultante sera +\cppinline{SelectRet(FarmArgs...)}. +Dans le cas ci-avant, la signature déduite est donc \cppinline{Solution(Problem const&)}. + +Pour construire l'expression d'un \graspels{}, c'est-à-dire le pseudo-corps correspondant au +corps (pour rappel, squelette et muscles) de \acref{lst:alsk/concept/inst/graspelsbody}, nous +utilisons donc les différents outils présentés jusqu'ici (voir +\acref{lst:alsk/usage/edsl/graspelsbody}). +Bien qu'il soit possible de le faire en une seule expression, notamment pour des raisons de +lisibilité, nous définissons dans un premier temps l'expression d'un \gls{ELS} pour l'utiliser +ensuite au sein d'un \gls{GRASP}. + +\begin{listing}[t] + \inputlst{usage/edsl/graspelsbody} + {Définition d'un pseudo-corps de \graspels{}} +\end{listing} + +Le pseudo-corps \cppinline{els} est écrit dans un premier temps pour fonctionner tel quel : les +liens établis (\cppinline{R<1>(Solution)}) permettent son utilisation en l'état. +Cependant, il est ensuite utilisé comme pseudo-muscle de \cppinline{graspels}, contexte nécessitant +un changement de liens (\cppinline{R<1>(R<0>)}) puisque la solution à améliorer provient alors du +muscle précédent. +Dans ce cas précis, il n'est donc pas utile de spécifier les liens initiaux de \cppinline{els}, +cependant le faire permet plus de flexibilité quant à son utilisation en général. + +La fonction \cppinline{box} permet d'empêcher certaines optimisations automatiques prématurées de +l'arbre construit (ici la suppression du \cppinline{Serial} correspondant à l'\gls{ELS}). +Dans cet exemple, cela permet d'éviter de devoir faire soi-même l'analyse des liens corrects à +employer. +Notamment, le retour \cppinline{R<1>} devrait être \cppinline{R<2>} avec l'aplatissement de l'arbre. +Cette optimisation est ainsi reportée, ce qui permet au système de procéder lui-même à la correction +des liens puisqu'il possèdera alors une représentation complète et non seulement locale. + +Bien que l'on puisse entièrement se reposer sur ce seul \gls{EDSL} pour utiliser notre bibliothèque, +la conception de squelettes algorithmiques réutilisables (typiquement pour les mettre à disposition +d'autres développeurs) requiert de définir des types ou des templates plutôt que des variables. +C'est-à-dire qu'il faut pour cela utiliser des squelettes et non des pseudo-corps, composés de +structures et de liens, tels que présentés initialement. +Pour extraire le squelette d'un pseudo-corps, il faut l'assembler à partir de la structure et des +liens qui sont produits par ce pseudo-corps. +À cet effet, une fonction utilitaire est fournie (voir \acref{lst:alsk/usage/edsl/getskeleton}). +Si l'on dispose déjà d'un pseudo-corps comme celui de \acref{lst:alsk/usage/edsl/graspelsbody}, +cette fonction peut être employée ainsi : \cppinline{decltype(getSkeleton(graspels))}. + +\begin{listing} + \inputlst{usage/edsl/getskeleton} + {Fonction \cppinline{getSkeleton} permettant de convertir un pseudo-corps en squelette} +\end{listing} + +Par \gls{TAD} (voir \acref{sec:gnx/tad}), le type \cppinline{Expression} est déduit lors de l'appel +à partir de l'argument qui est passé à la fonction. +Si ce type possède effectivement les types membres \cppinline{Struct} et \cppinline{Links} (c'est le +cas des pseudo-corps), le type de retour de la fonction est déterminé comme étant le squelette +construit à partir de ces deux éléments. +La fonction n'est jamais définie : son utilisation doit être faite dans un contexte non évalué, +c'est-à-dire que l'appel ne sera utilisé par le compilateur que pour déterminer le type de retour de +la fonction. +Un exemple de tel contexte est l'argument d'un \cppinline{decltype} (voir \acref{sec:gnx/auto}). + +Les squelettes pour le \gls{GRASP} et l'\gls{ELS} sont respectivement implémentés par +\acref{lst:alsk/usage/edsl/grasp} et \acref{lst:alsk/usage/edsl/els}. +Pour chaque cas, cela inclut donc la définition de la structure, des liens et la construction du +squelette les utilisant (pour le \gls{GRASP}, cela correspond, en n'utilisant pas l'\gls{EDSL}, +respectivement à \acref{lst:alsk/concept/struct/grasp}, \acref{lst:alsk/concept/links/grasp} et +\acref{lst:alsk/concept/inst/grasp}). + +\begin{listing} + \inputlst{usage/edsl/grasp} + {Squelette d'un \glsxtrshort{GRASP} à partir d'un pseudo-corps} +\end{listing} + +\begin{listing} + \inputlst{usage/edsl/els} + {Squelette d'un \glsxtrshort{ELS} à partir d'un pseudo-corps} +\end{listing} + +Cette syntaxe permet une plus grande expressivité et une définition plus dense d'un squelette par +rapport aux relativement nombreuses étapes nécessaires lorsque l'on passe par la structure et les +liens, puis enfin le squelette. +L'utilisation d'une lambda permet de créer des éléments intermédiaires et donc de rendre plus +lisible l'expression du pseudo-corps, ainsi que d'alléger l'écriture en s'autorisant +l'utilisation de \cppinline{using namespace} pour les espaces de noms de la bibliothèque en en +limitant efficacement la portée lexicale. +Les fonctions lambda ne peuvent être utilisées dans un contexte non évalué que depuis le standard +C++20. +Sans celui-ci il est possible, bien que cela nécessite davantage de code, d'utiliser à la place une +fonction nommée. diff --git a/src/alsk/usage/2_impl.tex b/src/alsk/usage/2_impl.tex new file mode 100644 index 0000000..2504d54 --- /dev/null +++ b/src/alsk/usage/2_impl.tex @@ -0,0 +1,95 @@ +\subsection{Implémentation de l'algorithme représenté par un corps} + +L'implémentation de l'algorithme que représente un squelette instancié (un corps) est la pénultième +étape avant son exécution. +Celle-ci consiste en la création d'un fonctionoïde dont l'interface principale est +l'\cppinline{operator()} acceptant comme arguments ceux décrits par les liens de l'os le plus +externe du squelette concerné. +Cette opération se déroule donc en partie durant la compilation. + +Pour obtenir une implémentation de l'algorithme, il faut fournir au moins le squelette instancié y +correspondant et la méthode d'exécution souhaitée (séquentielle, parallèle, ...) (voir +\acref{sec:alsk/exec}). +\Acref{lst:alsk/usage/impl/graspels} montre comment l'implémentation est obtenue. +Le premier argument de la fonction \cppinline{implement}, dans ce cas \cppinline{DynamicPool}, +correspond au template d'une politique d'exécution que l'on veut utiliser : on aurait pu mettre +\cppinline{Sequential}, \cppinline{StaticPool}, ... + +\begin{listing} + \inputlst{usage/impl/graspels} + {Implémentation de \graspels{} à partir d'un corps} +\end{listing} + +La variable \cppinline{graspEls} correspond alors à un fonctionoïde et l'appeler exécutera +l'algorithme décrit par le squelette algorithmique correspondant. +Cependant, en l'état, il manque les informations purement dynamiques telles que le nombre +d'itérations que doivent effectuer les os comme \cppinline{FarmSel} ou encore le nombre de +\en{threads} qui peuvent être utilisés. + +Ces données peuvent être renseignées comme cela est fait dans +\acref{lst:alsk/usage/impl/graspels_conf}. +La ligne~\ref{line:alsk/usage/impl/graspels_conf:n} configure le nombre d'itérations de la +boucle du \gls{GRASP}. +Les paramètres du squelette sont tous contenus dans le membre \cppinline{skeleton}, et les noms +sont définis par les os eux-mêmes. + +\begin{listing} + \inputlst{usage/impl/graspels_conf} + {Configuration du \graspels{} instancié (\cref{lst:alsk/usage/impl/graspels})} +\end{listing} + +La ligne~\ref{line:alsk/usage/impl/graspels_conf:taskn} est un peu plus difficile à lire. +Le premier os étant un \cppinline{FarmSel}, il possède plusieurs membres, notamment \cppinline{n} +que l'on a déjà vu, mais aussi \cppinline{task} qui réfère au muscle exécuté dans la boucle. +Celui-ci étant, dans ce cas, un os \cppinline{Serial}, il nous permet d'accéder aux différentes +tâches qu'il exécute : la deuxième (d'indice \cppinline{1}) correspond ici au cœur de cet +algorithme, la recherche locale implémentée par un \gls{ELS}. +Cet \gls{ELS} étant implémenté par une séquence de deux tâches, la seconde correspondant à un +\cppinline{IterSel}, cette ligne en modifie donc le nombre d'itérations. + +La suivante, la ligne~\ref{line:alsk/usage/impl/graspels_conf:tasktaskn}, suit le même principe pour +atteindre le \cppinline{FarmSel} au sein de l'\gls{ELS} afin d'en définir son nombre d'itérations. + +La ligne~\ref{line:alsk/usage/impl/graspels_conf:execcores} ne concerne pas le squelette mais +l'exécuteur. +Elle indique le nombre de cœurs disponibles (par défaut, la bibliothèque essaie de détecter +automatiquement le nombre de cœurs de la machine et utilise cette valeur). + +Enfin, la ligne~\ref{line:alsk/usage/impl/graspels_conf:repeat} permet de définir un ensemble de +valeurs pour lesquelles il faut garantir la répétabilité. +Le membre \cppinline{repeatability} comporte un ensemble de valeurs qui peut être rempli directement +ou au moyen de fonctions qui permettent de le remplir en suivant un certain motif. +La fonction \cppinline{upTo} insère toute valeur comprise entre deux bornes en avançant +par incréments successifs d'un certain pas (par défaut, de \num{1} à un maximum par pas de \num{1}). +La fonction utilisée dans l'exemple, \cppinline{expUpTo}, agit semblablement mais utilise son pas de +manière multiplicative. +Dans cet exemple, le pas par défaut étant de \num{2} et la valeur initiale \num{1}, l'ensemble des +nombres insérés sera $\{1, 2, 4, 8, 16, 32, 64\}$. +Il est important de ne pas faire dépendre cet ensemble de la variable +\cppinline{graspEls.executor.cores} : celle-ci est possiblement amenée à changer d'une exécution à +une autre, la répétabilité doit alors être conservée. + +En utilisant le langage dédié introduit précédemment, les paramètres propres au squelette peuvent +être indiqués en amont de l'implémentation. +Dans \acref{lst:alsk/usage/impl/graspels_edsl}, \acref{lst:alsk/usage/edsl/graspelsbody} est repris +en ajoutant les valeurs utilisées\footnote{Remarque : les deux syntaxes de l'\gls{EDSL} permettent +de le faire, que ce soit \cppinline{n * task} ou \cppinline{farm(task, n)}.} pour configurer le +\graspels{} dans \acref{lst:alsk/usage/impl/graspels_conf}. +Il est ensuite implémenté : la syntaxe varie légèrement puisque le langage dédié produit une +variable et non un type. +La configuration du nombre de cœurs et de la répétabilité se font alors exactement comme dans le +premier cas. + +\begin{listing} + \inputlst{usage/impl/graspels_edsl} + {Utilisation de l'\glsxtrshort{EDSL} pour configurer le \graspels{}} +\end{listing} + +À partir de là, \cppinline{graspEls} peut être appelé comme le montre +\acref{lst:alsk/usage/impl/graspels_call}. +Il n'y a rien de particulier à relever ici. + +\begin{listing} + \inputlst{usage/impl/graspels_call} + {Appel du fonctionoïde \texttt{graspEls} généré par un squelette algorithmique} +\end{listing} diff --git a/src/commands.tex b/src/commands.tex new file mode 100644 index 0000000..6d86ab6 --- /dev/null +++ b/src/commands.tex @@ -0,0 +1,4 @@ +%{{{ alsk " +\newcommand*{\graspils}{\glsxtrshort{GRASP}\raisebox{1pt}{\texttimes}\glsxtrshort{ILS}} +\newcommand*{\graspels}{\glsxtrshort{GRASP}\raisebox{1pt}{\texttimes}\glsxtrshort{ELS}} +%}}} diff --git a/src/document.tex b/src/document.tex new file mode 100644 index 0000000..a514a23 --- /dev/null +++ b/src/document.tex @@ -0,0 +1,51 @@ +\begin{document} + \input{src/0_0_titlepage} + \input{src/0_1_thanks} + \input{src/0_2_abstract} + \IfStrEq{\compilationversion}{print}{% + \input{src/0_3_preamble_print} + }{% + \input{src/0_3_preamble} + } + + { + \clearpage\pagestyle{plain} + \microtypesetup{protrusion=false} + \hypersetup{linkcolor=black} + \glsresetall + \tableofcontents + \listoffigures + \listoftables + \listofalgorithms + \listoflistings + } + + { + \pagestyle{plain} + \input{src/0_intro} + \clearpage + } + + { + \pagestyle{content} + \input{src/1_par} + \input{src/2_gnx} + \input{src/3_mp} + \input{src/4_pfor} + \input{src/5_alsk} + \clearpage + } + + { + \pagestyle{plain} + \input{src/6_conclusion} + \clearpage + } + + { + \pagestyle{plain} + \printacronyms[title={Sigles}] + \emergencystretch 3em% avoid some overfull hbox + \printbibliography[heading=bibintoc] + } +\end{document} diff --git a/src/fig/alsk/app/els.tex b/src/fig/alsk/app/els.tex new file mode 100644 index 0000000..c581962 --- /dev/null +++ b/src/fig/alsk/app/els.tex @@ -0,0 +1,72 @@ +\tikzsetnextfilename{els}% +\begin{tikzpicture} + \input{src/tikz/alsk} + + % Outer nodes + \begin{scope}[start chain=going below,node distance=4mm,every node/.style={on chain,join},every + join/.style=alsk/scheme/link] + \node[alsk/scheme/task] (outer init ls) {RLI}; + \node[alsk/scheme/loop] (outer loop begin) {}; + \node[alsk/scheme/task] (inner) {}; + \node[alsk/scheme/task] (outer select) {S\textsubscript{2}}; + \node[alsk/scheme/task] (outer accept) {A}; + \node[alsk/scheme/loop] (outer loop end) {}; + \end{scope} + + \node[above=3mm of outer init ls] (outer entry) {}; + \node[below=3mm of outer loop end] (outer return) {}; + + % Links + \begin{scope}[alsk/scheme/link] + \draw (outer entry) -- (outer init ls); + \draw (outer loop end) -- (outer return); + + \draw (outer loop end.west) -- ++(-4mm,0mm) + -- ([xshift=-4mm]outer loop begin.west) -- (outer loop begin); + \end{scope} + + % Inner nodes + \begin{scope}[common/overlay,spy using outlines={circle,magnification=.14,size=10mm}, + shift={($(outer select.east)+(45mm,7mm)$)}] + \matrix[row sep=4mm,column sep=1mm] (inner m) { + & \node[alsk/scheme/split] (inner split) {}; \\ + \node[alsk/scheme/task] (inner m0) {M}; & \node[alsk/label] (inner mi) {\dots}; & + \node[alsk/scheme/task] (inner mn) {M}; \\ + \node[alsk/scheme/task] (inner ls0) {RL}; & \node[alsk/label] (inner lsi) {\dots}; & + \node[alsk/scheme/task] (inner lsn) {RL}; \\ + & \node[alsk/scheme/join] (inner merge) {}; \\ + & \node[alsk/scheme/task] (inner select) {S\textsubscript{1}}; \\ + }; + + \node[above=3mm of inner split] (inner entry) {}; + \node[below=3mm of inner select] (inner return) {}; + + % Links + \begin{scope}[alsk/scheme/link] + \draw (inner entry) -- (inner split); + + \draw (inner split) -- (inner m0); + \draw (inner split) -- (inner mn); + + \draw (inner m0) -- (inner ls0); + \draw (inner mn) -- (inner lsn); + + \draw (inner ls0) -- (inner merge); + \draw (inner lsn) -- (inner merge); + + \draw (inner merge) -- (inner select); + + \draw (inner select) -- (inner return); + \end{scope} + + \spy[densely dotted,thick,colMuscle,connect spies] on (inner m) in node at (inner); + \end{scope} + + % Bounding box adjustments + \node[rectangle,fit=(inner m)(inner entry)(inner return),fit margins={right=0mm}]{}; + + % Zoom + \begin{scope}[common/overlay] + \node[circle,inner sep=-4.5mm,fit=(inner entry)(inner return)(inner m0)(inner lsn)] (inner frame) {}; + \end{scope} +\end{tikzpicture} diff --git a/src/fig/alsk/app/grasp.tex b/src/fig/alsk/app/grasp.tex new file mode 100644 index 0000000..d176b4d --- /dev/null +++ b/src/fig/alsk/app/grasp.tex @@ -0,0 +1,53 @@ +\tikzsetnextfilename{grasp}% +\begin{tikzpicture} + \input{src/tikz/alsk} + + % Nodes + \matrix[row sep=4mm, column sep=-1mm] { + & \node[alsk/scheme/split] (split) {}; \\ + \\ + \node[alsk/scheme/task] (ch0) {HC}; & \node[alsk/label] (chi) {\dots}; & + \node[alsk/scheme/task] (chn) {HC}; \\ + \node[alsk/scheme/task] (ls0) {RL}; & \node[alsk/label] (lsi) {\dots}; & + \node[alsk/scheme/task] (lsn) {RL}; \\ + \\ + & \node[alsk/scheme/join] (merge) {}; \\ + & \node[alsk/scheme/task] (select) {S}; \\ + }; + + \node[above=3mm of split] (entry) {}; + \node[below=3mm of select] (return) {}; + + % Frames + \node[alsk/scheme/frame=colStruct,fit margins={top=1.5mm,bottom=1.5mm}, + fit=(ch0)(ls0)] (serial0) {}; + \node[alsk/scheme/frame=colStruct,fit margins={top=1.5mm,bottom=1.5mm}, + fit=(chn)(lsn)] (serialn) {}; + + \node[alsk/scheme/frame=colStruct,fit margins={top=1.5mm,bottom=1.5mm}, + fit=(split)(select)(serial0)(serialn)] {}; + + % Links + \begin{scope}[alsk/scheme/link] + \draw (entry) -- (split); + + \draw (split.210) -- (serial0.north); + \draw (split.330) -- (serialn.north); + + \draw (serial0.north) -- (ch0); + \draw (serialn.north) -- (chn); + + \draw (ch0) -- (ls0); + \draw (chn) -- (lsn); + + \draw (ls0) -- (serial0.south); + \draw (lsn) -- (serialn.south); + + \draw (serial0.south) -- (merge.150); + \draw (serialn.south) -- (merge.30); + + \draw (merge) -- (select); + + \draw (select) -- (return); + \end{scope} +\end{tikzpicture} diff --git a/src/fig/alsk/app/ils.tex b/src/fig/alsk/app/ils.tex new file mode 100644 index 0000000..253f5d6 --- /dev/null +++ b/src/fig/alsk/app/ils.tex @@ -0,0 +1,28 @@ +\tikzsetnextfilename{ils}% +\begin{tikzpicture} + \input{src/tikz/alsk} + + % Nodes + \begin{scope}[start chain=going below,node distance=4mm,every node/.style={on chain,join},every + join/.style=alsk/scheme/link] + \node[alsk/scheme/task](init ls) {RLI}; + \node[alsk/scheme/loop](loop begin) {}; + \node[alsk/scheme/task](m) {M}; + \node[alsk/scheme/task](ls) {RL}; + \node[alsk/scheme/task](sel) {S}; + \node[alsk/scheme/task](a) {A}; + \node[alsk/scheme/loop](loop end) {}; + \end{scope} + + \node[above=3mm of init ls] (entry) {}; + \node[below=3mm of loop end] (return) {}; + + % Links + \begin{scope}[alsk/scheme/link] + \draw (entry) -- (init ls); + \draw (loop end) -- (return); + + \draw (loop end.west) -- ++(-4mm,0mm) + -- ([xshift=-4mm]loop begin.west) -- (loop begin); + \end{scope} +\end{tikzpicture} diff --git a/src/fig/alsk/app/localmin.tex b/src/fig/alsk/app/localmin.tex new file mode 100644 index 0000000..e72bd75 --- /dev/null +++ b/src/fig/alsk/app/localmin.tex @@ -0,0 +1,27 @@ +\tikzsetnextfilename{local_min}% +\begin{tikzpicture} + \draw[name path=curve] plot[smooth] coordinates {(0,5)(1,4)(2,5)(3,2)(4,3)(5,3)(6,4)}; + + { + \path[name path=hline] (0,1.957) -- +(6,0); + \path[name intersections={of=curve and hline, name=i, total=\t}] + [fill=black,draw=black!50] (i-1) circle (1pt) node(m1){}; + } + { + \path[name path=hline] (0,2.960) -- +(6,0); + \path[name intersections={of=curve and hline, name=i, total=\t}] + [fill=black,draw=black!50] (i-3) circle (1pt) node(m2){}; + } + { + \path[name path=hline] (0,4.0012) -- +(6,0); + \path[name intersections={of=curve and hline, name=i, total=\t}] + [fill=black,draw=black!50] (i-1) circle (1pt) node(m3){}; + } + { + \path[name path=hline] (0,4.7) -- +(6,0); + \path[name intersections={of=curve and hline, name=i, total=\t}] + [fill=black,draw=black!50] (i-2) node(o){\tiny+}; + } + + \draw[->] (o.west) -- (m3.north); +\end{tikzpicture} diff --git a/src/fig/alsk/app/tspgreedysolution.tex b/src/fig/alsk/app/tspgreedysolution.tex new file mode 100644 index 0000000..9f0567c --- /dev/null +++ b/src/fig/alsk/app/tspgreedysolution.tex @@ -0,0 +1,34 @@ +\tikzsetnextfilename{tsp_solution_greedy_\tspstart}% +\begin{tikzpicture} + \input{src/tikz/tsp} + + \tspnodes + + % \def\tspstart{0} + + \coordinate (E) at (p\tspstart); + + \ifthenelse{\tspstart=0}{\def\tspsolution{0,9,1,8,7,6,4,2,3,5}}{} + \ifthenelse{\tspstart=1}{\def\tspsolution{1,8,7,9,0,2,4,5,6,3}}{} + \ifthenelse{\tspstart=2}{\def\tspsolution{2,1,0,9,8,7,4,3,5,6}}{} + \ifthenelse{\tspstart=3}{\def\tspsolution{3,4,5,6,8,7,1,2,0,9}}{} + \ifthenelse{\tspstart=4}{\def\tspsolution{4,5,3,6,8,7,1,2,0,9}}{} + \ifthenelse{\tspstart=5}{\def\tspsolution{5,4,3,6,8,7,1,2,0,9}}{} + \ifthenelse{\tspstart=6}{\def\tspsolution{6,4,5,3,8,7,1,2,0,9}}{} + \ifthenelse{\tspstart=7}{\def\tspsolution{7,8,6,4,5,1,9,0,2,3}}{} + \ifthenelse{\tspstart=8}{\def\tspsolution{8,7,6,4,5,1,9,0,2,3}}{} + \ifthenelse{\tspstart=9}{\def\tspsolution{9,0,1,8,7,6,4,5,3,2}}{} + + \foreach [count=\i from 0] \o in \tspsolution { + \coordinate (s\i) at (p\o); + } + + \foreach \i in {0,...,\last} { + \node[tsp/node] at (p\i) {}; + } + \node[tsp/node,common/drawfillf={black}{20}] at (E) {}; + + \begin{scope}[on background layer] + \draw[tsp/edge,foreach/.style={insert path=--(s#1)}] (E) [foreach/.list={1,...,\last}] -- cycle; + \end{scope} +\end{tikzpicture} diff --git a/src/fig/alsk/app/tspinstance.tex b/src/fig/alsk/app/tspinstance.tex new file mode 100644 index 0000000..3b7c8bb --- /dev/null +++ b/src/fig/alsk/app/tspinstance.tex @@ -0,0 +1,19 @@ +\tikzsetnextfilename{tsp_instance}% +\begin{tikzpicture} + \input{src/tikz/tsp} + + \tspnodes + + \foreach \i in {0,...,\last} { + \node[tsp/node] at (p\i) {}; + } + + \begin{scope}[on background layer] + \foreach \i in {0,...,\penult} { + \pgfmathtruncatemacro{\b}{\i+1} + \foreach \j in {\b,...,\last} { + \draw[black!40] (p\i) -- (p\j); + } + } + \end{scope} +\end{tikzpicture} diff --git a/src/fig/alsk/app/tspsolution.tex b/src/fig/alsk/app/tspsolution.tex new file mode 100644 index 0000000..157c674 --- /dev/null +++ b/src/fig/alsk/app/tspsolution.tex @@ -0,0 +1,30 @@ +\tikzsetnextfilename{tsp_solutions}% +\begin{tikzpicture}[thick] + \input{src/tikz/tsp} + + \tspnodes + + \foreach \i in {0,...,\last} { + \coordinate (p\i') at ($(p\i)+(6,0)$); + } + + \coordinate (s0) at (p0); + \foreach [count=\i] \o in {2,3,1,4,5,8,6,7,9} { + \coordinate (s\i) at (p\o); + } + + \coordinate (s0') at (p0'); + \foreach [count=\i] \o in {1,2,3,5,4,6,7,8,9} { + \coordinate (s\i') at (p\o'); + } + + \foreach \i in {0,...,\last} { + \node[tsp/node] at (p\i) {}; + \node[tsp/node] at (p\i') {}; + } + + \begin{scope}[on background layer] + \draw[tsp/edge,foreach/.style={insert path=--(s#1)}] (p0) [foreach/.list={1,...,\last}] -- cycle; + \draw[tsp/edge,foreach/.style={insert path=--(s#1')}] (p0') [foreach/.list={1,...,\last}] -- cycle; + \end{scope} +\end{tikzpicture} diff --git a/src/fig/alsk/app/tspswap.tex b/src/fig/alsk/app/tspswap.tex new file mode 100644 index 0000000..41397af --- /dev/null +++ b/src/fig/alsk/app/tspswap.tex @@ -0,0 +1,75 @@ +\tikzsetnextfilename{tsp_swap_\swapI _5}% +\begin{tikzpicture} + \input{src/tikz/tsp} + + \tspnodes + + \foreach \i in {0,...,\last} { + \coordinate (p\i') at ($(p\i)+(6,0)$); + } + + \def\solution{0,9,1,8,7,6,4,2,3,5} + + % Points swapped + % \def\swapA{2}% 7 + \foreach[count=\i from 0] \p in \solution { + \ifthenelse{\p=\swapI}{\xdef\swapA{\i}}{} + } + + \def\swapB{9} + % \foreach[count=\i from 1] \p in \solution { + % \ifthenelse{\p=\swapII}{\xdef\swapB{\i}}{} + % } + + % First point is always 0 + \foreach[count=\i from 0] \o in \solution { + \coordinate (s\i) at (p\o); + + \ifthenelse{\i=\swapA}{\def\I{\swapB}}{ + \ifthenelse{\i=\swapB}{\def\I{\swapA}}{ + \def\I{\i} + } + } + \coordinate (s\I') at (p\o'); + \coordinate (S\i') at (p\o'); + } + + \foreach \i in {0,...,\last} { + \node[tsp/node] at (p\i) {}; + \node[tsp/node] at (p\i') {}; + } + + \node[tsp/node,common/drawfillf={blue}{25}] at (s\swapA) {}; + \node[tsp/node,common/drawfillf={blue}{25}] at (s\swapB) {}; + \node[tsp/node,common/drawfillf={blue}{25}] at (s\swapA') {}; + \node[tsp/node,common/drawfillf={blue}{25}] at (s\swapB') {}; + + \begin{scope}[on background layer,start chain] + \draw[tsp/edge,foreach/.style={insert path=--(s#1)}] (s0) [foreach/.list={1,...,\last}] -- cycle; + \draw[tsp/edge,foreach/.style={insert path=--(s#1')}] (s0') [foreach/.list={1,...,\last}] -- cycle; + + \pgfmathtruncatemacro{\prevA}{mod(\swapA-1+\n, \n)} + \pgfmathtruncatemacro{\nextA}{mod(\swapA+1, \n)} + \draw[tsp/edge,dashed,black!50] (s\prevA') -- (S\swapA') -- (s\nextA'); + \draw[tsp/edge,blue!90!black] (s\prevA') -- (s\swapA') -- (s\nextA'); + + \pgfmathtruncatemacro{\prevB}{mod(\swapB-1+\n, \n)} + \pgfmathtruncatemacro{\nextB}{mod(\swapB+1, \n)} + \draw[tsp/edge,dashed,black!50] (s\prevB') -- (S\swapB') -- (s\nextB'); + \draw[tsp/edge,blue!90!black] (s\prevB') -- (s\swapB') -- (s\nextB'); + \end{scope} + + \edef\listP{} + \edef\listPP{} + \foreach \i in {0,...,\last} { + \xdef\listP{(p\i) \listP} + \xdef\listPP{(p\i') \listPP} + } + + \begin{scope}[overlay] + \node[fit=\listP](fitp){}; + \node[fit=\listPP](fitpp){}; + + \draw[->] ([xshift=2mm]fitp.east) -- node[above]{échange} ([xshift=-2mm]fitpp.west); + \end{scope} +\end{tikzpicture} diff --git a/src/fig/alsk/app/tsptikzrp.tex b/src/fig/alsk/app/tsptikzrp.tex new file mode 100644 index 0000000..cc1d790 --- /dev/null +++ b/src/fig/alsk/app/tsptikzrp.tex @@ -0,0 +1,9 @@ +\begin{tikzpicture}[remember picture] + \input{src/tikz/tsp} + + \tspnodes + + \foreach \i in {0,...,\last} { + \node[tsp/node,draw=none,fill=none] (tsp node \i) at (p\i) {}; + } +\end{tikzpicture} diff --git a/src/fig/alsk/app/tsptikzrpii.tex b/src/fig/alsk/app/tsptikzrpii.tex new file mode 100644 index 0000000..1091c43 --- /dev/null +++ b/src/fig/alsk/app/tsptikzrpii.tex @@ -0,0 +1,12 @@ +\begin{tikzpicture}[remember picture] + \input{src/tikz/tsp} + + \tspnodes + + \foreach \i in {0,...,\last} { + \node[tsp/node,draw=none,fill=none] (tsp node \i) at (p\i) {}; + + \coordinate (p\i') at ($(p\i)+(6,0)$); + \node[tsp/node,draw=none,fill=none] (tsp node \i') at (p\i') {}; + } +\end{tikzpicture} diff --git a/src/fig/alsk/concept/inst/tree_a.tex b/src/fig/alsk/concept/inst/tree_a.tex new file mode 100644 index 0000000..ca350cf --- /dev/null +++ b/src/fig/alsk/concept/inst/tree_a.tex @@ -0,0 +1,7 @@ +\tikzsetnextfilename{alg_x_tree}% +\begin{tikzpicture} + \input{src/tikz/alsk} + \begin{scope}[alsk/treestyle] + \Tree [.farmsel [.serial A B C ] Sel ] + \end{scope} +\end{tikzpicture} diff --git a/src/fig/alsk/concept/inst/tree_ab.tex b/src/fig/alsk/concept/inst/tree_ab.tex new file mode 100644 index 0000000..fd29f6b --- /dev/null +++ b/src/fig/alsk/concept/inst/tree_ab.tex @@ -0,0 +1,7 @@ +\tikzsetnextfilename{alg_xy_tree}% +\begin{tikzpicture} + \input{src/tikz/alsk} + \begin{scope}[alsk/treestyle] + \Tree [.farmsel [.serial A [.serial D E ] C ] Sel ] + \end{scope} +\end{tikzpicture} diff --git a/src/fig/alsk/concept/inst/tree_ab_optimized.tex b/src/fig/alsk/concept/inst/tree_ab_optimized.tex new file mode 100644 index 0000000..2c24b2b --- /dev/null +++ b/src/fig/alsk/concept/inst/tree_ab_optimized.tex @@ -0,0 +1,7 @@ +\tikzsetnextfilename{alg_xy_optimized_tree}% +\begin{tikzpicture} + \input{src/tikz/alsk} + \begin{scope}[alsk/treestyle] + \Tree [.farmsel [.serial A D E C ] Sel ] + \end{scope} +\end{tikzpicture} diff --git a/src/fig/alsk/concept/inst/tree_b.tex b/src/fig/alsk/concept/inst/tree_b.tex new file mode 100644 index 0000000..fcd5355 --- /dev/null +++ b/src/fig/alsk/concept/inst/tree_b.tex @@ -0,0 +1,7 @@ +\tikzsetnextfilename{alg_y_tree}% +\begin{tikzpicture} + \input{src/tikz/alsk} + \begin{scope}[alsk/treestyle] + \Tree [.serial D E ] + \end{scope} +\end{tikzpicture} diff --git a/src/fig/alsk/concept/links/grasp.tex b/src/fig/alsk/concept/links/grasp.tex new file mode 100644 index 0000000..258dc5f --- /dev/null +++ b/src/fig/alsk/concept/links/grasp.tex @@ -0,0 +1,92 @@ +\tikzsetnextfilename{grasp_links}% +\begin{tikzpicture}[node distance=10mm] + \input{src/tikz/alsk} + + % Serial + \begin{scope}[every node/.style=alsk/links/muscle] + \node(init) {HC}; + \node[below=of init] (ls) {RL}; + \end{scope} + + \path (init.north west) -- coordinate[pos=.33](init i0) coordinate[pos=.66](init i1) (init.south west); + \path (ls.north west) -- coordinate[pos=.25](ls i0) coordinate[pos=.5](ls i1) coordinate[pos=.75] (ls i2) (ls.south west); + + \begin{scope}[every node/.style={alsk/links/sig=colLinkIn}] + \node[left=of init i0] (sp0) {\texttt{P\textsubscript{0}}}; + \node[node distance=4mm,below=of sp0] (sp1) {\texttt{P\textsubscript{1}}}; + \end{scope} + \begin{scope}[every node/.style={alsk/links/sig=colLinkOut}] + \node[right=of init] (sr0) {\texttt{R\textsubscript{0}}}; + \node[right=of ls] (sr1) {\texttt{R\textsubscript{1}}}; + \end{scope} + + \coordinate (ls i0 left) at ($(ls i0)+(-3mm,0)$); + \coordinate (sr0 right) at ($(sr0.east)+(1.5mm,0)$); + + \begin{scope}[alsk/links/link] + \draw (sp0.east) -- (init i0); + \draw (sp1.east) -| ($(init i1)!.55!(sp1.east)$) |- (init i1); + \draw (init) -- (sr0); + + \draw (sr0.east) -- (sr0 right) |- ($(sr0 right)!.5!(ls.west)$) -| (ls i0 left) -- (ls i0); + \draw (sp1.east) -| ($(ls i1)!.55!(sp1.east)$) |- (ls i1); + \draw (sp0.east) -| ($(ls i2)!.8!(sp0.east)$) |- (ls i2); + + \draw (ls) -- (sr1); + \end{scope} + + \begin{scope}[every node/.style={alsk/links/sig=colLinkIn}] + \node at (sp0) {\texttt{P\textsubscript{0}}}; + \node at (sp1) {\texttt{P\textsubscript{1}}}; + \end{scope} + + \begin{scope}[on background layer] + \node[alsk/links/fit=(sp0)(init)(ls)(sr0)(sr1)] (serial) {}; + \end{scope} + \node[node distance=0,above=of serial.north west,anchor=south west] (seriallbl) {Serial ($n$ fois)}; + + \coordinate (serial left) at ($(serial.south west)+(0,2.5mm)$); + \coordinate (serial bottom) at ($(serial.south west)+(2.5mm,0)$); + + % FarmSel + \node[alsk/links/muscle,below=of serial](select) {Select}; + + \coordinate (select i0) at (select.west); + + \begin{scope}[every node/.style={alsk/links/sig=colLinkIn}] + \node[left=of sp0] (fsp0) {\texttt{P\textsubscript{0}}}; + \node[left=of sp1] (fsp1) {\texttt{P\textsubscript{1}}}; + \end{scope} + + \begin{scope}[alsk/links/link] + \draw (fsp0) -- (sp0); + \draw (fsp1) -- (sp1); + \end{scope} + + \coordinate (select i0 left) at ($(select i0)+(-4mm,0)$); + \coordinate (sr1 right) at ($(sr1.east)+(5.5mm,0)$); + + \draw[alsk/links/link] (sr1.east) + -- (sr1 right) + |- ($(sr1 right)!.5!(select.west)$) + -| node[pos=.75,left] {$n$ fois} (select i0 left) + -- (select i0); + + \draw[->] (select.east) -- +(right:25.5mm) node[right] {Solution}; + + \begin{scope}[on background layer] + \node[alsk/links/fit=(fsp0)(fsp1)(serial)(seriallbl)(select)] (farmsel) {}; + \end{scope} + \node[node distance=0,above=of farmsel.north west,anchor=south west] (farmsellbl) {FarmSel}; + + % Input + \node[minimum width=18mm,node distance=3mm,above=of fsp0,anchor=south east,xshift=-4mm] (problem) + {Problème}; + \node[minimum width=18mm,node distance=3mm,below=of fsp1,anchor=north east,xshift=-4mm] (prng) + {\glsxtrshort{PRNG}}; + + \begin{scope}[alsk/links/link] + \draw (problem) |- (fsp0); + \draw (prng) |- (fsp1); + \end{scope} +\end{tikzpicture} diff --git a/src/fig/alsk/concept/struct/graspels.tex b/src/fig/alsk/concept/struct/graspels.tex new file mode 100644 index 0000000..e055186 --- /dev/null +++ b/src/fig/alsk/concept/struct/graspels.tex @@ -0,0 +1,128 @@ +\tikzsetnextfilename{grasp_els}% +\begin{tikzpicture} + \input{src/tikz/alsk} + + % GRASP nodes + \matrix[row sep=4mm, column sep=-1mm] { + & \node[alsk/scheme/split] (grasp split) {}; \\ + \\ + \node[alsk/scheme/task] (grasp ch0) {HC}; & \node[alsk/label] (grasp chi) {\dots}; & + \node[alsk/scheme/task] (grasp chn) {HC}; \\ + \node[alsk/scheme/task] (grasp ls0) {}; & \node[alsk/label] (grasp lsi) {\dots}; & + \node[alsk/scheme/task] (grasp lsn) {}; \\ + \\ + & \node[alsk/scheme/join] (grasp merge) {}; \\ + & \node[alsk/scheme/task] (grasp select) {Sel\textsubscript{1}}; \\ + }; + + \node[above=3mm of grasp split] (grasp entry) {}; + \node[below=3mm of grasp select] (grasp return) {}; + + % Frames + \node[alsk/scheme/frame=colStruct,fit margins={top=1.5mm,bottom=1.5mm}, + fit=(grasp ch0)(grasp ls0)] (grasp serial0) {}; + \node[alsk/scheme/frame=colStruct,fit margins={top=1.5mm,bottom=1.5mm}, + fit=(grasp chn)(grasp lsn)] (grasp serialn) {}; + + \node[alsk/scheme/frame=colStruct,fit margins={top=1.5mm,bottom=1.5mm}, + fit=(grasp split)(grasp select)(grasp serial0)(grasp serialn)] {}; + + % GRASP Links + \begin{scope}[alsk/scheme/link] + \draw (grasp entry) -- (grasp split); + + \draw (grasp split.210) -- (grasp serial0.north); + \draw (grasp split.330) -- (grasp serialn.north); + + \draw (grasp serial0.north) -- (grasp ch0); + \draw (grasp serialn.north) -- (grasp chn); + + \draw (grasp ch0) -- (grasp ls0); + \draw (grasp chn) -- (grasp lsn); + + \draw (grasp ls0) -- (grasp serial0.south); + \draw (grasp lsn) -- (grasp serialn.south); + + \draw (grasp serial0.south) -- (grasp merge.150); + \draw (grasp serialn.south) -- (grasp merge.30); + + \draw (grasp merge) -- (grasp select); + + \draw (grasp select) -- (grasp return); + \end{scope} + + % ELS + \begin{scope}[common/overlay,spy using outlines={circle,magnification=.109,size=10.0mm}] + \begin{scope}[every node/.append style={transform shape,scale=.7}] + \matrix[row sep=1.5mm, column sep=-1mm] (els) at (6.5, 0) { + & \node[alsk/scheme/task] (els init ls) {RLI}; \\ + & \node[alsk/scheme/loop] (els loopbegin) {}; \\ + & \node[alsk/scheme/split] (els split) {}; \\ + \\ + \node[alsk/scheme/task] (els m0) {M}; & \node[alsk/label] (els mi) {\dots}; & + \node[alsk/scheme/task] (els mn) {M}; \\ + \node[alsk/scheme/task] (els ls0) {RL}; & \node[alsk/label] (els lsi) {\dots}; & + \node[alsk/scheme/task] (els lsn) {RL}; \\ + \\ + & \node[alsk/scheme/join] (els merge) {}; \\ + & \node[alsk/scheme/task] (els select2) {Sel\textsubscript{2}}; \\ + & \node[alsk/scheme/task] (els select3) {Sel\textsubscript{3}}; \\ + & \node[alsk/scheme/task] (els accept) {A}; \\ + & \node[alsk/scheme/loop] (els loopend) {}; \\ + }; + + \node[above=2.5mm of els init ls] (els entry) {}; + \node[below=2.5mm of els loopend] (els return) {}; + + % Frames + \node[alsk/scheme/frame=colStruct,fit margins={top=3mm,bottom=3mm,left=1.2mm,right=1.2mm}, + fit=(els m0)(els ls0)] (els serial0) {}; + \node[alsk/scheme/frame=colStruct,fit margins={top=3mm,bottom=3mm,left=1.2mm,right=1.2mm}, + fit=(els mn)(els lsn)] (els serialn) {}; + + \node[alsk/scheme/frame=colStruct,fit margins={top=5.4mm,bottom=5mm,left=2.8mm,right=2.8mm}, + fit=(els split)(els select2)(els serial0)(els serialn)] {}; + + % Links + \begin{scope}[alsk/scheme/link] + \draw (els entry) -- (els init ls); + + \draw (els init ls) -- (els loopbegin); + + \draw (els loopbegin) -- (els split); + + \draw (els split.210) -- (els serial0.north); + \draw (els split.330) -- (els serialn.north); + + \draw (els serial0.north) -- (els m0); + \draw (els serialn.north) -- (els mn); + + \draw (els m0) -- (els ls0); + \draw (els mn) -- (els lsn); + + \draw (els ls0) -- (els serial0.south); + \draw (els lsn) -- (els serialn.south); + + \draw (els serial0.south) -- (els merge.150); + \draw (els serialn.south) -- (els merge.30); + + \draw (els merge) -- (els select2); + + \draw (els select2) -- (els select3); + \draw (els select3) -- (els accept); + \draw (els accept) -- (els loopend); + \end{scope} + + \draw[alsk/scheme/link] (els loopend) -| +(-14mm, 0) |- ([xshift=-7mm]els loopbegin.west) + -- (els loopbegin); + + \draw[alsk/scheme/link] (els loopend) -- (els return); + \end{scope} + + \spy[densely dotted,thick,colMuscle] on (els) in node at (grasp ls0); + \spy[densely dotted,thick,colMuscle,connect spies] on (els) in node at (grasp lsn); + \end{scope} + + % Bounding box adjustments + \node[rectangle,fit=(els)(els entry)(els return),fit margins={right=3mm}]{}; +\end{tikzpicture} diff --git a/src/fig/alsk/concept/struct/treegrasp.tex b/src/fig/alsk/concept/struct/treegrasp.tex new file mode 100644 index 0000000..824fec2 --- /dev/null +++ b/src/fig/alsk/concept/struct/treegrasp.tex @@ -0,0 +1,7 @@ +\tikzsetnextfilename{grasp_tree}% +\begin{tikzpicture} + \input{src/tikz/alsk} + \begin{scope}[alsk/treestyle] + \Tree[.farmsel [.serial HC RL ] Sel ] + \end{scope} +\end{tikzpicture} diff --git a/src/fig/alsk/concept/struct/treegraspels.tex b/src/fig/alsk/concept/struct/treegraspels.tex new file mode 100644 index 0000000..801ffed --- /dev/null +++ b/src/fig/alsk/concept/struct/treegraspels.tex @@ -0,0 +1,20 @@ +\tikzsetnextfilename{grasp_els_tree}% +\begin{tikzpicture} + \input{src/tikz/alsk} + + \begin{scope}[alsk/treestyle] + \Tree[.farmsel + [.serial + HC + [.serial + RLI + [.itersel + [.farmsel [.serial M RL ] Sel\textsubscript{3} ] + Sel\textsubscript{2} + ] + ] + ] + Sel\textsubscript{1} + ] + \end{scope} +\end{tikzpicture} diff --git a/src/fig/alsk/exec/executor/firstlevel.tex b/src/fig/alsk/exec/executor/firstlevel.tex new file mode 100644 index 0000000..3d0dd4a --- /dev/null +++ b/src/fig/alsk/exec/executor/firstlevel.tex @@ -0,0 +1,71 @@ +\tikzsetnextfilename{execution_policy_\N _tasks_\T _threads}% +\begin{tikzpicture} + \input{src/tikz/orchestration} + + % \def\N{15} + % \def\T{6} + + \pgfmathtruncatemacro{\nlasttask}{\N-1} + \pgfmathtruncatemacro{\nlastthread}{\T-1} + \pgfmathtruncatemacro{\R}{mod(\N, \T)} + + \begin{scope}[overlay] + \node(origin){}; + \node at (origin) (t-\T) {}; + \end{scope} + + \begin{scope}[node distance=4mm and 4mm] + \foreach \task in {0,...,\nlasttask} { + \pgfmathtruncatemacro{\r}{mod(\task, \T)} + \ifthenelse{\r = 0}{ + \pgfmathtruncatemacro{\uppertask}{\T*(\task/\T-1)} + \def\reltype{below} + \def\relnode{t\uppertask} + }{ + \pgfmathtruncatemacro{\prevtask}{\task-1} + \def\reltype{right} + \def\relnode{t\prevtask} + } + \node[orch/task=black,\reltype=of \relnode] (t\task) {m\textsubscript{\task}}; + } + + \ifthenelse{\R = 0}{}{ + \pgfmathtruncatemacro{\ntaskfill}{mod(\T-\R, \T)-1} + \foreach \taskfill in {0,...,\ntaskfill} { + \pgfmathtruncatemacro{\task}{\nlasttask+\taskfill+1} + \pgfmathtruncatemacro{\prevtask}{\task-1} + \node[orch/task=white,draw=none,fill=none,right=of t\prevtask] (t\task) {}; + \draw[-] (t\task.north) -- (t\task.south); + } + } + + \foreach \thread in {0,...,\nlastthread} { + \node[above=of t\thread] (label\thread) {\en{thread}~\thread}; + \draw[-] (label\thread) -- (t\thread); + + \pgfmathtruncatemacro{\n}{floor((\N-1)/\T)-1} + \ifthenelse{\n = -1}{}{% only if N>T + \foreach \task in {0,...,\n} { + \pgfmathtruncatemacro{\curtask}{\thread+\task*\T} + \pgfmathtruncatemacro{\nexttask}{\curtask+\T} + \draw[-] (t\curtask) -- (t\nexttask); + } + } + + \pgfmathtruncatemacro{\lasttask}{\thread+(\n+1)*\T} + \node[below=of t\lasttask] (endthread\thread) {}; + \draw[-] (t\lasttask) -- (endthread\thread); + } + \end{scope} + + \begin{scope}[common/overlay] + \draw[decorate,decoration={brace, amplitude=4pt, raise=9mm, mirror}] + (t0.north) -- (t0.south) node[left,midway,xshift=-11mm] {$D$}; + + \pgfmathtruncatemacro{\n}{ceil(\N/\T)} + \pgfmathtruncatemacro{\nr}{(\N-1)/\T} + \pgfmathtruncatemacro{\ftlt}{\nr*\T} + \draw[decorate,decoration={brace, amplitude=4pt, raise=18mm, mirror}] + (t0.north) -- (t\ftlt.south) node[left,midway,xshift=-20mm] {$\n\,D$}; + \end{scope} +\end{tikzpicture} diff --git a/src/fig/alsk/exec/executor/firstleveldense.tex b/src/fig/alsk/exec/executor/firstleveldense.tex new file mode 100644 index 0000000..6ccf681 --- /dev/null +++ b/src/fig/alsk/exec/executor/firstleveldense.tex @@ -0,0 +1,89 @@ +\tikzsetnextfilename{execution_policy_dense_\N _tasks_\T _threads}% +\begin{tikzpicture} + \input{src/tikz/orchestration} + + % \def\N{15} + % \def\T{5} + + \pgfmathtruncatemacro{\nlasttask}{\N-1} + \pgfmathtruncatemacro{\nlastthread}{\T-1} + \pgfmathtruncatemacro{\R}{mod(\N, \T)} + \pgfmathtruncatemacro{\ntlt}{\N/\T-1} + + \begin{scope}[overlay] + \node(origin){}; + \node at (origin) (t-\T) {}; + \end{scope} + + \begin{scope}[node distance=4mm and 4mm] + \foreach \task in {0,...,\nlasttask} { + \pgfmathtruncatemacro{\r}{mod(\task, \T)} + \ifthenelse{\r = 0}{ + \pgfmathtruncatemacro{\uppertask}{\T*(\task/\T-1)} + \def\reltype{below} + \def\relnode{t\uppertask} + }{ + \pgfmathtruncatemacro{\prevtask}{\task-1} + \def\reltype{right} + \def\relnode{t\prevtask} + } + \node[orch/task=black,\reltype=of \relnode] (t\task) {m\textsubscript{\task}}; + } + + \ifthenelse{\R = 0}{}{ + \pgfmathtruncatemacro{\ntaskfill}{mod(\T-\R, \T)-1} + \foreach \taskfill in {0,...,\ntaskfill} { + \pgfmathtruncatemacro{\task}{\nlasttask+\taskfill+1} + \pgfmathtruncatemacro{\prevtask}{\task-1} + \node[orch/task=white,draw=none,fill=none,right=of t\prevtask] (t\task) {}; + \draw[-] (t\task.north) -- (t\task.south); + } + } + + \node[orch/task=white,draw=none,fill=none,right=of t\nlastthread] (pt0) {}; + \draw[-] (pt0.north) -- (pt0.south); + \foreach \tlt in {1,...,\ntlt} { + \pgfmathtruncatemacro{\ptlt}{\tlt-1} + \node[orch/task=white,draw=none,fill=none,below=of pt\ptlt] (pt\tlt) {}; + \draw[-] (pt\ptlt) -- (pt\tlt); + \draw[-] (pt\tlt.north) -- (pt\tlt.south); + } + + \foreach \thread in {0,...,\nlastthread} { + \node[above=of t\thread] (label\thread) {\en{thread}~\thread}; + \draw[-] (label\thread) -- (t\thread); + + \pgfmathtruncatemacro{\n}{floor((\N-1)/\T)-1} + \ifthenelse{\n = -1}{}{% only if N>T + \foreach \task in {0,...,\n} { + \pgfmathtruncatemacro{\curtask}{\thread+\task*\T} + \pgfmathtruncatemacro{\nexttask}{\curtask+\T} + \draw[-] (t\curtask) -- (t\nexttask); + } + } + + \pgfmathtruncatemacro{\lasttask}{\thread+(\n+1)*\T} + \node[below=of t\lasttask] (endthread\thread) {}; + \draw[-] (t\lasttask) -- (endthread\thread); + } + + { + \node[above=of pt0] (label\T) {\en{thread}~\T}; + \node[below=of pt\ntlt] (endthread\T) {}; + + \draw[-] (label\T) -- (pt0); + \draw[-] (pt\ntlt) -- (endthread\T); + } + \end{scope} + + \begin{scope}[common/overlay] + \draw[decorate,decoration={brace, amplitude=4pt, raise=9mm, mirror}] + (t0.north) -- (t0.south) node[left,midway,xshift=-11mm] {$D$}; + + \pgfmathtruncatemacro{\n}{ceil(\N/\T)} + \pgfmathtruncatemacro{\nr}{(\N-1)/\T} + \pgfmathtruncatemacro{\ftlt}{\nr*\T} + \draw[decorate,decoration={brace, amplitude=4pt, raise=18mm, mirror}] + (t0.north) -- (t\ftlt.south) node[left,midway,xshift=-20mm] {$\n{}D$}; + \end{scope} +\end{tikzpicture} diff --git a/src/fig/alsk/exec/executor/firstleveldetails.tex b/src/fig/alsk/exec/executor/firstleveldetails.tex new file mode 100644 index 0000000..7f1eeab --- /dev/null +++ b/src/fig/alsk/exec/executor/firstleveldetails.tex @@ -0,0 +1,77 @@ +\tikzsetnextfilename{execution_policy_\N x2_tasks_\T _threads}% +\begin{tikzpicture} + \input{src/tikz/orchestration} + + % \def\N{15} + % \def\T{6} + + \pgfmathtruncatemacro{\nlasttask}{\N-1} + \pgfmathtruncatemacro{\nlastthread}{\T-1} + \pgfmathtruncatemacro{\R}{mod(\N, \T)} + + \begin{scope}[overlay] + \node(origin){}; + \node at (origin) (t-\T) {}; + \end{scope} + + \begin{scope}[node distance=4mm and 4mm] + \foreach \task in {0,...,\nlasttask} { + \pgfmathtruncatemacro{\r}{mod(\task, \T)} + \ifthenelse{\r = 0}{ + \pgfmathtruncatemacro{\uppertask}{\T*(\task/\T-1)} + \def\reltype{below} + \def\relnode{t\uppertask} + }{ + \pgfmathtruncatemacro{\prevtask}{\task-1} + \def\reltype{right} + \def\relnode{t\prevtask} + } + \node[orch/task=black,\reltype=of \relnode] (t\task) {}; + + \draw[-] (t\task.north) + -- node[pos=.3,orch/subtask=black] {} + node[pos=.7,orch/subtask=black] {} + (t\task.south); + } + + \ifthenelse{\R = 0}{}{ + \pgfmathtruncatemacro{\ntaskfill}{mod(\T-\R, \T)-1} + \foreach \taskfill in {0,...,\ntaskfill} { + \pgfmathtruncatemacro{\task}{\nlasttask+\taskfill+1} + \pgfmathtruncatemacro{\prevtask}{\task-1} + \node[orch/task=white,draw=none,fill=none,right=of t\prevtask] (t\task) {}; + \draw[-] (t\task.north) -- (t\task.south); + } + } + + \foreach \thread in {0,...,\nlastthread} { + \node[above=of t\thread] (label\thread) {\en{thread}~\thread}; + \draw[-] (label\thread) -- (t\thread); + + \pgfmathtruncatemacro{\n}{floor((\N-1)/\T)-1} + \ifthenelse{\n = -1}{}{% only if N>T + \foreach \task in {0,...,\n} { + \pgfmathtruncatemacro{\curtask}{\thread+\task*\T} + \pgfmathtruncatemacro{\nexttask}{\curtask+\T} + \draw[-] (t\curtask) -- (t\nexttask); + } + } + + \pgfmathtruncatemacro{\lasttask}{\thread+(\n+1)*\T} + \node[below=of t\lasttask] (endthread\thread) {}; + \draw[-] (t\lasttask) -- (endthread\thread); + } + \end{scope} + + \begin{scope}[common/overlay] + \pgfmathtruncatemacro{\n}{2*ceil(\N/\T)} + \pgfmathtruncatemacro{\nr}{(\N-1)/\T} + \pgfmathtruncatemacro{\ftlt}{\nr*\T} + + \draw[decorate,decoration={brace, amplitude=4pt, raise=9mm, mirror}] + (t0.north) -- (t0.south) node[left,midway,xshift=-11mm] {$2\,D$}; + + \draw[decorate,decoration={brace, amplitude=4pt, raise=18mm, mirror}] + (t0.north) -- (t\ftlt.south) node[left,midway,xshift=-20mm] {$\n\,D$}; + \end{scope} +\end{tikzpicture} diff --git a/src/fig/alsk/exec/executor/multilevel.tex b/src/fig/alsk/exec/executor/multilevel.tex new file mode 100644 index 0000000..184baea --- /dev/null +++ b/src/fig/alsk/exec/executor/multilevel.tex @@ -0,0 +1,86 @@ +\tikzsetnextfilename{execution_policy_multilevel_\N x2_tasks_\T _threads}% +\begin{tikzpicture} + \input{src/tikz/orchestration} + + % \def\N{15} + % \def\T{6} + + \pgfmathtruncatemacro{\nlasttask}{\N-1} + \pgfmathtruncatemacro{\nlastthread}{\T-1} + \pgfmathtruncatemacro{\R}{mod(\N, \T)} + + \begin{scope}[overlay] + \node(origin){}; + \node at (origin) (t-\T) {}; + \end{scope} + + \begin{scope}[node distance=4mm and 4mm] + \pgfmathtruncatemacro{\nlasttaskA}{\nlasttask-\R} + + \foreach \task in {0,...,\nlasttaskA} { + \pgfmathtruncatemacro{\r}{mod(\task, \T)} + \ifthenelse{\r = 0}{ + \pgfmathtruncatemacro{\uppertask}{\T*(\task/\T-1)} + \def\reltype{below} + \def\relnode{t\uppertask} + }{ + \pgfmathtruncatemacro{\prevtask}{\task-1} + \def\reltype{right} + \def\relnode{t\prevtask} + } + \node[orch/task=black,\reltype=of \relnode] (t\task) {}; + + \draw[-] (t\task.north) + -- node[pos=.3,orch/subtask=black] {} + node[pos=.7,orch/subtask=black] {} + (t\task.south); + } + + \foreach \taskfill in {1,...,\R} { + \pgfmathtruncatemacro{\task}{\taskfill-1} + \pgfmathtruncatemacro{\prevtaskl}{\nlasttaskA+\task-\T+\taskfill)} + \pgfmathtruncatemacro{\prevtaskr}{\prevtaskl+1} + \path (t\prevtaskl.south) -- coordinate[midway](prevtask\taskfill) (t\prevtaskr.south); + \node[orch/taskspan=black,below=of prevtask\taskfill] (ts\task) {}; + + \pgfmathtruncatemacro{\taska}{\nlasttaskA+\task*2+1} + \pgfmathtruncatemacro{\taskb}{\nlasttaskA+\task*2+2} + + \path (t\prevtaskl.south) |- node[orch/subtask=black] (t\taska) {} (ts\task.west); + \path (t\prevtaskr.south) |- node[orch/subtask=black] (t\taskb) {} (ts\task.west); + } + + \foreach \thread in {0,...,\nlastthread} { + \node[above=of t\thread] (label\thread) {\en{thread}~\thread}; + \draw[-] (label\thread) -- (t\thread); + + \pgfmathtruncatemacro{\n}{floor((\N-1)/\T)-1} + \ifthenelse{\n = -1}{}{% only if N>T + \foreach \task in {0,...,\n} { + \pgfmathtruncatemacro{\curtask}{\thread+\task*\T} + \pgfmathtruncatemacro{\nexttask}{\curtask+\T} + \draw[-] (t\curtask) -- (t\nexttask); + } + } + + \pgfmathtruncatemacro{\lasttask}{\thread+(\n+1)*\T} + \draw[-] (t\lasttask) -- (\curcoord |- ts0.south) -- +(0,-4mm); + } + \end{scope} + + \begin{scope}[common/overlay] + \pgfmathtruncatemacro{\n}{ceil((2*\N-1)/\T)} + \pgfmathtruncatemacro{\nr}{(\N-1)/\T} + \pgfmathtruncatemacro{\ftlt}{\nr*\T} + \node[orch/task=black,draw=none,fill=none](lt) at (t\ftlt) {}; + + \draw[decorate,decoration={brace, amplitude=4pt, raise=9mm, mirror}] + (t0.north) -- (t0.south) node[left,midway,xshift=-11mm] {$2\,D$}; + + \draw[decorate,decoration={brace, amplitude=4pt, raise=9mm, mirror}] + (lt.north) -- (lt.south) node[left,midway,xshift=-11mm] {$D$}; + + \draw[decorate,decoration={brace, amplitude=4pt, raise=18mm, mirror}] + (t0.north) -- (lt.south) node[left,midway,xshift=-20mm] {$\n{}\,D$}; + \end{scope} +\end{tikzpicture} diff --git a/src/fig/alsk/exec/executor/sequential.tex b/src/fig/alsk/exec/executor/sequential.tex new file mode 100644 index 0000000..92c54bf --- /dev/null +++ b/src/fig/alsk/exec/executor/sequential.tex @@ -0,0 +1,34 @@ +\tikzsetnextfilename{execution_policy_serial}% +\begin{tikzpicture} + \input{src/tikz/orchestration} + + \begin{scope}[overlay] + \node(origin){}; + \node at (origin) (t-1) {}; + \end{scope} + + \begin{scope}[node distance=4mm and 4mm] + \node[orch/task=black,below=of t-1] (t0) {m\textsubscript{0}}; + \node[orch/task=white,draw=none,fill=none,below=of t0] (t1) {}; + \node[orch/task=black,below=of t1] (t2) {m\textsubscript{N-1}}; + + \node[above=of t0] (label0) {\en{thread}~0}; + \node[below=of t2] (endthread0) {}; + \end{scope} + + \draw[-] (label0) -- (t0); + + \draw[-] (t0) -- (t1); + \draw[dashed] (t1.north) -- (t1.south); + \draw[-] (t1) -- (t2); + + \draw[-] (t2) -- (endthread0); + + \begin{scope}[common/overlay] + \draw[decorate,decoration={brace, amplitude=4pt, raise=9mm, mirror}] + (t0.north) -- (t0.south) node[left,midway,xshift=-11mm] {$D$}; + + \draw[decorate,decoration={brace, amplitude=4pt, raise=17mm, mirror}] + (t0.north) -- (t2.south) node[left,midway,xshift=-19mm] {$N \times D$}; + \end{scope} +\end{tikzpicture} diff --git a/src/fig/alsk/exec/ident/graspels.tex b/src/fig/alsk/exec/ident/graspels.tex new file mode 100644 index 0000000..45e020d --- /dev/null +++ b/src/fig/alsk/exec/ident/graspels.tex @@ -0,0 +1,61 @@ +\tikzsetnextfilename{grasp_els_tree_task_ids}% +\begin{tikzpicture} + \input{src/tikz/alsk} + + \begin{scope}[alsk/treestyle, + level distance=14mm, sibling distance=24mm, + every node/.style=leaf, + ] + \node[branch](farmsel1){farmsel\textsubscript{1}} + child{ node[branch](serial1){serial\textsubscript{1}} + child{ node(ch){HC} } + child{ node[branch](serial2){serial\textsubscript{2}} + child{ node(init ls){RLI} } + child{ node[branch](itersel){itersel} + child{ node[branch](farmsel2){farmsel\textsubscript{2}} + child{ node[branch](serial3){serial\textsubscript{3}} + child{ node(m){M} } + child{ node(ls){RL} } + } + child{ node(sel3){Sel\textsubscript{3}} } + } + child{ node(sel2){Sel\textsubscript{2}} } + } + } + } + child{ node(sel1){Sel\textsubscript{1}} }; + \end{scope} + + % task count + \path (farmsel1) -- node[left=2mm]{$a \times$} (serial1); + \path (farmsel2) -- node[left=2mm]{$b \times$} (serial3); + + % identifiers + \begin{scope}[ + ->,dashed,rounded corners=1mm, + frame/.style={draw,rectangle,rounded corners=1mm,dashed,fit=#1} + ] + \coordinate (yline) at ($(farmsel1)+(40mm,0)$); + + \draw (farmsel1) -- (farmsel1 -| yline) node(n9t){}; + \draw (sel1) -- (sel1 -| yline) node(n9b){}; + + \draw (serial3) |- ++(0,-6mm) -- (\curcoord -| yline) node(nbt){}; + \node[frame=(m)(ls)] (fserial3) {}; + \draw (fserial3) -- (fserial3 -| yline) node(nbb){}; + + \draw (serial1) |- ++(0,-6mm) -- (\curcoord -| yline) node(nat){}; + \node[frame=(ch)(itersel)(farmsel2)(sel2)] (fserial1) {}; + \draw (fserial1) -- (fserial1 -| yline); + \draw (sel3) -- (sel3 -| yline) node(nab){}; + \end{scope} + + \begin{scope}[decoration={brace,amplitude=4pt,raise=4pt}] + \draw[decorate] (n9t.north) -- (n9b.south) node[midway,right=10pt] + {$\text{id} = 0$}; + \draw[decorate] (nat.north) -- (nab.south) node[midway,right=10pt] + {$\text{id} \in \{0, b, 2\,b, \dots, (a-1)\,b\}$}; + \draw[decorate] (nbt.north) -- (nbb.south) node[midway,right=10pt] + {$\text{id} \in \displaystyle\bigcup^{a-1}_{k=0} \llbracket k\,b, (k+1)\,b-1 \rrbracket$}; + \end{scope} +\end{tikzpicture} diff --git a/src/fig/alsk/exec/impl/threadpool.tex b/src/fig/alsk/exec/impl/threadpool.tex new file mode 100644 index 0000000..bb824a2 --- /dev/null +++ b/src/fig/alsk/exec/impl/threadpool.tex @@ -0,0 +1,44 @@ +\tikzsetnextfilename{thread_pool}% +\begin{tikzpicture} + \input{src/tikz/exec} + + % pool + \begin{scope}[start chain=circle placed {at=(\tikzchaincount*72+18:.8)}] + \foreach \i in {0,...,4} { + \node[on chain,exec/thread=black] (t\i) {t\textsubscript{\i}}; + } + \end{scope} + \path (t0) |- node[pos=.2,exec/thread=black]{t\textsubscript{n}} ($(t2)!.5!(t3)$); + + \begin{scope}[on background layer] + \node[exec/pool=(t0)(t1)(t2)(t3)(t4)](pool){}; + \end{scope} + \path (pool.north west) -- node[above]{\en{pool}} (pool.north east); + + % fifo + \node[exec/fifonode,draw=none,fill=none,above=of t0](fifo3) {}; + \begin{scope}[node distance=-.1mm,start chain=fifo] + \foreach \i/\p in {2/3,1/2,0/1} { \node[exec/fifonode,left=of fifo\p] (fifo\i) {}; } + \foreach \i/\p in {4/3,5/4,6/5} { \node[exec/fifonode,right=of fifo\p] (fifo\i) {}; } + \end{scope} + \draw[-,densely dotted] ([yshift=-.05mm]fifo3.north west) -- ([yshift=-.05mm]fifo3.north east); + \draw[-,densely dotted] ([yshift=+.05mm]fifo3.south west) -- ([yshift=+.05mm]fifo3.south east); + + \node[exec/fit=(fifo0)(fifo6),fit margins={left=2.25mm},dash phase=2](mutex) {}; + \path (mutex.north west) + -- node[above](lblmutex){accès en section critique (\en{mutex})} (mutex.north east); + + % arrows + \draw[-] ([xshift=-8mm]fifo0.west) -- node[left=1em]{\phantom{p}soumission de tâches} (fifo0.west); + + \draw[->,rounded corners] (fifo6.east) + -- ++(4mm,0) + |- node[pos=.25,right,align=center](lblpool){acquisition d'une tâche\\par un \en{thread} disponible} + (pool.east); + + \node[exec/fit=(lblmutex)(mutex)(pool)(lblpool),densely dashed](threadpool) {}; + \path (threadpool.north west) -- node[above]{\en{thread pool}} (threadpool.north east); + + \draw[-,dotted,thick] (pool.west) -- (pool -| threadpool.west) + -- node[left,black!70]{(résultats éventuels)} ++(-2mm,0); +\end{tikzpicture} diff --git a/src/fig/alsk/exec/intro/diagram.tex b/src/fig/alsk/exec/intro/diagram.tex new file mode 100644 index 0000000..3b53342 --- /dev/null +++ b/src/fig/alsk/exec/intro/diagram.tex @@ -0,0 +1,56 @@ +\tikzsetnextfilename{internal_system_diagram}% +\begin{tikzpicture} + \input{src/tikz/diagram} + + \begin{scope}[node distance=1ex and 26mm] + \node[diagram/element=black] (impl) {implémentation}; + \node[diagram/element=black,below=of impl] (traits) {\en{traits}}; + \node[diagram/element=black,below=of traits] (skel) {squelette}; + + \node[diagram/fit=(impl)(traits)(skel)] (bone) {}; + \node[above] at (bone.north) {os}; + + \node[diagram/element=black,right=of skel] (execute) {système interne}; + + \node[diagram/element=white,draw=none,fill=none,right=of impl] (block) {}; + \node[diagram/element=black,right=of block] (policy) {politique\\d'exécution}; + + \node[minimum height=12mm,below=of policy] (inherit center) {}; + \node[diagram/small element=white,draw=none,fill=none,below=of inherit center] (ep) {\dots}; + \node[node distance=4mm,diagram/small element=black,left=of ep] (epl) {PE\textsubscript{0}}; + \node[node distance=4mm,diagram/small element=black,right=of ep] (epr) {PE\textsubscript{n}}; + \end{scope} + + \path (execute.north west) -- coordinate[pos=.33](execute i0) (execute.south west); + \path (execute.north west) -- coordinate[pos=.66](execute i1) (execute.south west); + + \path (impl.north east) -- coordinate[pos=.25](impl o0) (impl.south east); + \path (impl.north east) -- coordinate[pos=.75](impl o1) (impl.south east); + + \path (policy.north west) -- coordinate[pos=.25](policy i0) (policy.south west); + \path (policy.north west) -- coordinate[pos=.50](policy i1) (policy.south west); + \path (policy.north west) -- coordinate[pos=.75](policy i2) (policy.south west); + + \draw[diagram/arrow] (impl o1) -| ($(impl)!.5!(execute)$) node[right]{1. génère} |- (execute i0); + \draw[diagram/arrow] (impl o0) + -- node[above] {2. appelle (avec en argument)} + node[pos=.66](lbl2){} + (policy i0); + \draw[diagram/arrow] (execute) |- node[pos=.75](arg){} (policy i1); + + \draw[diagram/arrow,thick,densely dotted,-] (lbl2.north) -- (lbl2 |- arg); + + \draw[diagram/arrow] (policy i2) -| ($(policy)!.5!(execute)$) node[left]{3. utilise} |- (execute); + + \draw[diagram/arrow] (execute i1) -- node[below]{4. implémente} (execute i1 -| bone.east); + + \draw[diagram/inherit] (inherit center.center) -- (policy); + \draw[diagram/arrow,-] (inherit center.center) + [sharp corners]|- ($(inherit center.center)!.2!(epl)$) + [rounded corners]-| (epl); + + \coordinate(epr anchor) at ($(inherit center.center)!.2!(epr)$); + \draw[diagram/arrow,-] (inherit center.center |- epr anchor) + [sharp corners]|- (epr anchor) [rounded corners]-| (epr); + \draw[diagram/arrow,-,densely dashed] (inherit center.center) -- (ep); +\end{tikzpicture} diff --git a/src/fig/alsk/relwork/forkjoin.tex b/src/fig/alsk/relwork/forkjoin.tex new file mode 100644 index 0000000..1391d99 --- /dev/null +++ b/src/fig/alsk/relwork/forkjoin.tex @@ -0,0 +1,25 @@ +\tikzsetnextfilename{forkjoin_pattern}% +\begin{tikzpicture} + \input{src/tikz/parallel} + + \begin{scope}[node distance=8mm] + \begin{scope}[start chain=going below,every node/.style={on chain,join},parallel/arrow] + \node[parallel/block] (input) {entrée}; + \node[parallel/point] (fork) {}; + \node[parallel/task] (main) {T\textsubscript{0}}; + \node[parallel/point] (join) {}; + \node[parallel/block] (output) {sortie}; + \end{scope} + + \node[parallel/task,right=of main] (thread) {T\textsubscript{1}}; + \end{scope} + + \begin{scope}[on background layer] + \node[parallel/region=(main)(thread)] {}; + \end{scope} + + \begin{scope}[parallel/arrow] + \draw (fork) -| node[above,pos=.2]{\en{fork}} (thread); + \draw (thread) |- node[below,pos=.8]{\en{join}} (join); + \end{scope} +\end{tikzpicture} diff --git a/src/fig/alsk/relwork/map.tex b/src/fig/alsk/relwork/map.tex new file mode 100644 index 0000000..edf44ad --- /dev/null +++ b/src/fig/alsk/relwork/map.tex @@ -0,0 +1,24 @@ +\tikzsetnextfilename{map_pattern}% +\begin{tikzpicture} + \input{src/tikz/parallel} + + \def\N{5} + \pgfmathtruncatemacro{\n}{\N-1} + + \coordinate(e-1) at (0,0); + + \begin{scope}[node distance=8mm] + \foreach \i in {0,...,\n} { + \begin{scope}[start chain=going below,every node/.style={on chain,join,parallel/block},parallel/arrow] + \pgfmathtruncatemacro{\p}{\i-1} + \node[right=of e\p](e\i) {entrée\textsubscript{\i}}; + \node[parallel/task] (t\i) {T}; + \node {sortie\textsubscript{\i}}; + \end{scope} + } + \end{scope} + + \begin{scope}[on background layer] + \node[parallel/region=(t0)(t\n)] {}; + \end{scope} +\end{tikzpicture} diff --git a/src/fig/alsk/relwork/pipeline.tex b/src/fig/alsk/relwork/pipeline.tex new file mode 100644 index 0000000..deaaa5a --- /dev/null +++ b/src/fig/alsk/relwork/pipeline.tex @@ -0,0 +1,27 @@ +\tikzsetnextfilename{pipeline_pattern}% +\begin{tikzpicture} + \input{src/tikz/parallel} + + \def\N{5} + \pgfmathtruncatemacro{\n}{\N-1} + + \begin{scope}[node distance=8mm] + \begin{scope}[start chain=t,every node/.style={on chain,join,parallel/task},parallel/arrow] + \foreach \i in {0,...,\n} { + \node {T\textsubscript{\i}}; + } + \end{scope} + + \node[parallel/block,left=of t-1] (input) {entrée}; + \node[parallel/block,right=of t-\N] (output) {sortie}; + \end{scope} + + \begin{scope}[on background layer] + \node[parallel/region=(t-1)(t-\N)] {}; + \end{scope} + + \begin{scope}[parallel/arrow] + \draw (input) -- (t-1); + \draw (t-\N) -- (output); + \end{scope} +\end{tikzpicture} diff --git a/src/fig/alsk/repeat/intro/unique_stream.tex b/src/fig/alsk/repeat/intro/unique_stream.tex new file mode 100644 index 0000000..9a35250 --- /dev/null +++ b/src/fig/alsk/repeat/intro/unique_stream.tex @@ -0,0 +1,32 @@ +\tikzsetnextfilename{multithread_unique_random_sequence}% +\begin{tikzpicture} + \input{src/tikz/repeat} + + \tikzset{t0/.style=repeat/thread0} + \tikzset{t1/.style=repeat/thread1} + + \matrix[row sep=4mm, column sep=1.8mm] (m) { + \node[t1](l0){12}; & \node[t0]{2}; & \node[t1]{34}; & \node[t0] {85}; & + \node[t1] {4}; & \node[t0]{91}; & \node[t1]{29}; & \node[t0] {85}; & + \node[t1] {98}; & \node[t0]{3}; & \node[t0]{35}; & \node[t1](r0){65}; \\ + + \node[t0](l1){12}; & \node[t0]{2}; & \node[t1]{34}; & \node[t1] {85}; & + \node[t0] {4}; & \node[t1]{91}; & \node[t1]{29}; & \node[t0] {85}; & + \node[t1] {98}; & \node[t0]{3}; & \node[t1]{35}; & \node[t0](r1){65}; \\ + }; + + \begin{scope}[node distance=2mm] + \node[left=of l0] {exécution 1}; + \node[left=of l1] {exécution 2}; + \end{scope} + + \draw[dashed] ($(l0.south)!.5!(l1.north)$) -- ($(r0.south)!.5!(r1.north)$); + + \node[t0,node distance=8mm,below=of m.south west,anchor=west] (thread0) {}; + \node[node distance=1mm,right=of thread0] {tâche 0 sur le \en{thread} 0}; + + \node[t1,node distance=8mm,below=of m.south east,anchor=east] (thread1) {}; + \node[node distance=1mm,left=of thread1] {tâche 1 sur le \en{thread} 1}; + + \draw ($(l1.south)!.5!(thread0.north)$) -- ($(r1.south)!.5!(thread1.north)$); +\end{tikzpicture} diff --git a/src/fig/alsk/repeat/opti/mergingtasksets.tex b/src/fig/alsk/repeat/opti/mergingtasksets.tex new file mode 100644 index 0000000..4eadea1 --- /dev/null +++ b/src/fig/alsk/repeat/opti/mergingtasksets.tex @@ -0,0 +1,58 @@ +\tikzsetnextfilename{tasksets_merging}% +\begin{tikzpicture} + \input{src/tikz/repeat} + + \def\N{9} + \def\setsa{1,10} + \def\setsb{1,6,10} + \def\setsc{1,4,7,10} + \def\setsd{1,4,6,8,10} + \def\setsr{1,4,6,7,8,10} + + \begin{scope}[start chain=a,start chain=b,start chain=c,start chain=d,start chain=r, + every node/.style=repeat/opti/element,node distance=3mm and 3mm] + \node[on chain=a] {$m_0$}; + \node[on chain=b,below=of a-1] {$m_0$}; + \node[on chain=c,below=of b-1] {$m_0$}; + \node[on chain=d,below=of c-1] {$m_0$}; + \node[on chain=r,node distance=8mm,below=of d-1] {$m_0$}; + + \foreach \i in {2,...,\N} { + \pgfmathtruncatemacro{\c}{\i-1} + \node[on chain=a] {$m_\c$}; + \node[on chain=b] {$m_\c$}; + \node[on chain=c] {$m_\c$}; + \node[on chain=d] {$m_\c$}; + \node[on chain=r] {$m_\c$}; + } + \end{scope} + + \begin{scope}[node distance=2mm] + \foreach[count=\i] \line in {a,b,c,d} { + \node[left=of \line-1]{$S_\i$ : }; + } + \node[left=of r-1]{$S$ : }; + \end{scope} + + \begin{scope}[on background layer] + \foreach \line in {a,b,c,d} { + \edef\sets{\csname sets\line\endcsname} + \foreach[remember=\i as \p (initially 0)] \i in \sets { + \ifthenelse{\p = 0}{}{ + \pgfmathtruncatemacro{\c}{\i-1} + \node[repeat/opti/fit=(\line-\p)(\line-\c)]{}; + + \ifthenelse{\i < \N \OR \i = \N}{ + \draw[repeat/opti/sep] ($(\line-\c.north)!.5!(\line-\i.north)$) -- ($(r-\c.south)!.5!(r-\i.south)$); + }{} + } + } + } + \foreach[remember=\i as \p (initially 0)] \i in \setsr { + \ifthenelse{\p = 0}{}{ + \pgfmathtruncatemacro{\c}{\i-1} + \node[repeat/opti/fit=(r-\p)(r-\c)]{}; + } + } + \end{scope} +\end{tikzpicture} diff --git a/src/fig/alsk/repeat/repeat/grasp.tex b/src/fig/alsk/repeat/repeat/grasp.tex new file mode 100644 index 0000000..bc3a6a1 --- /dev/null +++ b/src/fig/alsk/repeat/repeat/grasp.tex @@ -0,0 +1,101 @@ +\tikzsetnextfilename{grasp_links_with_context}% +\begin{tikzpicture}[node distance=10mm] + \input{src/tikz/alsk} + + % Serial + \begin{scope}[every node/.style=alsk/links/muscle] + \node(init) {HC}; + \node[below=of init] (ls) {RL}; + \end{scope} + + \path (init.north west) -- coordinate[pos=.33](init i0) coordinate[pos=.66](init i1) (init.south west); + \path (ls.north west) -- coordinate[pos=.25](ls i0) coordinate[pos=.5](ls i1) coordinate[pos=.75] (ls i2) (ls.south west); + + \begin{scope}[every node/.style={alsk/links/sig=colLinkIn}] + \node[left=of init i0] (sp0) {\texttt{P\textsubscript{0}}}; + \end{scope} + \begin{scope}[every node/.style={alsk/links/sig=colLinkOut}] + \node[right=of init] (sr0) {\texttt{R\textsubscript{0}}}; + \node[right=of ls] (sr1) {\texttt{R\textsubscript{1}}}; + \end{scope} + + \coordinate (ls i0 left) at ($(ls i0)+(-3mm,0)$); + \coordinate (sr0 right) at ($(sr0.east)+(1.5mm,0)$); + + \begin{scope}[alsk/links/link] + \draw (sp0.east) -- (init i0); + \draw (init) -- (sr0); + + \draw (sr0.east) -- (sr0 right) |- ($(sr0 right)!.5!(ls.west)$) -| (ls i0 left) -- (ls i0); + \draw (sp0.east) -| ($(ls i2)!.8!(sp0.east)$) |- (ls i2); + + \draw (ls) -- (sr1); + \end{scope} + + \begin{scope}[on background layer] + \node[alsk/links/fit=(sp0)(init)(ls)(sr0)(sr1)] (serial) {}; + \end{scope} + \node[node distance=0,above=of serial.north west,anchor=south west] (seriallbl) {Serial ($n$ fois)}; + + \coordinate (serial left) at ($(serial.south west)+(0,2.5mm)$); + \coordinate (serial bottom) at ($(serial.south west)+(2.5mm,0)$); + + % FarmSel + \node[alsk/links/muscle,below=of serial](select) {Select}; + + \coordinate (select i0) at (select.west); + + \begin{scope}[every node/.style={alsk/links/sig=colLinkIn}] + \node[left=of sp0] (fsp0) {\texttt{P\textsubscript{0}}}; + \end{scope} + + \begin{scope}[alsk/links/link] + \draw (fsp0) -- (sp0); + \end{scope} + + \coordinate (select i0 left) at ($(select i0)+(-4mm,0)$); + \coordinate (sr1 right) at ($(sr1.east)+(5.5mm,0)$); + + \draw[alsk/links/link] (sr1.east) + -- (sr1 right) + |- ($(sr1 right)!.5!(select.west)$) + -| node[pos=.75,left] {$n$ fois} (select i0 left) + -- (select i0); + + \draw[->] (select.east) -- +(right:25.5mm) node[right] {Solution}; + + \begin{scope}[on background layer] + \node[alsk/links/fit=(fsp0)(serial)(seriallbl)(select)] (farmsel) {}; + \end{scope} + \node[node distance=0,above=of farmsel.north west,anchor=south west] (farmsellbl) {FarmSel}; + + % Input + \node[node distance=5mm,left=of fsp0] (problem) {Problème}; + + \begin{scope}[alsk/links/link] + \draw (problem) -- (fsp0); + \end{scope} + + % Context + \node[alsk/links/sig=colLinkCtx,node distance=20mm and -8mm,below left=of problem](rng) {\texttt{RNG}}; + \begin{scope}[on background layer] + \node[alsk/links/fit=(rng)] (context) {}; + \end{scope} + \node[node distance=0,above=of context.north west,anchor=south west] {Context}; + + \begin{scope}[alsk/links/link] + \path (init) -- coordinate(minitls) (ls); + \coordinate (fsw) at (minitls -| farmsel.west); + + \path (sr0.east) -- (sr0 right) |- ($(sr0 right)!.5!(ls.west)$) -| (ls i0 left) + coordinate[pos=.75](pr0ls) -- (ls i0); + \path (sp0.east) -| ($(ls i2)!.8!(sp0.east)$) |- coordinate[pos=.25](pp0ls) (ls i2); + \path (pr0ls -| pp0ls) -- coordinate(rngfork) (pr0ls); + + \coordinate (fswin) at (fsw |- rngfork); + + \draw (rng.east) -- (context) -| ($(context.east)!.5!(fsw)$) |- (fswin); + \draw (fswin) -| ($(rngfork)!.5!(rngfork |- ls i1)$) |- (ls i1); + \draw (fswin) -| ($(rngfork)!.5!(rngfork |- init i1)$) |- (init i1); + \end{scope} +\end{tikzpicture} diff --git a/src/fig/alsk/repeat/repeat/parallel_streams.tex b/src/fig/alsk/repeat/repeat/parallel_streams.tex new file mode 100644 index 0000000..f7f9bd0 --- /dev/null +++ b/src/fig/alsk/repeat/repeat/parallel_streams.tex @@ -0,0 +1,49 @@ +\tikzsetnextfilename{multithread_per_thread_random_sequences}% +\begin{tikzpicture} + \input{src/tikz/repeat} + + \tikzset{s0/.style={}} + \tikzset{s1/.style={text=red!70!black}} + + \tikzset{t0/.style={repeat/thread0,s0}} + \tikzset{t1/.style={repeat/thread1,s0}} + \tikzset{t2/.style={repeat/thread1,s1}} + + \matrix[row sep=4mm, column sep=1.8mm, row 2/.style={row sep=1mm}] (m) { + \node[t0](l0){12}; & \node[t0]{2}; & \node[t0]{34}; & \node[t0] {85}; & + \node[t0] {4}; & \node[t0]{91}; & \node[t1]{29}; & \node[t1] {85}; & + \node[t1] {98}; & \node[t1]{3}; & \node[t1]{35}; & \node[t1](r0){65}; \\ + + \node[t0](l1){12}; & \node[t0]{2}; & \node[t0]{34}; & \node[t0] {85}; & + \node[t0] {4}; & \node[t0](r1){91}; \\ + \node[t2](l2){8}; & \node[t2] {14}; & \node[t2] {6}; & \node[t2]{13}; & + \node[t2]{31}; & \node[t2](r2){22}; \\ + }; + + \node[t0,draw=none,fill=none] (l1-2) at ($(l1)!.5!(l2)$) {}; + + \begin{scope}[node distance=2mm] + \node[left=of l0] {\#\en{threads}\,=\,\num{1}}; + \node[left=of l1-2] {\#\en{threads}\,=\,\num{2}}; + \end{scope} + + \draw[dashed] ($(l0.south)!.5!(l1.north)$) -- (\curcoord -| r0.south |- r1.north); + + \node[t0,draw=none,fill=none] (l2') at (l2) {}; + \draw[decorate,decoration={brace,amplitude=4pt,raise=1mm,mirror}] (l1.north west) -- (l2'.south west); + + % First labels + \node[t0,node distance=8mm,below=of m.south west,anchor=west] (task0) {}; + \node[node distance=1mm,right=of task0] {tâche \num{0}}; + + \node[t1,node distance=8mm,below=of m.south east,anchor=east] (task1) {}; + \node[node distance=1mm,left=of task1] {tâche \num{1}}; + + \draw ($(l2.south)!.5!(task0.north)$) -- (\curcoord -| r0.south |- task1.north); + + % Second labels + \node[s0,node distance=4mm,below=of task0,anchor=west] + {flux \num{0} : $12, 2, 34, 85, 4, 91, 29, 85, 98, 3, 35, 65, \dots$}; + \node[s1,node distance=8mm,below=of task0,anchor=west] + {\textcolor{black}{flux \num{1} :} $8, 14, 6, 13, 31, 22, \dots$}; +\end{tikzpicture} diff --git a/src/fig/gnx/spe/newarray.tex b/src/fig/gnx/spe/newarray.tex new file mode 100644 index 0000000..20950b7 --- /dev/null +++ b/src/fig/gnx/spe/newarray.tex @@ -0,0 +1,19 @@ +\tikzsetnextfilename{newarray}% +\begin{tikzpicture}[scale=1] + \draw (0, 0) rectangle (8, 1); + \draw (2, 0) -- (2, 1); + \foreach \i in {3,...,7} { + \draw[dotted] (\i, 0) -- (\i, 1); + } + + \node at (1, .5) {6}; + + \draw[->] (0, -.5) node[below, text centered, text width=5em] {pointeur réel} -- (0, -.1); + \draw[->] (2, -.5) node[below, text centered, text width=5em] {pointeur renvoyé} -- (2, -.1); + + \draw[decorate, decoration={brace, amplitude=4pt, raise=2pt}] (0, 1) -- (2, 1) node[above, + midway, yshift=1ex] {nombre d'instances}; + + \draw[decorate, decoration={brace, amplitude=4pt, raise=2pt}] (2, 1) -- (8, 1) node[above, + midway, yshift=1ex] {instances}; +\end{tikzpicture} diff --git a/src/fig/mp/mpt/et/et_ma.tex b/src/fig/mp/mpt/et/et_ma.tex new file mode 100644 index 0000000..1e7b301 --- /dev/null +++ b/src/fig/mp/mpt/et/et_ma.tex @@ -0,0 +1,7 @@ +\tikzsetnextfilename{expression_tree}% +\begin{tikzpicture} + \input{src/tikz/et} + \begin{scope}[et/treestyle] + \Tree[.$+$ $v_0$ [.$\times$ $v_1$ $v_2$ ] ] + \end{scope} +\end{tikzpicture} diff --git a/src/fig/par/auto/alu.tex b/src/fig/par/auto/alu.tex new file mode 100644 index 0000000..3ae2a0f --- /dev/null +++ b/src/fig/par/auto/alu.tex @@ -0,0 +1,39 @@ +\tikzsetnextfilename{alu}% +\begin{tikzpicture} + \def\w{.5} + \def\W{3} + \def\h{1} + \def\H{4} + \def\Hr{2} + + \pgfmathsetmacro{\Hl}{(\H-\h)/2} + \pgfmathsetmacro{\hh}{\h/2} + \pgfmathsetmacro{\dr}{(\Hr-\H)/2} + + \path[common/drawfill=black,rounded corners=.5mm] + (0,0) -- node(i1){} ++(0,\Hl) -- ++(\w,\hh) -- ++(-\w,\hh) -- node(i0){} ++(0,\Hl) + -- node(if){} ++(\W,\dr) -- node(o){} ++(0,-\Hr) -- node(of){} + cycle; + + \begin{scope}[rounded corners=.5mm] + \draw (i0.center) -- node(i0m){} ++(-1,0) node[left] {$E_0$}; + \draw (i1.center) -- node(i1m){} ++(-1,0) node[left] {$E_1$}; + \draw (if.center) -- ++(0,1) -- ++(-1,0) node[left] {$E_f$}; + \draw (of.center) -- ++(0,-1) -- ++(1,0) node[right] {$S_f$}; + \draw (o.center) -- node(om){} ++(1,0) node[right] {$S$}; + \end{scope} + + \node[shift={(-.25,-.4)}] at (i0m.center) (i0b) {}; + \node[shift={(+.25,+.4)}] at (i0m.center) (i0a) {}; + \draw (i0a) -- node[shift={(0,-.1)},below] {$n$} (i0b); + + \node[shift={(-.25,-.4)}] at (i1m.center) (i1b) {}; + \node[shift={(+.25,+.4)}] at (i1m.center) (i1a) {}; + \draw (i1a) -- node[shift={(0,-.1)},below] {$n$} (i1b); + + \node[shift={(-.25,-.4)}] at (om.center) (ob) {}; + \node[shift={(+.25,+.4)}] at (om.center) (oa) {}; + \draw (oa) -- node[shift={(0,-.1)},below] {$n$} (ob); + + \node at (\W/2, \H/2) {\glsxtrshort{UAL}}; +\end{tikzpicture} diff --git a/src/fig/par/auto/ast.tex b/src/fig/par/auto/ast.tex new file mode 100644 index 0000000..8c24f8d --- /dev/null +++ b/src/fig/par/auto/ast.tex @@ -0,0 +1,18 @@ +\tikzsetnextfilename{ast}% +\begin{tikzpicture} + \input{src/tikz/ast} + + \begin{scope}[ast/treestyle] + \Tree + [.instructions + [.$=$ i 10 ] + [.$=$ r 0 ] + [.while + [.$\neq$ i 0 ] + [.$=$ r [.$+$ r i ] ] + [.$=$ i [.$-$ i 1 ] ] + ] + [.return r ] + ] + \end{scope} +\end{tikzpicture} diff --git a/src/fig/par/auto/autoparstep.tex b/src/fig/par/auto/autoparstep.tex new file mode 100644 index 0000000..af70870 --- /dev/null +++ b/src/fig/par/auto/autoparstep.tex @@ -0,0 +1,25 @@ +\tikzsetnextfilename{automatic_parallelization_steps}% +\begin{tikzpicture} + %{{{ style " + \tikzset{cell/.style={ + common/drawfill=black, + rectangle,rounded corners=2mm, + minimum width=50mm,minimum height=15mm, + align=center + }} + \tikzset{arrow/.style={ + ->,>=stealth,thick + }} + %}}} + + \begin{scope}[start chain=chain going below,every node/.style={on chain,join,cell},arrow] + \node{Analyse lexicale\\et syntaxique}; + \node{Analyse des dépendances}; + \node{Génération de code}; + \end{scope} + + \begin{scope}[arrow,every node/.style={align=center}] + \draw ([xshift=-10mm]chain-1.west) node[left]{programme\\séquentiel} -- (chain-1); + \draw (chain-3) -- ([xshift=+10mm]chain-3.east) node[right]{programme\\parallèle}; + \end{scope} +\end{tikzpicture} diff --git a/src/fig/par/auto/ilp_pipeline.tex b/src/fig/par/auto/ilp_pipeline.tex new file mode 100644 index 0000000..16f0bf3 --- /dev/null +++ b/src/fig/par/auto/ilp_pipeline.tex @@ -0,0 +1,15 @@ +\tikzsetnextfilename{pipeline}% +\begin{tikzpicture} + \foreach \i/\lbl in {0/IF,1/ID,2/Ex,3/MA,4/WB} { + \foreach \j in {0,...,4} { + \ifthenelse{\i = \j}{\colorlet{colFill}{black!30}}{\colorlet{colFill}{black!5}} + \draw[fill=colFill] (\i-\j, \j) rectangle node {\lbl} (\i-\j+1, \j+1); + } + } + + \draw[->] (.5, 5.5) node[above] {étape courante} -- (.5, 5.1); + \draw[->] (-4, -.5) -- (5, -.5) node[right] {temps}; + + \draw[decorate, decoration={brace, amplitude=8pt, raise=10pt}] (-4, 0) -- (-4, 5) node[left, + midway, xshift=-2em] {instructions}; +\end{tikzpicture} diff --git a/src/fig/par/comp/distributedmem.tex b/src/fig/par/comp/distributedmem.tex new file mode 100644 index 0000000..47d68da --- /dev/null +++ b/src/fig/par/comp/distributedmem.tex @@ -0,0 +1,27 @@ +\tikzsetnextfilename{distributed_memory}% +\begin{tikzpicture} + \input{src/tikz/arch} + + \def\margin{.2} + \def\wcpu{1.5} + \def\imem{.5} + \def\hbus{.75} + \def\dbuscpu{1} + \def\wmem{2} + \def\dcpumem{.5} + + \pgfmathsetmacro{\W}{4*\wmem+3*\imem+2*\margin} + + \foreach \i/\n in {0/0,1/1,3/n} { + \path[arch/cell] ({\margin+\i*(\wmem+\imem)}, \hbus+\dbuscpu+\wcpu+\dcpumem) + rectangle node (mem\i) {Mémoire\textsubscript{\n}} +(\wmem, \wmem); + \path[arch/cell] ([shift={(0, -\wmem/2)}]mem\i.center) -- node(lmem\i){} +(0, -\dcpumem); + + \path[arch/cell] ({\margin+\i*(\wmem+\imem)+(\wmem-\wcpu)/2}, \hbus+\dbuscpu) + rectangle node (cpu\i) {\glsxtrshort{CPU}\textsubscript{\n}} +(\wcpu, \wcpu); + \path[arch/cell] ([shift={(0, -\wcpu/2)}]cpu\i.center) -- +(0, -\dbuscpu); + } + + \path (lmem1) -- node {...} (lmem3); + \path[arch/cell] (0, 0) rectangle node {Communication (réseau, ...)} +(\W, \hbus); +\end{tikzpicture} diff --git a/src/fig/par/comp/list_insert.tex b/src/fig/par/comp/list_insert.tex new file mode 100644 index 0000000..f87a601 --- /dev/null +++ b/src/fig/par/comp/list_insert.tex @@ -0,0 +1,57 @@ +\tikzsetnextfilename{linked_list_insertion}% +\begin{tikzpicture} + \input{src/tikz/list} + + %{{{ t=1 " + \def\t{1} + \begin{scope}[list/arrow,every node/.style=list/node] + \begin{scope}[start chain=\t,every node/.append style={on chain,join}] + \node{a}; \node{b}; \node{c}; + \end{scope} + \draw ([xshift=-5mm]\t-1.west) -- (\t-1); + \end{scope} + %}}} + %{{{ t=2 " + \def\t{2} + \pgfmathtruncatemacro{\p}{\t-1} + \begin{scope}[list/arrow,every node/.style=list/node] + \begin{scope}[start chain=\t,every node/.append style={on chain}] + \node[list/far below=of \p-1]{a}; \node{b}; \node{c}; + \end{scope} + \node[list/near below=of \t-1](\t-x){x}; + + \draw ([xshift=-5mm]\t-x.west) -- (\t-x); + + \draw ([xshift=-5mm]\t-1.west) -- (\t-1); + \draw ($(\t-1.north east)!.33!(\t-1.south east)$) -- ($(\t-2.north west)!.33!(\t-2.south west)$); + \draw (\t-2) -- (\t-3); + + \coordinate (\t-2-x) at ($(\t-2.north west)!.66!(\t-2.south west)$); + \draw (\t-x) -| ($(\t-x.east)!.5!(\t-2-x)$) |- (\t-2-x); + \end{scope} + %}}} + %{{{ t=3 " + \def\t{3} + \pgfmathtruncatemacro{\p}{\t-1} + \begin{scope}[list/arrow,every node/.style=list/node] + \begin{scope}[start chain=\t,every node/.append style={on chain,join}] + \node[list/far below=of \p-x]{a}; \node{x}; \node{b}; \node{c}; + \end{scope} + \draw ([xshift=-5mm]\t-1.west) -- (\t-1); + \end{scope} + %}}} + %{{{ hlines " + \coordinate (hline end) at (3-4); + \draw ($(1-1)!.5!(2-1)$) -- (\curcoord -| hline end); + \draw ($(2-x)!.5!(3-1)$) -- (\curcoord -| hline end); + %}}} + %{{{ time and numbers " + \draw[list/arrow] ([xshift=-10mm]1-1.north west) -- ([xshift=-10mm]3-1.south west) + node[below]{temps}; + + \coordinate (lbl) at ([xshift=15mm]\t-4); + \foreach \i in {1,...,3} { + \node at (\i-1 -| lbl) {(\i)}; + } + %}}} +\end{tikzpicture} diff --git a/src/fig/par/comp/list_insert_bad_sync.tex b/src/fig/par/comp/list_insert_bad_sync.tex new file mode 100644 index 0000000..4e0c0fd --- /dev/null +++ b/src/fig/par/comp/list_insert_bad_sync.tex @@ -0,0 +1,113 @@ +\tikzsetnextfilename{bad_linked_list_insertion}% +\begin{tikzpicture} + \input{src/tikz/list} + + %{{{ t=1 " + \def\t{1} + \begin{scope}[list/arrow,every node/.style=list/node] + \begin{scope}[start chain=\t,every node/.append style={on chain,join}] + \node{a}; \node{b}; \node{c}; + \end{scope} + \draw ([xshift=-5mm]\t-1.west) -- (\t-1); + \end{scope} + %}}} + %{{{ t=2 " + \def\t{2} + \pgfmathtruncatemacro{\p}{\t-1} + \begin{scope}[list/arrow,every node/.style=list/node] + \begin{scope}[start chain=\t,every node/.append style={on chain}] + \node[list/far below=of \p-1]{a}; \node{b}; \node{c}; + \end{scope} + \node[list/near below=of \t-1](\t-x){x}; + + \draw ([xshift=-5mm]\t-x.west) -- (\t-x); + + \draw ([xshift=-5mm]\t-1.west) -- (\t-1); + \draw ($(\t-1.north east)!.33!(\t-1.south east)$) -- ($(\t-2.north west)!.33!(\t-2.south west)$); + \draw (\t-2) -- (\t-3); + + \coordinate (\t-2-x) at ($(\t-2.north west)!.66!(\t-2.south west)$); + \draw (\t-x) -| ($(\t-x.east)!.5!(\t-2-x)$) |- (\t-2-x); + \end{scope} + %}}} + %{{{ t=3 " + \def\t{3} + \pgfmathtruncatemacro{\p}{\t-1} + \begin{scope}[list/arrow,every node/.style=list/node] + \begin{scope}[start chain=\t,every node/.append style={on chain}] + \node[list/far below=of \p-x]{a}; \node{b}; \node{c}; + \end{scope} + \node[list/near below=of \t-1](\t-x){x}; + \node[list/near below=of \t-x](\t-y){y}; + + \draw ([xshift=-5mm]\t-x.west) -- (\t-x); + \draw ([xshift=-5mm]\t-y.west) -- (\t-y); + + \draw ([xshift=-5mm]\t-1.west) -- (\t-1); + \draw ($(\t-1.north east)!.25!(\t-1.south east)$) -- ($(\t-2.north west)!.25!(\t-2.south west)$); + \draw (\t-2) -- (\t-3); + + \coordinate (\t-2-x) at ($(\t-2.north west)!.50!(\t-2.south west)$); + \draw (\t-x) -| ($(\t-x.east)!.33!(\t-2-x)$) |- (\t-2-x); + + \coordinate (\t-2-y) at ($(\t-2.north west)!.75!(\t-2.south west)$); + \draw (\t-y) -| ($(\t-y.east)!.66!(\t-2-y)$) |- (\t-2-y); + \end{scope} + %}}} + %{{{ t=4 " + \def\t{4} + \pgfmathtruncatemacro{\p}{\t-1} + \begin{scope}[list/arrow,every node/.style=list/node] + \begin{scope}[start chain=\t,every node/.append style={on chain}] + \node[list/far below=of \p-y]{a}; \node{x}; \node{b}; \node{c}; + \end{scope} + \node[list/near below=of \t-1,draw=none,fill=none](\t-t){}; + \node[list/near below=of \t-2](\t-y){y}; + + \draw ([xshift=-5mm]\t-y.west) -- (\t-y); + + \draw ([xshift=-5mm]\t-1.west) -- (\t-1); + \draw (\t-1) -- (\t-2); + \draw ($(\t-2.north east)!.33!(\t-2.south east)$) -- ($(\t-3.north west)!.33!(\t-3.south west)$); + \draw (\t-3) -- (\t-4); + + \coordinate (\t-3-y) at ($(\t-3.north west)!.66!(\t-3.south west)$); + \draw (\t-y) -| ($(\t-y.east)!.5!(\t-3-y)$) |- (\t-3-y); + \end{scope} + %}}} + %{{{ t=5 " + \def\t{5} + \pgfmathtruncatemacro{\p}{\t-1} + \begin{scope}[list/arrow,every node/.style=list/node] + \begin{scope}[start chain=\t,every node/.append style={on chain}] + \node[list/far below=of \p-t]{a}; \node{y}; \node{b}; \node{c}; + \end{scope} + \node[list/near below=of \t-1,draw=none,fill=none](\t-t){}; + \node[list/near below=of \t-2](\t-x){x}; + + \draw ([xshift=-5mm]\t-1.west) -- (\t-1); + \draw (\t-1) -- (\t-2); + \draw ($(\t-2.north east)!.33!(\t-2.south east)$) -- ($(\t-3.north west)!.33!(\t-3.south west)$); + \draw (\t-3) -- (\t-4); + + \coordinate (\t-3-x) at ($(\t-3.north west)!.66!(\t-3.south west)$); + \draw (\t-x) -| ($(\t-x.east)!.5!(\t-3-x)$) |- (\t-3-x); + \end{scope} + %}}} + %{{{ hlines " + \coordinate (hline end) at (4-4); + \draw ($(1-1)!.5!(2-1)$) -- (\curcoord -| hline end); + \draw ($(2-x)!.5!(3-1)$) -- (\curcoord -| hline end); + \draw ($(3-y)!.5!(4-1)$) -- (\curcoord -| hline end); + \draw ($(4-t)!.5!(5-1)$) -- (\curcoord -| hline end); + %}}} + %{{{ time and numbers " + \draw[list/arrow] ([xshift=-10mm]1-1.north west) -- ([xshift=-10mm]5-t.south west) + node[below]{temps}; + + \coordinate (lbl) at ([xshift=15mm]\t-4); + \foreach \i in {1,...,5} { + \node at (\i-1 -| lbl) {(\i)}; + } + %}}} +\end{tikzpicture} diff --git a/src/fig/par/comp/list_insert_bad_sync_ocg.tex b/src/fig/par/comp/list_insert_bad_sync_ocg.tex new file mode 100644 index 0000000..74e72f4 --- /dev/null +++ b/src/fig/par/comp/list_insert_bad_sync_ocg.tex @@ -0,0 +1,145 @@ +\tikzsetnextfilename{bad_linked_list_insertion_step}% +\begin{tikzpicture} + \input{src/tikz/list} + + \def\ocgp{tikz-par-list-insert-bad} + + %{{{ t=1 " + \def\t{1} + \begin{ocg}[radiobtngrp=\ocgp-r]{\ocgp-\t}{\ocgp-\t}{1} + \begin{scope}[list/arrow,every node/.style=list/node] + \begin{scope}[start chain=\t,every node/.append style={on chain,join}] + \node{a}; \node{b}; \node{c}; + \end{scope} + \draw ([xshift=-5mm]\t-1.west) -- (\t-1); + \end{scope} + \end{ocg} + %}}} + %{{{ t=2 " + \def\t{2} + \pgfmathtruncatemacro{\p}{\t-1} + \begin{ocg}[radiobtngrp=\ocgp-r]{\ocgp-\t}{\ocgp-\t}{0} + \begin{scope}[list/arrow,every node/.style=list/node] + \begin{scope}[start chain=\t,every node/.append style={on chain}] + \node{a}; \node{b}; \node{c}; + \end{scope} + \ocgnodestyle{\ocgp-\t-x}{list/near below=of \t-1}{common/drawfill=red}{(\t-x)}{x} + + \draw ([xshift=-5mm]\t-x.west) -- (\t-x); + + \draw ([xshift=-5mm]\t-1.west) -- (\t-1); + \draw ($(\t-1.north east)!.33!(\t-1.south east)$) -- ($(\t-2.north west)!.33!(\t-2.south west)$); + \draw (\t-2) -- (\t-3); + + \coordinate (\t-2-x) at ($(\t-2.north west)!.66!(\t-2.south west)$); + \ocgpathstyle{\ocgp-\t-pb}{draw}{red}{(\t-x) -| ($(\t-x.east)!.5!(\t-2-x)$) |- (\t-2-x)} + \end{scope} + \end{ocg} + %}}} + %{{{ t=3 " + \def\t{3} + \pgfmathtruncatemacro{\p}{\t-1} + \begin{ocg}[radiobtngrp=\ocgp-r]{\ocgp-\t}{\ocgp-\t}{0} + \begin{scope}[list/arrow,every node/.style=list/node] + \begin{scope}[start chain=\t,every node/.append style={on chain}] + \node{a}; \node{b}; \node{c}; + \end{scope} + \node[list/near below=of \t-1](\t-x){x}; + \ocgnodestyle{\ocgp-\t-y}{list/near below=of \t-x}{common/drawfill=red}{(\t-y)}{y} + + \draw ([xshift=-5mm]\t-x.west) -- (\t-x); + \draw ([xshift=-5mm]\t-y.west) -- (\t-y); + + \draw ([xshift=-5mm]\t-1.west) -- (\t-1); + \draw ($(\t-1.north east)!.25!(\t-1.south east)$) -- ($(\t-2.north west)!.25!(\t-2.south west)$); + \draw (\t-2) -- (\t-3); + + \coordinate (\t-2-x) at ($(\t-2.north west)!.50!(\t-2.south west)$); + \draw (\t-x) -| ($(\t-x.east)!.33!(\t-2-x)$) |- (\t-2-x); + + \coordinate (\t-2-y) at ($(\t-2.north west)!.75!(\t-2.south west)$); + \ocgpathstyle{\ocgp-\t-pb}{draw}{red}{(\t-y) -| ($(\t-y.east)!.66!(\t-2-y)$) |- (\t-2-y)} + \end{scope} + \end{ocg} + %}}} + %{{{ t=4 " + \def\t{4} + \pgfmathtruncatemacro{\p}{\t-1} + \begin{ocg}[radiobtngrp=\ocgp-r]{\ocgp-\t}{\ocgp-\t}{0} + \begin{scope}[list/arrow,every node/.style=list/node] + \begin{scope}[start chain=\t,every node/.append style={on chain}] + \node{a}; \node{x}; \node{b}; \node{c}; + \end{scope} + \node[list/near below=of \t-1,draw=none,fill=none](\t-t){}; + \node[list/near below=of \t-2](\t-y){y}; + + \draw ([xshift=-5mm]\t-y.west) -- (\t-y); + + \draw ([xshift=-5mm]\t-1.west) -- (\t-1); + \draw (\t-1) -- (\t-2); + \ocgpathstyle{\ocgp-\t-px}{draw}{red}{(\t-1) -- (\t-2)} + \draw ($(\t-2.north east)!.33!(\t-2.south east)$) -- ($(\t-3.north west)!.33!(\t-3.south west)$); + \draw (\t-3) -- (\t-4); + + \coordinate (\t-3-y) at ($(\t-3.north west)!.66!(\t-3.south west)$); + \draw (\t-y) -| ($(\t-y.east)!.5!(\t-3-y)$) |- (\t-3-y); + \end{scope} + \end{ocg} + %}}} + %{{{ t=5 " + \def\t{5} + \pgfmathtruncatemacro{\p}{\t-1} + \begin{ocg}[radiobtngrp=\ocgp-r]{\ocgp-\t}{\ocgp-\t}{0} + \begin{scope}[list/arrow,every node/.style=list/node] + \begin{scope}[start chain=\t,every node/.append style={on chain}] + \node{a}; \node{y}; \node{b}; \node{c}; + \end{scope} + \node[list/near below=of \t-1,draw=none,fill=none](\t-t){}; + \ocgnodestyle{\ocgp-\t-x}{list/near below=of \t-2}{common/drawfill=red}{(\t-x)}{x} + + \draw ([xshift=-5mm]\t-1.west) -- (\t-1); + \ocgpathstyle{\ocgp-\t-py}{draw}{red}{(\t-1) -- (\t-2)} + \draw ($(\t-2.north east)!.33!(\t-2.south east)$) -- ($(\t-3.north west)!.33!(\t-3.south west)$); + \draw (\t-3) -- (\t-4); + + \coordinate (\t-3-x) at ($(\t-3.north west)!.66!(\t-3.south west)$); + \draw (\t-x) -| ($(\t-x.east)!.5!(\t-3-x)$) |- (\t-3-x); + \end{scope} + \end{ocg} + %}}} + %{{{ buttons " + \node[above=of 1-1](steps-0){Étapes~:}; + \begin{scope}[every node/.style={list/btn},node distance=2mm] + \foreach \i in {1,...,\t} { + \pgfmathtruncatemacro{\p}{\i-1} + \pgfmathtruncatemacro{\on}{\i == 1} + \ocgtikzrbtn{\ocgp-r}{\ocgp-\i}{\on}{right=of steps-\p}{steps-\i}{\i} + } + \end{scope} + %}}} + %{{{ texts " + \node[fit=(\t-1)(\t-4)(2-x)](box){}; + \ocgitemize{[below=of box]}{.8\linewidth}{ + \ocgitem{\ocgp-1,\ocgp-2,\ocgp-3,\ocgp-4,\ocgp-5}{[Étape 1] + La liste chaînée possède initialement trois éléments. + } + \ocgitem{\ocgp-2,\ocgp-3,\ocgp-4,\ocgp-5}{[Étape 2] + Le premier \en{thread} \switchocg{\ocgp-2-x}{crée $x$} et le + \switchocg{\ocgp-2-pb}{fait pointer sur $b$}. + } + \ocgitem{\ocgp-3,\ocgp-4,\ocgp-5}{[Étape 3] + Le premier \en{thread} est préempté, le second démarre son exécution. + Il \switchocg{\ocgp-3-y}{crée $y$} et le \switchocg{\ocgp-3-pb}{fait pointer sur $b$}. + } + \ocgitem{\ocgp-4,\ocgp-5}{[Étape 4] + Le premier \en{thread} reprend son exécution et + \switchocg{\ocgp-4-px}{fait pointer $a$ sur $x$}. + } + \ocgitem{\ocgp-5}{[Étape 5] + Enfin, le second \en{thread} termine son exécution en faisant à son tour + \switchocg{\ocgp-5-py}{pointer $a$ sur $y$}. + En conséquence, l'\switchocg{\ocgp-5-x}{élément $x$} n'est plus accessible. + } + } + %}}} +\end{tikzpicture} diff --git a/src/fig/par/comp/list_insert_ocg.tex b/src/fig/par/comp/list_insert_ocg.tex new file mode 100644 index 0000000..0acc4eb --- /dev/null +++ b/src/fig/par/comp/list_insert_ocg.tex @@ -0,0 +1,80 @@ +\tikzsetnextfilename{linked_list_insertion_step}% +\begin{tikzpicture} + \input{src/tikz/list} + + \def\ocgp{tikz-par-list-insert} + + %{{{ t=1 " + \def\t{1} + \begin{ocg}[radiobtngrp=\ocgp-r]{\ocgp-\t}{\ocgp-\t}{1} + \begin{scope}[list/arrow,every node/.style=list/node] + \begin{scope}[start chain=\t,every node/.append style={on chain,join}] + \node{a}; \node{b}; \node{c}; + \end{scope} + \draw ([xshift=-5mm]\t-1.west) -- (\t-1); + \end{scope} + \end{ocg} + %}}} + %{{{ t=2 " + \def\t{2} + \pgfmathtruncatemacro{\p}{\t-1} + \begin{ocg}[radiobtngrp=\ocgp-r]{\ocgp-\t}{\ocgp-\t}{0} + \begin{scope}[list/arrow,every node/.style=list/node] + \begin{scope}[start chain=\t,every node/.append style={on chain}] + \node{a}; \node{b}; \node{c}; + \end{scope} + \ocgnodestyle{\ocgp-\t-x}{list/near below=of \t-1}{common/drawfill=red}{(\t-x)}{x} + + \draw ([xshift=-5mm]\t-x.west) -- (\t-x); + + \draw ([xshift=-5mm]\t-1.west) -- (\t-1); + \draw ($(\t-1.north east)!.33!(\t-1.south east)$) -- ($(\t-2.north west)!.33!(\t-2.south west)$); + \draw (\t-2) -- (\t-3); + + \coordinate (\t-2-x) at ($(\t-2.north west)!.66!(\t-2.south west)$); + \ocgpathstyle{\ocgp-\t-pb}{draw}{red}{(\t-x) -| ($(\t-x.east)!.5!(\t-2-x)$) |- (\t-2-x)} + \end{scope} + \end{ocg} + %}}} + %{{{ t=3 " + \def\t{3} + \pgfmathtruncatemacro{\p}{\t-1} + \begin{ocg}[radiobtngrp=\ocgp-r]{\ocgp-\t}{\ocgp-\t}{0} + \begin{scope}[list/arrow,every node/.style=list/node] + \begin{scope}[start chain=\t,every node/.append style={on chain,join}] + \node{a}; \node{x}; \node{b}; \node{c}; + \end{scope} + \ocgnodestyle{\ocgp-\t-a}{}{common/drawfill=red}{(\t-a) at (\t-1)}{a} + + \draw ([xshift=-5mm]\t-1.west) -- (\t-1); + \ocgpathstyle{\ocgp-\t-px}{draw}{red}{(\t-1) -- (\t-2)} + \end{scope} + \end{ocg} + %}}} + %{{{ buttons " + \node[above=of 1-1](steps-0){Étapes~:}; + \begin{scope}[every node/.style={list/btn},node distance=2mm] + \foreach \i in {1,...,\t} { + \pgfmathtruncatemacro{\p}{\i-1} + \pgfmathtruncatemacro{\on}{\i == 1} + \ocgtikzrbtn{\ocgp-r}{\ocgp-\i}{\on}{right=of steps-\p}{steps-\i}{\i} + } + \end{scope} + %}}} + %{{{ texts " + \node[fit=(\t-1)(\t-4)(2-x)](box){}; + \ocgitemize{[node distance=0mm,below=of box]}{.8\linewidth}{ + \ocgitem{\ocgp-1,\ocgp-2,\ocgp-3}{[Étape 1] + La liste chaînée possède initialement trois éléments. + } + \ocgitem{\ocgp-2,\ocgp-3}{[Étape 2] + Un \switchocg{\ocgp-2-x}{nouvel élément $x$} est créé et + \switchocg{\ocgp-2-pb}{pointe sur $b$}. + } + \ocgitem{\ocgp-3}{[Étape 3] + Le \switchocg{\ocgp-3-a}{premier élément $a$} est modifié pour + \switchocg{\ocgp-3-px}{pointer sur $x$}. + } + } + %}}} +\end{tikzpicture} diff --git a/src/fig/par/comp/mimd.tex b/src/fig/par/comp/mimd.tex new file mode 100644 index 0000000..f171786 --- /dev/null +++ b/src/fig/par/comp/mimd.tex @@ -0,0 +1,60 @@ +\ifthenelse{\ninstr=1}{\def\tikzfnbase{simd}}{\def\tikzfnbase{mimd}}% +\tikzsetnextfilename{\tikzfnbase_\ndata x\ninstr}% +\begin{tikzpicture} + \input{src/tikz/mimd} + + \pgfmathtruncatemacro{\nlastdata}{\ndata-1} + \pgfmathtruncatemacro{\nlastinstr}{\ninstr-1} + + \def\w{.5} + \def\iw{2} + \def\dmargin{1} + \pgfmathsetmacro{\h}{\w+\dmargin} + + %{{{ main " + \foreach \data in {0,...,\nlastdata} { + \pgfmathsetmacro{\dpos}{\data*\h} + + \draw[mimd/arrow] (-\w*2, \dpos+\w/2) node[shift={(-.25,-\h/2)}] (d\data) {} -- (0, \dpos+\w/2); + } + \node(d\ndata) at (-\w*2-.25, \ndata*\h+\w/2-\h) {}; + + \foreach \instr in {0,...,\nlastinstr} { + \pgfmathsetmacro{\ipos}{\instr*\iw} + + \ifthenelse{\instr = \nlastinstr}{ + \pgfmathsetmacro{\arrw}{\w} + }{ + \pgfmathsetmacro{\arrw}{\iw-\w} + } + + \foreach \data in {0,...,\nlastdata} { + \pgfmathsetmacro{\dpos}{\data*\h} + + \draw (\ipos, \dpos) rectangle +(\w, \w); + % \draw[mimd/arrow,<-] (\ipos+\w/2, \dpos+\w) |- ++(0, \w/2) -- +(\w, 0); + \draw[mimd/arrow] (\ipos+\w*1.5, \dpos+\w*1.5) -| ++(-\w, -\w/2); + + \ifthenelse{\instr = 0}{ + }{ + \draw[mimd/arrow] (-2*\w, {\dpos-\instr*(\dmargin-\w/2)/\ninstr}) + -- ++(2*\w+\w/2-\iw/2+\ipos, 0) + -- ++(0, {\w/2+\instr*(\dmargin-\w/2)/\ninstr}) + -- ++(\iw/2-\w/2, 0); + } + } + + \draw (\ipos+\w*1.5, \w) -- +(0, \ndata*\h-\dmargin) node[shift={(0,+.25)}] (i\instr) {}; + } + %}}} + %{{{ labels " + \def\lbldata{flux de données} + \def\lblinstr{flux d'instructions} + + \node[mimd/vlabel=\lbldata,fit=(d0)(d\ndata)](lbldata){}; + \node[rotate=90] at (lbldata) {\lbldata}; + + \node[mimd/hlabel=\lblinstr,fit=(i0)(i\nlastinstr)](lblinstr){}; + \node at (lblinstr) {\lblinstr}; + %}}} +\end{tikzpicture} diff --git a/src/fig/par/comp/numa.tex b/src/fig/par/comp/numa.tex new file mode 100644 index 0000000..d4663ca --- /dev/null +++ b/src/fig/par/comp/numa.tex @@ -0,0 +1,38 @@ +\tikzsetnextfilename{numa}% +\begin{tikzpicture} + \input{src/tikz/arch} + + \def\margin{.2} + + \def\wmem{2} + \def\dhmem{.5} + \def\dbusmem{.5} + + \def\wcpu{1.5} + \pgfmathsetmacro\icpu{\wmem+\dhmem} + + \def\hbus{.75} + + \pgfmathsetmacro\redux{\wmem} + \pgfmathsetmacro{\W}{4*\wcpu+3*\icpu+2*\margin-\redux} + + \foreach \i/\n in {0/0,1/1,3/n} { + \pgfmathsetmacro{\bx}{\margin+\i*(\wcpu+\icpu)} + \ifthenelse{\i = 3}{ + \pgfmathsetmacro{\x}{\bx-\redux} + }{ + \pgfmathsetmacro{\x}{\bx} + } + + \draw[arch/cell] ({\x+(\wmem-\wcpu)/2}, \hbus+2*\dbusmem+\wmem) rectangle node (cpu\i) + {\glsxtrshort{CPU}\textsubscript{\n}} +(\wcpu, \wcpu); + \draw[arch/cell] ([shift={(0, -\wcpu/2)}]cpu\i.center) -- node(lcpu\i){} +(0, -2*\dbusmem-\wmem); + + \draw[arch/cell] ({\x-\wmem/2-\dhmem}, \hbus+\dbusmem) rectangle node (mem\i) {Mémoire} +(\wmem, \wmem); + \draw[arch/cell] ([shift={(\wmem/2, 0)}]mem\i.center) -- +(\dhmem, 0); + } + % \path (lcpu1) -- node[shift={(0,\wmem/2)},pos=.4] {...} (lcpu3); + \path (cpu1) -- node {...} (cpu3); + + \draw[arch/cell] ({(\wmem-\wcpu)/2}, 0) rectangle node {Communication} +(\W, \hbus); +\end{tikzpicture} diff --git a/src/fig/par/comp/simd.tex b/src/fig/par/comp/simd.tex new file mode 100644 index 0000000..a86d58b --- /dev/null +++ b/src/fig/par/comp/simd.tex @@ -0,0 +1,4 @@ +{% +\def\ninstr{1}% +\input{src/fig/par/comp/mimd}% +}% diff --git a/src/fig/par/comp/sisd.tex b/src/fig/par/comp/sisd.tex new file mode 100644 index 0000000..a45faef --- /dev/null +++ b/src/fig/par/comp/sisd.tex @@ -0,0 +1,26 @@ +\tikzsetnextfilename{sisd}% +\begin{tikzpicture} + \input{src/tikz/mimd} + + \def\w{.5} + \def\iw{4} + \def\dmargin{1} + \pgfmathsetmacro{\h}{\w+\dmargin} + + \begin{scope}[mimd/arrow] + \draw (-\w*2, \w/2) node[shift={(-.25,0)}] (d0) {} -- +(\w*2, 0); + \draw[<-] (\w/2, \w) -- +(0, 3.5em) node[shift={(0,.25)}] (i0) {}; + \draw (\w, \w/2) -- +(\w, 0); + \end{scope} + + \draw (0, 0) rectangle +(\w, \w); + + \def\lbldata{flux de données} + \def\lblinstr{flux d'instructions} + + \node[mimd/vlabel=\lbldata,fit=(d0)](lbldata){}; + \node[rotate=90] at (lbldata) {\lbldata}; + + \node[mimd/hlabel=\lblinstr,fit=(i0)](lblinstr){}; + \node at (lblinstr) {\lblinstr}; +\end{tikzpicture} diff --git a/src/fig/par/comp/thread_life_cycle.tex b/src/fig/par/comp/thread_life_cycle.tex new file mode 100644 index 0000000..2c1a287 --- /dev/null +++ b/src/fig/par/comp/thread_life_cycle.tex @@ -0,0 +1,22 @@ +\tikzsetnextfilename{thread_life_cycle}% +\begin{tikzpicture} + \input{src/tikz/sm} + + \begin{scope}[node distance=15mm] + \node[sm/state] (ready) {\strut prêt}; + \node[sm/near above=ready] (enter) {}; + \node[right=of ready] (spacer) {}; + \node[sm/state,right=of spacer] (blocked) {\strut bloqué}; + \node[sm/state,below=of spacer] (running) {\strut exécution}; + \node[sm/state,below=of running] (terminated) {\strut terminé}; + \end{scope} + + \begin{scope}[sm/arrow] + \draw (enter) node[above=-2mm]{création} -- (ready); + \draw ([xshift=-1mm]ready.south) |- node[left,pos=.25]{ordonnancé} ([yshift=-1mm]running.west); + \draw ([yshift=+1mm]running.west) -| node[right,pos=.75]{préempté} ([xshift=+1mm]ready.south); + \draw (running) -| node[right,pos=.75,text width=25mm]{\setstretch{1}attente d'une ressource} (blocked); + \draw (blocked) -- node[above]{\setstretch{1}fin d'attente} (ready); + \draw (running) -- node[right,text width=15mm]{fini ou annulé} (terminated); + \end{scope} +\end{tikzpicture} diff --git a/src/fig/par/comp/uma.tex b/src/fig/par/comp/uma.tex new file mode 100644 index 0000000..d8ccb5e --- /dev/null +++ b/src/fig/par/comp/uma.tex @@ -0,0 +1,26 @@ +\tikzsetnextfilename{uma}% +\begin{tikzpicture} + \input{src/tikz/arch} + + \def\margin{.2} + \def\wcpu{1.5} + \def\icpu{.5} + \def\hbus{.75} + \def\dbuscpu{1} + \def\wmem{2} + \def\dbusmem{1} + + \pgfmathsetmacro{\W}{4*\wcpu+3*\icpu+2*\margin} + + \foreach \i/\n in {0/0,1/1,3/n} { + \draw[arch/cell] ({\margin+\i*(\wcpu+\icpu)}, \hbus+\dbuscpu) rectangle node (cpu\i) + {\glsxtrshort{CPU}\textsubscript{\n}} +(\wcpu, \wcpu); + \draw[arch/cell] ([shift={(0, -\wcpu/2)}]cpu\i.center) -- +(0, -\dbuscpu); + } + \path (cpu1) -- node {...} (cpu3); + + \draw[arch/cell] (0, 0) rectangle node {Bus} +(\W, \hbus); + + \draw[arch/cell] ({(\W-\wmem)/2}, -\dbusmem) rectangle node (mem) {Mémoire} +(\wmem, -\wmem); + \draw[arch/cell] ([shift={(0, \wmem/2)}]mem.center) -- +(0, \dbusmem); +\end{tikzpicture} diff --git a/src/fig/pfor/detection/expression.tex b/src/fig/pfor/detection/expression.tex new file mode 100644 index 0000000..ebe8e93 --- /dev/null +++ b/src/fig/pfor/detection/expression.tex @@ -0,0 +1,7 @@ +\tikzsetnextfilename{expression_tree}% +\begin{tikzpicture} + \input{src/tikz/et} + \begin{scope}[et/treestyle] + \Tree[.\texttt{+} a b ]; + \end{scope} +\end{tikzpicture} diff --git a/src/fig/pfor/detection/indices.tex b/src/fig/pfor/detection/indices.tex new file mode 100644 index 0000000..13a5f7c --- /dev/null +++ b/src/fig/pfor/detection/indices.tex @@ -0,0 +1,17 @@ +\tikzsetnextfilename{indices_tree}% +\begin{tikzpicture} + \input{src/tikz/et} + + \begin{scope}[et/treestyle] + \Tree[., + [.= + [.\texttt{[]} a i ] + [.\texttt{*} [.\texttt{[]} a i ] [.\texttt{[]} b i ] ] + ] + [.= + [.\texttt{[]} c i ] + [.\texttt{[]} c [.\texttt{+} i 1 ] ] + ] + ]; + \end{scope} +\end{tikzpicture} diff --git a/src/fig/pfor/detection/linearadd.tex b/src/fig/pfor/detection/linearadd.tex new file mode 100644 index 0000000..e16ced2 --- /dev/null +++ b/src/fig/pfor/detection/linearadd.tex @@ -0,0 +1,19 @@ +\tikzsetnextfilename{linear_add_tree}% +\begin{tikzpicture} + \input{src/tikz/et} + + \begin{scope}[et/treestyle] + \begin{scope}[xshift=0mm] + \Tree[.\texttt{+} [.\texttt{*} i a ] \node(lhs b){b}; ] + \end{scope} + \begin{scope}[xshift=40mm] + \Tree[.\texttt{+} [.\node(rhs *){\texttt{*}}; i c ] \node(rhs d){d}; ] + \end{scope} + \begin{scope}[xshift=80mm] + \Tree[.\texttt{+} [.\node(res *){\texttt{*}}; i a+c ] b+d ] + \end{scope} + \end{scope} + + \path (lhs b) -- node{$+$} (rhs *); + \path (rhs d) -- node{$=$} (res *); +\end{tikzpicture} diff --git a/src/fig/pfor/detection/linearmul.tex b/src/fig/pfor/detection/linearmul.tex new file mode 100644 index 0000000..7f9696b --- /dev/null +++ b/src/fig/pfor/detection/linearmul.tex @@ -0,0 +1,19 @@ +\tikzsetnextfilename{linear_mul_tree}% +\begin{tikzpicture} + \input{src/tikz/et} + + \begin{scope}[et/treestyle] + \begin{scope}[xshift=0mm] + \Tree[.\texttt{+} [.\texttt{*} i a ] \node(lhs b){b}; ] + \end{scope} + \begin{scope}[xshift=40mm,edge from parent/.style={draw=none}] + \Tree[ \node(rhs d){d}; ] + \end{scope} + \begin{scope}[xshift=80mm] + \Tree[.\texttt{+} [.\node(res *){\texttt{*}}; i a$\,$d ] b$\,$d ] + \end{scope} + \end{scope} + + \path (lhs b) -- node{$\times$} (rhs d); + \path (rhs d) -- node{$=$} (res *); +\end{tikzpicture} diff --git a/src/fig/pfor/detection/linearnoopti.tex b/src/fig/pfor/detection/linearnoopti.tex new file mode 100644 index 0000000..afead48 --- /dev/null +++ b/src/fig/pfor/detection/linearnoopti.tex @@ -0,0 +1,22 @@ +\tikzsetnextfilename{linear_noopti_tree}% +\begin{tikzpicture} + \input{src/tikz/et} + + \begin{scope}[et/treestyle] + \begin{scope}[xshift=0mm] + \Tree[.\texttt{+} [.\texttt{*} i a ] \node(lhs b){b}; ] + \end{scope} + \begin{scope}[xshift=40mm] + \Tree[.\texttt{+} [.\node(rhs *){\texttt{*}}; i c ] \node(rhs d){d}; ] + \end{scope} + \begin{scope}[xshift=90mm] + \Tree[.\texttt{+} + [.\node(res +){\texttt{+}}; [.\texttt{*} i a ] b ] + [.\texttt{+} [.\texttt{*} i c ] d ] + ] + \end{scope} + \end{scope} + + \path (lhs b) -- node{$+$} (rhs *); + \path (rhs d) -- node{$=$} (res +); +\end{tikzpicture} diff --git a/src/fig/pfor/detection/lineartrees.tex b/src/fig/pfor/detection/lineartrees.tex new file mode 100644 index 0000000..cd241dd --- /dev/null +++ b/src/fig/pfor/detection/lineartrees.tex @@ -0,0 +1,22 @@ +\tikzsetnextfilename{linear_trees}% +\begin{tikzpicture} + \input{src/tikz/et} + + \begin{scope}[et/treestyle] + \begin{scope}[xshift=0mm] + \Tree[.b ] + \end{scope} + \begin{scope}[xshift=20mm] + \Tree[.i ] + \end{scope} + \begin{scope}[xshift=40mm] + \Tree[.\texttt{*} i a ] + \end{scope} + \begin{scope}[xshift=65mm] + \Tree[.\texttt{+} i b ] + \end{scope} + \begin{scope}[xshift=95mm] + \Tree[.\texttt{+} [.\texttt{*} i a ] b ] + \end{scope} + \end{scope} +\end{tikzpicture} diff --git a/src/fig/pfor/detection/multiinstr.tex b/src/fig/pfor/detection/multiinstr.tex new file mode 100644 index 0000000..62afbb5 --- /dev/null +++ b/src/fig/pfor/detection/multiinstr.tex @@ -0,0 +1,22 @@ +\tikzsetnextfilename{multi_instructions_tree}% +\begin{tikzpicture} + \input{src/tikz/et} + + \begin{scope}[et/treestyle,level 1/.style={sibling distance=5mm}] + \Tree[.\texttt{,} + [.\node(i0 affect){\texttt{=}}; \node(i0 lhs){a}; [.\texttt{+} b \node(i0 rhs){c}; ] ] + [.\node(i1 affect){\texttt{=}}; \node(i1 lhs){d}; [.\texttt{+} d \node(i1 rhs){1}; ] ] + [.\node(i2 affect){\texttt{=}}; \node(i2 lhs){b}; [.\texttt{*} 2 \node(i2 rhs){b}; ] ] + ]; + \end{scope} + + \node[et/frame=(i0 affect)(i0 lhs)(i0 rhs)] (i0) {}; + \node[et/frame=(i1 affect)(i1 lhs)(i1 rhs)] (i1) {}; + \node[et/frame=(i2 affect)(i2 lhs)(i2 rhs)] (i2) {}; + + \begin{scope}[node distance=0mm] + \node[below=of i0]{instruction 0}; + \node[below=of i1]{instruction 1}; + \node[below=of i2]{instruction 2}; + \end{scope} +\end{tikzpicture} diff --git a/src/fig/pfor/results/imgpro.tex b/src/fig/pfor/results/imgpro.tex new file mode 100644 index 0000000..4e832c0 --- /dev/null +++ b/src/fig/pfor/results/imgpro.tex @@ -0,0 +1,63 @@ +\tikzsetnextfilename{image_processing}% +\begin{tikzpicture} + \input{src/tikz/imgpro} + + \def\W{11} + \def\H{6} + \def\S{8mm} + \def\E{.25mm} + + \draw[step=\S] (0,0) grid (\W*\S,\H*\S); + + \begin{scope} + \clip (0,0) rectangle (\W*\S,\H*\S); + + \pgfmathtruncatemacro{\lasty}{\H-2} + \pgfmathtruncatemacro{\lastx}{\W-1} + \foreach \y in {1,...,\lasty} { + \pgfmathtruncatemacro{\firstx}{mod(\y, 2)} + \pgfmathtruncatemacro{\secondx}{\firstx+2} + \foreach \x in {\firstx,\secondx,...,\lastx} { + \pgfmathsetmacro{\X}{\x+.5} + \pgfmathsetmacro{\Y}{\lasty-\y+1+.5} + \pgfmathtruncatemacro{\ci}{mod(((1-\firstx)+(\x-\firstx)/2), 3)} + \ifthenelse{\ci=0} + {\def\lcolor{red}} + {\ifthenelse{\ci=1} + {\def\lcolor{green}} + {\def\lcolor{blue}} + } + \def\lcolor{black} + \node[imgpro/pixel=\lcolor](n\y) at (\X*\S,\Y*\S) {}; + + \begin{scope}[->,>=stealth,draw=\lcolor!80!black] + \draw (\X*\S,\Y*\S) -- +(\S-\E,0); + \draw (\X*\S,\Y*\S) -- +(-\S+\E,0); + \draw (\X*\S,\Y*\S) -- +(0,\S-\E); + \draw (\X*\S,\Y*\S) -- +(0,-\S+\E); + \end{scope} + + \node[imgpro/centre=black] at (\X*\S,\Y*\S) {}; + } + } + \end{scope} + + % add border arrow ends + \begin{scope}[->,>=stealth] + \def\lcolor{red} + \def\lcolor{black} + \draw[\lcolor!80!black] (0*\S,0.5*\S) -- +(\S/2-\E,0); + \draw[\lcolor!80!black] (0*\S,2.5*\S) -- +(\S/2-\E,0); + + \def\lcolor{green} + \def\lcolor{black} + \draw[\lcolor!80!black] (\W*\S,2.5*\S) -- +(-\S/2+\E,0); + \draw[\lcolor!80!black] (\W*\S,4.5*\S) -- +(-\S/2+\E,0); + \end{scope} + + % Width and height + \draw[<->] (0,\H*\S+2mm) -- node[above]{$W=\W$} +(\W*\S,0); + \begin{scope}[common/overlay] + \draw[<->] (-2mm,0) -- node[above,rotate=90]{$H=\H$} +(0,\H*\S); + \end{scope} +\end{tikzpicture} diff --git a/src/gnx/0_intro.tex b/src/gnx/0_intro.tex new file mode 100644 index 0000000..8644773 --- /dev/null +++ b/src/gnx/0_intro.tex @@ -0,0 +1,59 @@ +\section{Introduction} + +%{{{ +Un programme consiste en une suite d'instructions devant être exécutées par une machine +(généralement un texte, quelque fois un diagramme, ...). +L'objectif d'un programme est le traitement d'informations, et pour ce faire il manipule des données +et produit un résultat (une sortie numérique, un affichage, l'envoi d'une trame sur le réseau, ...). + +Généralement, pour des applications scientifiques, il s'agit de résoudre un problème spécifique. +Cependant, il est souvent intéressant de généraliser le code écrit afin de résoudre une classe plus +large de problèmes et ainsi fournir un code réutilisable. +Pour prendre un exemple simple : s'il faut disposer d'une fonction $f(x) = x + 7$, fournir la +fonction $f(x, y) = x + y$ implémente le comportement désiré pour peu que $y = 7$, mais permet par +ailleurs de répondre à d'autres besoins très similaires. +Ce principe est à la base de la manière de concevoir une bibliothèque, puisque l'objectif est de la +rendre utilisable au sein de différents projets, mais plus généralement, il est appliqué autant +qu'il peut l'être\footnote{Tant que cela ne nuit pas aux performances.} car il évite une forme de +répétition du code. +En faisant cela, on applique une abstraction sur les données manipulées par un algorithme en les +acceptant comme des paramètres qui seront remplacés par des arguments fournis à l'exécution de cet +algorithme. + +La généricité est un paradigme de programmation consistant en une abstraction de certaines +informations dans la définition d'un algorithme ou d'une structure de donnée et permettant +l'écriture de codes plus réutilisables~\autocite{ref:musser1989}. +Ce concept a été implémenté de différentes manières par de nombreux langages de programmation. +La généricité permet l'écriture d'un code paramétré pour générer des éléments du langage tels que +des fonctions ou des classes (Java, C\#, Eiffel, ...). +Les abstractions possibles varient selon le langage et peuvent inclure en particulier celles de +types ou de valeurs. + +Le préfixe \og méta \fg{} (au-delà de) est ajouté devant le nom de l'élément généré pour désigner le +générateur qui décrit comment seront organisés et produits les éléments souhaités. +Dans le contexte de la métaprogrammation, le mot \og méta \fg{} peut être compris avec un sens de +\og description \fg{} et de \og conformité \fg{} à une référence. +Une métaclasse paramétrée décrit comment produire des classes, une métafonction décrit comment +produire des fonctions. + +La généricité existe en C++ au moyen des patrons. +Nous utiliserons le terme anglais \en{template} à partir de maintenant dans ce document. +Le terme patron pourra être retenu pour les patrons de conception~\autocite{ref:gamma1995}. +Comme nous l'avons signalé, une des particularités de l'implémentation proposée par le C++ est +d'être entièrement résolue durant la compilation du code source. +D'autres langages utilisent des techniques comme l'effacement de type (Java) ou la réification (C\#) +qui sont effectués à l'exécution du programme, et seulement une vérification de la validité de la +paramétrisation est effectuée lors de la compilation. +Que tout soit statique (évalué durant la compilation) impose des contraintes : la paramétrisation ne +pourra être faite qu'avec des données connues par le compilateur durant la compilation. +Mais cela apporte aussi des avantages, en particulier la possibilité d'utiliser la généricité sans +surcoût en temps d'exécution par rapport à un programme n'en faisant pas l'usage. + +Ce chapitre présente, sans être exhaustif, la généricité en C++. +Des ressources présentant plus complètement ces aspects existent, par exemple l'ouvrage \og +\encitetitle{ref:vandevoorde2017} \fg{}, pour les normes de C++ avant +C++11~\autocite{ref:vandevoorde2010} et sa seconde édition pour aller jusqu'à la norme +C++17~\autocite{ref:vandevoorde2017}. +Le chapitre suivant traite de la métaprogrammation qui, en C++, repose sur des constructions +permises par les templates. +%}}} diff --git a/src/gnx/1_cpp.tex b/src/gnx/1_cpp.tex new file mode 100644 index 0000000..9762d43 --- /dev/null +++ b/src/gnx/1_cpp.tex @@ -0,0 +1,36 @@ +\section{Templates} + +%{{{ +Les templates du C++ sont des fonctions, exécutées par le compilateur durant la compilation, +dont le résultat (jusqu'en C++20) peut être : +\begin{itemize} + \item une structure ou une classe ou une union ;%TODO métaunion + \item une fonction ; + \item une variable {\tiny (depuis C++14)}. +\end{itemize} + +Les templates permettent ainsi d'exprimer des métaclasses, des métafonctions et des +métavariables. +L'exécution d'un template (c'est-à-dire la génération de l'élément du langage) est appelée +instanciation. +À l'instar des fonctions (classiques), qui sont évaluées à l'exécution, les templates peuvent +accepter des arguments. +La syntaxe générale est présentée dans \acref{lst:gnx/cpp/syntax}. +Après cette ligne, il est possible d'écrire la déclaration ou la définition des différents résultats +possibles d'un template. + +\begin{listing} + \inputlst{cpp/syntax} + {Syntaxe générale pour démarrer la déclaration ou la définition d'un template} +\end{listing} + +Les paramètres de la liste sont séparés par une virgule, et indiqués entre chevrons. +L'utilisation des chevrons permet de différencier les paramètres statiques, utilisés à la +compilation des paramètres dynamiques, utilisés à l'exécution, qui, eux, sont entre parenthèses. +Chacun des paramètres peut être : +\begin{itemize} + \item une valeur ; + \item un type ; + \item un template. +\end{itemize} +%}}} diff --git a/src/gnx/2_ccpp.tex b/src/gnx/2_ccpp.tex new file mode 100644 index 0000000..e3b5453 --- /dev/null +++ b/src/gnx/2_ccpp.tex @@ -0,0 +1,107 @@ +\section{Comparaison avec le \glsxtrshort{CPP}} + +%{{{ +%{{{ +Les exemples présentés dans cette section vont dans un premier temps être suffisamment simples pour +qu'il soit possible de les implémenter en utilisant le \gls{CPP}. +Or, s'il est possible dans certains cas d'obtenir un résultat similaire avec ces macros, il convient +de rendre compte des nombreuses limites que celles-ci ont et qui sont levées avec les templates. + +Le premier exemple qui sera présenté permettra le calcul de la somme de deux entiers. +Une implémentation correcte d'une macro \gls{CPP} \cppinline{SUM} est présentée dans +\acref{lst:gnx/ccpp/sum}. + +\begin{listing} + \inputlst{ccpp/sum} + {Macro \glsxtrshort{CPP} \texttt{SUM} qui est remplacée par la somme de deux valeurs} +\end{listing} + +Un second exemple, \cppinline{MIN} (\cref{lst:gnx/ccpp/min}), trouvant le minimum de +deux valeurs sera également utilisé pour présenter certaines limites. + +\begin{listing} + \inputlst{ccpp/min} + {Macro \glsxtrshort{CPP} \texttt{MIN} qui est remplacée par la plus petite de deux valeurs} +\end{listing} +%}}} + +%{{{ +\subsection{Faiblement typé} + +Les types des paramètres ou le type du résultat produit ne sont pas précisés. +Ceci est dû à la manière de fonctionner du \gls{CPP} : il ne traite que de chaînes de caractères et +effectue des remplacements de texte. +En conséquence, il est possible d'exécuter la macro \cppinline{SUM} en fournissant des valeurs dont +les types sont incompatibles (par inexistence d'un opérateur \cppinline{+}), l'erreur n'étant +relevée que lors de l'utilisation de la macro et ce dans le contexte d'utilisation. + +Pour la même raison, rien ne prévient l'injection d'un code qui modifiera entièrement le +comportement de la macro. +\Acref{lst:gnx/ccpp/injection} montre comment changer l'opérateur \cppinline{+} en opérateur +\cppinline{*}. + +\begin{listing} + \inputlst{ccpp/injection} + {Injection de code dans une macro} +\end{listing} +%}}} + +%{{{ +\subsection{Priorité des opérations} + +La rigueur nécessaire lors de l'écriture est plus grande : les parenthèses semblent être une +superfétation. +Cependant, en les retirant, il devient assez facile d'obtenir des comportements surprenants et +erronés. +Si l'on retire les parenthèses intérieures, l'utilisation d'un opérateur moins prioritaire que celui +employé en interne par la macro causera des résultats erronés. +Dans notre exemple, les opérateurs de décalage de bit vont causer le problème. +L'appel à \cppinline{SUM(32 >> 1, 3)} devrait donner le nombre $\frac{32}{2} + 3 = 19$, or +l'expansion de la macro donnerait \cppinline{32 >> 1 + 3} avec l'opérateur \cppinline{+} plus +prioritaire que l'opérateur \cppinline{>>}. +Autrement dit, c'est équivalent à \cppinline{32 >> (1 + 3)}, donnant comme résultat $\frac{32}{2^4} += 2$. +L'omission des parenthèses extérieures peut produire des problèmes similaires avec cette fois-ci le +contexte d'appel de la macro plutôt que la valeur de ses arguments. +%}}} + +%{{{ +\subsection{Évaluation multiple} + +L'évaluation multiple des arguments est un autre problème bien connu de ces macros. +En effet, lors de l'expansion de la macro, si un paramètre apparaît plusieurs fois, l'argument qui +lui correspond est écrit, tel quel, plusieurs fois. +Ainsi, si un argument est une expression produisant un effet de bord, l'exemple classique étant +l'appel à un opérateur d'incrément \cppinline{++}, celui-ci peut être exécuté plus d'une fois. + +En utilisant la macro \cppinline{MIN} avec l'expression \cppinline{a += 2} où \cppinline{a} est une +variable de type \cppinline{int} initialisée à 0 et l'expression \cppinline{3}, c'est-à-dire en +écrivant \cppinline{MIN(a += 2, 3)}, le résultat retourné peut être 4\footnote{Le standard du C++ +ne spécifie pas l'ordre d'évaluation des arguments d'une fonction. Il est donc possible de voir +s'exécuter d'abord les deux incréments de la variable avant d'exécuter le test, auquel cas le +résultat sera 3.}. +De plus, dans tous les cas, la variable \og \cppinline{a} \fg{} voit sa valeur augmenter de 4 au +lieu de 2. +%}}} + +%{{{ +\subsection{Utilisation} + +Les macros du \gls{CPP} sont traitées très tôt durant le processus de compilation, phase durant +laquelle toute occurrence d'une macro est remplacée par un autre texte supposé être du C++ valide. +La compilation du C++ intervient ensuite et ne peut donc interagir avec les macros. + +À l'inverse, le produit d'un template (étant une variable, une fonction ou un type) est utilisable +au même titre que s'il avait été écrit directement. +Si \cppinline{SUM} et \cppinline{MIN}, plutôt que des macros, sont implémentées par des fonctions +(que l'on nomme respectivement \cppinline{sum} et \cppinline{min}), elles peuvent être utilisées +comme argument d'une autre fonction. +\Acref{lst:gnx/ccpp/fun_arg} montre cela en calculant la somme des valeurs contenues dans +\cppinline{v} en utilisant \cppinline{std::accumulate}. + +\begin{listing} + \inputlst{ccpp/fun_arg} + {Appel d'une fonction avec comme 4\up{ème} argument une fonction} +\end{listing} +%}}} +%}}} diff --git a/src/gnx/3_syntax.tex b/src/gnx/3_syntax.tex new file mode 100644 index 0000000..ee8b93b --- /dev/null +++ b/src/gnx/3_syntax.tex @@ -0,0 +1,69 @@ +\section{Déclaration et définition} + +%{{{ value " +Pour utiliser une valeur comme argument d'un template, celle-ci doit être connue au moment de la +compilation. +Il est par exemple possible d'écrire un template \cppinline{sum} (\cref{lst:gnx/syntax/sum}) qui +accepte deux paramètres entiers (exemple d'instanciation : \cppinline{sum<5, 7>}) et qui génère une +fonction retournant la somme des valeurs données en argument. + +\begin{listing} + \inputlst{syntax/sum} + {Définition d'un template de fonction} +\end{listing} + +L'instanciation générant une fonction, il est possible de la nommer comme dans +\acref{lst:gnx/syntax/sum_call} puis d'utiliser cette fonction comme n'importe quelle autre. + +\begin{listing} + \inputlst{syntax/sum_call} + {Instanciation d'un template} +\end{listing} + +Dans la pratique, des appels directs (\cppinline{sum<5, 7>()}) sont plus courants : si le template a +déjà été instancié, le compilateur se contente de retourner le même résultat qu'à la première +instanciation, cela n'implique donc aucun temps supplémentaire durant la compilation. + +Il est de la même manière possible d'écrire un template de classe +(\cref{lst:gnx/syntax/arrayint}) ou de variable (\cref{lst:gnx/syntax/isoddvar}). +Ces templates seront ensuite utilisables de manière similaire, leur instanciation générant +respectivement une classe ou une variable. +Pour le cas de la variable, il est possible d'en faire davantage une valeur plutôt qu'une variable à +proprement parler comme c'est le cas de \acref{lst:gnx/syntax/isoddvar} : lorsqu'une instance de +\cppinline{isOdd} est utilisée, elle est remplacée par une valeur directe. +Cela peut être comparé à la manière de fonctionner du préprocesseur, à l'exception du typage fort +qu'apporte la généricité. + +\begin{listing} + \inputlst{syntax/arrayint} + {Définition d'un template de classe} +\end{listing} + +\begin{listing} + \inputlst{syntax/isoddvar} + {Définition d'un template de variable} +\end{listing} +%}}} + +%{{{ type " +L'écriture d'un code générique implique très souvent l'abstraction du type de données sur lequel +celui-ci s'applique. +Cette abstraction est possible avec les templates puisqu'ils peuvent accepter des types comme +arguments. +Un exemple d'algorithme très simple qui peut fonctionner pour différents types est celui de la +recherche du minimum de deux valeurs (\cref{lst:gnx/syntax/min}). + +\begin{listing} + \inputlst{syntax/min} + {Définition d'un template de fonction \cppinline{min}} +\end{listing} + +Lorsque ce template est instancié (par exemple : \cppinline{min}), le compilateur produit une +fonction, de la même manière que cela avait été fait avec \cppinline{sum}. +Une différence cependant : la fonction obtenue attend deux paramètres de type \cppinline{int}. +Pour que la fonction générée compile et donne le bon résultat, il est nécessaire d'avoir un +opérateur \cppinline{<} valide pour le type \cppinline{T}. +%}}} + +%{{{ template " +%}}} diff --git a/src/gnx/4_tad.tex b/src/gnx/4_tad.tex new file mode 100644 index 0000000..90863a5 --- /dev/null +++ b/src/gnx/4_tad.tex @@ -0,0 +1,124 @@ +\section{Déduction d'arguments template} +\label{sec:gnx/tad} + +%{{{ +La déduction d'arguments template (de l'anglais \gls{TAD}, à ne pas confondre avec \og type abstrait +de données \fg) permet l'instanciation d'un template de fonction en déduisant ses arguments template +à partir des types des arguments passés à la fonction (qui est l'instanciation du template). +Pour que cela soit possible, il faut qu'il existe un lien entre les paramètres template et les +types des paramètres non template. +Dans le template \cppinline{min} de \acref{lst:gnx/syntax/min}, les arguments \cppinline{a} et +\cppinline{b} sont indiqués de type \cppinline{T}, le seul paramètre template de \cppinline{min}. +En écrivant \cppinline{min(0, 1)} (sans chevrons), le type des arguments est \cppinline{int} et la +valeur de \cppinline{T} est donc déduite à \cppinline{int}. +Dans ce cas-là, c'est donc équivalent à écrire \cppinline{min(0, 1)}. + +Les contraintes de ce mécanisme se devinent facilement mais doivent être prises en compte. +Comme dit plus haut, il faut un lien entre les paramètres template et les types des paramètres +de la fonction. +Ainsi, le template de \acref{lst:gnx/tad/non_deducible} ne permet pas le \gls{TAD}, et le seul +moyen de l'instancier est de le faire explicitement en utilisant les chevrons (par exemple : +\cppinline{f}). + +\begin{listing} + \inputlst{tad/non_deducible} + {Template de fonction pour lequel le \glsxtrshort{TAD} n'est pas possible} +\end{listing} + +Ce mécanisme requiert de plus une concordance de déduction dans le cas où un paramètre template est +utilisé pour plusieurs paramètres non template. +En écrivant \cppinline{min(0, 1.)}, \cppinline{T} sera déduit de valeur \cppinline{int} à cause du +premier argument, et de type \cppinline{double} à cause du second. +Le compilateur ne fera pas le choix et abandonnera la déduction de \cppinline{T} plutôt que de +forcer une conversion implicite, même si elle est possible. +S'il existe un autre candidat (moins prioritaire), celui-ci valide, il sera alors utilisé, sinon la +compilation échoue et le compilateur affiche tous les candidats. +Dans un tel cas, pour utiliser la version template, il est possible de l'instancier explicitement et +donc ne pas recourir au \gls{TAD} : le compilateur peut alors utiliser les conversions implicites. +Les conversions implicites sont néanmoins à éviter autant que possible et un bon compilateur lèvera +une alerte en cas de perte d'information due à une telle conversion (par exemple de +\cppinline{double} vers \cppinline{int} lorsque la partie décimale est non nulle). + +Il est également possible de déduire autre chose qu'un type, que ce soit une valeur ou un template. +Le principe reste fondamentalement le même, cependant il y a des limites supplémentaires lorsqu'il +s'agit de déduire des valeurs. +\Acref{lst:gnx/tad/value} présente un cas pour lequel la déduction fonctionne. + +\begin{listing} + \inputlst{tad/value} + {TAD de valeur} +\end{listing} + +Cependant, cet appariement est bien plus limité puisqu'il faut que le lien soit immédiat. +Par exemple, un calcul comme dans \acref{lst:gnx/tad/value_bad} empêche la +déduction. +Ceci reste valable même si le calcul semble immédiat (\cppinline{n+1}) voire trivial +(\cppinline{n+0}). + +\begin{listing} + \inputlst{tad/value_bad} + {TAD de valeur - déduction impossible} +\end{listing} + +Si la déduction d'arguments template peut déterminer la totalité des arguments, elle peut aussi se +limiter à un sous-ensemble de ceux-ci. +Dans ce cas-là, les arguments qui n'ont pas été déduits doivent être spécifiés explicitement. +\Acref{lst:gnx/tad/partial} présente un cas de spécification partielle (qui aurait pu être laissé +entièrement déduit). + +\begin{listing} + \inputlst{tad/partial} + {TAD partielle} +\end{listing} + +Si un argument déduit précède un argument devant être explicité, il n'est pas possible de bénéficier +de la déduction, puisque pour donner sa valeur au second argument, une valeur doit aussi être donnée +au premier. +\Acref{lst:gnx/tad/partial_explicit} montre un template avec 3 paramètres dont deux peuvent être +déduits par \gls{TAD}. +Puisque \cppinline{U} ne peut pas être déduit, il est obligatoire d'en spécifier la valeur. +Pour cela, il faut dans cet exemple également spécifier \cppinline{T} qui précède \cppinline{U}. + +\begin{listing} + \inputlst{tad/partial_explicit} + {TAD partielle} +\end{listing} + +Il est possible d'imaginer une syntaxe, utilisant par exemple \cppinline{auto}, pour maintenir +la déduction d'arguments template, mais cela n'existe pas à ce jour. +Généralement, lorsqu'un template qui peut bénéficier du \gls{TAD} requiert également des paramètres +qui ne peuvent pas être déduits, afin de conserver le bénéfice du \gls{TAD}, ceux-ci sont placés en +premier. + +Depuis C++17, il existe le \gls{CTAD} : le \gls{TAD} peut aussi s'appliquer aux templates de classe +en se basant sur des guides de déduction, explicites ou implicitement déduits des constructeurs. +En simplifiant, le \gls{CTAD} implicitement déduit consiste en l'utilisation de fonctions fictives +(une par constructeur) ayant comme paramètres template les mêmes que ceux de la classe, suivis +des éventuels paramètres template du constructeur s'il en a ; les paramètres non template quant à +eux sont ceux du constructeur. +Puis le \gls{TAD} est utilisé sur ces fonctions fictives pour tenter de déduire les arguments +template de la classe. + +\Acref{lst:gnx/tad/guides} présente un template de classe dont le seul paramètre est un type. +Un constructeur prenant un paramètre de ce type est déclaré et cela cause la création d'un guide de +déduction implicite : une fonction template (dont le paramètre est nommé dans l'exemple +\cppinline{U} pour lever l'ambiguïté avec \cppinline{T}, mais dont le nom n'importe pas) dont le +type de retour spécifié permet de connaître la valeur qui doit être donnée à \cppinline{T} lors d'un +appel à ce constructeur. + +\begin{listing}[!b] + \inputlst{tad/guides} + {Guides de déduction implicites et explicites} +\end{listing} + +Le second constructeur est lui-même template et prend 3 paramètres, le guide de déduction implicite +construit à partir de celui-ci prend donc lui aussi 3 paramètres et sera un template paramétré non +seulement sur un type \cppinline{U} (comme précédemment) mais également sur un type \cppinline{E} à +l'instar du constructeur. +De la même manière, le type retourné permet au compilateur de déduire que c'est \cppinline{U} qui +doit être utilisé comme valeur pour le template de classe. + +Le guide de déduction explicite indique que pour le cas particulier où le premier constructeur +serait appelé avec un paramètre de type \cppinline{char const*}, le type à déduire n'est pas +celui-ci, mais \cppinline{std::string}. +%}}} diff --git a/src/gnx/5_spe.tex b/src/gnx/5_spe.tex new file mode 100644 index 0000000..02e1d98 --- /dev/null +++ b/src/gnx/5_spe.tex @@ -0,0 +1,135 @@ +\section{Spécialisation} + +%{{{ +La spécialisation de template est un des mécanismes les plus importants de la généricité en C++. +Il permet de donner un comportement particulier pour certaines valeurs des arguments qui sont donnés +au template. + +Il existe deux cas principaux de spécialisation en C++, la spécialisation complète et la +spécialisation partielle. + +%{{{ full specialisation " +La spécialisation complète consiste en la définition d'une classe, d'une fonction ou d'une variable +correspondant à l'instanciation d'un template qui fixe la totalité des arguments qui lui seront +passés. +Considérant l'exemple de la fonction template \cppinline{min} présentée ci-avant +(\cref{lst:gnx/syntax/min}), il est souhaitable d'avoir un comportement spécifique pour le cas des +chaînes de caractères définies comme dans le langage C, de type \cppinline{char*} : plutôt que +comparer les adresses, il faut comparer ce qui est pointé. +Pour ce faire, il est possible de spécialiser le template comme montré dans +\acref{lst:gnx/spe/min_charp}. + +\begin{listing} + \inputlst{spe/min_charp} + {Spécialisation totale d'un template} +\end{listing} + +Dans la première ligne, \cppinline{template<>} permet d'indiquer que ce qui suit est une +spécialisation complète (aucune partie n'est inconnue) d'un template. +Toutes les occurrences de \cppinline{T} sont remplacées par la valeur spécifique correspondant à la +spécialisation, et cette valeur est indiquée au niveau du nom de la fonction +(\cppinline{min}). +%}}} + +%{{{ partial specialisation " +La spécialisation peut aussi être partielle (c'est-à-dire qu'il reste au moins une inconnue dans les +paramètres du template), mais uniquement pour les classes template. +Le template \cppinline{Array} (\cref{lst:gnx/spe/array}) permet de généraliser l'exemple +\cppinline{ArrayInt} (\cref{lst:gnx/syntax/arrayint}). + +\begin{listing} + \inputlst{spe/array} + {Template de classe \cppinline{Array}} +\end{listing} + +Il est possible de spécialiser pour le cas d'un tableau à 1 case, quel que soit le type. +Cela se fait en utilisant une spécialisation comme le montre \acref{lst:gnx/spe/array1}. + +\begin{listing} + \inputlst{spe/array1} + {Spécialisation partielle d'un template} +\end{listing} + +À l'instar de la spécialisation totale, la spécialisation partielle se fait dans un contexte de +définition de template, mais cette fois-ci, les chevrons ne sont pas vides puisqu'il reste au moins +une inconnue : ici, le type \cppinline{T}. +Les paramètres indiqués à la ligne suivante indiquent au compilateur lesquels sont déterminés et +lesquels sont encore inconnus, dépendants de paramètres templates. + +Les spécialisations partielles peuvent être plus complexes que les spécialisations totales. +Pour montrer un exemple simple, prenons le cas d'une classe semblable à \cppinline{std::unique_ptr}. +Le but de cette classe est de posséder un pointeur sur une zone mémoire allouée dynamiquement pour +la libérer automatiquement à sa destruction (principe du +\gls{RAII}~\autocite[§14.4.1]{ref:stroustrup1997}, quelquefois renommé \gls{RRID}). +La partie qui va nous intéresser est le destructeur d'une telle classe. +En effet, s'il est écrit comme dans \acref{lst:gnx/spe/uniqueptr}, il fonctionnera correctement +uniquement si la mémoire a été allouée en utilisant l'opérateur \cppinline{new}. +Or, il existe aussi l'opérateur \cppinline{new[]} pour allouer plusieurs éléments contigus. +Si ce deuxième opérateur est utilisé, il est nécessaire d'utiliser l'opérateur correspondant +\cppinline{delete[]} (qui s'occupe de l'appel du destructeur sur chacun des éléments construits, et +qui libérera correctement la mémoire : une implémentation possible de \cppinline{new[]} retourne un +pointeur au-delà de la zone mémoire effectivement allouée, pour conserver la quantité d'éléments +comme dans \acref{fig:gnx/spe/newarray}). + +\begin{figure} + \inputfig{spe/newarray} + {Implémentation possible d'une allocation par \cppinline{new[]}} +\end{figure} + +\begin{listing} + \inputlst{spe/uniqueptr} + {Extrait d'une implémentation d'un template de classe \cppinline{UniquePtr}} +\end{listing} + +Il faut écrire une spécialisation, mais celle-ci doit être utilisée lorsque l'adresse stockée pointe +sur un tableau. +La spécialisation partielle dans \acref{lst:gnx/spe/uniqueptr_array} répond au besoin. +\begin{listing}[t] + \inputlst{spe/uniqueptr_array} + {Spécialisation partielle d'un template} +\end{listing} +Il faut une spécialisation partielle car une part d'inconnu subsiste : le tableau peut contenir des +données de n'importe quel type. +%}}} + +%{{{ piorities +Lorsqu'il y a une définition primaire et au moins une définition spécialisée d'un template, le +compilateur doit en choisir une lorsqu'une instanciation est demandée. +Dans l'exemple avec le template de classe \cppinline{UniquePtr}, il faut que le compilateur +sélectionne la spécialisation dans le cas de \cppinline{UniquePtr}. +C'est effectivement ce qui arrive grâce au mécanisme de sélection de la version la plus spécialisée. + +La sélection du meilleur template n'est appliquée que s'il n'existe pas une version non template qui +corresponde\footnote{À l'exception de fonctions variadiques utilisant la syntaxe du langage C, à +savoir de la forme \cppinline{void f(...);}, \cppinline{void f(int, ...);}, etc. Attention +cependant, ce document traitera d'une syntaxe C++ qui y ressemble (l'utilisation des +\cppinline{...}) mais ne doit pas être confondue.}. +Dans ce cas, cette sélection se déroule en deux phases. +Dans un premier temps, seuls les templates primaires sont considérés. +Celui qui correspond, c'est-à-dire celui pour lequel les arguments sont valides, et qui est le plus +restrictif sur ses arguments est choisi. +Ensuite, ses spécialisations sont considérées et la version la plus spécialisée est choisie. + +Cette procédure cause un comportement qui peut paraître non naturel si sa logique n'est pas connue. +Dans \acref{lst:gnx/spe/fun_select}, la première occurrence de \cppinline{foo} correspond à un +template primaire, la deuxième à une spécialisation de ce template pour le cas \cppinline{T = int*}. +La dernière est un autre template primaire (une surcharge), contrainte sur des types qui sont +des pointeurs. +La deuxième semble correspondre parfaitement (c'est le cas), mais elle ne sera pas choisie car la +sélection va d'abord chercher parmi les templates primaires lequel correspond le mieux, puis +parmi ses spécialisations. +Or, dans cet exemple, le template primaire (3) correspond mieux que le (1). +Pour déterminer lequel correspond mieux, en simplifiant, le compilateur cherche parmi les candidats +celui dont l'ensemble des arguments possibles est le plus petit\footnote{Pour une explications plus +correcte, il est possible de se référer à +\url{https://en.cppreference.com/w/cpp/language/function_template}.}. + +\begin{listing} + \inputlst{spe/fun_select} + {Cas de sélection de template moins intuitif} +\end{listing} +%}}} + +%{{{ tricks: partial specialisation for functions +%}}} +%}}} diff --git a/src/gnx/6_sfinae.tex b/src/gnx/6_sfinae.tex new file mode 100644 index 0000000..e5daba2 --- /dev/null +++ b/src/gnx/6_sfinae.tex @@ -0,0 +1,128 @@ +\section{Contraintes} +\label{sec:gnx/sfinae} +% TODO: contraintes grammaticales (T*, std::vector) +% attention : contrainte grammaticale et non sémantique +% sur les classes aussi : t struct A; t struct A> {}; +% tag dispatching + +%{{{ +La spécialisation seule permet de définir un template qui sera utilisé pour un sous-ensemble +spécifié d'arguments, mais il est souvent intéressant de contraindre de manière plus spécifique les +arguments que peut accepter un template. + +Pour cela, le principe de \glsreset{SFINAE}\gls{SFINAE} est une solution possible. +Cet acronyme correspond à une règle du langage qui stipule que, lorsque le compilateur cherche un +candidat valide parmi les templates disponibles par rapport aux arguments donnés, si la substitution +des arguments aux paramètres échoue, cela n'est pas en soi une erreur. +Il y aura une erreur si cela se produit pour tous les candidats. +Une telle substitution peut échouer si, par exemple, il est attendu qu'un des paramètres possède un +membre \cppinline{B} (c'est-à-dire, étant donné \cppinline{A} un paramètre, qu'il y a une occurrence +de \cppinline{A::B}) et que l'argument passé n'en possède pas, par exemple le type \cppinline{int}. + +Le terme \gls{SFINAE} est maintenant utilisé pour nommer les techniques permettant de spécifier des +contraintes sur les arguments d'un template et utilisant la règle du même +nom~\autocite{ref:vandevoorde2010}. + +Pour observer ce qu'implique la \gls{SFINAE}, \acref{lst:gnx/sfinae/sfinae} présente un exemple. + +\begin{listing} + \inputlst{sfinae/sfinae} + {Utilisation du principe de SFINAE} +\end{listing} + +Deux fonctions \cppinline{f} sont définies, la première accepte un argument de type {\it a priori} +quelconque \cppinline{T}. +La seconde accepte n'importe quoi et en un nombre quelconque de paramètres\footnote{Une syntaxe +C++ existe et sera abordée plus tard dans le document. Pour cet exemple, la syntaxe héritée du C +suffit.}. +S'il n'y avait pas la partie \cppinline{, typename = typename T::X} des paramètres template de la +première fonction \cppinline{f}, les deux appels utiliseraient cette définition de \cppinline{f}. +Cette partie du code indique qu'un paramètre (un type : \cppinline{typename}) est attendu, qu'il +dispose d'une valeur par défaut (\cppinline{= typename T::X}). +Le \cppinline{typename} supplémentaire sert à lever l'ambiguïté sur ce qu'est \cppinline{X}, +c'est-à-dire un type, car ce qu'est ce membre dépend d'une inconnue (\cppinline{T}). +Ce paramètre n'a, par ailleurs, pas besoin d'être nommé. +L'objectif de ce code est seulement de faire apparaître \cppinline{T::X} : ceci déclenchera un échec +de substitution de \cppinline{T} si l'argument correspond ne possède pas de type membre nommé +\cppinline{X}. +Cet échec de substitution, grâce à la \gls{SFINAE}, n'interrompt pas la compilation, mais force le +compilateur à rejeter le candidat. +Tout ce qui entoure \cppinline{T::X} est nécessaire uniquement pour correspondre à la grammaire du +langage et garantir que le compilateur ne va pas ignorer cette contrainte. +Ainsi, si le premier appel à \cppinline{f} dans le code ci-dessus va pouvoir utiliser la première +définition de cette fonction, le second quant à lui va causer un échec de substitution +(\cppinline{int::X} est invalide puisque le type \cppinline{int} n'a pas de type membre +\cppinline{X}) : le compilateur va éliminer ce candidat et voir s'il y a d'autres fonctions +\cppinline{f} possibles, ici c'est le cas, donc aucune erreur de compilation n'est émise, et la +deuxième définition de la fonction est utilisée. + +Cette syntaxe atteint vite ses limites. +En définissant deux fonctions \cppinline{f} ayant les mêmes paramètres, l'une pour tout +\cppinline{T} ayant un type membre \cppinline{X} et l'autre un type membre \cppinline{Y} de la même +manière que dans l'exemple précédent, le code ressemblera à \acref{lst:gnx/sfinae/bad}. +Or, ce code ne peut pas compiler car le compilateur ne peut distinguer les deux définitions de +\cppinline{f}. + +\begin{listing} + \inputlst{sfinae/bad} + {Tentative d'utilisation de SFINAE ne compilant pas} +\end{listing} + +Pour parvenir à notre objectif, il est possible de rendre la liste de paramètres template différente +pour chaque template. +\Acref{lst:gnx/sfinae/good} présente une des manières de procéder. +Ce code permet de sélectionner \cppinline{f} selon que le type \cppinline{T} possède un type membre +\cppinline{X} ou \cppinline{Y}, et d'échouer à compiler s'il n'en possède aucun des deux. +La compilation échouera aussi si le type possède les deux membres à cause d'une ambiguïté entre les +deux versions de la fonction qui sont alors aussi valables l'une que l'autre. +Si ce comportement n'est pas souhaité, il peut être évité, mais ce n'est pas le propos pour le +moment. +Plutôt que d'utiliser un paramètre ayant une valeur par défaut dépendant de \cppinline{T}, il faut +qu'il dépende directement de cet inconnu \cppinline{T}. +Pour cela, il suffit de créer un paramètre attendant une valeur et dont le type dépend de +\cppinline{T}. +Même si après évaluation ce type se révèle être systématiquement le même, le compilateur ne cherche +pas à vérifier cela et considère les différentes versions de \cppinline{f}. +Le fait de donner une valeur par défaut à ce paramètre permet d'en cacher l'existence à +l'utilisateur. +L'utilisation d'un pointeur permet par ailleurs à cette astuce de fonctionner indépendamment du type +de \cppinline{X} ou \cppinline{Y}. + +\begin{listing} + \inputlst{sfinae/good} + {Contraintes différentes sur une même fonction} +\end{listing} + +D'autres techniques, utilisant le type de retour ou des paramètres non template de la fonction +(avec une valeur par défaut) existent, mais elles ont l'inconvénient de mélanger la fonction et le +template, ce que la technique présentée ci-dessus évite. + +Nous venons de voir une méthode pour tester la présence d'un type membre, mais s'il s'agit de +tester, par exemple, la parité d'un argument template, cela ne suffit pas. +Pour ce cas, il est possible d'écrire, grâce à la spécialisation de template, un outil pour +transformer un test en contrainte par \gls{SFINAE}. +L'idée consiste à fournir un template de classe primaire ayant un booléen en paramètre et aucun +membre, et le spécialiser pour le cas où le booléen est vrai pour fournir un membre. +Ce concept a été implémenté pour la première fois par une bibliothèque de Boost, au nom de +\cppinline{enable_if}. +Une implémentation minimale peut ressembler à \acref{lst:gnx/sfinae/enableif} et +s'utiliser comme montré dans \acref{lst:gnx/sfinae/parity}. + +\begin{listing} + \inputlst{sfinae/enableif} + {Implémentation minimale d'un \og enable if \fg} +\end{listing} + +\begin{listing} + \inputlst{sfinae/parity} + {SFINAE sur la parité d'un argument} +\end{listing} + +Si le test \cppinline{i%2 == 0} est évalué à faux, ce sera \cppinline{EnableIf} sans type membre qui +sera utilisé, faisant échouer la substitution. +Au contraire, s'il est évalué à vrai, le type membre existera et la fonction sera instanciée. +Dans cet exemple, les conditions d'activation des templates sont exclusives et couvrent tous +les cas, donc pour tout argument utilisé, l'une des deux \cppinline{f} sera valide. +Depuis C++11, la bibliothèque standard inclut une implémentation de \cppinline{EnableIf} : +\cppinline{std::enable_if}. +%}}} diff --git a/src/gnx/7_concepts.tex b/src/gnx/7_concepts.tex new file mode 100644 index 0000000..531bf62 --- /dev/null +++ b/src/gnx/7_concepts.tex @@ -0,0 +1,121 @@ +\section{Concepts} + +%{{{ +Même s'il est beaucoup utilisé pour cela, le mécanisme de \gls{SFINAE} n'a pas été créé avec pour +objectif la spécification de contraintes. +La syntaxe exposée par ce mécanisme n'est donc pas vraiment adaptée. + +En concevant un mécanisme pensé pour cette tâche, il est facile d'écrire des contraintes +similaires avec une meilleure lisibilité, et éventuellement une plus grande expressivité. +La programmation générique définit pour cela les +{\it concepts}~\autocite{ref:siek2005,ref:stroustrup2017}, correspondant à un ensemble +d'exigences devant être respectées, que ce soit syntaxiques, sémantiques, ou sur d'autres critères +tels que la complexité temporelle ou spatiale. +Plusieurs implémentations, plus ou moins complètes, ont été proposées. +Boost fournit une bibliothèque~\autocite{ref:siek2000} permettant la vérification de concepts, +c'est-à-dire la partie validation syntaxique du respect de contraintes spécifiées. +Une autre implémentation partielle~\autocite{ref:bachelet2017} s'est concentrée sur l'aspect +raffinement et sélection du template du meilleur concept, c'est-à-dire le plus raffiné de ceux qui +sont respectés. +Cette implémentation est aussi une bibliothèque écrite en C++ et utilise un système de tag pour la +sélection du template. +À l'inverse de la bibliothèque de Boost, le respect d'un concept est cette fois-ci déclaratif et +déclaré après le type. +Outre les bibliothèques, il est possible de fournir cette fonctionnalité directement au niveau du +langage par le biais du compilateur comme cela a été fait pour Clang~\autocite{ref:voufo2011} et +GCC\footnote{\url{https://gcc.gnu.org/projects/cxx-status.html}} depuis sa version 6, et comme cela +sera le cas pour tout compilateur respectant le standard C++20 puisque les concepts y ont été +intégrés~\autocite[§13.7.8 et §18]{ref:iso2020}. +Pour être exact, les concepts intégrés sont une sous partie de ce qu'ils auraient pu être, se +limitant aux aspects statiques, et excluant donc la vérification sémantique par exemple. + +Les concepts du C++20 permettent ainsi la spécification de contraintes devant être respectées par un +argument donné à un template afin que le candidat soit considéré. +Si on considère cet exemple présenté dans \acref{lst:gnx/concepts/sfinae_base_of}, il est +possible de remplacer l'utilisation de la \gls{SFINAE} par un \cppinline{requires} comme dans la +nouvelle définition de \cppinline{f} de \acref{lst:gnx/concepts/requires_base_of}. + +\begin{listing} + \inputlst{concepts/sfinae_base_of} + {Utilisation de la SFINAE - contrainte d'héritage} +\end{listing} + +\begin{listing} + \inputlst{concepts/requires_base_of} + {Utilisation de \cppinline{requires} - contrainte d'héritage} +\end{listing} + +Plusieurs avantages sont à observer. +Le premier est qu'il n'y a plus de recours à un outil fourni par une bibliothèque puisque le nouveau +mécanisme existe directement au niveau du langage. +Ensuite, les rôles étant mieux distingués, le contournement de la contrainte est rendu impossible. +Enfin, cela permet aussi de disposer d'informations bien plus précises en cas d'erreur. +En utilisant le même compilateur, GCC 8.3, la différence entre les diagnostics que l'on obtient, en +utilisant soit la \gls{SFINAE} (\cref{lst:gnx/concepts/error_sfinae}) soit les contraintes +(\cref{lst:gnx/concepts/error_concept}), est intéressante. +\begin{listing}[!b] + \inputlst{concepts/error_sfinae} + {Diagnostic de GCC 8.3 en l'absence de candidat valide par \glsxtrshort{SFINAE}} +\end{listing} +\begin{listing}[t] + \inputlst{concepts/error_concept} + {Diagnostic de GCC 8.3 en cas de contrainte non satisfaite} +\end{listing} +Dans le premier cas, bien que l'information utile soit présente, elle n'est pas mise en valeur. +Dans le second cas, au contraire, le problème est mieux indiqué et son origine est explicitée. + +Par ailleurs, il est possible de nommer un ensemble de contraintes sous la forme d'un concept et +ainsi l'utiliser plutôt que les contraintes qu'il représente. +\Acref{lst:gnx/concepts/named_constraint} montre comment il est possible de nommer la contrainte +\og hérite de A \fg{} (ce qui n'est pas nécessairement pertinent à faire, mais sert ici d'exemple). + +\begin{listing} + \inputlst{concepts/named_constraint} + {Définition d'un concept} +\end{listing} + +Un concept est défini comme un prédicat booléen à vérifier, lequel peut utiliser des conjonctions et +des disjonctions (et logique et ou logique) pour construire une expression plus complexe. +Le raffinement de concept se fait alors en définissant un nouveau concept comme étant la conjonction +ou disjonction d'un ou plusieurs concepts existants et éventuellement d'autres contraintes +spécifiques. +Enfin, le mot-clé \cppinline{requires} permet, lors de la définition d'un concept, d'établir des +contraintes syntaxiques. +% TODO exemples ? + +Ainsi, si le type doit avoir un constructeur par défaut, supporter les comparaisons d'égalité et +hériter de \cppinline{A}, le concept peut être défini comme présenté dans +\acref{lst:gnx/concepts/eqcomparablea}. +Requérir un concept peut être fait de la même manière qu'une contrainte non nommée, en utilisant le +mot-clé \cppinline{requires} (\cref{lst:gnx/concepts/requires_concept}). +Il est également possible d'utiliser une syntaxe plus légère où le mot-clé \cppinline{typename} est +remplacé par le nom du concept (\cref{lst:gnx/concepts/direct_concept}). + +\begin{listing} + \inputlst{concepts/eqcomparablea} + {Implémentation du \og concept \fg{} \cppinline{EqComparableA}} +\end{listing} + +\begin{listing} + \inputlst{concepts/requires_concept} + {Utilisation d'un concept avec \cppinline{requires}} +\end{listing} + +\begin{listing} + \inputlst{concepts/direct_concept} + {Exemple d'utilisation d'un concept} +\end{listing} + +L'exemple de \acref{lst:gnx/concepts/eqcomparablea} utilise le raffinement de concept : il faut +respecter le concept \cppinline{InheritsFromA} pour respecter \cppinline{EqComparableA}. +Le raffinement peut être comparé à l'héritage de la \gls{POO}, avec davantage de libertés (par +exemple, cela n'est pas limité à la conjonction logique). +Émuler cela avec la \gls{SFINAE} est possible, cependant, le raffinement est plus puissant lorsqu'il +s'agit de la résolution d'une surcharge de fonction contrainte par exemple. +Si l'on a une surcharge contrainte par \cppinline{InheritsFromA} et une autre par +\cppinline{EqComparableA}, un argument satisfaisant le second va faire sélectionner la seconde +surcharge. +Si cela paraît évident, obtenir ce comportement avec la \gls{SFINAE} est difficile : en procédant de +la même manière que présenté dans ce document, les deux surcharges seront acceptables et la +compilation échouera pour cause d'ambiguïté. +%}}} diff --git a/src/gnx/8_inst.tex b/src/gnx/8_inst.tex new file mode 100644 index 0000000..0e7635b --- /dev/null +++ b/src/gnx/8_inst.tex @@ -0,0 +1,15 @@ +\section{Instanciation} + +%{{{ +Le mécanisme d'instanciation des templates est une étape importante du fonctionnement de la +généricité telle qu'elle est implémentée en C++. +En particulier dans le développement de métaprogrammes pour lesquels certaines spécificités du +mécanisme d'instanciation sont nécessaires. +Le principe de base ressemble beaucoup au fonctionnement du pré-processeur du langage C : chaque +occurrence d'une variable template est remplacée par sa valeur. +Cependant, l'instanciation d'un template diffère sur certains aspects qui sont particulièrement +importants. +Un de ces aspects est la validation syntaxique du code, vérifiée au moment de l'instanciation et non +de l'utilisation. +Le chapitre suivant détaillera davantage l'instanciation des templates. +%}}} diff --git a/src/gnx/9_overload.tex b/src/gnx/9_overload.tex new file mode 100644 index 0000000..91913e7 --- /dev/null +++ b/src/gnx/9_overload.tex @@ -0,0 +1,16 @@ +\section{Mécanisme de résolution de surcharges} + +%{{{ +Outre les templates, le C++ permet la généricité par la surcharge de fonctions. +La surcharge de fonctions consiste en l'écriture de plusieurs fonctions portant le même nom mais +n'acceptant pas strictement les mêmes paramètres. +Lors de l'appel, plusieurs candidats sont possibles et le compilateur doit donc déterminer lequel +utiliser, s'il en existe un unique qui corresponde. +Pour cela, il considère les arguments utilisés (leur nombre et type) et vérifie si une fonction +peut les accepter. +Différentes règles s'appliquent selon que le candidat est un template ou non : dans le premier +cas, une conversion implicite ne sera pas permise pour adapter un argument dont le type déduit entre +en conflit avec un type déjà déduit (\cref{sec:gnx/tad}). +De plus, tout candidat template sera moins prioritaire qu'un candidat non template. +Si plusieurs candidats de même priorité correspondent, il y a ambiguïté et une erreur survient. +%}}} diff --git a/src/gnx/a_auto.tex b/src/gnx/a_auto.tex new file mode 100644 index 0000000..fc5b4c4 --- /dev/null +++ b/src/gnx/a_auto.tex @@ -0,0 +1,65 @@ +\section{Inférence de type} +\label{sec:gnx/auto} + +%{{{ +Pour expliquer l'intérêt de l'inférence de type, implémentons une fonction d'addition générique. +\Acref{lst:gnx/auto/add_tt} propose une première version, acceptant deux paramètres de type +\cppinline{T} et retournant donc une valeur de ce type\footnote{En réalité, ce type de retour peut +déjà être un problème si l'on souhaite gérer le dépassement de valeur des types numériques.}. +\begin{listing}[t] + \inputlst{auto/add_tt} + {Addition template (première version)} +\end{listing} +À l'appel, pour que le type \cppinline{T} soit déduit, il faut que les paramètres \cppinline{lhs} et +\cppinline{rhs} soient de même type, sans possibilité de conversion implicite. +Si l'on veut pouvoir additionner des instances de types distincts, il faut changer la manière de +définir cette fonction. + +Il convient pour cela de prendre deux paramètres de deux types, \cppinline{LHS} et \cppinline{RHS}. +Un problème est alors soulevé : le type de retour n'est plus aussi simple que dans +\acref{lst:gnx/auto/add_tt} car les opérandes de l'addition peuvent avoir des types différents. +Il existe des techniques sans inférence de type par le langage, mais elles requièrent davantage de +code et ne sont pas l'objet de cette section. +Pour résoudre ce problème, il est possible de laisser le compilateur décider du type qu'il convient +de retourner (\cref{lst:gnx/auto/add_auto}). + +\begin{listing} + \inputlst{auto/add_auto} + {Addition template (deuxième version)} +\end{listing} + +L'usage du mot-clé \cppinline{auto}, introduit en C++11, permet l'inférence statique d'un type à +partir de la valeur utilisée pour initialiser la variable (dans le cas de l'exemple : le retour de +la fonction). +Les règles d'inférence pour \cppinline{auto} font que le type déduit sera celui de la valeur qui va +être stockée, sans qualificatif (\cppinline{const} et \cppinline{volatile}) ni +référence\footnote{Si le type est \cppinline{int const*}, ce qualificatif est conservé : sont +retirés ceux qui s'appliquent après toute indirection.}. +Cela permet de choisir d'ajouter ou non ces modificateurs de type selon le besoin. + +Si, au contraire, le type exact de déclaration de l'expression est souhaité, il faut utiliser +\cppinline{decltype} avec l'expression pour argument. +On pourra donc par exemple écrire pour une expression \cppinline{a} : \cppinline{decltype(a)}. +Ce dernier possède cependant une particularité syntaxique : \cppinline{decltype((a))} ne correspond +pas au type de \cppinline{a} mais à une référence sur ce type\footnote{Si \cppinline{a} est déjà +une référence, \cppinline{decltype((a))} correspond alors à son type exact.}. + +Il est possible de combiner l'utilisation de \cppinline{decltype} et de \cppinline{auto}, le premier +pour déterminer le type exact, et le second comme remplaçant de l'expression qui servira à +initialiser la variable. +Il est ainsi possible de déclarer une variable de type \cppinline{decltype(auto)}, ou d'utiliser +cela comme type de retour pour une fonction. +À noter que, dans le cas du retour de fonction, la règle de \cppinline{decltype} sur les parenthèses +s'applique également, ainsi \cppinline{return var;} et \cppinline{return (var);} ne sont pas +équivalents si le type de retour est \cppinline{decltype(auto)} : le second retournera +systématiquement une référence sur la variable tandis que le premier retournera une variable dont le +type sera exactement celui spécifié. + +Ce mécanisme d'inférence de type est utile en général, mais il devient pratiquement indispensable à +la métaprogrammation template : les types manipulés peuvent rapidement devenir difficiles à écrire, +et devoir le faire rendrait considérablement plus lourd l'utilisation de n'importe quel +métaprogramme. +À vrai dire, certains usages de la métaprogrammation template reposent sur la génération de types +que l'utilisateur ne peut donc pas écrire lui-même sans annuler l'intérêt du métaprogramme. +Des exemples de tels cas sont présents dans le chapitre suivant. +%}}} diff --git a/src/gnx/b_forward.tex b/src/gnx/b_forward.tex new file mode 100644 index 0000000..4a60b88 --- /dev/null +++ b/src/gnx/b_forward.tex @@ -0,0 +1,55 @@ +\section{Transmission parfaite} +\label{sec:gnx/forward} + +%{{{ +Il n'est pas rare, à l'écriture de fonctions en général, d'avoir à traiter de multiples cas selon +les modificateurs appliqués aux types, inconnus, que ce soit des qualificatifs, des références ou +des références sur des r-values (des références sur une donnée expirant et dont le contenu peut être +modifié, permettant d'éviter des copies profondes en faveur de copies superficielles). +Lorsque cela arrive sur une fonction unaire, cela fait jusqu'à 8 variantes à écrire, et la +croissance est exponentielle en le nombre de paramètres pour lesquels les différentes possibilités +sont gérées ($8^n$, où $n$ est le nombre de paramètres à gérer). + +En passant par un paramètre générique, il est possible de réduire ces différentes variantes en une +seule qui s'adaptera selon le type réel. +La transmission parfaite du type (en anglais, {\it perfect forwarding}) utilise des règles dites de +{\it reference collapsing}~\autocite{ref:dimov2002} qui se comportent comme dans +\acref{lst:gnx/forward/collapsing} et résumées par \acref{tab:collapsing}. + +\begin{listing} + \inputlst{forward/collapsing} + {Règles de \text\it{reference collapsing}~\autocite{ref:cppref_ref_decl}} +\end{listing} + +\begin{table} + \center + \begin{tabular}{|l|l|l|} + \hline + type & référence ajoutée & résultat \\ + \hline + \& & \& & \& \\ + \& & \&\& & \& \\ + \&\& & \& & \& \\ + \&\& & \&\& & \&\& \\ + \hline + \end{tabular} + \caption{Table des règles de {\it reference collapsing}} + \label{tab:collapsing} +\end{table} + +Pour transmettre un paramètre en conservant son type, et donc appeler la bonne fonction en cas de +surcharge, la bibliothèque standard fournit un outil qui s'occupe de la conversion : +\cppinline{std::forward}. +\Acref{lst:gnx/forward/example} est un exemple d'utilisation de la référence de transmission (en +anglais, {\it forwarding reference}) avec \cppinline{std::forward}. +Dans cet exemple, comme \cppinline{T} est un paramètre template, \cppinline{T&&} ne représente pas +une référence sur une r-value mais une référence de transmission. +Il est important de noter que si l'on écrit quelque chose comme \cppinline{std::vector&&}, ce qui +est à gauche des esperluettes n'étant pas directement un paramètre template (même s'il en dépend), +cela correspond à une référence sur une r-value et non plus à une référence de transmission. + +\begin{listing} + \inputlst{forward/example} + {Exemple de transmission parfaite} +\end{listing} +%}}} diff --git a/src/gnx/c_pack.tex b/src/gnx/c_pack.tex new file mode 100644 index 0000000..e1b9d5b --- /dev/null +++ b/src/gnx/c_pack.tex @@ -0,0 +1,68 @@ +\section{Packs de paramètres} +\label{sec:gnx/pack} +% TODO ne pas laisser croire que le compilateur irait écrire du C++ en étendant le pack + +%{{{ +Depuis la norme C++11, il est possible de prendre un pack de paramètres de même nature, c'est-à-dire +exclusivement des types, ou exclusivement des valeurs ou exclusivement des templates attendant les +mêmes paramètres. +Le but de cette section n'est pas de présenter tout ce qu'il est possible de faire avec cet outil, +mais d'en expliquer succinctement le fonctionnement. +Il sera employé par la suite, et de manière plus complète que présenté ici. + +Les packs de paramètres permettent entre autres l'implémentation de fonctions variadiques typées. +La différence la plus importante avec les fonctions variadiques, comme il est possible de les +implémenter en C, est que le type de chaque paramètre est bien connu au moment de la compilation. +Cela signifie qu'il n'est plus nécessaire d'avoir recours à une astuce pour retrouver le type comme +le fait par exemple \texttt{printf}, et donc, dès la compilation, la bonne surcharge d'une fonction +appelée sur un paramètre peut être sélectionnée. + +L'exemple présenté dans \acref{lst:gnx/pack/add} montre l'utilisation d'un pack de +paramètres. +La fonction \texttt{add} calcule la somme de tous ses paramètres. +Cette fonction est récursive car il faut isoler un paramètre du pack afin de pouvoir l'utiliser +spécifiquement. +Des techniques permettent d'éviter cette récursivité, elles seront présentées ultérieurement. +Un pack ne peut être utilisé tel quel : il doit être étendu, et c'est à cela que servent les 3 +points \og \texttt{...} \fg. + +L'expansion du pack de paramètres template \cppinline{Ts} sert à faire correspondre à chaque +paramètre template un argument de la fonction \cppinline{add} lors de son appel. +Ces arguments sont eux-mêmes regroupés dans le pack \cppinline{values}. +Dans l'exemple, l'expansion du pack \cppinline{values} permet d'appeler la fonction \texttt{add} +avec tous les paramètres sauf le premier. +Un pack de paramètres peut être vide, et lorsque cela surviendra, la surcharge de \texttt{add} +n'acceptant qu'un paramètre sera appelée et terminera la récursion. + +Si cette fonction est appelée avec une série d'entiers comme arguments, leur somme sera retournée, +tandis qu'avec une série de chaînes de caractères, c'est la concaténation de celles-ci qui sera +construite, parce que les types des paramètres sont connus. + +\begin{listing} + \inputlst{pack/add} + {Calcul de la somme d'un nombre quelconque de valeurs} +\end{listing} + +La suite de cette section va s'attarder sur quelques détails de fonctionnement de l'expansion de +pack. +En C++17, il est devenu possible de choisir quel opérateur binaire sépare les éléments d'un pack +lors de son expansion à la place de la virgule avec les notations \cppinline{(... OP pack)} et +\cppinline{(pack OP ...)} (la position du pack par rapport à l'opérateur change l'associativité). +Cela inclut l'opérateur virgule, mais ce n'est pas équivalent à la syntaxe \cppinline{pack...} qui +n'utilise pas l'opérateur virgule mais une virgule servant à séparer des arguments d'une fonction. +L'exemple présenté dans \acref{lst:gnx/pack/avg} montre une utilisation de ce mécanisme. +\begin{listing}[t] + \inputlst{pack/avg} + {Calcul de la moyenne d'un nombre quelconque de valeurs} +\end{listing} +L'expansion du pack se fera ainsi : \cppinline{(values0+(values1+(values2+...)))}. +L'opérateur \cppinline{sizeof...} retourne le nombre d'éléments contenu dans un pack. + +Le pack peut faire parti d'un motif (voir \autocite[§14.5.3.4]{ref:iso2011}), auquel cas l'expansion +applique ce motif sur chacun des éléments du pack. +Par exemple \cppinline{f(pack)...} sera étendu \cppinline{f(p0), f(p1), f(p2), ...}. +Ceci est très utilisé pour les paramètres d'une fonction. +Ainsi, pour la fonction suivante : \cppinline{void f(Ts const&... args)}, les éléments du pack +\cppinline{args} sont tous une référence (\cppinline{&}) sur un type $T_i$ quelconque constant +(\cppinline{const}). +%}}} diff --git a/src/gnx/d_ifcx.tex b/src/gnx/d_ifcx.tex new file mode 100644 index 0000000..4f7c77e --- /dev/null +++ b/src/gnx/d_ifcx.tex @@ -0,0 +1,39 @@ +\section{\texttt{if constexpr}} + +%{{{ +Il arrive fréquemment, lors de l'écriture d'un programme générique, d'avoir à effectuer un +traitement qui dépende des données en entrée comme pour le cas d'un programme classique. +Cependant, il y a une différence importante : dans un programme classique, il est possible +d'utiliser un simple branchement (\cppinline{if}) pour sélectionner le comportement désiré. +Par exemple, les entiers ne permettant pas de diviser par 0, un programme divisant par une variable +pourra tester sa valeur et ne pas exécuter la division le cas échéant. +Dans le cas du programme générique, il est possible de dépendre d'un type et selon la valeur de ce +dernier, certaines opérations peuvent être invalides du point de vue du compilateur (par exemple +l'opérateur d'indirection ne peut être utilisé sur une instance de type \cppinline{int}), or, toutes +les branches d'un \cppinline{if} sont effectivement compilées. + +Si l'on prend un exemple de fonction dont l'objectif est de retourner la valeur pointée dans le cas +où l'argument est une adresse, sinon la valeur elle-même, il est possible de l'écrire en se reposant +sur la surcharge de fonction et le fait que la version la plus contrainte va être utilisée. +Deux versions sont à écrire : le cas général, et une surcharge acceptant un pointeur +(\cref{lst:gnx/ifcx/getvalue}). + +\begin{listing} + \inputlst{ifcx/getvalue} + {Indirection conditionnelle avant C++17} +\end{listing} + +Depuis C++17, il existe \cppinline{if constexpr}, grâce auquel il est possible de n'écrire qu'une +fonction, laquelle se scinde en deux branches selon que l'argument est vrai ou faux. +La différence avec le \cppinline{if} classique est que la branche que le programme n'exécute pas +est entièrement supprimée du programme : cela ne pose aucun problème qu'elle soit erronée. +Dans \acref{lst:gnx/ifcx/getvalue_if}, si \cppinline{T} n'est pas un pointeur, en utilisant +un \cppinline{if}, l'appel à l'indirection cause une erreur de compilation\footnote{Sauf si +\cppinline{T} représente un type qui implémente l'opérateur d'indirection sans pour autant avoir la +sémantique de pointeur, mais ce n'est pas le propos ici.}. + +\begin{listing} + \inputlst{ifcx/getvalue_if} + {Indirection conditionnelle avec \cppinline{if constexpr}} +\end{listing} +%}}} diff --git a/src/gnx/e_conclusion.tex b/src/gnx/e_conclusion.tex new file mode 100644 index 0000000..b2e3d2a --- /dev/null +++ b/src/gnx/e_conclusion.tex @@ -0,0 +1,22 @@ +\section{Conclusion} + +%{{{ +La généricité est un concept omniprésent en informatique et permet l'écriture de bibliothèques dont +les fonctions peuvent être utilisées dans des projets variés. +Au-delà de ce principe de base, cela s'étend également à la généralisation de types dans certains +langages ayant le paradigme \og orienté objet \fg. + +En C++, le mécanisme des \og templates \fg{} répond à ce besoin en permettant de paramétrer un type, +une fonction ou encore une variable. +Ceux-ci peuvent alors dépendre de valeurs ou de types. +Une particularité des templates est d'être entièrement résolu au moment de la compilation. +Ce faisant, ils permettent une forme d'abstraction sans nécessairement causer d'augmentation du +temps d'exécution. + +Ce chapitre a présenté différents concepts de la généricité en C++. +La métaprogrammation dite \og template \fg{}, qui est le sujet du chapitre suivant, est +essentiellement construite à partir des templates et de leurs spécificités. +Pour cette raison, ce chapitre a principalement introduit les éléments qui sont nécessaires à cette +métaprogrammation, notamment la spécialisation des templates, mais aussi des fonctionnalités qui, +bien que dispensables, sont incontournables, par exemple les packs. +%}}} diff --git a/src/lst/alsk/concept/inst/elsbody.tex b/src/lst/alsk/concept/inst/elsbody.tex new file mode 100644 index 0000000..b7ad656 --- /dev/null +++ b/src/lst/alsk/concept/inst/elsbody.tex @@ -0,0 +1,7 @@ +\begin{cppcode} + using ELS = ElsSkel< + tsp::Problem, tsp::Solution, std::mt19937, + Descent, Move2Opt, Descent, FN(selectMin), FN(selectMin), FN(acceptation) + >; +\end{cppcode} +\mincpp{14} diff --git a/src/lst/alsk/concept/inst/grasp.tex b/src/lst/alsk/concept/inst/grasp.tex new file mode 100644 index 0000000..4216265 --- /dev/null +++ b/src/lst/alsk/concept/inst/grasp.tex @@ -0,0 +1,11 @@ +\begin{cppcode} + template< + typename Problem, typename Solution, typename PRNG, + typename CH, typename LS, typename Sel + > + using GraspSkel = BuildSkeleton::template skeleton< + Pack, + Pack + >; +\end{cppcode} +\mincpp{14} diff --git a/src/lst/alsk/concept/inst/graspelsbody.tex b/src/lst/alsk/concept/inst/graspelsbody.tex new file mode 100644 index 0000000..1a4a7b6 --- /dev/null +++ b/src/lst/alsk/concept/inst/graspelsbody.tex @@ -0,0 +1,9 @@ +\begin{cppcode} + // ELS est défini dans |\acref{lst:alsk/concept/inst/elsbody}|. + using GRASPxELS = GraspSkel< + tsp::Problem, tsp::Solution, std::mt19937, + RGreedy, ELS, + FN(selectMin) + >; +\end{cppcode} +\mincpp{14} diff --git a/src/lst/alsk/concept/inst/selectmin.tex b/src/lst/alsk/concept/inst/selectmin.tex new file mode 100644 index 0000000..ecbba42 --- /dev/null +++ b/src/lst/alsk/concept/inst/selectmin.tex @@ -0,0 +1,6 @@ +\begin{cppcode} + Solution const& selectMin(Solution const& a, Solution const& b) { + return a.value() < b.value()? |a| : b; + } +\end{cppcode} +\mincpp{98} diff --git a/src/lst/alsk/concept/links/grasp.tex b/src/lst/alsk/concept/links/grasp.tex new file mode 100644 index 0000000..cc0187d --- /dev/null +++ b/src/lst/alsk/concept/links/grasp.tex @@ -0,0 +1,12 @@ +\begin{cppcode*}{linenos} + template + using GraspLinks = + L(P<0>, P<1>),|\label{line:alsk/concept/links/grasp:serial}| + Solution(P<0>, P<1>), + Solution(P<0>, R<0>, P<1>)|\label{line:alsk/concept/links/grasp:ls}| + >, + Solution(Solution const&, Solution const&)|\label{line:alsk/concept/links/grasp:sel}| + >; +\end{cppcode*} +\mincpp{14} diff --git a/src/lst/alsk/concept/links/minimal.tex b/src/lst/alsk/concept/links/minimal.tex new file mode 100644 index 0000000..23c0d53 --- /dev/null +++ b/src/lst/alsk/concept/links/minimal.tex @@ -0,0 +1,7 @@ +\begin{cppcode} + L +\end{cppcode} +\mincpp{14} diff --git a/src/lst/alsk/concept/parheight/calcheight.tex b/src/lst/alsk/concept/parheight/calcheight.tex new file mode 100644 index 0000000..bdd657b --- /dev/null +++ b/src/lst/alsk/concept/parheight/calcheight.tex @@ -0,0 +1,25 @@ +\begin{cppcode} + template struct CalcHeightImpl; + + // Branch case: height = max of nodes + 1 if parallel + template class Skel, typename... Ts> + struct CalcHeightImpl, Ts...> { + static constexpr int par = SkeletonTraits::serial? 0 : 1; + using type = std::integral_constant>; + }; + + // Leaf case: height = 0 + template + struct CalcHeightImpl, Ts...> { + using type = std::integral_constant; + }; + + template + using CalcHeight = typename CalcHeightImpl::type; + + using CalcHeightDefault = std::integral_constant; + + using Tree = TreeFromSkeleton; + constexpr int value = treeAccumulate; +\end{cppcode} +\mincpp{14} diff --git a/src/lst/alsk/concept/parheight/parheightalt.tex b/src/lst/alsk/concept/parheight/parheightalt.tex new file mode 100644 index 0000000..eec1931 --- /dev/null +++ b/src/lst/alsk/concept/parheight/parheightalt.tex @@ -0,0 +1,10 @@ +\begin{cppcode} + using Tree = TreeFromSkeleton; + using RtlPaths = TreeAllRTLPaths; + using Parallel = Transform; + using ParallelC = Transform; + + using DefaultHeight = std::integral_constant; + constexpr int value = accumulate; +\end{cppcode} +\mincpp{14} diff --git a/src/lst/alsk/concept/parheight/treeaccumulate.tex b/src/lst/alsk/concept/parheight/treeaccumulate.tex new file mode 100644 index 0000000..01a1e4d --- /dev/null +++ b/src/lst/alsk/concept/parheight/treeaccumulate.tex @@ -0,0 +1,25 @@ +\begin{cppcode} + template class, typename> struct TreeAccumulateImpl; + + template class F, typename A> + struct TreeAccumulateImpl, F, A> { + using type = F::type...>; + }; + + template class F, typename A> + struct TreeAccumulateImpl, F, A> { + using type = F; + }; + + template class F, typename A> + struct TreeAccumulateImpl, F, A> { + using type = A; + }; + + template class F, typename A> + using TreeAccumulate = typename TreeAccumulateImpl::type; + + template class F, typename A> + constexpr auto treeAccumulate = TreeAccumulate::value; +\end{cppcode} +\mincpp{14} diff --git a/src/lst/alsk/concept/struct/grasp.tex b/src/lst/alsk/concept/struct/grasp.tex new file mode 100644 index 0000000..aee860d --- /dev/null +++ b/src/lst/alsk/concept/struct/grasp.tex @@ -0,0 +1,9 @@ +\begin{cppcode} + template + using GraspStruct = + S, + Sel + >; +\end{cppcode} +\mincpp{14} diff --git a/src/lst/alsk/concept/struct/graspels.tex b/src/lst/alsk/concept/struct/graspels.tex new file mode 100644 index 0000000..3164db3 --- /dev/null +++ b/src/lst/alsk/concept/struct/graspels.tex @@ -0,0 +1,23 @@ +\begin{cppcode} + template + using ElsStruct = + S, + Sel1 + >, + Sel2 + > + >; + + // GraspStruct est défini dans |\acref{lst:alsk/concept/struct/grasp}| + template< + typename CH, typename Sel1, + typename InitLS, typename M, typename LS, typename Sel2, typename Sel3 + > + using GraspElsStruct = + GraspStruct, Sel1>; +\end{cppcode} +\mincpp{14} diff --git a/src/lst/alsk/concept/struct/minimal.tex b/src/lst/alsk/concept/struct/minimal.tex new file mode 100644 index 0000000..78d8a87 --- /dev/null +++ b/src/lst/alsk/concept/struct/minimal.tex @@ -0,0 +1,4 @@ +\begin{cppcode} + S +\end{cppcode} +\mincpp{14} diff --git a/src/lst/alsk/concept/struct/minimal_param.tex b/src/lst/alsk/concept/struct/minimal_param.tex new file mode 100644 index 0000000..108ebec --- /dev/null +++ b/src/lst/alsk/concept/struct/minimal_param.tex @@ -0,0 +1,5 @@ +\begin{cppcode} + template + using MinStruct = S; +\end{cppcode} +\mincpp{14} diff --git a/src/lst/alsk/exec/bones/farm.tex b/src/lst/alsk/exec/bones/farm.tex new file mode 100644 index 0000000..a84dbc4 --- /dev/null +++ b/src/lst/alsk/exec/bones/farm.tex @@ -0,0 +1,10 @@ +\begin{cppcode} + template struct Farm; + + template + struct Farm>: BoneBase { + Task task; + std::size_t n; + }; +\end{cppcode} +\mincpp{11} diff --git a/src/lst/alsk/exec/bones/farmpar.tex b/src/lst/alsk/exec/bones/farmpar.tex new file mode 100644 index 0000000..0d8390d --- /dev/null +++ b/src/lst/alsk/exec/bones/farmpar.tex @@ -0,0 +1,15 @@ +\begin{cppcode} + template + struct Impl, tag::Parallel, Executor>: + BoneImplBase, tag::Parallel, Executor> + { + // ... + + void operator()(Args... args) { + executor.template executeParallel( + *this, skeleton.task, std::forward_as_tuple(args...), skeleton.n + ); + } + }; +\end{cppcode} +\mincpp{11} diff --git a/src/lst/alsk/exec/bones/farmseq.tex b/src/lst/alsk/exec/bones/farmseq.tex new file mode 100644 index 0000000..c54c5c3 --- /dev/null +++ b/src/lst/alsk/exec/bones/farmseq.tex @@ -0,0 +1,19 @@ +\begin{cppcode} + template + struct Impl, tag::Sequential, Executor>: + BoneImplBase, tag::Sequential, Executor> + { + using This = Impl; + using Task = Execute; + + typename This::Skeleton skeleton; + typename This::Executor executor; + + void operator()(Args... args) { + executor.template executeSequential( + *this, skeleton.task, std::forward_as_tuple(args...), skeleton.n + ); + } + }; +\end{cppcode} +\mincpp{11} diff --git a/src/lst/alsk/exec/bones/farmtraits.tex b/src/lst/alsk/exec/bones/farmtraits.tex new file mode 100644 index 0000000..9ed03be --- /dev/null +++ b/src/lst/alsk/exec/bones/farmtraits.tex @@ -0,0 +1,8 @@ +\begin{cppcode} + template<> + struct SkeletonTraits { + static constexpr bool serial = false; + // ... + }; +\end{cppcode} +\mincpp{11} diff --git a/src/lst/alsk/exec/impl/threadpoolpar.tex b/src/lst/alsk/exec/impl/threadpoolpar.tex new file mode 100644 index 0000000..ea9a856 --- /dev/null +++ b/src/lst/alsk/exec/impl/threadpoolpar.tex @@ -0,0 +1,22 @@ +\begin{cppcode} + template< + typename Task, + typename Impl, typename Muscle, typename Params + > + void executeParallel( + Impl& impl, Muscle& muscle, Params const& params, std::size_t n + ) { + std::vector> futures(n); + + typename Impl::State& state = impl.state; + + for(std::size_t i = 0; i < n; ++i) { + futures[i] = state.executor.run([&]{ + Task::execute(impl, task, i, params); + }); + } + + state.executor.wait(futures); + } +\end{cppcode} +\mincpp{14} diff --git a/src/lst/alsk/repeat/opti/ctxid.tex b/src/lst/alsk/repeat/opti/ctxid.tex new file mode 100644 index 0000000..db9ca09 --- /dev/null +++ b/src/lst/alsk/repeat/opti/ctxid.tex @@ -0,0 +1,8 @@ +\begin{cppcode} + std::size_t contextIdFromTaskId(std::size_t id) { + // s: std::set, une subdivision + auto it = std::upper_bound(std::begin(s), std::end(s), id); + return std::distance(std::begin(s), it) - 1; + } +\end{cppcode} +\mincpp{11} diff --git a/src/lst/alsk/repeat/repeat/grasp.tex b/src/lst/alsk/repeat/repeat/grasp.tex new file mode 100644 index 0000000..d3d2e50 --- /dev/null +++ b/src/lst/alsk/repeat/repeat/grasp.tex @@ -0,0 +1,12 @@ +\begin{cppcode} + template + using GraspLinks = + L(P<0>), + Solution(P<0>, RNG), + Solution(P<0>, R<0>, RNG) + >, + Solution(Solution const&, Solution const&) + >; +\end{cppcode} +\mincpp{14} diff --git a/src/lst/alsk/usage/edsl/els.tex b/src/lst/alsk/usage/edsl/els.tex new file mode 100644 index 0000000..547147e --- /dev/null +++ b/src/lst/alsk/usage/edsl/els.tex @@ -0,0 +1,24 @@ +\begin{cppcode} + template< + typename Solution, typename PRNG, + typename InitLS, typename Mutate, typename LS, + typename InnerSelect, typename OuterSelect + > + using SkelEls = decltype(getSkeleton([]{ + constexpr auto initLS = link, P<1>), InitLS>(); + constexpr auto mutate = link, P<1>), Mutate>(); + constexpr auto ls = link), LS>(); + constexpr auto innerSelect = link(); + constexpr auto outerSelect = link(); + + return link(Solution, PRNG&)>(( + initLS, + link, P<1>)>( + seq(*link)>( + *link(P<0>, P<1>)>(mutate, ls) ->* innerSelect + )) ->* outerSelect + ) + )); + }())); +\end{cppcode} +\mincpp{20} diff --git a/src/lst/alsk/usage/edsl/farmsel.tex b/src/lst/alsk/usage/edsl/farmsel.tex new file mode 100644 index 0000000..25f9469 --- /dev/null +++ b/src/lst/alsk/usage/edsl/farmsel.tex @@ -0,0 +1,5 @@ +\begin{cppcode} + auto select = link(); + auto farmSel = link(*task) ->* select; +\end{cppcode} +\mincpp{14} diff --git a/src/lst/alsk/usage/edsl/getskeleton.tex b/src/lst/alsk/usage/edsl/getskeleton.tex new file mode 100644 index 0000000..5105eea --- /dev/null +++ b/src/lst/alsk/usage/edsl/getskeleton.tex @@ -0,0 +1,6 @@ +\begin{cppcode} + template + constexpr auto getSkeleton(Expression) + -> BuildSkeletonT; +\end{cppcode} +\mincpp{11} diff --git a/src/lst/alsk/usage/edsl/grasp.tex b/src/lst/alsk/usage/edsl/grasp.tex new file mode 100644 index 0000000..e7706a5 --- /dev/null +++ b/src/lst/alsk/usage/edsl/grasp.tex @@ -0,0 +1,16 @@ +\begin{cppcode} + template< + typename Problem, typename Solution, typename PRNG, + typename Init, typename LS, typename Select + > + using SkelGrasp = decltype(getSkeleton([]{ + constexpr auto init = link, P<1>), Init>(); + constexpr auto ls = link, P<1>), LS>(); + constexpr auto select = link(); + + return link( + (*link(P<0>, P<1>)>(init, ls)) ->* select + ); + }())); +\end{cppcode} +\mincpp{20} diff --git a/src/lst/alsk/usage/edsl/graspelsbody.tex b/src/lst/alsk/usage/edsl/graspelsbody.tex new file mode 100644 index 0000000..f123c77 --- /dev/null +++ b/src/lst/alsk/usage/edsl/graspelsbody.tex @@ -0,0 +1,19 @@ +\begin{cppcode} + // all pseudo-muscles are already defined and correctly linked + auto els = link(Solution)>( + initLocalSearch, + link)>( + iter(link( + farm(link(P<0>)>(mutate, ls)).select(innerSelect) + )).select(outerSelect) + ) + ); + + auto graspels = link( + farm(link(P<0>)>( + constructiveHeuristic, + box(link(R<0>)>(els)) + ).select(select) + ); +\end{cppcode} +\mincpp{14} diff --git a/src/lst/alsk/usage/edsl/indirlinks.tex b/src/lst/alsk/usage/edsl/indirlinks.tex new file mode 100644 index 0000000..4cecddc --- /dev/null +++ b/src/lst/alsk/usage/edsl/indirlinks.tex @@ -0,0 +1,5 @@ +\begin{cppcode} + auto localSearch = makeOperand(); + auto indirect = link, P<1>)>(localSearch); +\end{cppcode} +\mincpp{14} diff --git a/src/lst/alsk/usage/edsl/inlinegrasp.tex b/src/lst/alsk/usage/edsl/inlinegrasp.tex new file mode 100644 index 0000000..47a8e20 --- /dev/null +++ b/src/lst/alsk/usage/edsl/inlinegrasp.tex @@ -0,0 +1,10 @@ +\begin{cppcode} + auto init = link, P<1>), Init>(); + auto ls = link, P<1>), LS>(); + auto select = link(); + + auto grasp = link( + (*link(P<0>, P<1>)>(init, ls)) ->* select + ); +\end{cppcode} +\mincpp{14} diff --git a/src/lst/alsk/usage/edsl/links.tex b/src/lst/alsk/usage/edsl/links.tex new file mode 100644 index 0000000..6197964 --- /dev/null +++ b/src/lst/alsk/usage/edsl/links.tex @@ -0,0 +1,5 @@ +\begin{cppcode} + auto direct = makeOperand, P<1>), LocalSearch>(); + auto alt = link, P<1>), LocalSearch>(); +\end{cppcode} +\mincpp{14} diff --git a/src/lst/alsk/usage/edsl/loop.tex b/src/lst/alsk/usage/edsl/loop.tex new file mode 100644 index 0000000..1549dc0 --- /dev/null +++ b/src/lst/alsk/usage/edsl/loop.tex @@ -0,0 +1,4 @@ +\begin{cppcode} + auto taskLoop = seq(*task); // or loop(task) +\end{cppcode} +\mincpp{14} diff --git a/src/lst/alsk/usage/edsl/makeoperand.tex b/src/lst/alsk/usage/edsl/makeoperand.tex new file mode 100644 index 0000000..ba0018f --- /dev/null +++ b/src/lst/alsk/usage/edsl/makeoperand.tex @@ -0,0 +1,4 @@ +\begin{cppcode} + auto localSearch = makeOperand(); +\end{cppcode} +\mincpp{14} diff --git a/src/lst/alsk/usage/edsl/structures.tex b/src/lst/alsk/usage/edsl/structures.tex new file mode 100644 index 0000000..040ab3e --- /dev/null +++ b/src/lst/alsk/usage/edsl/structures.tex @@ -0,0 +1,6 @@ +\begin{cppcode} + auto taskSeq = (task0, task1); // or serial(task0, task1);|\label{line:alsk/usage/edsl/structures:seq}| + auto taskFarm = *task; // or farm(task);|\label{line:alsk/usage/edsl/structures:farm}| + auto taskWhile = while_(cond).do_(task);|\label{line:alsk/usage/edsl/structures:while}| +\end{cppcode} +\mincpp{14} diff --git a/src/lst/alsk/usage/impl/graspels.tex b/src/lst/alsk/usage/impl/graspels.tex new file mode 100644 index 0000000..b146b84 --- /dev/null +++ b/src/lst/alsk/usage/impl/graspels.tex @@ -0,0 +1,4 @@ +\begin{cppcode} + auto graspEls = implement(); +\end{cppcode} +\mincpp{14} diff --git a/src/lst/alsk/usage/impl/graspels_call.tex b/src/lst/alsk/usage/impl/graspels_call.tex new file mode 100644 index 0000000..a2149f5 --- /dev/null +++ b/src/lst/alsk/usage/impl/graspels_call.tex @@ -0,0 +1,4 @@ +\begin{cppcode} + tsp::Solution solution = graspEls(problem); +\end{cppcode} +\mincpp{14} diff --git a/src/lst/alsk/usage/impl/graspels_conf.tex b/src/lst/alsk/usage/impl/graspels_conf.tex new file mode 100644 index 0000000..7709d45 --- /dev/null +++ b/src/lst/alsk/usage/impl/graspels_conf.tex @@ -0,0 +1,9 @@ +\begin{cppcode*}{linenos} + graspEls.skeleton.n = 10;|\label{line:alsk/usage/impl/graspels_conf:n}| + graspEls.skeleton.task.task<1>().task<1>().n = 15;|\label{line:alsk/usage/impl/graspels_conf:taskn}| + graspEls.skeleton.task.task<1>().task<1>().task.n = 20;|\label{line:alsk/usage/impl/graspels_conf:tasktaskn}| + + graspEls.executor.cores = 16;|\label{line:alsk/usage/impl/graspels_conf:execcores}| + graspEls.executor.repeatability.expUpTo(64);|\label{line:alsk/usage/impl/graspels_conf:repeat}| +\end{cppcode*} +\mincpp{14} diff --git a/src/lst/alsk/usage/impl/graspels_edsl.tex b/src/lst/alsk/usage/impl/graspels_edsl.tex new file mode 100644 index 0000000..b840492 --- /dev/null +++ b/src/lst/alsk/usage/impl/graspels_edsl.tex @@ -0,0 +1,21 @@ +\begin{cppcode} + // all pseudo-muscles are already defined and correctly linked + auto elsBody = link(Solution)>( + initLocalSearch, + link)>( + seq(15 * link( + (20 * link(P<0>)>(mutate, ls)) ->* innerSelect + )) ->* (outerSelect) + ) + ); + + auto graspElsBody = link( + (10 * link(P<0>)>( + constructiveHeuristic, + box(link(R<0>)>(elsBody)) + )) ->* select + ); + + auto graspEls = implement(graspElsBody); +\end{cppcode} +\mincpp{14} diff --git a/src/lst/gnx/auto/add_auto.tex b/src/lst/gnx/auto/add_auto.tex new file mode 100644 index 0000000..55a10b1 --- /dev/null +++ b/src/lst/gnx/auto/add_auto.tex @@ -0,0 +1,7 @@ +\begin{cppcode} + template + auto addition(LHS lhs, RHS rhs) { + return lhs + rhs; + } +\end{cppcode} +\mincpp{14} diff --git a/src/lst/gnx/auto/add_tt.tex b/src/lst/gnx/auto/add_tt.tex new file mode 100644 index 0000000..923bd4f --- /dev/null +++ b/src/lst/gnx/auto/add_tt.tex @@ -0,0 +1,7 @@ +\begin{cppcode} + template + T addition(T lhs, T rhs) { + return lhs + rhs; + } +\end{cppcode} +\mincpp{98} diff --git a/src/lst/gnx/ccpp/fun_arg.tex b/src/lst/gnx/ccpp/fun_arg.tex new file mode 100644 index 0000000..aa7e724 --- /dev/null +++ b/src/lst/gnx/ccpp/fun_arg.tex @@ -0,0 +1,4 @@ +\begin{cppcode} + std::accumulate(std::begin(v), std::end(v), 0, sum); +\end{cppcode} +\mincpp{11} diff --git a/src/lst/gnx/ccpp/injection.tex b/src/lst/gnx/ccpp/injection.tex new file mode 100644 index 0000000..359eba2 --- /dev/null +++ b/src/lst/gnx/ccpp/injection.tex @@ -0,0 +1,9 @@ +\begin{ccode} + #define A * ((0 + #define B 0), + #define C ) + + // SUM(2, 3) : ((2) + (3)) = 2 + 3 = 5 + // SUM(2 A, B 3 C) : (( 2 * ((0+0), 3) )) = 2 * 3 = 6 +\end{ccode} +\minc{89} diff --git a/src/lst/gnx/ccpp/min.tex b/src/lst/gnx/ccpp/min.tex new file mode 100644 index 0000000..986a7cb --- /dev/null +++ b/src/lst/gnx/ccpp/min.tex @@ -0,0 +1,4 @@ +\begin{ccode} + #define MIN(a, b) ((a) < (b)? (a) : (b)) +\end{ccode} +\minc{89} diff --git a/src/lst/gnx/ccpp/sum.tex b/src/lst/gnx/ccpp/sum.tex new file mode 100644 index 0000000..e7ae815 --- /dev/null +++ b/src/lst/gnx/ccpp/sum.tex @@ -0,0 +1,4 @@ +\begin{ccode} + #define SUM(a, b) ((a) + (b)) +\end{ccode} +\minc{89} diff --git a/src/lst/gnx/concepts/direct_concept.tex b/src/lst/gnx/concepts/direct_concept.tex new file mode 100644 index 0000000..a82ea6c --- /dev/null +++ b/src/lst/gnx/concepts/direct_concept.tex @@ -0,0 +1,5 @@ +\begin{cppcode} + template + void f(T const&); +\end{cppcode} +\mincpp{20} diff --git a/src/lst/gnx/concepts/eqcomparablea.tex b/src/lst/gnx/concepts/eqcomparablea.tex new file mode 100644 index 0000000..ae74ef0 --- /dev/null +++ b/src/lst/gnx/concepts/eqcomparablea.tex @@ -0,0 +1,9 @@ +\begin{cppcode} + template + concept EqComparableA = InheritsFromA and requires(T a, T b) { + T(); + { a == b } -> bool; + { a != b } -> bool; + }; +\end{cppcode} +\mincpp{20} diff --git a/src/lst/gnx/concepts/error_concept.tex b/src/lst/gnx/concepts/error_concept.tex new file mode 100644 index 0000000..5df7131 --- /dev/null +++ b/src/lst/gnx/concepts/error_concept.tex @@ -0,0 +1,11 @@ +\begin{minted}[autogobble,breaklines]{text} + main.cpp: In function ‘int main()’: + main.cpp:12:8: error: cannot call function ‘void f(const T&) [with T = int]’ + f(0); // error + ^ + main.cpp:7:6: note: constraints not satisfied + void f(T const&); + ^ + main.cpp:7:6: note: ‘is_base_of_v’ evaluated to false +\end{minted} +\listingnote{(g++ 8.3)} diff --git a/src/lst/gnx/concepts/error_sfinae.tex b/src/lst/gnx/concepts/error_sfinae.tex new file mode 100644 index 0000000..d8cf709 --- /dev/null +++ b/src/lst/gnx/concepts/error_sfinae.tex @@ -0,0 +1,11 @@ +\begin{minted}[autogobble,breaklines]{text} + main.cpp: In function ‘int main()’: + main.cpp:12:8: error: no matching function for call to ‘f(int)’ + f(0); // error + ^ + main.cpp:7:6: note: candidate: ‘template >* > void f(const T&)’ + void f(T const&); + ^ + main.cpp:7:6: note: template argument deduction/substitution failed: +\end{minted} +\listingnote{(g++ 8.3)} diff --git a/src/lst/gnx/concepts/named_constraint.tex b/src/lst/gnx/concepts/named_constraint.tex new file mode 100644 index 0000000..f0de152 --- /dev/null +++ b/src/lst/gnx/concepts/named_constraint.tex @@ -0,0 +1,5 @@ +\begin{cppcode} + template + concept InheritsFromA = std::is_base_of_v; +\end{cppcode} +\mincpp{20} diff --git a/src/lst/gnx/concepts/requires_base_of.tex b/src/lst/gnx/concepts/requires_base_of.tex new file mode 100644 index 0000000..0a3b697 --- /dev/null +++ b/src/lst/gnx/concepts/requires_base_of.tex @@ -0,0 +1,5 @@ +\begin{cppcode} + template requires(std::is_base_of_v) + void f(T const&); +\end{cppcode} +\mincpp{20} diff --git a/src/lst/gnx/concepts/requires_concept.tex b/src/lst/gnx/concepts/requires_concept.tex new file mode 100644 index 0000000..3690cac --- /dev/null +++ b/src/lst/gnx/concepts/requires_concept.tex @@ -0,0 +1,5 @@ +\begin{cppcode} + template requires(EqComparableA) + void f(T const&); +\end{cppcode} +\mincpp{20} diff --git a/src/lst/gnx/concepts/sfinae_base_of.tex b/src/lst/gnx/concepts/sfinae_base_of.tex new file mode 100644 index 0000000..a63912f --- /dev/null +++ b/src/lst/gnx/concepts/sfinae_base_of.tex @@ -0,0 +1,16 @@ +\begin{cppcode} + #include + + struct A {}; + struct B: A {}; + + template>* = nullptr> + void f(T const&); + + int main() { + f(A{}); // ok + f(B{}); // ok + f(0); // error + } +\end{cppcode} +\mincpp{17} diff --git a/src/lst/gnx/cpp/syntax.tex b/src/lst/gnx/cpp/syntax.tex new file mode 100644 index 0000000..87f7744 --- /dev/null +++ b/src/lst/gnx/cpp/syntax.tex @@ -0,0 +1,4 @@ +\begin{cppcode} + template +\end{cppcode} +\mincpp{98} diff --git a/src/lst/gnx/forward/collapsing.tex b/src/lst/gnx/forward/collapsing.tex new file mode 100644 index 0000000..d5f2be3 --- /dev/null +++ b/src/lst/gnx/forward/collapsing.tex @@ -0,0 +1,11 @@ +\begin{cppcode} + using LRef = int&; + using RRef = int&&; + int n; + + LRef& r1 = n; // decltype(r1): int& + LRef&& r2 = n; // decltype(r2): int& + RRef& r3 = n; // decltype(r3): int& + RRef&& r4 = 0; // decltype(r4): int&& +\end{cppcode} +\mincpp{11} diff --git a/src/lst/gnx/forward/example.tex b/src/lst/gnx/forward/example.tex new file mode 100644 index 0000000..108717b --- /dev/null +++ b/src/lst/gnx/forward/example.tex @@ -0,0 +1,7 @@ +\begin{cppcode} + template + void callF(T&& arg) { + f(std::forward(arg)); + } +\end{cppcode} +\mincpp{11} diff --git a/src/lst/gnx/ifcx/getvalue.tex b/src/lst/gnx/ifcx/getvalue.tex new file mode 100644 index 0000000..0d7f82c --- /dev/null +++ b/src/lst/gnx/ifcx/getvalue.tex @@ -0,0 +1,12 @@ +\begin{cppcode} + template + T getValue(T t) { + return t; + } + + template + T getValue(T* t) { + return *t; + } +\end{cppcode} +\mincpp{98} diff --git a/src/lst/gnx/ifcx/getvalue_if.tex b/src/lst/gnx/ifcx/getvalue_if.tex new file mode 100644 index 0000000..745d558 --- /dev/null +++ b/src/lst/gnx/ifcx/getvalue_if.tex @@ -0,0 +1,10 @@ +\begin{cppcode} + template + auto getValue(T t) { + if constexpr(std::is_pointer_v) + return *t; + else + return t; + } +\end{cppcode} +\mincpp{17} diff --git a/src/lst/gnx/pack/add.tex b/src/lst/gnx/pack/add.tex new file mode 100644 index 0000000..1733bf5 --- /dev/null +++ b/src/lst/gnx/pack/add.tex @@ -0,0 +1,12 @@ +\begin{cppcode} + template + auto add(T value, Ts... values) { + return value + add(values...); + } + + template + auto add(T value) { + return value; + } +\end{cppcode} +\mincpp{14} diff --git a/src/lst/gnx/pack/avg.tex b/src/lst/gnx/pack/avg.tex new file mode 100644 index 0000000..ad17750 --- /dev/null +++ b/src/lst/gnx/pack/avg.tex @@ -0,0 +1,7 @@ +\begin{cppcode} + template + auto avg(Ts... values) { + return (values + ...) / sizeof...(Ts); + } +\end{cppcode} +\mincpp{14} diff --git a/src/lst/gnx/sfinae/bad.tex b/src/lst/gnx/sfinae/bad.tex new file mode 100644 index 0000000..7dac229 --- /dev/null +++ b/src/lst/gnx/sfinae/bad.tex @@ -0,0 +1,8 @@ +\begin{cppcode} + template + void f(T); + + template + void f(T); +\end{cppcode} +\errcpp{} diff --git a/src/lst/gnx/sfinae/enableif.tex b/src/lst/gnx/sfinae/enableif.tex new file mode 100644 index 0000000..c46d902 --- /dev/null +++ b/src/lst/gnx/sfinae/enableif.tex @@ -0,0 +1,10 @@ +\begin{cppcode} + template + struct EnableIf {}; + + template<> + struct EnableIf { + using type = void; + }; +\end{cppcode} +\mincpp{11} diff --git a/src/lst/gnx/sfinae/good.tex b/src/lst/gnx/sfinae/good.tex new file mode 100644 index 0000000..98851fb --- /dev/null +++ b/src/lst/gnx/sfinae/good.tex @@ -0,0 +1,8 @@ +\begin{cppcode} + template + void f(T); + + template + void f(T); +\end{cppcode} +\mincpp{11} diff --git a/src/lst/gnx/sfinae/parity.tex b/src/lst/gnx/sfinae/parity.tex new file mode 100644 index 0000000..e3ddf38 --- /dev/null +++ b/src/lst/gnx/sfinae/parity.tex @@ -0,0 +1,8 @@ +\begin{cppcode} + template::type* = nullptr> + void f(); + + template::type* = nullptr> + void f(); +\end{cppcode} +\mincpp{11} diff --git a/src/lst/gnx/sfinae/sfinae.tex b/src/lst/gnx/sfinae/sfinae.tex new file mode 100644 index 0000000..0661d77 --- /dev/null +++ b/src/lst/gnx/sfinae/sfinae.tex @@ -0,0 +1,12 @@ +\begin{cppcode} + template + void f(T); + + void f(...); + + struct A { using X = void; }; + + f(A{}); // first + f(0); // second +\end{cppcode} +\mincpp{11} diff --git a/src/lst/gnx/spe/array.tex b/src/lst/gnx/spe/array.tex new file mode 100644 index 0000000..0ab3b1c --- /dev/null +++ b/src/lst/gnx/spe/array.tex @@ -0,0 +1,7 @@ +\begin{cppcode} + template + struct Array { + T data[capacity]; + }; +\end{cppcode} +\mincpp{98} diff --git a/src/lst/gnx/spe/array1.tex b/src/lst/gnx/spe/array1.tex new file mode 100644 index 0000000..d04bbdd --- /dev/null +++ b/src/lst/gnx/spe/array1.tex @@ -0,0 +1,7 @@ +\begin{cppcode} + template + struct Array { + T data; + }; +\end{cppcode} +\mincpp{98} diff --git a/src/lst/gnx/spe/fun_select.tex b/src/lst/gnx/spe/fun_select.tex new file mode 100644 index 0000000..e06a8b3 --- /dev/null +++ b/src/lst/gnx/spe/fun_select.tex @@ -0,0 +1,11 @@ +\begin{cppcode} + template void foo(T); // 1 + template<> void foo(int*); // 2 + template void foo(T*); // 3 + + int main() { + int bar; + foo(&bar); + } +\end{cppcode} +\mincpp{98} diff --git a/src/lst/gnx/spe/min_charp.tex b/src/lst/gnx/spe/min_charp.tex new file mode 100644 index 0000000..12f8144 --- /dev/null +++ b/src/lst/gnx/spe/min_charp.tex @@ -0,0 +1,7 @@ +\begin{cppcode} + template<> + char* min(char* a, char* b) { + return strcmp(a, b) < 0? |a| : b; + } +\end{cppcode} +\mincpp{98} diff --git a/src/lst/gnx/spe/uniqueptr.tex b/src/lst/gnx/spe/uniqueptr.tex new file mode 100644 index 0000000..47e69c9 --- /dev/null +++ b/src/lst/gnx/spe/uniqueptr.tex @@ -0,0 +1,11 @@ +\begin{cppcode} + template + class UniquePtr { + T *_ptr; + + // rest of implementation + public: + ~UniquePtr() { delete _ptr; } + }; +\end{cppcode} +\mincpp{98} diff --git a/src/lst/gnx/spe/uniqueptr_array.tex b/src/lst/gnx/spe/uniqueptr_array.tex new file mode 100644 index 0000000..1d1b4ef --- /dev/null +++ b/src/lst/gnx/spe/uniqueptr_array.tex @@ -0,0 +1,11 @@ +\begin{cppcode} + template + class UniquePtr { + T *_ptr; + + // rest of implementation + public: + ~UniquePtr() { delete[] _ptr; } + }; +\end{cppcode} +\mincpp{98} diff --git a/src/lst/gnx/syntax/arrayint.tex b/src/lst/gnx/syntax/arrayint.tex new file mode 100644 index 0000000..dc07ef8 --- /dev/null +++ b/src/lst/gnx/syntax/arrayint.tex @@ -0,0 +1,7 @@ +\begin{cppcode} + template + struct ArrayInt { + int data[capacity]; + }; +\end{cppcode} +\mincpp{98} diff --git a/src/lst/gnx/syntax/isoddvar.tex b/src/lst/gnx/syntax/isoddvar.tex new file mode 100644 index 0000000..2dd689a --- /dev/null +++ b/src/lst/gnx/syntax/isoddvar.tex @@ -0,0 +1,5 @@ +\begin{cppcode} + template + constexpr bool isOdd = n&|1|; +\end{cppcode} +\mincpp{14} diff --git a/src/lst/gnx/syntax/min.tex b/src/lst/gnx/syntax/min.tex new file mode 100644 index 0000000..b176035 --- /dev/null +++ b/src/lst/gnx/syntax/min.tex @@ -0,0 +1,7 @@ +\begin{cppcode} + template + T min(T a, T b) { + return a < b? |a| : b; + } +\end{cppcode} +\mincpp{98} diff --git a/src/lst/gnx/syntax/sum.tex b/src/lst/gnx/syntax/sum.tex new file mode 100644 index 0000000..4bababe --- /dev/null +++ b/src/lst/gnx/syntax/sum.tex @@ -0,0 +1,7 @@ +\begin{cppcode} + template + int sum() { + return a + b; + } +\end{cppcode} +\mincpp{98} diff --git a/src/lst/gnx/syntax/sum_call.tex b/src/lst/gnx/syntax/sum_call.tex new file mode 100644 index 0000000..02d5c9e --- /dev/null +++ b/src/lst/gnx/syntax/sum_call.tex @@ -0,0 +1,5 @@ +\begin{cppcode} + int (*sum_5_7)() = sum<5, 7>; + std::cout << sum_5_7(); +\end{cppcode} +\mincpp{98} diff --git a/src/lst/gnx/tad/guides.tex b/src/lst/gnx/tad/guides.tex new file mode 100644 index 0000000..0633c75 --- /dev/null +++ b/src/lst/gnx/tad/guides.tex @@ -0,0 +1,25 @@ +\begin{cppcode} + template + struct A { + A(T const&); + + template + A(T const&, E const&, int); + }; + + /* implicit deduction guides: + * + * template + * A(U) -> A; + * + * template + * A(U, E, int) -> A; + */ + + A(char const*) -> A; + + A a{0}; // a: A + A b{0, "", 1}; // b: A + A c{""}; // c: A +\end{cppcode} +\mincpp{17} diff --git a/src/lst/gnx/tad/non_deducible.tex b/src/lst/gnx/tad/non_deducible.tex new file mode 100644 index 0000000..bc2f0b2 --- /dev/null +++ b/src/lst/gnx/tad/non_deducible.tex @@ -0,0 +1,5 @@ +\begin{cppcode} + template + T f(); +\end{cppcode} +\mincpp{98} diff --git a/src/lst/gnx/tad/partial.tex b/src/lst/gnx/tad/partial.tex new file mode 100644 index 0000000..2b258c9 --- /dev/null +++ b/src/lst/gnx/tad/partial.tex @@ -0,0 +1,7 @@ +\begin{cppcode} + template + void f(T, U); + + f(0, 5.f); // T is specified to be int, U is deduced to be float +\end{cppcode} +\mincpp{98} diff --git a/src/lst/gnx/tad/partial_explicit.tex b/src/lst/gnx/tad/partial_explicit.tex new file mode 100644 index 0000000..678d6a2 --- /dev/null +++ b/src/lst/gnx/tad/partial_explicit.tex @@ -0,0 +1,9 @@ +\begin{cppcode} + template + void f(T, V); + + // f(0, 0); // U cannot be deduced + + f(0, 0); +\end{cppcode} +\mincpp{98} diff --git a/src/lst/gnx/tad/value.tex b/src/lst/gnx/tad/value.tex new file mode 100644 index 0000000..aa601cc --- /dev/null +++ b/src/lst/gnx/tad/value.tex @@ -0,0 +1,5 @@ +\begin{cppcode} + template + void f(std::array); +\end{cppcode} +\mincpp{11} diff --git a/src/lst/gnx/tad/value_bad.tex b/src/lst/gnx/tad/value_bad.tex new file mode 100644 index 0000000..7a965c7 --- /dev/null +++ b/src/lst/gnx/tad/value_bad.tex @@ -0,0 +1,5 @@ +\begin{cppcode} + template + void f(std::array); +\end{cppcode} +\errcpp{} diff --git a/src/lst/mp/mpt/et/1_asm.tex b/src/lst/mp/mpt/et/1_asm.tex new file mode 100644 index 0000000..62304f7 --- /dev/null +++ b/src/lst/mp/mpt/et/1_asm.tex @@ -0,0 +1,6 @@ +\begin{asmcode} + imul eax, edx + add eax, ecx +\end{asmcode} +\vspace{-7.5ex} +\asm diff --git a/src/lst/mp/mpt/et/1_binexpr.tex b/src/lst/mp/mpt/et/1_binexpr.tex new file mode 100644 index 0000000..4220a54 --- /dev/null +++ b/src/lst/mp/mpt/et/1_binexpr.tex @@ -0,0 +1,12 @@ +\begin{cppcode} + template + struct BinExpr { + LHS lhs; + RHS rhs; + + auto eval() const { + return Op::eval(lhs.eval(), rhs.eval()); + } + }; +\end{cppcode} +\mincpp{14} diff --git a/src/lst/mp/mpt/et/1_eval.tex b/src/lst/mp/mpt/et/1_eval.tex new file mode 100644 index 0000000..f07e376 --- /dev/null +++ b/src/lst/mp/mpt/et/1_eval.tex @@ -0,0 +1,6 @@ +\begin{cppcode} + MyExpr expr{{v0}, {{v1}, {v2}}}; + int result = expr.eval(); +\end{cppcode} +\vspace{-.9ex} +\mincpp{11} diff --git a/src/lst/mp/mpt/et/1_expr.tex b/src/lst/mp/mpt/et/1_expr.tex new file mode 100644 index 0000000..61dde85 --- /dev/null +++ b/src/lst/mp/mpt/et/1_expr.tex @@ -0,0 +1,5 @@ +\begin{cppcode} + using SubExpr = BinExpr, Value>; + using MyExpr = BinExpr, SubExpr>; +\end{cppcode} +\mincpp{11} diff --git a/src/lst/mp/mpt/et/1_operators.tex b/src/lst/mp/mpt/et/1_operators.tex new file mode 100644 index 0000000..691993f --- /dev/null +++ b/src/lst/mp/mpt/et/1_operators.tex @@ -0,0 +1,17 @@ +% TODO inline, decltype(auto), std::forward, ... ? +\begin{cppcode} + struct Add { + template + static auto eval(T lhs, T rhs) { + return lhs + rhs; + } + }; + + struct Mul { + template + static auto eval(T lhs, T rhs) { + return lhs * rhs; + } + }; +\end{cppcode} +\mincpp{14} diff --git a/src/lst/mp/mpt/et/1_value.tex b/src/lst/mp/mpt/et/1_value.tex new file mode 100644 index 0000000..531ce8b --- /dev/null +++ b/src/lst/mp/mpt/et/1_value.tex @@ -0,0 +1,13 @@ +% TODO inline, decltype(auto), std::forward, ... ? +\begin{cppcode} + template + class Value { + T _value; + + public: + Value(T value): _value{value} {} + + auto eval() const { return _value; } + }; +\end{cppcode} +\mincpp{14} diff --git a/src/lst/mp/mpt/et/2_expr.tex b/src/lst/mp/mpt/et/2_expr.tex new file mode 100644 index 0000000..08d47f0 --- /dev/null +++ b/src/lst/mp/mpt/et/2_expr.tex @@ -0,0 +1,18 @@ +\begin{cppcode} + template + class Expr { + std::tuple operands; + + public: + auto eval() const { + return eval(std::make_index_sequence{}); + } + + private: + template + auto eval(std::index_sequence) const { + return Op::eval(std::get(operands).eval()...); + } + }; +\end{cppcode} +\mincpp{14} diff --git a/src/lst/mp/mpt/et/3_expandedpack.tex b/src/lst/mp/mpt/et/3_expandedpack.tex new file mode 100644 index 0000000..9a7bc4c --- /dev/null +++ b/src/lst/mp/mpt/et/3_expandedpack.tex @@ -0,0 +1,8 @@ +\begin{cppcode} + Op::eval( + std::get<0>(operands).eval(), + std::get<1>(operands).eval(), + std::get<2>(operands).eval() + ) +\end{cppcode} +\mincpp{11} diff --git a/src/lst/mp/mpt/et/3_op_add.tex b/src/lst/mp/mpt/et/3_op_add.tex new file mode 100644 index 0000000..0fb9dbf --- /dev/null +++ b/src/lst/mp/mpt/et/3_op_add.tex @@ -0,0 +1,7 @@ +\begin{cppcode} + template + auto operator+(Value lhs, BinExpr, Value> rhs) { + return BinExpr, BinExpr, Value>>{lhs, rhs}; + } +\end{cppcode} +\mincpp{14} diff --git a/src/lst/mp/mpt/et/3_op_gen.tex b/src/lst/mp/mpt/et/3_op_gen.tex new file mode 100644 index 0000000..5df98a8 --- /dev/null +++ b/src/lst/mp/mpt/et/3_op_gen.tex @@ -0,0 +1,10 @@ +\begin{cppcode} + template< + typename LHS, typename RHS, + std::enable_if_t && IsExpr>* = nullptr + > + auto operator*(LHS lhs, RHS rhs) { + return BinExpr{lhs, rhs}; + } +\end{cppcode} +\mincpp{14} diff --git a/src/lst/mp/mpt/et/3_op_mul.tex b/src/lst/mp/mpt/et/3_op_mul.tex new file mode 100644 index 0000000..b6d668d --- /dev/null +++ b/src/lst/mp/mpt/et/3_op_mul.tex @@ -0,0 +1,7 @@ +\begin{cppcode} + template + auto operator*(Value lhs, Value rhs) { + return BinExpr, Value>{lhs, rhs}; + } +\end{cppcode} +\mincpp{14} diff --git a/src/lst/mp/mpt/et/3_traits.tex b/src/lst/mp/mpt/et/3_traits.tex new file mode 100644 index 0000000..6fc14e5 --- /dev/null +++ b/src/lst/mp/mpt/et/3_traits.tex @@ -0,0 +1,11 @@ +% TODO fix coloring +\begin{cppcode} + template struct IsExpr: std::false_type {}; + + template + struct IsExpr>: std::true_type {}; + + template + struct IsExpr>: std::true_type {}; +\end{cppcode} +\mincpp{11} diff --git a/src/lst/mp/mpt/et/4_evaluators.tex b/src/lst/mp/mpt/et/4_evaluators.tex new file mode 100644 index 0000000..f4eae4a --- /dev/null +++ b/src/lst/mp/mpt/et/4_evaluators.tex @@ -0,0 +1,19 @@ +\begin{cppcode} + struct EvaluatorElem { + template + static auto eval(tag::Add, T lhs, T rhs) { return lhs + rhs; } + + template + static auto eval(tag::Mul, T lhs, T rhs) { return lhs * rhs; } + + template + static auto eval(tag::Minus, T rhs) { return -rhs; } + }; + + struct EvaluatorBool { + static auto eval(tag::Add, bool lhs, bool rhs) { return lhs or rhs; } + static auto eval(tag::Mul, bool lhs, bool rhs) { return lhs and rhs; } + static auto eval(tag::Minus, bool rhs) { return not rhs; } + }; +\end{cppcode} +\mincpp{14} diff --git a/src/lst/mp/mpt/et/4_evaluators_template.tex b/src/lst/mp/mpt/et/4_evaluators_template.tex new file mode 100644 index 0000000..f1a6013 --- /dev/null +++ b/src/lst/mp/mpt/et/4_evaluators_template.tex @@ -0,0 +1,22 @@ +\begin{cppcode} + template struct EvaluatorElem; + + template<> + struct EvaluatorElem { + template + static auto eval(T lhs, T rhs) { return lhs + rhs; } + }; + + template<> + struct EvaluatorElem { + template + static auto eval(T lhs, T rhs) { return lhs * rhs; } + }; + + template<> + struct EvaluatorElem { + template + static auto eval(T rhs) { return -rhs; } + }; +\end{cppcode} +\mincpp{14} diff --git a/src/lst/mp/mpt/et/4_expr_eval.tex b/src/lst/mp/mpt/et/4_expr_eval.tex new file mode 100644 index 0000000..05d9823 --- /dev/null +++ b/src/lst/mp/mpt/et/4_expr_eval.tex @@ -0,0 +1,7 @@ +\begin{cppcode} + template + auto eval(std::index_sequence) const { + return Evaluator::eval(Op{}, std::get(operands).template eval()...); + } +\end{cppcode} +\mincpp{14} diff --git a/src/lst/mp/mpt/et/4_expr_eval_template.tex b/src/lst/mp/mpt/et/4_expr_eval_template.tex new file mode 100644 index 0000000..b84cadd --- /dev/null +++ b/src/lst/mp/mpt/et/4_expr_eval_template.tex @@ -0,0 +1,7 @@ +\begin{cppcode} + template class Evaluator, std::size_t... is> + auto eval(std::index_sequence) const { + return Evaluator::eval(std::get(operands).template eval()...); + } +\end{cppcode} +\mincpp{14} diff --git a/src/lst/mp/mpt/et/4_tags.tex b/src/lst/mp/mpt/et/4_tags.tex new file mode 100644 index 0000000..70e4eaf --- /dev/null +++ b/src/lst/mp/mpt/et/4_tags.tex @@ -0,0 +1,10 @@ +\begin{cppcode} + namespace tag { + + struct Add {}; + struct Mul {}; + struct Minus {}; + + } +\end{cppcode} +\mincpp{98} diff --git a/src/lst/mp/mpt/et/5_bind.tex b/src/lst/mp/mpt/et/5_bind.tex new file mode 100644 index 0000000..ca622db --- /dev/null +++ b/src/lst/mp/mpt/et/5_bind.tex @@ -0,0 +1,10 @@ +\begin{cppcode} + int f(int x0, int x1, int x2, int x3, int x4) { + return x0 + 2*x1 + 3*x2 + 5*x3 + 7*x4; + } + + // the two next lines are equivalent + f(42, 73, 73, 137, 108); + std::bind(f, 42, _1, _1, _3, _2)(73, 108, 137); +\end{cppcode} +\mincpp{11} diff --git a/src/lst/mp/mpt/et/5_lambda.tex b/src/lst/mp/mpt/et/5_lambda.tex new file mode 100644 index 0000000..41e7837 --- /dev/null +++ b/src/lst/mp/mpt/et/5_lambda.tex @@ -0,0 +1,7 @@ +\begin{cppcode} + // sort in descending order + std::sort(std::begin(c), std::end(c), _0 > _1); + // generate square values + std::transform(std::begin(c), std::end(c), std::begin(o), _0 * _0); +\end{cppcode} +\mincpp{98} diff --git a/src/lst/mp/mpt/et/5_lambda_arguments.tex b/src/lst/mp/mpt/et/5_lambda_arguments.tex new file mode 100644 index 0000000..ff21d9c --- /dev/null +++ b/src/lst/mp/mpt/et/5_lambda_arguments.tex @@ -0,0 +1,13 @@ +\begin{cppcode} + template + struct Argument {}; + + namespace arg { + + Argument<0> _0; + Argument<1> _1; + // ... + + } +\end{cppcode} +\mincpp{98} diff --git a/src/lst/mp/mpt/metafunction/factorial.11.tex b/src/lst/mp/mpt/metafunction/factorial.11.tex new file mode 100644 index 0000000..b1c9c31 --- /dev/null +++ b/src/lst/mp/mpt/metafunction/factorial.11.tex @@ -0,0 +1,12 @@ +\begin{cppcode} + template + struct Factorial { + static constexpr unsigned int value = n * Factorial::value; + }; + + template<> + struct Factorial<0> { + static constexpr unsigned int value = 1; + }; +\end{cppcode} +\mincpp{11} diff --git a/src/lst/mp/mpt/metafunction/factorial.14.tex b/src/lst/mp/mpt/metafunction/factorial.14.tex new file mode 100644 index 0000000..9560114 --- /dev/null +++ b/src/lst/mp/mpt/metafunction/factorial.14.tex @@ -0,0 +1,8 @@ +\begin{cppcode} + template + constexpr unsigned int factorial = n * factorial; + + template<> + constexpr unsigned int factorial<0> = 1; +\end{cppcode} +\mincpp{14} diff --git a/src/lst/mp/mpt/metafunction/factorial.17.tex b/src/lst/mp/mpt/metafunction/factorial.17.tex new file mode 100644 index 0000000..b78ce01 --- /dev/null +++ b/src/lst/mp/mpt/metafunction/factorial.17.tex @@ -0,0 +1,8 @@ +\begin{cppcode} + template + unsigned int factorial() { + if constexpr(n > 1) return n * factorial(); + return 1; + } +\end{cppcode} +\mincpp{17} diff --git a/src/lst/mp/mpt/metafunction/factorial.20.tex b/src/lst/mp/mpt/metafunction/factorial.20.tex new file mode 100644 index 0000000..ba5412a --- /dev/null +++ b/src/lst/mp/mpt/metafunction/factorial.20.tex @@ -0,0 +1,7 @@ +\begin{cppcode} + consteval unsigned int factorial(int n) { + if(n > 1) return n * factorial(n-1); + return 1; + } +\end{cppcode} +\mincpp{20} diff --git a/src/lst/mp/mpt/metafunction/factorial.98.tex b/src/lst/mp/mpt/metafunction/factorial.98.tex new file mode 100644 index 0000000..5dd2417 --- /dev/null +++ b/src/lst/mp/mpt/metafunction/factorial.98.tex @@ -0,0 +1,12 @@ +\begin{cppcode} + template + struct Factorial { + enum { value = n * Factorial::value }; + }; + + template<> + struct Factorial<0> { + enum { value = 1 }; + }; +\end{cppcode} +\mincpp{98} diff --git a/src/lst/mp/mpt/metafunction/factorial.asm.tex b/src/lst/mp/mpt/metafunction/factorial.asm.tex new file mode 100644 index 0000000..d1b4d3f --- /dev/null +++ b/src/lst/mp/mpt/metafunction/factorial.asm.tex @@ -0,0 +1,20 @@ +\begin{asmcode} +factorial(int): ; argument stored in edi + mov eax, 1 ; eax is the return value + cmp edi, 1 + jle .L4 ; edi <= 1 + mov edx, 1 +.L3: ; begin loop, edx from 1 to edi + imul eax, edx + add edx, 1 + cmp edi, edx + jne .L3 ; loop while edx < edi + ret +.L4: + ret +main: + mov edi, 10 ; prepare argument + call factorial(int) ; call factorial + ret +\end{asmcode} +\asm diff --git a/src/lst/mp/mpt/metafunction/factorial.haskell.tex b/src/lst/mp/mpt/metafunction/factorial.haskell.tex new file mode 100644 index 0000000..26fa71a --- /dev/null +++ b/src/lst/mp/mpt/metafunction/factorial.haskell.tex @@ -0,0 +1,5 @@ +\begin{haskellcode} + factorial 0 = 1 + factorial n = n * factorial (n-1) +\end{haskellcode} +\haskell diff --git a/src/lst/mp/mpt/metafunction/fibonacci.tex b/src/lst/mp/mpt/metafunction/fibonacci.tex new file mode 100644 index 0000000..dc7f4b1 --- /dev/null +++ b/src/lst/mp/mpt/metafunction/fibonacci.tex @@ -0,0 +1,11 @@ +\begin{cppcode} + template + constexpr unsigned int Fibonacci = Fibonacci + Fibonacci; + + template<> + constexpr unsigned int Fibonacci<0> = 0; + + template<> + constexpr unsigned int Fibonacci<1> = 1; +\end{cppcode} +\mincpp{14} diff --git a/src/lst/mp/mpt/metafunction/if.tex b/src/lst/mp/mpt/metafunction/if.tex new file mode 100644 index 0000000..ecaba0c --- /dev/null +++ b/src/lst/mp/mpt/metafunction/if.tex @@ -0,0 +1,14 @@ +\begin{cppcode} + template struct If; + + template + struct If { + using type = T; + }; + + template + struct If { + using type = F; + }; +\end{cppcode} +\mincpp{11} diff --git a/src/lst/mp/mpt/metafunction/pow.rt.tex b/src/lst/mp/mpt/metafunction/pow.rt.tex new file mode 100644 index 0000000..7be0fca --- /dev/null +++ b/src/lst/mp/mpt/metafunction/pow.rt.tex @@ -0,0 +1,8 @@ +\begin{cppcode} + int pow(int x, int n) { + int r = 1; + while(n-- > 0) r *= x; + return r; + } +\end{cppcode} +\mincpp{98} diff --git a/src/lst/mp/mpt/metafunction/pow.tex b/src/lst/mp/mpt/metafunction/pow.tex new file mode 100644 index 0000000..13b0e92 --- /dev/null +++ b/src/lst/mp/mpt/metafunction/pow.tex @@ -0,0 +1,12 @@ +\begin{cppcode} + template + constexpr int pow(int x) { + return x * pow(x); + } + + template<> + constexpr int pow<0>(int) { + return 1; + } +\end{cppcode} +\mincpp{11} diff --git a/src/lst/mp/mpt/metafunction/removecv.tex b/src/lst/mp/mpt/metafunction/removecv.tex new file mode 100644 index 0000000..53d8c62 --- /dev/null +++ b/src/lst/mp/mpt/metafunction/removecv.tex @@ -0,0 +1,11 @@ +\begin{cppcode} + template struct RemoveCV + { using type = T; }; + template struct RemoveCV + { using type = T; }; + template struct RemoveCV + { using type = T; }; + template struct RemoveCV + { using type = T; }; +\end{cppcode} +\mincpp{11} diff --git a/src/lst/mp/mpt/metafunction/typelist.tex b/src/lst/mp/mpt/metafunction/typelist.tex new file mode 100644 index 0000000..aeef521 --- /dev/null +++ b/src/lst/mp/mpt/metafunction/typelist.tex @@ -0,0 +1,10 @@ +\begin{cppcode} + struct EndOfList {}; + + template + struct TypeList { + using Head = Head_; + using Tail = Tail_; + }; +\end{cppcode} +\mincpp{11} diff --git a/src/lst/mp/mpt/metafunction/typelistcontains.tex b/src/lst/mp/mpt/metafunction/typelistcontains.tex new file mode 100644 index 0000000..76996d6 --- /dev/null +++ b/src/lst/mp/mpt/metafunction/typelistcontains.tex @@ -0,0 +1,20 @@ +% TODO overflow +\begin{cppcode} + template struct TypeListContains; + + template + struct TypeListContains, T> { + static constexpr bool value = TypeListContains, T>::value; + }; + + template + struct TypeListContains, T> { + static constexpr bool value = true; + }; + + template + struct TypeListContains, T> { + static constexpr bool value = false; + }; +\end{cppcode} +\mincpp{11} diff --git a/src/lst/mp/mpt/metafunction/typelistget.call.tex b/src/lst/mp/mpt/metafunction/typelistget.call.tex new file mode 100644 index 0000000..ba1aadc --- /dev/null +++ b/src/lst/mp/mpt/metafunction/typelistget.call.tex @@ -0,0 +1,5 @@ +\begin{cppcode} + using Second = TypeListGet::type; + // Second is: int +\end{cppcode} +\mincpp{11} diff --git a/src/lst/mp/mpt/metafunction/typelistget.tex b/src/lst/mp/mpt/metafunction/typelistget.tex new file mode 100644 index 0000000..3bfd5cd --- /dev/null +++ b/src/lst/mp/mpt/metafunction/typelistget.tex @@ -0,0 +1,14 @@ +\begin{cppcode} + template struct TypeListGet; + + template + struct TypeListGet, i> { + using type = typename TypeListGet, i-1>::type; + }; + + template + struct TypeListGet, 0> { + using type = H; + }; +\end{cppcode} +\mincpp{11} diff --git a/src/lst/mp/mpt/metafunction/typelistpushback.call.tex b/src/lst/mp/mpt/metafunction/typelistpushback.call.tex new file mode 100644 index 0000000..aa2ee1a --- /dev/null +++ b/src/lst/mp/mpt/metafunction/typelistpushback.call.tex @@ -0,0 +1,6 @@ +\begin{cppcode} + using Types = TypeList; + using Result = TypeListPushBack::type; + // Result is: TypeList +\end{cppcode} +\mincpp{11} diff --git a/src/lst/mp/mpt/metafunction/typelistpushback.tex b/src/lst/mp/mpt/metafunction/typelistpushback.tex new file mode 100644 index 0000000..a3338ce --- /dev/null +++ b/src/lst/mp/mpt/metafunction/typelistpushback.tex @@ -0,0 +1,9 @@ +\begin{cppcode} + template struct TypeListPushBack; + + template + struct TypeListPushBack, T> { + using type = TypeList; + }; +\end{cppcode} +\mincpp{11} diff --git a/src/lst/mp/mpt/metafunction/typelistrev.call.tex b/src/lst/mp/mpt/metafunction/typelistrev.call.tex new file mode 100644 index 0000000..b784945 --- /dev/null +++ b/src/lst/mp/mpt/metafunction/typelistrev.call.tex @@ -0,0 +1,5 @@ +\begin{cppcode} + using Result = TypeListRev::type; + // Result is: TypeList +\end{cppcode} +\mincpp{11} diff --git a/src/lst/mp/mpt/metafunction/typelistrev.tex b/src/lst/mp/mpt/metafunction/typelistrev.tex new file mode 100644 index 0000000..c858b72 --- /dev/null +++ b/src/lst/mp/mpt/metafunction/typelistrev.tex @@ -0,0 +1,15 @@ +\begin{cppcode} + template struct TypeListRev; + + template + struct TypeListRev> { + using Rev = typename TypeListRev>::type; + using type = typename TypeListPushBack::type; + }; + + template<> + struct TypeListRev> { + using type = TypeList<>; + }; +\end{cppcode} +\mincpp{11} diff --git a/src/lst/mp/mpt/metafunction/typelistreverse.tex b/src/lst/mp/mpt/metafunction/typelistreverse.tex new file mode 100644 index 0000000..c858b72 --- /dev/null +++ b/src/lst/mp/mpt/metafunction/typelistreverse.tex @@ -0,0 +1,15 @@ +\begin{cppcode} + template struct TypeListRev; + + template + struct TypeListRev> { + using Rev = typename TypeListRev>::type; + using type = typename TypeListPushBack::type; + }; + + template<> + struct TypeListRev> { + using type = TypeList<>; + }; +\end{cppcode} +\mincpp{11} diff --git a/src/lst/mp/mpt/metafunction/typelistuniq.call.tex b/src/lst/mp/mpt/metafunction/typelistuniq.call.tex new file mode 100644 index 0000000..eb265c6 --- /dev/null +++ b/src/lst/mp/mpt/metafunction/typelistuniq.call.tex @@ -0,0 +1,7 @@ +\begin{cppcode} + using Result = TypeListUniq< + TypeList + >::type; + // Result is: TypeList +\end{cppcode} +\mincpp{11} diff --git a/src/lst/mp/mpt/metafunction/typelistuniq.tex b/src/lst/mp/mpt/metafunction/typelistuniq.tex new file mode 100644 index 0000000..63e7c51 --- /dev/null +++ b/src/lst/mp/mpt/metafunction/typelistuniq.tex @@ -0,0 +1,19 @@ +\begin{cppcode} + template> struct TypeListUniq {}; + + template + struct TypeListUniq, TypeList> { + using Result = typename If< + TypeListContains, H>::value, + TypeList, + TypeList + >::type; + using type = typename TypeListUniq, Result>::type; + }; + + template + struct TypeListUniq, Result> { + using type = Result; + }; +\end{cppcode} +\mincpp{11} diff --git a/src/lst/mp/mpt/metafunction/typelistvar.tex b/src/lst/mp/mpt/metafunction/typelistvar.tex new file mode 100644 index 0000000..2a6450b --- /dev/null +++ b/src/lst/mp/mpt/metafunction/typelistvar.tex @@ -0,0 +1,4 @@ +\begin{cppcode} + template struct TypeList {}; +\end{cppcode} +\mincpp{11} diff --git a/src/lst/mp/mpt/usages/helpertype.tex b/src/lst/mp/mpt/usages/helpertype.tex new file mode 100644 index 0000000..c51de49 --- /dev/null +++ b/src/lst/mp/mpt/usages/helpertype.tex @@ -0,0 +1,5 @@ +\begin{cppcode} + template + using TypeListUniq = typename TypeListUniqImpl::type; +\end{cppcode} +\mincpp{11} diff --git a/src/lst/mp/mpt/usages/helpervar.tex b/src/lst/mp/mpt/usages/helpervar.tex new file mode 100644 index 0000000..3712039 --- /dev/null +++ b/src/lst/mp/mpt/usages/helpervar.tex @@ -0,0 +1,5 @@ +\begin{cppcode} + template + constexpr bool typeListContains = TypeListContains::value; +\end{cppcode} +\mincpp{14} diff --git a/src/lst/mp/mpt/usages/typelistuniq_sep.tex b/src/lst/mp/mpt/usages/typelistuniq_sep.tex new file mode 100644 index 0000000..24f0307 --- /dev/null +++ b/src/lst/mp/mpt/usages/typelistuniq_sep.tex @@ -0,0 +1,22 @@ +\begin{cppcode} + template struct TypeListUniqImpl {}; + + template + struct TypeListUniqImpl, TypeList> { + using Result = typename If< + TypeListContains, H>::value, + TypeList, + TypeList + >::type; + using type = typename TypeListUniqImpl, Result>::type; + }; + + template + struct TypeListUniqImpl, Result> { + using type = Result; + }; + + template + using TypeListUniq = typename TypeListUniqImpl>::type; +\end{cppcode} +\mincpp{11} diff --git a/src/lst/mp/types/cmacro.tex b/src/lst/mp/types/cmacro.tex new file mode 100644 index 0000000..b0a4528 --- /dev/null +++ b/src/lst/mp/types/cmacro.tex @@ -0,0 +1,17 @@ +\begin{ccode} + #include + + void displayi(int i) { printf("%d\n", i); } + void displayf(float f) { printf("%f\n", f); } + void displays(char *s) { puts(s); } + + #define display(X) \ + _Generic((X), int: displayi, float: displayf, char*: displays) \ + (X) + + int main() { + display("Hello World!"); + return 0; + } +\end{ccode} +\minc{11} diff --git a/src/lst/mp/types/metaprogram.tex b/src/lst/mp/types/metaprogram.tex new file mode 100644 index 0000000..452af57 --- /dev/null +++ b/src/lst/mp/types/metaprogram.tex @@ -0,0 +1,19 @@ +\begin{ccode} + #include + + int main(int argc, char **argv) { + FILE *output = fopen(argv[1], "w"); + if(!output) return 1; + (void)argc; + + fputs("#include \n\n", output); + fputs("int main() {\n", output); + fprintf(output, "\tputs(\"%s\");\n", argv[2]); + fputs("}", output); + + fclose(output); + + return 0; + } +\end{ccode} +\minc{89} diff --git a/src/lst/par/assist/barrier.tex b/src/lst/par/assist/barrier.tex new file mode 100644 index 0000000..6811224 --- /dev/null +++ b/src/lst/par/assist/barrier.tex @@ -0,0 +1,18 @@ +\begin{cppcode} + void task(std::promise promise) { + // travail à effectuer avant la barrière de synchronisation + promise.set_value(); + // travail à effectuer après la barrière de synchronisation + } + + int main() { + std::promise promise; + std::future future = promise.get_future(); + std::|thread| threadTask{task, std::move(promise)}; + + future.get(); // synchronisation avec promise.set_value() + + threadTask.join(); + } +\end{cppcode} +\mincpp{11} diff --git a/src/lst/par/assist/futurepromise.tex b/src/lst/par/assist/futurepromise.tex new file mode 100644 index 0000000..8c96938 --- /dev/null +++ b/src/lst/par/assist/futurepromise.tex @@ -0,0 +1,17 @@ +\begin{cppcode} + void task(std::promise promise) { + double result = 1.61803; // production d'un résultat + promise.set_value(result); + } + + int main() { + std::promise promise; + std::future future = promise.get_future(); + std::|thread| threadTask{task, std::move(promise)}; + + double phi = future.get(); + + threadTask.join(); + } +\end{cppcode} +\mincpp{11} diff --git a/src/lst/par/assist/llinsertion_cpp.tex b/src/lst/par/assist/llinsertion_cpp.tex new file mode 100644 index 0000000..da89c26 --- /dev/null +++ b/src/lst/par/assist/llinsertion_cpp.tex @@ -0,0 +1,23 @@ +\begin{cppcode} + #include + #include + + std::mutex mutex; + + void task() { + std::lock_guard lock{mutex}; + + // insertion non atomique dans une liste chaînée + } + + int main() { + std::array threads; + + for(auto&& |thread|: threads) + |thread| = std::|thread|{task}; + + for(auto&& |thread|: threads) + |thread|.join(); + } +\end{cppcode} +\mincpp{11} diff --git a/src/lst/par/assist/pthread.tex b/src/lst/par/assist/pthread.tex new file mode 100644 index 0000000..08af46d --- /dev/null +++ b/src/lst/par/assist/pthread.tex @@ -0,0 +1,27 @@ +\begin{ccode} + #include + + pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + + void *task(void *arg) { + (void)arg; /* argument inutilisé */ + + pthread_mutex_lock(&mutex); + /* accès en écriture à une variable partagée */ + pthread_mutex_unlock(&mutex); + + return NULL; + } + + int main() { + pthread_t |thread|; + + pthread_create(&|thread|, NULL, task, NULL); + task(NULL); /* appel exécuté dans le thread principal */ + + pthread_join(|thread|, NULL); + + return 0; + } +\end{ccode} +\minc{89} diff --git a/src/lst/par/assist/stdthread.tex b/src/lst/par/assist/stdthread.tex new file mode 100644 index 0000000..ca5c956 --- /dev/null +++ b/src/lst/par/assist/stdthread.tex @@ -0,0 +1,20 @@ +\begin{cppcode} + #include + #include + + std::mutex m; // initialisation faite par le constructeur + + void task() { + // Le constructeur prend le |\en{mutex}| m + std::lock_guard lock{m}; + // accès en écriture à une variable partagée + } // le destructeur de lock libère le |\en{mutex}| m + + int main() { + std::|thread| threadTask{task}; // création du thread faite par le constructeur + task(); // appel exécuté dans le thread principal + + threadTask.join(); + } +\end{cppcode} +\mincpp{11} diff --git a/src/lst/par/comp/llinsertion.tex b/src/lst/par/comp/llinsertion.tex new file mode 100644 index 0000000..8ee9333 --- /dev/null +++ b/src/lst/par/comp/llinsertion.tex @@ -0,0 +1,37 @@ +\begin{ccode} + #include + #include + + pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + + void *task(void *arg) { + int data = *(int*)arg; + _Bool *success = malloc(sizeof *success); + + pthread_mutex_lock(&mutex); + /* insertion non atomique dans une liste chaînée de la donnée data */ + /* la réussite de l'instruction est indiquée dans le booléen pointé par success */ + pthread_mutex_unlock(&mutex); + + return success; + } + + int main() { + int i; + int values[] = {5, 7}; + pthread_t threads[2]; + + for(i = 0; i < 2; ++i) + pthread_create(threads+i, NULL, task, values+i); + + for(i = 0; i < 2; ++i) { + _Bool *success; + pthread_join(threads[i], (void**)&success); + /* vérification de la réussite avec *success */ + free(success); + } + + return 0; + } +\end{ccode} +\minc{99} diff --git a/src/lst/pfor/conditions/loop.tex b/src/lst/pfor/conditions/loop.tex new file mode 100644 index 0000000..6a74f4c --- /dev/null +++ b/src/lst/pfor/conditions/loop.tex @@ -0,0 +1,11 @@ +\begin{cppcode*}{linenos} + // a, b, c, d, e and f are arrays + for(int i = 0; i < n; ++i) { + a[i] = a[i] * b[i];|\label{line:pfor/conditions/loop:i1}| + c[i] = c[i+1] - d[i];|\label{line:pfor/conditions/loop:i2}| + b[i] = b[i] + i;|\label{line:pfor/conditions/loop:i3}| + d[i] = std::pow(c[i], e[i]);|\label{line:pfor/conditions/loop:i4}| + f[i*i] = 2 * f[i*i];|\label{line:pfor/conditions/loop:i5}| + } +\end{cppcode*} +\mincpp{98} diff --git a/src/lst/pfor/conditions/loop_par.tex b/src/lst/pfor/conditions/loop_par.tex new file mode 100644 index 0000000..cd7717f --- /dev/null +++ b/src/lst/pfor/conditions/loop_par.tex @@ -0,0 +1,15 @@ +\begin{cppcode} + // a, b, c, d, e and f are arrays + // possibly parallel loop + for(int i = 0; i < n; ++i) { + a[i] = a[i] * b[i];|\label{line:pfor/conditions/loop_par:i1}| + b[i] = b[i] + i;|\label{line:pfor/conditions/loop_par:i3}| + f[i*i] = 2 * f[i*i];|\label{line:pfor/conditions/loop_par:i5}| + } + // sequential loop + for(int i = 0; i < n; ++i) { + c[i] = c[i+1] - d[i];|\label{line:pfor/conditions/loop_par:i2}| + d[i] = std::pow(c[i], e[i]);|\label{line:pfor/conditions/loop_par:i4}| + } +\end{cppcode} +\mincpp{98} diff --git a/src/lst/pfor/conditions/loop_split.tex b/src/lst/pfor/conditions/loop_split.tex new file mode 100644 index 0000000..0e71c60 --- /dev/null +++ b/src/lst/pfor/conditions/loop_split.tex @@ -0,0 +1,14 @@ +\begin{cppcode*}{linenos} + // a, b, c, d, e and f are arrays + for(int i = 0; i < n; ++i) { + a[i] = a[i] * b[i];|\label{line:pfor/conditions/loop_split:i1}| + b[i] = b[i] + i;|\label{line:pfor/conditions/loop_split:i3}| + } + for(int i = 0; i < n; ++i) { + c[i] = c[i+1] - d[i];|\label{line:pfor/conditions/loop_split:i2}| + d[i] = std::pow(c[i], e[i]);|\label{line:pfor/conditions/loop_split:i4}| + } + for(int i = 0; i < n; ++i) + f[i*i] = 2 * f[i*i];|\label{line:pfor/conditions/loop_split:i5}| +\end{cppcode*} +\mincpp{98} diff --git a/src/lst/pfor/detection/aliasing.tex b/src/lst/pfor/detection/aliasing.tex new file mode 100644 index 0000000..1ff6c62 --- /dev/null +++ b/src/lst/pfor/detection/aliasing.tex @@ -0,0 +1,10 @@ +\begin{cppcode} + int aData[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + int cData[] = {0, 1, 2, 3, 4}; + + Operand a(aData); + Operand b(aData+5); // {5, 6, 7, 8, 9} + Operand c(cData); + Operand d(cData); +\end{cppcode} +\mincpp{14} diff --git a/src/lst/pfor/detection/calcstrictinc.tex b/src/lst/pfor/detection/calcstrictinc.tex new file mode 100644 index 0000000..006aab1 --- /dev/null +++ b/src/lst/pfor/detection/calcstrictinc.tex @@ -0,0 +1,5 @@ +\begin{cppcode} + Index i; + auto e = strictinc(i) - strictdec(-i); +\end{cppcode} +\mincpp{14} diff --git a/src/lst/pfor/detection/expression.tex b/src/lst/pfor/detection/expression.tex new file mode 100644 index 0000000..024fa75 --- /dev/null +++ b/src/lst/pfor/detection/expression.tex @@ -0,0 +1,9 @@ +\begin{cppcode} + int aData[] = {0, 1, 2, 3, 4}, bData[] = {5, 6, 7, 8, 9}; + Operand a(aData); + Operand b(bData); + + auto e = a + b; + // ^ Expression, Operand> +\end{cppcode} +\mincpp{14} diff --git a/src/lst/pfor/detection/indices.tex b/src/lst/pfor/detection/indices.tex new file mode 100644 index 0000000..9e4eda0 --- /dev/null +++ b/src/lst/pfor/detection/indices.tex @@ -0,0 +1,9 @@ +\begin{cppcode} + // a, b, c are operands + Index i; + auto e = ( + a[i] = a[i] * b[i], + c[i] = c[i+ctv<1>] + ); +\end{cppcode} +\mincpp{14} diff --git a/src/lst/pfor/detection/loopstmtexpr.tex b/src/lst/pfor/detection/loopstmtexpr.tex new file mode 100644 index 0000000..f4b13a1 --- /dev/null +++ b/src/lst/pfor/detection/loopstmtexpr.tex @@ -0,0 +1,12 @@ +\begin{cppcode} + // a, b, c, d, e and f are expression template operands + Index i; + auto expr = ( + a[i] = a[i] * b[i],|\label{line:pfor/detection/loopstmtexpr:i1}| + c[i] = c[i+ctv<1>] + d[i],|\label{line:pfor/detection/loopstmtexpr:i2}| + b[i] = b[i] + i,|\label{line:pfor/detection/loopstmtexpr:i3}| + d[i] = pow(c[i], e[i]),|\label{line:pfor/detection/loopstmtexpr:i4}| + f[i*i] = 2 * f[i*i]|\label{line:pfor/detection/loopstmtexpr:i5}| + ); +\end{cppcode} +\mincpp{} diff --git a/src/lst/pfor/detection/makeoperator.tex b/src/lst/pfor/detection/makeoperator.tex new file mode 100644 index 0000000..b026e91 --- /dev/null +++ b/src/lst/pfor/detection/makeoperator.tex @@ -0,0 +1,13 @@ +\begin{cppcode} + template + auto makeOperator(F&& f) { + return [f = std::forward(f)](auto&&... args) { + using Expr = Expression< + UserOperator, + AsExpression>... + >; + return Expr{{f}, decltype(args)(args)...}; + }; + } +\end{cppcode} +\mincpp{14} diff --git a/src/lst/pfor/detection/multiinstr.tex b/src/lst/pfor/detection/multiinstr.tex new file mode 100644 index 0000000..13891d0 --- /dev/null +++ b/src/lst/pfor/detection/multiinstr.tex @@ -0,0 +1,9 @@ +\begin{cppcode} + // a, b, c, d are operands + auto e = ( + a = b + c, + d = d + 1, + b = 2 * b + ); +\end{cppcode} +\mincpp{14} diff --git a/src/lst/pfor/detection/operand.tex b/src/lst/pfor/detection/operand.tex new file mode 100644 index 0000000..ea75699 --- /dev/null +++ b/src/lst/pfor/detection/operand.tex @@ -0,0 +1,5 @@ +\begin{cppcode} + int aData[] = {1, 2, 3, 4, 5}; + Operand a(aData); +\end{cppcode} +\mincpp{14} diff --git a/src/lst/pfor/detection/propinjective.tex b/src/lst/pfor/detection/propinjective.tex new file mode 100644 index 0000000..8723c49 --- /dev/null +++ b/src/lst/pfor/detection/propinjective.tex @@ -0,0 +1,4 @@ +\begin{cppcode} + injective(i*i) +\end{cppcode} +\mincpp{14} diff --git a/src/lst/pfor/detection/useroperator.tex b/src/lst/pfor/detection/useroperator.tex new file mode 100644 index 0000000..10700b0 --- /dev/null +++ b/src/lst/pfor/detection/useroperator.tex @@ -0,0 +1,17 @@ +\begin{cppcode} + template + struct UserOperator { + F f; + + template>* = nullptr> + inline decltype(auto) eval(Args&&... args) { + return f(std::forward(args).eval()...); + } + + template + inline decltype(auto) eval(std::size_t i, Args&&... args) { + return f(std::forward(args)[i]...); + } + }; +\end{cppcode} +\mincpp{14} diff --git a/src/lst/pfor/detection/userops.tex b/src/lst/pfor/detection/userops.tex new file mode 100644 index 0000000..b2844c4 --- /dev/null +++ b/src/lst/pfor/detection/userops.tex @@ -0,0 +1,6 @@ +\begin{cppcode} + auto factorial = makeOperator(::factorial); + auto add3 = makeOperator([](auto a, auto b, auto c) { return a+b+c; }); + auto pow = makeOperator(std::pow); +\end{cppcode} +\mincpp{14} diff --git a/src/lst/pfor/generation/clustergen.tex b/src/lst/pfor/generation/clustergen.tex new file mode 100644 index 0000000..fe207a7 --- /dev/null +++ b/src/lst/pfor/generation/clustergen.tex @@ -0,0 +1,31 @@ +\begin{cppcode} + template struct ClusterDepends; + + template + struct ClusterDepends, Cs...>, Pack> { + using next = ClusterDepends, Pack>; + static constexpr bool value = PackIntersects::value || PackIntersects::value || PackIntersects::value || next::value; + }; + + template + struct ClusterDepends, T> { + static constexpr bool value = false; + }; + + template struct ClustersInsert; + + template + struct ClustersInsert, T> { + static constexpr bool depends = ClusterDepends::value; + using tmp_cluster = typename PackSortInsert::type; + using new_cluster = typename TIf::type; + using next = typename ClustersInsert, T>::type; + using type = typename PackPrepend, next>::type, new_cluster>::type; + }; + + template + struct ClustersInsert, Pack> { + using type = Pack>>; + }; +\end{cppcode} +\mincpp{14} diff --git a/src/lst/pfor/generation/loop_hard.tex b/src/lst/pfor/generation/loop_hard.tex new file mode 100644 index 0000000..249ce32 --- /dev/null +++ b/src/lst/pfor/generation/loop_hard.tex @@ -0,0 +1,5 @@ +\begin{cppcode} + for(int i = 0; i < 10; i += 2) + a[i] = a[i+1]; +\end{cppcode} +\mincpp{98} diff --git a/src/lst/pfor/generation/pfor.tex b/src/lst/pfor/generation/pfor.tex new file mode 100644 index 0000000..2003a29 --- /dev/null +++ b/src/lst/pfor/generation/pfor.tex @@ -0,0 +1,12 @@ +\begin{cppcode} + // a, b, c, d, e and f are expression template operands + Index i; + parallelFor(Range{0, n}, + a[i] = a[i] * b[i], + c[i] = c[i+ctv<1>] + d[i], + b[i] = b[i] + i, + d[i] = pow(c[i], e[i]), + f[injective(i*i)] = 2 * f[injective(i*i)] + ); +\end{cppcode} +\mincpp{14} diff --git a/src/lst/pfor/generation/pforopenmp.tex b/src/lst/pfor/generation/pforopenmp.tex new file mode 100644 index 0000000..7352496 --- /dev/null +++ b/src/lst/pfor/generation/pforopenmp.tex @@ -0,0 +1,9 @@ +\begin{cppcode} + #pragma omp parallel for + for(int i = 0; i < n; ++i) { + a[i] = a[i] * b[i]; + b[i] = b[i] + i; + f[i*i] = 2 * f[i*i]; + } +\end{cppcode} +\mincpp{98} diff --git a/src/lst/pfor/generation/pforseq.tex b/src/lst/pfor/generation/pforseq.tex new file mode 100644 index 0000000..21e4736 --- /dev/null +++ b/src/lst/pfor/generation/pforseq.tex @@ -0,0 +1,7 @@ +\begin{cppcode} + for(int i = 0; i < n; ++i) { + c[i] = c[i+1] + d[i]; + d[i] = std::pow(c[i], e[i]); + } +\end{cppcode} +\mincpp{98} diff --git a/src/lst/pfor/generation/pforthread.tex b/src/lst/pfor/generation/pforthread.tex new file mode 100644 index 0000000..e23be76 --- /dev/null +++ b/src/lst/pfor/generation/pforthread.tex @@ -0,0 +1,12 @@ +\begin{cppcode} + // a, b, c, d, e and f are expression template operands + Index i; + parallelFor(Range{0, n}, + a[i] = a[i] * b[i], + c[i] = c[i+ctv<1>] + d[i], + b[i] = b[i] + i, + d[i] = pow(c[i], e[i]), + f[injective(i*i)] = 2 * f[injective(i*i)] + ); +\end{cppcode} +\mincpp{14} diff --git a/src/lst/pfor/generation/stratthread.tex b/src/lst/pfor/generation/stratthread.tex new file mode 100644 index 0000000..e30a6c4 --- /dev/null +++ b/src/lst/pfor/generation/stratthread.tex @@ -0,0 +1,50 @@ +\begin{cppcode} + template struct ForLoopThread; + + template + struct ForLoopThread { + static void eval(Range const& range, E e) { + if(range.step() > 0) + for(auto it = +range.begin(); it < range.end(); it += range.step()) e[it]; + else + for(auto it = +range.begin(); it > range.end(); it += range.step()) e[it]; + } + }; + + template + struct ForLoopThread { + using Index = typename Range::ValueType; + + static void eval(Range const& range, E e) { + using SeqRange = decltype(makeRange(+range.begin(), +range.end(), +range.step())); + auto const& sequence = &ForLoopThread::eval; + + Index const count = (range.end() - range.begin() + + (range.step()-(range.step() > 0? +1 : -1)))/range.step(); + std::size_t const nThreads = + std::min(ParallelForParameters::nThreads, count); + + std::vector threads(nThreads-1); + for(std::size_t k = 0; k < nThreads-1; ++k) { + auto lRange = makeRange( + range.begin() + static_cast(k*range.step()*count/nThreads), + range.begin() + static_cast((k+1)*range.step()*count/nThreads), + +range.step() + ); + threads[k] = std::|thread|{sequence, lRange, e}; + } + + { + auto lRange = makeRange( + range.begin() + static_cast((nThreads-1)*range.step()*count/nThreads), + range.end(), + range.step() + ); + sequence(lRange, e); + } + + for(auto&& |thread|: threads) thread.join(); + } + }; +\end{cppcode} +\mincpp{14} diff --git a/src/lst/pfor/generation/stratunrolling.tex b/src/lst/pfor/generation/stratunrolling.tex new file mode 100644 index 0000000..8fe909f --- /dev/null +++ b/src/lst/pfor/generation/stratunrolling.tex @@ -0,0 +1,43 @@ +\begin{cppcode} + template + struct ForLoopUnrolling { + template + struct Template { + using Index = typename Range::ValueType; + using Indices = std::array; + + static void eval(Range const& range, E e) { + constexpr auto indexSeq = std::make_index_sequence(); + Index const count = (range.end() - range.begin() + (range.step()-(range.step() > 0? +1 : -1)))/range.step(); + + Index const lEnd = count/n; + for(Index it{}; it != lEnd; ++it) + evalUnroll(e, indexSeq, makeIndices(indexSeq, it)); + + evalUnrollRemainder(e, indexSeq, makeIndices(indexSeq, lEnd), range.end()); + } + + private: + template + static void evalUnroll(E& e, std::index_sequence, Indices const& i) { + using Expander = int[]; + static_cast(Expander{(e[i[indices]], 0)...}); + } + + template + static void evalUnrollRemainder(E& e, std::index_sequence, Indices const& i, Index const& end) { + if(i[index] == end) return; + e[i[index]]; + evalUnrollRemainder(e, std::index_sequence{}, i, end); + } + + static void evalUnrollRemainder(E&, std::index_sequence<>, Indices const&, Index const&) {} + + template + static Indices makeIndices(std::index_sequence, Index i) { + return {(Index{n}*i+Index{indices})...}; + } + }; + }; +\end{cppcode} +\mincpp{14} diff --git a/src/lst/pfor/results/imgpro.tex b/src/lst/pfor/results/imgpro.tex new file mode 100644 index 0000000..5a92d03 --- /dev/null +++ b/src/lst/pfor/results/imgpro.tex @@ -0,0 +1,12 @@ +\begin{cppcode} + pfor::Index i; + pfor::parallelFor(pfor::RangeCT{(H-1)*W}, + img[i] = calc( + img[i-pfor::ctv], // north cell + img[i-pfor::ctv<1>], // west cell + img[i+pfor::ctv], // south cell + img[i+pfor::ctv<1>] // east cell + ) + ); +\end{cppcode} +\mincpp{14} diff --git a/src/main_genfigures.tex b/src/main_genfigures.tex new file mode 100644 index 0000000..d949045 --- /dev/null +++ b/src/main_genfigures.tex @@ -0,0 +1,19 @@ +\input{src/usr/silence} + +\documentclass[11pt,a4paper,french]{report} + +\input{src/preamble} +\input{src/usr/header_oneside} + +\tikzexternalize[prefix=buildfig/thesis_] +\tikzset{every picture/.style={ + execute at end picture={\node[fit=(current bounding box),inner sep=2mm]{};} +}} +% \tikzset{external/force remake} +\tikzsetfigurename{} +\tikzset{external/only named=true} + +\renewcommand{\ocgfigRPbuttons}[4]{} +\tikzset{common/overlay/.style={}} + +\input{src/document} diff --git a/src/main_oneside.tex b/src/main_oneside.tex new file mode 100644 index 0000000..75bfaac --- /dev/null +++ b/src/main_oneside.tex @@ -0,0 +1,8 @@ +\input{src/usr/silence} + +\documentclass[11pt,a4paper,french]{report} + +\input{src/preamble} +\input{src/usr/header_oneside} + +\input{src/document} diff --git a/src/main_print.tex b/src/main_print.tex new file mode 100644 index 0000000..4c1cf5a --- /dev/null +++ b/src/main_print.tex @@ -0,0 +1,12 @@ +\input{src/usr/silence} + +\documentclass[11pt,a4paper,twoside,openright,french]{report} + +\input{src/preamble} +\input{src/usr/ocg_print}% disable OCGs + +\AtBeginEnvironment{abstract}{\pagestyle{empty}\cleardoublepage}% + +\def\compilationversion{print} + +\input{src/document} diff --git a/src/main_twoside.tex b/src/main_twoside.tex new file mode 100644 index 0000000..69a9a37 --- /dev/null +++ b/src/main_twoside.tex @@ -0,0 +1,9 @@ +\input{src/usr/silence} + +\documentclass[11pt,a4paper,twoside,openright,french]{report} + +\input{src/preamble} + +\AtBeginEnvironment{abstract}{\cleardoublepage}% + +\input{src/document} diff --git a/src/mp/0_intro.tex b/src/mp/0_intro.tex new file mode 100644 index 0000000..d175082 --- /dev/null +++ b/src/mp/0_intro.tex @@ -0,0 +1,35 @@ +\section{Introduction} +\label{sec:mp/intro} + +%{{{ +Dans le cadre de cette thèse, la métaprogrammation est utilisée pour répondre au besoin de traiter +de manière la plus générique possible un ensemble défini de problèmes sans que les conséquences sur +les performances obtenues ne soient trop pénalisantes. +Afin de faciliter la compréhension par le lecteur des travaux présentés, ce chapitre présente la +métaprogrammation et en particulier celle utilisée dans ce cadre. + +On parle de métaprogramme lorsqu'un programme traite ou produit des données représentant un +programme. +Le premier langage à proposer un mécanisme permettant la métaprogrammation était un langage de la +famille Lisp. +\autocite{ref:sheard2001}~distingue deux catégories de métaprogrammes : d'une part les générateurs +de programmes, et d'autre part les analyseurs de programmes qui traitent de données représentant un +programme et fournissent une sortie qui n'en est pas un. +Un exemple typique d'analyseur est un programme de preuve~\autocite{ref:hoare1971}, tandis que le +compilateur correspond à un générateur de programmes~\autocite{ref:aho1986}, que ce soit en +transformant un code source d'un langage en code source d'un autre langage (voire du même langage +après transformation) ou encore en fichier de pseudo-code ou binaire, ce qui est techniquement juste +un autre langage. +C'est cette dernière catégorie, la génération de programme, qui nous intéressera davantage. + +Le langage dans lequel le métaprogramme est écrit se nomme un métalangage, et celui qui est traité +comme une donnée est appelé le langage \og objet \fg{} (en anglais, \en{object language}). +Si les deux langages sont identiques, il s'agit alors de métaprogrammation homogène, sinon de +métaprogrammation hétérogène. + +Dans un premier temps, les différents types de métaprogrammation sont présentés. +Ensuite, le concept de généricité est détaillé afin de préparer le lecteur au prochain chapitre +qui en fait un usage extensif. +Enfin, la métaprogrammation template, qui est un cas spécifique de métaprogrammation, est présentée +et expliquée en étudiant différents cas. +%}}} diff --git a/src/mp/1_types.tex b/src/mp/1_types.tex new file mode 100644 index 0000000..61b3d16 --- /dev/null +++ b/src/mp/1_types.tex @@ -0,0 +1,231 @@ +\section{Types de métaprogrammation} +\label{sec:mp/types} + +%{{{ +Il est possible de classer les différents types de métaprogrammation selon de nombreux axes +distincts. +Selon~\autocite{ref:sheard2001}, il est possible de distinguer principalement les analyseurs et les +générateurs. + +Les analyseurs traitent des données représentant d'autres programmes sans nécessairement produire de +telles données. +Dans cette catégorie se trouvent de nombreux outils, du debugger aux programmes de +preuve~\autocite{ref:binkley2007} qui produisent souvent des graphes très utiles aux vérifications +et aux optimisations. +Si certaines techniques qui sont employées par les analyseurs sont intéressantes, elles sont +également utilisées avec les générateurs, et c'est plutôt cette dernière catégorie qui nous +concerne. + +Les générateurs produisent des données représentant un programme. +Ainsi, un métaprogramme qui écrit dans un fichier le code source d'un programme affichant une chaîne +de caractères spécifique qui aura été donnée en paramètre du métaprogramme est un exemple très +simple de générateur. +\Acref{lst:mp/types/metaprogram} est une implémentation possible en C d'un tel métaprogramme. + +\begin{listing}[t] + \inputlst{types/metaprogram} + {Métaprogramme en C générant un code C} +\end{listing} + +Il s'agit ici de génération de texte : le métalangage utilisé (le langage C) ne fournit pas d'outil +permettant de générer du langage \og objet \fg{} (ici, également du C) de manière plus élaborée. +Cette manière d'opérer possède des inconvénients : il n'est pas possible de vérifier la validité (ne +serait-ce que syntaxique) du programme généré au niveau du métaprogramme. +Cela signifie qu'il est nécessaire de compiler le fichier source généré pour le vérifier. + +Cette section est inspirée des travaux de~\autocite{ref:lilis2019} et présente différents types de +métaprogrammation. + +%}}} + +%{{{ +\subsection{Macros} +\label{subsec:mp/types/macros} + +Le langage C permet la métaprogrammation au moyen de macros~\autocite{ref:kernighan1988}. +Celles-ci sont en réalité traitées par le préprocesseur du langage C, \gls{CPP}. +L'exécution de ces macros est faite avant la compilation du code C. +Il s'agit principalement de remplacement simple de texte, où \cppinline{#define A B} instruit le +\gls{CPP} qu'il faut ensuite remplacer toute occurrence de \cppinline{A} par \cppinline{B}, +\cppinline{B} pouvant être lui-même une autre définition, aussi longtemps qu'aucun cycle n'est +induit par la séquence de remplacements. +Ces remplacements de texte peuvent prendre la forme de \og fonctions \fg en acceptant des paramètres +(traités comme du texte eux aussi) qui peuvent être utilisés, par exemple \cppinline{#define +PRINT_TWICE(T) puts(T); puts(T)} qui affiche deux fois son argument. +Il existe, depuis 2011, un moyen de sélectionner le texte à produire en fonction du type d'un +argument d'une macro, permettant un certain niveau de généricité qui étend la capacité du langage à +l'écriture de code générique grâce au mot-clé \cppinline{_Generic}. +\Acref{lst:mp/types/cmacro} montre une utilisation de \cppinline{_Generic}. +Dans cet exemple, l'utilisation de la macro \cppinline{display} va causer un appel à différentes +fonctions selon le type de l'argument donné pour le paramètre \cppinline{X}. +Cette fonctionnalité ne permet, durant la compilation, qu'une simple association entre le type d'une +expression (ici \cppinline{X}) et une expression à utiliser : il n'est donc pas possible d'utiliser +à la place une valeur par exemple. +Par ailleurs, étant implémentée par le \gls{CPP}, la substitution est faite en amont de la +compilation et ne permet pas l'utilisation de la macro \cppinline{display} au même titre qu'une +fonction qui peut être utilisée comme argument d'une autre. + +\begin{listing}[t] + \inputlst{types/cmacro} + {Utilisation de \cppinline{_Generic} en C} +\end{listing} + +Dans les langages Lisp il existe également un système de macros, mais celui-ci est différent de ce +que \gls{CPP} propose puisque celles-ci permettent de traiter les données non comme du texte mais +comme du code. +À l'instar des macros \gls{CPP}, ces macros sont exécutées avant l'évaluation de l'\gls{AST}. + +Le Lisp est homoiconique, les listes de données permettant de représenter une expression +fonctionnelle si le premier élément est une fonction (les éléments suivants devenant les opérandes +de celle-ci). +Cette particularité facilite la manipulation des fonctions en Lisp au travers des macros, et +permet de faire de la métaprogrammation homogène, puisque les macros Lisp peuvent résulter en un +\gls{AST} comprenant des macros à évaluer. + +% (schéma d'évaluation d'un \gls{AST} Lisp) % TODO + +Un dialecte de Lisp, le Scheme, complète les fonctionnalités des macros Lisp en les rendant \og +hygiéniques \fg : c'est-à-dire que leur résolution/expansion ne peut pas causer de confusion entre +un identifiant interne à la macro et un identifiant externe à celle-ci. +%}}} + +%{{{ +\subsection{Réflexion} +\label{subsec:mp/types/reflexion} + +Dans le contexte de la programmation informatique, la réflexion représente la possibilité d'un +programme d'agir sur lui-même. +Il s'agit d'introspection lorsque le programme est capable de s'examiner, en lecture seule. +À l'inverse, lorsqu'il est capable de se modifier, il s'agit d'intercession. +Deux types de réflexion sont également distingués, indépendamment de l'introspection et +l'intercession. +La réflexion structurelle permet l'accès à la structure du programme : les types, les fonctions, ... +La réflexion comportementale permet l'accès aux éléments dynamiques du programme : l'instanciation +ou l'accès aux variables, les appels de fonctions... + +Le Java est un exemple de langage capable d'introspection structurelle ainsi que d'introspection et +intercession comportementale, tous deux durant l'exécution du programme. +Le support de la réflexion repose sur les métaclasses. +Couplé à l'utilisation de la compilation à la volée qui permet une métaprogrammation par génération +de texte, il est possible de faire de la métaprogrammation dynamique. + +Le support complet de la réflexion n'est généralement pas sans coût observable sur le temps +d'exécution du programme~\autocite{ref:asai2014}. +La réflexion statique étant appliquée au moment de la compilation du programme, le surcoût qu'elle +peut engendrer y est restreint, et possède d'autres avantages tels que la sûreté du typage et +l'assurance de produire un code bien formé. + +Le C++ permet partiellement la réflexion statique au moyen des \en{type traits}, une technique qui +permet d'associer différentes caractéristiques à des types (et de manière plus étendue à tout +élément pouvant être utilisé en argument d'un patron). +De plus, une proposition d'évolution du langage~\autocite{ref:sutter2019} permettrait la définition +de métaclasses\footnote{Le terme \og métaclasse \fg est ici à différencier de l'usage qui en est +fait par exemple en Java.}. +Une métaclasse telle que présentée par la proposition est un nouveau genre de fonction évaluée par +le compilateur et qui pourra, entre autres, itérer sur les différents membres d'une classe +(introspection structurelle) et produire une nouvelle classe (intercession structurelle). +Il s'agit ainsi d'une extension au fonctionnement actuel des classes qui font déjà ce travail au +niveau du compilateur (par exemple, une classe définie avec le mot-clé \cppinline{class} aura des +membres par défaut privés, avec le mot-clé \cppinline{struct} par défaut publics ; par héritage, des +fonctions membres peuvent se voir ajouter la propriété \cppinline{virtual} automatiquement). +Le programme décidant de ce qui est produit à partir de la définition faite est alors écrit en C++. + +Cet outil rend possible l'implémentation de concepts dont les contraintes seront garanties par le +compilateur, par exemple celui d'interface (qui serait alors implémentée par une classe ne contenant +ni variable membre ni fonction membre définie) ou celui de valeur (une classe fournissant +automatiquement, entre autres, les opérateurs de comparaison). % TODO intérêt, perspectives ? +%}}} + +%{{{ +\subsection{Patrons} +\label{subsec:mp/types/template} + +Cette méthode de métaprogrammation fait partie de la programmation générative, qui consiste à +développer un programme qui produit d'autres programmes en fonction de données d'entrée spécifiques. +Les langages C++ et D sont connus pour leur mécanisme de patrons. +Nous utiliserons le terme anglais \en{template} à partir de maintenant dans ce document. +Le terme patron pourra être retenu pour les patrons de conception~\autocite{ref:gamma1995}. +Ces templates, à l'origine conçus pour permettre la généricité au sein du C++, peuvent être utilisés +pour de la métaprogrammation supposée\footnote{Les parties du langage C++ sur lesquelles + repose la preuve n'étant pas définies formellement, la preuve ne peut pas être formelle + elle-même.} + complète au sens de Turing\footnote{La Turing-complétude est limitée par la profondeur + d'instanciation autorisée par le compilateur, réglable + arbitrairement.}~\autocite{ref:unruh1994, ref:veldhuizen2003}. +Cela est possible grâce aux mécanismes d'instanciation et de spécialisation des templates +implémentés par ces langages. + +L'évaluation des templates est effectuée par le compilateur, elle est donc purement statique. +Cela empêche une forme de métaprogrammation durant l'exécution du programme mais permet en +contrepartie la génération de programmes qui ne subissent pas de surcoût en temps d'exécution +comparés à une écriture manuelle équivalente. + +D'autres langages ont un support de la généricité que l'on distingue des templates du C++, parce +qu'ils n'offrent pas les mêmes avantages, en les nommant \og génériques \fg. +Les génériques du langage C\# ne permettant, entre autres, pas la spécialisation, ils ne permettent +donc pas l'implémentation d'algorithmes de métaprogrammation comme le font les templates. +D'autre part, leur résolution (la substitution des types) est effectuée durant l'exécution du +programme, ce qui induit un surcoût. +En Java, les génériques sont implémentés par effacement de type, limitant la sûreté du typage à une +vérification à la compilation. + +Un avantage des génériques par rapport aux templates est qu'ils ne causent la génération que d'une +seule instance de l'élément générique. +En revanche, dans le cadre de l'écriture d'un métaprogramme en C++, le nombre d'instances de +templates qui peuvent être générées par le compilateur peut être grand. +Une limite, configurable, de nombre d'instances générées en cascade existe pour empêcher une +génération infinie en cas d'erreur de programmation. + +Pour que la métaprogrammation template puisse être efficace (étant donné la création potentielle de +nombreuses instances d'un template, les appels à des fonctions intermédiaires, ...), certaines +optimisations fournies par les compilateurs sont indispensables (notamment l'\en{inlining} qui +permet d'annuler le coût d'un appel de fonction) ou au moins particulièrement bénéfiques (par +exemple l'\gls{EBO} qui permet de réduire la taille en mémoire d'une classe en cas d'héritage avec +une classe de taille nulle). +%}}} + +%{{{ +\subsection{Programmation multi-étapes} +\label{subsec:mp/types/multistep} + +La programmation multi-étapes permet de générer du code qui sera ensuite évalué dans une étape +ultérieure de l'exécution du programme. +Des annotations peuvent être utilisées pour distinguer le code censé être évalué durant une étape de +celui qui doit être transmis tel quel à l'étape suivante. +Un avantage de cette méthode réside dans la validité par rapport au langage, attestée par le +compilateur, à la fois du code évalué durant une étape ainsi que de celui transmis. + +Certains langages Lisp permettent cela. +Il existe la fonction \lispinline{quote} qui permet de ne pas évaluer une expression. +Le mécanisme de \en{backquote} (macro caractère \lispinline{`}) est similaire mais permet avec la +function \en{unquote} (macro caractère \lispinline{,}) que des portions de l'expression soient +évaluées au sein d'une expression non évaluée (donc transmise à l'étape suivante, mais avec une +partie évaluée). + +Les templates peuvent être assimilés à une forme de programmation multi-étapes dans la mesure où +l'évaluation d'un template peut causer l'évaluation d'au moins un autre template, de manière +similaire à ce qu'il est possible de faire en Lisp. +%}}} + +%{{{ +\subsection{Conclusion} +\label{subsec:mp/types/conclusion} + +La métaprogrammation est un moyen de générer un programme spécialisé pour résoudre efficacement un +problème donné en bénéficiant d'un niveau plus élevé d'abstraction. +Cela permet ainsi d'écrire un programme générique pour résoudre un ensemble de problèmes sans +sacrifier les performances. +Lorsque la métaprogrammation intervient durant l'exécution du programme, il est difficile, sinon +impossible, de ne pas causer de surcoût. +Pour cette raison, les méthodes s'appliquant avant ou pendant la compilation ont été préférées. +La métaprogrammation template, qui intervient durant la compilation, possède l'avantage d'être +exprimée dans le même langage que le langage \og objet \fg{} et ainsi d'assurer que le code généré +est valide. +Enfin, non seulement le C++ est plus couramment utilisé et \textit{a fortiori} plus éprouvé que le +D, mais il évolue beaucoup (depuis la norme validée en 2011) dans une direction favorisant la +métaprogrammation (\cppinline{constexpr} et \cppinline{consteval}, structures conditionnelles +statiques, métaclasses, ...). + +Les travaux présentés dans cette thèse utilisant tous la métaprogrammation template en C++, nous +donnons dans ce chapitre les éléments permettant d'expliquer son fonctionnement. +%}}} diff --git a/src/mp/2_mpt.tex b/src/mp/2_mpt.tex new file mode 100644 index 0000000..a323e4e --- /dev/null +++ b/src/mp/2_mpt.tex @@ -0,0 +1,23 @@ +\section{Métaprogrammation template en C++} +\label{sec:mp/mpt} + +%{{{ +Telle qu'est conçue la généricité en C++, celle-ci permet la métaprogrammation. +Cela a été montré la première fois par Erwin Unruh~\autocite{ref:unruh1994} au moyen d'un programme +affichant dans sa sortie d'erreur durant la compilation la suite des nombres premiers. +De par la nature à l'origine de la mise en œuvre de la métaprogrammation en C++, utilisant les +templates, cette manière de programmer a pris le nom de \gls{TMP}, en français métaprogrammation par +les patrons ou plus communément métaprogrammation template. +Suite à cela, de nombreux usages de la métaprogrammation template ont été faits, introduisant le +concept de \og bibliothèque active \fg~\autocite{ref:veldhuizen1998a}, c'est-à-dire une bibliothèque +dont une portion du travail est effectué durant la phrase de compilation, par le compilateur, et qui +peuvent donc agir de manière analogue à une extension de compilateur. +Cette fonctionnalité de métaprogrammation du langage est améliorée au fur et à mesure de ses +évolutions. +Cette section présente certaines techniques élémentaires de métaprogrammation et, grâce à +l'utilisation de ces dernières, les outils utilisés durant la thèse. +%}}} + +\inputsrc{mpt/0_metafunction} +\inputsrc{mpt/1_usages} +\inputsrc{mpt/2_et} diff --git a/src/mp/3_conclusion.tex b/src/mp/3_conclusion.tex new file mode 100644 index 0000000..3f33aea --- /dev/null +++ b/src/mp/3_conclusion.tex @@ -0,0 +1,36 @@ +\section{Conclusion} +\label{sec:mp/conclusion} + +%{{{ +Ce chapitre a présenté les outils de métaprogrammation qui sont nécessaires dans les propositions +qui sont faites dans \acref{ch:pfor,ch:alsk}. +La métaprogrammation en général et les différentes méthodes permettant son application ont été +détaillées avec les avantages de chacune, ainsi que les raisons nous ayant conduit à choisir la +métaprogrammation template. + +Dans la suite de ce chapitre, la métaprogrammation template a été présentée en introduisant les +concepts qui lui sont propres et les moyens techniques permettant leur implémentation. +Durant cette introduction, nous avons montré que la métaprogrammation template permet effectivement +d'exécuter une partie d'un programme durant sa compilation. +Cela nous permet de profiter de certaines optimisations auxquelles les compilateurs procèdent, voire +de les forcer en produisant un code que l'on sait propice à être optimisé. +Nous avons ensuite présenté la spécificité de la métaprogrammation template qui est capable de +traiter des types comme des variables. + +S'agissant de programmation, il existe plusieurs manières d'atteindre un même résultat. +Nous avons donc présenté certains de nos usages afin de faciliter la lecture des codes créés durant +cette thèse. + +Enfin, sous la forme d'un cas pratique, ce chapitre a présenté les patrons d'expression et une +implémentation simplifiée de ceux-ci. +Les patrons d'expression sont d'abord conçus pour répondre à une grammaire très simple, puis +complétés pour supporter une arité quelconque, permettre une écriture naturelle grâce à la surcharge +d'opérateurs, ... + +La métaprogrammation nous permet notamment, pour les applications présentées dans cette thèse, +d'acquérir des informations sur le code en utilisant la technique des patrons d'expression dont +l'interface donnée à l'utilisateur est alors un \gls{EDSL}. +Ces informations nous permettent ensuite, à nouveau par l'utilisation de la métaprogrammation +template, de procéder à des analyses puis à de la génération de code. +C'est ce que nous verrons dans le chapitre suivant. +%}}} diff --git a/src/mp/mpt/0_metafunction.tex b/src/mp/mpt/0_metafunction.tex new file mode 100644 index 0000000..af8d31b --- /dev/null +++ b/src/mp/mpt/0_metafunction.tex @@ -0,0 +1,592 @@ +\subsection{Métafonction} +\label{subsec:mp/mpt/metafunction} + +%{{{ +Une métafonction, au sens de la généricité, correspond au modèle générique capable de produire une +fonction. +Cependant, comme présenté dans la section précédente, le terme fonction template est préféré. +Au sens de la métaprogrammation, à l'inverse, le terme métafonction est utilisé, et c'est à celui-ci +que correspondront les utilisations futures de ce mot. + +Une métafonction est une fonction exécutée durant la compilation et dont le résultat est +également utilisable durant cette phase. +Il est possible de retourner une valeur (une instance d'un type donné) ou un type. + +Cette section présente diverses métafonctions, en suivant un ordre de difficulté croissant, avec +pour objectif la familiarisation avec les techniques utilisées en métaprogrammation. +L'intérêt des métafonctions présentées peut paraître limité, et il est vrai que, sans les accompagner +d'applications, il peut être difficile de se rendre compte des usages qui peuvent être faits. +Des applications ne seront cependant pas traitées ici, mais dans des parties ultérieures pour +lesquelles les concepts explorés, ainsi que certaines métafonctions, seront indispensables. +%}}} + +%{{{ +\subsubsection{Métafonctions pures} +\label{subsubsec:mp/mpt/metafunction/pure} + +Une métafonction est pure si elle est entièrement résolue durant la phase de compilation. +Un exemple de telle métafonction est le calcul, au moyen de la métaprogrammation template, d'une +factorielle. +Cet exemple va nous permettre de définir certains points utiles par la suite, en particulier +certains usages. +S'il existe plusieurs manières d'écrire un même programme, c'est également le cas pour les +métaprogrammes et le C++ ne fait pas exception. +Les évolutions récentes du langage ont apporté de nouveaux outils pour la métaprogrammation, et ceci +va se poursuivre avec les futurs standards~\autocite{ref:sutter2019,ref:meneide2020}. +Plusieurs versions vont être présentées pour le cas de la factorielle. + +Avant d'écrire un métaprogramme, l'\cref{eq:factorial} donne une définition possible de ce qu'est la +factorielle. + +\begin{equation} + n! = \Pi_{i=1}^{n} i + \label{eq:factorial} +\end{equation} + +Si en programmation plus classique, l'écriture récursive d'une fonction est souvent évitée, +potentiellement pour des raisons d'optimisation, ce ne sera pas le cas en métaprogrammation +template. +En effet, tout comme en programmation fonctionnelle, il n'y a pas d'effet de bord en +métaprogrammation template, ce qui rend nécessaire d'exprimer les fonctions de manière récursive. +Ainsi, pour produire le métaprogramme, l'\cref{eq:factorialrec} donnant une autre définition de la +factorielle, récursive cette fois, est plus adaptée. + +\begin{equation} + \left\lbrace + \begin{aligned} + 0! &= 1\\ + n! &= n \times (n-1)! + \end{aligned} + \right. + \label{eq:factorialrec} +\end{equation} + +Puisque le métaprogramme est évalué durant la compilation, sa complexité et son temps d'exécution +peuvent être ignorés au regard de l'exécutable produit et de son temps d'exécution. +Malgré cela, le temps d'évaluation d'un métaprogramme ayant un impact direct sur le temps de +compilation, il ne sera pas inutile de chercher à écrire des implémentations de complexité minimale. + +À partir de cette définition par récurrence de la factorielle, il est possible d'écrire le +métaprogramme de \acref{lst:mp/mpt/metafunction/factorial.11}. + +\begin{listing} + \inputlst{mpt/metafunction/factorial.11} + {Factorielle en C++11} +\end{listing} + +On retrouve dans ce code les deux égalités du système \acref{eq:factorial} affectées à la valeur +membre \cppinline{value}, la terminaison étant implémentée par une spécialisation du template pour +le cas particulier de la valeur \num{0}. +Ce premier exemple montre comment il est possible d'employer les templates pour faire travailler le +compilateur : lorsque l'instanciation de \cppinline{Factorial} est demandée, le compilateur doit +créer une classe possédant un membre dont la valeur, pour être déterminée, requiert l'instanciation +d'un autre template (excepté pour \num{0}). %TODO « classe » => « structure » +Ainsi, si \cppinline{Factorial<3>::value} est demandé, le compilateur devra également instancier +\cppinline{Factorial<2>}, \cppinline{Factorial<1>} et \cppinline{Factorial<0>}, et ce membre +\cppinline{value} prendra la valeur $3 \times 2 \times 1 \times 1$, ce qui sera évalué par le +compilateur à la valeur \num{6}. + +On peut s'assurer qu'il n'y a aucun calcul effectué durant l'exécution du programme en observant +l'appel à la métafonction et l'assembleur que cela produit dans +\acref{lst:mp/mpt/metafunction/tmpfactoasm}. + +\begin{listing} + \begin{multicols}{2} + \begin{cppcode} + int i = Factorial<10>::value; + \end{cppcode} + \mincpp{98} + \columnbreak + \begin{asmcode} + mov DWORD PTR [rsp-0x4], 3628800 + \end{asmcode} + \vspace{-4.5ex} + \asm + \end{multicols} + \vspace{-4ex} + \caption{Utilisation de la métafonction \cppinline{Factorial} et assembleur produit} + \label{lst:mp/mpt/metafunction/tmpfactoasm} +\end{listing} + +On voit dans l'instruction assembleur, où \num{3628800} correspond à $10!$, qu'il n'y a pas d'appel +à une fonction, contrairement à ce qui est obtenu avec une version non basée sur la +métaprogrammation (\cref{lst:mp/mpt/metafunction/factorial.asm}). + +\begin{listing}[t] + \inputlst{mpt/metafunction/factorial.asm} + {Assembleur produit par GCC pour une implémentation usuelle d'une factorielle} +\end{listing} + +L'assembleur présenté dans \acref{lst:mp/mpt/metafunction/factorial.asm}, commenté et simplifié pour +ne conserver que les parties qui nous intéressent, est ce qui est produit à partir d'une fonction +factorielle écrite classiquement. +La présence d'une instruction \asminline{call} et d'une boucle rend assez évident le possible gain +de performance qui peut être atteint au moyen de la métaprogrammation. +À noter toutefois que pour des exemples particulièrement simples -- c'est le cas de la factorielle +-- un compilateur est habituellement en mesure de produire un code pour lequel l'appel est remplacé +par un \asminline{mov} identique à celui obtenu par métaprogrammation. +Avec GCC 8.1.0, il faut cependant, pour cela, activer les optimisations incluses dans \texttt{-O2} +plus \texttt{-ftree-loop-vectorize} (incluse dans \texttt{-O3}), la résolution en un simple nombre +n'est alors pas toujours garantie. +Par contre, en employant la métaprogrammation, il est certain que cette optimisation sera faite, +et ce quel que soit le niveau d'optimisation utilisé. + +L'utilisation du mot-clé \cppinline{constexpr} permet de dire au compilateur que la valeur lui est +connue (la compilation échoue dans le cas contraire) et donc de l'utiliser dans les contextes où +cette connaissance est nécessaire pour opérer. +Le mot-clé \cppinline{static} permet de rendre la variable propre à la classe et non à ses +instances. + +Ainsi, \cppinline{Factorial<3>::value} est équivalent au nombre \num{6} et peut être utilisé en +particulier comme argument d'un autre template. +On peut alors écrire \cppinline{Factorial::value>::value}, équivalent à $3!!$. + +Une écriture encore plus moderne est possible en C++14, permettant une utilisation de la factorielle +très similaire à un appel de fonction (les parenthèses étant remplacées par des chevrons) grâce à +l'utilisation d'une variable template. + +Le C++14 apporte une fonctionnalité au langage qui permet une écriture plus légère en utilisant les +variables template (\cref{lst:mp/mpt/metafunction/factorial.14}). +Cette solution rend au mieux le terme de métafonction au regard de l'appel à celle-ci : +\cppinline{factorial<10>}. +Outre les chevrons, remplaçant les parenthèses, la syntaxe est identique entre l'appel d'une +fonction et celui d'une métafonction conçue de la sorte. + +\begin{listing} + \inputlst{mpt/metafunction/factorial.14} + {Factorielle en C++14} +\end{listing} + +On observe au travers de ces exemples l'aspect programmation fonctionnelle de la métaprogrammation +en C++. +Pour s'en convaincre, \acref{lst:mp/mpt/metafunction/factorial.haskell} présente une implémentation +possible de la factorielle en Haskell, un langage de programmation fonctionnel. + +\begin{listing} + \inputlst{mpt/metafunction/factorial.haskell} + {Factorielle en Haskell} +\end{listing} + +Si l'écriture d'une métafonction au moyen d'un template de classe est courante et encore usitée en +C++ moderne, il est cependant possible dans certains cas d'arriver à une écriture moins +fonctionnelle et plus impérative avec l'utilisation du mot-clé \cppinline{constexpr} appliqué aux +fonctions, rendant possible leur évaluation durant la compilation, et d'une variante de +\cppinline{if}, à savoir \cppinline{if constexpr}. +\Acref{lst:mp/mpt/metafunction/factorial.17} implémente la factorielle en utilisant \cppinline{if +constexpr}. + +\begin{listing} % TODO inline ? + \inputlst{mpt/metafunction/factorial.17} + {Factorielle en C++17} +\end{listing} + +L'instanciation d'un template comportant un branchement à évaluation statique, c'est-à-dire avec +\cppinline{if constexpr}, ignore les branches dont la condition est fausse, ce qui permet de +terminer la récursion lorsque $n \leqslant 1$. + +Le C++20 introduit un nouveau mot-clé, \cppinline{consteval}~\autocite{ref:smith2018}, qui permet la +définition de fonctions évaluées immédiatement par le compilateur. +À la différence de \cppinline{constexpr} qui permet l'utilisation de la fonction même si au moins un +de ses arguments n'est pas connu durant la compilation (elle s'effectue alors durant l'exécution), +\cppinline{consteval} fait échouer la compilation si l'appel ne peut être effectué durant celle-ci. +\Acref{lst:mp/mpt/metafunction/factorial.20} est une implémentation de la factorielle utilisant +cette fonctionnalité. +Dans ce cas, \cppinline{constexpr} est à éviter : si la fonction est utilisée avec un argument dont +la valeur est inconnue durant la compilation, cette implémentation récursive de la factorielle sera +inefficacement employée durant l'exécution du programme. +Or, une erreur d'utilisation permet facilement de donner un argument que le compilateur considérera +comme inconnu (par exemple, en utilisant une variable non déclarée \cppinline{constexpr} pour +contenir la valeur donnée en argument). + +\begin{listing} + \inputlst{mpt/metafunction/factorial.20} + {Factorielle en C++20} +\end{listing} + +Le mécanisme de \cppinline{consteval} peut être utilisé pour remplacer un template pour implémenter +une métafonction lorsque celle-ci manipule des valeurs (par opposition à des types) et est pure +(n'opérant que durant la compilation). +Quant aux fonctions \cppinline{constexpr}, leur évaluation est effectuée entièrement durant la +compilation ou, exclusivement, entièrement durant l'exécution, en plus de ne pas non plus permettre +le traitement de types. +Pour ces raisons, elles ne forment pas une base utilisable pour la métaprogrammation dont fait usage +ce document. +%}}} + +%{{{ +\subsubsection{Métafonctions mixtes} +\label{subsubsec:mp/mpt/metafunction/dual} + +Si l'on se limite aux métafonctions pures uniquement, la métaprogrammation ne permet alors que le +déplacement d'un traitement durant l'exécution à un traitement durant la compilation. +Elles sont alors intéressantes par exemple s'il s'agit de produire un résultat à partir de données +connues durant la compilation et qui seront utilisées par le programme durant son exécution. +Cette section présente, au travers de l'exemple du calcul d'une puissance ($x^n$ où $x$ est un +entier inconnu durant la compilation et $n$ un entier naturel connu), les capacités des +métafonctions mixtes. +Une métafonction mixte est effectivement exécutée durant la compilation, mais produit un résultat +qui sera ensuite traité durant l'exécution du programme plutôt qu'une donnée constante. + +Dans le cas où l'exposant est un entier naturel, l'\cref{eq:power} est une définition de +la puissance. +\begin{equation} + x^n = \Pi_{i=1}^{n} x + \label{eq:power} +\end{equation} + +Une première manière d'implémenter ce calcul consiste à écrire directement la boucle comme montré +dans \acref{lst:mp/mpt/metafunction/pow.rt}. + +\begin{listing} + \inputlst{mpt/metafunction/pow.rt} + {Implémentation possible de la fonction \cppinline{pow}} +\end{listing} + +Cependant, ce code est mal optimisé: malgré la connaissance de $n$, au pire une boucle est +effectuée, et selon le compilateur utilisé, environ \num{50} lignes d'assembleur pour $n = 24$. +Il est possible de faire bien mieux. +Si l'on écrit directement dans le programme : +{\setlength\topsep{0pt}\setlength\parskip{0pt} +\begin{center} + \cppinline{x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x} +\end{center}} +\noindent(il y a 24 occurrences de $x$), le compilateur parvient à produire un code assembleur ne +faisant que \num{5} multiplications. +Cela est possible par exponentiation rapide\footnote{À noter : en utilisant + des flottants (norme IEEE754), cette optimisation se heurte à la non associativité de leur + multiplication et un compilateur ne va l'appliquer que s'il lui est autorisé de ne pas tenir + compte de cette propriété. + C'est donc au prix d'une éventuelle perte de précision des résultats que cette optimisation peut +être forcée pour les flottants.}~\autocite{ref:gordon1998}. +Il est évident qu'écrire un code similaire est impensable : s'assurer d'avoir le bon nombre de $x$ +ou modifier la valeur de l'exposant sont des tâches particulièrement difficiles à accomplir. + +Grâce à la métaprogrammation, il est possible d'avoir le meilleur des deux solutions. +À l'instar du cas de la factorielle, une définition par récurrence comme dans l'\cref{eq:powerrec} +est plus simple à transcrire en programmation fonctionnelle, et donc en métaprogrammation template +(\cref{lst:mp/mpt/metafunction/pow}). + +\begin{equation} + \left\lbrace + \begin{aligned} + x^0 &= 1\\ + x^n &= x \times x^{n-1} + \end{aligned} + \right. + \label{eq:powerrec} +\end{equation} + +\begin{listing} + \inputlst{mpt/metafunction/pow} + {Métafonction puissance} +\end{listing} + +Cette fonction s'utilise comme montré dans \acref{lst:mp/mpt/metafunction/powasm}, à gauche. +L'assembleur présenté à droite correspond à ce qui est généré par le compilateur. +Seules 5 multiplications sont nécessaires à l'évaluation de cette nouvelle fonction, exactement +comme si les multiplications avaient été écrites directement. + +\begin{listing} + \begin{multicols}{2} + \begin{cppcode} + // volatile : évite la suppression + // des instructions inutilisées + int main() { + int volatile x = 2; + int volatile r = pow<24>(x); + } + \end{cppcode} + \vspace{-1ex} + \mincpp{98} + \columnbreak + \begin{asmcode} + ; extrait de l'assembleur produit + imul ecx, ecx + imul ecx, eax + imul ecx, ecx + imul ecx, ecx + imul ecx, ecx + \end{asmcode} + \vspace{-7ex} + \asm + \end{multicols} + \vspace{-4ex} + \caption{Utilisation de la métafonction \cppinline{pow} et assembleur produit} + \label{lst:mp/mpt/metafunction/powasm} +\end{listing} + +Ce qui peut être retenu, c'est qu'en écrivant une métafonction, il est possible de générer un code +C++ qui pourra ensuite être plus efficacement optimisé par le compilateur. % « c'est qu'en » +%}}} + +%{{{ +\subsubsection{Mécanisme d'instanciation} +\label{subsubsec:mp/mpt/metafunction/fibonacci} + +Dans cette section, nous allons présenter l'implémentation d'une métafonction pure retournant un +terme, dont l'indice sera en paramètre, de la suite de Fibonacci. +Les +\cref{eq:mp/mpt/metafunction/fibonacci:0,eq:mp/mpt/metafunction/fibonacci:1,eq:mp/mpt/metafunction/fibonacci:n} +définissent cette suite. + +\begin{align} + F_0 &= 0 + \label{eq:mp/mpt/metafunction/fibonacci:0}\\ + F_1 &= 1 + \label{eq:mp/mpt/metafunction/fibonacci:1}\\ + \forall n \in \llbracket 2, +\infty \llbracket, F_n &= F_{n-1} + F_{n-2} + \label{eq:mp/mpt/metafunction/fibonacci:n} +\end{align} + +En C++, la métafonction \cppinline{Fibonacci} peut être implémentée comme dans +\acref{lst:mp/mpt/metafunction/fibonacci}. +Lors de l'appel, par exemple \cppinline{Fibonacci<7>}, le compilateur doit résoudre la métafonction +pour les valeurs \num{6} et \num{5}. +La complexité associée au nombre d'instances qui doivent être créées est exponentielle en $n$, +l'argument donné à la fonction. +Évidemment, si cette fonction devait être implémentée dans un langage de programmation impérative, +par exemple, elle ne le serait pas ainsi puisqu'il est possible de la résoudre en complexité +linéaire. + +\begin{listing} + \inputlst{mpt/metafunction/fibonacci} + {Métafonction \cppinline{Fibonacci}} +\end{listing} + +La métaprogrammation template hérite de la programmation fonctionnelle certaines caractéristiques, +et comme l'ont montré les précédents extraits de code, certaines manières de penser dont la +récursivité. +Cette métaprogrammation repose sur le mécanisme des templates, nécessaires à la généricité du +langage. +En conséquence, un autre aspect fonctionnel dont elle hérite est l'absence d'effet de bord de ses +fonctions. +En effet, un template ne peut que produire un résultat à partir des arguments qui lui sont fournis, +mais il ne peut en aucun cas modifier d'état. + +Grâce à cela, il est garanti que deux instanciations d'un même template avec le même n-uplet +d'arguments produiront exactement le même résultat. +Un compilateur peut donc réutiliser le résultat d'une instanciation si celle-ci est à nouveau +demandée, et c'est ce qui est fait en pratique. + +Transitivement, une métafonction ne possède pas non plus d'effet de bord et ne sera exécutée qu'une +fois par le compilateur pour un même n-uplet d'arguments. +Ainsi, si le calcul de la factorielle d'un entier $n$ nécessite bien de générer $n$ instances d'un +template, demander à nouveau ce calcul ne causera aucune instanciation supplémentaire, et par +extension, demander le calcul de la factorielle d'un entier supérieur ne demandera que la différence +entre ces deux nombres en instances additionnelles. +Enfin, pour cet exemple du calcul de Fibonacci, on peut se rendre compte que la complexité qui +semble exponentielle sera effectivement linéaire. +%}}} + +%{{{ +\subsubsection{Traitements sur des types} +\label{subsubsec:mp/mpt/metafunction/types} + +La métaprogrammation en C++ ne permet pas seulement de travailler avec des valeurs mais aussi avec +des types. +Un exemple des plus simples (sans aller jusqu'à la fonction identité) est la manipulation d'un type +pour le modifier. +Il est par exemple possible de lui retirer ses qualificatifs (\cppinline{const} et +\cppinline{volatile}) grâce à la métafonction RemoveCV (\cref{lst:mp/mpt/metafunction/removecv}). +\begin{listing}[b] + \inputlst{mpt/metafunction/removecv} + {Métafonction RemoveCV} +\end{listing} +Le principe à implémenter est le suivant : par défaut, c'est le type lui-même qui est retourné ; si +le type est qualifié d'un \cppinline{const}, c'est le type sans le qualificatif ; de même pour +\cppinline{volatile} ; enfin, le dernier cas est celui où les deux qualificatifs sont employés, et +encore une fois, ce que la métafonction retourne est le type nu. + +La manipulation de types à la manière de simples données permet d'imaginer l'application de +traitements classiques en programmation, mais transposés à la métaprogrammation. +Pour cela, une bonne première étape consiste à créer une structure de donnée basique pour contenir +les types. +La liste de types~\autocite{ref:alexandrescu2001} (\en{typelist}) est un des premiers conteneurs de +type a avoir été créé et une implémentation possible (\cref{lst:mp/mpt/metafunction/typelist}) +consiste en une liste chaînée de types, avec un type particulier servant à reconnaître la fin. +Des métafonctions spécifiques accompagnent cette structure de donnée pour la manipuler, mais +celles-ci ne seront pas abordées pour cette implémentation spécifique des listes de types. +Pour cela, l'ouvrage \og \encitetitle{ref:alexandrescu2001} \fg~\autocite{ref:alexandrescu2001} est +une bonne référence. + +\begin{listing} + \inputlst{mpt/metafunction/typelist} + {Liste de types} +\end{listing} + +Cette manière d'implémenter les listes de types n'est cependant plus aussi utile qu'elle a pu l'être +avant C++11 : l'arrivée des templates variadiques a permis une très grande simplification de +l'écriture et de la manipulation des structures de données en métaprogrammation. +Une implémentation minimale d'une liste de types est présentée dans +\acref{lst:mp/mpt/metafunction/typelistvar}. % TODO appuyer propos pour !linkedlist +% https://www.reddit.com/r/cpp/comments/4k57tk/boost_mpl_dead/ + +\begin{listing} + \inputlst{mpt/metafunction/typelistvar} + {Liste de types moderne minimale} +\end{listing} + +Cette définition permet ensuite l'écriture de métafonctions pour manipuler la structure de données. +La suite de cette partie va présenter certaines de ces métafonctions, à commencer par l'ajout d'un +élément en fin de liste. +Avant de détailler le fonctionnement de la métafonction \cppinline{TypeListPushBack}, +\acref{lst:mp/mpt/metafunction/typelistpushback.call} présente un exemple d'utilisation. + +\begin{listing} + \inputlst{mpt/metafunction/typelistpushback.call} + {Utilisation de la métafonction \cppinline{TypeListPushBack}} +\end{listing} + +L'implémentation (\cref{lst:mp/mpt/metafunction/typelistpushback}) de cette métafonction est +composée de deux parties : la déclaration et la définition. +\begin{listing}[t] + \inputlst{mpt/metafunction/typelistpushback} + {Ajout d'un élément à une liste de types} +\end{listing} +La déclaration n'est pas systématiquement séparée de la définition, mais cela est nécessaire dans +cet exemple : la définition permet d'avoir une vue sur les paramètres templates plus fine qu'un +simple type. +La partie définition est en réalité une spécialisation, et par ce biais, le pack \cppinline{Ts} est +indiqué comme correspondant aux types contenu dans la liste : l'avoir nommé permet de l'utiliser par +la suite. % TODO reformuler ? + +Ajouter un élément en fin de liste devient alors simple : il suffit de créer une nouvelle liste dont +les éléments sont ceux de la liste originale suivis du type à insérer. +Il est important de noter que la liste donnée en argument n'a pas été modifiée par l'opération. +D'aucune manière il n'est possible de la modifier, et ce sera le cas pour toutes les données qui +seront manipulées par les métafonctions : elle sont immuables. +Plutôt que d'ajouter un élément à une liste, il s'agit en fait ici de créer une nouvelle liste +comportant le nouvel élément, et c'est un abus de langage par analogie à ce qui est fait de +similaire en programmation. +En cela, la métaprogrammation template possède un attribut de la programmation fonctionnelle pure +que nous avons déjà mentionné : l'absence d'effet de bord. + +Une autre opération intéressante est l'accès à un élément de la liste connaissant son indice. +Cette métafonction, \cppinline{TypeListGet}, peut s'utiliser comme dans +\acref{lst:mp/mpt/metafunction/typelistget.call}. + +\begin{listing} + \inputlst{mpt/metafunction/typelistget.call} + {Utilisation de la métafonction \cppinline{TypeListGet}} +\end{listing} + +L'implémentation (\cref{lst:mp/mpt/metafunction/typelistget}) est composée de trois parties : la +déclaration, le cas général et le cas particulier qui permet de terminer la récursion. +\begin{listing}[b] + \inputlst{mpt/metafunction/typelistget} + {Accès à un élément d'une liste de types} +\end{listing} +Comme cela a été fait pour \cppinline{TypeListPushBack}, il est possible d'accéder aux éléments de +la liste par un pack. +Par ailleurs, il est aussi possible d'isoler le premier (\cppinline{H}) des autres (pack +\cppinline{Ts}). +Accéder au $I^\text{ème}$ élément ($I$ non nul) d'une liste équivaut à accéder au +$(I-1)^\text{ème}$ élément d'une liste privée de son premier élément : en séparant le premier type +des autres dans la liste de types, il est possible d'appeler \cppinline{TypeListGet} en lui donnant +comme argument une liste de types ne comportant pas le premier élément \cppinline{H} mais seulement +les suivants \cppinline{Ts...}. +L'appel à \cppinline{TypeListGet} est précédé du mot-clé \cppinline{typename} ici. +Cet usage de ce mot-clé sert à indiquer au compilateur que le membre \cppinline{type} sera un type +(plutôt qu'une variable, par exemple). +Le compilateur ne peut pas le vérifier lui-même dans ce contexte car le template +\cppinline{TypeListGet} pourrait être tout à fait différent (le membre \cppinline{type} pourrait +être d'une autre nature, voire ne pas exister) selon les arguments qui lui sont donnés, or à ce +niveau, ces arguments sont inconnus. +À partir de C++20, cet usage ne sera plus obligatoire~\autocite{ref:ranns2018}, le compilateur étant +capable de supposer qu'un type est attendu puisque l'instruction sert à définir un nouveau type. +Le cas terminal arrive lorsque l'indice est nul, auquel cas l'élément qui doit être retourné est le +premier élément de la liste, lequel est facilement accessible. + +Les métafonctions peuvent ensuite être utilisées dans l'implémentation d'autres métafonctions comme +c'est le cas de \cppinline{TypeListPushBack} pour \cppinline{TypeListRev} par exemple, qui permet +d'inverser l'ordre des éléments d'une liste donnée en argument, et qui peut être appelé comme +présenté dans \acref{lst:mp/mpt/metafunction/typelistrev.call}. + +\begin{listing} + \inputlst{mpt/metafunction/typelistrev.call} + {Exemple d'appel de la métafonction \cppinline{TypeListRev}} +\end{listing} + +L'implémentation (voir \acref{lst:mp/mpt/metafunction/typelistrev}) est composée des trois mêmes +parties que l'est \cppinline{TypeListGet}. +Le principe est par ailleurs assez semblable, le cas général consistant à isoler le premier élément +de la liste pour l'ajouter en fin d'une sous liste dont l'ordre des éléments aura aussi été inversé. +Le cas particulier permettant de terminer la récursion est simplement l'inversion des éléments d'une +liste vide, qui revient à retourner une liste vide. + +\begin{listing} + \inputlst{mpt/metafunction/typelistrev} + {Inversion de l'ordre des éléments d'une liste de types} +\end{listing} + +%}}} + +%{{{ +\subsubsection{Liste de types : exemple} % TODO annexe ? +\label{subsubsec:mp/mpt/metafunction/typelist} + +Pour terminer sur ce sujet des listes de types avec un exemple plus complexe, nécessitant +l'implémentation de plusieurs métafonctions, le prochain cas étudié est la construction d'une liste +dans laquelle les valeurs (qui sont des types) sont toutes uniques, à partir d'une liste quelconque. +Cette métafonction se nomme \cppinline{TypeListUniq} et l'objectif est de pouvoir l'utiliser à la +manière présentée dans \acref{lst:mp/mpt/metafunction/typelistuniq.call}. + +\begin{listing} + \inputlst{mpt/metafunction/typelistuniq.call} + {Appel de la métafonction \cppinline{TypeListUniq}} +\end{listing} + +Pour résoudre le problème, il est possible de raisonner de la même manière que pour les exemples +précédents : isoler la queue de la liste (tous les éléments excepté le premier) pour faire l'appel +récursif et construire la solution en utilisant la valeur retournée par cet appel ainsi que la tête +de la liste, tout en prévoyant un cas terminal pour lequel le retour ne nécessite pas d'appel +récursif. +Dans le présent cas, il est possible de construire le résultat en y insérant le type courant +uniquement s'il n'est pas déjà présent. +Cette simple description implique deux métafonctions : une permettant de savoir si un type est +présent (ou contenu) dans une liste, et une autre un peu particulière permettant de sélectionner un +résultat selon une condition (une forme de structure de contrôle). + +La première métafonction nécessaire, \cppinline{TypeListContains} +(\cref{lst:mp/mpt/metafunction/typelistcontains}) permettant de déterminer si un type est contenu +dans une liste est semblable aux métafonctions présentées jusqu'ici. +Si le premier type de la liste n'est pas le type recherché, la métafonction s'appelle elle-même sur +une liste privée de son premier élément. +Si au contraire le premier type de la liste est le type recherché, elle retourne vrai. +Un second cas terminal existe pour le cas où le type n'existe pas dans la liste. +Si cela arrive, la récursion doit être terminée lorsque la liste est vide, auquel cas la +métafonction retourne faux. + +\begin{listing} + \inputlst{mpt/metafunction/typelistcontains} + {Test de la présence d'un type dans une liste} +\end{listing} + +La deuxième métafonction, \cppinline{If} (\cref{lst:mp/mpt/metafunction/if}), permet de sélectionner +un type parmi deux selon une valeur booléenne. +Celle-ci est fréquemment utilisée en métaprogrammation et est implémentée de manière assez +immédiate : si le booléen est vrai, le premier type est retourné, sinon c'est le second qui l'est. + +\begin{listing} + \inputlst{mpt/metafunction/if} + {Sélection d'un type selon une valeur booléenne} +\end{listing} + +À l'aide de ces deux métafonctions, l'implémentation de \cppinline{TypeListUniq} +(\cref{lst:mp/mpt/metafunction/typelistuniq}) est possible. +Comme expliqué partiellement, deux cas sont à distinguer. +Si plus aucun élément n'est à traiter, la liste qui a été construite peut être retournée. +Autrement, il faut ajouter à la liste en cours de construction l'élément courant s'il n'est pas déjà +présent. +Pour cela, dans un premier temps, \cppinline{Result} est déterminé en fonction de la présence de +\cppinline{H} dans la liste construite courante \cppinline{Rs...} : la liste retournée est +\cppinline{Rs...} à laquelle on ajoute en fin \cppinline{H} seulement s'il n'existe pas déjà dans +\cppinline{Rs...} (ce que l'on détermine en utilisant la métafonction \cppinline{TypeListContains}) +grâce à la métafonction \cppinline{If}. +\cppinline{Result} deviendra ensuite la liste courante lors de l'appel récursif. + +\begin{listing} + \inputlst{mpt/metafunction/typelistuniq} + {Implémentation de TypeListUniq} +\end{listing} +%}}} diff --git a/src/mp/mpt/1_usages.tex b/src/mp/mpt/1_usages.tex new file mode 100644 index 0000000..c546d69 --- /dev/null +++ b/src/mp/mpt/1_usages.tex @@ -0,0 +1,60 @@ +\subsection{\en{Helper type} et \en{helper variable}} +\label{subsec:mp/mpt/helpers} + +%{{{ +Avant d'entrer dans un cas concret de métaprogrammation, cette section présente quelques pratiques +utilisées dans cette thèse. +Dans la section précédente, plusieurs métafonctions ont été présentées. +Pour les appeler, il était nécessaire d'accéder à un membre (type ou valeur), et ce explicitement, +en suffixant d'un \cppinline{::type} ou d'un \cppinline{::value}. +Ceci peut être allégé en fournissant des \en{helpers}. +Pour la bibliothèque standard, le choix a été fait d'ajouter au nom de la métafonction le suffixe +\cppinline{_t} pour celles retournant un type, et \cppinline{_v} pour celles retournant une valeur. +Elle fournit ainsi pour la métafonction \cppinline{std::decay} le \en{helper type} +\cppinline{std::decay_t} et pour la métafonction \cppinline{std::is_same} la \en{helper variable} +\cppinline{std::is_same_v}. + +Puisqu'ici aucune contrainte de compatibilité n'a besoin d'être respectée, il est possible de +plutôt renommer l'implémentation effective en la suffixant de \cppinline{Impl} ou en l'encapsulant +dans un espace de noms \cppinline{impl}. +L'identifiant d'origine peut alors être utilisé comme alias. + +L'implémentation de tels outils est très simple, \acref{lst:mp/mpt/usages/helpertype} définit un +type template qui appelle la métafonction et accède au membre \cppinline{type}, et de manière +similaires, \acref{lst:mp/mpt/usages/helpervar} définit une variable template qui appelle la +métafonction et accède au membre \cppinline{value}. + +\begin{listing} + \inputlst{mpt/usages/helpertype} + {\en{Helper type} \cppinline{TypeListUniq}} +\end{listing} + +\begin{listing} + \inputlst{mpt/usages/helpervar} + {\en{Helper variable} \cppinline{typeListContains}} +\end{listing} + +De plus, cette technique permet de proposer une interface plus naturelle lors de l'utilisation de la +métafonction. +En effet, le second paramètre template de \cppinline{TypeListUniqImpl} possède une valeur par +défaut et une valeur ne doit pas lui être attribué par l'utilisateur lors du premier appel. +S'il le fait, le résultat peut devenir faux ou pire, la fonction peut ne plus compiler. +Avec \acref{lst:mp/mpt/usages/helpertype}, la métafonction \cppinline{TypeListUniq} n'a bien qu'un +paramètre, le risque est donc évité. + +Ces \en{helpers} sont possibles seulement pour les métafonctions qui ne retournent qu'une +information et dont le nom est prédictible (\cppinline{type} ou \cppinline{value}). +Si une métafonction doit enfreindre une de ces contraintes, elle devra être appelée de manière +spécifique. +%}}} + +% \subsubsection{Évolution de la métaprogrammation en C++} +% \label{subsec:mp/mpt/custom/evolution} +% +% Durant cette thèse, nous nous sommes contraints à la norme C++14 afin de maximiser la portabilité du +% code produit. +% Cependant, le langage évolue continuellement, et récemment dans le sens d'une facilitation de la +% métaprogrammation. +% Cette section présente succinctement une partie des nouvelles fonctionnalités concernées. +% +% diff --git a/src/mp/mpt/2_et.tex b/src/mp/mpt/2_et.tex new file mode 100644 index 0000000..a559952 --- /dev/null +++ b/src/mp/mpt/2_et.tex @@ -0,0 +1,582 @@ +\subsection{Patrons d'expression} +\label{subsec:mp/mpt/et} + +%{{{ +Cette section traite des \gls{ET}~\autocite{ref:veldhuizen1995}, patrons d'expression en français. +Les \gls{ET} vont servir de prétexte à l'exploration de diverses techniques de métaprogrammation +template, mais ils ont aussi été utilisés dans l'implémentation d'une partie des travaux réalisés +durant la thèse. +Les \gls{ET} permettent une représentation, sous la forme d'un type, d'un \gls{AST} issu d'une +expression C++ et à laquelle il est ensuite possible d'appliquer un traitement durant la compilation +afin par exemple de générer un nouveau code C++. + +Elles sont utilisées par exemple pour optimiser l'évaluation d'une expression arithmétique, +particulièrement dans le cadre de vecteurs et de matrices. +Sans \gls{ET}, un calcul comme $A = B + C + D + E$, où $B$, $C$, $D$ et $E$ sont des vecteurs de $n$ +éléments, va produire une séquence de trois calculs avec ces vecteurs : calcul du temporaire $T_1 = +B + C$, puis du temporaire $T_2 = T_1 + D$ et enfin du temporaire $T_3 = T_2 + E$, enfin déplacé +dans $A$. +Autrement dit, cela correspond à la séquence des trois boucles qui permettent d'implémenter les +formules des +\cref{eq:mp/mpt/et/calc_vectors:1,eq:mp/mpt/et/calc_vectors:2,eq:mp/mpt/et/calc_vectors:3} : +\begin{align} + \forall i \in \llbracket 1, n \rrbracket, \quad &T_1[i] = B[i] + C[i] + \label{eq:mp/mpt/et/calc_vectors:1}\\ + \forall i \in \llbracket 1, n \rrbracket, \quad &T_2[i] = T_1[i] + D[i] + \label{eq:mp/mpt/et/calc_vectors:2}\\ + \forall i \in \llbracket 1, n \rrbracket, \quad &A[i] = T_2[i] + E[i]. + \label{eq:mp/mpt/et/calc_vectors:3} +\end{align} + +Grâce aux \gls{ET}, il est possible de ne produire qu'une boucle, implémentant la formule de +\acref{eq:mp/mpt/et/calc_vectors_et} : +\begin{align} + \forall i \in \llbracket 1, n \rrbracket, \quad &A[i] = B[i] + C[i] + D[i] + E[i]. + \label{eq:mp/mpt/et/calc_vectors_et} +\end{align} + +En effet, l'expression C++ écrite ne procède pas au calcul immédiatement mais permet de générer à la +place un autre code. +Ce type de techniques est utilisé par Blitz++~\autocite{ref:veldhuizen2000} ou +LLANO~\autocite{ref:kirby2003}. + +Afin d'expliquer le fonctionnement des \gls{ET}, la suite de cette section présente des +implémentations partielles possibles, en augmentant la complexité au fur et à mesure\footnote{Des +exemples complets sont disponibles à l'adresse \url{https://phd.pereda.fr/compl/mp}.}. +Les extraits de code peuvent être un peu simplifiés afin de ne pas devoir se perdre dans des détails +de mise en œuvre qui ne sont pas directement liés au fonctionnement même des \gls{ET}. +%}}} + +%{{{ +\subsubsection{Premier jet} +\label{subsec:mp/mpt/et/firstdraft} + +Le premier exemple est particulièrement simple, mais permet de montrer les principes. +L'objectif dans un premier temps est d'être capable d'exprimer une expression (arithmétique) et de +l'évaluer indépendamment de sa construction : faire de l'évaluation dite +paresseuse~\autocite{ref:iglberger2012}. +Pour cela, il faut représenter l'expression qui va se limiter, dans un premier temps, à des +opérations binaires sur des opérandes connues. +Acceptons deux opérateurs : \cppinline{Add} et \cppinline{Mul}. +Limitons une opérande à soit une valeur, soit une expression, où une expression est une opération +appliquée à deux opérandes. +\begin{align} + operator &::= Add \;|\; Mul\\ + operand &::= value \;|\; expression\\ + expression &::= operator(operand, operand) +\end{align} + +\Acref{lst:mp/mpt/et/1_operators} définit les deux opérateurs. +\begin{listing}[b] % TODO @Bruno : oui, simplifier. + \inputlst{mpt/et/1_operators} + {Définition des opérateurs \cppinline{Add} et \cppinline{Mul}} +\end{listing} +Afin de fonctionner sans tenir compte du type effectif des opérandes, leur fonction d'évaluation est +générique et retourne respectivement la somme et la multiplication des arguments qui leur sont +donnés. + + +Ces opérateurs définis, il est ensuite possible d'implémenter ce qui permettra de contenir une +valeur. +Pour que cela s'intègre dans une expression, une valeur doit aussi pouvoir être évaluée. +L'évaluation d'une valeur consiste simplement à retourner la valeur elle-même comme le montre +\acref{lst:mp/mpt/et/1_value}. +Il faut par ailleurs un constructeur acceptant un argument du type de la valeur. + +\begin{listing} + \inputlst{mpt/et/1_value} + {Définition de \cppinline{Value}} +\end{listing} + +Enfin, \acref{lst:mp/mpt/et/1_binexpr} définit ce qu'est une expression (binaire) : un opérateur et +deux opérandes. +L'évaluation de l'expression correspond à l'application de son opérateur aux arguments fournis. +Le type \cppinline{Value} et le type \cppinline{BinExpr} définissent une fonction membre +\cppinline{eval} sans paramètre et qui retourne un résultat. +Cette propriété commune, générique, permet d'utiliser l'un ou l'autre des deux types de la même +manière. +Grâce à cela, il est possible de composer une expression à partir d'une autre expression pour former +un arbre dont les feuilles seront nécessairement de type \cppinline{Value}. + +\begin{listing} + \inputlst{mpt/et/1_binexpr} + {Définition d'une expression binaire} +\end{listing} + +L'expression arithmétique $v_0 + v_1 \times v_2$ peut être représentée par l'arbre de la +\cref{fig:mp/mpt/et/et_ma} et retranscrit en un type C++ comme montré dans +\acref{lst:mp/mpt/et/1_expr} : \cppinline{SubExpr} représente le sous arbre de racine $\times$ dans +\acref{fig:mp/mpt/et/et_ma}, c'est-à-dire l'application de l'opérateur \cppinline{Mul} sur les +valeurs \cppinline{v1} et \cppinline{v2} ; puis \cppinline{Expr} représente l'arbre entier par +l'application de l'opérateur \cppinline{Add} sur la valeur \cppinline{v0} et le sous arbre +\cppinline{SubExpr}. + +\begin{figure} + \centering + \inputfig{mpt/et/et_ma} + {Arbre de l'expression $v_0 + v_1 \times v_2$} +\end{figure} + +\begin{listing} + \inputlst{mpt/et/1_expr} + {Création du type représentant l'expression $v_0 + v_1 \times v_2$} +\end{listing} + +Pour terminer, sur cet exemple, l'assembleur généré montre que le code produit par l'évaluation de +l'expression (\cref{lst:mp/mpt/et/1_eval}) est identique à une écriture directe sans la couche +d'\gls{ET}. + +\begin{listing} + \begin{multicols}{2} + \inputlst*{mpt/et/1_eval} + \columnbreak + \inputlst*{mpt/et/1_asm} + \end{multicols} + \vspace{-4ex} + \caption{Instanciation, évaluation et assembleur généré de l'expression $v_0 + v_1 \times v_2$} + \label{lst:mp/mpt/et/1_eval} +\end{listing} + +L'instanciation de l'expression est pour le moment assez peu intuitive et ne correspond pas à ce qui +est attendu d'un patron d'expression. +Ce problème est réglé par la suite, lorsque la surcharge des opérateurs sera introduite afin +d'obtenir une écriture semblable à l'expression mathématique correspondante. +La première ligne (partie gauche) de \acref{lst:mp/mpt/et/1_eval} correspondra alors à un code tel +que \cppinline{v0 + v1*v2}. +Il s'agit ici d'évaluation paresseuse puisque cette expression C++ ne procède pas encore au calcul +demandé sur les opérandes mais construit à la place un type (et son instance) représentant ce +calcul. +L'évaluation peut ensuite être déclenchée (éventuellement par un opérateur, ici par un appel de +fonction), celle-ci faisant appel à un code généré par l'\gls{ET}. +%}}} + +%{{{ +\subsubsection{Arité générique} +\label{subsubsec:mp/et/genarity} + +Telles que sont implémentées les différentes parties nécessaires au fonctionnement des \gls{ET} +jusqu'à présent, seules les opérations binaires sont possibles. +Nous proposons d'implémenter la grammaire suivante (où $\varepsilon$ dénote l'absence d'élément) : +\begin{align} + operator &::= Add \mid Mul \mid Minus\\ + operand &::= value \mid expression\\ + operands &::= \varepsilon \mid operand, operands\\ + expression &::= operator(operands) +\end{align} + +Pour cela, plusieurs solutions sont réalisables. +Cette section propose une implémentation, construite à partir de ce qui a déjà été proposé, et qui +sera adaptée aux modifications ultérieures. +Seule la classe template \cppinline{BinExpr} est remplacée par une nouvelle classe template +\cppinline{Expr} (\cref{lst:mp/mpt/et/2_expr}) capable de représenter une opération dont l'arité est +librement fixée à l'utilisation. +Le template est donc variadique sur les types des opérandes. + +\begin{listing}[t] + \inputlst{mpt/et/2_expr} + {Définition d'une expression d'arité générique} +\end{listing} + +La classe template \cppinline{std::tuple} implémente un n-uplet qui permet à la classe template +\cppinline{Expr} de contenir les instances des opérandes. +La fonction d'évaluation doit être implémentée pour appeler la fonction d'évaluation de l'opérateur +(\cppinline{Op::eval}) en passant comme arguments le résultat obtenu par l'évaluation de chacun des +opérandes, c'est-à-dire le résultat de l'appel de la fonction membre \cppinline{eval()} sur chacun +des éléments contenu dans le n-uplet. + +Une manière classique de faire cela est d'utiliser un pack d'indices des éléments auxquels on +souhaite accéder au sein du tuple (dans ce cas, tous, dans l'ordre). +Que ce soit sous la forme d'un pack permet son utilisation durant la compilation en appliquant une +expansion sur celui-ci, de la forme \cppinline{accessTupleElement(tuple)...} où +\cppinline{accessTupleElement} est un template permettant d'accéder à un élément d'un tuple à partir +de son indice, et \cppinline{is} un pack d'indices. + +La fonction membre \cppinline{eval()} ne dispose pas d'un tel pack d'indice. +Une indirection supplémentaire est donc nécessaire afin de créer un pack \textit{ad hoc} +contenant les indices d'accès aux éléments du n-uplet, c'est-à-dire tous les nombres de \num{0} à +$N-1$ si $N$ est le nombre d'opérandes. +La bibliothèque standard fournit pour cela une classe template : \cppinline{std::index_sequence}. +Son rôle est de contenir un pack d'indices, en écrivant par exemple +\cppinline{std::index_sequence<0, 1, 2, 3>}. +En complément, \cppinline{std::make_index_sequence} retourne une instance de +\cppinline{std::index_sequence} contenant des indices allant de \num{0} jusqu'à une borne +supérieure, exclue, donnée en argument. + +Le langage fournit depuis C++11 un opérateur, \cppinline{sizeof...}, qui retourne le nombre +d'éléments contenus dans un pack. +Il faut donc utiliser ce nombre comme argument à \cppinline{std::make_index_sequence} afin d'obtenir +le pack nécessaire (contenu dans le type d'une variable) que l'on peut ainsi transmettre à la +fonction qui va véritablement évaluer l'expression. + +Cette fonction accepte une instance de \cppinline{std::index_sequence} afin de pouvoir en extraire +le pack \cppinline{is} que son type contient. +L'expansion d'un pack pouvant se faire sur un motif complexe (voir \acref{sec:gnx/pack}), il est +possible d'appliquer sur le n-uplet d'opérandes l'accès à un opérande puis l'appel sur +celui-ci de sa fonction d'évaluation. +La fonction template \cppinline{std::get} permet d'accéder à un élément d'un n-uplet par son indice. +Par exemple, l'appel à la fonction membre \cppinline{eval} d'une classe \cppinline{Expr} dont le +pack d'opérandes est de taille \num{3} va se traduire par la création d'un pack d'indices $\{0, 1, +2\}$ qui produit par expansion \acref{lst:mp/mpt/et/3_expandedpack}. + +\begin{listing} + \inputlst{mpt/et/3_expandedpack} + {Appel à la fonction d'évaluation d'un opérateur avec pack étendu} +\end{listing} +%}}} + +%{{{ +\subsubsection{\en{Embedded Domain Specific Language}} +\label{subsubsec:mp/et/edsl} + +Un \gls{DSL} est un langage conçu pour être adapté à répondre aux besoins d'un domaine précis, +s'opposant aux langages de programmation plus polyvalents comme le C++ ou le Python par exemple. +Lorsqu'un \gls{DSL} est construit au sein d'un autre langage de programmation, il s'agit d'un +\gls{EDSL}. +Un \gls{EDSL} permet de fournir aux utilisateurs du langage hôte, par le moyen d'une bibliothèque, +des outils dont la grammaire et/ou la syntaxe sont optimisées pour retranscrire une problématique +d'un domaine en code source valide pour le langage hôte et qui implémente le comportement désiré. + +Les \gls{ET} permettent l'implémentation de langages embarqués en utilisant des fonctions pour +construire le type complexe d'une expression. +Le C++ supportant la surcharge de la plupart des opérateurs, il est classique d'utiliser ces +fonctions particulières pour remplir ce rôle. +Dans le cas d'un langage dédié à la construction d'expressions arithmétiques, l'utilisation des +opérateurs pour construire une expression devient même une évidence. + +Pour cela, il suffit de surcharger un opérateur, par exemple \cppinline{operator*} pour +\cppinline{Mul} (\cref{lst:mp/mpt/et/3_op_mul}). +Cet opérateur particulier accepte deux paramètres qui sont des valeurs, utilisées comme +opérandes d'une expression représentant l'application de l'opérateur \cppinline{Mul}. + +\begin{listing} + \inputlst{mpt/et/3_op_mul} + {Opérateur spécifique produisant une expression de multiplication de deux valeurs} +\end{listing} + +Afin de pouvoir construire le type correspondant à l'expression spécifique utilisée jusqu'ici $v_0 + +v_1 \times v_2$, la définition de l'opérateur \cppinline{operator+} de +\acref{lst:mp/mpt/et/3_op_add} est nécessaire. +Celle-ci accepte une valeur et une expression binaire de multiplication en paramètre et retourne la +nouvelle expression. + +\begin{listing} + \inputlst{mpt/et/3_op_add} + {Opérateur spécifique produisant une expression d'addition d'une valeur avec une expression} +\end{listing} + +La limite de cette manière de procéder est vite atteinte : si cela permet d'écrire quelques +expressions, supporter davantage de formes requiert l'écriture de nombreuses surcharges +d'opérateurs. +Une alternative plus générique consiste à être capable de combiner toute expression avec un +opérateur valide, il suffit donc de reconnaître ce qu'est une expression et ne pas accepter ce qui +n'en est pas une. +S'il est possible de supposer qu'un type correspond à une expression en vérifiant la présence de la +fonction membre \cppinline{eval} qui doit être présente, cela peut mener à de faux positifs. +Le mécanisme qui sera employé repose sur les \en{type traits}~\autocite{ref:maddock2002}. +Le principe d'un \en{type trait} est de fournir des informations à propos d'un type au moyen d'un +template l'acceptant en seul argument. +Par exemple, il existe dans la bibliothèque standard le \en{type trait} \cppinline{std::is_pointer} +qui retourne, sous la forme d'un booléen nommé \cppinline{value}, vrai si le type correspond à un +pointeur. +D'autres \en{type traits} possèdent plusieurs caractéristiques comme c'est le cas de +\cppinline{std::numeric_limits} qui peut informer, entre autres, sur les valeurs pouvant être +représentées par le type (minimale, maximale, précision, ...). + +Ce mécanisme peut s'appliquer de deux façons. +En étant intrusifs dans l'implémentation des types qui sont acceptés dans une expression, il est +possible de leur ajouter un membre qui sera spécifique et détecté automatiquement. +L'avantage réside dans la simplicité de l'implémentation de la classe de \en{traits}. +Cependant, en plus du fait que ce mécanisme est intrusif -- et donc inadapté aux types qui ne +peuvent pas avoir de membre, les types primitifs, ainsi qu'aux types ne pouvant être modifiés, +provenant d'une bibliothèque tierce par exemple --, pour véritablement éliminer le risque de faux +positif, il faut que le membre testé ne puisse pas être confondu avec un membre qui pourrait exister +sans avoir cette signification. +Cela est possible en utilisant un type membre dont la valeur est un type spécifique créé pour ce +besoin. % TODO @Bruno comment clarifier ? + +La seconde façon de procéder n'est pas intrusive mais requiert une classe de \en{traits} un peu plus +complexe. +Aucun des types acceptés n'a à être altéré, mais une spécialisation de la classe de \en{traits} +doit être fournie pour chacun d'eux (\cref{lst:mp/mpt/et/3_traits}). +De plus, puisqu'il s'agit de spécialisations d'une classe template, il est dans certains cas +possible d'exprimer algorithmiquement la caractéristique pour déduire par une métafonction sa +valeur. +Le \en{type trait} \cppinline{std::is_pointer} permet d'illustrer ce principe, bien qu'en en étant +un cas trivial, puisqu'il suffit de spécialiser le template pour le cas des pointeurs en retournant +alors \og vrai \fg, et de retourner \og faux \fg{} dans le cas général. +À noter également qu'il serait impossible d'appliquer la première méthode pour ce cas là puisque les +pointeurs sont des types directement fournis par le langage et sont ainsi non modifiables. +C'est donc cette seconde manière qui est habituellement utilisée, y compris dans la bibliothèque +standard. + +\begin{listing} % TODO introduire la syntaxe par héritage avant ? + \inputlst{mpt/et/3_traits} + {Classe de \en{traits} déterminant si un type peut être utilisé au sein d'une expression} +\end{listing} + +En utilisant cette information, il est alors possible de simplifier la création des opérateurs en +les rendant génériques sur les types qu'ils acceptent (\cref{lst:mp/mpt/et/3_op_gen}). +\begin{listing}[b] + \inputlst{mpt/et/3_op_gen} + {Opérateur générique produisant une expression de multiplication} +\end{listing} +La \gls{SFINAE} (voir \acref{sec:gnx/sfinae}) permet ici de s'assurer que les deux types sont +valides au sein d'une expression, en utilisant la classe de \en{traits} définie juste avant. + +L'écriture d'un autre opérateur binaire, par exemple \cppinline{operator+}, est très similaire : il +suffit de changer le nom de la fonction et le type d'opération à appliquer (\cppinline{Mul} devient +\cppinline{Add} par exemple). +Il est généralement préférable d'éviter d'avoir recours au préprocesseur en C++ dès lors que le +langage propose une alternative, car le \gls{CPP}, n'étant qu'un système de remplacement de texte, +permet l'écriture et la génération de code de manière quelquefois trop libre. +Cependant, pour le moment, le C++ ne permet pas l'écriture de code génératif d'opérateurs. +Lorsqu'il s'agit de créer un \gls{DSL}, il est souvent nécessaire de surcharger, comme nous l'avons +présenté, plusieurs opérateurs, et pour réduire la répétition du code, le préprocesseur est donc la +seule solution standard qui existe. +%}}} + +%{{{ +\subsubsection{Séparation données/traitement} +\label{subsubsec:mp/mpt/et/sepdata} + +Afin de rendre le système d'\gls{ET} plus polymorphe et extensible, l'implémentation peut être +adaptée pour séparer la manière de représenter l'expression et le comportement des opérateurs. +Cela permet, à partir d'une même expression, d'appliquer des traitements différents en utilisant une +évaluation différente. +Par exemple, l'expression C++ \cppinline{v0 * -(v1 + v2)} peut être évaluée à la manière de +l'arithmétique élémentaire, auquel cas l'exécution de cette expression correspond au calcul $v_0 +\times -(v_1 + v_2)$ ; mais il est également possible de faire correspondre aux opérateurs de +l'expression des opérateurs correspondants de l'algèbre booléenne, le calcul devenant $v_0 \land +\lnot (v_1 \lor v_2)$. + +Une possibilité est le \en{tag dispatching}~\autocite{ref:esterie2014}, une technique qui permet +d'utiliser un type pour sélectionner une fonction à appeler et qui est basée sur les mécanismes liés +à la surcharge de fonctions du C++. +Les structures qui avaient pour membre les fonctions à exécuter (\cref{lst:mp/mpt/et/1_operators}) +sont remplacées par des \en{tags}, de simples structures creuses dont seul le type, différent pour +chacune, est utilisé (\cref{lst:mp/mpt/et/4_tags}). + +\begin{listing} + \inputlst{mpt/et/4_tags} + {\en{Tags} correspondant aux opérations} +\end{listing} + +La fonction d'évaluation d'une expression doit être adaptée à ce mécanisme. +Pour cela, l'opérateur duquel elle obtenait jusqu'alors l'implémentation à exécuter et qui est +remplacé par un simple \en{tag} ne va plus servir qu'à sélectionner une fonction fournie par un +évaluateur qui reste à spécifier (\cref{lst:mp/mpt/et/4_expr_eval}). + +\begin{listing} + \inputlst{mpt/et/4_expr_eval} + {Fonction d'évaluation d'une expression basée sur le \en{tag dispatching}} +\end{listing} + +Cette fonction template \cppinline{eval}, membre de \cppinline{Expr} (\cref{lst:mp/mpt/et/2_expr}), +accepte en paramètre template l'évaluateur : une classe fournissant une fonction d'évaluation +surchargée basée sur le \en{tag dispatching} pour sélectionner la version qui implémente l'opération +souhaitée (le \en{tag} étant l'instance créée du type \cppinline{Op} (paramètre de +\cppinline{Expr})). + +L'évaluation de l'opérateur est appliquée sur les opérandes eux-mêmes évalués par leur fonction +membre \cppinline{eval}, faisant indirectement appel à cette surcharge +(\cref{lst:mp/mpt/et/4_expr_eval}). +Cela cause donc en cascade l'évaluation de l'entièreté de l'arbre. + +Grâce à ce système, choisir la manière dont sera évaluée l'expression est devenu très simple : il +suffit d'écrire une classe disposant de fonctions membres \cppinline{eval} pour chaque opération qui +doit être supportée, en utilisant le \en{tag} correspondant. +\Acref{lst:mp/mpt/et/4_evaluators} présente ainsi l'implémentation des évaluateurs sus-cités +calculant selon l'arithmétique élémentaire (\cppinline{EvaluatorElem}) et selon l'arithmétique +booléenne (\cppinline{EvaluatorBool}). + +\begin{listing} + \inputlst{mpt/et/4_evaluators} + {Évaluateurs par arithmétique élémentaire et booléenne basés sur le \en{tag dispatching}} +\end{listing} + +Cependant, l'extensibilité de ce système n'est pas totale. +En effet, si, \textit{a posteriori}, un utilisateur souhaite ajouter le support d'une nouvelle +opération à un évaluateur existant, il doit le modifier. +Une technique ressemblant au \en{tag dispatching}, mais utilisant la spécialisation d'un template +selon un \en{tag} plutôt que la résolution de surcharge de fonction, permet de supprimer cette +limite. +L'implémentation des \en{tags} ne change pas, en revanche, comme la manière de définir l'évaluateur +est différente, l'évaluateur lui-même devenant un template plutôt qu'un type +(\cref{lst:mp/mpt/et/4_evaluators_template}), la fonction d'évaluation d'une expression doit être +adaptée (\cref{lst:mp/mpt/et/4_expr_eval_template}). + +\begin{listing} + \inputlst{mpt/et/4_expr_eval_template} + {Fonction d'évaluation d'une expression (template)} +\end{listing} + +Ainsi, plutôt que d'accepter comme paramètre template un type, elle accepte un template, et lors de +l'évaluation, le \en{tag} correspondant à l'opérateur n'est plus donné comme premier argument de la +fonction d'évaluation (plus exactement, c'était une instance de ce \en{tag}) mais comme argument +template à l'évaluateur. + +\begin{listing} + \inputlst{mpt/et/4_evaluators_template} + {Évaluateur par arithmétique élémentaire (template)} +\end{listing} + +Les codes présentés dans ce document font abstraction de difficultés techniques par souci de +simplicité et de concision. +Notamment, les paramètres dont le type est un paramètre template de la fonction devraient être +traités spécifiquement (voir \acref{sec:gnx/forward} sur la transmission parfaite). +%}}} + +%{{{ +\subsubsection{Application partielle et curryfication} +\label{subsubsec:mp/mpt/et/curry} + +Un autre usage possible des \gls{ET} est la construction d'une expression qui comporte la structure +de celle-ci mais pas (tous) les arguments sur lesquels elle va s'appliquer. +Dès lors, l'expression construite peut être réutilisée pour différentes données, et elle peut par +exemple servir de fonction anonyme~\autocite{ref:jarvi2003a} (fonction {\it +lambda}~\autocite{ref:turing1937}, dont une syntaxe propre au langage a été introduite en C++11). +Adapter l'implémentation d'\gls{ET} présentée jusqu'ici à ce type d'application requiert une +indirection supplémentaire sur l'évaluation. +Celle-ci n'est pas nécessaire si l'évaluateur est fixé à l'avance. +Dans le cas présenté, le principe reste donc fondamentalement le même, à ceci près que l'évaluation +est déplacée dans une nouvelle classe qui aura connaissance à la fois de l'expression et de +l'évaluateur, et qui peut être construite directement par l'utilisateur ou par une fonction d'aide +triviale. + +La classe évaluable, pour être adaptée à une utilisation semblable à une fonction, surcharge alors +\cppinline{operator()} pour un nombre variadique de paramètres et doit associer ceux-ci aux +paramètres attendus de l'expression. +Dans le cadre des fonctions lambda, contrairement à ce qui a été présenté avant, les paramètres de +l'expression ne sont pas immédiatement attachés à des valeurs. +L'implémentation présentée ici permet, à l'instar de la bibliothèque Boost.Lambda de +\autocite{ref:boostlambda}, l'utilisation de variables spéciales +(\cref{lst:mp/mpt/et/5_lambda_arguments}) servant à indiquer la correspondance entre les paramètres +de la fonction générée et les opérandes de l'expression. + +\begin{listing} + \inputlst{mpt/et/5_lambda_arguments} + {Définitions d'arguments servant dans la création de fonctions anonymes} +\end{listing} + +Ces variables ont seulement besoin de comporter l'information de quel argument doit les remplacer, +ainsi un nombre ordinal suffit pour les désigner. + +Ensuite, il faut être capable d'évaluer l'expression en utilisant les arguments donnés lors de +l'appel sans qu'ils soient préalablement correctement associés comme c'était le cas jusqu'ici. +Pour cela, il est possible de regrouper ces arguments dans un n-uplet grâce auquel, en utilisant un +indice connu à la compilation, on peut accéder à un argument précis. +Cette technique a déjà été présentée dans les exemples précédents, lorsque les opérandes étaient +stockés. + +Prenons un exemple concret pour expliquer le principe : \cppinline{(_1 - _0)(5, 7)}. +La première paire de parenthèses comporte une expression, \cppinline{_1 - _0}, dont les opérandes +sont de types respectifs \cppinline{Argument<1>} et \cppinline{Argument<0>}. +Grâce aux \gls{ET}, cette expression construit un arbre dont la racine est l'opérateur \cppinline{-} +et les deux feuilles sont les arguments \cppinline{_1} et \cppinline{_0}. +Cet arbre expose un \cppinline{operator()} afin d'être utilisable comme une fonction, dont les +paramètres sont au nombre de deux (déterminé par le nombre ordinal maximum utilisé par les +feuilles). +Lorsque cette fonction est appelée (la seconde paire de parenthèses), les arguments qui lui sont +donnés (dans cet exemple, 5 et 7) vont être regroupés dans un n-uplet. +L'argument à l'indice 0 du n-uplet est 5 et l'argument à l'indice 1 du n-uplet est 7. +Un premier pack \textit{ad hoc} est construit pour contenir $\{0, 1\}$, de la même manière que cela +avait été fait dans \acref{subsubsec:mp/et/genarity}. +Celui-ci permet d'obtenir à partir de l'ensemble des opérandes de l'expression (\cppinline{_1} et +\cppinline{_0}) le pack des indices correspondant aux nombres ordinaux qui sont stockés dans ces +variables, c'est-à-dire $\{1, 0\}$. +De manière similaire, l'expansion de ce nouveau pack permet la génération des arguments pour l'appel +à la fonction d'évaluation de l'opérateur de l'expression en produisant quelque chose de similaire à +\cppinline{Substract::eval(7, 5)}. + +Une autre utilisation de ces fonctions anonymes est présentée dans \acref{lst:mp/mpt/et/5_lambda}. +À noter, encore une fois, que l'étape liant l'expression à un évaluateur est dispensable si ce +dernier est fixé à l'avance, et les bibliothèques implémentant ce concept de fonction anonyme ne +l'ont généralement pas. +Pour cette raison, et pour alléger l'écriture ici, les exemples en sont exempts. + +\begin{listing} + \inputlst{mpt/et/5_lambda} + {Exemples d'utilisation d'une fonction anonyme} +\end{listing} + +Lorsque l'évaluation est ainsi reportée, l'idée d'application partielle ou de +curryfication~\autocite{ref:kuchen2002} vient rapidement. + +Une application partielle consiste à créer une fonction à partir d'une autre, la nouvelle ayant une +arité plus faible que l'ancienne. +Prenons une fonction quelconque $f$ acceptant $n$ paramètres : +\[ + f : X_0 \times X_1 \times ... \times X_{n-1} \to Y +\] +L'application partielle de $f$ qui lie le premier paramètre à une valeur précise correspond alors à +: +\[ + bind(f, x_0) : X_1 \times ... \times X_{n-1} \to Y +\] + +Pour y parvenir, une sous partie des paramètres de la fonction doit être déterminée, c'est-à-dire +qu'une valeur connue doit être assignée pour chacun d'eux. +Il existe une implémentation d'applications partielles de fonctions quelconques en C++ +(\cppinline{std::bind}~\autocite[§20.8.9]{ref:iso2011}) qui permet également de réordonner les +paramètres (auquel cas l'arité n'est pas réduite) ou l'utilisation d'un même paramètre de la +nouvelle fonction comme argument pour plusieurs paramètres de l'ancienne +fonction~\autocite{ref:jarvi2001}. +Par exemple, il est possible d'effectuer cette transformation : +\[ + f : X_0 \times X_1 \times X_2 \times X_3 \times X_4 \to Y\\ +\] +\[ + bind(f, x_0, \_0, \_0, \_2, \_1) : X_{1,2} \times X_4 \times X_3 \to Y +\] + +On associe au paramètre $X_0$ l'argument $x_0$, réduisant l'arité de \num{1}, puis on utilise deux +fois le premier argument de la fonction créée pour l'associer aux deux paramètres $X_1$ et $X_2$, +réduisant à nouveau l'arité de \num{1}. +Enfin, le dernier argument de la fonction créée est associé au paramètre $X_4$ tandis que le +pénultième argument, $\_1$ est affecté au paramètre $X_3$. +L'équivalent écrit en C++ est présenté par \acref{lst:mp/mpt/et/5_bind}. + +\begin{listing} + \inputlst{mpt/et/5_bind} + {Exemple d'utilisation de \cppinline{std::bind}} +\end{listing} + +La curryfication consiste en la transformation d'une fonction à $n$ paramètres en une nouvelle +fonction à \num{1} paramètre retournant une fonction à $n-1$ paramètres qui fonctionne de la même +manière. +Cette récursion se termine pour la fonction générée n'ayant qu'un paramètre qui, plutôt que de +retourner une fonction sans paramètre, évalue la fonction d'origine. +La curryfication de la fonction $f$ présentée ci-dessus est ainsi : +\[ + curry(f) : X_0 \to (X_1 \to (X_2 \to ... (X_n \to Y))) +\] + +Il s'agit donc d'une forme d'application partielle qui permet de fournir à une fonction ses +arguments un à un plutôt que tous en même temps. +Cela permet par exemple de séparer certains processus qui seraient autrement liés par le fait qu'ils +produisent chacun un sous-ensemble des arguments à donner à une même fonction. +Après curryfication, celle-ci est en charge de contenir les arguments en attente jusqu'à application +complète. + +Par exemple, pour une fonction ayant deux paramètres $g : a, b \mapsto a+b$, l'application de la +curryfication donnerait une fonction $g_0 : a \mapsto (g_1 : b \mapsto a+b)$. +Autrement dit, il est possible d'appeler la nouvelle fonction en fournissant un seul argument, par +exemple $g_0(9)$, elle retourne alors la fonction $g_1 : b \mapsto 9+b$. + +Ce mécanisme peut être implémenté dans le cadre des patrons d'expression en changeant la manière de +fonctionner de la fonction d'évaluation, à laquelle sont donnés les arguments. +Plutôt que d'accepter uniquement exactement le bon nombre d'arguments, celle-ci doit accepter un +nombre inférieur, idéalement un seul pour correspondre exactement au concept de curryfication. +En permettant d'en accepter une quantité quelconque comprise entre \num{1} et l'arité de la +fonction, la fonction est cependant plus adaptable. + +La fonction d'évaluation de l'expression doit accepter un nombre variable de paramètres, et procéder +à différents traitements selon la quantité d'arguments fournis. +À chaque appel, si le nombre d'arguments fournis en tout n'est pas égal à l'arité de l'expression, +la fonction d'évaluation conserve l'ensemble des arguments. +Le nombre d'arguments total est déterminé par la somme du nombre d'arguments ainsi conservés et du +nombre d'arguments fournis à l'appel de la fonction d'évaluation. +Si ce nombre dépasse l'arité lors d'un appel, la compilation échoue. +Lorsqu'un appel fait que ce nombre atteint exactement l'arité de l'expression, celle-ci est évaluée +en utilisant tous les arguments, ceux conservés ainsi que ceux fournis en dernier. +%}}} diff --git a/src/par/0_intro.tex b/src/par/0_intro.tex new file mode 100644 index 0000000..0bc72c9 --- /dev/null +++ b/src/par/0_intro.tex @@ -0,0 +1,20 @@ +\section{Introduction} +\label{sec:par/intro} + +La puissance des ordinateurs croît en fonction de plusieurs critères. +Si la vitesse des accès aux différentes ressources est à considérer, celle des processeurs est +évidemment importante. +La performance des processeurs, mesurée en particulier par leur fréquence d'horloge, depuis leur +création, a augmenté exponentiellement pendant des dizaines d'années~\autocite{ref:mack2011} mais +cette amélioration est contrainte entre autres par des limites physiques~\autocite{ref:kish2002}. +De ce fait, les fréquences d'horloge n'ont plus guère augmenté depuis presque deux décennies. +L'utilisation de plusieurs cœurs de calcul au sein des processeurs permet de pallier ce problème, +nécessitant le recours aux paradigmes de programmation parallèle. + +Ce chapitre présente dans un premier temps la programmation parallèle : les différents types de +parallélisme et les différentes architectures ainsi que les difficultés que cela engendre. +Ensuite, deux axes permettant de réduire ces difficultés sont introduits : d'abord la +parallélisation automatique dont l'objectif est de produire un programme parallèle à partir d'un +programme séquentiel ; puis des abstractions permettant d'aider à l'écriture de programmes +parallèles qui se situent entre la parallélisation automatique et la programmation parallèle et que +nous qualifions de parallélisation assistée. diff --git a/src/par/1_general.tex b/src/par/1_general.tex new file mode 100644 index 0000000..5f3981d --- /dev/null +++ b/src/par/1_general.tex @@ -0,0 +1,85 @@ +\section{Généralités} +\label{sec:par/general} + +L'écriture d'un programme capable d'utiliser plusieurs processeurs est un exercice différent et plus +difficile que celui d'écrire un programme \og classique \fg, c'est-à-dire n'utilisant qu'un +processeur. +La tâche est particulièrement ardue puisqu'il faut encore considérer la grande variété +des architectures parallèles, ainsi que l'évolution des architectures existantes. + +Par ailleurs, il est possible de raisonner sur l'intérêt de la parallélisation en considérant, par +simplification, que tout programme peut être divisé en une sous partie séquentielle irréductible et +une sous partie idéalement parallélisable. +Une partie idéalement parallélisable implique que le gain obtenu en utilisant $n$ cœurs peut être +directement d'un facteur $n$, facteur que l'on nomme accélération (\en{speedup} en anglais). +Pour cela sont ignorées certaines contraintes techniques telles que le surcoût induit par la gestion +des tâches ou encore le risque de saturation de bus matériels pour l'accès aux données. + +Amdahl~\autocite{ref:amdahl1967} a utilisé cette modélisation pour affirmer que l'accélération dont +peut bénéficier un programme grâce à sa parallélisation sur $N$ cœurs est limitée par sa fraction +non parallélisable, notée $1-f_a$ avec $f_a \in \left[0, 1\right]$ sa fraction parallélisable. +Il en découle \acref{eq:amdahllaw} (où $s_{f_a}(n)$ est l'accélération, étant donnée une fraction +parallélisable $f_a$, en fonction du nombre de cœurs $n > 0$) qui, si l'on fait tendre $n$ +vers l'infini, borne l'accélération (\cref{eq:amdahlinf}). + +\begin{equation} + s_{f_a}(n) = \frac{1}{(1-f_a) + \frac{f_a}{n}} + \label{eq:amdahllaw} +\end{equation} + +\begin{equation} + \lim_{n \to \infty} s_{f_a}(n) = \frac{1}{1-f_a} + \label{eq:amdahlinf} +\end{equation} + +Ainsi, plus $f_a$ est petit, moins la valeur de $n$ affecte l'accélération obtenue, jusqu'au cas +extrême trivial pour $f_a = 0$ qui donne $s_0(n) = 1$. +Cette loi indique que même pour une valeur de $f_a$ optimiste supérieure à \num{0.95}, +l'accélération que l'on peut espérer est seulement de \num{20}, laissant penser que la +parallélisation avec un grand nombre de cœurs ne peut être réellement efficace. + +Cette analyse a été révisée par Gustafson~\autocite{ref:gustafson1988} qui a étudié le problème dans +le sens inverse. +Pour cela, il a considéré le temps d'exécution $t_n$ du programme exécuté en parallèle avec $n$ +cœurs, et, prenant en compte la fraction parallélisable $f_g \in \left[0, 1\right]$, a calculé le +temps que prendrait ce programme à s'exécuter sur un seul cœur (\cref{eq:gustafson!t1}). + +\begin{equation} + t_1 = (1-f_g)\,t_n + f_g\,t_n\,n + \label{eq:gustafson!t1} +\end{equation} + +Il suffit alors de calculer le rapport entre $t_1$ et $t_n$ pour connaître l'accélération obtenue +(\cref{eq:gustafson}) : +\begin{align} + s_{f_g}(n) &= \frac{t_1}{t_n} \tag*{($t_1$ : \cref{eq:gustafson!t1})~~~~~} \\ + &= \frac{(1-f_g)\,t_n + f_g\,t_n\,n}{t_n} \label{eq:gustafson} \\ + &= (1-f_g) + f_g\,n. \notag +\end{align} + +Cette loi fait prendre conscience que dans la réalité des applications parallélisables, le volume +des traitements à effectuer n'est pas fixe. +Ceci implique que tant que le problème est d'une taille croissante et que l'architecture sur +laquelle le programme est exécuté l'est également, l'accélération qu'il est possible d'avoir n'est +pas limitée. + +Comme cela a été dit, cette seconde loi découle d'une interprétation différente de la même réalité, +et il est facile de démontrer qu'elles sont en fait deux versions d'une même formule, utilisant des +variables différentes~\autocite{ref:shi1996}. +Si la première, la loi d'Amdahl, considère sa fraction séquentielle $1-f_a$ comme le rapport de +temps passé dans des parties non parallélisables du programme sur le temps total lorsque celui-ci +est exécuté sur un seul cœur, donc de manière séquentielle, ce n'est pas le cas de la seconde. +La loi de Gustafson utilise comme fraction séquentielle le même rapport, mais pour une exécution +parallèle utilisant un certain nombre de cœurs. + +Cette formule reste fondée sur des données simplifiées et ne prend pas en considération des facteurs +qui jouent un rôle important dans l'accélération réelle obtenue. + +Elle ne doit donc pas être utilisée (quelle qu'en soit la version) comme véritable argument en +faveur ou défaveur de la parallélisation mais plutôt comme un outil pour estimer le gain idéal qui +pourrait en découler. + +La parallélisation n'est absolument pas vouée à disparaître, au contraire, et il est nécessaire de +disposer d'outils permettant, en particulier aux personnes qui ne sont pas spécialistes de ce +domaine, de produire des programmes qui utilisent au maximum les possibilités offertes par la +machine sur laquelle ils sont exécutés. diff --git a/src/par/2_comp.tex b/src/par/2_comp.tex new file mode 100644 index 0000000..9239132 --- /dev/null +++ b/src/par/2_comp.tex @@ -0,0 +1,496 @@ +\section{Parallélisme} +\label{sec:par/comp} + +%{{{ +Il convient dans un premier temps de distinguer \og parallélisme \fg{} et \og concurrence \fg{} bien +que les deux domaines aient de nombreux points communs. +Il y a concurrence lorsque plusieurs tâches sont ordonnancées indépendamment les unes des autres. +Cela signifie que lorsqu'une tâche est bloquée, les autres peuvent poursuivre leur exécution. +En revanche, les tâches ne sont pas nécessairement exécutées simultanément : il est possible de +faire de la programmation concurrente en ne disposant que d'un cœur. +Lorsqu'il s'agit de disposer de plusieurs tâches pour améliorer les performances (par exemple dans +le domaine du calcul à haute performance, en anglais \gls{HPC}), ce n'est pas de la programmation +concurrente mais de la programmation parallèle (aussi nommée parallélisme). +Parmi les premières implémentations de la concurrence sur les monoprocesseurs, on peut noter +l'introduction des co-routines par \autocite{ref:dahl1966} dans le langage Simula. + +Le parallélisme peut être accompli sur un grand nombre d'architectures matérielles, chacune +fonctionnant différemment et imposant donc des contraintes variées. +Cette section traite dans un premier temps des différents cas relatifs à la manière d'accéder à la +mémoire. +Les axes sur lesquels peuvent s'appliquer le parallélisme sont ensuite présentés. +Enfin, cette section explique les bases du parallélisme par les \en{threads}. +Les points abordés dans ce chapitre sont orientés vers l'objectif général de cette thèse dont les +applications sont en mémoire partagée, utilisant des \en{threads}. +%}}} + +%{{{ +\subsection{Accès à la mémoire} +\label{subsec:par/comp/memory} + +Il faut principalement distinguer deux types d'accès à la mémoire : l'accès à une mémoire partagée +et l'accès à une mémoire distribuée. +Ces deux archétypes, souvent combinés dans la réalité, sont présentés dans cette section. + +%{{{ +\subsubsection{Mémoire partagée} +\label{subsubsec:par/comp/memory/shared} + +La mémoire principale (en opposition aux mémoires locales des processeurs sous la forme de +différents niveaux de cache) peut être unique et donc partagée par les différents processeurs, ce +qui constitue l'architecture la plus répandue actuellement au sein des micro-processeurs modernes. +Il s'agit alors de \gls{SMP} et il existe principalement deux architectures d'accès à la mémoire +principale : l'accès uniforme à la mémoire (\gls{UMA}) et l'accès non uniforme à la mémoire +(\gls{NUMA}). + +Dans le premier cas, le temps d'accès à la mémoire ne dépend pas de quel processeur effectue cet +accès : dans \acref{fig:par/comp/uma}, chaque processeur accède par le même mécanisme à la mémoire +centrale (\gls{UMA}). +À l'inverse, \acref{fig:par/comp/numa} schématise une architecture \gls{NUMA} possible. +Si toute la mémoire est directement accessible depuis chaque processeur de manière implicite, les +temps d'accès peuvent être différents selon le processeur et la zone mémoire à laquelle on accède. +En effet, la mémoire existe en plusieurs localités, et une mémoire plus proche sera donc plus rapide +d'accès. +Connaître le type d'architecture est important et permet d'observer de meilleures +performances~\autocite{ref:li2013}. + +\begin{figure} + \inputfig{comp/uma} + {Schéma d'architecture \glsxtrshort{UMA} à mémoire partagée} +\end{figure} + +\begin{figure} + \inputfig{comp/numa} + {Schéma d'architecture \glsxtrshort{NUMA} à mémoire partagée} +\end{figure} + +Du côté logiciel, la programmation pour une machine \gls{SMP} peut être faite en utilisant de +multiples processus, que ce soit d'un même programme ou de différents programmes. +Les processus pourront alors s'exécuter chacun sur un cœur différent et communiquer entre eux au +moyen de mécanismes tels que la \og communication inter-processus \fg{} (en anglais \gls{IPC}) +grâce auxquels ils peuvent par exemple partager un segment de mémoire, utiliser des files de +messages, ... + +La création et l'utilisation d'un processus est une tâche coûteuse en temps d'exécution : deux +processus ne partagent pas d'état, en particulier leur espace en mémoire. +Lors de la création d'un nouveau processus, il est donc nécessaire d'allouer un nouvel espace +mémoire, puis, lorsqu'une commutation de contexte survient, l'état du processus sortant doit être +sauvegardé et celui du processus entrant restauré. + +Les processus légers, plus communément appelés \en{threads}, diffèrent des processus \og lourds \fg, +appelés classiquement \og processus \fg, puisqu'ils partagent justement une grande partie de leur +état, l'espace mémoire notamment (la pile restant propre à chaque \en{thread}). +Ceci permet aux \en{threads} d'être moins coûteux en termes d'encombrement mémoire mais aussi en +facilité de commutation de contexte d'exécution~\autocite{ref:barney2009}, et en particulier lors de +leur création. +Ces caractéristiques justifient d'éviter l'utilisation de multiples processus pour +paralléliser de manière particulièrement fine un traitement. +Outre cette différence, un \en{thread} se comporte à l'identique d'un processus. +Le fait qu'ils partagent en particulier leur espace en mémoire peut simplifier la communication +entre les \en{threads}, mais cela soulève également certaines problématiques qui seront présentées +dans ce chapitre. + +De nombreux outils reposent sur les \en{threads} et permettent d'en simplifier l'utilisation. +On peut par exemple citer \gls{OpenMP}~\autocite{ref:dagum1998}, une \gls{API} pour les langages C, +C++ et Fortran ; \gls{CUDA}~\autocite{ref:luebke2008} qui est orientée pour une catégorie de +\en{threads} qui s'exécutent sur les nouvelles générations de processeurs graphiques (\gls{GPU}) qui +sont devenus de plus en plus généralistes ; \gls{OpenCL}~\autocite{ref:munshi2009}, à la fois une +\gls{API} et un langage, conçu à partir du langage C, permettant la programmation pour des systèmes +hétérogènes. +%}}} + +%{{{ +\subsubsection{Mémoire distribuée} +\label{subsubsec:par/comp/memory/distributed} + +À l'opposé de la mémoire partagée, il existe la possibilité d'avoir une mémoire distribuée. +Dans ce cas, chaque processeur dispose de sa propre mémoire principale privée +(\cref{fig:par/comp/distributedmem}). +Contrairement à une architecture \gls{NUMA} à mémoire partagée, si un processeur doit accéder à la +mémoire d'un autre processeur, la communication est explicite et à la charge du développeur. +Pour cela, un standard a été développé au moyen de la norme \gls{MPI}~\autocite{ref:walker1996}. +Les deux approches ne sont pas incompatibles et il existe des systèmes adjoignant plusieurs systèmes +à mémoire partagée pour en former un plus complexe à mémoire distribuée~\autocite{ref:protic1996}. + +\begin{figure} + \inputfig{comp/distributedmem} + {Schéma d'une architecture multiprocesseurs à mémoire distribuée} +\end{figure} + +Bien que les bibliothèques produites durant cette thèse aient été conçues pour être modulaires et +facilement adaptables à différents mécanismes de parallélisation, les travaux effectués n'ont été +appliqués que sur des systèmes à mémoire partagée avec une architecture \gls{UMA} et dont +l'implémentation repose sur les \en{threads} conformes aux normes \gls{IEEE} 1003, plus couramment +désignées par \gls{POSIX}\footnote{Quant à la présence du X dans l'acronyme : +\url{https://stallman.org/articles/posix.html}.}, précisément \gls{POSIX}.1c (ou \gls{IEEE} +1003.1c). +Pour cette raison, la suite de ce chapitre se concentre sur ces outils lorsqu'il s'agit de +parallélisation manuelle. +%}}} +%}}} + +%{{{ +\subsection{Classification des architectures} +\label{subsec:par/comp/archs} + +La taxonomie de Flynn~\autocite{ref:flynn1972} définit 4 modèles selon qu'une ou plusieurs +instructions sont appliquées à une ou plusieurs données simultanément, c'est-à-dire selon le nombre +de flux d'instructions et de données. +La programmation séquentielle correspond à un modèle \gls{SISD} (\cref{fig:par/comp/sisd}). +Le cas un peu particulier de \gls{MISD} permet de traiter une même donnée de plusieurs manières et +ne sera pas traité. +Les deux modèles \gls{SIMD} et \gls{MIMD} couvrent les différents cas de programmation parallèle. + +\begin{figure}[h] + \inputfig{comp/sisd} + {Schéma du fonctionnement \glsxtrshort{SISD}} + {\small inspiré de \url{https://en.wikipedia.org/wiki/File:SISD.svg}} +\end{figure} + +%{{{ +\subsubsection{Parallélisme de donnée} +\label{subsubsec:par/comp/archs/data} + +Le parallélisme de donnée correspond au modèle \gls{SIMD} : le flot d'exécution permet à une +instruction d'être exécutée sur un ensemble de flux de données (\cref{fig:par/comp/simd}). +L'axe de parallélisation étant celui des données, plus il y en a à traiter, plus il est possible de +bénéficier d'une forte accélération par la parallélisation. +On parle d'approche vectorielle de la parallélisation telle que l'a introduite +\autocite{ref:cray1978} + +\begin{figure}[!b] + \ocgfigI{Schéma SIMD} + {comp/simd} + {Schéma du fonctionnement \glsxtrshort{SIMD}} + {données :}{\ndata}{2/off,3/off,4/on} + {\small inspiré de \url{https://en.wikipedia.org/wiki/File:SIMD.svg}} +\end{figure} + +Un exemple classique est le traitement d'image matricielle pour lequel les mêmes instructions +doivent être exécutées pour tous les pixels qui peuvent donc être groupés de sorte à ce que chaque +cœur traite une partie de l'image. +C'est pour ce type de traitement spécifique qu'ont été conçus les \gls{GPU} qui sont les processeurs +correspondant au modèle \gls{SIMD} les plus connus et les plus répandus après l'introduction des +supercalculateurs vectoriels de marque \en{CRAY}. +Sur ces processeurs, il est par exemple possible d'exécuter des \en{fragment shaders} : un programme +prenant en entrée les coordonnées du pixel sur lequel il agit et retournant une couleur (des données +supplémentaires telles que le temps peuvent être utilisées). +Un tel programme permet d'altérer des données existantes (filtre pour rendre l'image plus floue) ou +de créer entièrement une nouvelle image, par exemple par des techniques comme le lancer de rayon +couplées à des fonctions de distance qui encodent les objets~\autocite{ref:tomczak2012} +(\cref{fig:par/comp/fragmentshader}). + +\begin{figure}[!b] + \centering + \includegraphics[width=.8\textwidth]{img/par/raymarching.png} + \caption{Résultat d'un \en{fragment shader}} + \label{fig:par/comp/fragmentshader} + {\small version complète et dynamique en ligne : \url{https://www.shadertoy.com/view/wslBzr}} +\end{figure} + +Les cœurs d'un \gls{GPU} ne sont pas aussi généralistes que les cœurs des \gls{CPU}, mais ils sont +en revanche très nombreux. +Les autres processeurs conçus pour du parallélisme de donnée présentent généralement une structure +similaire, mais possèdent des caractéristiques différentes de celles d'un \gls{CPU} classique, dont +une fréquence d'horloge plus faible. +Ainsi, ils sont optimisés pour effectuer un traitement plus simple mais très hautement parallélisé +avec une approche vectorielle. + +En conséquence, les \gls{GPU} ont été utilisés pour effectuer des traitements plus génériques et +l'on parle alors de \gls{GPGPU}. +S'il est possible d'exécuter de tels traitements au moyen de \en{shaders}, cela requiert l'encodage +des données à traiter sous forme d'image sur laquelle travaillera le \en{shader}. +Des outils ont donc été conçus pour simplifier la mise en œuvre de \gls{GPGPU}. +Parmi ceux-ci, \gls{C++ AMP}~\autocite{ref:lopez2016} de Microsoft (une norme couplée à une +bibliothèque implémentée en C++ et fonctionnant au-dessus de DirectX 11), +\gls{CUDA}~\autocite{ref:luebke2008} de Nvidia (une interface de programmation d'application (en +anglais, \gls{API}) pouvant fonctionner avec les langages C, C++ et Fortran), +OpenCL~\autocite{ref:munshi2009} de Apple Inc.\ et Khronos Group (un standard et une \gls{API} pour +la programmation sur des architectures hétérogènes, incluant les \gls{GPU} mais aussi disponibles +pour toutes sortes de \gls{FPGA}). + +Les \gls{CPU} sont également conçus pour supporter des instructions \gls{SIMD}. +Pour l'architecture x86, très répandue parmi les ordinateurs personnels et serveurs, on peut citer, +par ordre d'apparition, les instructions \gls{MMX}~\autocite{ref:peleg1997}, +\gls{SSE}~\autocite{ref:raman2000} et \gls{AVX}~\autocite{ref:zhang2018}. +% Lorsque ces jeux d'instructions sont utilisés, il s'agit d'un cas particulier de parallélisation +% nommé vectorisation. +%}}} + +%{{{ +\subsubsection{Parallélisme de tâche} +\label{subsubsec:par/comp/archs/task} + +Le parallélisme de tâche correspond au modèle \gls{MIMD} : plusieurs flux d'instructions sont +exécutés, chacun sur un flux propre de données (\cref{fig:par/comp/mimd}). +La parallélisation intervient sur les deux axes : données et instructions. +Ce type de parallélisme permet ainsi de bénéficier d'une accélération plus élevée lorsque le nombre +de données, ou le nombre d'instructions pouvant être parallélisées, augmente. + +\begin{figure}[!b] + \ocgfigII{Schéma MIMD} + {comp/mimd} + {Schéma du fonctionnement \glsxtrshort{MIMD}} + {instructions :}{\ninstr}{2/off,3/on,4/off,5/off,6/off} + {; données :}{\ndata}{2/off,3/off,4/on} + {\small inspiré de \url{https://en.wikipedia.org/wiki/File:MIMD.svg}} +\end{figure} + +Tout processeur disposant de plusieurs cœurs, c'est-à-dire presque la totalité des processeurs +actuels pour ordinateurs personnels (incluant les téléphones) et serveurs, permet ce parallélisme. +%}}} + +%}}} + +%{{{ +\subsection{Fondamentaux du parallélisme par \en{thread}} +\label{subsec:par/comp/thread} + +Cette section traite du parallélisme suivant le modèle \gls{MIMD} sur une architecture à mémoire +partagée avec un accès supposé uniforme à la mémoire en utilisant les \en{threads} \gls{POSIX}, +usuellement désignés par pthreads. +Il s'agit de présenter les concepts fondamentaux sur lesquels reposent les outils de plus haut +niveaux qui sont abordés ensuite. +Les pthreads sont plus complètement détaillés dans +\encitetitle{ref:butenhof1997}~\autocite{ref:butenhof1997}. +Ce livre est une source importante de cette section. + +%{{{ +\subsubsection{Gestion des \en{threads}} +\label{subsubsec:par/comp/thread/manage} + +La vie d'un \en{thread} est similaire à celle d'un processus. +\Acref{fig:par/comp/thread_life_cycle} représente les différents états dans lesquels peut se +trouver un \en{thread} et comment se font les transitions entre ces états. + +\begin{figure}[!b] + \inputfig{comp/thread_life_cycle} + {États d'un \en{thread}} +\end{figure} + +Lorsqu'un programme est exécuté, un nouveau processus est créé au sein duquel l'unique flot +d'exécution représente alors le \en{thread} principal. +La primitive correspondante de l'\gls{API} pthread est \texttt{pthread\_create}. +Contrairement au mécanisme de création d'un processus qui duplique entièrement le processus père +puis exécute, habituellement, une autre branche du programme, dans le cas des \en{threads} il ne +s'agit que de l'exécution, dans un flot indépendant, d'une fonction. +Le nouveau \en{thread} entre alors dans l'état \og prêt \fg. + +L'ordonnanceur du système d'exploitation traite alors le \en{thread} de la même manière qu'un +processus : lorsqu'il le décidera, c'est-à-dire lorsque le \en{thread} en cours d'exécution quittera +cet état, il passera le \en{thread} dans l'état \og exécution \fg. +À partir de cet état, un \en{thread} peut soit être préempté par l'ordonnanceur après un certain +temps d'exécution, soit terminer son exécution (normalement ou en étant annulé par un autre +\en{thread}), soit se retrouver dans l'état \og bloqué \fg{} pour attendre la disponibilité d'une +ressource. +Le système retient la raison du blocage, et lorsque celle-ci disparaît, il change l'état du +\en{thread} de \og bloqué \fg à \og prêt \fg. + +Un \en{thread} peut être bloqué en attendant, à l'instar des processus, la possibilité de lire le +contenu d'un fichier, qu'une connexion réseau soit établie, ... +Cependant, le fait que tous les \en{threads} d'un processus partagent la même mémoire crée une autre +ressource qui peut être bloquante : l'accès aux données partagées par les \en{threads}. +Ceci induit des problématiques de synchronisations entre les \en{threads} qui sont l'objet de la +section suivante. + +La primitive \texttt{pthread\_exit} permet à un \en{thread} de passer lui-même +dans l'état \og terminé \fg. +Ceci est équivalent à atteindre la fin de la fonction qu'il devait exécuter. +Un \en{thread} peut en terminer un autre en annulant son exécution avec la primitive +\texttt{pthread\_cancel}. +La terminaison d'un \en{thread} implique la restitution au système des ressources qui lui ont été +allouées. +Lorsqu'un \en{thread} est dans l'état \og terminé \fg, il doit donc être traité pour libérer tout +état lui étant propre, ce qui est accompli par la primitive \texttt{pthread\_detach}. +Cette fonction peut être utilisée sur un \en{thread} qui n'est pas terminé, auquel cas il est +configuré pour libérer automatiquement ses ressources lorsqu'il se finira. +%}}} + +%{{{ +\subsubsection{Synchronisation} +\label{subsubsec:par/comp/thread/sync} + +Les différents \en{threads} d'un même processus partagent leur mémoire, à l'exception de la pile qui +leur est propre. +Ceci est un avantage pour la communication entre les \en{threads} qui peut être faite au moyen de +variables partagées. +En revanche, cela signifie également que les \en{threads} doivent être synchrones quant à leurs +accès aux données communes : sans cela, il est possible d'obtenir des résultats incorrects. +\Acref{fig:par/comp/list_insert} montre comment une insertion dans une liste chaînée peut se +dérouler en trois étapes. +L'insertion se fait avant le deuxième élément. +La liste comporte initialement trois éléments $a$, $b$ et $c$ chaînés dans cet ordre +(étape~\num{1}). +Un nouveau maillon, $x$, est créé et pointe sur le second élément de la liste (étape~\num{2}). +Enfin, le premier maillon, $a$, est modifié pour pointer sur le nouvel élément dans la liste +(étape~\num{3}). + +\begin{figure} + \centering + + \ocgalt{Insertion dans une liste chaînée} + {Insertion dans une liste chaînée d'un élément en deuxième position}{list-insert} + {\input{src/fig/par/comp/list_insert}} + {\input{src/fig/par/comp/list_insert_ocg}} + + \caption{Insertion dans une liste chaînée d'un élément en deuxième position} + \label{fig:par/comp/list_insert} + \ocgtogglestaticdynamic{list-insert} +\end{figure} + +Sans prendre de précaution particulière, une exécution possible de deux \en{threads} appliquant +exactement cet algorithme peut être celle représentée par +\acref{fig:par/comp/list_insert_bad_sync}. +Dans celle-ci, la première étape est inchangée et représente une liste chaînée composée de trois +éléments. +Un des deux \en{threads} crée un nouveau maillon, $x$, et le fait pointer sur le deuxième élément de +la liste (étape~\num{2}). +Il est alors possible que ce \en{thread} soit préempté par l'ordonnanceur, auquel cas un autre +\en{thread} s'exécutant peut à son tour créer un nouveau maillon, $y$, et le faire pointer sur $b$ +(étape~\num{3}). +Si le premier \en{thread} reprend son exécution à ce moment, il termine son travail en faisant +pointer le premier maillon sur $x$ (étape~\num{4}). +Enfin, le second \en{thread} termine également son exécution en faisant pointer $a$ sur son maillon +$y$ (étape~\num{5}). +Le maillon $x$ est définitivement perdu et la liste ne contient qu'un nouvel élément au lieu de +deux. + +\begin{figure}[t] + \centering + + \ocgalt{Insertion parallèle incorrecte dans une liste chaînée} + {Insertion incorrecte par deux threads dans une liste chaînée}{list-insert-bad-sync} + {\input{src/fig/par/comp/list_insert_bad_sync}} + {\input{src/fig/par/comp/list_insert_bad_sync_ocg}} + + \caption{Insertion incorrecte par deux \en{threads} dans une liste chaînée} + \label{fig:par/comp/list_insert_bad_sync} + \ocgtogglestaticdynamic{list-insert-bad-sync} +\end{figure} + +La synchronisation des \en{threads} est donc indispensable lorsqu'ils agissent sur des données +partagées. +Les parties du code source dans lesquelles plusieurs \en{threads} peuvent accéder à ces données +sont appelées des sections critiques. +Le lecteur intéressé par ces concepts se reportera utilement à l'ouvrage de Quinn sur la +programmation parallèle~\autocite{ref:quinn2003}. +Les sections critiques participent à la fraction de temps d'exécution séquentielle du programme +puisque dans ces sections, deux \en{threads} ne doivent pas pouvoir progresser simultanément. + +Pour synchroniser plusieurs \en{threads}, il est possible d'utiliser les +sémaphores~\autocite{ref:dijkstra1968}. +Un sémaphore peut être décrit comme un entier non signé adjoint à une liste sur lequel deux +opérations peuvent être réalisées : +\begin{itemize} + \item incrémenter l'entier du sémaphore de \num{1}, nommée V ; + \item décrémenter dès que possible l'entier du sémaphore (il ne doit pas valoir \num{0}), nommée P. +\end{itemize} + +Ce qui rend intéressante l'utilisation d'un sémaphore plutôt que d'une simple structure disposant +d'un entier et d'une liste est l'atomicité garantie des opérations P et V. +Une opération est atomique si le \en{thread} qui l'exécute ne peut pas être préempté par +l'ordonnanceur avant de l'avoir terminée. +L'opération V ne peut qu'augmenter le sémaphore de \num{1}, en revanche l'opération P peut produire +deux résultats. +Si le sémaphore est non nul, il est simplement décrémenté et le \en{thread} peut continuer son +exécution. +Sinon, le \en{thread} est bloqué par l'attente d'une ressource : le sémaphore. +Il sera automatiquement débloqué si le sémaphore est incrémenté, donc suite à l'exécution d'une +opération V par un autre \en{thread}. +Si plusieurs \en{threads} sont en attente d'un même sémaphore, ils sont placés dans une file +d'attente de type \gls{PEPS} (en anglais, \gls{FIFO}). + +Sur ce principe peut être construit l'outil de synchronisation par exclusion +mutuelle~\autocite{ref:courtois1971}, en anglais \en{mutual exclusion} donnant le terme \en{mutex}. +Un \en{mutex} permet de limiter à un seul \en{thread} l'accès à une donnée : lorsque celui-ci aura +indiqué son entrée dans la section critique, tout autre \en{thread} voulant y entrer avant la sortie +du premier sera bloqué. +Ce cas particulier requiert un sémaphore initialisé à \num{1}. +La section critique doit alors être précédée de l'opération P, et suivie de l'opération V comme le +montre \acref{alg:par/comp/mutex}. +Les \en{threads} \gls{POSIX} fournissent les structures (\texttt{pthread\_mutex\_t}) et les +opérations (\texttt{pthread\_mutex\_lock} correspondant à P, \texttt{pthread\_mutex\_unlock} +correspondant à V) nécessaires à la mise en place d'un \en{mutex}. + +\begin{algorithm} + \inputalg{comp/mutex} + {Utilisation d'un \en{mutex} pour protéger une section critique} +\end{algorithm} + +En utilisant cette interface, le problème de l'insertion depuis de multiples \en{threads} dans une +même liste chaînée (illustré par \acref{fig:par/comp/list_insert_bad_sync}) peut être résolu comme +présenté dans \acref{lst:par/comp/llinsertion}. + +\begin{listing} + \inputlst{comp/llinsertion} + {Utilisation des \en{threads} \gls{POSIX} en C} +\end{listing} + +Un autre patron d'utilisation des sémaphores est la barrière de +synchronisation~\autocite{ref:hill1998}. +Son objectif, lorsqu'il s'agit de synchroniser $N$ \en{threads}, est de bloquer les $N-1$ premiers +qui arrivent au point de synchronisation, en utilisant le sémaphore $b$, jusqu'à ce que le dernier +l'atteigne également. +Ce cas général peut être implémenté comme le montre \acref{alg:par/comp/barrier}. +\begin{algorithm}[t] + \inputalg{comp/barrier} + {Barrière de synchronisation pour $N_t$ \en{threads}} +\end{algorithm} +Cet algorithme utilise aussi un sémaphore $m$ comme un \en{mutex} pour protéger la variable entière +qui compte le nombre de \en{threads} arrivés à la barrière. + +Certaines implémentations des sémaphores fournissent une opération supplémentaire Z qui permet de +bloquer l'exécution jusqu'à ce qu'un sémaphore donné soit nul. +Avec une telle possibilité, la barrière de synchronisation pour un nombre $N$ de \en{threads} peut +simplement initialiser un sémaphore à $N$ et, pour chacun des $N$ \en{threads}, effectuer +l'opération P suivie de l'opération Z. + +\Acref{alg:par/comp/barrier2} présente une version simplifiée pour le cas particulier d'une +barrière synchronisant seulement deux \en{threads}. +Il arrive fréquemment que ce cas particulier survienne pour synchroniser la fin de l'exécution d'un +\en{thread} avec un moment précis de l'exécution d'un autre. +Le standard pthread fournit pour cela \texttt{pthread\_join} que peut appeler un \en{thread} pour +attendre la terminaison d'un autre. +Cette fonction est donc bloquante si le \en{thread} décrit par l'identifiant donné en argument n'est +pas déjà terminé. +Le \en{thread} attendu est ensuite automatiquement détaché par \texttt{pthread\_join}. + +\begin{algorithm}[!b] + \inputalg{comp/barrier2} + {Barrière de synchronisation pour \num{2} \en{threads}} +\end{algorithm} + +Un autre mécanisme de synchronisation est l'attente qu'un certain état soit vrai. +Par exemple, un \en{thread} peut devoir travailler sur des données présentes dans une file +d'attente, auquel cas il doit attendre que celle-ci contienne au moins un élément. +Cela peut être accompli avec un sémaphore par l'utilisation de l'opération P dans le \en{thread} +travailleur, et ce avant de retirer un élément de la file d'attente. +Un autre \en{thread}, fournissant des données et donc remplissant la file d'attente, devra alors +utiliser l'opération V après avoir ajouté un élément pour notifier du changement d'état. + +Si ce schéma peut relativement facilement être étendu à de multiples travailleurs lorsqu'il s'agit +de faire par exemple de l'équilibrage de charge entre les \en{threads}, c'est-à-dire qu'un seul +\en{thread} doit être notifié lorsque la file d'attente acquiert un nouvel élément, il devient +complexe d'étendre réellement l'utilisation pour inclure par exemple un mécanisme de notification de +tous les travailleurs. + +L'\gls{API} des \en{threads} \gls{POSIX} propose pour cela le mécanisme de \en{condition variable}. +Leur utilisation repose principalement sur les fonctions \texttt{pthread\_cond\_wait}, +\texttt{pthread\_cond\_signal} et \texttt{pthread\_cond\_broadcast}. +La première permet d'attendre la réception d'un signal qui sera émis par l'une des deux autres. +La fonction \texttt{pthread\_cond\_signal} aura pour effet de débloquer un des \en{threads} en +attente avec un sémaphore, comme cela est expliqué ci-avant dans la présentation de ce mécanisme de +synchronisation. +La fonction \texttt{pthread\_cond\_broadcast} en revanche réveillera tous les \en{threads} en +attente. + +Tous les cas d'utilisations de sémaphores ne sont évidemment pas présentés et les pthreads +offrent également davantage de possibilités. +Cependant, les cas présentés en montrent assez pour rendre compte des difficultés qui peuvent +survenir avec la programmation \en{multi-thread}. +Il est très facile de mal employer les nombreux mécanismes de synchronisations, ce qui peut résulter +en des résultats incorrects, des interblocages~\autocite{ref:howard1973}, ... +De nombreux problèmes très connus~\autocite{ref:gingras1990,ref:zhang2009} existent pour montrer les +difficultés soulevées par la nécessité de synchronisation. +%}}} +%}}} diff --git a/src/par/3_auto.tex b/src/par/3_auto.tex new file mode 100644 index 0000000..f2efa39 --- /dev/null +++ b/src/par/3_auto.tex @@ -0,0 +1,294 @@ +\section{Parallélisation automatique} +\label{sec:par/auto} + +%{{{ +La section précédente a permis de montrer que la programmation parallèle est un exercice difficile. +Tout développeur n'est ainsi pas nécessairement capable de programmer avec ces contraintes, et le +travail requis pour se former peut dépasser les avantages obtenus : même si quasiment tout programme +bénéficierait d'être développé en pensant à sa parallélisation, les gains sont variables. +Cette difficulté a induit la recherche de méthodes d'automatisation afin de produire des exécutables +qui utilisent plus efficacement le matériel à leur disposition sans nécessiter de la part du +développeur un effort de programmation supplémentaire. + +La parallélisation automatique consiste en la production d'un programme qui s'exécute en utilisant +plusieurs processeurs sans que le code source ait besoin d'être modifié par un humain. +Il s'agit ainsi de parallélisme implicite. +L'outil qui procède à la parallélisation automatique doit en conséquence être capable de déterminer +de lui-même quelles parties du programme peuvent être parallélisées et comment elles doivent l'être. +Un outil de parallélisation automatique est proposé dans \acref{ch:alsk}. +%}}} + +%{{{ +\subsection{Parallélisme matériel} +\label{subsec:par/auto/hardware} + +De nombreux processeurs ont été conçus sur mesure, construits sur la base de \gls{FPGA} (un +processeur matériellement programmable) ou de \gls{DSP} (un processeur dédié au traitement du +signal) pour gérer des données ou des calculs très nombreux en +parallèle~\autocite{ref:batcher1980,ref:batlle2002,ref:perach2018}. +Ces circuits sont dédiés à un traitement spécifique et doivent être conçus pour chaque besoin. +Il existe en outre, sur les processeurs plus généraux, différentes techniques de parallélisation qui +sont maintenant très courantes : le \gls{BLP} ou \og parallélisme au niveau du bit \fg{} et +l'\gls{ILP} ou \og parallélisme au niveau des instructions \fg. + +%{{{ +\subsubsection{Parallélisme au niveau du bit} +\label{subsubsec:par/auto/hardware/bit} + +Les processeurs disposent d'unités dédiées aux calculs (\gls{UAL}, \gls{UVF}, ...) que l'on peut +schématiser par \acref{fig:par/auto/alu}. +Sur cette figure, les entrées sont à gauche et les sorties à droite. +En simplifiant, une \gls{UAL} attend 2 entrées numériques ($E_0$ et $E_1$ dans la figure) sur $n$ +bits ainsi qu'un ensemble d'autres informations ($E_f$), par exemple l'opération à effectuer. +La sortie principale ($S$) est également sur $n$ bits. +Les sorties supplémentaires ($S_f$) permettent d'indiquer par exemple l'occurrence d'un dépassement +d'entier. + +Ces unités de calcul peuvent ainsi travailler avec des nombres dont l'amplitude de valeur dépend de +la taille des bus de données utilisés pour les nombres en entrée et en sortie (et qui sont de taille +identique). +Une telle unité de calcul prend également en entrée et fournit en sortie d'autres informations qui +ne seront pas particulièrement détaillées, mais qui vont permettre la détection de dépassement de +capacité, par exemple. + +\begin{figure} + \inputfig{auto/alu} + {Schéma de l'interface d'une unité arithmétique et logique} +\end{figure} + +Lorsqu'un compilateur produit les instructions assembleur pour un processeur, il prend en compte ces +caractéristiques. +Dans un langage de haut niveau, une addition entre deux nombres sera ainsi convertie en +l'instruction correspondante. + +Le parallélisme au niveau du bit est une technique consistant en l'utilisation de bus de données +plus grands, permettant l'envoi, en une seule instruction, de davantage de données. +Après être passé de 4 à 8, puis 16, 32, 64... bits, le parallélisme au niveau du bit s'est intéressé +à la vectorisation grâce aux travaux de~\autocite{ref:cray1978}. +Par exemple, un processeur peut fournir une instruction pour effectuer \num{4} additions parallèles +de \num{8} entiers, chacun sur \num{16} bits. +Cette instruction acceptera donc deux nombres sur \num{64} bits. + +Un compilateur peut générer automatiquement des instructions profitant de ce mécanisme de +vectorisation\footnote{Dès lors que ces instructions sont comprises dans le jeu d'instructions qui +lui est connu et permis.}. +On retrouve ce que nous avions introduit dans \acref{sec:par/comp}, cette technique étant utilisée +depuis longtemps pour accélérer des calculs au sein même des +microprocesseurs~\autocite{ref:jackson1992}, tout comme Seymour Cray les réalisait avec des +composants de l'époque pour constituer ses unités vectorielles. +%}}} + +%{{{ +\subsubsection{Parallélisme au niveau des instructions} +\label{subsubsec:par/auto/hardware/instruction} + +Les processeurs sont capables de procéder à du parallélisme au niveau des instructions. +Plusieurs techniques existent, dont la chaîne de traitement +d'instructions~\autocite{ref:stallings1988} (\en{pipeline}), l'exécution +dynamique~\autocite{ref:keller1975} ou encore l'exécution spéculative~\autocite{ref:smith1998} qui +peuvent être implémentées dans les processeurs. + +L'exécution d'une instruction correspond à une séquence d'opérations telles que la récupération +d'une instruction (\gls{IF}), le décodage de celle-ci (\gls{ID}), son exécution (Ex), un éventuel +accès à la mémoire (\gls{MA}) et enfin la sauvegarde en mémoire (\gls{WB}). +Ces opérations sont effectuées par différentes parties du processeur et peuvent donc être effectuées +simultanément pour différentes instructions (\cref{fig:par/auto/ilp_pipeline}). +Cette approche du parallélisme est appelée \en{pipeline}. +Elle a été utilisée depuis longtemps dans tous types de chaînes de production manufacturières. + +\begin{figure}[!b] + \inputfig{auto/ilp_pipeline} + {Schéma d'une chaîne de traitement à \num{5} étapes, avec \num{5} instructions} + \small inspiré de \url{https://en.wikipedia.org/wiki/File:Fivestagespipeline.png} +\end{figure} + +L'exécution dynamique, aussi appelée exécution dans le désordre (de l'anglais \en{out of order +execution}) repose sur le fait que plusieurs instructions consécutives peuvent dépendre d'une même +partie du processeur, auquel cas il n'est pas possible en l'état de les paralléliser par chaîne de +traitement par exemple. +Ce que \autocite{ref:keller1975} a proposé est de regarder dans les instructions suivantes s'il en +existe des compatibles avec la courante, et que l'on peut exécuter prématurément sans pour autant +changer le comportement global des instructions. +Dans ce cas, le processeur va exécuter les instructions dans un autre ordre que celui qui lui a été +fourni, de manière à minimiser l'inoccupation des différentes sous-parties du processeur. + +L'exécution spéculative permet d'exécuter des instructions avant de savoir si elles vont devoir +l'être ou non. +Ainsi, s'il est avéré par la suite que ces instructions devaient être exécutées, le travail est déjà +accompli. +En revanche, dans le cas contraire, du travail supplémentaire peut être requis pour annuler des +opérations. +Le cas classique utilise la prédiction de branchement : par une heuristique, le processeur suppose +pour un branchement donné laquelle des deux branches va être exécutée, et ce avec de bons +résultats~\autocite{ref:patterson1995}. +Ces branchements sont causés par des structures de contrôle, et utilisant en assembleur des +instructions de la famille des \texttt{jmp}. +%}}} + +%{{{ +Ces techniques sont habituellement directement implémentées par les processeurs bien qu'elles +puissent également être conçues au niveau des compilateurs~\autocite{ref:bringmann1993}. +La plupart des processeurs faisant usage de ces techniques, tous les programme, y compris +séquentiels, bénéficient automatiquement de cette parallélisation. % TODO benny +%}}} +%}}} + +%{{{ +\subsection{Parallélisme logiciel} +\label{subsec:par/auto/software} + +La parallélisation peut être accomplie automatiquement à différents niveaux logiciels, que ce soit +par un compilateur, un outil externe ou une +bibliothèque~\autocite{ref:mathews2016,ref:arabnejad2018}. +Quel que soit l'outil, \acref{fig:par/auto/autoparstep} présente les étapes primaires nécessaires +pour convertir l'entrée qu'est le programme séquentiel en programme parallèle. +\begin{figure}[!b] + \inputfig{auto/autoparstep} + {Étapes de la parallélisation automatique d'un programme} +\end{figure} +La première étape, l'analyse lexicale et syntaxique, comporte la reconnaissance du programme et de +sa structure, qui va généralement être représenté en interne par un arbre syntaxique. +Par exemple, \acref{alg:par/auto/ast} sera représenté par l'arbre syntaxique de +\acref{fig:par/auto/ast}. +Cet arbre décrit l'organisation des instructions d'un programme, chaque nœud représentant une +instruction dont l'exécution est causée par l'exécution du nœud parent. + +\begin{algorithm} + \inputalg{auto/ast} + {Algorithme calculant la somme des \num{10} premiers entiers} +\end{algorithm} + +\begin{figure}[t] + \inputfig{auto/ast} + {Arbre syntaxique de \acref{alg:par/auto/ast}} +\end{figure} + +L'étape d'analyse lexicale et syntaxique est intrinsèque au fonctionnement d'un compilateur, et les +mécanismes dont peut faire usage une bibliothèque sont traités dans \acref{ch:pfor}. +Grâce à cette information, l'outil peut déterminer, par une analyse de dépendances, comment sont +utilisées les variables, les dépendances entre les instructions, ... +Enfin, à partir de ces informations, il est possible de générer le programme parallèle. +À nouveau, c'est une étape naturelle au sein d'un compilateur, et les techniques employées par les +bibliothèques seront traitées à part. + +\subsubsection{Analyse des dépendances} +\label{subsubsec:par/auto/software/dependencies} + +Un programme est une séquence d'instructions à effectuer qui ont été décrites dans un langage +spécifique. +Paralléliser un sous-ensemble de ces opérations ne peut être accompli que si ce qui est réalisé par +un programme séquentiel est conservé lors de son exécution parallèle. +Ainsi, certaines structures de programmes ne sont pas parallélisables car cela modifierait le +comportement général du programme. + +Il existe plusieurs types de dépendances : +\begin{itemize} + \item la dépendance de flux (\gls{RAW}) (\cref{alg:par/auto/depraw}) ; + \item l'anti-dépendance (\gls{WAR}) (\cref{alg:par/auto/depwar}) ; + \item la dépendance d'écriture (\gls{WAW}) (\cref{alg:par/auto/depwaw}) ; + \item la dépendance de contrôle (\cref{alg:par/auto/depcontrol}). +\end{itemize} + +\begin{algorithm}[!b] + \inputalg{auto/depraw} + {Dépendance \glsxtrshort{RAW}} +\end{algorithm} + +\begin{algorithm}[!b] + \inputalg{auto/depwar} + {Dépendance \glsxtrshort{WAR}} +\end{algorithm} + +\begin{algorithm}[!b] + \inputalg{auto/depwaw} + {Dépendance \glsxtrshort{WAW}} +\end{algorithm} + +\begin{algorithm}[!b] + \inputalg{auto/depcontrol} + {Dépendance de contrôle} +\end{algorithm} + +Une dépendance de flux apparaît lorsqu'une ressource est lue après avoir été modifiée : la valeur +lue dépend de l'ordre d'exécution et est correcte si l'écriture est effectuée d'abord. +Une anti-dépendance correspond à une ressource lue avant d'être modifiée. +À nouveau, l'ordre d'exécution doit être conservé pour que la donnée lue soit correcte. +La dépendance d'écriture survient lorsqu'une même variable est utilisée en écriture plusieurs fois +de suite, l'ordre des écritures devant être conservé. +Enfin, la dépendance de contrôle est liée aux structures de contrôle telles que le branchement +conditionnel. +L'exécution des instructions d'une branche du programme dépend alors d'une ressource utilisée pour +déterminer quelle branche est choisie (ou pour une boucle, combien de fois elle est exécutée). + +Ces dépendances affectent la parallélisation automatique à tous les niveaux, y compris, lorsqu'elles +existent au sein des instructions pour le processeur, dans le cadre des optimisations telles que +l'exécution dynamique~\autocite{ref:hwu1986}. +Les dépendances \gls{WAR} et \gls{WAW} peuvent être contournées dans certains cas : si la ressource +correspond à une variable, servant donc à retenir une information, il est possible d'introduire de +nouvelles variables pour supprimer la dépendance~\autocite{ref:nicolau1992}. +\Acref{alg:par/auto/depwar_solved} montre une solution pour rendre indépendantes les deux +instructions présentes dans \acref{alg:par/auto/depwar}. + +\begin{algorithm} + \inputalg{auto/depwar_solved} + {Résolution d'une dépendance \glsxtrshort{WAR}} +\end{algorithm} + +En revanche, à une échelle d'instructions moins fine, une ressource peut correspondre, par +exemple, à un fichier. +Dans cet exemple, deux écritures consécutives ne peuvent jamais être désordonnées sans que le +contenu final du fichier ne soit différent. +Si l'ordre des écritures est important, alors la dépendance \gls{WAW} est bloquante. + +La difficulté d'analyse de ces dépendances est accrue lorsque l'on accède aux ressources de manière +indirecte~\autocite{ref:horwitz1989} comme c'est le cas dans de nombreux langages, par exemple ceux +permettant l'utilisation de pointeurs (des variables contenant l'adresse en mémoire d'autres +variables). + +\subsubsection{Dépendances entre les itérations d'une boucle} +\label{subsubsec:par/auto/software/loops} + +De très nombreuses études se sont concentrées sur la parallélisation des +boucles~\autocite{ref:collard1995,ref:artigas2000,ref:ramon-cortes2018,ref:neth2019}. +En effet, dans la plupart des programmes, la majorité du temps d'exécution est localisé dans des +boucles, et il semble donc normal d'y consacrer beaucoup d'efforts si l'on souhaite améliorer les +performances. + +La parallélisation d'une boucle est soumise à des contraintes similaires à la parallélisation de +deux segments d'un programme, mais dans ce cadre, la dépendance est portée par les différentes +itérations et doit être vérifiée pour toutes ces itérations. +Les boucles peuvent principalement être catégorisées selon leur condition d'arrêt. +Celles dont on connaît à l'avance le nombre d'itérations (de nombreux langages utilisant +classiquement pour celles-ci le mot-clé \og \texttt{for} \fg, ce nom est quelquefois utilisé pour +les qualifier), et plus précisément le n-uplet des indices de l'itérateur utilisé, sont traitées +dans \acref{ch:pfor}. +Ce chapitre aborde en détail différentes optimisations applicables aux boucles, ainsi que les +contraintes sur leur parallélisation. +Les techniques de détection de dépendances et de génération de code y sont également expliquées. + +Les autres boucles, dont le nombre d'itérations est \textit{a priori} inconnu, sont couramment +qualifiées de boucle \og \texttt{while} \fg. +Malgré l'usage, il ne faut cependant pas croire qu'une boucle \og \texttt{for} \fg{} corresponde +nécessairement à une boucle ayant un nombre d'itérations bien connu : il est possible de les +utiliser pour remplacer une boucle \og \texttt{while} \fg, ou encore de la quitter prématurément +dans certaines conditions. +À l'inverse, une boucle \og \texttt{while} \fg{} peut être construite de telle sorte que le nombre +d'itérations soit connu. +La parallélisation automatique des boucles \og \texttt{while} \fg{} a été étudiée, en particulier +dans le cadre de boucles \og \texttt{while} \fg{} imbriquées dans des boucles \og \texttt{for} +\fg~\autocite{ref:griebl1995,ref:lengauer1995,ref:geuns2011}. + +Les données sur lesquelles agissent les boucles sont souvent des tableaux. +Dans ce cas, il est possible de vectoriser l'exécution. +Le principe est d'utiliser les instructions \gls{SIMD} du \gls{CPU} (\gls{MMX}, \gls{SSE}, +\gls{AVX}... Voir \acref{subsubsec:par/comp/archs/data}) pour exécuter la boucle par partie. +Par exemple, une boucle de \num{16} itérations peut être transformée en boucle de \num{4} +itérations, chacune exécutant de manière vectorisée \num{4} instructions. % TODO cache ? + +La parallélisation peut également être accomplie automatiquement sur des structures de programmes +plus complexes. +Les structures de données de type arbre sont fréquentes en recherche scientifique, les algorithmes +qui s'appliquent sur celles-ci sont donc souvent utilisés. +Il existe ainsi de nombreux travaux dont l'objectif est la vectorisation, ou la parallélisation, +automatique de différents algorithmes appliqués aux arbres~\autocite{ref:jo2013,ref:matsuzaki2006a}. +%}}} diff --git a/src/par/4_assist.tex b/src/par/4_assist.tex new file mode 100644 index 0000000..0d8f8dd --- /dev/null +++ b/src/par/4_assist.tex @@ -0,0 +1,198 @@ +\section{Parallélisation assistée} +\label{sec:par/assist} + +%{{{ +Si la parallélisation automatique permet à un développeur sans connaissance dans le domaine du +parallélisme de profiter davantage du matériel multicœur, cela reste limité à cause de l'analyse +complexe du programme qui est nécessaire. +Une analyse qui ne prend pas le risque de générer un programme dont le comportement dévie de +l'original (ce qui est normalement attendu d'un outil de parallélisation automatique) est souvent +amené à échouer à paralléliser des portions qui auraient pu l'être~\autocite{ref:kennedy1994}. + +Une approche intermédiaire, entre manuel et automatique, consiste à assister autant que possible le +développeur, mais en lui laissant une part de la charge de décision. +Les décisions concernées peuvent être de l'ordre du mécanisme bas niveau de parallélisation +(\en{threads}, processus, ...), de quelles parties du programme doivent, ou ne doivent pas, être +parallélisées, ou d'informations de plus haut niveau. +\Acref{ch:alsk} propose un outil entrant dans cette catégorie, la parallélisation assistée. +%}}} + +%{{{ +\subsection{Abstractions} +\label{subsec:par/assist/abstractions} + +\Acref{sec:par/comp} (plus précisément, \acref{subsec:par/comp/thread}) a introduit les mécanismes +du parallélisme basé sur les \en{threads}. +Une première étape pour assister l'utilisateur est de fournir une \gls{API} standard permettant +d'utiliser les \en{threads} facilement et de manière portable : c'est ce qu'a permis l'\gls{API} +\gls{POSIX}. +Cependant, mal employer les différentes primitives et structures de données est facile et le langage +C par exemple ne prévient pas certaines erreurs qui pourraient être évitées. +Parmi celles-ci : oublier l'initialisation d'un \en{mutex} (qui est une structure complexe). + +\Acref{lst:par/assist/pthread} montre l'aspect d'un programme construit en utilisant uniquement les +primitives de ce standard. +Cet algorithme démarre un \en{thread} qui partage un \en{mutex} avec le \en{thread} principal. +Chacun des deux \en{threads} travaille sur une donnée partagée (pour justifier l'utilisation d'un +\en{mutex} dans cet exemple). +Malgré la simplicité apparente (aucune problématique propre au parallélisme, sinon la protection +d'une section critique, n'est abordée), cet algorithme présente déjà le risque de commettre +plusieurs erreurs. +Parmi celles-ci : oublier d'initialiser le \en{mutex} (avec \texttt{PTHREAD\_MUTEX\_INITIALIZER} car +le type associé aux \en{mutex} est en fait une structure) ; mal associer la prise et la libération +d'un \en{mutex} ; ou encore oublier l'appel \texttt{pthread\_join}. + +Ces erreurs peuvent être évitées, par exemple, dans les langages orientés objets. +Ces langages fournissent la possibilité d'exécuter une fonction à la création et à la destruction +d'une instance d'une structure. +Ainsi, \acref{lst:par/assist/stdthread} représente le même programme que +\acref{lst:par/assist/pthread}, mais en utilisant l'interface d'une implémentation possible dans un +tel langage. +\begin{listing}[!b] + \inputlst{assist/pthread} + {Exemple simple utilisant des \en{threads} \glsxtrshort{POSIX} en C} +\end{listing} +\begin{listing}[!b] + \inputlst{assist/stdthread} + {Utilisation des \en{threads} \glsxtrshort{POSIX} en C++} +\end{listing} +À l'instar de ce qui est accompli pour le \en{mutex}, il est possible d'utiliser le destructeur de +\texttt{std::thread} pour automatiquement appeler la fonction membre \cppinline{join}. +Ceci a été proposé par~\autocite{ref:voutilainen2016}, et peut facilement être implémenté. +Cela ne fait pour le moment pas partie du standard, et il existe des discussions sur les risques que +cette fonctionnalité peut présenter en cas de mauvais usage~\autocite{ref:vollmann2016}. + +Pour aller plus loin, les concepts de \og futures \fg~\autocite{ref:halstead1985} et de \og +promesses \fg~\autocite{ref:liskov1988} qui introduisent un cadre au sein de la programmation +asynchrone ont été proposés. +Ces outils permettent d'exécuter une tâche dans un autre \en{thread} (donc de manière asynchrone) et +de disposer d'un mécanisme de synchronisation automatique lorsqu'une valeur que doit produire cette +tâche est effectivement utilisée. +\Acref{lst:par/assist/futurepromise} présente un cas simplifié d'utilisation. +La tâche produisant la valeur l'écrit dans la \og promesse \fg tandis que le \en{thread} principal +peut travailler pendant ce temps jusqu'à ce qu'il ait besoin de cette valeur. +Il utilise alors la \og future \fg{} pour l'obtenir. +Si la valeur est déjà disponible, elle est immédiatement acquise et l'exécution se poursuit. +Sinon, le \en{thread} est bloqué, en attente que la valeur soit renseignée dans la \og promesse \fg. +Ce mécanisme peut être détourné pour construire une barrière de synchronisation entre deux +\en{threads} comme le montre \acref{lst:par/assist/barrier}. +% Généralement, il existe un outil permettant d'exécuter de manière asynchrone une fonction (dans un +% nouveau \en{thread}) et d'obtenir directement une \og future \fg{} sans avoir à créer soi-même une +% \og promesse \fg{} préalablement, simplifiant davantage leur utilisation. + +\begin{listing} + \inputlst{assist/futurepromise} + {Synchronisation par \og future \fg{} et \og promesse \fg} +\end{listing} + +\begin{listing} + \inputlst{assist/barrier} + {Barrière de synchronisation par \og future \fg{} et \og promesse \fg} +\end{listing} +%}}} + +%{{{ +\subsection{Annotations} +\label{subsec:par/assist/annotations} + +Grâce aux outils de la section précédente, un développeur sachant écrire des programmes parallèles +risque moins de commettre certaines erreurs et dispose d'outils de plus haut niveau que les +primitives fournies par le standard \gls{POSIX} pour synchroniser des \en{threads}. +Cependant, l'utilisation des \en{threads} reste explicite. + +L'objectif de la parallélisation assistée est de libérer le développeur de la charge de programmer +explicitement ses \en{threads} (ou autres unités de parallélisme) sans pour autant laisser le +système décider entièrement. +Des annotations, ajoutées au code séquentiel, permettent alors au système d'obtenir des informations +fiables, fournies directement par le développeur. +L'\gls{API} d'\gls{OpenMP}~\autocite{ref:dagum1998} utilise des annotations, traitées par le +compilateur, pour indiquer par exemple que deux séquences d'un programme sont exécutables en +parallèle ou encore qu'une boucle peut voir ses itérations être parallélisées. +Lorsqu'un utilisateur précède une boucle de l'annotation \gls{OpenMP} \texttt{\#pragma omp parallel +for}, il indique en fait que la boucle ne possède aucune dépendance entre les différentes +itérations. +Si le développeur commet une erreur d'analyse et ajoute une annotation pour paralléliser un code +ayant des dépendances, le compilateur produira alors tout de même une implémentation parallèle et le +résultat de l'exécution du programme sera erroné. + +Certains langages de programmation subissent plusieurs transformations entre le code source et +l'exécution effective. +Le dernier état d'un tel programme avant son exécution est nommé \en{bytecode} et est interprété par +une machine virtuelle. +Le \en{bytecode} est ainsi comparable à un langage assembleur destiné à un programme, un +interpréteur, plutôt qu'à un processeur réel. +Dans ces langages, les annotations peuvent être encodées dans le \en{bytecode} et traitées \textit{a +posteriori} de la compilation~\autocite{ref:dittamo2007}. + +Le langage Cilk (et ses variantes Cilk Plus et Cilk++)~\autocite{ref:blumofe1996}, construit sur les +langage C et C++, propose des annotations au moyen de directives du préprocesseur (à l'instar +d'\gls{OpenMP}) mais également par des mots-clés supplémentaires permettant par exemple d'indiquer +au compilateur qu'une fonction peut être exécutée en parallèle. + +Depuis la norme C++17, le langage C++ dispose dans sa bibliothèque standard d'un système apparenté à +des annotations. +Les polices d'exécution~\autocite{ref:hoberock2013} permettent d'indiquer aux algorithmes de la +bibliothèque standard (et à toute autre bibliothèque se rendant compatible) leur manière de +s'exécuter. +Sans annotation, l'exécution est par défaut séquentielle, mais le développeur peut ainsi demander +une exécution parallèle. +Cependant, bien que présent dans la norme, ce mécanisme n'est pas encore implémenté par la majorité +des compilateurs. % TODO benny: code C++, Cilk +%}}} + +%{{{ +\subsection{Patrons parallèles} +\label{subsec:par/assist/patterns} + +Il existe pour la programmation un ensemble de patrons classiques~\autocite{ref:gamma1995} qui +apparaissent dans de très nombreux programmes. +Ceci existe de manière similaire spécifiquement pour la programmation +parallèle~\autocite{ref:mattson2005,ref:mccool2012}. + +Le patron \en{fork-join} est un des plus classiques et permet l'exécution parallèle de plusieurs +tâches indépendantes dont la terminaison est synchronisée. +Une application récursive de ce patron permet l'implémentation parallèle d'un algorithme de type \og +diviser pour régner \fg. +Ce patron est directement lié aux primitives \texttt{pthread\_create} (\en{fork}) et +\texttt{pthread\_join}. % TODO benny: code C++ + +Le patron \en{map} permet d'exécuter une même tâche sur un ensemble de données (modèle \gls{SIMD}). +Le terme \en{farm} désigne un patron \en{map} appliqué pour un flux plutôt qu'un ensemble de +données. +Ce patron requiert l'indépendance des différentes exécutions de la tâche. + +Le patron \en{pipeline} correspond à un ensemble de tâches exécutées en parallèle et dont les +entrées et sorties sont chaînées. +La première tâche accepte ainsi en entrée un flux de données fourni de manière externe. +Chaque autre tâche obtient alors son entrée à partir de la sortie de la tâche qui la précède. + +D'autres patrons existent, dont des patrons permettant une exécution séquentielle, cependant +l'objectif de cette section n'est pas d'en faire une liste exhaustive mais d'en présenter le +principe. +Des bibliothèques comme \gls{TBB}~\autocite{ref:willhalm2008} proposent des structures correspondant +à ces différents patrons pour être utilisées et combinées par le développeur. + +L'utilisation de patrons pour masquer au développeur la complexité réelle de l'implémentation +parallèle d'un programme a été introduite par \autocite{ref:cole1989} au moyen de squelettes +algorithmiques. +Ceux-ci sont définis comme des fonctions d'ordre supérieur fournissant un patron (le squelette) dans +lequel la tâche effective est encore non définie (la fonction en paramètre). +Ainsi, un développeur capable de choisir un patron d'exécution adapté à son programme peut le +paralléliser en utilisant le squelette correspondant. + +Pour permettre l'utilisation des squelettes algorithmiques sur la plupart des algorithmes, il faut +qu'il soit possible de les composer~\autocite{ref:benoit2005}. +Leur nature de fonction d'ordre supérieur, selon l'implémentation, le permet puisqu'un squelette +algorithmique est lui-même une fonction : il peut donc être utilisé comme argument d'un autre +squelette. + +\Acref{ch:alsk} présente une nouvelle proposition sur les squelettes algorithmiques par rapport aux +nombreuses implémentations qui ont déjà été +proposées~\autocite{ref:kuchen2002a,ref:falcou2006a,ref:marques2013,ref:ernstsson2018}. +Celle-ci s'oriente sur l'utilisation de la métaprogrammation afin de générer les codes représentés +par les squelettes et propose une description intégrale de ceux-ci (plutôt que de supposer à +l'avance certaines informations comme le font la plupart des implémentations de bibliothèques basées +sur les squelettes algorithmiques). +Grâce à cela, notre proposition permet l'intégration d'outils avancés pour, par exemple, simplifier +l'utilisation de nombres pseudo-aléatoires dans un contexte parallèle. +%}}} diff --git a/src/par/5_conclusion.tex b/src/par/5_conclusion.tex new file mode 100644 index 0000000..1e6ce4b --- /dev/null +++ b/src/par/5_conclusion.tex @@ -0,0 +1,29 @@ +\section{Conclusion} +\label{sec:par/conclusion} + +Ce chapitre a présenté la notion d'exécution parallèle et de parallélisme en introduisant les +notions fondamentales de ce domaine. +Parmi celles-ci, on trouve notamment le concept de \en{thread} qui permet de créer un flux +d'exécution indépendant, mais partageant une partie de la mémoire. + +Ce chapitre a également traité des problèmes de synchronisation des accès aux données communes et a +donc expliqué différents mécanismes tels que les sémaphores, le \en{mutex} ou encore les \og futures +\fg{}. +En général, ceux-ci doivent être évités lorsque l'objectif est de maximiser les performances du +programme. +Ainsi ils ne seront utilisés que très ponctuellement dans les travaux présentés. + +Il existe différentes techniques de parallélisation, allant de celles qui procèdent entièrement +automatiquement à la conversion d'un programme séquentiel en un programme parallèle à celles qui +opèrent en suivant des instructions spécifiques pour la parallélisation. +Chacune possède des avantages : une parallélisation automatique demande \textit{a priori} moins +d'efforts de la part du développeur ; à l'inverse une parallélisation assistée permet un plus grand +contrôle. + +Que ce soit pour procéder à une parallélisation automatique ou assistée, les deux étant traités dans +cette thèse, il est nécessaire d'obtenir des informations à propos du programme. +Dans le premier cas, ces informations peuvent être acquises depuis le code source. +Dans le second cas, le développeur fournira une forme d'annotation permettant d'assister la +parallélisation. +Les chapitres suivants donnent les notions élémentaires pour ce faire, en C++ et durant la +compilation. diff --git a/src/pfor/0_intro.tex b/src/pfor/0_intro.tex new file mode 100644 index 0000000..5474928 --- /dev/null +++ b/src/pfor/0_intro.tex @@ -0,0 +1,33 @@ +\section{Introduction} +\label{sec:pfor/introduction} + +Les principes généraux de la parallélisation automatique ont été introduits dans +\acref{sec:par/auto}. +Dans ce chapitre, nous nous intéressons en particulier à la parallélisation logicielle, et plus +spécifiquement au cas des boucles, puisque le temps d'exécution d'un programme est majoritairement +localisé au sein de celles-ci. + +S'agissant de parallélisation automatique, aucune information ne doit être apportée par le +développeur quant à savoir si le code peut effectivement être exécuté en parallèle de manière +valide, l'unique entrée du système étant le code source original devant être parallélisé. +Notre objectif n'est cependant pas de modifier la structure de l'algorithme pour le rendre +parallélisable s'il ne l'était pas. +Ainsi, il s'agit de détecter si le code fourni peut être parallélisé, c'est-à-dire s'il est possible +de générer un programme dont le comportement global est le même que celui de sa version +séquentielle, donc produisant la même sortie. + +Ce chapitre présente dans un premier temps les solutions existantes au problème de la +parallélisation automatique en général. +Ensuite, notre approche sous forme de bibliothèque\footnote{\url{https://phd.pereda.fr/dev/pfor}} +est présentée par étape, en expliquant d'abord les conditions qui doivent être respectées pour +permettre la parallélisation, puis comment notre bibliothèque acquiert l'information nécessaire à +l'aide d'\gls{ET}. +Ce chapitre explique alors les différentes parties de l'\gls{EDSL} fourni pour la génération de +l'\gls{ET}, de la représentation des instructions à la caractérisation des fonctions d'indice +d'accès à des tableaux. +Nous présentons ensuite la vérification des conditions introduites au début du chapitre en +analysant, durant la compilation, les informations acquises pour grouper les instructions +interdépendantes et déterminer si elles sont parallélisables pour ensuite générer le programme dont +seules les parties qui peuvent l'être sont exécutées en parallèle. +Enfin, ce chapitre présente des mesures de performances obtenues en utilisant notre bibliothèque et +les compare à des programmes équivalents mais écrits sans l'utiliser. diff --git a/src/pfor/1_relwork.tex b/src/pfor/1_relwork.tex new file mode 100644 index 0000000..b1e46c0 --- /dev/null +++ b/src/pfor/1_relwork.tex @@ -0,0 +1,60 @@ +\section{Travaux connexes} + +La parallélisation est un sujet très étudié comme montré dans \acref{ch:par}. +La parallélisation automatique a été introduite dans \acref{sec:par/auto}. +Celle-ci présente des techniques appliquées par le matériel, ainsi que certaines pouvant être +effectuées au niveau logiciel. + +Généralement, les outils de parallélisation automatique logicielle fonctionnent directement comme +des compilateurs~\autocite{ref:blume1995,ref:fonseca2016}, générant un exécutable parallèle, ou +comme des méta-compilateurs~\autocite{ref:zima1988,ref:ahmad1997}, produisant un nouveau code source +parallèle à partir d'un code source séquentiel. +Il existe également des langages intrinsèquement +parallèles~\autocite{ref:roscoe1988,ref:loveman1993,ref:chamberlain2007} car ils incluent dans leur +définition des éléments faisant de la parallélisation automatique par le compilateur une +fonctionnalité obligatoire, à l'inverse de la majorité des langages pour lesquels cette optimisation +est un atout propre au compilateur qui la propose. +Enfin, il existe des bibliothèques logicielles~\autocite{ref:chan2004} qui procèdent à l'analyse et +à la parallélisation durant l'exécution du programme. + +Ces outils peuvent fonctionner en analysant l'\gls{ASA} qu'ils ont construit à la lecture du code +source afin de déterminer les dépendances entre les données utilisées et, lorsque les dépendances +observées le permettent, produire une implémentation parallèle~\autocite{ref:lazarescu2012}. + +Le modèle polyédral de compilation~\autocite{ref:griebl1998} est une autre méthode. +Dans ce modèle, on représente des boucles imbriquées sous la forme de polytopes sur lesquels des +optimisations peuvent être appliquées, ce qui permet la génération d'un code +parallèle~\autocite{ref:bondhugula2008}. + +Ce modèle est très utilisé en pratique car la parallélisation logicielle est fréquemment appliquée +aux boucles~\autocite{ref:collard1995,ref:artigas2000,ref:ramon-cortes2018,ref:neth2019}. +En effet, dans la plupart des programmes, la majorité du temps d'exécution est localisé dans +celles-ci, et il semble donc normal d'y consacrer beaucoup d'efforts si l'on souhaite améliorer les +performances. + +Puisqu'il est nécessaire d'analyser le programme afin de déterminer les portions qui sont +parallélisables, les outils proposés sont, comme nous l'avons vu, des compilateurs, +méta-compilateurs, des extensions pour des compilateurs existants ou encore des bibliothèques, +lesquelles agissent durant l'exécution du programme. +À part les bibliothèques, ces solutions contraignent le développeur à l'utilisation d'un +environnement de développement dédié, ce qui peut ne pas être applicable pour diverses raisons : +non implémentation des standards les plus récents du langage, incompatibilité avec certaines +extensions, ... +Quant aux bibliothèques, de par leur fonctionnement dynamique, elles induisent un surcoût en temps +d'exécution qui peut réduire l'intérêt de la parallélisation. + +La métaprogrammation template du C++, introduite dans \acref{ch:mp}, permet l'écriture de +bibliothèques dites actives. +Cela signifie qu'elles ont la possibilité d'agir, en addition de leur action dynamique, durant la +phase de compilation. +Ainsi, elles permettent l'acquisition et le traitement d'informations nécessaires à la prise de +décision souhaitée. +Il s'agit d'une utilisation standard du langage, ce qui signifie une compatibilité \textit{a priori} +indépendante des évolutions de celui-ci. +Elle est utilisée pour différents aspects de la parallélisation +automatique~\autocite{ref:falcou2008,ref:videau2018}. + +Nous proposons donc dans ce chapitre une solution de parallélisation automatique de boucles, au +niveau logiciel, à l'aide de la métaprogrammation template qui nous permet, par rapport aux +solutions évoquées ci-avant, de fournir un outil indépendant du compilateur mais dont le surcoût en +temps d'exécution, usuellement impliqué par de telles abstractions, est contrôlé et minimisé. diff --git a/src/pfor/2_conditions.tex b/src/pfor/2_conditions.tex new file mode 100644 index 0000000..4fa9765 --- /dev/null +++ b/src/pfor/2_conditions.tex @@ -0,0 +1,221 @@ +\section{Conditions pour la parallélisation de boucles} +\label{sec:pfor/conditions} + +%{{{ +Cette section présente un ensemble de conditions devant être respectées pour permettre la +parallélisation d'une boucle. +Par parallélisation d'une boucle, il est entendu que ce sont les itérations de cette boucle qui sont +exécutées en parallèle sans altérer le comportement global du programme. + +Deux types de conditions vont être abordés : celles permettant d'identifier des dépendances entre +les instructions au sein d'une même itération et celles permettant de vérifier l'indépendance des +itérations entre elles. + +Au cours de ce chapitre, nous utiliserons l'exemple de boucle présenté par +\acref{lst:pfor/conditions/loop}. +Dans cet exemple, les variables \cppinline{a}, \cppinline{b}, \cppinline{c}, \cppinline{d}, +\cppinline{e} et \cppinline{f} sont des tableaux de taille suffisante. +Cette boucle présente des particularités qui vont permettre de mettre en exergue différentes +problématiques et donc de présenter les solutions apportées par la bibliothèque introduite par ce +chapitre. + +\begin{listing} + \inputlst{conditions/loop} + {Boucle travaillant sur des tableaux} +\end{listing} +%}}} + +%{{{ +\subsection{Dépendances au sein d'une itération} +\label{subsec:pfor/conditions/iteration} + +Au sein d'une même itération la boucle n'est pas à considérer, il s'agit simplement d'une séquence +d'instructions. +Considérons deux sous-ensembles contigus $P_a$ et $P_b$ qui sont des segments de cette séquence. +Pour que ceux-ci puissent être exécutés en parallèle, aucune des instructions du segment $P_a$ +(respectivement $P_b$) ne doit dépendre d'une quelconque instruction du segment $P_b$ +(respectivement $P_a$). +Pour formaliser cette contrainte, notons $W_i$ l'ensemble des variables pour lesquelles au moins une +instruction du segment $P_i$ fait un accès en écriture et $R_i$ l'ensemble des variables auxquelles +au moins une instruction du segment $P_i$ accède en lecture. +Les conditions pour considérer les deux segments indépendants, et donc parallélisables, ont été +introduites par~\autocite{ref:bernstein1966} et peuvent être exprimées par +\acref{eq:pfor/conditions/bernstein_wbra,eq:pfor/conditions/bernstein_rbwa,eq:pfor/conditions/bernstein_wawb}. +\begin{align} + W_b \cap R_a &= \emptyset\label{eq:pfor/conditions/bernstein_wbra}\\ + R_b \cap W_a &= \emptyset\label{eq:pfor/conditions/bernstein_rbwa}\\ + W_a \cap W_b &= \emptyset\label{eq:pfor/conditions/bernstein_wawb} +\end{align} + +\Acref{eq:pfor/conditions/bernstein_wbra,eq:pfor/conditions/bernstein_rbwa} empêchent l'utilisation +d'une même variable dans les deux segments si l'un d'eux la modifie. +Cela détecte les dépendances de flux (\gls{RAW}) et les anti-dépendances (\gls{WAR}). +\Acref{eq:pfor/conditions/bernstein_wawb} empêche l'existence d'une variable qui serait modifiée par +les deux segments. +Cela détecte les dépendances d'écriture (\gls{WAW}). + +Ainsi, lorsqu'une variable est partagée par les deux segments, il est nécessaire que celle-ci +n'apparaisse que dans les ensembles $R_a$ et $R_b$ (c'est-à-dire qu'il n'y ait pour cette variable +que des accès en lecture) pour ne pas empêcher la parallélisation. + +Un segment $P = \{I_1,...,I_n\}$ peut être analysé et partitionné en $r$ sous-ensembles +d'instructions dépendantes $D_i$ tels que : +\begin{itemize} + \item $\cup_{i=1}^{r}{D_i} = P$ ; + \item $\forall i \in \llbracket 1, r\rrbracket$, il existe une dépendance, directe ou non, entre + toutes les instructions de $D_i$ ; + \item $\forall i,j \in \llbracket 1, r\rrbracket$, si $i \neq j$, $D_i$ ne possède aucune + instruction dépendante de l'une de celles de $D_j$. +\end{itemize} +\Acref{alg:pfor/conditions/groupdependent} représente une manière d'implémenter cela en utilisant +une fonction \Call{TestBernstein}{$x$, $y$} qui est vraie si et seulement si les segments $x$ et $y$ +respectent les conditions de Bernstein. + +En partant d'un ensemble vide $G$, dont l'objectif est de contenir les sous-ensembles indépendants +$D_i$, pour chaque instruction $I_k$, considérée comme un segment atomique $P_a = \{I_k\}$, le test +d'indépendance est effectué avec chacun des ensembles $P_b$ présents dans $G$. +En cas de dépendance, l'ensemble $P_b$ est retiré de $G$ et ses instructions sont ajoutées à $P_a$. +Lorsque tous les éléments de $G$ ont été testés, l'ensemble $P_a$ lui est ajouté. + +\begin{algorithm}[H] + \inputalg{conditions/groupdependent} + {Identification des groupes d'instructions dépendantes} +\end{algorithm} + +Grâce à cette identification, il est possible de séparer une boucle en de multiples boucles dont on +sait que, à l'intérieur de chacune, les instructions sont dépendantes. +\Acref{lst:pfor/conditions/loop} peut donc être transformé en \acref{lst:pfor/conditions/loop_split} +sans en changer le comportement. +En effet, les instructions aux lignes~\ref{line:pfor/conditions/loop:i1} et +\ref{line:pfor/conditions/loop:i3} sont dépendantes à cause de l'utilisation de \cppinline{b} à la +fois en lecture et écriture ; les lignes~\ref{line:pfor/conditions/loop:i2} et +\ref{line:pfor/conditions/loop:i4} sont également dépendantes entre elles à cause des variables +\cppinline{c} et \cppinline{d} ; enfin, la ligne~\ref{line:pfor/conditions/loop:i5} ne dépend +d'aucune autre. + +\begin{listing} + \inputlst{conditions/loop_split} + {Boucles séparées implémentant une boucle unique (\protect\cref{lst:pfor/conditions/loop})} +\end{listing} + +Lors de cette transformation, il est important de conserver l'ordre des instructions dépendantes (au +sein d'une même boucle), en revanche, les boucles elles-mêmes peuvent être dans un ordre quelconque. +%}}} + +%{{{ +\subsection{Dépendances entre les itérations} +\label{subsec:pfor/conditions/loop} + +Les dépendances entre les itérations empêchent une parallélisation triviale (sans avoir recours à +des transformations sur le code en entrée). +Pour les identifier, il est possible de considérer une boucle exécutant une séquence d'instructions +$n$ fois comme une séquence d'instructions composée de $n$ fois la séquence initiale. +Cela permet d'utiliser à nouveau les conditions de Bernstein pour déterminer leur indépendance. + +Nous allons considérer deux cas de variables auxquelles il peut y avoir des accès au sein d'une +boucle : les tableaux et les scalaires. +L'intégralité d'un tableau n'est pas utilisée à chaque itération d'une boucle, un indice est +généralement employé pour en atteindre une partie spécifique. +Cet indice est alors la plupart du temps fonction de l'itération courante. +Chaque cellule d'un tableau correspond donc à une variable distincte, au même titre que les +scalaires, et le tableau $a$ de dimension \num{1} sera représenté par le vecteur $a = (a_j)_{j \in +\llbracket 0, n_a-1\rrbracket}$ où chaque élément $a_j$ correspond à un élément scalaire à l'indice +$j$ du tableau. + +Ainsi, pour les scalaires, $W^s$ et $R^s$ correspondent aux ensembles des variables auxquelles on +accède, respectivement, en écriture et en lecture. +Dans le cas des tableaux, les ensembles correspondants sont plus complexes. +Pour tout tableau $a$, $F_a$ est l'ensemble des fonctions $f: \mathbb{N} \to \mathbb{N}$ utilisées +pour calculer l'indice d'accès à l'élément du tableau en fonction de l'indice de l'itération. +Cet ensemble $F_a$ se décompose en un ensemble $F^w_a$ pour les accès en écriture et $F^r_a$ pour +les accès en lecture. +Par exemple, pour le tableau \cppinline{c} de \acref{lst:pfor/conditions/loop}, on a $F^w_c = \{i +\mapsto i\}$ et $F^r_c = \{i \mapsto i, i \mapsto i+1\}$. +À partir de ces fonctions, il est possible de construire les ensembles des éléments des tableaux +auxquels accède l'itération $i$ en écriture, $W^t_i$ (\cref{eq:pfor/conditions/wti}), et en +lecture, $R^t_i$ (\cref{eq:pfor/conditions/rti}). +Les ensembles $W^t$ et $R^t$ contiennent les tableaux dont au moins un élément est utilisé +respectivement en écriture ou en lecture. +\begin{align} + W^t_i &= \bigcup\limits_{a \in W^t} \bigcup\limits_{f \in F^w_a} a_{f(i)} + \label{eq:pfor/conditions/wti}\\ + R^t_i &= \bigcup\limits_{a \in R^t} \bigcup\limits_{f \in F^r_a} a_{f(i)} + \label{eq:pfor/conditions/rti} +\end{align} + +Il est ensuite possible de définir les ensembles $W_i$ (\cref{eq:pfor/conditions/wi}) et $R_i$ +(\cref{eq:pfor/conditions/ri}). +Le premier comporte toutes les variables (scalaires ou éléments d'un tableau) pour lesquelles il y a +un accès en écriture, le second en lecture, durant l'itération $i$. +\begin{align} + W_i &= W^s \cup W^t_i\label{eq:pfor/conditions/wi}\\ + R_i &= R^s \cup R^t_i\label{eq:pfor/conditions/ri} +\end{align} + +L'adaptation des conditions de Bernstein, en utilisant ces nouvelles variables, à l'identification +des dépendances entre plusieurs itérations correspond aux +\cref{eq:pfor/conditions/bernstein_loop_wirj,eq:pfor/conditions/bernstein_loop_riwj,eq:pfor/conditions/bernstein_loop_wi}. +\begin{align} + \forall i \in \llbracket 1, k\rrbracket, \quad W_i \cap (\bigcup\limits_{j \neq i} R_j) &= \emptyset + \label{eq:pfor/conditions/bernstein_loop_wirj}\\ + \forall i \in \llbracket 1, k\rrbracket, \quad R_i \cap (\bigcup\limits_{j \neq i} W_j) &= \emptyset + \label{eq:pfor/conditions/bernstein_loop_riwj}\\ + \bigcap\limits_{i = 1}^{k} W_i &= \emptyset + \label{eq:pfor/conditions/bernstein_loop_wi} +\end{align} + +Pour le cas des scalaires, les contraintes peuvent être traduites ainsi : l'accès en lecture ne +présente pas de problème, en revanche, l'accès en écriture va systématiquement empêcher la +parallélisation dès lors que la boucle possède au moins 2 itérations (non-respect de +\acref{eq:pfor/conditions/bernstein_loop_wi} puisque tout $W_i$ contient la variable). + +Il est possible de transformer +\acref{eq:pfor/conditions/bernstein_loop_wirj,eq:pfor/conditions/bernstein_loop_riwj,eq:pfor/conditions/bernstein_loop_wi} +afin d'obtenir +\acref{eq:pfor/conditions/bernstein_loop_wirjp,eq:pfor/conditions/bernstein_loop_riwjp,eq:pfor/conditions/bernstein_loop_wip} +dans le cas des tableaux. +\begin{align} + \forall a \in W^t \cap R^t, \quad + \forall i \in \llbracket 1, k\rrbracket, \quad &\nexists f \in F^w_a \;/\; + \exists j \neq i, g \in F^r_a, f(i) = g(j) + \tag{\ref{eq:pfor/conditions/bernstein_loop_wirj}$'$} + \label{eq:pfor/conditions/bernstein_loop_wirjp}\\ + \forall a \in W^t \cap R^t, \quad + \forall i \in \llbracket 1, k\rrbracket, \quad &\nexists f \in F^r_a \;/\; + \exists j \neq i, g \in F^w_a, f(i) = g(j) + \tag{\ref{eq:pfor/conditions/bernstein_loop_riwj}$'$} + \label{eq:pfor/conditions/bernstein_loop_riwjp}\\ + \forall a \in W^t, \quad + \forall i \in \llbracket 1, k\rrbracket, \quad &\nexists f \in F^w_a \;/\; + \exists j \neq i, g \in F^w_a, f(i) = g(j) + \tag{\ref{eq:pfor/conditions/bernstein_loop_wi}$'$} + \label{eq:pfor/conditions/bernstein_loop_wip} +\end{align} + +\Acref{eq:pfor/conditions/bernstein_loop_wip} est respectée à condition que l'accès à un même indice +d'un même tableau en écriture ne se fasse jamais dans deux itérations distinctes. +\Acref{eq:pfor/conditions/bernstein_loop_wirjp,eq:pfor/conditions/bernstein_loop_riwjp} empêchent +l'accès en lecture à un indice donné dans un tableau durant une itération si cet élément est utilisé +en écriture durant une autre itération. + +Appliqué à \acref{lst:pfor/conditions/loop_split}, ces équations permettent de déterminer que des +trois boucles, seule la deuxième ne peut être parallélisée. +Cela est dû à l'instruction accédant au tableau \cppinline{c} à la fois à l'indice \cppinline{i} en +écriture et \cppinline{i+1} en lecture, ne respectant donc pas +\acref{eq:pfor/conditions/bernstein_loop_wirjp,eq:pfor/conditions/bernstein_loop_riwjp}. + +Il s'agit alors de produire deux boucles : l'une parallélisée comprenant les instructions de la +première et de la dernière ; l'autre exécutée de manière séquentielle correspondant à la deuxième. +\Acref{lst:pfor/conditions/loop_par} présente un possible regroupement des boucles, l'ordre des +segments d'instructions issus de chaque boucle n'ayant aucune importance au sein de la boucle +résultante. +La réunion des boucles en fonction de leur capacité à être parallélisées est cruciale pour les +performances : sans cela, le nombre total d'itérations est augmenté. +Si toutes les boucles intermédiaires partagent la propriété d'être, ou non, parallélisables, alors +une seule boucle doit être produite pour la même raison. + +\begin{listing} + \inputlst{conditions/loop_par} + {Boucles groupées en fonction de leur capacité à être exécutée en parallèle (depuis \acref{lst:pfor/conditions/loop_split})} +\end{listing} +%}}} diff --git a/src/pfor/3_detection.tex b/src/pfor/3_detection.tex new file mode 100644 index 0000000..ee2d5cb --- /dev/null +++ b/src/pfor/3_detection.tex @@ -0,0 +1,440 @@ +\section{Analyse lexicale et syntaxique par métaprogrammation} +\label{sec:pfor/detection} + +%{{{ +Il existe plusieurs manières d'obtenir les informations nécessaires à la vérification des +conditions énoncées dans la section précédente. +Principalement, il est possible d'agir au niveau du compilateur (directement ou au moyen d'une +extension) ou durant l'exécution du programme. +La métaprogrammation template est une solution intermédiaire qui permet à une bibliothèque (écrite +dans le langage du développeur) d'agir sur le déroulement de la compilation. + +Notre objectif est de représenter durant la compilation l'\gls{ASA} (\gls{AST} en anglais) +exécuté par la boucle d'origine. +Les patrons d'expression~\autocite{ref:veldhuizen1995} (voir \acref{subsec:mp/mpt/et}) sont adaptés +puisqu'ils permettent précisément cela. +Grâce à ceux-ci et à la surcharge des opérateurs permise en C++, nous pouvons fournir un \gls{EDSL} +dont l'utilisation construit un \gls{ASA} pouvant être analysé durant la compilation par +métaprogrammation template. + +Cette section traite de deux \gls{ASA} distincts dont nous nous servons : dans un premier temps +celui qui représente l'expression des instructions qui doivent être exécutées et dans un second +temps celui qui encode les fonctions d'indice utilisées pour accéder aux éléments des tableaux +auxquels les instructions recourent. +%}}} + +%{{{ +\subsection{Représentation d'une expression} +\label{subsec:pfor/detection/expr} + +L'\gls{ASA} d'une expression globale est construit avec des nœuds internes représentant des +opérations et des feuilles représentant leurs opérandes. +Au sein du code, il va donc s'agir d'utiliser la notion de patron d'expression (voir +\acref{subsec:mp/mpt/et}). +Pour identifier les feuilles de cet arbre (et ne pas appliquer les opérateurs spécifiques en dehors +de notre cas d'utilisation), un type spécifique contenant la variable effective est employé. +\Acref{lst:pfor/detection/operand} montre la définition d'une variable de ce type. +En réalité, il s'agit d'un template, et ce pour deux raisons. +La première est qu'il ne dépend pas du type contenu, à l'instar de ce que font les conteneurs de la +bibliothèque standard (par exemple \cppinline{std::vector}), expliquant la présence de +\cppinline{int*} en premier argument template (celui-ci pourrait être automatiquement déduit en +utilisant le \gls{CTAD}, voir \acref{sec:gnx/tad}). +Le second argument permet d'identifier l'opérande durant la compilation. +En effet, distinguer une variable d'une autre peut être accompli durant l'exécution, en comparant +leur adresse, mais pas durant la compilation : il faut donc ajouter une information permettant +cette disjonction. +Plusieurs techniques permettent d'automatiser partiellement la génération d'un identifiant différent +pour chaque opérande, cependant aucune n'est parfaitement fiable dans tous les contextes, aussi +notre bibliothèque n'en intègre pas par défaut. + +\begin{listing} + \inputlst{detection/operand} + {Définition d'un opérande de patron d'expression} +\end{listing} + +Un problème qui peut survenir est l'\en{aliasing}~\autocite{ref:landi1991} : une même zone mémoire +accessible à partir d'au moins deux noms. +Celui-ci existe également dans le cadre des patrons d'expression~\autocite{ref:hardtlein2005}. +Dans \acref{lst:pfor/detection/aliasing}, les opérandes \cppinline{a} et \cppinline{b} peuvent +éventuellement adresser une même portion de mémoire en fonction des indices utilisés. +\begin{listing}[b] + \inputlst{detection/aliasing} + {Définition d'opérandes avec \en{aliasing}} +\end{listing} +En effet, le premier élément de \cppinline{b} est à la même adresse que le sixième élément de +\cppinline{a}, or les deux opérandes possèdent un identifiant différent, faisant d'eux des +variables distinctes du point de vue de nos patrons d'expression. +À l'inverse, les opérandes \cppinline{c} et \cppinline{d} qui partagent exactement la même section +de mémoire (représentée par \cppinline{cData}) ne créent pas de problème puisqu'ils ont un +identifiant commun, faisant d'eux une seule et même variable au sein d'une expression malgré leur +distinction en C++. +À noter qu'un problème ne survient que lorsque deux opérandes ayant ce souci d'\en{aliasing} sont +utilisés dans une même expression. + +Ainsi que nous l'avons déjà dit, l'adresse en mémoire d'une variable ne peut être connue que durant +l'exécution du programme. +La détection d'un problème d'\en{aliasing} ne peut donc pas être effectuée durant la compilation. +De plus, pour y parvenir durant l'exécution, une information supplémentaire est nécessaire : +le domaine d'utilisation des données qui sont représentées par un opérande. +En effet, si l'opérande \cppinline{a} n'accède qu'aux \num{5} premiers éléments de son tableau +sous-jacent, il ne peut y avoir d'\en{aliasing} avec \cppinline{b} (dès lors que des indices +négatifs ne sont pas utilisés). + +Une bonne pratique reste néanmoins d'éviter l'utilisation de tableaux partiels et de recourir à +\cppinline{std::array} au lieu des tableaux et pointeurs \og nus \fg. +Ces derniers sont utilisés dans l'unique but de rendre les exemples plus compacts. + +L'utilisation des opérandes est simplifiée par la surcharge des opérateurs pour générer des +expressions. +Par exemple, dans \acref{lst:pfor/detection/expression} l'expression \cppinline{a + b}, stockée dans +\cppinline{e}, fait appel à une surcharge de \cppinline{operator+} qui produit une instance d'un +type représentant l'arbre de \acref{fig:pfor/detection/expression}. +Ce type est une instanciation d'un template avec pour arguments un type identifiant l'opération +effectuée (\cppinline{+}) et les deux types des opérandes. +Cette instance comporte également les opérandes elles-mêmes afin de les utiliser ultérieurement. + +\begin{listing} + \inputlst{detection/expression} + {Expression créée par la surcharge de l'\cppinline{operator+}} +\end{listing} + +\begin{figure} + \inputfig{detection/expression} + {\Acrshort{ASA} représenté par l'expression de \acref{lst:pfor/detection/expression}} +\end{figure} + +Une instruction C++ est typiquement terminée par un point-virgule. +Ceci n'est pas un opérateur et ne peut donc pas être surchargé, c'est pourquoi, afin de permettre la +capture d'une expression composée de multiples instructions, nous avons utilisé à la place +l'opérateur virgule (\cppinline{operator,}). +La virgule est utilisée de plusieurs manières dans le langage selon le contexte : comme opérateur ou +comme élément lexical de ponctuation. +Pour s'assurer que la virgule sera utilisée comme opérateur, il est possible de placer l'expression +dans une paire de parenthèses. +Par exemple, dans \acref{lst:pfor/detection/multiinstr}, \cppinline{e} est initialisée par une +expression utilisant cet opérateur virgule. +Dans ce contexte, l'absence de parenthèses aurait conduit à la tentative de définition de trois +variables (\cppinline{e}, \cppinline{d} et \cppinline{b}). + +\begin{listing} + \inputlst{detection/multiinstr} + {Expression comportant plusieurs instructions séparées par des virgules} +\end{listing} + +Cette expression est représentée par l'arbre de \acref{fig:pfor/detection/multiinstr}. +L'\cppinline{operator,} est utilisé pour accumuler \num{3} instructions. +On notera l'utilisation d'arguments \og bruts \fg{} qui ne sont pas explicitement des opérandes de +patron d'expression : \cppinline{1} et \cppinline{2}. +Deux cas doivent être distingués quant à l'utilisation de tels arguments : opérateur unaire et +opérateur binaire ; les opérateurs pouvant être surchargés en C++ à ce jour étant tous de l'une de +ces deux arités. +Si un argument brut est utilisé avec un opérateur unaire, ce dernier sera appliqué directement, +résultant en une nouvelle donnée qui n'est pas, elle non plus, un opérande de patron d'expression. +Si un argument brut est utilisé avec un opérateur binaire, pour que cela forme une expression, +l'autre argument de cet opérateur doit être un opérande de patron d'expression. + +\begin{figure}[H] + \inputfig{detection/multiinstr} + {\Acrshort{ASA} représenté par l'expression de \acref{lst:pfor/detection/multiinstr}} +\end{figure} +%}}} + +%{{{ +\subsection{Représentation des fonctions d'indice} +\label{subsec:pfor/detection/indices} + +Jusqu'à présent, les expressions, bien qu'utilisant des opérandes représentant des tableaux, +n'utilisent pas l'opérateur d'accès à un élément (\cppinline{operator[]}) : cela revient à utiliser +implicitement $i \mapsto i$ comme fonction d'indice. +Ainsi, dans les extraits présentés jusqu'ici, chaque accès aux opérandes \cppinline{a}, +\cppinline{b}, \cppinline{c} et \cppinline{d} étaient équivalents à \cppinline{a[i]}, +\cppinline{b[i]}, \cppinline{c[i]} et \cppinline{d[i]} respectivement. +Rendre cette information explicite permet éventuellement d'améliorer la lisibilité du code, mais +cela permet surtout de définir une fonction différente. +Dans \acref{lst:pfor/detection/indices}, des indices explicites sont utilisés grâce au type +\cppinline{Index}. +La représentation des fonctions d'indice est acquise au moyen d'un patron d'expression annexe à +celui construit jusqu'ici (qui permet la représentation des instructions). +Ce patron d'expression supporte un sous-ensemble d'opérations spécifiques, propres aux fonctions +d'indice. + +\begin{listing}[t] + \inputlst{detection/indices} + {Expression avec des indices explicites} +\end{listing} + +Afin de permettre l'analyse des expressions d'indice, les constantes numériques les composant +doivent être connues durant la compilation. +Au moment de la rédaction, la seule syntaxe permise par le langage pour atteindre cet objectif dans +ce contexte est d'utiliser ces constantes numériques comme arguments d'un template. +C'est à cela que sert le template \cppinline{ctv} (pour \en{compile time value}) qui correspond à +une variable utilisable dans une expression d'indice et dont le type comporte l'information utile. +L'utilisation de fonctions (dans ce cas précis, d'opérateurs) \cppinline{constexpr} ne suffit pas. +Une telle fonction peut s'exécuter aussi bien durant la compilation que durant l'exécution du +programme et ses paramètres sont donc considérés comme inconnus durant la compilation. + +De nouveaux éléments arrivant en C++ (standard C++20) auraient pu apporter une solution : +\cppinline{consteval} et \cppinline{std::is_constant_evaluated}~\autocite{ref:smith2018a}. +Le premier, \cppinline{consteval}, bien que correspondant à une fonction ne pouvant être exécutée +que durant la compilation du programme, ne permet pas l'utilisation de ses paramètres comme +arguments d'un template. +Il est possible que cette fonctionnalité fasse partie d'une future révision du standard. +Le second, \cppinline{std::is_constant_evaluated}, également apparu en C++20, correspond à une +fonction, dont l'implémentation est spécifique au compilateur, déterminant si l'exécution en cours +est effectuée par le compilateur ou non. +Elle peut s'utiliser avec un \cppinline{if constexpr} pour distinguer les deux cas possibles d'une +fonction \cppinline{constexpr}. +Cependant, cela ne permet pas non plus l'utilisation des paramètres de la fonction comme arguments +d'un template. +\autocite{ref:revzin2020} fait par ailleurs remarquer que même les fonctions \cppinline{consteval} +ne sont alors pas permises et introduit le \cppinline{if consteval}. +Le cas des templates n'est en revanche pas abordé dans ce document (révision \num{2}). + +\Acref{fig:pfor/detection/indices} correspond à ce qui est représenté par cette nouvelle expression. +\begin{figure}[!b] + \inputfig{detection/indices} + {\Acrshort{ASA} représenté par l'expression de \acref{lst:pfor/detection/indices}} +\end{figure} +Cet arbre inclut plusieurs patrons d'expression : un pour chaque occurrence de l'opérateur d'accès à +un élément d'un tableau (\cppinline{operator[]}) en plus de celui encodant l'expression globale. +Toutes ces informations permettent de connaître durant la compilation à la fois quelles variables +sont utilisées et comment elles le sont. + +Par exemple, un opérateur tel que celui effectuant une addition, ou celui effectuant une +multiplication, ne modifie pas ses opérandes : celles-ci sont seulement lues. +En revanche, l'opérande à gauche d'un opérateur d'affectation peut être modifié : il s'agit d'un +accès en écriture. +Ainsi, en parcourant cet arbre, il est possible de déduire $F_a = \{i \mapsto i\}$ donc $|F_a| = 1$, +$F_b = \{i \mapsto i\}$ donc $|F_b| = 1$ et $F_c = \{i \mapsto i, i \mapsto i+1\}$ donc $|F_c| = 2$. +Plus précisément, $F^w_c = \{i \mapsto i\}$ et $F^r_c = \{i \mapsto i+1\}$, ce qui invalide la +condition de \acref{eq:pfor/conditions/bernstein_loop_wirjp} puisqu'il existe un élément de $F^w_c$ +($i \mapsto i$) et un élément de $F^r_c$ ($i \mapsto i+1$) tels qu'ils produisent la même sortie +pour deux entrées différentes (par exemple les entrées $2$ et $1$, dans cet ordre, pour lesquelles +ces fonctions retournent toutes les deux $2$). +Cette information permet de déterminer que l'utilisation faite de la variable \cppinline{c} empêche +la parallélisation de ces instructions. +%}}} + +%{{{ +\subsection{Propriétés des expressions d'indice} +\label{subsec:pfor/detection/properties} + +Les expressions d'indice peuvent être marquées d'un ensemble de propriétés afin d'aider le système à +prendre des décisions. +Nous présenterons comment ces propriétés peuvent être utilisées dans la section suivante, en +particulier pour \og injective \fg{} et \og affine \fg{}. + +Les propriétés peuvent être ajoutées sur une expression d'indice au moyen de fonctions dédiées à +celles-ci. +Il est par exemple possible de marquer une expression d'indice de la propriété \og injective \fg{} +(qui indique que la fonction d'indice représentée est déclarée injective par l'utilisateur pour les +valeurs possibles en entrée) comme le montre \acref{lst:pfor/detection/propinjective}. + +\begin{listing} + \inputlst{detection/propinjective} + {Application de la propriété d'injectivité sur une expression d'indice} +\end{listing} + +Lorsque des propriétés sont présentes sur une expression utilisée comme opérande d'une fonction (par +exemple dans \cppinline{a + b} où \cppinline{a} et \cppinline{b} sont des expressions), la +bibliothèque permet de calculer les propriétés de l'expression résultante. +Le calcul considère les propriétés de chaque opérande et l'action que représente la fonction +appliquée afin de générer de nouvelles propriétés. +Ce mécanisme correspond à un ensemble de spécialisations d'un template. + +Pour illustrer cela, considérons deux expressions respectivement marquées strictement croissante +(par la fonction \cppinline{strictinc}) et strictement décroissante (\cppinline{strictdec}). +Ces propriétés correspondent en pratique à trois propriétés : \og monotone \fg, \og stricte \fg{} et +respectivement \og croissante \fg{} et \og décroissante \fg. +Lorsque l'on calcule la différence de ces deux expressions, l'expression produite conserve les +propriétés \og monotone \fg{} et \og stricte \fg{}, et obtient \og croissante \fg{} ou \og +décroissante \fg{} selon l'ordre des opérandes. +\Acref{lst:pfor/detection/calcstrictinc} présente la construction d'une expression dont les +opérandes sont marquées comme décrit ci-avant et dont l'expression résultante possède +automatiquement les propriétés l'indiquant comme strictement croissante. + +\begin{listing}[b] + \inputlst{detection/calcstrictinc} + {Expression ayant la propriété d'être strictement croissante déduite des propriétés de ses + opérandes} +\end{listing} + +En plus des propriétés ainsi acquises, une expression peut automatiquement obtenir des propriétés +inférées à partir de celles qu'elle possède. +À l'instar des propriétés calculées, les propriétés inférées sont produites par des fonctions +définies par spécialisation d'un template. +Celles-ci produisent un ensemble de nouvelles propriétés sachant un ensemble initial de propriétés. +Par exemple, lorsque la propriété \og monotone \fg{} est présente, s'il y a aussi \og stricte \fg{}, +\og injective \fg{} est déduite. +Ainsi, l'expression \cppinline{e} de \acref{lst:pfor/detection/calcstrictinc} possède également +cette dernière propriété. + +L'\gls{ASA} construit pour les indices est plus contraint que celui qui représente les +instructions dans leur ensemble. +Les opérations permises sont moins nombreuses (on ne calculera pas la factorielle d'un indice, par +exemple) et n'opèrent que sur des entiers relatifs. +Les constantes sont toutes, elles aussi, entières, mais sont surtout connues durant la compilation. +Grâce à ces contraintes, il est possible de procéder à certaines transformations sur ces arbres. + +Le plus souvent, l'indice d'accès à un élément d'un tableau est une fonction affine de l'indice +courant de la boucle, donc de la forme $i \mapsto a\,i + b$, où $a, b, i \in \mathbb{Z}$. +\Acref{fig:pfor/detection/lineartrees} montre les formes d'arbres considérées comme affines +(c'est-à-dire que l'expression représentée est une fonction affine de la variable de boucle). +Elles correspondent aux cas suivants : $a = 0$ ; $a = 1$ et $b = 0$ ; $a$ quelconque et $b = 0$ ; +$a = 1$ et $b$ quelconque ; et enfin le cas général. +Dans tous ces cas, la propriété \og affine \fg{} est automatiquement ajoutée à une expression sans +nécessiter un marquage explicite par le développeur. +Cette propriété implique par ailleurs l'injectivité de la fonction. + +Un problème de détection survient si le développeur écrit une expression complexe qui aurait pu être +simplifiée en une expression trivialement affine, par exemple $i \mapsto 2 \times (i + 1)$ dont +l'\gls{ASA} ne fait pas partie des cas identifiés mais qui pourrait être transformée en $i \mapsto +2\,i + 2$. +Cette remarque peut être étendue à des cas plus complexes tels que $i \mapsto (2\,i + 5) + 2 \times +(i - 2)$ qui correspond plus simplement à $i \mapsto 4\,i + 1$. + +Transformer une expression complète en une version simplifiée et reconnaissable suivant les motifs +de \acref{fig:pfor/detection/lineartrees} est une opération difficile. +Cependant, la construction d'une expression est effectuée par étape : les opérateurs sont traités +selon un ordre dépendant de l'associativité et de la priorité de chacun. +En conséquence, il suffit de maintenir, tant que l'expression est affine, une écriture simplifiée +tout au long de la procédure. + +Prenons en exemple la première fonction introduite ci-avant, $i \mapsto 2 \times (i + 1)$ : la +première étape est la construction de l'arbre représentant $i+1$. +Lorsque la multiplication intervient, les deux opérandes sont des expressions affines : on peut +obtenir par \en{type traits} la valeur de leur $a$ et $b$ respectifs (ici \num{0} et \num{2} à +gauche et \num{1} et \num{1} à droite). +Il s'agit dans ce cas d'une multiplication : pour que le résultat soit une expression affine, il +faut qu'au moins l'un des deux $a$ des expressions opérandes soit nul. +C'est validé ici, et les valeurs de $a$ et $b$ de l'expression résultante peuvent être calculées +facilement. +À partir de celles-ci, il est possible de générer un nouvel arbre représentant l'expression affine. +En fonction des valeurs spécifiques de $a$ et $b$, le motif le plus adapté est choisi parmi ceux de +\acref{fig:pfor/detection/lineartrees}. + +Le comportement primitif des patrons d'expression fait qu'une expression comme $(a\,i + b) + (c\,i + +d)$ produit l'arbre à droite dans \acref{fig:pfor/detection/linearnoopti}. +Grâce à ce système, l'arbre est produit en suivant la règle représentée par +\acref{fig:pfor/detection/linearadd}. +\Acref{fig:pfor/detection/linearmul} schématise la règle utilisée pour la transformation dans le cas +de la multiplication par un scalaire. + +\begin{figure} + \inputfig{detection/lineartrees} + {\Acrshort{ASA} des expressions reconnues comme affines} +\end{figure} + +\begin{figure} + \inputfig{detection/linearnoopti} + {Addition non optimisée de deux \glsxtrshort{ASA} d'expressions affines} +\end{figure} + +\begin{figure} + \inputfig{detection/linearadd} + {Addition optimisée de deux \glsxtrshort{ASA} d'expressions affines} +\end{figure} + +\begin{figure} + \inputfig{detection/linearmul} + {Multiplication optimisée de deux \glsxtrshort{ASA} d'expressions affines} +\end{figure} +%}}} + +%{{{ +\subsection{Intégration des fonctions} + +Le patron d'expression est initialement conçu au sein de la bibliothèque pour prendre en compte les +opérateurs fournis par le langage. +En effet, les opérateurs (de surcroît ceux qui peuvent être surchargés) sont en nombre fini : il est +possible de tous les considérer. +En revanche, ce n'est pas le cas lorsque l'on veut généraliser aux fonctions quelconques. + +Or, un utilisateur peut souhaiter utiliser de telles fonctions au sein d'une expression, comme c'est +le cas dans \acref{lst:pfor/conditions/loop} avec \cppinline{std::pow}. +Cette fonction ne peut pas être utilisée directement au sein d'une expression : les opérandes qui +lui seraient transmises sont de types non numériques (puisque ce sont des opérandes de patron +d'expression) et cette fonction n'est pas capable de retourner un type exprimant un \gls{ASA}. + +Il est donc nécessaire d'écrire une nouvelle fonction dont les paramètres et le type de retour sont +compatibles avec le patron d'expression, et fournissant un mécanisme d'évaluation utilisant la +fonction désirée. +En se référant à \acref{subsubsec:mp/et/edsl} pour définir la fonction utilisable dans l'expression +et à \acref{subsubsec:mp/et/genarity} pour la définition du type qui implémente l'appel effectif à +la fonction cible, un développeur peut implémenter le nécessaire pour chaque nouvelle fonction. + +Afin de simplifier cette procédure, la bibliothèque fournit un outil pour générer automatiquement ce +qui est nécessaire et retourner un +fonctionoïde\footnote{\url{https://isocpp.org/wiki/faq/pointers-to-members\#functionoids}} pouvant +être utilisé dans une expression. +Celui-ci repose sur une classe générique permettant de gérer l'appel sous-jacent à n'importe quel +type de fonction d'arité quelconque. + +Cette classe (\cref{lst:pfor/detection/useroperator}) doit conserver la fonction à appeler, +contrairement à ce qui est habituellement fait avec les opérateurs pour lesquels chaque +évaluateur connaît directement la fonction à exécuter. +Les deux fonctions d'évaluation attendues (l'une sans indice, l'autre avec) sont implémentées en +transmettant automatiquement tous les arguments à la fonction les évaluant pour utiliser leur valeur +effective comme argument (voir \acref{sec:gnx/forward} sur la transmission parfaite). + +Afin d'éviter une éventuelle ambiguïté, la fonction d'évaluation qui n'accepte pas comme premier +paramètre un entier s'assure d'être considérée pour résoudre l'appel uniquement lorsque ses +arguments sont des expressions. +Pour cela, la \gls{SFINAE} est utilisée à l'aide de \cppinline{std::enable_if_t} (voir +\acref{sec:gnx/sfinae}). + +\begin{listing} + \inputlst{detection/useroperator} + {Évaluateur générique pour les extensions par l'utilisateur} +\end{listing} + +\Acref{lst:pfor/detection/makeoperator} montre la fonction produisant une nouvelle fonction, +compatible avec les expressions de la bibliothèque, elle-même retournant une expression dont +l'opérateur est une instance de l'évaluateur générique présenté ci-avant. + +Il n'est pas nécessaire de s'assurer au préalable que les paramètres de la fonction générée sont +effectivement des expressions : elle est conçue pour être spécifiquement utilisée dans ce contexte, +contrairement aux opérateurs. +En revanche, si un argument n'est pas une expression, il faut le transformer. +Le template \cppinline{Expression} est donc instancié avec les types des arguments transformés en +expression si nécessaire par \cppinline{AsExpression}. + +\begin{listing} + \inputlst{detection/makeoperator} + {Fonction générant une fonction compatible avec les expressions à partir d'une fonction + quelconque} +\end{listing} + +Cet outil permet de créer aisément de nouvelles fonctions comme présenté dans +\acref{lst:pfor/detection/userops}. +La première indique que l'on crée localement \cppinline{factorial} à partir de la fonction portant +le même nom (mais issue de l'espace de noms global pour éviter la confusion, d'où le préfixe +\cppinline{::}). +La deuxième procède à partir d'une fonction lambda. +Enfin, la troisième montre comment faire lorsqu'il existe des surcharges : il est nécessaire +d'effectuer une conversion de type explicite, normalement dénotée par un \cppinline{static_cast} +dans ce cas. +Puisque le premier paramètre template de la fonction \cppinline{makeOperator} est le type de la +fonction acceptée en argument, il est possible de le spécifier afin de simplifier l'écriture de la +conversion. + +\begin{listing} + \inputlst{detection/userops} + {Génération de fonctions utilisables dans une expression} +\end{listing} + +Si l'on reprend le code introduit au début de ce chapitre (\cref{lst:pfor/conditions/loop}), on peut +donc écrire les instructions du corps de la boucle comme dans +\acref{lst:pfor/detection/loopstmtexpr}, en utilisant cette fonction générée \cppinline{pow}. + +\begin{listing}[t] + \inputlst{detection/loopstmtexpr} + {Expression des instructions du corps de la boucle de \acref{lst:pfor/conditions/loop}} +\end{listing} + +À noter que la ligne~\ref{line:pfor/detection/loopstmtexpr:i3} est permise grâce à une +transformation automatique d'une expression d'indice en expression générale. +Dans certains cas, il peut être utile de forcer cette encapsulation (une fonction est fournie pour +cet usage) par exemple pour permettre l'utilisation de valeurs non connues à la compilation avec un +indice (comme dans \cppinline{i*2}, qui nécessite donc l'encapsulation \cppinline{xpr(i)*2}). +%}}} diff --git a/src/pfor/4_generation.tex b/src/pfor/4_generation.tex new file mode 100644 index 0000000..8a97aa8 --- /dev/null +++ b/src/pfor/4_generation.tex @@ -0,0 +1,456 @@ +\section{Analyse sémantique et génération du programme} +\label{sec:pfor/gen} + +%{{{ +L'objectif est de générer un programme correct à partir du patron d'expression en entrée consistant +en l'encodage d'une boucle exécutant une série d'instructions, le patron d'expression étant tel que +décrit dans la section précédente. +Le programme correct consiste, au maximum, en deux boucles : une dont l'exécution doit être +séquentielle, l'autre dont l'exécution peut être parallèle. +Pour cela, il s'agit dans un premier temps de regrouper les instructions interdépendantes d'une même +itération. +Il est alors possible de tester, pour chaque groupe d'instructions, s'il est possible de procéder à +une exécution parallèle. +Enfin, il faut générer la ou les boucles en conséquence. +Cette section présente ces trois étapes principales dans cet ordre. +%}}} + +%{{{ +\subsection{Groupement des instructions} +\label{subsec:pfor/gen/cluster} + +Nous appliquons au cours de cette section les conditions de Bernstein présentées dans +\acref{subsec:pfor/conditions/iteration} afin de créer des ensembles d'instructions dépendantes tels +que ces ensembles sont indépendants entre eux. +Pour rappel, l'information dont nous disposons est un patron d'expression qui liste les instructions +qu'exécutera la boucle, obtenu comme expliqué dans \acref{sec:pfor/detection}. + +Ce patron d'expression est traité comme une liste d'expressions (les arbres fils de la racine +représentée par l'opérateur virgule, à défaut de cette racine, l'expression complète est la seule de +la liste). +Une première étape consiste en la création, à partir de cette liste d'expressions, d'une liste +d'informations les concernant : le mode d'accès à chaque variable que l'expression emploie. + +Une première métafonction prend donc en entrée une liste d'expressions pour produire une liste de +triplets de la forme $(id_e, W, R)$ où $id_e$ est l'identifiant de l'expression, et où $W$ et $R$ +sont des n-uplets contenant respectivement les variables auxquelles on accède en écriture et celles +auxquelles on accède en lecture. +Par exemple, le résultat obtenu à partir de l'expression définie dans +\acref{lst:pfor/detection/loopstmtexpr} est représenté par \acref{eq:pfor/generation/exprinfo}. +\begin{equation} + \begin{aligned} + \{\quad\\ + &(0, (a), (a, b)),\\ + &(1, (c), (c, d)),\\ + &(2, (b), (b)),\\ + &(3, (d), (c, e)),\\ + &(4, (f), (f))\\ + \}\quad + \end{aligned} + \label{eq:pfor/generation/exprinfo} +\end{equation} + +Sur cette information, il est possible d'appliquer \acref{alg:pfor/conditions/groupdependent} +présenté dans \acref{subsec:pfor/conditions/iteration}. +Pour chaque instruction, le test d'indépendance est effectué avec les ensembles déjà construits en +utilisant les n-uplets $W$ et $R$. + +Le résultat de cette opération est l'ensemble des groupes d'instructions (c'est-à-dire des +ensembles) interdépendantes représentées par leur triplet $(id_e, W, R)$. +Ainsi, pour l'exemple de \acref{eq:pfor/generation/exprinfo}, nous avons l'ensemble de +\acref{eq:pfor/generation/exprclusters}. +\begin{equation} + \begin{aligned} + \{\quad\\ + &\{(0, (a), (a, b)), (2, (b), (b))\},\\ + &\{(1, (c), (c, d)), (3, (d), (c, e))\},\\ + &\{(4, (f), (f))\}\\ + \}\quad + \end{aligned} + \label{eq:pfor/generation/exprclusters} +\end{equation} + +Les informations $W$ et $R$ étant alors inutiles, une métafonction permet de construire l'ensemble +ne comportant que les identifiants $id_e$ des expressions qui seront ensuite utilisés pour +reconstruire un arbre lorsque la possibilité de paralléliser chaque groupe aura été déterminée. +Ce que retourne cette dernière métafonction pour l'exemple de +\acref{eq:pfor/generation/exprclusters} est présenté dans \acref{eq:pfor/generation/expridclusters}. +\begin{equation} + \begin{aligned} + \{\quad\\ + &\{0, 2\},\\ + &\{1, 3\},\\ + &\{4\}\\ + \}\quad + \end{aligned} + \label{eq:pfor/generation/expridclusters} +\end{equation} +%}}} + +%{{{ +\subsection{Test de parallélisabilité} +\label{subsec:pfor/gen/test} + +Dans cette section, nous présentons l'application des conditions présentées dans +\acref{subsec:pfor/conditions/loop}. +Il faut pour cela associer à chaque opération un mode d'accès pour chacun des opérandes. +Le système est par conception extensible : les opérateurs fournis par la bibliothèque permettent un +ensemble de constructions possibles, mais un développeur peut fournir des fonctions supplémentaires. +Par exemple, il est possible de créer la fonction \cppinline{sin} permettant la création d'un nœud +dans l'\gls{ASA} correspondant à une fonction unaire, dont l'exécution sera de calculer le sinus de +l'argument qui lui est transmis. +Pour cela, nous utilisons donc un système de \en{type traits} qui associe au couple (Opération, +Indice d'opérande) un mode d'accès, lecture ou écriture. +Grâce à cela, il est possible de parcourir durant la compilation l'\gls{ASA} construit par le patron +d'expression et d'appliquer une transformation afin d'associer à chaque feuille un mode d'accès. + +Afin de vérifier exactement les conditions décrites par +\acref{eq:pfor/conditions/bernstein_loop_wirjp,eq:pfor/conditions/bernstein_loop_riwjp}, il serait +nécessaire de vérifier pour chaque itération de la boucle que les indices utilisés ne causent pas de +problème. +Cette solution peut être implémentée si le n-uplet des indices des itérations de la boucle est +connu. +Pour exemple, il est possible de considérer le cas trivial montré dans +\acref{lst:pfor/generation/loop_hard}. +\begin{listing}[bt] + \inputlst{generation/loop_hard} + {Boucle nécessitant de connaître les indices pour déterminer qu'elle peut être exécutée en + parallèle} +\end{listing} +Dans ce code, sans savoir que \cppinline{i} prend ses valeurs dans $\{0, 2, 4, 6, 8\}$, les accès +semblent indiquer que l'instruction exécutée par cette boucle ne peut pas l'être en parallèle. + +Il est possible de déduire ce n-uplet à partir de la valeur initiale, du pas et de la condition de +terminaison, ce qui nécessite de connaître ces informations dès la compilation. +Nous voulons proposer une solution qui n'apporte pas une contrainte si forte : il est très commun +d'utiliser une boucle pour traiter les éléments d'une collection (ce que nous faisons dans nos +exemples) dont la taille peut n'être connue que durant l'exécution du programme. +La bibliothèque peut cependant fournir une interface alternative bénéficiant de cette contrainte +lorsque cela est applicable. + +Sans cette connaissance, il n'est pas possible de produire un algorithme ayant une sensibilité +parfaite (correctement détecter qu'un code peut être exécuté en parallèle) sans affecter la +spécificité (correctement détecter qu'un code ne peut pas être exécuté en parallèle). +Pour qu'un système de parallélisation automatique soit utilisable, un faux positif (mauvaise +spécificité) ne peut être permis, puisqu'il causerait la génération d'un programme dont le +comportement serait incorrect. +En admettant certaines inconnues (par exemple ce n-uplet d'indices), il nous a semblé impossible +d'éviter automatiquement l'existence de faux négatifs sans risquer de produire des faux positifs. +Sans connaissance \textit{a priori} du pas, dans l'exemple donné par +\acref{lst:pfor/generation/loop_hard} nous devons supposer qu'il est impossible de paralléliser +cette boucle. +En utilisant l'hypothèse contraire, lorsque le pas est de \num{1} la déduction serait erronément +positive. + +Nous avons en conséquence proposé~\autocite{ref:pereda2018} une condition (\cref{eq:pfor/gen/cond}), +conçue à partir des conditions exactes, spécifique mais non parfaitement sensible, afin de +déterminer la capacité d'une expression à être exécutée en parallèle : +\begin{align} + F^w_a = \emptyset \vee |F_a| = 1. + \label{eq:pfor/gen/cond} +\end{align} + +Ce test est particulièrement limité : soit aucun accès en écriture n'est fait sur les éléments du +tableau \cppinline{a}, soit les accès sont effectués en utilisant une seule et unique fonction +d'indice. +Il repose en outre, lorsque $F^w_a \neq \emptyset$, sur l'hypothèse que cette fonction est +injective pour le domaine utilisé. +Dans le cas contraire, par exemple avec $i \mapsto i^2$ et $i \in \{-1, 0, 1\}$, il est possible +d'accéder à une même variable à partir de deux itérations distinctes malgré l'unicité de la +fonction. + +La création de l'image de la fonction (nécessitant une énumération à partir du domaine) n'étant pas +envisageable pour des raisons de performances, voire de capacité, ces cas particuliers doivent être +traités de manière spécifique. +Ainsi, ce test peut être appliqué lorsque l'expression d'indice possède la propriété \og injective +\fg, que ce soit dû à un marquage explicite de l'utilisateur ou issu d'une déduction automatique. +En cas d'erreur dû à un marquage explicite incorrect, la responsabilité incombe au développeur qui +doit alors vérifier la fonction ou son domaine. + +Depuis cette proposition, nous avons amélioré la sensibilité du test par analyse des fonctions +d'indice pour le cas polynômial de degré \num{1}. +En effet, les indices d'accès ainsi que les différents coefficients employés pour construire les +expressions d'indice sont des entiers. +En conséquence, ces expressions, lorsqu'elles sont polynômiales et de degré \num{1}, correspondent à +des fonctions affines entières. +Il est donc possible de déterminer par analyse s'il existe deux entrées distinctes permettant de +produire la même sortie en considérant un ensemble de fonctions de ce type. + +Deux de ces fonctions peuvent donc prendre la forme générale $i \mapsto a\,i + b$ et $i \mapsto c\,i ++ d$, où $i, a, b, c, d \in \mathbb{Z}$. +Le mécanisme de détection expliqué dans \acref{subsec:pfor/detection/indices} garantit que toute +expression affine est représentée sous cette forme, éventuellement avec un coefficient nul. + +Considérer ces fonctions nous permet d'éviter un faux négatif qu'aurait donné le test initial pour +des fonctions telles que $i \mapsto 2\,i$ et $i \mapsto 2\,i+1$. +Les images de celles-ci ont une intersection vide pour $i \in \mathbb{Z}$, ainsi, bien que ce soit +les images de deux fonctions distinctes (faisant échouer $|F_a| = 1$), même en utilisant l'une pour +un accès en lecture et l'autre pour un accès en écriture (faisant échouer $F^w_a = \emptyset$), il +faut déterminer qu'elles n'empêchent pas la parallélisation. + +Une expression utilisant deux fonctions affines pour accéder aux éléments d'un même tableau sera +toujours parallélisable si \acref{eq:pfor/gen/lineareq} est respectée, c'est-à-dire s'il n'est pas +possible d'obtenir le même résultat avec les deux fonctions pour des indices différents : +\begin{align} + \forall i, j \in \mathbb{Z}, i \neq j, \; a\,i+b \neq c\,j+d, \qquad a, b, c, d \in \mathbb{Z}. + \label{eq:pfor/gen/lineareq} +\end{align} + +La contrainte $i \neq j$ correspond à l'objectif de comparer les accès sur deux itérations +distinctes d'une boucle. +Pour vérifier les contraintes données par \acref{eq:pfor/gen/lineareq}, il est possible d'utiliser +une équation Diophantienne affine à deux inconnues dont les coefficients sont $a$, $c$ et $d-b$ +(\cref{eq:pfor/gen/diophante}) : +\begin{align} + a\,i - c\,j = d-b, \qquad i, j \in \mathbb{Z}. + \label{eq:pfor/gen/diophante} +\end{align} + +La résolution de cette équation lorsqu'elle est utilisée pour $a=c$ et $d=b$ donne la solution +triviale $i=j$ et correspond au cas exclu par la contrainte $i \neq j$. +Cependant, cette contrainte peut être mise de côté puisque lorsque les fonctions à comparer +correspondent en réalité à une même fonction, on ne cherchera pas à résoudre cette équation +Diophantienne. + +L'inexistence de solutions à cette équation prouve donc que l'expression peut être exécutée en +parallèle. +Pour tester cela, nous utilisons le théorème de Bachet-Bézout, lequel indique l'existence de +solutions si $d-b$ est un multiple du \gls{PGCD} de $a$ et $c$. + +Grâce au travail présenté dans \acref{subsec:pfor/detection/indices} sur la représentation des +expressions affines, il est possible, durant la compilation, de vérifier cette propriété dans le cas +où les fonctions sont effectivement affines, sinon d'utiliser par défaut le test moins sensible +évoqué précédemment. + +Ce résultat s'étend encore lorsque l'on peut connaître le pas de la boucle durant la compilation. +Si la valeur initiale et la condition d'arrêt sont des informations habituellement dépendantes de +valeurs dynamiques (taille d'une collection par exemple), le pas est en revanche souvent connu. +Grâce à cette information, il est possible de détecter correctement que le cas présenté dans +\acref{lst:pfor/generation/loop_hard} dont les fonctions sont $i \mapsto i$ (en écriture) et $i +\mapsto i+1$ (en lecture) peut être parallélisé. +Pour utiliser cette information, nous transformons la fonction initiale de sorte à en obtenir une +nouvelle fonction équivalente mais pour laquelle le pas serait de \num{1}. + +Considérons une fonction affine quelconque $a\,I + b$ utilisée comme fonction d'indice au sein d'une +boucle dont l'indice varie de sa valeur initiale $v_i$ jusqu'à sa valeur finale $v_f$ exclue par un +pas de $p$. +L'image de cette fonction est générée par \acref{eq:pfor/gen/imbase} : +\begin{align} + \{a\,I+b \mid I \in \llbracket v_i, v_f\llbracket, I = v_i + k\,p, k \in \mathbb{N}\}, + \qquad a, b, v_i, v_f \in \mathbb{Z}, p \in \mathbb{Z}^{*}. + \label{eq:pfor/gen/imbase} +\end{align} + +Il est important de noter que l'on a $v_i \leqslant v_f$. % TODO < ou ≤ ? +Ainsi, lorsque $p < 0$, $v_i$ correspond à la valeur finale plutôt qu'initiale et, à l'inverse, +$v_f$ correspond à la valeur initiale plutôt que finale. +Cette inversion sémantique conditionnelle suffit à généraliser pour $p \in \mathbb{Z}^{*}$ au lieu +de limiter le pas aux entiers positifs. + +En posant $I = i_p + v_i$, où $i_p$ est la nouvelle variable servant d'indice, on peut décrire la +même image en utilisant une valeur initiale de \num{0} comme dans \acref{eq:pfor/gen/imoffset} : +\begin{align} + \{a\,(i_p + v_i) + b \mid i_p \in p\,\mathbb{N}, i_p < v_f-v_i\}, + \qquad a, b, v_i, v_f \in \mathbb{Z}, p \in \mathbb{Z}^{*}. + \label{eq:pfor/gen/imoffset} +\end{align} + +Enfin, en posant $i_p = p\,i$, où $i$ est la nouvelle variable servant d'indice, la description de +cette image évolue à nouveau et utilise un nouveau pas de \num{1} en addition de la valeur initiale +de \num{0}. +La nouvelle description correspond à \acref{eq:pfor/gen/imoffsetstep} : +\begin{align} + \{a\,(p\,i + v_i) + b \mid i \in \mathbb{N}, i < \left\lceil \frac{v_f-v_i}{p} \right\rceil\}, + \qquad a, b, v_i, v_f \in \mathbb{Z}, p \in \mathbb{Z}^{*}. + \label{eq:pfor/gen/imoffsetstep} +\end{align} + +En utilisant cette transformation pour deux fonctions $a\,I + b$ et $c\,J + d$ avec une valeur +initiale $v_i$, une valeur finale $v_f$ et un pas $p$, on cherche alors à résoudre +\acref{eq:pfor/gen/diophanteoffsetstep} : +\begin{equation} + \begin{aligned} + a\,p\,i + a\,v_i + b &= c\,p\,j + c\,v_i + d\\ + \iff \qquad\qquad a\,p\,i - c\,p\,j &= d-b + (c-a)\,v_i. + \end{aligned} + \label{eq:pfor/gen/diophanteoffsetstep} +\end{equation} + +Lorsque le terme $A = (c-a)\,v_i$ n'est pas insignifiant dans la résolution de l'équation, la valeur +initiale $v_i$ devient nécessaire. +Il nous faut donc déterminer quels critères permettent d'ignorer $A$. +Puisque cette équation ne sera utilisée que pour tester l'existence de solutions, c'est la +divisibilité de la partie droite ($d-b + A$) par le \gls{PGCD} de $a\,p$ et $c\,p$ qui nous +intéresse. +Ainsi, $A$ peut être ignoré lorsqu'il est divisible par le \gls{PGCD} de $a\,p$ et $c\,p$. + +Le \gls{PGCD} de $a\,p$ et $c\,p$ vaut $p \times \text{pgcd}(a, c)$. +Or, le \gls{PGCD} de $a$ et $c$ divise leur différence $(c-a)$. +Ainsi, il reste à savoir si $v_i$ est un multiple de $p$ ou non. +Ce résultat permet de montrer que $v_i$ peut être ignoré lorsque $p = 1$. +On retrouve alors \acref{eq:pfor/gen/diophante}. + +Lorsque $p \neq 1$ en revanche, nous utilisons \acref{eq:pfor/gen/diophanteoffsetstep}, c'est-à-dire +les coefficients $a\,p$, $c\,p$ et $d-b + (c-a)\,v_i$. +Lorsque le pas et la valeur initiale sont connus, les fonctions d'indice affines $a\,i+b$ sont donc +mises à jour pour le test en utilisant $A = a\,p$ et $B = b + a\,v_i$. + +Dans le cadre de cette thèse, nous avons donc plusieurs tests. +Pour déterminer celui qui peut être appliqué, l'arbre construit par le patron d'expression est +d'abord transformé en une liste par un parcours en profondeur (de type NLR), en appliquant une +transformation qui associe aux feuilles un triplet $(id_v, rw, i)$ comportant l'identifiant de la +variable, $id_v$, le mode d'accès $rw$ (soit lecture, $r$, soit écriture, $w$) et le patron +d'expression représentant la fonction d'indice $i$. +Les branches de l'arbre ne sont pas représentées dans cette liste. + +Par exemple, si l'on considère les instructions des +lignes~\ref{line:pfor/detection/loopstmtexpr:i2} et \ref{line:pfor/detection/loopstmtexpr:i4} de +\acref{lst:pfor/detection/loopstmtexpr}, qui sont interdépendantes (voir +\acref{eq:pfor/generation/expridclusters}), \acref{eq:pfor/generation/exprtags} représente le +résultat de la transformation expliquée. +\begin{equation} + \begin{aligned} + \{\quad\\ + &(c, w, i \mapsto i),\\ + &(c, r, i \mapsto i+1),\\ + &(d, r, i \mapsto i),\\ + &(d, w, i \mapsto i),\\ + &(c, r, i \mapsto i),\\ + &(e, r, i \mapsto i)\\ + \}\quad + \end{aligned} + \label{eq:pfor/generation/exprtags} +\end{equation} + +Jusqu'à ce que l'ensemble soit vide, on en retire tous les éléments référant à une même variable +pour les isoler dans un nouvel ensemble à traiter, et on applique cette procédure récursivement sur +l'ensemble restant. + +En isolant par exemple $e$, on obtient deux ensembles $\{(e, r, i \mapsto i)\}$ et $\{(c, w, i +\mapsto i), (c, r, i \mapsto i+1), (d, r, i \mapsto i), (d, w, i \mapsto i), (c, r, i \mapsto i)\}$. +Le premier, ne contenant que des triplets à propos de la variable $e$, peut alors être traité pour +vérifier s'il empêche la parallélisation. +Toutes les expressions d'indice concernées (c'est-à-dire $\{i \mapsto i\}$) ayant la propriété +d'être affines, le test appliqué est celui spécifique aux fonctions affines. +S'il n'avait pu être utilisé, dans ce cas précis le test moins sensible (\cref{eq:pfor/gen/cond}) +aurait tout de même été correct puisque l'on a $F^w_e = \emptyset$. + +L'étape suivante isolera par exemple $d$, afin d'obtenir les ensembles $\{(d, r, i \mapsto i), (d, +w, i \mapsto i)\}$ et $\{(c, w, i \mapsto i), (c, r, i \mapsto i+1)\}$. +Le premier ensemble permet à nouveau, à partir du seul test simplifié, de déterminer que $d$ ne +cause pas de problème : bien que $F^w_d \neq \emptyset$, on a $|F_d| = 1$ puisque la seule fonction +est $i \mapsto i$. +À l'instar du cas précédent, cependant, dans la pratique il s'agit encore du test spécifique aux +fonctions affines qui sera appliqué ici. + +Enfin, en isolant les triplets correspondants à la variable $c$, on obtient l'ensemble $\{(c, w, i +\mapsto i), (c, r, i \mapsto i+1), (c, r, i \mapsto i)\}$ et l'ensemble vide, il s'agit donc de la +dernière étape. +Pour ce cas, le test simplifié affirmerait que les instructions ne doivent pas être exécutées en +parallèle à cause des accès effectués à la variable $c$. +Les fonctions $i \mapsto i$ et $i \mapsto i+1$ étant toutes deux affines, le test spécifique est +utilisé dans ce cas. + +En observant les autres instructions de \acref{lst:pfor/detection/loopstmtexpr}, en particulier +la ligne~\ref{line:pfor/detection/loopstmtexpr:i5}, on observe un cas pour lequel toutes les +fonctions ne sont pas affines. +Si le développeur précise pour chacune des fonctions qu'elle est injective (en écrivant +\cppinline{injective(i*i)}), ce qui est correct par exemple si $i$ prend ses valeurs dans +$\mathbb{N}$, alors le test de \acref{eq:pfor/gen/cond} peut être utilisé. +Dans ce cas précis, $|F_f| = 1$ donc l'instruction peut être exécutée en parallèle. + +En revanche, si au moins une expression n'est ni affine, ni injective, aucun test ne peut être +effectué et, en l'état, le groupe d'instructions concernées est considéré comme ne pouvant pas être +exécuté en parallèle. +%}}} + +%{{{ +\subsection{Génération du programme} +\label{subsec:pfor/gen/loop} + +Chaque ensemble d'instructions interdépendantes peut être testé pour sa parallélisabilité. +Une fois cette propriété déterminée, il est possible de les réunir en deux groupes : l'un contenant +les instructions parallélisables et l'autre contenant les instructions non parallélisables. + +Pour chaque groupe, le patron d'expression correspondant est reconstruit, les instructions sont +associées à un nœud père commun représentant une séquence d'exécution (l'opérateur virgule). +Il est possible de créer un patron d'expression vide si un groupe est vide (si toutes les +instructions peuvent, ou à l'inverse aucune d'elles ne peuvent, être exécutées en parallèle). +Aucun code n'est généré pour un tel patron d'expression. + +Cette étape est accomplie par l'interface principale de la bibliothèque, la fonction +\cppinline{parallelFor}. +\Acref{lst:pfor/generation/pfor} présente son utilisation pour l'exemple initial de +\acref{lst:pfor/conditions/loop}. +La fonction accepte en premier argument un élément définissant l'évolution de l'indice : dans cet +exemple, $i$ variera de \num{0} à $n$ (exclu) par un pas, par défaut, de \num{1}. +Pour choisir un pas $p$, il est possible d'écrire \cppinline{Range{0, n, p}}. + +\begin{listing} + \inputlst{generation/pfor} + {Boucle de \acref{lst:pfor/conditions/loop} utilisant \cppinline{parallelFor}} +\end{listing} + +Utilisant ce qui est expliqué dans ce chapitre, la fonction \cppinline{parallelFor} produit dans ce +cas deux boucles. +Pour le patron d'expression issu du groupe d'instructions non parallélisables, le code généré +correspond directement à une boucle \cppinline{for} dont chaque itération exécute l'expression avec +pour argument la valeur de l'itérateur courant tel que le présente +\acref{lst:pfor/generation/pforseq}. + +\begin{listing} + \inputlst{generation/pforseq} + {Boucle séquentielle générée par \acref{lst:pfor/generation/pfor}} +\end{listing} + +Pour le patron d'expression résultant du groupe d'instructions parallélisables, un traitement +spécifique est appliqué. +L'implémentation par défaut fournie par notre bibliothèque repose sur l'utilisation de la +bibliothèque OpenMP, en l'occurrence en ajoutant une directive indiquant au compilateur qu'il faut +exécuter les itérations de la boucle en parallèle. +En conservant ce comportement, le code généré peut correspondre à +\acref{lst:pfor/generation/pforopenmp}. + +\begin{listing} + \inputlst{generation/pforopenmp} + {Boucle parallèle générée par \acref{lst:pfor/generation/pfor} en utilisant OpenMP} +\end{listing} + +La méthode de parallélisation employée peut être sélectionnée par le développeur (il est possible +d'étendre la bibliothèque en implémentant d'autres méthodes) au moyen d'un argument optionnel à +fournir lors de l'appel à \cppinline{parallelFor}. +Par exemple, prenons une version faisant directement usage des \en{threads} telle que dans +\acref{lst:pfor/generation/stratthread}. +Deux spécialisations sont fournies : l'une pour le cas séquentiel (le booléen est faux), l'autre +pour le cas parallèle (le booléen est vrai). + +\Acref{lst:pfor/generation/pforthread} montre l'utilisation de \cppinline{parallelFor} avec une +implémentation sous-jacente de la parallélisation grâce aux \en{threads}. + +\begin{listing} + \inputlst{generation/pforthread} + {Utilisation de la stratégie de parallélisation par \texttt{std::thread}} +\end{listing} + +\begin{listing}[tbp] + \inputlst{generation/stratthread} + {Stratégie de parallélisation par \texttt{std::thread}} +\end{listing} + +Il est possible d'utiliser une alternative à \cppinline{Range} pour indiquer un pas connu durant la +compilation : \cppinline{RangeCT{end}}. +S'il est employé, \cppinline{parallelFor} utilise automatiquement la valeur initiale et le pas +fourni pour améliorer la détection de la parallélisabilité du code. + +La spécialisation utilisée pour le cas séquentiel est librement définie par le développeur afin de +permettre des variations dans ce cas également, et non seulement lorsque le code peut être exécuté +en parallèle. +Un exemple concret d'utilisation est le déroulage de boucles~\autocite{ref:davidson1995}. +Cela consiste en la transformation d'une boucle de $n$ itérations en une boucle de $k$ itérations, +où $k$ est plus petit que $n$. +Chaque itération de la nouvelle boucle procède à l'exécution de $\left\lfloor \frac{n}{k} +\right\rfloor$ itérations de la boucle d'origine. +Lorsque $k$ ne divise pas $n$, il existe un reste entier $r$ tel que $n = k \left\lfloor \frac{n}{k} +\right\rfloor + r$. +Ces $r$ instructions peuvent alors être exécutées dans un bloc après la boucle. +%}}} diff --git a/src/pfor/5_results.tex b/src/pfor/5_results.tex new file mode 100644 index 0000000..7215880 --- /dev/null +++ b/src/pfor/5_results.tex @@ -0,0 +1,325 @@ +\section{Performances} + +%{{{ Introduction " +Cette section présente des mesures de temps de compilation et d'exécution de programmes utilisant la +bibliothèque présentée dans ce chapitre. +Toutes les mesures ont été effectuées sur une machine dotée d'un Intel Xeon E7-8890 v3, cadencé à +\SI{2.5}{\GHz} et ayant \num{72} cœurs physiques (\num{18} processeurs ayant chacun \num{4} cœurs +physiques). +Les programmes sont compilés en utilisant GCC (g++) 8.2.0 avec notamment le pack d'optimisations +\texttt{O2}. +Les valeurs données dans ce document sont obtenues par une moyenne sur \num{20} exécutions (du +programme pour les temps d'exécution, du compilateur et de l'éditeur de liens lorsqu'il s'agit du +temps de compilation). +Lorsque cela est pertinent (suffisamment visible), l'intervalle de confiance à \SI{99}{\percent} est +affiché. +La compilation n'est pas exécutée en parallèle et n'utilise donc qu'un cœur. +Pour ce qui est des exécutions, l'affinité du processus a été réglée afin qu'au maximum un cœur soit +utilisé pour chaque processeur. +Cela limite donc à \num{18} cœurs pour protéger d'un biais de mesure potentiel dû à l'utilisation +simultanée de plusieurs cœurs d'un même processeur. +%}}} + +%{{{ Compilation " +Pour permettre une utilisation de cette bibliothèque en pratique, il faut que celle-ci n'induise pas +un surcoût en temps de compilation trop important. +De par sa nature de bibliothèque active, il est inévitable d'observer des temps de compilation plus +longs car la bibliothèque agit durant cette phase. +La métaprogrammation template est par ailleurs connue pour son effet significatif sur ces temps, +bien que celui-ci soit réduit au fur et à mesure des évolutions des compilateurs. + +Afin d'évaluer ces temps de compilation, nous avons généré plusieurs ensembles de codes source qui +utilisent la bibliothèque, en suivant différentes contraintes. +Nous avons évalué l'effet de la variation du nombre de boucles utilisées et celui de la variation du +nombre d'instructions que doit traiter une même boucle. +Dans les deux cas, le nombre de variables créées est fixé à \num{100}. + +De plus, l'accès aux variables peut se faire de plusieurs manières. +D'abord, les fonctions d'indice utilisées peuvent être soit identiques pour tous les accès +(étiquette \og fixed \fg), soit différentes pour tous les accès auquel cas nous utilisons des +fonctions d'indice affines dont les coefficients sont aléatoires (étiquette \og rand \fg). +Ensuite, au sein d'une même boucle, les variables peuvent être dépendantes les unes avec les autres +(étiquette \og dep \fg) ou non (étiquette \og indep \fg). +Cela fait donc \num{4} cas à distinguer : +\begin{itemize} + \item \og dep/fixed \fg{} : des instructions interdépendantes dont les fonctions d'indice sont + identiques ; + \item \og indep/fixed \fg{} : des instructions indépendantes dont les fonctions d'indice sont + identiques ; + \item \og dep/rand \fg{} : des instructions interdépendantes dont les fonctions d'indice sont + différentes ; + \item \og indep/rand \fg{} : des instructions indépendantes dont les fonctions d'indice sont + différentes. +\end{itemize} + +\Acref{fig:pfor/results/ct_fixediv} montre les temps de compilation lorsque le nombre de boucles +varie de \num{10} à \num{100} par pas de \num{10}. +Le nombre d'instructions est alors invariable et défini à \num{100}. +Pour maintenir le nombre d'instructions à \num{100} pour $n$ boucles, en posant la division +euclidienne $100 = q \times n + r$, on fait exécuter $q$ instructions à $n-r$ boucles et, si $r>0$, +$q+1$ instructions à $r$ boucles. + +\begin{figure} + \centering + \includegraphics{img/pfor/fixediv.pdf} + \caption{Temps de compilation en fonction du nombre de boucles dans le code source} + \label{fig:pfor/results/ct_fixediv} +\end{figure} + +On observe globalement une croissance lente du temps de compilation quelles que soient les +caractéristiques des instructions. +Le surcoût initial de quelques secondes est dû à l'emploi de la métaprogrammation template : dès +lors qu'au moins une boucle est utilisée, la bibliothèque instancie de nombreux templates, imposant +donc du travail au compilateur. + +On observe également une influence notable, en particulier, de l'identité des fonctions d'indice. +Procéder à l'analyse des instructions si celles-ci utilisent de nombreuses fonctions d'indice +différentes est logiquement plus coûteux. +Cependant, cet effet est volontairement exagéré par la configuration des codes sources générés : +pour les \num{100} instructions, plusieurs centaines de fonctions d'indice différentes sont +utilisées. +En pratique, il est plus fréquent d'avoir de nombreuses fonctions d'indice identiques, et +éventuellement quelques unes distinctes. +Les temps réalistes sont donc compris entre les valeurs mesurées pour des fonctions d'indice +aléatoires et celles pour des fonctions d'indice identiques. + +L'influence de l'interdépendance des instructions semble moins prononcée mais s'explique notamment +par le fonctionnement de l'algorithme servant à grouper les instructions dépendantes. +Enfin, nous n'expliquons pas la décroissance du temps de compilation pour le cas des instructions +interdépendantes utilisant une unique fonction d'indice. + +\Acref{fig:pfor/results/ct_fixedv} montre les temps de compilation lorsque le nombre d'instructions +au sein d'une même boucle varie. +\begin{figure} + \centering + \includegraphics{img/pfor/fixedv.pdf} + \caption{Temps de compilation en fonction du nombre d'instructions par boucle dans le code source} + \label{fig:pfor/results/ct_fixedv} +\end{figure} +L'axe des ordonnées est affiché en échelle logarithmique. +Une seule boucle exécute \num{10} à \num{100} instructions (par pas de \num{10}) utilisant jusqu'à +\num{100} variables (lesquelles sont toujours définies). + +Contrairement au cas précédent, on observe un temps de compilation exponentiel par rapport au nombre +d'instructions au sein d'une même boucle. +Cela se justifie principalement par les algorithmes utilisés qui sont exécutés durant la +compilation. +Ces temps de compilation peuvent donc être améliorés en changeant ces algorithmes, soit en trouvant +de nouvelles méthodes pour procéder aux tests effectués par la bibliothèque, soit en améliorant les +algorithmes actuels si cela est possible. +Si l'on considère une utilisation réaliste de la bibliothèque, c'est-à-dire de nombreuses boucles +ayant chacune quelques instructions, le temps de compilation est donc au maximum d'un peu plus de +\SI{10}{\s}. +Même en allant jusqu'à quelques dizaines d'instructions au sein des boucles, ce temps ne dépasse pas +la minute. +%}}} + +%{{{ Execution " +%{{{ Basic " +Par rapport aux temps d'exécution, nous avons également pris en compte différents critères. +\Acref{fig:pfor/results/rt_seq,fig:pfor/results/rt_par} correspondent à l'exécution en boucle de +\num{4} instructions effectuant chacune une multiplication, une addition et une affectation sur des +entiers conservés dans des tableaux. +La taille des tableaux (et donc le nombre d'itérations de la boucle) varie de $10^2$ à $10^7$. +À noter que ces figures sont affichées en échelle logarithmique pour les deux axes. +Les instructions exécutées étant particulièrement courtes, la boucle complète est elle-même répétée +\num{1000} fois. + +\Acref{fig:pfor/results/rt_seq} présente les temps mesurés lorsque les instructions sont telles +qu'il n'est pas possible d'exécuter en parallèle les itérations de la boucle. +Les trois cas, dont les étiquettes sont \og seq \fg, \og gen\_omp \fg et \og gen\_thread \fg, +correspondent : +\begin{itemize} + \item \og seq \fg{} : à un programme écrit classiquement, sans utiliser la bibliothèque ; + \item \og gen\_omp \fg{} : à un programme écrit en utilisant la bibliothèque, laquelle utilise + OpenMP pour la parallélisation (même s'il n'y a, dans cet exemple, aucune parallélisation + possible) ; + \item \og gen\_thread \fg{} : à un programme écrit en utilisant la bibliothèque, laquelle + utilise cette fois-ci des \en{threads}. +\end{itemize} +À noter qu'il n'est pas possible d'utiliser OpenMP directement puisqu'il ne faut pas exécuter les +itérations en parallèle. +La bibliothèque, que ce soit avec OpenMP ou les \en{threads}, produit automatiquement, dans ce cas, +un code n'effectuant aucune parallélisation. + +\begin{figure} + \centering + \includegraphics{img/pfor/rt_seq.pdf} + \caption{Temps d'exécution séquentielle en fonction de la taille des données} + \label{fig:pfor/results/rt_seq} +\end{figure} + +Puisque l'objectif de la bibliothèque, lorsque les itérations de la boucle ne peuvent pas être +exécutées en parallèle, est de générer un code aussi proche que possible du code d'origine, ce que +l'on souhaite observer est l'absence de surcoût significatif en temps d'exécution par rapport à la +version n'utilisant pas la bibliothèque. +Sur cette figure, on observe effectivement un léger surcoût apporté par l'utilisation de la +bibliothèque. +Celui-ci pourrait être évité en agissant directement au niveau du compilateur. +Cependant une bibliothèque, même active, ne peut pas générer des instructions immédiates : de +nombreuses indirections sont nécessitées par les mécanismes mis en place par la bibliothèque. +Cela dit, le surcoût observé à partir de $10^4$ itérations est assez faible pour être accepté. +Quant aux mesures pour $10^3$ itérations et moins, les écarts étant minimes, ils peuvent être +attribués au fait que les instructions (au niveau assembleur) sont légèrement différentes à cause +des indirections causées par la bibliothèque et sont négligeables. + +\Acref{fig:pfor/results/rt_par} présente les temps mesurés lorsque les instructions sont telles +qu'il est possible, contrairement au cas précédent, d'exécuter en parallèle les itérations de la +boucle. +Un quatrième cas s'ajoute donc aux trois précédents : un programme écrit en utilisant directement +OpenMP et donc sans la bibliothèque, avec l'étiquette \og omp \fg. +Toutes les exécutions ont été faites en ayant \num{16} cœurs à disposition. + +\begin{figure} + \ocgfigIG{Temps d'exécution parallèle en fonction de la taille des données} + {results/rt_par}{rt_par_}{.pdf} + {Temps d'exécution parallèle en fonction de la taille des données (\num{16} cœurs alloués)} + {nombre de cœurs :}{\T}{1/off,2/off,4/off,6/off,8/off,10/off,12/off,14/off,16/on,18/off} +\end{figure} + +Le résultat le plus important à observer ici est le faible surcoût lorsque l'on compare les deux cas +utilisant OpenMP pour la parallélisation : avec et sans la bibliothèque. +Quelle que soit la taille des données, et donc le nombre d'itérations, ce surcoût en temps d'exécution +est négligeable. + +La version séquentielle et celle utilisant les \en{threads} pour la parallélisation sont présentées +pour référence. +Cette dernière semble moins efficace que les versions employant OpenMP, surtout pour un nombre +faible d'itérations. +Cela s'explique notamment par le coût de création des \en{threads} (dont l'importance est donc +relative à la taille des données) qui sont recréés à chaque nouvelle boucle, donc dans notre cas +\num{1000} fois. +Au coût d'une section critique, la mise en place d'un \en{thread pool} pourrait réduire ce surcoût : +les \en{threads} créés serviraient donc pour toutes les boucles parallélisées. +De plus, chaque boucle ne donnant au maximum qu'une tâche par \en{thread} disponible (une portion de +la boucle), la section critique ne devrait pas causer de ralentissement significatif. +%}}} + +%{{{ Image processing " +Ces premiers résultats sont issus de l'exécution de tâches très courtes (seulement quelques +instructions assembleur par itération). +\Acref{fig:pfor/results/rt_cores,fig:pfor/results/rt_speedup} correspondent à un traitement plus +complexe, un parcours d'image pour la modifier \textit{in situ}. +Celui-ci nous permet de montrer les performances de la bibliothèque lorsque les instructions sont +plus longues, mais aussi d'en rappeler l'intérêt. + +Le traitement correspond à la modification de certaines cellules : à l'exception de la première et +de la dernière ligne, une cellule sur deux est modifiée de sorte que celles modifiées ne soient pas +contiguës. +Pour chacune d'entre elles, on utilise la valeur des cellules dans le voisinage de von Neumann pour +effectuer un calcul dont le résultat est la nouvelle valeur de la cellule à modifier. +\Acref{fig:pfor/results/imgpro} montre, en grisé, quelles cellules seront modifiées dans une image +de largeur \num{11} et de hauteur \num{6} et au moyen de flèches quelles cellules sont utilisées +en lecture pour chacune d'entre elles. +\begin{figure} + \inputfig{results/imgpro} + {Traitement d'image matricielle avec voisinage de von Neumann} +\end{figure} +Afin de simplifier l'expression du parcours, les cellules en bordure à gauche et à droite de l'image +utilisent des cellules voisines issues de lignes adjacentes. + +Le code correspondant est celui de \acref{lst:pfor/results/imgpro}. +\begin{listing}[t] + \inputlst{results/imgpro} + {Traitement matriciel avec \cppinline{parallelFor}} +\end{listing} +Les constantes (\cppinline{constexpr}) \cppinline{W} et \cppinline{H} correspondent respectivement à +la largeur et à la hauteur de la matrice, laquelle est conservée dans un tableau mono dimensionnel. +Ainsi, par exemple la cellule au-dessus de la cellule courante $i$ est en $i-W$. + +On devine que ce traitement peut être effectué en parallèle en regardant +\acref{fig:pfor/results/imgpro}. +Pour le savoir, en revanche, il faut analyser les accès au tableau. +Les \num{5} fonctions d'indice, considérant le pas utilisé ($2$) et la valeur de départ ($W+1$), +sont $i \mapsto 2\,i + W+1$, utilisée pour un accès en écriture, et $i \mapsto 2\,i + 1$, $i \mapsto +2\,i + W$, $i \mapsto 2\,i+ 2\,W+1$ et $i \mapsto 2\,i + W+2$, utilisées pour des accès en lecture. + +On doit alors vérifier l'inexistence de solutions aux équations Diophantiennes suivantes : +\begin{align} + 2\,i + W+1 &= 2\,j + 1 + \label{eq:pfor/results/2j1}\\ + 2\,i + W+1 &= 2\,j + W + \label{eq:pfor/results/2jW}\\ + 2\,i + W+1 &= 2\,j + 2\,W+1 + \label{eq:pfor/results/2jW1}\\ + 2\,i + W+1 &= 2\,j + W+2. + \label{eq:pfor/results/2jW2} +\end{align} +Lesquelles correspondent, une fois simplifiées, aux équations suivantes : +\begin{align} + 2\,i + W &= 2\,j + \tag{\ref{eq:pfor/results/2j1}$'$} + \label{eq:pfor/results/2j1p}\\ + 2\,i + 1 &= 2\,j + \tag{\ref{eq:pfor/results/2jW}$'$} + \label{eq:pfor/results/2jWp}\\ + 2\,i &= 2\,j + W + \tag{\ref{eq:pfor/results/2jW1}$'$} + \label{eq:pfor/results/2jW1p}\\ + 2\,i &= 2\,j + 1. + \tag{\ref{eq:pfor/results/2jW2}$'$} + \label{eq:pfor/results/2jW2p} +\end{align} + +Puisque $i$ et $j$ sont entiers, \acref{eq:pfor/results/2jWp,eq:pfor/results/2jW2p} n'ont +trivialement pas de solution de par la différence de parité de part et d'autre de l'égalité. +Quant aux \cref{eq:pfor/results/2j1p,eq:pfor/results/2jW1p}, la même observation peut être faite à +condition que $W$ soit lui-même impair. +En revanche, si $W$ est pair, il apparaît que les itérations de la boucle ne peuvent pas être +exécutées en parallèle. +Cela peut être observé de manière plus empirique en reproduisant \acref{fig:pfor/results/imgpro} +avec une largeur $W$ paire. +En utilisant la bibliothèque, cette analyse est effectuée automatiquement : si $W$ peut varier, la +décision de paralléliser ou non sera donc adaptée sans intervention. + +Nous avons utilisé $W = 10^5+1$, $H = 10^5$ et une fonction \cppinline{calc} définie de telle +manière que le calcul effectué ne soit pas aussi court que dans les premiers résultats présentés. +En pratique, la durée d'une exécution de ce calcul prend un peu moins de \SI{10}{\ns}. +Avec ces paramètres, \acref{fig:pfor/results/rt_cores,fig:pfor/results/rt_speedup} montrent +respectivement le temps d'exécution en fonction du nombre de cœurs alloués et l'accélération +obtenue correspondante. + +\begin{figure} + \centering + \includegraphics{img/pfor/rt_cores.pdf} + \caption{Temps d'exécution parallèle en fonction du nombre de cœurs alloués} + \label{fig:pfor/results/rt_cores} +\end{figure} + +Quelle que soit la manière de paralléliser, avec ou sans utiliser la bibliothèque, on observe sur +\acref{fig:pfor/results/rt_cores} quasiment les mêmes temps d'exécution. +Le surcoût engendré par l'utilisation des \en{threads} s'estompe lorsque le nombre de cœurs croît. +Il ne s'agit pas cette fois-ci d'une atténuation relative à la taille des données, puisque celle-ci +est fixe, mais cela peut en revanche être lié à un coût statique (ou évoluant suffisamment lentement +en fonction du nombre de \en{threads} créés) de mise en place des \en{threads}. + +\begin{figure} + \centering + \includegraphics{img/pfor/rt_speedup.pdf} + \caption{Accélération en fonction du nombre de cœurs alloués} + \label{fig:pfor/results/rt_speedup} +\end{figure} + +Dès lors que les itérations ne sont pas particulièrement courtes, l'accélération obtenue en +utilisant la bibliothèque est linéaire et pratiquement identique à celle obtenue en utilisant +directement OpenMP comme le montre \acref{fig:pfor/results/rt_speedup}. +Lorsque les itérations sont trop courtes, même en utilisant directement OpenMP, comme l'ont montré +les résultats ci-avant, l'accélération est limitée et ce n'est donc pas intrinsèque à notre +bibliothèque. +Pour éventuellement obtenir dans ce cas une meilleure accélération, il faut procéder à une +parallélisation sur mesure, requérant potentiellement des modifications profondes dans le code +source initial. +%}}} +%}}} + +%{{{ Conclusion " +En conclusion, nous avons montré que le surcoût engendré par la bibliothèque durant la compilation, +inévitable puisque celle-ci est active durant cette phase, n'empêche pas son utilisation puisqu'il +reste faible pour des utilisations classiques. +Nous avons également montré qu'elle permet d'obtenir de bonnes performances parallèles, c'est-à-dire +une bonne accélération. +Plus généralement, les temps d'exécution en utilisant cette bibliothèque sont aussi bons que sans +l'utiliser, que ce soit lorsque la boucle doit être exécutée en parallèle ou non. +Ainsi, il est effectivement possible de bénéficier de l'abstraction apportée sans devoir subir une +contrepartie significative. +%}}} diff --git a/src/pfor/6_conclusion.tex b/src/pfor/6_conclusion.tex new file mode 100644 index 0000000..16ce18e --- /dev/null +++ b/src/pfor/6_conclusion.tex @@ -0,0 +1,56 @@ +\section{Conclusion} + +Dans ce chapitre, nous avons présenté notre proposition de bibliothèque C++ utilisant la +métaprogrammation template pour automatiser la parallélisation d'une boucle. +Celle-ci se distingue de solutions de parallélisation automatique par sa compatibilité avec tout +compilateur C++. +C'est un atout que n'ont pas les extensions, ni les compilateurs dédiés. +Elle se différencie également par son utilisation de la métaprogrammation template pour effectuer, +bien qu'étant une bibliothèque, les traitements nécessaires autant que possible durant la +compilation afin de minimiser le surcoût induit par l'abstraction proposée, faisant d'elle une +bibliothèque dite \og active \fg. + +Nous avons également détaillé les conditions nécessaires pour garantir une exécution parallèle +conforme à l'exécution séquentielle : le comportement du programme ne doit pas être modifié en +procédant à sa parallélisation. +Pour pouvoir tester ces conditions, nous avons eu besoin d'acquérir des informations sur le +programme devant être parallélisé. +Nous avons donc expliqué comment les patrons d'expression sont utilisés pour disposer d'une +représentation, durant la compilation, à la fois des instructions composant ce programme et aussi +des fonctions utilisées pour calculer les indices permettant l'accès aux données. +Ces fonctions peuvent être marquées de certaines propriétés, lesquelles sont ensuite propagées et +peuvent également être automatiquement déduites. + +Grâce à ces représentations, nous avons pu appliquer un test de parallélisabilité dont la +sensibilité est encore améliorable pour le cas des fonctions non affines. +La spécificité du test est en revanche parfaite, ce qui permet de garantir que seuls des codes +pouvant effectivement être exécutés en parallèle seront positifs. +Lorsque les fonctions d'indice sont affines, nous proposons un test parfaitement sensible. +À défaut d'être affines, si les fonctions sont injectives, un test moins sensible est utilisé. +Enfin, si les fonctions n'ont aucune propriété utilisable, par précaution, elles sont considérées +comme non parallélisables. +Le test peut être étendu pour traiter davantage de cas, mais certains peuvent nécessiter davantage +de connaissance dès la compilation. +En revanche, il semble difficile de maximiser la sensibilité sans sacrifier partiellement la +spécificité. + +Enfin, nous avons expliqué comment notre bibliothèque génère un programme après avoir déterminé +quelles instructions peuvent être exécutées en parallèle et lesquelles ne peuvent pas l'être (soit +directement soit indirectement par dépendance avec une autre instruction qui ne peut elle-même pas +l'être). +Nous avons montré comment notre méthode de génération permet l'implémentation d'optimisations +différentes telles que le déroulage de boucle. + +Dans l'ensemble, nous avons donc introduit un cadre utilisable par des développeurs pour automatiser +la parallélisation de boucles sans se préoccuper de la faisabilité de cette action. +En effet, nous avons montré que le coût de l'abstraction est négligeable que ce soit pour générer +des programmes séquentiels ou parallèles, mais aussi indépendamment de la granularité des +instructions à paralléliser. +Par ailleurs, ce cadre permet à des développeurs dont la parallélisation est l'expertise d'intégrer +de nouvelles analyses, par exemple pour compléter les tests déjà implémentés, en tirant également +profit de cette absence de surcoût en temps d'exécution. + +Si l'on souhaite laisser davantage de contrôle au développeur quant à la parallélisation de son +programme par rapport à une solution de parallélisation automatique, par exemple telle que présentée +dans ce chapitre, la parallélisation assistée est une possibilité. +Ce sujet est traité dans le chapitre suivant. diff --git a/src/preamble.tex b/src/preamble.tex new file mode 100644 index 0000000..f6ceae9 --- /dev/null +++ b/src/preamble.tex @@ -0,0 +1,227 @@ +%{{{ packages " +\usepackage[T1]{fontenc} +\usepackage[utf8]{inputenc} +\usepackage[english,main=french]{babel} +\usepackage{CJKutf8} + +\usepackage{lmodern} +\usepackage{microtype} + +% openright: chapters start on right page +% \usepackage[inner=40mm,outer=20mm,top=25mm,bottom=25mm]{geometry} +\usepackage[width=160mm,top=25mm,bottom=25mm]{geometry} + +\usepackage{fancyhdr} +\usepackage{caption} +\usepackage{titling} +\usepackage{subcaption} +\usepackage{titletoc} +\usepackage{abstract} +\usepackage{shapepar} +\usepackage{emptypage} + +\usepackage{minted}% before csquotes + +\usepackage{csquotes} +\usepackage[ + backend=biber,backref=true, + style=authoryear,citestyle=authoryear,sorting=nyt, + dashed=false, + maxbibnames=99,maxcitenames=2,uniquelist=false, + language=auto]{biblatex} +\AtEveryBibitem{\clearfield{month}} +\AtEveryBibitem{\clearfield{day}} +\DeclareNameAlias{sortname}{last-first} + +\usepackage{array} +\usepackage{float} +\usepackage{flafter} +\usepackage[bottom]{footmisc} +\usepackage{multicol} + +\usepackage[section]{placeins}% prevent float to go out of its section + +\usepackage{amsmath}% cleveref +\usepackage{amsthm} +\usepackage{amssymb} +\usepackage{stmaryrd} + +\usepackage{xcolor} +\usepackage{xspace} +\usepackage{setspace} +\usepackage{suffix} + +\usepackage[binary-units]{siunitx} + +\usepackage{algorithm} + +\usepackage{tikz} +\usepackage{ifthen} +\usepackage[tikz]{ocgx2} +\usepackage{tikz-qtree} + +\usepackage{morewrites}% increase 16 open files limit (scrwfile is incompatible with titletoc) +\usepackage{chngcntr} + +\PassOptionsToPackage{hyphens}{url} +\usepackage{hyperref} +\usepackage[noabbrev,nameinlink]{cleveref}% after hyperref (see documentation) +\usepackage[commentColor=black!75]{algpseudocodex}% after cleveref +\usepackage[acronyms,section=chapter]{glossaries-extra}% after hyperref +\usepackage{sty/acref}% after cleveref +%}}} + +%{{{ configuration " +\title{Application de la métaprogrammation template à la conception de bibliothèques actives de +parallélisation assistée} +\author{Alexis Pereda} +% \date{} + +\input{src/usr/bibstyle} +\addbibresource{src/references.bib} +\AtEveryCite{\restorecommand\mkbibnamefamily}% disable small caps in citations + +\setcounter{tocdepth}{2} +\setcounter{secnumdepth}{4} +\renewcommand{\baselinestretch}{1.15} + +\AtBeginDocument{ + \counterwithin{listing}{chapter} + \counterwithin{algorithm}{chapter} +} + +\sisetup{locale=FR} +\DeclareSIUnit{\octet}{o} + +\microtypesetup{ + protrusion=alltext-nott, + expansion=alltext-nott, + final +} + +\floatplacement{listing}{H} + +\hypersetup{ + pdftitle={\thetitle}, + pdfauthor={\theauthor}, + pdfsubject={\thetitle}, + pdfcreator={\theauthor}, + pdfproducer={\theauthor}, + pdfkeywords={C++, métaprogrammation, template, parallélisation, parallélisation automatique, + répétabilité}, + pdfnewwindow=true, + colorlinks, + linkcolor={red!50!black}, + citecolor={blue!50!black}, + urlcolor={blue!80!black} +} + +%{{{ TikZ " +\usetikzlibrary{shapes,matrix,fit,spy,arrows.meta,chains,backgrounds} +\usetikzlibrary{positioning,intersections,calc} +\usetikzlibrary{decorations.pathreplacing,decorations.pathmorphing} +\usetikzlibrary{shadows,patterns} +\usetikzlibrary{external} + +\AtBeginEnvironment{tikzpicture}{\shorthandoff{;}} +%}}} + +%{{{ acref " +\crefname{listing}{extrait de code}{extraits de code} +\Crefname{listing}{Extrait de code}{Extraits de code} +\acrefarticle{listing}{l'}{L'} +%}}} + +%{{{ locale " +\renewcommand{\listingscaption}{Extrait de code} +\renewcommand{\listoflistingscaption}{Liste des extraits de code} + +\DefineBibliographyStrings{french}{% + backrefpage = {page}, + backrefpages = {pages}, + in = {dans}, +} +\DeclareFieldFormat{titlecase}{\MakeSentenceCase{\foreignlanguage{english}{#1}}} +%}}} + +%{{{ fancyhdr " +\setlength{\headheight}{14pt} +\addtolength{\topmargin}{-2pt} +%}}} + +%{{{ thanks " +\newenvironment{thesisthanks}{\cleardoublepage\newgeometry{width=160mm,top=25mm,bottom=25mm}\chapter*{Remerciements}\thispagestyle{empty}}{} + +%}}} + +%{{{ abstract " +\renewcommand*{\abstractnamefont}{\flushleft\huge\bfseries}% +\patchcmd{\endabstract}{\null}{}{}{}% +\AtBeginEnvironment{abstract}{\newgeometry{width=160mm,top=25mm,bottom=25mm}}% +%}}} +%}}} + +%{{{ input extra configuration " +\input{src/usr/alg} +\input{src/usr/header} +\input{src/usr/listing} +\input{src/usr/math} +\input{src/usr/ocg} +\input{src/usr/tikz} +%}}} + +%{{{ other input " +\input{src/tikz/common} +%}}} + +%{{{ commands " +\newcommand*{\en}[1]{\foreignlanguage{english}{\textit{#1}}} +\newcommand*{\encitetitle}[1]{{\foreignlanguage{english}{\citetitle{#1}}}} + +\newcommand*{\chapterdir}{} +\newcommand*{\inputchd}[2]{\input{src/#1/\chapterdir/#2}} +\newcommand*{\labelchd}[2]{\label{#1:\chapterdir/#2}} + +\newcommand*{\inputsrc}[1]{\input{src/\chapterdir/#1}} +\newcommand*{\inputlst}[2]{\inputchd{lst}{#1}\caption{#2}\labelchd{lst}{#1}} +\newcommand*{\inputfig}[2]{\centering\inputchd{fig}{#1}\caption{#2}\labelchd{fig}{#1}} +\newcommand*{\inputalg}[2]{\caption{#2}\labelchd{alg}{#1}\inputchd{alg}{#1}} + +\WithSuffix\newcommand\inputlst*[1]{\inputchd{lst}{#1}} +\WithSuffix\newcommand\inputfig*[1]{\inputchd{fig}{#1}} +\WithSuffix\newcommand\inputalg*[1]{\inputchd{alg}{#1}} + +\newcommand*{\chapterx}[2]{ + \glsresetall + \chapter{#2}\label{ch:#1} + \thispagestyle{chapter} + \renewcommand*{\chapterdir}{#1} + \addtocontents{lol}{\protect\addvspace{10pt}} + \addtocontents{loa}{\protect\addvspace{10pt}} + \startcontents[chapters] + { + \hypersetup{linkcolor=black} + \setcounter{tocdepth}{3} + \printcontents[chapters]{}{1}{} + } + \newpage + \tikzsetexternalprefix{buildfig/\chapterdir _} +} +\WithSuffix\newcommand\chapterx*[1]{% + \glsresetall + \chapter*{#1} + \addcontentsline{toc}{chapter}{#1} + \markboth{#1}{#1} +} + +\def\compilationversion{} + +\input{src/commands} +%}}} + +%{{{ glossaries " +\makeglossaries +\setabbreviationstyle[acronym]{short-long} + +\input{src/acronyms} +%}}} diff --git a/src/references.bib b/src/references.bib new file mode 100644 index 0000000..cfa17b5 --- /dev/null +++ b/src/references.bib @@ -0,0 +1,2457 @@ + +@book{ref:abrahams2004, + title = {C++ Template Metaprogramming: Concepts, Tools, and Techniques from Boost and Beyond}, + shorttitle = {C++ Template Metaprogramming}, + author = {Abrahams, David and Gurtovoy, Aleksey}, + date = {2004}, + edition = {5. printing}, + publisher = {{Addison-Wesley}}, + location = {{Boston, Mass}}, + annotation = {OCLC: 553640289}, + isbn = {978-0-321-22725-6}, + langid = {english}, + pagetotal = {373}, + series = {The {{C}}++ In-Depth Series} +} + +@inproceedings{ref:ahmad1997, + title = {Automatic Parallelization and Scheduling of Programs on Multiprocessors Using {{CASCH}}}, + booktitle = {Proceedings of the {{International Conference}} on {{Parallel Processing}}}, + author = {Ahmad, Ishfaq and Kwok, Yu-Kwong and Wu, Min-You and Shu, Wei}, + date = {1997}, + pages = {288--291}, + publisher = {{IEEE Computer Society}}, + location = {{Washington, DC, USA}}, + doi = {10.1109/ICPP.1997.622657}, + isbn = {978-0-8186-8108-0}, + langid = {english}, + series = {{{ICPP}} '97} +} + +@book{ref:aho1986, + title = {Compilers: Principles, Techniques, and Tools}, + shorttitle = {Compilers}, + author = {Aho, Alfred V and Sethi, Ravi and Ullman, Jeffrey D}, + date = {1986}, + publisher = {{Pearson Education}}, + location = {{India}}, + annotation = {OCLC: 57725385}, + isbn = {978-81-7808-046-8}, + langid = {english} +} + +@inproceedings{ref:aldinucci2009, + title = {Efficient Streaming Applications on Multi-Core with {{FastFlow}}: The Biosequence Alignment Test-Bed}, + booktitle = {{{ParCo}} 2009: {{Parallel Computing}}}, + author = {Aldinucci, Marco and Danelutto, Marco and Meneghin, Massimiliano and Torquati, Massimo and Kilpatrick, Peter}, + date = {2009-09}, + volume = {19}, + pages = {273--280}, + doi = {10.3233/978-1-60750-530-3-273}, + langid = {english} +} + +@article{ref:alexandrescu2000, + title = {Traits: The Else-If-Then of Types}, + author = {Alexandrescu, Andrei}, + date = {2000}, + journaltitle = {C++ Report}, + volume = {12}, + langid = {english}, + number = {4} +} + +@book{ref:alexandrescu2001, + title = {Modern {{C}}++ Design: Generic Programming and Design Patterns Applied}, + shorttitle = {Modern {{C}}++ Design}, + author = {Alexandrescu, Andrei}, + date = {2001}, + edition = {2. print}, + publisher = {{Addison-Wesley}}, + location = {{Boston, Mass.}}, + annotation = {OCLC: 747927476}, + isbn = {0-201-70431-5}, + langid = {english}, + pagetotal = {352}, + series = {The {{C}}++ In-Depth Series} +} + +@inproceedings{ref:amdahl1967, + title = {Validity of the Single Processor Approach to Achieving Large Scale Computing Capabilities}, + booktitle = {Proceedings of the {{April}} 18-20, 1967, Spring Joint Computer Conference on - {{AFIPS}} '67 ({{Spring}})}, + author = {Amdahl, Gene M.}, + date = {1967}, + pages = {483}, + publisher = {{ACM Press}}, + location = {{Atlantic City, New Jersey}}, + doi = {10.1145/1465482.1465560}, + eventtitle = {The {{April}} 18-20, 1967, Spring Joint Computer Conference}, + langid = {english} +} +% == BibLateX quality report for ref:amdahl1967: +% ? Unsure about the formatting of the booktitle + +@inproceedings{ref:arabnejad2018, + title = {{{AutoPar}}-{{Clava}}: {{An Automatic Parallelization}} Source-to-Source Tool for {{C}} Code Applications}, + shorttitle = {{{AutoPar}}-{{Clava}}}, + booktitle = {Proceedings of the 9th {{Workshop}} and 7th {{Workshop}} on {{Parallel Programming}} and {{RunTime Management Techniques}} for {{Manycore Architectures}} and {{Design Tools}} and {{Architectures}} for {{Multicore Embedded Computing Platforms}} - {{PARMA}}-{{DITAM}} '18}, + author = {Arabnejad, Hamid and Bispo, Jo\~ao and Barbosa, Jorge G. and Cardoso, Jo\~ao M.P.}, + date = {2018}, + pages = {13--19}, + publisher = {{ACM Press}}, + location = {{Manchester, United Kingdom}}, + doi = {10.1145/3183767.3183770}, + eventtitle = {The 9th {{Workshop}} and 7th {{Workshop}}}, + isbn = {978-1-4503-6444-7}, + langid = {english} +} + +@inproceedings{ref:artigas2000, + title = {Automatic Loop Transformations and Parallelization for {{Java}}}, + booktitle = {Proceedings of the 14th International Conference on {{Supercomputing}} - {{ICS}} '00}, + author = {Artigas, Pedro V. and Gupta, Manish and Midkiff, Samuel P. and Moreira, Jos\'e E.}, + date = {2000}, + pages = {1--10}, + publisher = {{ACM Press}}, + location = {{Santa Fe, New Mexico, United States}}, + doi = {10.1145/335231.335232}, + eventtitle = {The 14th International Conference}, + isbn = {978-1-58113-270-0}, + langid = {english} +} +% == BibLateX quality report for ref:artigas2000: +% ? Unsure about the formatting of the booktitle + +@inproceedings{ref:asai2014, + title = {Compiling a Reflective Language Using {{MetaOCaml}}}, + booktitle = {Proceedings of the 2014 {{International Conference}} on {{Generative Programming}}: {{Concepts}} and {{Experiences}} - {{GPCE}} 2014}, + author = {Asai, Kenichi}, + date = {2014}, + pages = {113--122}, + publisher = {{ACM Press}}, + doi = {10.1145/2658761.2658775}, + eventtitle = {The 2014 {{International Conference}}}, + isbn = {978-1-4503-3161-6}, + langid = {english} +} + +@article{ref:bachelet2013, + title = {Template Metaprogramming Techniques for Concept-Based Specialization}, + author = {Bachelet, Bruno and Mahul, Antoine and Yon, Lo\"ic}, + date = {2013}, + journaltitle = {Scientific Programming}, + volume = {21}, + pages = {43--61}, + issn = {1058-9244, 1875-919X}, + doi = {10.1155/2013/581397}, + langid = {english}, + number = {1-2} +} +% == BibLateX quality report for ref:bachelet2013: +% 'issn': not a valid ISSN + +@thesis{ref:bachelet2016, + title = {Flexibilit\'e et Performance de Codes de Calcul En Optimisation et Simulation}, + author = {Bachelet, Bruno}, + date = {2016}, + institution = {{Universit\'e Blaise Pascal - Clermont-Ferrand II}}, + langid = {english} +} +% == BibLateX quality report for ref:bachelet2016: +% Missing required field 'type' + +@article{ref:bachelet2017, + title = {Designing Expression Templates with Concepts}, + shorttitle = {Designing Expression Templates with Concepts}, + author = {Bachelet, Bruno and Yon, Lo\"ic}, + date = {2017-11}, + journaltitle = {Software: Practice and Experience}, + volume = {47}, + pages = {1521--1537}, + issn = {00380644}, + doi = {10.1002/spe.2483}, + langid = {english}, + number = {11} +} + +@article{ref:barney2009, + title = {{{POSIX}} Threads Programming}, + author = {Barney, Blaise}, + date = {2009}, + journaltitle = {Lawrence Livermore National Laborator}, + pages = {26}, + langid = {english} +} + +@article{ref:batcher1980, + title = {Design of a Massively Parallel Processor}, + author = {Batcher, Kenneth}, + date = {1980-09}, + journaltitle = {IEEE Transactions on Computers}, + volume = {C-29}, + pages = {836--840}, + issn = {0018-9340}, + doi = {10.1109/TC.1980.1675684}, + number = {9} +} + +@article{ref:batlle2002, + title = {A {{New FPGA}}/{{DSP}}-{{Based Parallel Architecture}} for {{Real}}-{{Time Image Processing}}}, + author = {Batlle, J}, + date = {2002-10}, + journaltitle = {Real-Time Imaging}, + volume = {8}, + pages = {345--356}, + issn = {10772014}, + doi = {10.1006/rtim.2001.0273}, + langid = {english}, + number = {5} +} +% == BibLateX quality report for ref:batlle2002: +% ? Title looks like it was stored in title-case in Zotero + +@inproceedings{ref:beard2015, + title = {{{RaftLib}}: A {{C}}++ Template Library for High Performance Stream Parallel Processing}, + shorttitle = {{{RaftLib}}}, + author = {Beard, Jonathan C. and Li, Peng and Chamberlain, Roger D.}, + date = {2015}, + pages = {96--105}, + publisher = {{ACM}}, + location = {{New York, NY, USA}}, + doi = {10.1145/2712386.2712400}, + isbn = {978-1-4503-3404-4}, + langid = {english}, + series = {{{PMAM}} '15} +} +% == BibLateX quality report for ref:beard2015: +% Missing required field 'booktitle' + +@incollection{ref:benoit2005, + title = {Two Fundamental Concepts in Skeletal Parallel Programming}, + booktitle = {Computational {{Science}} \textendash{} {{ICCS}} 2005}, + author = {Benoit, Anne and Cole, Murray}, + date = {2005}, + volume = {3515}, + pages = {764--771}, + publisher = {{Springer Berlin Heidelberg}}, + location = {{Berlin, Heidelberg}}, + doi = {10.1007/11428848_98}, + isbn = {978-3-540-26043-1}, + langid = {english} +} +% == BibLateX quality report for ref:benoit2005: +% Missing required field 'editor' + +@article{ref:bernstein1966, + title = {Analysis of Programs for Parallel Processing}, + author = {Bernstein, A. J.}, + date = {1966-10}, + journaltitle = {IEEE Transactions on Electronic Computers}, + volume = {EC-15}, + pages = {757--763}, + issn = {0367-7508}, + doi = {10.1109/PGEC.1966.264565}, + langid = {english}, + number = {5} +} + +@inproceedings{ref:binkley2007, + title = {Source Code Analysis: A Road Map}, + shorttitle = {Source {{Code Analysis}}}, + booktitle = {Future of {{Software Engineering}} ({{FOSE}} '07)}, + author = {Binkley, David}, + date = {2007-05}, + pages = {104--119}, + publisher = {{IEEE}}, + location = {{Minneapolis, MN, USA}}, + doi = {10.1109/FOSE.2007.27}, + eventtitle = {Future of {{Software Engineering}}}, + isbn = {978-0-7695-2829-8} +} + +@article{ref:blume1995, + title = {Effective Automatic Parallelization with {{Polaris}}}, + author = {Blume, William and Eigenmann, Rudolf and Faigin, Keith and Grout, John and Hoeflinger, Jay and Padua, David and Petersen, Paul and Pottenger, William and Rauchwerger, Lawrence and Tu, Peng and Weatherford, Stephen}, + date = {1995}, + journaltitle = {International Journal of Parallel Programming}, + langid = {english} +} + +@article{ref:blumofe1996, + title = {Cilk: An Efficient Multithreaded Runtime System}, + shorttitle = {Cilk}, + author = {Blumofe, Robert D. and Joerg, Christopher F. and Kuszmaul, Bradley C. and Leiserson, Charles E. and Randall, Keith H. and Zhou, Yuli}, + date = {1996-08}, + journaltitle = {Journal of Parallel and Distributed Computing}, + volume = {37}, + pages = {55--69}, + issn = {07437315}, + doi = {10.1006/jpdc.1996.0107}, + langid = {english}, + number = {1} +} + +@inproceedings{ref:bondhugula2008, + title = {A Practical Automatic Polyhedral Parallelizer and Locality Optimizer}, + booktitle = {Proceedings of the 2008 {{ACM SIGPLAN}} Conference on {{Programming}} Language Design and Implementation - {{PLDI}} '08}, + author = {Bondhugula, Uday and Hartono, Albert and Ramanujam, J. and Sadayappan, P.}, + date = {2008}, + pages = {101}, + publisher = {{ACM Press}}, + location = {{Tucson, AZ, USA}}, + doi = {10.1145/1375581.1375595}, + eventtitle = {The 2008 {{ACM SIGPLAN}} Conference}, + isbn = {978-1-59593-860-2}, + langid = {english} +} +% == BibLateX quality report for ref:bondhugula2008: +% ? Unsure about the formatting of the booktitle + +@online{ref:boostlambda, + title = {Boost.{{Lambda}}}, + author = {J\"arvi, Jaakko and Powell, Gary}, + date = {2003}, + url = {https://www.boost.org/doc/libs/1_73_0/doc/html/lambda.html}, + urldate = {2020-06-10}, + annotation = {boostlambda} +} + +@inproceedings{ref:bringmann1993, + title = {Speculative Execution Exception Recovery Using Write-Back Suppression}, + booktitle = {Proceedings of the 26th {{Annual International Symposium}} on {{Microarchitecture}}}, + author = {Bringmann, Roger A. and Mahlke, Scott A. and Hank, Richard E. and Gyllenhaal, John C. and Hwu, Wen-mei W.}, + date = {1993}, + pages = {214--223}, + publisher = {{IEEE}}, + location = {{Austin, TX, USA}}, + doi = {10.1109/MICRO.1993.282757}, + eventtitle = {Proceedings of 26th {{Annual International Symposium}} on {{Microarchitecture}} ({{Cat}}. {{No}}.{{93TH0602}}-3)}, + isbn = {978-0-8186-5280-6} +} + +@book{ref:butenhof1997, + title = {Programming with {{POSIX}} Threads}, + author = {Butenhof, David R.}, + date = {1997}, + publisher = {{Addison-Wesley}}, + location = {{Reading, Mass}}, + isbn = {978-0-201-63392-4}, + pagetotal = {381}, + series = {Addison-{{Wesley}} Professional Computing Series} +} + +@report{ref:campbell1996, + title = {Towards the Classification of Algorithmic Skeletons}, + author = {Campbell, Duncan K. G.}, + date = {1996}, + institution = {{University of York department of computer science YCS}}, + langid = {english}, + number = {YCS 276} +} +% == BibLateX quality report for ref:campbell1996: +% Missing required field 'type' + +@thesis{ref:caux2012, + title = {Parall\'elisation et optimisation d'un simulateur de morphog\'en\`ese d'organes. Application aux \'el\'ements du rein}, + author = {Caux, Jonathan}, + date = {2012-10}, + institution = {{Universit\'e Blaise Pascal}}, + url = {https://tel.archives-ouvertes.fr/tel-00932303/}, + langid = {french}, + pagetotal = {267} +} +% == BibLateX quality report for ref:caux2012: +% Missing required field 'type' + +@article{ref:chamberlain2007, + title = {Parallel Programmability and the {{Chapel}} Language}, + author = {Chamberlain, B.L. and Callahan, D. and Zima, H.P.}, + date = {2007-08}, + journaltitle = {The International Journal of High Performance Computing Applications}, + volume = {21}, + pages = {291--312}, + issn = {1094-3420, 1741-2846}, + doi = {10.1177/1094342007078442}, + langid = {english}, + number = {3} +} +% == BibLateX quality report for ref:chamberlain2007: +% 'issn': not a valid ISSN + +@article{ref:chan2004, + title = {Run-Time Support for the Automatic Parallelization of {{Java}} Programs}, + author = {Chan, Bryan and Abdelrahman, Tarek S.}, + date = {2004-04}, + journaltitle = {The Journal of Supercomputing}, + volume = {28}, + pages = {91--117}, + issn = {0920-8542}, + doi = {10.1023/B:SUPE.0000014804.20789.21}, + langid = {english}, + number = {1} +} + +@article{ref:ciechanowicz2009, + title = {The {{M\"unster}} Skeleton Library {{Muesli}} - a Comprehensive Overview}, + author = {Ciechanowicz, Philipp and Poldner, Michael and Kuchen, Herbert}, + date = {2009}, + langid = {english} +} +% == BibLateX quality report for ref:ciechanowicz2009: +% Missing required field 'journaltitle' + +@book{ref:cole1989, + title = {Algorithmic Skeletons: Structured Management of Parallel Computation}, + shorttitle = {Algorithmic {{Skeletons}}}, + author = {Cole, Murray}, + date = {1989}, + publisher = {{MIT Press}}, + location = {{Cambridge, MA, USA}}, + isbn = {978-0-262-53086-6} +} + +@article{ref:collard1995, + title = {Automatic Parallelization of While-Loops Using Speculative Execution}, + author = {Collard, Jean-Fran\c{c}ois}, + date = {1995-04}, + journaltitle = {International Journal of Parallel Programming}, + volume = {23}, + pages = {191--219}, + issn = {0885-7458, 1573-7640}, + doi = {10.1007/BF02577789}, + langid = {english}, + number = {2} +} +% == BibLateX quality report for ref:collard1995: +% 'issn': not a valid ISSN + +@inproceedings{ref:cordes2010, + title = {Automatic Parallelization of Embedded Software Using Hierarchical Task Graphs and Integer Linear Programming}, + booktitle = {International {{Conference}} on {{Hardware Software Codesign}}}, + author = {Cordes, Daniel and Marwedel, Peter and Mallik, Arindam}, + date = {2010}, + pages = {267--276}, + langid = {english} +} +% == BibLateX quality report for ref:cordes2010: +% ? Unsure about the formatting of the booktitle + +@article{ref:courtois1971, + title = {Concurrent Control with ``Readers'' and ``Writers''}, + author = {Courtois, P. J. and Heymans, F. and Parnas, D. L.}, + date = {1971-10-01}, + journaltitle = {Communications of the ACM}, + volume = {14}, + pages = {667--668}, + issn = {00010782}, + doi = {10.1145/362759.362813}, + number = {10} +} + +@online{ref:cppref_ref_decl, + title = {Reference Declaration}, + author = {{cppreference}}, + date = {2011}, + url = {https://en.cppreference.com/w/cpp/language/reference#Reference_collapsing}, + urldate = {2019-11-06}, + annotation = {cppref ref decl} +} + +@patent{ref:cray1978, + title = {Computer Vector Register Processing}, + author = {Cray, Seymour R}, + date = {1978}, + pages = {19}, + langid = {english}, + number = {4128880}, + type = {patent} +} +% == BibLateX quality report for ref:cray1978: +% Unexpected field 'pages' + +@book{ref:culler1997, + title = {Parallel Computer Architecture: A Hardware/Software Approach}, + shorttitle = {Parallel Computer Architecture}, + author = {Culler, David E. and Singh, Jaswinder Pal and Gupta, Anoop}, + date = {1997}, + edition = {Nachdr.}, + publisher = {{Kaufmann}}, + location = {{San Francisco, Calif}}, + annotation = {OCLC: 255755318}, + isbn = {978-1-55860-343-1}, + langid = {english}, + pagetotal = {879} +} + +@article{ref:dagum1998, + title = {{{OpenMP}}: An Industry Standard {{API}} for Shared-Memory Programming}, + shorttitle = {{{OpenMP}}}, + author = {Dagum, L. and Menon, R.}, + date = {1998-01}, + journaltitle = {IEEE Computational Science and Engineering}, + volume = {5}, + pages = {46--55}, + issn = {1070-9924}, + doi = {10.1109/99.660313}, + langid = {english}, + number = {1} +} + +@article{ref:dahl1966, + title = {{{SIMULA}}: An {{ALGOL}}-Based Simulation Language}, + shorttitle = {{{SIMULA}}}, + author = {Dahl, Ole-Johan and Nygaard, Kristen}, + date = {1966-09-01}, + journaltitle = {Communications of the ACM}, + volume = {9}, + pages = {671--678}, + issn = {00010782}, + doi = {10.1145/365813.365819}, + langid = {english}, + number = {9} +} + +@article{ref:dantzig1954, + title = {Solution of a Large-Scale Traveling-Salesman Problem}, + author = {Dantzig, George and Fulkerson, Ray and Johnson, Selmer}, + date = {1954}, + journaltitle = {Journal of the operations research society of America}, + edition = {Informs}, + pages = {393--410}, + entrysubtype = {newspaper} +} +% == BibLateX quality report for ref:dantzig1954: +% Unexpected field 'edition' + +@inproceedings{ref:davidson1995, + title = {Improving Instruction-Level Parallelism by Loop Unrolling and Dynamic Memory Disambiguation}, + booktitle = {Proceedings of the 28th {{Annual International Symposium}} on {{Microarchitecture}}}, + author = {Davidson, J.W. and Jinturkar, S.}, + date = {1995-11}, + pages = {125--132}, + publisher = {{IEEE}}, + location = {{Ann Arbor, MI, USA}}, + doi = {10.1109/MICRO.1995.476820}, + eventtitle = {Proceedings of {{MICRO}}'95: 28th {{Annual IEEE}}/{{ACM International Symposium}} on {{Microarchitecture}}}, + isbn = {978-0-8186-7349-8} +} + +@incollection{ref:dietz2010, + title = {{{MIMD Interpretation}} on a {{GPU}}}, + booktitle = {Languages and {{Compilers}} for {{Parallel Computing}}}, + author = {Dietz, Henry G. and Young, B. Dalton}, + editor = {Gao, Guang R. and Pollock, Lori L. and Cavazos, John and Li, Xiaoming}, + date = {2010}, + volume = {5898}, + pages = {65--79}, + publisher = {{Springer Berlin Heidelberg}}, + location = {{Berlin, Heidelberg}}, + doi = {10.1007/978-3-642-13374-9_5}, + editorb = {Hutchison, David and Kanade, Takeo and Kittler, Josef and Kleinberg, Jon M. and Mattern, Friedemann and Mitchell, John C. and Naor, Moni and Nierstrasz, Oscar and Pandu Rangan, C. and Steffen, Bernhard and Sudan, Madhu and Terzopoulos, Demetri and Tygar, Doug and Vardi, Moshe Y. and Weikum, Gerhard}, + editorbtype = {redactor}, + isbn = {978-3-642-13373-2 978-3-642-13374-9}, + series = {Lecture {{Notes}} in {{Computer Science}}} +} +% == BibLateX quality report for ref:dietz2010: +% 'isbn': not a valid ISBN +% ? Title looks like it was stored in title-case in Zotero + +@incollection{ref:dijkstra1968, + title = {Cooperating Sequential Processes}, + booktitle = {The {{Origin}} of {{Concurrent Programming}}}, + author = {Dijkstra, Edsger W.}, + editor = {Hansen, Per Brinch}, + date = {1968}, + pages = {65--138}, + publisher = {{Springer New York}}, + location = {{New York, NY}}, + doi = {10.1007/978-1-4757-3472-0_2}, + isbn = {978-1-4419-2986-0}, + langid = {english} +} + +@online{ref:dimov2002, + title = {The Forwarding Problem: Arguments}, + shorttitle = {N1385}, + author = {Dimov, Peter and Hinnant, Howard E. and Abrahams, David}, + date = {2002-09}, + url = {http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1385.htm}, + annotation = {dimov2002}, + langid = {english} +} + +@inproceedings{ref:dittamo2007, + title = {Parallelization of {{C}}\# Programs through Annotations}, + booktitle = {Computational {{Science}} \textendash{} {{ICCS}} 2007}, + author = {Dittamo, Cristian and Cisternino, Antonio and Danelutto, Marco}, + editor = {Shi, Yong and family=Albada, given=Geert Dick, prefix=van, useprefix=true and Dongarra, Jack and Sloot, Peter M. A.}, + date = {2007}, + volume = {4488}, + pages = {585--592}, + publisher = {{Springer Berlin Heidelberg}}, + location = {{Berlin, Heidelberg}}, + doi = {10.1007/978-3-540-72586-2_86}, + editorb = {Hutchison, David and Kanade, Takeo and Kittler, Josef and Kleinberg, Jon M. and Mattern, Friedemann and Mitchell, John C. and Naor, Moni and Nierstrasz, Oscar and Rangan, C. Pandu and Steffen, Bernhard and Sudan, Madhu and Terzopoulos, Demetri and Tygar, Doug and Vardi, Moshe Y. and Weikum, Gerhard}, + editorbtype = {redactor}, + series = {Lecture {{Notes}} in {{Computer Science}}} +} +% == BibLateX quality report for ref:dittamo2007: +% Unexpected field 'editorb' +% Unexpected field 'editorbtype' +% ? Unsure about the formatting of the booktitle + +@inproceedings{ref:drummond2009, + title = {Replicability Is Not Reproducibility: Nor Is It Good Science}, + booktitle = {Proceedings of the {{Evaluation Methods}} for {{Machine Learning Workshop}}}, + author = {Drummond, Chris}, + date = {2009}, + pages = {696--701}, + location = {{Montreal, Quebec, Canada}}, + eventtitle = {26th {{International Conference}} for {{Machine Learning}}}, + langid = {english} +} + +@article{ref:ernstsson2018, + title = {{{SkePU}} 2: Flexible and Type-Safe Skeleton Programming for Heterogeneous Parallel Systems}, + shorttitle = {{{SkePU}} 2}, + author = {Ernstsson, August and Li, Lu and Kessler, Christoph}, + date = {2018-02}, + journaltitle = {International Journal of Parallel Programming}, + volume = {46}, + pages = {62--80}, + issn = {0885-7458, 1573-7640}, + doi = {10.1007/s10766-017-0490-5}, + langid = {english}, + number = {1} +} +% == BibLateX quality report for ref:ernstsson2018: +% 'issn': not a valid ISSN + +@article{ref:esterie2014, + title = {The Numerical Template Toolbox: A Modern {{C}}++ Design for Scientific Computing}, + shorttitle = {The Numerical Template Toolbox}, + author = {Est\'erie, Pierre and Falcou, Joel and Gaunard, Mathias and Laprest\'e, Jean-Thierry and Lacassagne, Lionel}, + date = {2014-12}, + journaltitle = {Journal of Parallel and Distributed Computing}, + volume = {74}, + pages = {3240--3253}, + issn = {07437315}, + doi = {10.1016/j.jpdc.2014.07.002}, + langid = {english}, + number = {12} +} + +@article{ref:falcou2006a, + title = {Quaff: Efficient {{C}}++ Design for Parallel Skeletons}, + shorttitle = {Quaff}, + author = {Falcou, J. and S\'erot, J. and Chateau, T. and Laprest\'e, J. T.}, + date = {2006-09}, + journaltitle = {Parallel Computing}, + shortjournal = {Parallel Computing}, + volume = {32}, + pages = {604--615}, + issn = {0167-8191}, + doi = {10.1016/j.parco.2006.06.001}, + langid = {english}, + number = {7}, + series = {Algorithmic {{Skeletons}}} +} + +@incollection{ref:falcou2008, + title = {Meta-Programming Applied to Automatic {{SMP}} Parallelization of Linear Algebra Code}, + booktitle = {Euro-{{Par}} 2008 \textendash{} {{Parallel Processing}}}, + author = {Falcou, Joel and S\'erot, Jocelyn and Pech, Lucien and Laprest\'e, Jean-Thierry}, + date = {2008}, + volume = {5168}, + pages = {729--738}, + publisher = {{Springer Berlin Heidelberg}}, + location = {{Berlin, Heidelberg}}, + doi = {10.1007/978-3-540-85451-7_78}, + isbn = {978-3-540-85450-0}, + langid = {english}, + series = {Lecture {{Notes}} in {{Computer Science}}} +} +% == BibLateX quality report for ref:falcou2008: +% Missing required field 'editor' + +@article{ref:feo1989, + title = {A Probabilistic Heuristic for a Computationally Difficult Set Covering Problem}, + author = {Feo, Thomas A. and Resende, Mauricio G. C.}, + date = {1989-04}, + journaltitle = {Operations Research Letters}, + volume = {8}, + pages = {67--71}, + issn = {01676377}, + doi = {10.1016/0167-6377(89)90002-3}, + langid = {english}, + number = {2} +} + +@article{ref:ferrante1987, + title = {The Program Dependence Graph and {{Its}} Use in Optimization}, + author = {Ferrante, Jeanne and Ottenstein, Karl J. and Warren, Joe D.}, + date = {1987-07}, + journaltitle = {ACM Transactions on Programming Languages and Systems}, + volume = {9}, + pages = {319--349}, + issn = {0164-0925}, + doi = {10.1145/24039.24041}, + langid = {english}, + number = {3} +} + +@article{ref:flynn1972, + title = {Some Computer Organizations and Their Effectiveness}, + author = {Flynn, Michael J.}, + date = {1972-09}, + journaltitle = {IEEE Transactions on Computers}, + volume = {C-21}, + pages = {948--960}, + issn = {0018-9340}, + doi = {10.1109/TC.1972.5009071}, + number = {9} +} + +@article{ref:fonseca2016, + title = {Automatic Parallelization: Executing Sequential Programs on a Task-Based Parallel Runtime}, + shorttitle = {Automatic {{Parallelization}}}, + author = {Fonseca, Alcides and Cabral, Bruno and Rafael, Jo\~ao and Correia, Ivo}, + date = {2016-12}, + journaltitle = {International Journal of Parallel Programming}, + volume = {44}, + pages = {1337--1358}, + issn = {0885-7458, 1573-7640}, + doi = {10.1007/s10766-016-0426-5}, + langid = {english}, + number = {6} +} +% == BibLateX quality report for ref:fonseca2016: +% 'issn': not a valid ISSN + +@inproceedings{ref:frumkin1998, + title = {A Comparison of Automatic Parallelization Tools/Compilers on the {{SGI Origin}} 2000}, + booktitle = {Proceedings of the 1998 {{ACM}}/{{IEEE Conference}} on {{Supercomputing}}}, + author = {Frumkin, Michael and Hribar, Michelle and Jin, Haoqiang and Waheed, Abdul and Yan, Jerry}, + date = {1998}, + pages = {1--22}, + publisher = {{IEEE Computer Society}}, + location = {{Washington, DC, USA}}, + url = {http://dl.acm.org/citation.cfm?id=509058.509119}, + urldate = {2018-02-15}, + isbn = {978-0-89791-984-5}, + langid = {english}, + series = {{{SC}} '98} +} + +@book{ref:gamma1995, + title = {Design Patterns: Elements of Reusable Software Architecture}, + author = {Gamma, Erich and Helm, Richard and Johnson, Ralph and Vlissides, John}, + date = {1995}, + edition = {Addison-Wesley} +} + +@inproceedings{ref:geuns2011, + title = {Parallelization of While Loops in Nested Loop Programs for Shared-Memory Multiprocessor Systems}, + booktitle = {2011 {{Design}}, {{Automation}} \& {{Test}} in {{Europe}}}, + author = {Geuns, S J and Bekooij, M J G and Bijlsma, T and Corporaal, H}, + date = {2011-03}, + pages = {1--6}, + publisher = {{IEEE}}, + location = {{Grenoble}}, + doi = {10.1109/DATE.2011.5763118}, + eventtitle = {2011 {{Design}}, {{Automation}} \& {{Test}} in {{Europe}}}, + isbn = {978-3-9810801-8-6} +} +% == BibLateX quality report for ref:geuns2011: +% ? Unsure about the formatting of the booktitle + +@article{ref:gingras1990, + title = {Dining Philosophers Revisited}, + author = {Gingras, Armando R.}, + date = {1990-08}, + journaltitle = {ACM SIGCSE Bulletin}, + volume = {22}, + pages = {21}, + issn = {0097-8418}, + doi = {10.1145/101085.101091}, + langid = {english}, + number = {3} +} + +@article{ref:gordon1998, + title = {A Survey of Fast Exponentiation Methods}, + author = {Gordon, Daniel M.}, + date = {1998-04}, + journaltitle = {Journal of Algorithms}, + volume = {27}, + pages = {129--146}, + issn = {01966774}, + doi = {10.1006/jagm.1997.0913}, + langid = {english}, + number = {1} +} + +@incollection{ref:griebl1995, + title = {Generation of Synchronous Code for Automatic Parallelization of While Loops}, + booktitle = {{{EURO}}-{{PAR}} '95 {{Parallel Processing}}}, + author = {Griebl, Martin and Collard, Jean-Fran\c{c}ois}, + editor = {Haridi, Seif and Ali, Khayri and Magnusson, Peter}, + date = {1995}, + volume = {966}, + pages = {313--326}, + publisher = {{Springer Berlin Heidelberg}}, + location = {{Berlin, Heidelberg}}, + doi = {10.1007/BFb0020474}, + editorb = {Goos, Gerhard and Hartmanis, Juris and family=Leeuwen, given=Jan, prefix=van, useprefix=true}, + editorbtype = {redactor}, + isbn = {978-3-540-60247-7}, + langid = {english}, + series = {Lecture {{Notes}} in {{Computer Science}}} +} + +@inproceedings{ref:griebl1998, + title = {Code Generation in the Polytope Model}, + booktitle = {Proceedings. 1998 {{International Conference}} on {{Parallel Architectures}} and {{Compilation Techniques}} ({{Cat}}. {{No}}.{{98EX192}})}, + author = {Griebl, M. and Lengauer, C. and Wetzel, S.}, + date = {1998}, + pages = {106--111}, + publisher = {{IEEE Comput. Soc}}, + location = {{Paris, France}}, + doi = {10.1109/PACT.1998.727179}, + eventtitle = {1998 {{International Conference}} on {{Parallel Architectures}} and {{Compilation Techniques}}}, + isbn = {978-0-8186-8591-0} +} +% == BibLateX quality report for ref:griebl1998: +% ? Unsure about the formatting of the booktitle + +@article{ref:gropp1996, + title = {A High-Performance, Portable Implementation of the {{MPI}} Message Passing Interface Standard}, + author = {Gropp, William and Lusk, Ewing and Doss, Nathan and Skjellum, Anthony}, + date = {1996-09}, + journaltitle = {Parallel Computing}, + shortjournal = {Parallel Computing}, + volume = {22}, + pages = {789--828}, + issn = {0167-8191}, + doi = {10.1016/0167-8191(96)00024-5}, + langid = {english}, + number = {6} +} + +@inproceedings{ref:gustafson1988, + title = {The Scaled-Sized Model: A Revision of {{Amdahl}}'s Law}, + booktitle = {Supercomputing}, + author = {Gustafson, John L}, + date = {1988}, + pages = {130--133}, + location = {{Boston, MA, USA}}, + eventtitle = {International Conference on Supercomputing}, + langid = {english} +} +% == BibLateX quality report for ref:gustafson1988: +% ? Unsure about the formatting of the booktitle + +@article{ref:halstead1985, + title = {{{MULTILISP}}: A Language for Concurrent Symbolic Computation}, + shorttitle = {{{MULTILISP}}}, + author = {Halstead, Robert H.}, + date = {1985-10}, + journaltitle = {ACM Transactions on Programming Languages and Systems (TOPLAS)}, + volume = {7}, + pages = {501--538}, + issn = {0164-0925, 1558-4593}, + doi = {10.1145/4472.4478}, + langid = {english}, + number = {4} +} +% == BibLateX quality report for ref:halstead1985: +% 'issn': not a valid ISSN + +@incollection{ref:hardtlein2005, + title = {Fast {{Expression Templates}}: Object-Oriented {{High Performance Computing}}}, + booktitle = {Lecture {{Notes}} in {{Computer Science}}}, + author = {H\"ardtlein, Jochen and Linke, Alexander and Pflaum, Christoph}, + date = {2005}, + edition = {Springer-Verlag}, + pages = {1055--1063}, + langid = {english} +} +% == BibLateX quality report for ref:hardtlein2005: +% Missing required field 'editor' + +@inproceedings{ref:hill1998, + title = {Practical Barrier Synchronisation}, + booktitle = {Proceedings of the {{Sixth Euromicro Workshop}} on {{Parallel}} and {{Distributed Processing}} - {{PDP}} '98 -}, + author = {Hill, J.M.D. and Skillicorn, D.B.}, + date = {1998}, + pages = {438--444}, + publisher = {{IEEE Comput. Soc}}, + location = {{Madrid, Spain}}, + doi = {10.1109/EMPDP.1998.647231}, + eventtitle = {Sixth {{Euromicro Workshop}} on {{Parallel}} and {{Distributed Processing}} - {{PDP}} '98 -}, + isbn = {978-0-8186-8332-9} +} + +@article{ref:hill2013, + title = {Distribution of Random Streams for Simulation Practitioners: {{CPE HPCS}} 2010 Special Issue Submission}, + shorttitle = {Distribution of Random Streams for Simulation Practitioners}, + author = {Hill, David R. C. and Mazel, Claude and Passerat-Palmbach, Jonathan and Traore, Mamadou K.}, + date = {2013-07}, + journaltitle = {Concurrency and Computation: Practice and Experience}, + volume = {25}, + pages = {1427--1442}, + issn = {15320626}, + doi = {10.1002/cpe.2942}, + langid = {english}, + number = {10} +} + +@article{ref:hill2015, + title = {Parallel Random Numbers, Simulation, and Reproducible Research}, + author = {Hill, David R. C.}, + date = {2015-07}, + journaltitle = {Computing in Science \& Engineering}, + volume = {17}, + pages = {66--71}, + issn = {1521-9615}, + doi = {10.1109/MCSE.2015.79}, + number = {4} +} + +@article{ref:hoare1971, + title = {Proof of a Program: {{FIND}}}, + shorttitle = {Proof of a Program}, + author = {Hoare, C. A. R.}, + date = {1971-01-01}, + journaltitle = {Communications of the ACM}, + volume = {14}, + pages = {39--45}, + issn = {00010782}, + doi = {10.1145/362452.362489}, + number = {1} +} + +@online{ref:hoberock2013, + title = {A Parallel Algorithms Library}, + shorttitle = {N3724}, + author = {Hoberock, Jared and Marathe, Jaydeep and Garland, Michael and Giroux, Olivier and Grover, Vinod and Laksberg, Artur and Sutter, Herb and Robison, Arch}, + date = {2013-08-30}, + url = {http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3724.pdf}, + annotation = {hoberock2013}, + langid = {english} +} + +@inproceedings{ref:horwitz1989, + title = {Dependence Analysis for Pointer Variables}, + booktitle = {Proceedings of the {{ACM SIGPLAN}} 1989 {{Conference}} on {{Programming}} Language Design and Implementation - {{PLDI}} '89}, + author = {Horwitz, S. and Pfeiffer, P. and Reps, T.}, + date = {1989}, + pages = {28--40}, + publisher = {{ACM Press}}, + location = {{Portland, Oregon, United States}}, + doi = {10.1145/73141.74821}, + eventtitle = {The {{ACM SIGPLAN}} 1989 {{Conference}}}, + isbn = {978-0-89791-306-5}, + langid = {english} +} + +@article{ref:howard1973, + title = {Mixed Solutions for the Deadlock Problem}, + author = {Howard, John H.}, + date = {1973-07-01}, + journaltitle = {Communications of the ACM}, + volume = {16}, + pages = {427--430}, + issn = {00010782}, + doi = {10.1145/362280.362290}, + number = {7} +} + +@article{ref:hwu1986, + title = {{{HPSm}}, a High Performance Restricted Data Flow Architecture Having Minimal Functionality}, + author = {Hwu, W. and Patt, Y. N.}, + date = {1986-06-01}, + journaltitle = {ACM SIGARCH Computer Architecture News}, + volume = {14}, + pages = {297--306}, + issn = {01635964}, + doi = {10.1145/17356.17391}, + langid = {english}, + number = {2} +} + +@inproceedings{ref:iglberger2012, + title = {High Performance Smart Expression Template Math Libraries}, + booktitle = {2012 {{International Conference}} on {{High Performance Computing}} \& {{Simulation}} ({{HPCS}})}, + author = {Iglberger, Klaus and Hager, Georg and Treibig, Jan and Rude, Ulrich}, + date = {2012-07}, + pages = {367--373}, + publisher = {{IEEE}}, + location = {{Madrid, Spain}}, + doi = {10.1109/HPCSim.2012.6266939}, + eventtitle = {2012 {{International Conference}} on {{High Performance Computing}} \& {{Simulation}} ({{HPCS}})}, + isbn = {978-1-4673-2362-8}, + langid = {english} +} +% == BibLateX quality report for ref:iglberger2012: +% ? Unsure about the formatting of the booktitle + +@article{ref:ioannidis2005, + title = {Why Most Published Research Findings Are False}, + author = {Ioannidis, John P. A.}, + date = {2005-08-30}, + journaltitle = {PLoS Medicine}, + volume = {2}, + pages = {e124}, + issn = {1549-1676}, + doi = {10.1371/journal.pmed.0020124}, + langid = {english}, + number = {8} +} + +@report{ref:iso2011, + title = {Programming Language - {{C}}++}, + author = {{ISO} and {C++ committee}}, + date = {2011-09}, + pages = {1338}, + institution = {{International Organization for Standardization}}, + url = {http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf}, + langid = {english}, + number = {ISO/IEC 14882:2011}, + type = {Standard} +} + +@report{ref:iso2020, + title = {Programming Language - {{C}}++}, + author = {{ISO} and {C++ committee}}, + date = {2020-01}, + pages = {1815}, + institution = {{International Organization for Standardization}}, + langid = {english}, + number = {ISO/IEC CD 14882}, + type = {Standard} +} + +@inproceedings{ref:jackson1992, + title = {Exploiting Bit-Level Parallelism in {{Boolean}} Matrix Operations for Graph Analysis}, + booktitle = {Proceedings {{IEEE Southeastcon}} '92}, + author = {Jackson, D.J. and Whiteside, D.M. and Wurtz, L.T.}, + date = {1992}, + pages = {838--841}, + publisher = {{IEEE}}, + location = {{Birmingham, AL, USA}}, + doi = {10.1109/SECON.1992.202252}, + eventtitle = {{{IEEE Southeastcon}} '92}, + isbn = {978-0-7803-0494-9} +} + +@article{ref:jarvi2001, + title = {Side Effects and Partial Function Application in {{C}}++}, + author = {J\"arvi, Jaakko and Powell, Gary}, + date = {2001}, + journaltitle = {Proceedings of the Multiparadigm Programming with OO Languages Workshop (MPOOL'01)}, + pages = {17}, + langid = {english} +} + +@article{ref:jarvi2003a, + title = {The Lambda Library: Unnamed Functions in {{C}}++}, + shorttitle = {The {{Lambda Library}}}, + author = {J\"arvi, Jaakko and Powell, Gary and Lumsdaine, Andrew}, + date = {2003-03}, + journaltitle = {Software: Practice and Experience}, + volume = {33}, + pages = {259--291}, + issn = {0038-0644}, + doi = {10.1002/spe.504}, + langid = {english}, + number = {3} +} + +@inproceedings{ref:jo2013, + title = {Automatic Vectorization of Tree Traversals}, + booktitle = {Proceedings of the 22nd {{International Conference}} on {{Parallel Architectures}} and {{Compilation Techniques}}}, + author = {Jo, Youngjoon and Goldfarb, Michael and Kulkarni, Milind}, + date = {2013}, + pages = {12}, + publisher = {{IEEE}}, + location = {{Edinburgh, UK}}, + doi = {10.1109/PACT.2013.6618832}, + eventtitle = {22nd {{International Conference}} on {{Parallel Architectures}} and {{Compilation Techniques}}}, + isbn = {978-1-4799-1021-2}, + langid = {english} +} + +@inproceedings{ref:kale1993, + title = {{{CHARM}}++: A Portable Concurrent Object Oriented System Based on {{C}}++}, + shorttitle = {{{CHARM}}++}, + booktitle = {Proceedings of the {{Conference}} on {{Object Oriented Programming Systems}}, {{Languages}} and {{Applications}}}, + author = {Kale, Laxmikant V. and Krishnan, Sanjeev}, + date = {1993}, + pages = {91--108}, + publisher = {{ACM Press}}, + doi = {10.1145/165854.165874}, + isbn = {978-0-89791-587-8}, + langid = {english} +} + +@article{ref:keller1975, + title = {Look-Ahead Processors}, + author = {Keller, Robert M.}, + date = {1975-12-01}, + journaltitle = {ACM Computing Surveys}, + volume = {7}, + pages = {177--195}, + issn = {03600300}, + doi = {10.1145/356654.356657}, + number = {4} +} + +@article{ref:kennedy1994, + title = {Compiler Technology for Machine-Indepenent Parallel Programming}, + author = {Kennedy, Ken}, + date = {1994-02}, + journaltitle = {International Journal of Parallel Programming}, + volume = {22}, + pages = {79--98}, + issn = {0885-7458, 1573-7640}, + doi = {10.1007/BF02577793}, + langid = {english}, + number = {1} +} +% == BibLateX quality report for ref:kennedy1994: +% 'issn': not a valid ISSN + +@book{ref:kernighan1988, + title = {The {{C}} Programming Language}, + author = {Kernighan, Brian W and Ritchie, Dennis M}, + date = {1988}, + publisher = {{Prentice Hall}}, + location = {{Upper Saddle River, NJ}}, + annotation = {OCLC: 1047831315}, + isbn = {978-0-13-110362-7}, + langid = {english} +} + +@article{ref:kirby2003, + title = {A New Look at Expression Templates for Maxtrix Computation}, + author = {Kirby, R.C.}, + date = {2003-05}, + journaltitle = {Computing in Science \& Engineering}, + volume = {5}, + pages = {66--70}, + issn = {1521-9615}, + doi = {10.1109/MCISE.2003.1196309}, + langid = {english}, + number = {3} +} + +@article{ref:kish2002, + title = {End of {{Moore}}'s Law: Thermal (Noise) Death of Integration in Micro and Nano Electronics}, + shorttitle = {End of {{Moore}}'s Law}, + author = {Kish, Laszlo B}, + date = {2002-12}, + journaltitle = {Physics Letters A}, + volume = {305}, + pages = {144--149}, + issn = {03759601}, + doi = {10.1016/S0375-9601(02)01365-8}, + langid = {english}, + number = {3-4} +} + +@inproceedings{ref:klint2009, + title = {{{RASCAL}}: A Domain Specific Language for Source Code Analysis and Manipulation}, + shorttitle = {{{RASCAL}}}, + booktitle = {2009 {{Ninth IEEE International Working Conference}} on {{Source Code Analysis}} and {{Manipulation}}}, + author = {Klint, Paul and family=Storm, given=Tijs, prefix=van der, useprefix=false and Vinju, Jurgen}, + date = {2009}, + pages = {168--177}, + publisher = {{IEEE}}, + location = {{Edmonton, Alberta, Canada}}, + doi = {10.1109/SCAM.2009.28}, + eventtitle = {2009 {{Ninth IEEE International Working Conference}} on {{Source Code Analysis}} and {{Manipulation}}}, + isbn = {978-0-7695-3793-1} +} +% == BibLateX quality report for ref:klint2009: +% ? Unsure about the formatting of the booktitle + +@article{ref:kremer1988, + title = {Advanced Tools and Techniques for Automatic Parallelization}, + author = {Kremer, Ulrich and Bast, Heinz-J and Gerndt, Michael and Zima, Hans P.}, + date = {1988-09}, + journaltitle = {Parallel Computing}, + shortjournal = {Parallel Computing}, + volume = {7}, + pages = {387--393}, + issn = {0167-8191}, + doi = {10.1016/0167-8191(88)90057-9}, + langid = {english}, + number = {3} +} + +@inproceedings{ref:kuchen2002, + title = {Higher-Order Functions and Partial Applications for a {{C}}++ Skeleton Library}, + booktitle = {Proceedings of the 2002 Joint {{ACM}}-{{ISCOPE}} Conference on {{Java Grande}} - {{JGI}} '02}, + author = {Kuchen, Herbert and Striegnitz, J\"org}, + date = {2002}, + pages = {122--130}, + publisher = {{ACM Press}}, + location = {{Seattle, Washington, USA}}, + doi = {10.1145/583810.583824}, + eventtitle = {The 2002 Joint {{ACM}}-{{ISCOPE}} Conference}, + isbn = {978-1-58113-599-2}, + langid = {english} +} +% == BibLateX quality report for ref:kuchen2002: +% ? Unsure about the formatting of the booktitle + +@incollection{ref:kuchen2002a, + title = {A Skeleton Library}, + booktitle = {Euro-{{Par}} 2002 {{Parallel Processing}}}, + author = {Kuchen, Herbert}, + date = {2002}, + volume = {2400}, + pages = {620--629}, + publisher = {{Springer Berlin Heidelberg}}, + location = {{Berlin, Heidelberg}}, + doi = {10.1007/3-540-45706-2_86}, + isbn = {978-3-540-44049-9} +} +% == BibLateX quality report for ref:kuchen2002a: +% Missing required field 'editor' + +@inproceedings{ref:landi1991, + title = {Pointer-Induced Aliasing: A Problem Taxonomy}, + shorttitle = {Pointer-Induced Aliasing}, + booktitle = {Proceedings of the 18th {{ACM SIGPLAN}}-{{SIGACT}} Symposium on {{Principles}} of Programming Languages - {{POPL}} '91}, + author = {Landi, William and Ryder, Barbara G.}, + date = {1991}, + pages = {93--103}, + publisher = {{ACM Press}}, + location = {{Orlando, Florida, United States}}, + doi = {10.1145/99583.99599}, + eventtitle = {The 18th {{ACM SIGPLAN}}-{{SIGACT}} Symposium}, + isbn = {978-0-89791-419-2}, + langid = {english} +} +% == BibLateX quality report for ref:landi1991: +% ? Unsure about the formatting of the booktitle + +@inproceedings{ref:lazarescu2012, + title = {Dynamic Trace-Based Data Dependency Analysis for Parallelization of {{C}} Programs}, + booktitle = {2012 {{IEEE}} 12th {{International Working Conference}} on {{Source Code Analysis}} and {{Manipulation}}}, + author = {Lazarescu, Mihai T. and Lavagno, Luciano}, + date = {2012-09}, + pages = {126--131}, + publisher = {{IEEE}}, + location = {{Riva del Garda, Italy}}, + doi = {10.1109/SCAM.2012.15}, + eventtitle = {2012 12th {{IEEE Working Conference}} on {{Source Code Analysis}} and {{Manipulation}} ({{SCAM}})}, + isbn = {978-0-7695-4783-1} +} +% == BibLateX quality report for ref:lazarescu2012: +% ? Unsure about the formatting of the booktitle + +@article{ref:legaux2013, + title = {{{OSL}}: An Algorithmic Skeleton Library with Exceptions}, + shorttitle = {{{OSL}}}, + author = {Legaux, Joeffrey and Loulergue, Fr\'ed\'eric and Jubertie, Sylvain}, + date = {2013}, + journaltitle = {Procedia Computer Science}, + volume = {18}, + pages = {260--269}, + issn = {18770509}, + doi = {10.1016/j.procs.2013.05.189}, + langid = {english} +} + +@inproceedings{ref:lengauer1995, + title = {On the Parallelization of Loop Nests Containing While Loops}, + booktitle = {Proceedings of the {{First Aizu International Symposium}} on {{Parallel Algorithms}}/{{Architecture Synthesis}}}, + author = {Lengauer, C. and Griebl, M.}, + date = {1995}, + pages = {10--18}, + publisher = {{IEEE Comput. Soc. Press}}, + location = {{Fukushima, Japan}}, + doi = {10.1109/AISPAS.1995.401360}, + eventtitle = {The {{First Aizu International Symposium}} on {{Parallel Algorithms}}/{{Architecture Synthesis}}}, + isbn = {978-0-8186-7038-1} +} + +@inproceedings{ref:leyton2010, + title = {Skandium: Multi-Core Programming with Algorithmic Skeletons}, + shorttitle = {Skandium}, + booktitle = {2010 18th {{Euromicro Conference}} on {{Parallel}}, {{Distributed}} and {{Network}}-Based {{Processing}}}, + author = {Leyton, Mario and Piquer, Jos\'e M.}, + date = {2010-02}, + pages = {289--296}, + publisher = {{IEEE}}, + location = {{Pisa}}, + doi = {10.1109/PDP.2010.26}, + annotation = {Print ISBN: 978-1-4244-5672-7}, + eventtitle = {18th {{Euromicro International Conference}} on {{Parallel}}, {{Distributed}} and {{Network}}-{{Based Processing}} ({{PDP}} 2010)}, + isbn = {978-1-4244-5673-4} +} +% == BibLateX quality report for ref:leyton2010: +% ? Unsure about the formatting of the booktitle + +@inproceedings{ref:li2013, + title = {{{NUMA}}-Aware Shared-Memory Collective Communication for {{MPI}}}, + booktitle = {Proceedings of the 22nd {{International Symposium}} on {{High}}-{{Performance Parallel}} and {{Distributed Computing}}}, + author = {Li, Shigang and Hoefler, Torsten and Snir, Marc}, + date = {2013}, + pages = {85--96}, + publisher = {{Association for Computing Machinery}}, + location = {{New York, New York, USA}}, + doi = {10.1145/2462902.2462903}, + eventtitle = {{{HPDC}} '13}, + isbn = {978-1-4503-1910-2}, + langid = {english} +} + +@article{ref:lilis2019, + title = {A Survey of Metaprogramming Languages}, + author = {Lilis, Yannis and Savidis, Anthony}, + date = {2019-10-16}, + journaltitle = {ACM Computing Surveys}, + volume = {52}, + pages = {1--39}, + issn = {03600300}, + doi = {10.1145/3354584}, + langid = {english}, + number = {6} +} + +@article{ref:liskov1988, + title = {Promises: Linguistic Support for Efficient Asynchronous Procedure Calls in Distributed Systems}, + shorttitle = {Promises}, + author = {Liskov, B. and Shrira, L.}, + date = {1988-07-01}, + journaltitle = {ACM SIGPLAN Notices}, + volume = {23}, + pages = {260--267}, + issn = {03621340}, + doi = {10.1145/960116.54016}, + langid = {english}, + number = {7} +} + +@incollection{ref:lopez2016, + title = {Using {{C}}++ {{AMP}} to Accelerate {{HPC}} Applications on Multiple Platforms}, + booktitle = {High {{Performance Computing}}}, + author = {Lopez, M. Graham and Bergstrom, Christopher and Li, Ying Wai and Elwasif, Wael and Hernandez, Oscar}, + editor = {Taufer, Michela and Mohr, Bernd and Kunkel, Julian M.}, + date = {2016}, + volume = {9945}, + pages = {563--576}, + publisher = {{Springer International Publishing}}, + location = {{Cham}}, + doi = {10.1007/978-3-319-46079-6_38}, + isbn = {978-3-319-46078-9}, + series = {Lecture {{Notes}} in {{Computer Science}}} +} + +@incollection{ref:lourenco2003, + title = {Iterated {{Local Search}}}, + booktitle = {Handbook of {{Metaheuristics}}}, + author = {Louren\c{c}o, Helena R. and Martin, Olivier C. and St\"utzle, Thomas}, + editor = {Glover, Fred and Kochenberger, Gary A.}, + date = {2003}, + pages = {320--353}, + publisher = {{Springer US}}, + location = {{Boston, MA}}, + doi = {10.1007/0-306-48056-5_11}, + isbn = {978-0-306-48056-0}, + langid = {english}, + series = {International {{Series}} in {{Operations Research}} \& {{Management Science}}} +} +% == BibLateX quality report for ref:lourenco2003: +% ? Title looks like it was stored in title-case in Zotero + +@article{ref:loveman1993, + title = {High Performance {{Fortran}}}, + author = {Loveman, D. B.}, + date = {1993-02}, + journaltitle = {IEEE Parallel \& Distributed Technology: Systems \& Applications}, + volume = {1}, + pages = {25--42}, + issn = {1063-6552}, + doi = {10.1109/88.219857}, + langid = {english}, + number = {1} +} + +@inproceedings{ref:luebke2008, + title = {{{CUDA}}: Scalable Parallel Programming for High-Performance Scientific Computing}, + shorttitle = {{{CUDA}}}, + booktitle = {2008 5th {{IEEE International Symposium}} on {{Biomedical Imaging}}: {{From Nano}} to {{Macro}}}, + author = {Luebke, David}, + date = {2008-05}, + pages = {836--838}, + publisher = {{IEEE}}, + location = {{Paris, France}}, + doi = {10.1109/ISBI.2008.4541126}, + eventtitle = {2008 5th {{IEEE International Symposium}} on {{Biomedical Imaging}} ({{ISBI}} 2008)}, + isbn = {978-1-4244-2002-5} +} + +@article{ref:mack2011, + title = {Fifty Years of {{Moore}}'s Law}, + author = {Mack, Chris A.}, + date = {2011-05}, + journaltitle = {IEEE Transactions on Semiconductor Manufacturing}, + volume = {24}, + pages = {202--207}, + issn = {0894-6507}, + doi = {10.1109/TSM.2010.2096437}, + number = {2} +} + +@online{ref:maddock2002, + title = {A Proposal to Add Type Traits to the Standard Library}, + shorttitle = {N1345}, + author = {Maddock, John}, + date = {2002-03-07}, + url = {http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1345.html}, + urldate = {2020-09-01}, + annotation = {maddock2002}, + langid = {english} +} + +@incollection{ref:marques2013, + title = {Algorithmic Skeleton Framework for the Orchestration of {{GPU}} Computations}, + booktitle = {Euro-{{Par}} 2013 {{Parallel Processing}}}, + author = {Marques, Ricardo and Paulino, Herv\'e and Alexandre, Fernando and Medeiros, Pedro D.}, + date = {2013}, + volume = {8097}, + pages = {874--885}, + publisher = {{Springer Berlin Heidelberg}}, + location = {{Berlin, Heidelberg}}, + doi = {10.1007/978-3-642-40047-6_86}, + isbn = {978-3-642-40046-9}, + langid = {english} +} +% == BibLateX quality report for ref:marques2013: +% Missing required field 'editor' + +@thesis{ref:masliah2016, + title = {Automatic code generation methods applied to numerical linear algebra in high performance computing}, + author = {Masliah, Ian}, + date = {2016}, + institution = {{Universit\'e Paris-Saclay}}, + url = {https://tel.archives-ouvertes.fr/tel-01395496/document}, + langid = {french} +} +% == BibLateX quality report for ref:masliah2016: +% Missing required field 'type' + +@inproceedings{ref:mathews2016, + title = {Automatic Code Parallelization with {{OpenMP}} Task Constructs}, + booktitle = {2016 {{International Conference}} on {{Information Science}} ({{ICIS}})}, + author = {Mathews, Manju and Abraham, Jisha P}, + date = {2016-08}, + pages = {233--238}, + publisher = {{IEEE}}, + location = {{Kochi, India}}, + doi = {10.1109/INFOSCI.2016.7845333}, + eventtitle = {2016 {{International Conference}} in {{Information Science}} ({{ICIS}})}, + isbn = {978-1-5090-1987-8} +} +% == BibLateX quality report for ref:mathews2016: +% ? Unsure about the formatting of the booktitle + +@inproceedings{ref:matsuzaki2006a, + title = {Towards Automatic Parallelization of Tree Reductions in Dynamic Programming}, + booktitle = {Proceedings of the Eighteenth Annual {{ACM}} Symposium on {{Parallelism}} in Algorithms and Architectures - {{SPAA}} '06}, + author = {Matsuzaki, Kiminori and Hu, Zhenjiang and Takeichi, Masato}, + date = {2006}, + pages = {39}, + publisher = {{ACM Press}}, + location = {{Cambridge, Massachusetts, USA}}, + doi = {10.1145/1148109.1148116}, + eventtitle = {The Eighteenth Annual {{ACM}} Symposium}, + isbn = {978-1-59593-452-9}, + langid = {english} +} +% == BibLateX quality report for ref:matsuzaki2006a: +% ? Unsure about the formatting of the booktitle + +@book{ref:mattson2005, + title = {Patterns for Parallel Programming}, + author = {Mattson, Timothy G. and Sanders, Beverly A. and Massingill, Berna}, + date = {2005}, + publisher = {{Addison-Wesley}}, + location = {{Boston}}, + isbn = {978-0-321-22811-6}, + pagetotal = {355} +} + +@book{ref:mccool2012, + title = {Structured Parallel Programming: Patterns for Efficient Computation}, + shorttitle = {Structured {{Parallel Programming}}}, + author = {McCool, Michael and Reinders, James and Robison, Arch}, + date = {2012}, + publisher = {{Elsevier}}, + eprint = {2hYqeoO8t8IC}, + eprinttype = {googlebooks}, + isbn = {978-0-12-391443-9}, + langid = {english}, + pagetotal = {433} +} + +@online{ref:meneide2020, + title = {{{std::embed}} and {{\#depend}}}, + shorttitle = {p1040r6}, + author = {Meneide, JeanHeyd}, + date = {2020-02-29}, + url = {http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1040r6.html}, + annotation = {meneide2020}, + langid = {english} +} + +@inproceedings{ref:munshi2009, + title = {The {{OpenCL}} Specification}, + booktitle = {2009 {{IEEE Hot Chips}} 21 {{Symposium}} ({{HCS}})}, + author = {Munshi, Aaftab}, + date = {2009-08}, + pages = {1--314}, + publisher = {{IEEE}}, + location = {{Stanford, CA}}, + doi = {10.1109/HOTCHIPS.2009.7478342}, + eventtitle = {2009 {{IEEE Hot Chips}} 21 {{Symposium}} ({{HCS}})}, + isbn = {978-1-4673-8873-3} +} +% == BibLateX quality report for ref:munshi2009: +% ? Unsure about the formatting of the booktitle + +@incollection{ref:musser1989, + title = {Generic Programming}, + booktitle = {Symbolic and {{Algebraic Computation}}}, + author = {Musser, David R. and Stepanov, Alexander A.}, + editor = {Gianni, P.}, + date = {1989}, + volume = {358}, + pages = {13--25}, + publisher = {{Springer Berlin Heidelberg}}, + location = {{Berlin, Heidelberg}}, + doi = {10.1007/3-540-51084-2_2}, + editorb = {Goos, G. and Hartmanis, J. and Barstow, D. and Brauer, W. and Brinch Hansen, P. and Gries, D. and Luckham, D. and Moler, C. and Pnueli, A. and Seegm\"uller, G. and Stoer, J. and Wirth, N.}, + editorbtype = {redactor}, + isbn = {978-3-540-51084-0}, + langid = {english} +} + +@article{ref:namsungkim2003, + title = {Leakage Current: {{Moore}}'s Law Meets Static Power}, + shorttitle = {Leakage Current}, + author = {{Nam Sung Kim} and Austin, T. and Blaauw, D. and Mudge, T. and Flautner, K. and {Jie S. Hu} and Irwin, M.J. and Kandemir, M. and Narayanan, V.}, + date = {2003-12}, + journaltitle = {Computer}, + shortjournal = {Computer}, + volume = {36}, + pages = {68--75}, + issn = {0018-9162}, + doi = {10.1109/MC.2003.1250885}, + langid = {english}, + number = {12} +} + +@inproceedings{ref:neth2019, + title = {Automatic Parallelization of Irregular X86-64 Loops}, + booktitle = {2019 {{IEEE}}/{{ACM International Symposium}} on {{Code Generation}} and {{Optimization}} ({{CGO}})}, + author = {Neth, Brandon and Strout, Michelle Mills}, + date = {2019-02}, + pages = {266--266}, + publisher = {{IEEE}}, + location = {{Washington, DC, USA}}, + doi = {10.1109/CGO.2019.8661167}, + eventtitle = {2019 {{IEEE}}/{{ACM International Symposium}} on {{Code Generation}} and {{Optimization}} ({{CGO}})}, + isbn = {978-1-72811-436-1} +} +% == BibLateX quality report for ref:neth2019: +% ? Unsure about the formatting of the booktitle + +@article{ref:netzer1992, + title = {What Are Race Conditions?: {{Some}} Issues and Formalizations}, + shorttitle = {What {{Are Race Conditions}}?}, + author = {Netzer, Robert H. B. and Miller, Barton P.}, + date = {1992-03}, + journaltitle = {ACM Letters on Programming Languages and Systems}, + volume = {1}, + pages = {74--88}, + issn = {1057-4514}, + doi = {10.1145/130616.130623}, + langid = {english}, + number = {1} +} + +@book{ref:nichols1996, + title = {{{PThreads Programming}}: {{A POSIX Standard}} for {{Better Multiprocessing}}}, + shorttitle = {{{PThreads Programming}}}, + author = {Nichols, Bradford and Buttlar, Dick and Farrell, Jacqueline}, + date = {1996}, + publisher = {{O'Reilly Media, Inc.}}, + eprint = {oMtCFSnvwmoC}, + eprinttype = {googlebooks}, + isbn = {978-1-56592-115-3}, + langid = {english}, + pagetotal = {289} +} +% == BibLateX quality report for ref:nichols1996: +% ? Title looks like it was stored in title-case in Zotero + +@incollection{ref:nicolau1992, + title = {Register Allocation, Renaming and Their Impact on Fine-Grain Parallelism}, + booktitle = {Languages and {{Compilers}} for {{Parallel Computing}}}, + author = {Nicolau, A. and Potasman, R. and Wang, H.}, + editor = {Banerjee, Utpal and Gelernter, David and Nicolau, Alex and Padua, David}, + date = {1992}, + volume = {589}, + pages = {218--235}, + publisher = {{Springer-Verlag}}, + location = {{Berlin/Heidelberg}}, + doi = {10.1007/BFb0038667}, + isbn = {978-3-540-55422-6}, + langid = {english}, + series = {Lecture {{Notes}} in {{Computer Science}}} +} + +@article{ref:owens2008, + title = {{{GPU}} Computing}, + author = {Owens, J.D. and Houston, M. and Luebke, D. and Green, S. and Stone, J.E. and Phillips, J.C.}, + date = {2008-05}, + journaltitle = {Proceedings of the IEEE}, + volume = {96}, + pages = {879--899}, + issn = {0018-9219}, + doi = {10.1109/JPROC.2008.917757}, + number = {5} +} + +@thesis{ref:passerat-palmbach2013, + title = {Contributions to Parallel Stochastic Simulation: {{Application}} of Good Software Engineering Practices to the Distribution of Pseudorandom Streams in Hybrid {{Monte}}-{{Carlo}} Simulations}, + shorttitle = {Contributions to Parallel Stochastic Simulation}, + author = {Passerat-Palmbach, Jonathan}, + date = {2013-10-11}, + institution = {{Universit\'e Blaise Pascal - Clermont-Ferrand II}}, + url = {https://tel.archives-ouvertes.fr/tel-00858735/document}, + urldate = {2018-08-22}, + langid = {english}, + type = {phdthesis} +} + +@inproceedings{ref:patterson1995, + title = {Accurate Static Branch Prediction by Value Range Propagation}, + booktitle = {Proceedings of the {{ACM SIGPLAN}} 1995 Conference on {{Programming}} Language Design and Implementation - {{PLDI}} '95}, + author = {Patterson, Jason R. C.}, + date = {1995}, + pages = {67--78}, + publisher = {{ACM Press}}, + location = {{La Jolla, California, United States}}, + doi = {10.1145/207110.207117}, + eventtitle = {The {{ACM SIGPLAN}} 1995 Conference}, + isbn = {978-0-89791-697-4}, + langid = {english} +} +% == BibLateX quality report for ref:patterson1995: +% ? Unsure about the formatting of the booktitle + +@article{ref:peleg1997, + title = {Intel {{MMX}} for Multimedia {{PCs}}}, + author = {Peleg, Alex and Wilkie, Sam and Weiser, Uri}, + date = {1997-01-01}, + journaltitle = {Communications of the ACM}, + volume = {40}, + pages = {24--38}, + issn = {00010782}, + doi = {10.1145/242857.242865}, + langid = {english}, + number = {1} +} + +@article{ref:perach2018, + title = {{{SiMT}}-{{DSP}}: {{A Massively Multithreaded DSP Architecture}}}, + shorttitle = {{{SiMT}}-{{DSP}}}, + author = {Perach, Ben and Weiss, Shlomo}, + date = {2018-08}, + journaltitle = {IEEE Transactions on Very Large Scale Integration (VLSI) Systems}, + volume = {26}, + pages = {1413--1426}, + issn = {1063-8210, 1557-9999}, + doi = {10.1109/TVLSI.2018.2817564}, + number = {8} +} +% == BibLateX quality report for ref:perach2018: +% 'issn': not a valid ISSN +% ? Title looks like it was stored in title-case in Zotero + +@inproceedings{ref:pereda2018, + title = {Static Loop Parallelization Decision Using Template Metaprogramming}, + booktitle = {2018 {{International Conference}} on {{High Performance Computing}} \& {{Simulation}} ({{HPCS}})}, + author = {Pereda, Alexis and Hill, David R. C. and Mazel, Claude and Bachelet, Bruno}, + date = {2018-07}, + pages = {1015--1021}, + publisher = {{IEEE}}, + location = {{Orleans}}, + doi = {10.1109/HPCS.2018.00159}, + isbn = {978-1-5386-7878-7} +} +% == BibLateX quality report for ref:pereda2018: +% ? Unsure about the formatting of the booktitle + +@inproceedings{ref:pereda2020, + title = {Processing Algorithmic Skeletons at Compile-Time}, + booktitle = {21\`eme Congr\`es de La Soci\'et\'e Fran\c{c}aise de {{Recherche Op\'erationnelle}} et d'{{Aide}} \`a La {{D\'ecision}} ({{ROADEF}})}, + author = {Pereda, Alexis and Hill, David R. C. and Mazel, Claude and Yon, Lo\"ic and Bachelet, Bruno}, + date = {2020-02}, + location = {{Montpellier, France}}, + url = {https://hal.archives-ouvertes.fr/hal-02573660} +} +% == BibLateX quality report for ref:pereda2020: +% ? Unsure about the formatting of the booktitle + +@inproceedings{ref:philippe2019, + title = {{{PySke}}: Algorithmic Skeletons for {{Python}}}, + shorttitle = {{{PySke}}}, + booktitle = {The 2019 {{International Conference}} on {{High Performance Computing}} \& {{Simulation}} ({{HPCS}})}, + author = {Philippe, Jolan and Loulergue, Fr\'ed\'eric}, + date = {2019-07}, + pages = {40--47}, + location = {{Dublin, Ireland}} +} +% == BibLateX quality report for ref:philippe2019: +% ? Unsure about the formatting of the booktitle + +@incollection{ref:pinto2012, + title = {{{GPU}} Metaprogramming: A Case Study in Biologically-Inspired Machine Vision}, + booktitle = {{{GPU Computing Gems Jade Edition}}}, + author = {Pinto, Nicolas and Cox, David D.}, + date = {2012}, + pages = {457--471}, + publisher = {{Elsevier}}, + doi = {10.1016/B978-0-12-385963-1.00033-2}, + isbn = {978-0-12-385963-1}, + langid = {english} +} +% == BibLateX quality report for ref:pinto2012: +% Missing required field 'editor' + +@book{ref:popper2005, + title = {The Logic of Scientific Discovery}, + author = {Popper, Karl}, + date = {2005-11-04}, + publisher = {{Routledge}}, + eprint = {LWSBAgAAQBAJ}, + eprinttype = {googlebooks}, + isbn = {978-1-134-47002-0}, + langid = {english}, + pagetotal = {545} +} + +@incollection{ref:prins2009a, + title = {A {{GRASP}} \texttimes{} {{Evolutionary Local Search Hybrid}} for the {{Vehicle Routing Problem}}}, + booktitle = {Bio-Inspired {{Algorithms}} for the {{Vehicle Routing Problem}}}, + author = {Prins, Christian}, + date = {2009}, + pages = {35--53}, + publisher = {{Springer}}, + location = {{Berlin, Heidelberg}}, + doi = {10.1007/978-3-540-85152-3_2}, + isbn = {978-3-540-85152-3}, + langid = {english}, + series = {Studies in {{Computational Intelligence}}} +} +% == BibLateX quality report for ref:prins2009a: +% Missing required field 'editor' +% ? Title looks like it was stored in title-case in Zotero + +@article{ref:protic1996, + title = {Distributed Shared Memory: Concepts and Systems}, + shorttitle = {Distributed Shared Memory}, + author = {Protic, J. and Tomasevic, M. and Milutinovic, V.}, + date = {1996}, + journaltitle = {IEEE Parallel \& Distributed Technology: Systems \& Applications}, + volume = {4}, + pages = {63--71}, + issn = {10636552}, + doi = {10.1109/88.494605}, + number = {2} +} + +@book{ref:quinn2003, + title = {Parallel Programming in {{C}} with {{MPI}} and {{OpenMP}}}, + author = {Quinn, Michael Jay}, + date = {2003}, + volume = {526}, + publisher = {{McGraw-Hill}}, + annotation = {OCLC: 553844668}, + isbn = {978-0-07-123265-4}, + langid = {english}, + pagetotal = {529} +} + +@article{ref:raman2000, + title = {Implementing Streaming {{SIMD}} Extensions on the {{Pentium III}} Processor}, + author = {Raman, S.K. and Pentkovski, V. and Keshava, J.}, + date = {2000-07}, + journaltitle = {IEEE Micro}, + volume = {20}, + pages = {47--57}, + issn = {02721732}, + doi = {10.1109/40.865866}, + number = {4} +} + +@report{ref:ramon-cortes2018, + title = {{{AutoParallel}}: {{A Python}} Module for Automatic Parallelization and Distributed Execution of Affine Loop Nests}, + author = {Ramon-Cortes, Cristian and Amela, Ramon and Ejarque, Jorge and Clauss, Philippe and Badia, Rosa}, + date = {2018}, + pages = {13}, + langid = {english} +} +% == BibLateX quality report for ref:ramon-cortes2018: +% Missing required field 'type' +% Missing required field 'institution' + +@online{ref:ranns2018, + title = {Down with typename!}, + shorttitle = {p0634r2}, + author = {Ranns, Nina and Vandevoorde, Daveed}, + date = {2018-10-02}, + url = {http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0634r2.html}, + annotation = {ranns2018}, + langid = {english} +} + +@online{ref:revzin2020, + title = {If Consteval}, + shorttitle = {P1938r2}, + author = {Revzin, Barry and Smith, Richard and Sutton, Andrew and Vandevoorde, Daveed}, + date = {2020-10-09}, + url = {http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1938r2.html}, + annotation = {revzin2020}, + langid = {english} +} + +@inproceedings{ref:rieger2019, + title = {Musket: A Domain-Specific Language for High-Level Parallel Programming with Algorithmic Skeletons}, + shorttitle = {Musket}, + booktitle = {Proceedings of the 34th {{ACM}}/{{SIGAPP Symposium}} on {{Applied Computing}}}, + author = {Rieger, Christoph and Wrede, Fabian and Kuchen, Herbert}, + date = {2019-04-08}, + pages = {1534--1543}, + publisher = {{ACM}}, + location = {{Limassol Cyprus}}, + doi = {10.1145/3297280.3297434}, + eventtitle = {{{SAC}} '19: {{The}} 34th {{ACM}}/{{SIGAPP Symposium}} on {{Applied Computing}}}, + isbn = {978-1-4503-5933-7}, + langid = {english} +} + +@report{ref:robinson1949, + title = {On the {{Hamiltonian}} Game (a Traveling Salesman Problem)}, + author = {Robinson, Julia}, + date = {1949}, + institution = {{Rand project air force arlington va.}} +} +% == BibLateX quality report for ref:robinson1949: +% Missing required field 'type' + +@article{ref:roscoe1988, + title = {The Laws of {{OCCAM}} Programming}, + author = {Roscoe, Andrew William and Hoare, Charles Antony Richard}, + date = {1988-09-01}, + journaltitle = {Theoretical Computer Science}, + volume = {60}, + pages = {177--229}, + publisher = {{Elsevier}}, + issn = {0304-3975}, + doi = {10.1016/0304-3975(88)90049-7}, + langid = {english}, + number = {2} +} +% == BibLateX quality report for ref:roscoe1988: +% Unexpected field 'publisher' + +@report{ref:rudd1993, + title = {{{X3H5}} Parallel Extensions for Programming Language {{C}}}, + author = {Rudd, Walter G.}, + date = {1993}, + institution = {{Oregon State University}}, + location = {{Corvallis, OR, USA}}, + langid = {english} +} +% == BibLateX quality report for ref:rudd1993: +% Missing required field 'type' + +@inproceedings{ref:saidani2009, + title = {Algorithmic Skeletons within an {{Embedded Domain Specific Language}} for the {{CELL Processor}}}, + author = {Saidani, Tarik and Falcou, Joel and Tadonki, Claude and Lacassagne, Lionel and Etiemble, Daniel}, + date = {2009-09}, + pages = {67--76}, + publisher = {{IEEE}}, + doi = {10.1109/PACT.2009.21}, + isbn = {978-0-7695-3771-9} +} +% == BibLateX quality report for ref:saidani2009: +% Missing required field 'booktitle' + +@article{ref:schaller1997, + title = {Moore's Law: Past, Present and Future}, + shorttitle = {Moore's Law}, + author = {Schaller, R.R.}, + date = {1997-06}, + journaltitle = {IEEE Spectrum}, + volume = {34}, + pages = {52--59}, + issn = {0018-9235}, + doi = {10.1109/6.591665}, + number = {6} +} + +@article{ref:schmidt1998, + title = {Evaluating Architectures for Multithreaded Object Request Brokers}, + author = {Schmidt, Douglas C.}, + date = {1998-10-01}, + journaltitle = {Communications of the ACM}, + volume = {41}, + pages = {54--60}, + issn = {00010782}, + doi = {10.1145/286238.286248}, + number = {10} +} + +@incollection{ref:sheard2001, + title = {Accomplishments and Research Challenges in Meta-Programming}, + booktitle = {Semantics, {{Applications}}, and {{Implementation}} of {{Program Generation}}}, + author = {Sheard, Tim}, + editor = {Taha, Walid}, + date = {2001}, + volume = {2196}, + pages = {2--44}, + publisher = {{Springer Berlin Heidelberg}}, + location = {{Berlin, Heidelberg}}, + doi = {10.1007/3-540-44806-3_2}, + editorb = {Goos, Gerhard and Hartmanis, Juris and family=Leeuwen, given=Jan, prefix=van, useprefix=true}, + editorbtype = {redactor}, + isbn = {978-3-540-42558-8} +} + +@report{ref:shi1996, + title = {Reevaluating {{Amdahl}}'s Law and {{Gustafson}}'s Law}, + author = {Shi, Yuan}, + date = {1996-10}, + institution = {{Computer Sciences Department, Temple University (MS:38-24)}}, + type = {technical} +} + +@incollection{ref:shirako2008, + title = {Languages and Compilers for Parallel Computing}, + author = {Shirako, Jun and Kasahara, Hironori and Sarkar, Vivek}, + editor = {Adve, Vikram and Garzar\'an, Mar\'ia Jes\'us and Petersen, Paul}, + date = {2008}, + pages = {78--94}, + publisher = {{Springer-Verlag}}, + location = {{Berlin, Heidelberg}}, + doi = {10.1007/978-3-540-85261-2_6}, + isbn = {978-3-540-85260-5}, + langid = {english} +} +% == BibLateX quality report for ref:shirako2008: +% Missing required field 'booktitle' + +@inproceedings{ref:siek2000, + title = {Concept Checking: Binding Parametric Polymorphism in {{C}}++}, + author = {Siek, Jeremy and Lumsdaine, Andrew}, + date = {2000-10}, + eventtitle = {First Workshop on {{C}}++ {{Template Programming}}} +} +% == BibLateX quality report for ref:siek2000: +% Missing required field 'booktitle' + +@online{ref:siek2005, + title = {Concepts for C++0x}, + shorttitle = {n1758}, + author = {Siek, Jeremy and Gregor, Douglas and Garcia, Ronald and Willcock, Jeremiah and Järvi, Jaakko and Lumsdaine, Andrew}, + date = {2005-01-17}, + url = {http://www.open-std.org/JTC1/SC22/wg21/docs/papers/2005/n1758.pdf}, + annotation = {siek2005}, + langid = {english} +} +% == BibLateX quality report for ref:siek2005: +% ? Title looks like it was stored in title-case in Zotero + +@inproceedings{ref:smith1998, + title = {A Study of Branch Prediction Strategies}, + booktitle = {25 Years of the International Symposia on {{Computer}} Architecture (Selected Papers) - {{ISCA}} '98}, + author = {Smith, James E.}, + date = {1998}, + pages = {202--215}, + publisher = {{ACM Press}}, + location = {{Barcelona, Spain}}, + doi = {10.1145/285930.285980}, + eventtitle = {25 Years of the International Symposia}, + isbn = {978-1-58113-058-4}, + langid = {english} +} + +@online{ref:smith2018, + title = {Immediate Functions}, + shorttitle = {P1073r3}, + author = {Smith, Richard and Sutton, Andrew and Vandevoorde, Daveed}, + date = {2018-11-06}, + url = {http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1073r3.html}, + annotation = {smith2018}, + langid = {english} +} + +@online{ref:smith2018a, + title = {std::is\_constant\_ evaluated}, + shorttitle = {P0595r2}, + author = {Smith, Richard and Sutton, Andrew and Vandevoorde, Daveed}, + date = {2018-11-09}, + url = {http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0595r2.html}, + annotation = {smith2018}, + langid = {english} +} +% == BibLateX quality report for ref:smith2018a: +% ? Title looks like it was stored in lower-case in Zotero + +@article{ref:stallings1988, + title = {Reduced Instruction Set Computer Architecture}, + author = {Stallings, W.}, + date = {1988-01}, + journaltitle = {Proceedings of the IEEE}, + volume = {76}, + pages = {38--55}, + issn = {00189219}, + doi = {10.1109/5.3287}, + number = {1} +} + +@article{ref:stone2010, + title = {{{OpenCL}}: A Parallel Programming Standard for Heterogeneous Computing Systems}, + shorttitle = {{{OpenCL}}}, + author = {Stone, John E. and Gohara, David and Shi, Guochun}, + date = {2010-05}, + journaltitle = {Computing in science \& engineering}, + shortjournal = {Comput Sci Eng}, + volume = {12}, + pages = {66--73}, + issn = {1521-9615}, + doi = {10.1109/MCSE.2010.69}, + eprint = {21037981}, + eprinttype = {pmid}, + langid = {english}, + number = {3}, + pmcid = {PMC2964860} +} +% == BibLateX quality report for ref:stone2010: +% Unexpected field 'pmcid' + +@inproceedings{ref:striegnitz2000, + title = {Making {{C}}++ Ready for Algorithmic Skeletons}, + author = {Striegnitz, J\"org}, + date = {2000}, + pages = {10} +} +% == BibLateX quality report for ref:striegnitz2000: +% Missing required field 'booktitle' + +@book{ref:stroustrup1997, + title = {The {{C}}++ Programming Language}, + author = {Stroustrup, Bjarne}, + date = {1997}, + edition = {3rd ed}, + publisher = {{Addison-Wesley}}, + location = {{Reading, Mass}}, + isbn = {978-0-201-88954-3}, + langid = {english}, + pagetotal = {910} +} + +@online{ref:stroustrup2017, + title = {Concepts: the future of generic programming}, + shorttitle = {p0557r0}, + author = {Stroustrup, Bjarne}, + date = {2017-01-31}, + url = {http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0557r0.pdf}, + annotation = {stroustrup2017}, + langid = {english} +} + +@online{ref:sutter2019, + title = {Metaclasses: Generative {{C}}++}, + shorttitle = {P0707r4}, + author = {Sutter, Herb}, + date = {2019-06-16}, + url = {http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0707r4.pdf}, + annotation = {sutter2019}, + langid = {english} +} + +@inproceedings{ref:tamai1992, + title = {Software Lifetime and Its Evolution Process over Generations}, + booktitle = {Proceedings {{Conference}} on {{Software Maintenance}} 1992}, + author = {Tamai, T. and Torimitsu, Y.}, + date = {1992}, + pages = {63--69}, + publisher = {{IEEE Comput. Soc. Press}}, + location = {{Orlando, FL, USA}}, + doi = {10.1109/ICSM.1992.242557}, + eventtitle = {Conference on {{Software Maintenance}} 1992}, + isbn = {978-0-8186-2980-8} +} + +@article{ref:tomczak, + title = {{{GPU Ray Marching}} of {{Distance Fields}}}, + author = {Tomczak, Lukasz Jaroslaw}, + pages = {79}, + langid = {english} +} +% == BibLateX quality report for ref:tomczak: +% Exactly one of 'date' / 'year' must be present +% Missing required field 'journaltitle' +% ? Title looks like it was stored in title-case in Zotero + +@thesis{ref:tomczak2012, + title = {{{GPU}} Ray Marching of Distance Fields}, + author = {Tomczak, Lukasz Jaroslaw}, + date = {2012}, + institution = {{Technical University of Denmark}}, + location = {{Asmussens Alle, Building 305, DK-2800 Kgs. Lyngby, Denmark}}, + langid = {english}, + pagetotal = {79} +} +% == BibLateX quality report for ref:tomczak2012: +% Missing required field 'type' + +@thesis{ref:touraille2012, + title = {Application of Model-Driven Engineering and Metaprogramming to {{DEVS}} Modeling \& Simulation}, + author = {Touraille, Luc}, + date = {2012-12}, + institution = {{Universit\'e Blaise Pascal}}, + url = {https://tel.archives-ouvertes.fr/tel-00914327/}, + langid = {english}, + pagetotal = {312} +} +% == BibLateX quality report for ref:touraille2012: +% Missing required field 'type' + +@thesis{ref:toussaint2010, + title = {Algorithmique rapide pour les probl\`emes de tourn\'ees et d'ordonnancement}, + author = {Toussaint, H\'el\`ene}, + date = {2010-01-01}, + institution = {{Clermont-Ferrand 2}}, + url = {http://www.theses.fr/2010CLF22053}, + urldate = {2018-05-28}, + langid = {french} +} +% == BibLateX quality report for ref:toussaint2010: +% Missing required field 'type' + +@article{ref:turing1937, + title = {Computability and {$\lambda$}-Definability}, + author = {Turing, A. M.}, + date = {1937-12}, + journaltitle = {Journal of Symbolic Logic}, + volume = {2}, + pages = {153--163}, + issn = {0022-4812, 1943-5886}, + doi = {10.2307/2268280}, + langid = {english}, + number = {4} +} +% == BibLateX quality report for ref:turing1937: +% 'issn': not a valid ISSN + +@report{ref:unruh1994, + title = {Prime Number Computation}, + author = {Unruh, Erwin}, + date = {1994}, + institution = {{ANSI X3J16-94-0075/ISO WG21-462}} +} +% == BibLateX quality report for ref:unruh1994: +% Missing required field 'type' + +@book{ref:vandevoorde2010, + title = {C++ Templates: The Complete Guide}, + shorttitle = {C++ Templates: The Complete Guide}, + author = {Vandevoorde, Daveed and Josuttis, Nicolai M.}, + date = {2010}, + publisher = {{Addison-Wesley}}, + annotation = {OCLC: 706076856}, + isbn = {978-0-201-73484-3}, + langid = {english}, + pagetotal = {528} +} + +@book{ref:vandevoorde2017, + title = {C++ Templates: The Complete Guide}, + shorttitle = {C++ Templates: The Complete Guide}, + author = {Vandevoorde, Daveed and Josuttis, Nicolai M. and Gregor, Douglas}, + date = {2017}, + publisher = {{Addison-Wesley}}, + annotation = {OCLC: 1013589455}, + isbn = {978-0-321-71412-1}, + langid = {english}, + pagetotal = {788} +} + +@inproceedings{ref:vandierendonck2010, + title = {The Paralax Infrastructure: Automatic Parallelization with a Helping Hand}, + shorttitle = {The Paralax Infrastructure}, + booktitle = {2010 19th {{International Conference}} on {{Parallel Architectures}} and {{Compilation Techniques}} ({{PACT}})}, + author = {Vandierendonck, H. and Rul, S. and Bosschere, K. De}, + date = {2010-09}, + pages = {389--399}, + eventtitle = {2010 19th {{International Conference}} on {{Parallel Architectures}} and {{Compilation Techniques}} ({{PACT}})}, + langid = {english} +} +% == BibLateX quality report for ref:vandierendonck2010: +% ? Unsure about the formatting of the booktitle + +@article{ref:veldhuizen1995, + title = {Expression Templates}, + author = {Veldhuizen, Todd L.}, + date = {1995}, + journaltitle = {C++ Report}, + volume = {7}, + pages = {26--31}, + langid = {english} +} + +@incollection{ref:veldhuizen1996, + title = {Using {{C}}++ Template Metaprograms}, + booktitle = {C++ Gems}, + author = {Veldhuizen, Todd L.}, + editor = {Lippman, Stanley B.}, + date = {1996}, + pages = {459--473}, + publisher = {{SIGS Publications, Inc.}}, + location = {{New York, NY, USA}}, + url = {http://dl.acm.org/citation.cfm?id=260627.260748}, + urldate = {2018-08-27}, + isbn = {978-1-884842-37-5} +} + +@incollection{ref:veldhuizen1998, + title = {Arrays in {{Blitz}}++}, + booktitle = {Lecture {{Notes}} in {{Computer Science}}}, + author = {Veldhuizen, Todd L.}, + date = {1998}, + volume = {1505}, + pages = {223--230}, + publisher = {{Springer-Verlag}}, + langid = {english} +} +% == BibLateX quality report for ref:veldhuizen1998: +% Missing required field 'editor' +% ? Title looks like it was stored in title-case in Zotero + +@inproceedings{ref:veldhuizen1998a, + title = {Active Libraries: Rethinking the Roles of Compilers and Libraries}, + shorttitle = {Active {{Libraries}}}, + booktitle = {Proceedings of the {{SIAM Workshop}} on {{Object Oriented Methods}} for {{Inter}}-Operable {{Scientific}} and {{Engineering Computing}}}, + author = {Veldhuizen, Todd L. and Gannon, Dennis}, + date = {1998}, + pages = {286--295}, + publisher = {{SIAM Press}}, + langid = {english} +} + +@incollection{ref:veldhuizen2000, + title = {Blitz++: The Library That Thinks It Is a Compiler}, + shorttitle = {Blitz++}, + booktitle = {Advances in {{Software Tools}} for {{Scientific Computing}}}, + author = {Veldhuizen, Todd L.}, + date = {2000}, + volume = {10}, + pages = {57--87}, + publisher = {{Springer Berlin Heidelberg}}, + location = {{Berlin, Heidelberg}}, + doi = {10.1007/978-3-642-57172-5_2}, + isbn = {978-3-540-66557-1} +} +% == BibLateX quality report for ref:veldhuizen2000: +% Missing required field 'editor' + +@book{ref:veldhuizen2000a, + title = {Five Compilation Models for {{C}}++ Templates ({{Extended Abstract}})}, + author = {Veldhuizen, Todd L.}, + date = {2000}, + langid = {english} +} + +@report{ref:veldhuizen2003, + title = {C++ Templates Are {{Turing}} Complete}, + author = {Veldhuizen, Todd L.}, + date = {2003}, + institution = {{Indiana University Computer Science}}, + langid = {english} +} +% == BibLateX quality report for ref:veldhuizen2003: +% Missing required field 'type' + +@article{ref:videau2018, + title = {{{BOAST}}: A Metaprogramming Framework to Produce Portable and Efficient Computing Kernels for {{HPC}} Applications}, + shorttitle = {{{BOAST}}}, + author = {Videau, Brice and Pouget, Kevin and Genovese, Luigi and Deutsch, Thierry and Komatitsch, Dimitri and Desprez, Fr\'ed\'eric and M\'ehaut, Jean-Fran\c{c}ois}, + date = {2018-01}, + journaltitle = {The International Journal of High Performance Computing Applications}, + volume = {32}, + pages = {28--44}, + issn = {1094-3420, 1741-2846}, + doi = {10.1177/1094342017718068}, + langid = {english}, + number = {1} +} +% == BibLateX quality report for ref:videau2018: +% 'issn': not a valid ISSN + +@online{ref:vollmann2016, + title = {Why Joining\_thread from {{P0206}} Is a Bad Idea}, + shorttitle = {P0379r0}, + author = {Vollmann, Detlef}, + date = {2016-05-27}, + url = {http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0379r0.html}, + annotation = {vollmann2016}, + langid = {english} +} + +@inproceedings{ref:voufo2011, + title = {{{ConceptClang}}: An Implementation of {{C}}++ Concepts in {{Clang}}}, + shorttitle = {{{ConceptClang}}}, + booktitle = {Proceedings of the Seventh {{ACM SIGPLAN}} Workshop on {{Generic}} Programming - {{WGP}} '11}, + author = {Voufo, Larisse and Zalewski, Marcin and Lumsdaine, Andrew}, + date = {2011}, + pages = {71}, + publisher = {{ACM Press}}, + location = {{Tokyo, Japan}}, + doi = {10.1145/2036918.2036929}, + eventtitle = {The Seventh {{ACM SIGPLAN}} Workshop}, + isbn = {978-1-4503-0861-8}, + langid = {english} +} +% == BibLateX quality report for ref:voufo2011: +% ? Unsure about the formatting of the booktitle + +@online{ref:voutilainen2016, + title = {A Joining Thread}, + author = {Voutilainen, Ville}, + date = {2016-03-09}, + url = {http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0206r1.html}, + annotation = {voutilainen2016}, + langid = {english} +} + +@article{ref:walker1996, + title = {{{MPI}}: A Standard Message Passing Interface}, + author = {Walker, David W and Dongarra, Jack J}, + date = {1996}, + journaltitle = {Supercomputer}, + pages = {15}, + langid = {english} +} + +@inproceedings{ref:willhalm2008, + title = {Putting {{Intel}}\textregistered{} {{Threading Building Blocks}} to Work}, + booktitle = {Proceedings of the 1st {{International Workshop}} on {{Multicore Software Engineering}}}, + author = {Willhalm, Thomas and Popovici, Nicolae}, + date = {2008}, + pages = {3--4}, + publisher = {{ACM}}, + location = {{New York, NY, USA}}, + doi = {10.1145/1370082.1370085}, + isbn = {978-1-60558-031-9}, + langid = {english}, + series = {{{IWMSE}} '08} +} + +@inproceedings{ref:wolf2007, + title = {Evolutionary {{Local Search}} for the {{Super}}-{{Peer Selection Problem}} and the p-{{Hub Median Problem}}}, + booktitle = {Hybrid {{Metaheuristics}}}, + author = {Wolf, Steffen and Merz, Peter}, + editor = {Bartz-Beielstein, Thomas and Blesa Aguilera, Mar\'ia Jos\'e and Blum, Christian and Naujoks, Boris and Roli, Andrea and Rudolph, G\"unter and Sampels, Michael}, + date = {2007}, + pages = {1--15}, + publisher = {{Springer}}, + location = {{Berlin, Heidelberg}}, + doi = {10.1007/978-3-540-75514-2_1}, + isbn = {978-3-540-75514-2}, + langid = {english}, + series = {Lecture {{Notes}} in {{Computer Science}}} +} +% == BibLateX quality report for ref:wolf2007: +% ? Unsure about the formatting of the booktitle +% ? Title looks like it was stored in title-case in Zotero + +@article{ref:wrede2020, + title = {Generation of High-Performance Code Based on a Domain-Specific Language for Algorithmic Skeletons}, + author = {Wrede, Fabian and Rieger, Christoph and Kuchen, Herbert}, + date = {2020-07}, + journaltitle = {The Journal of Supercomputing}, + volume = {76}, + pages = {5098--5116}, + doi = {10.1007/s11227-019-02825-6}, + langid = {english}, + number = {7} +} + +@inproceedings{ref:zhang2009, + title = {Implementing and Testing Producer-Consumer Problem Using Aspect-Oriented Programming}, + booktitle = {2009 {{Fifth International Conference}} on {{Information Assurance}} and {{Security}}}, + author = {Zhang, Yang and Zhang, Jingjun and Zhang, Dongwen}, + date = {2009}, + pages = {749--752}, + publisher = {{IEEE}}, + location = {{Xi'An China}}, + doi = {10.1109/IAS.2009.41}, + eventtitle = {2009 {{Fifth International Conference}} on {{Information Assurance}} and {{Security}}}, + isbn = {978-0-7695-3744-3} +} +% == BibLateX quality report for ref:zhang2009: +% ? Unsure about the formatting of the booktitle + +@inproceedings{ref:zhang2018, + title = {Vectorized Parallel Sparse Matrix-Vector Multiplication in {{PETSc}} Using {{AVX}}-512}, + booktitle = {Proceedings of the 47th {{International Conference}} on {{Parallel Processing}}}, + author = {Zhang, Hong and Mills, Richard T. and Rupp, Karl and Smith, Barry F.}, + date = {2018-08-13}, + pages = {1--10}, + publisher = {{ACM}}, + location = {{Eugene OR USA}}, + doi = {10.1145/3225058.3225100}, + eventtitle = {{{ICPP}} 2018: 47th {{International Conference}} on {{Parallel Processing}}}, + isbn = {978-1-4503-6510-9}, + langid = {english} +} + +@article{ref:zhu1993, + title = {A General Descent Framework for Monotone Variational Inequalities}, + author = {Zhu, Daoli L. and Marcotte, Patrice}, + date = {1993}, + journaltitle = {Journal of optimisation theory and applications}, + entrysubtype = {newspaper} +} + +@article{ref:zima1988, + title = {{{SUPERB}}: A Tool for Semi-Automatic {{MIMD}}/{{SIMD}} Parallelization}, + shorttitle = {{{SUPERB}}}, + author = {Zima, Hans P and Bast, Heinz-J and Gerndt, Michael}, + date = {1988-01}, + journaltitle = {Parallel Computing}, + shortjournal = {Parallel Computing}, + volume = {6}, + pages = {1--18}, + issn = {0167-8191}, + doi = {10.1016/0167-8191(88)90002-6}, + langid = {english}, + number = {1} +} + + +% Required packages: +% * textcomp + diff --git a/src/tikz/alsk.tex b/src/tikz/alsk.tex new file mode 100644 index 0000000..15eeb37 --- /dev/null +++ b/src/tikz/alsk.tex @@ -0,0 +1,98 @@ +\colorlet{colStruct}{cyan!50!blue} +\colorlet{colMuscle}{yellow!50!red} +\definecolor{colLinkIn}{RGB}{127,66,255} +\definecolor{colLinkOut}{RGB}{127,189,0} +\colorlet{colLinkCtx}{colLinkIn!50!red!80!black} + +%{{{ +\tikzset{alsk/label/.style={}} +%}}} + +%{{{ pack for schemes " +\tikzset{alsk/scheme/split/.style={ + common/drawfill=colStruct, + regular polygon,regular polygon sides=3, + minimum width=12mm, + inner sep=0mm +}} + +\tikzset{alsk/scheme/join/.style={ + alsk/scheme/split, + shape border rotate=180 +}} + +\tikzset{alsk/scheme/loop/.style={ + common/drawfill=colStruct, + regular polygon,regular polygon sides=4, + minimum width=10mm, + inner sep=0mm, + shape border rotate=45 +}} + +\tikzset{alsk/scheme/frame/.style={ + draw=#1, + rectangle,dotted,thick, + rounded corners=1mm, + inner sep=1mm +}} + +\tikzset{alsk/scheme/task/.style={ + common/drawfill=colMuscle, + circle, + minimum width=10mm, + inner sep=0mm +}} + +\tikzset{alsk/scheme/link/.style={ + ->, + rounded corners +}} +%}}} + +%{{{ pack for trees " +\tikzset{alsk/tree/muscle/.style={ + common/drawfill=colMuscle, + rectangle,rounded corners=.7mm, + minimum width=12mm,minimum height=6mm +}} + +\tikzset{alsk/tree/struct/.style={ + common/drawfill=colStruct, + rectangle,rounded corners=.7mm, + minimum width=16mm,minimum height=6mm +}} + +\tikzset{alsk/treestyle/.style={ + edge from parent path={(\tikzparentnode) -- (\tikzchildnode.north)}, + level distance=15mm,sibling distance=1mm, + branch/.style=alsk/tree/struct, + leaf/.style=alsk/tree/muscle, + every internal node/.style=branch, + every leaf node/.style=leaf, +}} +%}}} + +%{{{ pack for links " +\tikzset{alsk/links/muscle/.style={ + common/drawfill=colMuscle, + rectangle,rounded corners=.7mm, + minimum width=10mm,minimum height=5mm, +}} + +\tikzset{alsk/links/sig/.style={ + common/drawfill=#1, + signal,signal from=west,signal to=east, + minimum width=2mm, +}} + +\tikzset{alsk/links/link/.style={ + -,rounded corners=.8mm +}} + +\tikzset{alsk/links/fit/.style={ + draw, + dashed,rounded corners=2mm, + fit=#1, + fit margins={left=-1mm,bottom=1.5mm,right=2mm,top=1.5mm}, +}} +%}}} diff --git a/src/tikz/arch.tex b/src/tikz/arch.tex new file mode 100644 index 0000000..4932fa6 --- /dev/null +++ b/src/tikz/arch.tex @@ -0,0 +1,4 @@ +\tikzset{arch/cell/.style={ + common/drawfill=black, + rectangle,rounded corners=.5mm +}} diff --git a/src/tikz/ast.tex b/src/tikz/ast.tex new file mode 100644 index 0000000..dcb78a5 --- /dev/null +++ b/src/tikz/ast.tex @@ -0,0 +1,11 @@ +\tikzset{ast/tree/node/.style={ + common/drawfill=black, + rectangle,minimum size=6mm,rounded corners=1mm, + align=center +}} + +\tikzset{ast/treestyle/.style={ + edge from parent path={(\tikzparentnode) -- (\tikzchildnode.north)}, + level distance=15mm,sibling distance=3mm, + every tree node/.style=ast/tree/node, +}} diff --git a/src/tikz/common.tex b/src/tikz/common.tex new file mode 100644 index 0000000..cb807d2 --- /dev/null +++ b/src/tikz/common.tex @@ -0,0 +1,23 @@ +\tikzset{common/drawfillf/.style n args={2}{ + draw=#1,fill=#1!#2 +}} + +\tikzset{common/drawfill/.style={ + common/drawfillf={#1}{5} +}} + +\tikzset{common/arrow/.style={ + rounded corners +}} + +\tikzset{common/overlay/.style={overlay}} + +\tikzset{fit margins/.style={ + /tikz/afit/.cd,#1, + /tikz/.cd, + inner xsep=\pgfkeysvalueof{/tikz/afit/left}+\pgfkeysvalueof{/tikz/afit/right}, + inner ysep=\pgfkeysvalueof{/tikz/afit/top}+\pgfkeysvalueof{/tikz/afit/bottom}, + xshift=-\pgfkeysvalueof{/tikz/afit/left}+\pgfkeysvalueof{/tikz/afit/right}, + yshift=-\pgfkeysvalueof{/tikz/afit/bottom}+\pgfkeysvalueof{/tikz/afit/top} +}, afit/.cd,left/.initial=2pt,right/.initial=2pt,bottom/.initial=2pt,top/.initial=2pt +} diff --git a/src/tikz/diagram.tex b/src/tikz/diagram.tex new file mode 100644 index 0000000..624c319 --- /dev/null +++ b/src/tikz/diagram.tex @@ -0,0 +1,25 @@ +\tikzset{diagram/element/.style={ + draw=#1,fill=#1!5, + rectangle,rounded corners=2mm, + minimum width=32mm,minimum height=12mm, + align=center +}} + +\tikzset{diagram/small element/.style={ + draw=#1,fill=#1!5, + rectangle,rounded corners=1mm, + minimum width=12mm,minimum height=12mm +}} + +\tikzset{diagram/fit/.style={ + fit=#1,draw,dashed, + rectangle,rounded corners=2mm +}} + +\tikzset{diagram/arrow/.style={ + ->,rounded corners=1mm +}} + +\tikzset{diagram/inherit/.style={ + -{Triangle[fill=white,angle=75:15pt]} +}} diff --git a/src/tikz/et.tex b/src/tikz/et.tex new file mode 100644 index 0000000..4a3f9b0 --- /dev/null +++ b/src/tikz/et.tex @@ -0,0 +1,17 @@ +\tikzset{et/tree/node/.style={ + common/drawfill=black, + circle,minimum size=9mm, + inner sep=.5mm +}} + +\tikzset{et/treestyle/.style={ + edge from parent path={(\tikzparentnode) -- (\tikzchildnode.north)}, + level distance=15mm,sibling distance=1mm, + every tree node/.style=et/tree/node, +}} + +\tikzset{et/frame/.style={ + draw,dashed, + rectangle,rounded corners=3mm, + fit=#1 +}} diff --git a/src/tikz/exec.tex b/src/tikz/exec.tex new file mode 100644 index 0000000..cf93fe8 --- /dev/null +++ b/src/tikz/exec.tex @@ -0,0 +1,21 @@ +\tikzset{exec/fifonode/.style={ + draw,fill=black!3,signal,signal from=west,signal to=east, + minimum width=12mm,minimum height=6mm,line width=.1mm +}} + +\tikzset{exec/thread/.style={ + draw=#1,fill=#1!10, + rectangle,rounded corners=2mm, + minimum width=6mm,minimum height=6mm +}} + +\tikzset{exec/pool/.style={ + draw,fill=black!2, + fit=#1, + decorate,decoration={snake,amplitude=1pt} +}} + +\tikzset{exec/fit/.style={ + draw,rounded corners,dashed, + fit=#1 +}} diff --git a/src/tikz/imgpro.tex b/src/tikz/imgpro.tex new file mode 100644 index 0000000..004514f --- /dev/null +++ b/src/tikz/imgpro.tex @@ -0,0 +1,13 @@ +\tikzset{imgpro/pixel/.style={ + fill=#1!25, + rectangle, + minimum size=7.8mm, + inner sep=0mm +}} + +\tikzset{imgpro/centre/.style={ + common/drawfillf={black}{10}, + circle, + minimum size=0.8mm, + inner sep=0mm +}} diff --git a/src/tikz/list.tex b/src/tikz/list.tex new file mode 100644 index 0000000..dc46367 --- /dev/null +++ b/src/tikz/list.tex @@ -0,0 +1,28 @@ +\tikzset{list/node/.style={ + common/drawfill=black, + rectangle,rounded corners=1mm, + minimum width=8mm,minimum height=8mm +}} + +\tikzset{list/arrow/.style={ + ->,>=stealth,thick, + rounded corners=1mm +}} + +\tikzset{list/far below/.style={ + node distance=6mm, + below=#1 +}} + +\tikzset{list/near below/.style={ + node distance=3mm, + below=#1 +}} + +\tikzset{list/btn/.style={ + draw=white!5!black, + top color=black!25,bottom color=white, + drop shadow, + rectangle,rounded corners, + minimum width=6mm +}} diff --git a/src/tikz/mimd.tex b/src/tikz/mimd.tex new file mode 100644 index 0000000..e629575 --- /dev/null +++ b/src/tikz/mimd.tex @@ -0,0 +1,17 @@ +\tikzset{mimd/label/.style={ + common/drawfill=black, + rectangle,rounded corners=.5mm +}} + +\tikzset{mimd/hlabel/.style={ + mimd/label,minimum width={width("#1")+1em} +}} + +\tikzset{mimd/vlabel/.style={ + mimd/label,minimum height={width("#1")+1em} +}} + +\tikzset{mimd/arrow/.style={ + ->,>=stealth, + rounded corners=.3mm +}} diff --git a/src/tikz/orchestration.tex b/src/tikz/orchestration.tex new file mode 100644 index 0000000..2ca368b --- /dev/null +++ b/src/tikz/orchestration.tex @@ -0,0 +1,15 @@ +\tikzset{orch/task/.style={ + draw=#1,fill=#1!5, + circle, minimum width=12mm +}} + +\tikzset{orch/subtask/.style={ + draw=#1,fill=#1!15, + circle, minimum width=4mm +}} + +\tikzset{orch/taskspan/.style={ + draw=#1,fill=#1!5, + rectangle,rounded corners=1mm, + minimum width=28mm,minimum height=8mm +}} diff --git a/src/tikz/parallel.tex b/src/tikz/parallel.tex new file mode 100644 index 0000000..cd2bc0e --- /dev/null +++ b/src/tikz/parallel.tex @@ -0,0 +1,27 @@ +\tikzset{parallel/block/.style={ + thick, + minimum width=8mm,minimum height=8mm +}} + +\tikzset{parallel/task/.style={ + common/drawfill=black, + parallel/block, + rectangle,rounded corners, +}} + +\tikzset{parallel/arrow/.style={ + thick,->,>=stealth, + rounded corners +}} + +\tikzset{parallel/point/.style={ + common/drawfill=black, + circle,minimum width=2mm +}} + +\tikzset{parallel/region/.style={ + draw=black!50,fill=green!4, + rectangle,rounded corners=2mm, + dashed, + fit=#1 +}} diff --git a/src/tikz/repeat.tex b/src/tikz/repeat.tex new file mode 100644 index 0000000..35c0bd7 --- /dev/null +++ b/src/tikz/repeat.tex @@ -0,0 +1,37 @@ +\colorlet{colThread0}{red!60!green} +\colorlet{colThread1}{blue!70!green} + +\tikzset{repeat/thread0/.style={ + common/drawfill=colThread0, + rectangle,rounded corners=1mm, + minimum width=7mm,minimum height=7mm,inner sep=0mm +}} + +\tikzset{repeat/thread1/.style={ + common/drawfill=colThread1, + circle, + minimum width=7mm,inner sep=0mm +}} + +\tikzset{repeat/opti/element/.style={ + % common/drawfill=black, + rectangle,rounded corners, + text=black!55, + minimum width=5mm,minimum height=5mm, + inner sep=0mm, + % drop shadow={shadow xshift=.5mm,shadow yshift=-.5mm} +}} + +\tikzset{repeat/opti/fit/.style={ + % common/drawfill=black, + rectangle,rounded corners, + fit=#1, + fit margins={right=.4mm,top=.4mm,left=.4mm,bottom=.4mm}, + pattern={north east lines},pattern color=black!50 +}} + +\tikzset{repeat/opti/sep/.style={ + draw, + dashed, + very thick, +}} diff --git a/src/tikz/sm.tex b/src/tikz/sm.tex new file mode 100644 index 0000000..64d4873 --- /dev/null +++ b/src/tikz/sm.tex @@ -0,0 +1,15 @@ +\tikzset{sm/state/.style={ + common/drawfill=black, + rectangle,rounded corners=1mm, + minimum width=20mm,minimum height=10mm +}} + +\tikzset{sm/near above/.style={ + node distance=5mm, + above=of #1 +}} + +\tikzset{sm/arrow/.style={ + ->,>=stealth,thick, + rounded corners=1mm +}} diff --git a/src/tikz/tsp.tex b/src/tikz/tsp.tex new file mode 100644 index 0000000..99497d5 --- /dev/null +++ b/src/tikz/tsp.tex @@ -0,0 +1,27 @@ +\newcommand{\tspnodes}{ + \coordinate (p0) at (0, 0); + \coordinate (p1) at (1, 1); + \coordinate (p2) at (2, -.5); + \coordinate (p3) at (3.5, 1.5); + \coordinate (p4) at (2.5, 2); + \coordinate (p5) at (3, 2.5); + \coordinate (p6) at (2, 3); + \coordinate (p7) at (.5, 2.75); + \coordinate (p8) at (0.75, 2.25); + \coordinate (p9) at (-.5, 1.25); + + \def\n{10} + \pgfmathtruncatemacro{\last}{\n-1} + \pgfmathtruncatemacro{\penult}{\last-1} +} + +\tikzset{tsp/node/.style={ + fill=black, + circle, + minimum size=5pt, + inner sep=0pt +}} + +\tikzset{tsp/edge/.style={ + thick +}} diff --git a/src/usr/alg.tex b/src/usr/alg.tex new file mode 100644 index 0000000..fb5727c --- /dev/null +++ b/src/usr/alg.tex @@ -0,0 +1,28 @@ +% \renewcommand*\Call[2]{\textproc{#1}(#2)} + +\renewcommand{\listalgorithmname}{Liste des algorithmes} +\floatname{algorithm}{Algorithme} + +\algrenewcommand\algorithmicreturn{\textbf{retourner}} +\algrenewcommand\algorithmicprocedure{\textbf{procédure}} +\algrenewcommand\algorithmicfunction{\textbf{fonction}} +\algrenewcommand\algorithmicrequire{\textbf{Entrée :}} +\algrenewcommand\algorithmicensure{\textbf{Sortie :}} +\algrenewcommand\algorithmicend{\textbf{fin}} +\algrenewcommand\algorithmicif{\textbf{si}} +\algrenewcommand\algorithmicthen{\textbf{alors}} +\algrenewcommand\algorithmicelse{\textbf{sinon}} +\algrenewcommand\algorithmicfor{\textbf{pour}} +\algrenewcommand\algorithmicforall{\textbf{pour tout}} +\algrenewcommand\algorithmicdo{\textbf{faire}} +\algrenewcommand\algorithmicwhile{\textbf{tant que}} +\algrenewcommand\algorithmicrepeat{\textbf{répéter}} +\algrenewcommand\algorithmicuntil{\textbf{jusqu'à ce que}} + +% \renewcommand\Not{\textbf{non}\ } +\renewcommand\And{\textbf{et}\ } +% \renewcommand\Or{\textbf{ou}\ } + +\newcommand{\algorithmicelsif}{\algorithmicelse\ \algorithmicif} +\newcommand{\algorithmicendif}{\algorithmicend\ \algorithmicif} +\newcommand{\algorithmicendfor}{\algorithmicend\ \algorithmicfor} diff --git a/src/usr/bibstyle.tex b/src/usr/bibstyle.tex new file mode 100644 index 0000000..29246f2 --- /dev/null +++ b/src/usr/bibstyle.tex @@ -0,0 +1,58 @@ +% citations links include author +\DeclareFieldFormat{citehyperref}{% + \DeclareFieldAlias{bibhyperref}{noformat}% Avoid nested links + \bibhyperref{#1}} + +\DeclareFieldFormat{textcitehyperref}{% + \DeclareFieldAlias{bibhyperref}{noformat}% Avoid nested links + \bibhyperref{% + #1% + \ifbool{cbx:parens} + {\bibcloseparen\global\boolfalse{cbx:parens}} + {}}} + +\savebibmacro{cite} +\savebibmacro{textcite} + +\renewbibmacro*{cite}{% + \printtext[citehyperref]{% + \restorebibmacro{cite}% + \usebibmacro{cite}}} + +\renewbibmacro*{textcite}{% + \ifboolexpr{ + ( not test {\iffieldundef{prenote}} and + test {\ifnumequal{\value{citecount}}{1}} ) + or + ( not test {\iffieldundef{postnote}} and + test {\ifnumequal{\value{citecount}}{\value{citetotal}}} ) + } + {\DeclareFieldAlias{textcitehyperref}{noformat}} + {}% + \printtext[textcitehyperref]{% + \restorebibmacro{textcite}% + \usebibmacro{textcite}}} + +% replace cite by autocite +% \renewbibmacro*{cite}{\usebibmacro{autocite}} + +% disable small caps for author names +\renewcommand*{\mkbibnamefamily}[1]{#1} +\renewcommand*{\mkbibnameprefix}[1]{#1} + +% replace parenthesis by angle brackets +\makeatletter + +\newrobustcmd*{\parentexttrack}[1]{% + \begingroup + \blx@blxinit + \blx@setsfcodes + \blx@bibopenparen#1\blx@bibcloseparen + \endgroup} + +\AtEveryCite{% + \let\parentext=\parentexttrack% + \let\bibopenparen=\bibopenbracket% + \let\bibcloseparen=\bibclosebracket} + +\makeatother diff --git a/src/usr/header.tex b/src/usr/header.tex new file mode 100644 index 0000000..0ed09f0 --- /dev/null +++ b/src/usr/header.tex @@ -0,0 +1,25 @@ +\renewcommand{\chaptermark}[1]{\markboth{#1}{}} +\renewcommand{\sectionmark}[1]{\markright{#1}} + +\fancypagestyle{chapter}{ + \fancyhf{} + \fancyfoot[LE,RO]{\thepage} + \renewcommand{\headrulewidth}{0pt} + \renewcommand{\footrulewidth}{0pt} +} + +\fancypagestyle{plain}{ + \fancyhf{} + \fancyhead[LE,RO]{\nouppercase{\rightmark}} + \fancyfoot[LE,RO]{\thepage} + \renewcommand{\headrulewidth}{.4pt} + \renewcommand{\footrulewidth}{0pt} +} + +\fancypagestyle{content}{ + \fancyhf{} + \fancyhead[LE,RO]{\thesection~\rightmark} + \fancyfoot[LE,RO]{\thepage} + \renewcommand{\headrulewidth}{.4pt} + \renewcommand{\footrulewidth}{0pt} +} diff --git a/src/usr/header_oneside.tex b/src/usr/header_oneside.tex new file mode 100644 index 0000000..88bb023 --- /dev/null +++ b/src/usr/header_oneside.tex @@ -0,0 +1,25 @@ +\renewcommand{\chaptermark}[1]{\markboth{#1}{}} +\renewcommand{\sectionmark}[1]{\markright{#1}} + +\fancypagestyle{chapter}{ + \fancyhf{} + \fancyfoot[C]{\thepage} + \renewcommand{\headrulewidth}{0pt} + \renewcommand{\footrulewidth}{0pt} +} + +\fancypagestyle{plain}{ + \fancyhf{} + \fancyhead[L]{\nouppercase{\rightmark}} + \fancyfoot[C]{\thepage} + \renewcommand{\headrulewidth}{.4pt} + \renewcommand{\footrulewidth}{0pt} +} + +\fancypagestyle{content}{ + \fancyhf{} + \fancyhead[L]{\thesection~\rightmark} + \fancyfoot[C]{\thepage} + \renewcommand{\headrulewidth}{.4pt} + \renewcommand{\footrulewidth}{0pt} +} diff --git a/src/usr/listing.tex b/src/usr/listing.tex new file mode 100644 index 0000000..eeefd22 --- /dev/null +++ b/src/usr/listing.tex @@ -0,0 +1,107 @@ +%{{{ test environment " +\makeatletter +\def\ifenv#1{% + \def\@tempa{#1}% + \ifx\@tempa\@currenvir% + \expandafter\@firstoftwo% + \else% + \expandafter\@secondoftwo% + \fi% +} +\makeatother +%}}} + +%{{{ listingnote " +\def\ifempty#1{\def\temp{#1} \ifx\temp\empty} +\newcommand{\listingnote}[1]{% + \ifenv{listing}{% + \newcommand*{\mincppAddspace}{\vspace{-4.5ex}}% + }{% + \newcommand*{\mincppAddspace}{}% + }% + \begin{flushright}% + \vspace{-3.5ex}% + \ifempty{#1}% + \else% + {\scriptsize #1}% + \fi% + \mincppAddspace% + \end{flushright}% + \undef\mincppAddspace% +} +%}}} + +%{{{ C " +\newcommand*{\clanguage}[1]{C#1} + +\newcommand{\minc}[1]{% + \ifempty{#1}% + \listingnote{}% + \else% + \listingnote{($\geqslant$ \clanguage{#1})}% + \fi% +} +%}}} + +%{{{ C++ " +\newcommand*{\cpp}[1]{C++#1} + +\newcommand{\mincpp}[1]{% + \ifempty{#1}% + \listingnote{}% + \else% + \listingnote{($\geqslant$ \cpp{#1})}% + \fi% +} +\newcommand{\errcpp}[1]{% + \listingnote{(ne compile pas)}% +} +%}}} + +%{{{ asm x86 Intel " +\newcommand{\asm}{% + \listingnote{(x86 Intel)}% +} +%}}} + +%{{{ Haskell " +\newcommand{\haskell}{% + \listingnote{(Haskell)}% +} +%}}} + +%{{{ +\newcommand{\codenote}[1]{% + \begin{flushleft}% + \vspace{-3ex}% + {\scriptsize #1}% + \vspace{-2ex}% + \end{flushleft}% +}% +%}}} + +%{{{ current font size " +\makeatletter +\newcommand{\currentfontsize}{\fontsize{\f@size}{\f@baselineskip}\selectfont} +\makeatother +%}}} + +%{{{ configuration " +\setminted{frame=lines,fontsize=\small,tabsize=2,autogobble,breaklines} +\setmintedinline{fontsize=\currentfontsize} + +\newminted{cpp}{escapeinside=||} +\newmintinline{cpp}{breaklines,breakafter=_:<>} + +\newminted{c}{escapeinside=||} +\newmintinline{c}{breaklines,breakafter=_:<>} + +\newminted{asm}{} +\newmintinline{asm}{} + +\newminted{haskell}{} +\newmintinline{haskell}{} + +\newminted{lisp}{} +\newmintinline{lisp}{} +%}}} diff --git a/src/usr/math.tex b/src/usr/math.tex new file mode 100644 index 0000000..077e7a9 --- /dev/null +++ b/src/usr/math.tex @@ -0,0 +1,18 @@ +\newtheoremstyle{thmstyle} +{}% measure of space to leave above the theorem +{}% measure of space to leave below the theorem +{}% name of font to use in the body of the theorem +{}% measure of space to indent +{}% name of head font +{~:}% punctuation between head and body +{ }% space after theorem head +{\thmname{#1}\thmnumber{ #2}\thmnote{ #3}} + +\theoremstyle{thmstyle} + +\newtheorem*{thm}{Théorème} +\newtheorem*{thmdef}{Définition} +\newtheorem*{thmlemme}{Lemme} +\newtheorem*{thmproof}{Démonstration} +\newtheorem*{thmrq}{Remarque} +\newtheorem*{thmex}{Exemple} diff --git a/src/usr/ocg.tex b/src/usr/ocg.tex new file mode 100644 index 0000000..092881a --- /dev/null +++ b/src/usr/ocg.tex @@ -0,0 +1,269 @@ +%{{{ globals " +\newcounter{ocgid} +%}}} +%{{{ +\newcommand{\ocgradio}[4]{ + \makebox[0pt][l]{\actionsocg{}{#2}{}{#3}}% + \begin{ocg}[radiobtngrp=#1]{#2}{#2}{#4}\actionsocg{}{}{}{\underline{#3}}\end{ocg} +} + +\newcommand{\ocgcase}[4]{\makebox[0pt][c]{\begin{ocg}[radiobtngrp=#1]{#2}{#2}{#3}#4\end{ocg}}} +\newcommand{\ocmdcase}[2]{\makebox[0pt][c]{\begin{ocmd}{\And{#1}}#2\end{ocmd}}} +%}}} +%{{{ TikZ " +%{{{ +\newcommand{\ocgtikzrbtn}[6]{ + \node[#4,show ocg={#2}](#5){#6}; + \begin{ocg}[radiobtngrp=#1]{#2}{#2}{#3} + \node[#4,thick](#5){\textbf{#6}}; + \end{ocg} +} +%}}} +%{{{ alternative node/path " +\newcommand{\ocgnodestyle}[5]{ + \begin{ocmd}{\Not{\Or{#1}}} + \node[#2]#4{#5}; + \end{ocmd} + \begin{ocg}{#1}{#1}{0} + \node[#2,#3]#4{#5}; + \end{ocg} +} + +\newcommand{\ocgpathstyle}[4]{ + \begin{ocmd}{\Not{\Or{#1}}} + \path[#2] #4; + \end{ocmd} + \begin{ocg}{#1}{#1}{0} + \path[#2,#3] #4; + \end{ocg} +} +%}}} +%{{{ itemize " +\newcommand{\ocgitemize}[3]{ + \node#1{ + \begin{minipage}{#2} + \begin{itemize} + #3 + \end{itemize} + \end{minipage} + }; +} + +\newcommand{\ocgitem}[2]{ + \begin{ocmd}{\Or{#1}} + \item#2 + \end{ocmd} +} +%}}} +%{{{ makeprefix " +\newcommand*{\ocgmakeprefixes}[1]{% + \StrSubstitute{#1}{/}{-}[\clid]% + \edef\ocgprefix{fig-\chapterdir-\clid}% + \edef\prefix{\ocgprefix-vars}% + \edef\prefixI{\ocgprefix-var1}% + \edef\prefixII{\ocgprefix-var2}% +} +%}}} +%{{{ dynamic figure (1 dimension) " +\newcommand{\ocgfigI}[6]{% + % #1 global layer name ; + % #2 chapter-local path ; #3 caption ; + % #4 label I ; #5 var I ; #6 list I + \ocgmakeprefixes{#2}% + \def\listI{#6}% + + \centering + \stepcounter{ocgid} + \begin{ocg}{#1}{ocg\theocgid}{1} + \foreach #5/\ignI in \listI {% + \ocmdcase{\prefixI-#5}{% + \input{src/fig/\chapterdir/#2}% + }% + }% + \caption{#3} + \label{fig:\chapterdir/#2} + {% + \small $\triangleright$ + #4% + \foreach \I/\state in \listI {% + \ocgradio{\prefixI}{\prefixI-\I}{\I}{\state}% + }% + }\\ + \end{ocg} +} +%}}} +%{{{ dynamic figure (2 dimensions) " +\newcommand{\ocgfigII}[9]{% + % #1 global layer name ; + % #2 chapter-local path ; #3 caption ; + % #4 label I ; #5 var I ; #6 list I ; + % #7 label II ; #8 var II ; #9 list II + \ocgmakeprefixes{#2}% + \def\listI{#6}% + \def\listII{#9}% + + \centering + \stepcounter{ocgid} + \begin{ocg}{#1}{ocg\theocgid}{1} + \foreach #5/\ignI in \listI {% + \foreach #8/\ignII in \listII {% + \ocmdcase{\prefixI-#5,\prefixII-#8}{% + \input{src/fig/\chapterdir/#2}% + }% + }% + }% + \caption{#3} + \label{fig:\chapterdir/#2} + {% + \small $\triangleright$ + #4% + \foreach \I/\state in \listI {% + \ocgradio{\prefixI}{\prefixI-\I}{\I}{\state}% + }% + #7% + \foreach \II/\state in \listII {% + \ocgradio{\prefixII}{\prefixII-\II}{\II}{\state}% + }% + }\\ + \end{ocg} +} +%}}} +%{{{ dynamic figure (2 bound dimensions) " +\newcommand{\ocgfigIB}[7]{% + % #1 global layer name ; + % #2 chapter-local path ; #3 caption ; + % #4 label I/II ; #5 var I ; #6 var II ; #7 list I/II + \ocgmakeprefixes{#2}% + \def\list{#7}% + + \centering + \stepcounter{ocgid} + \begin{ocg}{#1}{ocg\theocgid}{1} + \foreach #5/#6/\ign in \list {% + \ocmdcase{\prefix-#5-#6}{% + \input{src/fig/\chapterdir/#2}% + }% + }% + \caption{#3} + \label{fig:\chapterdir/#2} + {% + \small $\triangleright$ + #4% + \foreach \I/\II/\state in \list {% + \ocgradio{\prefix}{\prefix-\I-\II}{\I/\II}{\state}% + }% + }\\ + \end{ocg} +} +%}}} +%{{{ dynamic figure (1 dimension with remember picture) " +%{{{ ocgfigRPbuttons " +\newcommand{\ocgfigRPbuttons}[4]{% + \def\listI{#2}% + \foreach #1/\state in \listI {% + \foreach \position in {#4} {% + \tikz[remember picture,overlay]{\node[#3,show ocg={\prefixI-#1}] at (\position) {};}% + }% + }% +} +%}}} +\newcommand{\ocgfigRP}[9]{% + % #1 global layer name ; + % #2 chapter-local path (tikz rp) ; #3 chapter-local path ; #4 caption ; + % #5 var I ; #6 list I ; + % #7 node style ; #8 node positions + % #9 subcaption text + \ocgmakeprefixes{#3}% + \def\listI{#6}% + + \centering + \stepcounter{ocgid} + \begin{ocg}{#1}{ocg\theocgid}{1} + \makebox[0pt][c]{\begin{ocg}{.ocg}{.ocg}{1}\input{src/fig/\chapterdir/#2}\end{ocg}}% + \foreach #5/\state in \listI {% + \ocgcase{\prefixI}{\prefixI-#5}{\state}{% + \input{src/fig/\chapterdir/#3}% + }% + }% + \caption{#4} + \label{fig:\chapterdir/#3} + {% + \small $\triangleright$ + #9% + }\\ + \ocgfigRPbuttons{#5}{#6}{#7}{#8}% + \end{ocg} +} +%}}} +%{{{ dynamic figure (1 dimension) with includegraphics " +\newcommand{\ocgfigIG}[8]{% + % #1 global layer name ; + % #2 chapter-local path ; #3 fn pre ; #4 fn post ; #5 caption ; + % #6 label I ; #7 var I ; #8 list I + \ocgmakeprefixes{#2}% + \def\listI{#8}% + + \centering + \stepcounter{ocgid} + \begin{ocg}{#1}{ocg\theocgid}{1} + \foreach #7/\ignI in \listI {% + \ocmdcase{\prefixI-#7}{% + \includegraphics{img/\chapterdir/#3#7#4}% + }% + }% + \caption{#5} + \label{fig:\chapterdir/#2} + {% + \small $\triangleright$ + #6% + \foreach \I/\state in \listI {% + \ocgradio{\prefixI}{\prefixI-\I}{\I}{\state}% + }% + }\\ + \end{ocg} +} +%}}} +%}}} +%{{{ Specific for chapter par " +\newcommand*{\ocgpdfonly}[1]{% +\begin{ocg}[printocg=never,exportocg=never]{OCG \theocgid}{ocg\theocgid}{1} +#1 +\end{ocg} +} + +\newcommand{\ocgalt}[5]{% +\stepcounter{ocgid}% +\begin{ocg}{#1}{ocg\theocgid}{1}% +\makebox[0pt][l]{% +\begin{ocg}[printocg=never,exportocg=never]{#2 (alt)}{#3-alt}{0} +#5 +\end{ocg} +} +\begin{ocg}[printocg=always,exportocg=always]{#2}{#3}{1} +#4 +\end{ocg} +\end{ocg} +} + +\newcommand{\ocgtikzalt}[4][n]{% +\ifstrequal{#1}{n} +{\def\status{visible}\def\altstatus{invisible}} +{\def\status{invisible}\def\altstatus{visible}} +\begin{scope}[ocg={ref=#2,status=\status}] +#3 +\end{scope} +\begin{scope}[ocg={ref=#2-alt,status=\altstatus}] +#4 +\end{scope} +} + +\newcommand*{\switchocgalt}[2]{\switchocg{#1 #1-alt}{#2}} + +\newcommand*{\ocgtogglestaticdynamic}[1]{% +\ocgpdfonly{% +\switchocgalt{#1}{% +\small $\triangleright$ basculer entre la version statique et la version dynamique +} +} +} +%}}} diff --git a/src/usr/ocg_print.tex b/src/usr/ocg_print.tex new file mode 100644 index 0000000..e7add5c --- /dev/null +++ b/src/usr/ocg_print.tex @@ -0,0 +1,115 @@ +%{{{ +\renewcommand{\ocgradio}[4]{} +\renewcommand{\ocgcase}[4]{} +\renewcommand{\ocmdcase}[2]{} +%}}} +%{{{ TikZ " +%{{{ +\renewcommand{\ocgtikzrbtn}[6]{} +%}}} +%{{{ alternative node/path " +\renewcommand{\ocgnodestyle}[5]{} +\renewcommand{\ocgpathstyle}[4]{} +%}}} +%{{{ itemize " +\renewcommand{\ocgitemize}[3]{} +\renewcommand{\ocgitem}[2]{} +%}}} +%{{{ dynamic figure (1 dimension) " +\renewcommand{\ocgfigI}[6]{% + % #1 global layer name ; + % #2 chapter-local path ; #3 caption ; + % #4 label I ; #5 var I ; #6 list I + \def\listI{#6} + + \centering + \foreach #5/\state in \listI {\IfStrEq{\state}{on}{\input{src/fig/\chapterdir/#2}}{}}% + \caption{#3} + \label{fig:\chapterdir/#2} + {% + \phantom{\small $\triangleright$ #4}% + }\\ +} +%}}} +%{{{ dynamic figure (2 dimensions) " +\renewcommand{\ocgfigII}[9]{% + % #1 global layer name ; + % #2 chapter-local path ; #3 caption ; + % #4 label I ; #5 var I ; #6 list I ; + % #7 label II ; #8 var II ; #9 list II + \def\listI{#6} + \def\listII{#9} + + \centering + \foreach #5/\stateI in \listI {% + \IfStrEq{\stateI}{on}{% + \foreach #8/\stateII in \listII {% + \IfStrEq{\stateII}{on}{\input{src/fig/\chapterdir/#2}}{}% + }% + }{}% + }% + \caption{#3} + \label{fig:\chapterdir/#2} + {% + \phantom{\small $\triangleright$ #4}% + }\\ +} +%}}} +%{{{ dynamic figure (2 bound dimensions) " +\renewcommand{\ocgfigIB}[7]{% + % #1 global layer name ; + % #2 chapter-local path ; #3 caption ; + % #4 label I/II ; #5 var I ; #6 var II ; #7 list I/II + \def\list{#7} + + \centering + \foreach #5/#6/\state in \list {\IfStrEq{\state}{on}{\input{src/fig/\chapterdir/#2}}{}}% + \caption{#3} + \label{fig:\chapterdir/#2} + {% + \phantom{\small $\triangleright$ #4}% + }\\ +} +%}}} +%{{{ dynamic figure (1 dimension with remember picture) " +\renewcommand{\ocgfigRP}[9]{% + % #1 global layer name ; + % #2 chapter-local path (tikz rp) ; #3 chapter-local path ; #4 caption ; + % #5 var I ; #6 list I ; + % #7 node style ; #8 node position + % #9 subcaption text + \def\listI{#6}% + + \centering + \foreach #5/\state in \listI {\IfStrEq{\state}{on}{\input{src/fig/\chapterdir/#3}}{}}% + \caption{#4} + \label{fig:\chapterdir/#3} + {% + \phantom{\small $\triangleright$ #9}% + }\\ +} +%}}} +%{{{ dynamic figure (1 dimension) with includegraphics " +\renewcommand{\ocgfigIG}[8]{% + % #1 global layer name ; + % #2 chapter-local path ; #3 fn pre ; #4 fn post ; #5 caption ; + % #6 label I ; #7 var I ; #8 list I + \def\listI{#8}% + + \centering + \foreach #7/\state in \listI {\IfStrEq{\state}{on}{\includegraphics{img/\chapterdir/#3#7#4}}{}}% + \caption{#5} + \label{fig:\chapterdir/#2} + {% + \phantom{\small $\triangleright$ #6}% + }\\ +} +%}}} +%}}} +%{{{ Specific for chapter par " +\renewcommand*{\ocgpdfonly}[1]{} +\renewcommand{\ocgalt}[5]{#4} +\renewcommand*{\ocgtogglestaticdynamic}[1]{% + \phantom{\small $\triangleright$ basculer entre la version statique et la version dynamique} +} +%}}} diff --git a/src/usr/silence.tex b/src/usr/silence.tex new file mode 100644 index 0000000..a46db3d --- /dev/null +++ b/src/usr/silence.tex @@ -0,0 +1,5 @@ +\makeatletter +\renewcommand{\PackageInfo}[2]{}% Remove package information +\renewcommand{\@font@info}[1]{}% Remove font information +\renewcommand{\@latex@info}[1]{}% Remove LaTeX information +\makeatother diff --git a/src/usr/tikz.tex b/src/usr/tikz.tex new file mode 100644 index 0000000..e00e89b --- /dev/null +++ b/src/usr/tikz.tex @@ -0,0 +1,3 @@ +\makeatletter +\newcommand\curcoord{\the\tikz@lastxsaved,\the\tikz@lastysaved} +\makeatother diff --git a/sty/acref.sty b/sty/acref.sty new file mode 100644 index 0000000..5260ab6 --- /dev/null +++ b/sty/acref.sty @@ -0,0 +1,132 @@ +\ProvidesPackage{sty/acref} + +\RequirePackage{etoolbox} +\RequirePackage{listofitems} + +%{{{ +\def\acref@instring#1#2{TT\fi\begingroup + \edef\x{\endgroup\noexpand\in@{#1}{#2}}\x\ifin@} +%}}} + +%{{{ cleveref bindings " +\def\acref@cleveref@getname#1#2{\csname #1@#2@name\endcsname} +%}}} + +%{{{ acref " +\let\acref@cref\cref +\renewcommand*{\cref}[1]{\begin{otherlanguage}{english}\acref@cref{#1}\end{otherlanguage}\xspace} + +\newcommand*{\acref@error@article}[2]{% + \PackageError{acref}{undefined article for '#1' (type: '#2', page: \thepage) (possible solution is to add: + \string\acrefarticle{#2}{}{})}{}% +} + +\newcommand*{\acref@error@type}[2]{% + \PackageError{acref}{undefined type for '#1' (type: '#2', page: \thepage) (require changes in + package in \string\acref@article and \string\acref@Article: add + \string\acref@printarticle{\#1}{#2}{\string\acref@art@#2}}{} +} + +\newcommand*{\acref@warning@prefix}[1]{% + \ClassWarning{acref}{Reference '#1' on page \thepage\space has unknown prefix}% +} + +\newcommand*{\acref@printarticle}[3]{\expandafter\ifstrequal\expandafter{#1}{#2}{% + \csname acref@#3@#2\endcsname\def\acref@article@isset{1}}{}% +} + +\newcommand*{\acref@prefixtotype}[1]{\csname acref@prefix@#1\endcsname} +\newcommand*{\acref@crefname}[1]{\acref@cleveref@getname{cref}{#1}} + +\newcommand*{\acref@article}[2]{% + \acref@printarticle{#1}{algorithm}{#2}% + \acref@printarticle{#1}{chapter}{#2}% + \acref@printarticle{#1}{enumi}{#2}% + \acref@printarticle{#1}{equation}{#2}% + \acref@printarticle{#1}{figure}{#2}% + \acref@printarticle{#1}{listing}{#2}% + \acref@printarticle{#1}{section}{#2}% + \acref@printarticle{#1}{subfigure}{#2}% + \acref@printarticle{#1}{subsection}{#2}% + \acref@printarticle{#1}{subsubsection}{#2}% + \acref@printarticle{#1}{table}{#2}% + \acref@printarticle{#1}{theorem}{#2}% + \acref@printarticle{#1}{plural}{#2}% +} + +\newcommand*{\acref@acref}[2]{% + \if\acref@instring{,}{#1}% + \acref@article{plural}{#2}% + \else + \begin{otherlanguage}{english} + \ifcsname r@#1@cref\endcsname + \cref@gettype{#1}{\acref@type}% + \ifcsname acref@art@\acref@type\endcsname + \def\acref@article@isset{}% + \acref@article{\acref@type}{#2}% + \expandafter\ifstrequal\expandafter{\acref@article@isset}{1}{}{\acref@error@type{#1}{\acref@type}}% + \else + \acref@error@article{#1}{\acref@type} + \fi + \else + {% invalid reference + \setsepchar{:}% + \edef\@arg{#1}% + \readlist*\@tmp{\@arg}% + \edef\@prefix{\@tmp[1]}% + \ifcsname acref@prefix@\@prefix\endcsname + \edef\acref@type{\acref@prefixtotype{\@prefix}}% + \acref@article{\acref@type}{#2}% + \acref@crefname{\acref@type}% + \else + \textbf{\@prefix}% + \acref@warning@prefix{#1}% + \fi + } + \fi + \end{otherlanguage} + \fi + \cref{#1}% +} + +\newcommand*{\acrefarticle}[3]{% + \expandafter\def\csname acref@art@#1\endcsname{#2} + \expandafter\def\csname acref@Art@#1\endcsname{#3} +} + +\newcommand*{\acrefprefix}[2]{% + \expandafter\def\csname acref@prefix@#1\endcsname{#2} +} + +\newcommand*{\acref}[2][n]{\acref@acref{#2}{art}} +\newcommand*{\Acref}[2][n]{\acref@acref{#2}{Art}} +%}}} + +%{{{ " default configuration +\acrefarticle{algorithm}{l'}{L'} +\acrefarticle{chapter}{le }{Le } +\acrefarticle{enumi}{le }{Le } +\acrefarticle{equation}{l'}{L'} +\acrefarticle{figure}{la }{La } +\acrefarticle{listing}{la }{La } +\acrefarticle{section}{la }{La } +\acrefarticle{subfigure}{la }{La } +\acrefarticle{subsection}{la }{La } +\acrefarticle{subsubsection}{la }{La } +\acrefarticle{table}{le }{Le } +\acrefarticle{theorem}{le }{Le } + +\acrefarticle{plural}{les }{Les } + +\acrefprefix{alg}{algorithm} +\acrefprefix{ch}{chapter} +\acrefprefix{enumi}{enumi} +\acrefprefix{eq}{equation} +\acrefprefix{fig}{figure} +\acrefprefix{lst}{listing} +\acrefprefix{sec}{section} +\acrefprefix{subsec}{subsection} +\acrefprefix{subsubsec}{subsubsection} +\acrefprefix{tab}{table} +\acrefprefix{th}{theorem} +%}}}