Résultat différent suivant options de compilations

Le problème exposé dans ce sujet a été résolu.

Bonjour,

J’ai un problème avec un petit programme en C++ servant à compter le nombre de dimanche tombant le premier du mois. En effet, les résultats retournés ne sont pas les mêmes suivant les options de compilation. Le programme donne le résultat attendu avec les options suivantes :

1
2
g++ -std=c++14 -Wall -Wextra -pedantic -O2 Ex019.cpp
clang++ -std=c++14 -Wall -Wextra -pedantic Ex019.cpp

Mais pas avec les options suivantes (retourne 0) :

1
clang++ -std=c++14 -Wall -Wextra -pedantic -O2 Ex019.cpp

Mais je n’arrive vraiment pas à comprendre pourquoi. Voici le programme :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <iostream>
#include <chrono>
#include <algorithm>

using namespace std;
using  ms = chrono::milliseconds;
using get_time = chrono::steady_clock;


int main(){
    auto start = get_time::now();

    int dayInit {0};
    int dayCounter {1};
    int sundayCounter {0};

    for(int year {1901}; year < 2001; year++)
    {
        for(int month {1}; month <= 12; month++)
        {
            if(month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12)
            {
                dayInit = 31;
            }
            if(month == 4 || month == 6 || month == 9 || month == 11)
            {
                dayInit = 30;
            }
            if(month == 2)
            {
                if(year % 4 == 0)
                {
                    dayInit = 29;
                }

                if(year % 4 != 0)
                {
                    dayInit = 28;
                }
            }

            for(int day {1}; day <= dayInit; day++)
            {
                dayCounter++;
                if(day == 1 and dayCounter % 7 == 0)
                {
                    //  cout << day << "/" << month << "/" << year << endl; // SI CETTE LIGNE N'EST PAS COMMENTÉE, LE PROGRAMME MARCHE QUELQUE SOIT LES OPTIONS DE COMPILATION !!!
                    sundayCounter++;
                }

            }
        }
    }

    auto end = get_time::now();

    cout << sundayCounter << " " << dayCounter <<  endl;

    cout << "Elapsed time is :  " << chrono::duration_cast <ms>(end - start).count() << " ms " << endl;

}

