Intro

Il parait qu'avec le langage C++ on peut écrire du code

Pourtant le langage est une hydre protozoaire... Donc

Merci le Compilo o/

Serge « sans paille » Guelton

Apprendre à communiquer avec le meilleur ami du langage

CPPP — 15 juin 2019

Contexte

Environnement

Houat else?

C++, ce language si rapide

#include <iostream>
int main(int argc, char** argv) {
  long s = 0;
  while (std::cin) {
    long tmp = 0;
    std::cin >> tmp;
    s += tmp;
  }
  std::cout << s << std::endl;
  return 0;
}

Python, ce language si lent

import sys
print(sum(int(x)
          for x in sys.stdin.readlines()))

Ze Bench, Marc !

$ seq 1000000 > numbers
$ clang++ sum.cpp -o sum
$ time ./sum < numbers
0.61s user 0.01s system 94% cpu 0.659 total

$ time python sum.py < numbers
0.77s user 0.04s system 99% cpu 0.818 total

L'utilisateur n'a pas précisé son intention

Spécifier son intention

$ clang++ -O2 sum.cpp -o sum
$ time ./count < numbers
0.34s user 0.00s system 99% cpu 0.348 total

Optimisation multi-critère

 #
 ##                           #
 ##                           ##
 ##            ##             ##
 ##            ##             ##
 ##            ##             ##
 ##    ##      ##             ##
 ##    ##      ##      #      ##
 ##    ##      ##      ##     ##
PERF  DEBUG  ÉDITION  SÉCU  TAILLE

Performance

« Je veux que le code généré soit efficace »

Debug

« Je veux que le code généré soit facile à déverminer »

$ curl $sq | clang -xc -c -g - -o sq.o
$ objdump -h sq.o | grep debug
  #  name            size      ...
   9 .debug_str      00012b2d  ...
  10 .debug_abbrev   0000038d  ...
  11 .debug_info     0005056c  ...
  12 .debug_ranges   00000240  ...
  13 .debug_macinfo  00000001  ...
  14 .debug_pubnames 0000c73a  ...
  15 .debug_pubtypes 00001068  ...
  19 .debug_line     00073402  ...

Sécurité

« Je veux me protéger de moi-même »

$ clang -xc -c -O2 - -S -emit-llvm -o \
    - -D_FORTIFY_SOURCE=2 << EOF
#include <stdio.h>
void foo(char *s) {
  printf(s, s);
}
EOF
define void @foo(i8*) {
  %2 = tail call i32 (i32, i8*, ...) \
   @__printf_chk(i32 1, i8* %0, i8* %0)
  ret void
}

Taille

« Je veux un binaire de petite taille »

$ curl $sq|clang -xc - -O2 -c -o-|wc -c
1488400
$ curl $sq|clang -xc - -Os -c -o-|wc -c
850696
$ curl $sq|clang -xc - -Oz -c -o-|wc -c
796976

Édition

$ cat hello.cpp
#include <iostream>
int main(int argc, char**argv) {
  std::co
$ clang++ -Xclang \
  -code-completion-at=hello.cpp:3:10 \
  -fsyntax-only hello.cpp
COMPLETION: codecvt : codecvt<<#typename _InternT#>, <#typename _ExternT#>, <#typename _StateT#>>
COMPLETION: codecvt_base : codecvt_base
...
COMPLETION: cout : [#ostream#]cout

Faire les con (promis)

Debug vs Size

Impact du niveau de debug sur la taille

$ for g in 1 2 3 ""
  do
  printf "-g$g: \t" && \
  curl $sq|clang -c -O2 -g$g -xc - -o-|\
    wc -c
  done
-g1   : 3168632
-g2   : 7025488
-g3   : 7025488
-g    : 7025488

Bonus : -fdebug-macro -g : 7167752

Impact du niveau d'optimisation sur temps de compilation

$ for O in 0 1 2 3
  do
  /usr/bin/time -f "-O$O: %e s" \
    clang sqlite3.c -c -O$O
  done
-O0: 22.15 s
-O1: 24.02 s
-O2: 22.68 s
-O3: 22.36 s

exercice : proposez une explication

Faire les con (promis)

Précision vs Performance

$ clang -xc - -o- -S -emit-llvm -O2 \
     -freciprocal-math << EOF
double rm(double x) {
  return x / 10.;
}
EOF
define double @rm(double) {
  %2 = fmul arcp double %0, 1.000000e-01
  ret double %2
}

Variation

$ clang -xc++ - -o- -S -emit-llvm \
  -Ofast << EOF
#include <numeric>
#include <vector>
using namespace std;
double acc(vector<double> const& some)
{
  return accumulate(
           some.begin(),
           some.end(),
           0.);
}
EOF
...
%95 = fadd fast <2 x double> %94, %93
...

Pourquoi la vectorisation est-elle légale ici ?

Faire des compromis

Portabilité vs Performance

$ clang++ -O2 -S -o- -march=native \
  -ffp-contract=fast << EOF
double fma(double x, double y, double z) {
  return x + y * z;
}
EOF
...
vfmadd213sd %xmm0, %xmm2, %xmm1

Faire des compromis

Performance vs Sureté

// mem.cpp
#include <memory>
double x(std::unique_ptr<double> y) {
  return *y;
}

QUIZZ : combien de déréférencements ?

...

$ clang++ -fsanitize=address mem.cpp -S -emit-llvm -o- -O2
...
%h = getelementptr inbounds %"class.std::unique_ptr", %"class.std::unique_ptr"* %y, i64 0, i32 0, i32 0, i32 0, i32 0, i32 0
%1 = ptrtoint double** %h to i64
%2 = lshr i64 %1, 3
%3 = add i64 %2, 2147450880
%4 = inttoptr i64 %3 to i8*
%5 = load i8, i8* %4
%6 = icmp ne i8 %5, 0
br i1 %6, label %7, label %8

; <label>:7:
call void @__asan_report_load8(i64 %1)
call void asm sideeffect "", ""()
unreachable

; <label>:8:
%9 = load double*, double** %h, align 8, !tbaa !2

from __future__ import

$ clang --autocomplete=-std=,
...
c++2a
...
cuda
...
gnu1x
...
iso9899:2011

Controler le compilateur avec plus de précision

Security

...

$ clang -O2 -fstack-protector-all -S \
-o- -xc++ - << EOF
#include <array>
using namespace std;
auto access(array<__int128_t, 10> a,
            unsigned i)
{
  return a[i];
}
EOF
...
cmpq    (%rsp), %rcx
jne .LBB0_2
popq    %rcx
retq
.LBB0_2:
callq   __stack_chk_fail

Optimisation Top Tier (1/2)

P rofile G uided O ptimisation

  1. Compiler avec -fprofile-generate
  2. Exécuter son code sur des cas représentatifs
  3. Recompiler son code avec -fprofile-use

⇒ Influe sur

Optimisation Top Tier (2/2)

L ink T ime O ptimisation

$ echo 'foo() { return 0;}' | \
  clang -flto -O2 -xc - -c -ofoo.o
$ file foo.o
foo.o: LLVM bitcode

Quelques leviers d'optimisation supplémentaires

Rubrique à brac

Restons #pragma tic (1/3)

Pour ne pas optimiser certaines fonctions (e.g. pour du debug) :

#pragma clang optimize off
...
#pragma clang optimize on

Restons #pragma tic (2/3)

Pour bien optimiser ses boucles :

#pragma clang loop unroll(enable|full)
#pragma clang loop unroll_count(8)
#pragma clang loop distribute(enable)
#pragma clang loop vectorize_width(4)

Restons #pragma tic (3/3)

Pour la précision :

#pragma clang fp contract(fast)

Pour l'éditeur de lien :

#pragma clang section bss="myBSS" \
              data="myData" \
              rodata="myRodata" \
              text="myText"

Medley

#if __has_attribute(always_inline)

#if __has_builtin(__builtin_trap)

#if __has_include("myinclude.h")

__clang__

typedef float float4 \
 __attribute__((ext_vector_type(4)));

 __fp16

__is_pod (GNU, Microsoft)

Aide au développement

...

// d.cpp
double x(double* y, bool cond) {
  if(cond)
    delete y;
  bool ncond = !cond;
  if(ncond)
    return 1.;
  else
    return *y;
}

...

$ clang++ --analyze -Xanalyzer -analyzer-output=text d.cpp

d.cpp:8:12: warning: Use of memory after it is freed
    return *y;
           ^~
d.cpp:2:6: note: Assuming 'cond' is not equal to 0
  if(cond)
     ^~~~
d.cpp:2:3: note: Taking true branch
  if(cond)
  ^
d.cpp:3:5: note: Memory is released
    delete y;
    ^~~~~~~~
d.cpp:5:3: note: Taking false branch
  if(ncond)
  ^
d.cpp:8:12: note: Use of memory after it is freed
    return *y;

Pour comprendre...

Un compilateur un peu lent :

Une optimisation qui aurait mal tournée

...

$ { clang -xc++ - -c \
  -O2 -Rpass=inline << EOF
#include <numeric>
#include <vector>
using namespace std;
double acc(vector<double> const& some)
{
  return accumulate(
           some.begin(),
           some.end(),
           0.);
}
EOF
} 2>&1 | c++filt
...
... remark: __gnu_cxx::__normal_iterator<double const*, std::vector<double, std::allocator<double> > >::__normal_iterator(double const* const&) \
... inlined into std::vector<double, std::allocator<double> >::begin() const with cost=-40 (threshold=337) [-Rpass=inline]

Une exploration sans fin

$ clang --autocomplete=- | wc -l
2847

En compilation comme dans la vie, il faut une forme de balance

credit: Wotc / Mark Poole
1
SpaceForward
Right, Down, Page DownNext slide
Left, Up, Page UpPrevious slide
GGo to slide number
POpen presenter console
HToggle this help