`

De plus, si la ligne 47 (environ) n’est pas commentée, le programme marche quelque soit les options de compilation.

Avez-vous une idée du problème ?

Merci d’avance !

+0 -0

Je n’ai pas ce comportement.

g++ --version

1
2
3
4
g++ (GCC) 7.2.0
Copyright © 2017 Free Software Foundation, Inc.
Ce logiciel est un logiciel libre; voir les sources pour les conditions de copie.  Il n'y a
AUCUNE GARANTIE, pas même pour la COMMERCIALISATION ni L'ADÉQUATION À UNE TÂCHE PARTICULIÈRE.

clang++ --version

1
2
3
4
clang version 5.0.0 (tags/RELEASE_500/final)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /usr/bin

C’est curieux.

+1 -0

Effectivement, c’est curieux. Mais g++ et clang++ n’ont pas l’air d’être à jour :

1
2
3
Debian clang version 3.5.0-10 (tags/RELEASE_350/final) (based on LLVM 3.5.0)
Target: x86_64-pc-linux-gnu
Thread model: posix
1
2
3
4
g++ (Debian 4.9.2-10) 4.9.2
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

g++ était déjà installé (LMDE 2) et j’ai installé clang++ depuis les dépots. Il faudrait que je les réinstalle. J’ai pas le temps tout de suite, je le fais demain et je donne des nouvelles. Merci

+0 -0

J’ai rien essayé de plus. Tu veux dire quoi par réduire le code ? Parce que c’est bizarre qu’en rajoutant un cout ça marche…

EDIT : j’ai désinstallé clang++-3.5 et installé la 5.0. Et du coup, ça marche très bien. C’est mieux d’avoir des logiciels à jour… Ça aurait dû être la première chose à vérifier. Mais c’est quand même bizarre que ça marche sur wandbox

Merci à tous les deux !

+0 -0

Ce n’est pas étonnant qu’en rajoutant un cout ça marche. Lorsqu’un compilateur est face à une boucle relativement simple, il va essayer d’optimiser en vectorisant la boucle. L’idée étant d’utiliser les registres vectoriels qui contiennent 128 bits, soit 4 entiers 32 bits. Grossièrement, le compilateur va utiliser chacun des 4 emplacements d’entiers 32 bits pour 4 itérations de boucle différente, ce qui permet de faire 4 itérations en même temps.

Pour te montrer un exemple, la transformation suivante peux être faite:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Boucle d'origine
int sum = 0;
for (int i = 0; i < 1024; ++i) {
  ++sum;
}

// Boucle vectorisé
int sum = 0;
// vecteur de 4 entiers 32 bits
__m128i vec_sum;
// Met la valeur 0 dans chaque emplacement du registre (1 instruction CPU)
vec_sum = _mm_set1_epi32(0);
// Vecteur utilisé pour l'incrémentation
__m128i vec_one = _mm_set1_epi32(1);
for (int i = 0; i < 1024; i += 4) {
  // Ajoute vec_sum et vec_one et enregistre le résultat dans vec_sum (1 instruction CPU)
  vec_sum = _mm_add_epi32(vec_sum, vec_one);
}
// Prends les vecteurs {a0, a1, a2, a3} et {b0, b1, b2, b3} et retourne {a0+a1, a2+a3, b0+b1, b2+b3}.
// 1 instruction CPU
vec_sum = _mm_hadd_epi32(vec_sum, vec_sum);  
vec_sum = _mm_hadd_epi32(vec_sum, vec_sum);
// Retourne le premier entier 32 bit du registre
sum = _mm_cvtsi128_si32(vec_sum);

Seulement, il n’est pas possible de vectoriser un appel de fonction (ce que fait ton cout), donc introduire cette ligne va simplement empêcher le compilateur de faire cette optimisation.

Est-ce que tu peux donner le résultat correct et incorrect que tu obtiens?

Je ne suis pas sûr d’avoir compris. vec_one ne contient que des 1 ? Donc dans vec_sum, on a toujours : a0=a1=a2=a3 ? et au final, sum = (a0+a1) + (a2+a3) = 4a0 = 4(1024/4) = 1024. C’est bien ça qui se passe ?

Le bon résultat est 171 et l’incorrect 0.

+0 -0

En effet, vec_one ne contient que des 1. Il n’y a pas d’opération d’incrémentation, donc si tu veux incrémenter un vecteur, tu es obligé d’avoir un vecteur de 1 à ajouter. Le reste de ton raisonnement est bon.

Le résultat de 0 est clairement soit le résultat d’un undefined behaviour qu’on a tous raté (peu probable). Soit c’est le compilateur qui a fait une optimisation foireuse (peu probable aussi). Sans pouvoir regarder l’assembleur généré, ça va être difficile d’aller plus loin. Et même avec l’assembleur, si le problème est subtile, je doute pouvoir le trouver.

Je viens de m’amuser avec ça, et je pencherai pour une optimisation foireuse (bug ?) dans clang 3.5

Pour ceux qui veulent faire l’expérience chez eux, voici un bout de code (moche) minimal qui reproduit le problème :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>

int main(){
    srand(time(NULL));

    int dayInit {rand() % 31};
    int dayCounter {6};
    int sundayCounter {0};

    for(int day {1}; day <= dayInit; day++)
    {
        dayCounter++;
        if(day == 1 && (dayCounter % 7) == 0)
        {
            //std::cout << "test\n";
            sundayCounter++;
        }

    }

    std::cout << "END " << sundayCounter << " " << dayCounter <<  "\n";
}

Ici, sundayCounter ne vaudra 1 que si dayCounter est inférieur à 14 (si on décommente la ligne 15, ça affiche bien 1 dans tous les cas).

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte