@ntoskrnl En tant que personne travaillant avec les internes de la JVM, je voudrais appuyer votre opinion selon laquelle "les intrinsèques ne se comportent pas nécessairement de la même manière que les méthodes StrictMath". Pour le savoir (ou le prouver), nous pouvons simplement écrire un test simple.
Prenez Math.pow
par exemple, en examinant le code Java de java.lang.Math.pow(double a, double b), nous verrons :
public static double pow(double a, double b) {
return StrictMath.pow(a, b); // default impl. delegates to StrictMath
}
Mais la JVM est libre de l'implémenter avec des intrinsèques ou des appels au moment de l'exécution, ainsi le résultat retourné peut être différent de ce que nous attendons de StrictMath.pow
.
Et le code suivant montre cet appel Math.pow()
contre StrictMath.pow()
//Strict.java, testing StrictMath.pow against Math.pow
import java.util.Random;
public class Strict {
static double testIt(double x, double y) {
return Math.pow(x, y);
}
public static void main(String[] args) throws Exception{
final double[] vs = new double[100];
final double[] xs = new double[100];
final double[] ys = new double[100];
final Random random = new Random();
// compute StrictMath.pow results;
for (int i = 0; i<100; i++) {
xs[i] = random.nextDouble();
ys[i] = random.nextDouble();
vs[i] = StrictMath.pow(xs[i], ys[i]);
}
boolean printed_compiled = false;
boolean ever_diff = false;
long len = 1000000;
long start;
long elapsed;
while (true) {
start = System.currentTimeMillis();
double blackhole = 0;
for (int i = 0; i < len; i++) {
int idx = i % 100;
double res = testIt(xs[idx], ys[idx]);
if (i >= 0 && i<100) {
//presumably interpreted
if (vs[idx] != res && (!Double.isNaN(res) || !Double.isNaN(vs[idx]))) {
System.out.println(idx + ":\tInterpreted:" + xs[idx] + "^" + ys[idx] + "=" + res);
System.out.println(idx + ":\tStrict pow : " + xs[idx] + "^" + ys[idx] + "=" + vs[idx] + "\n");
}
}
if (i >= 250000 && i<250100 && !printed_compiled) {
//presumably compiled at this time
if (vs[idx] != res && (!Double.isNaN(res) || !Double.isNaN(vs[idx]))) {
System.out.println(idx + ":\tcompiled :" + xs[idx] + "^" + ys[idx] + "=" + res);
System.out.println(idx + ":\tStrict pow :" + xs[idx] + "^" + ys[idx] + "=" + vs[idx] + "\n");
ever_diff = true;
}
}
}
elapsed = System.currentTimeMillis() - start;
System.out.println(elapsed + " ms ");
if (!printed_compiled && ever_diff) {
printed_compiled = true;
return;
}
}
}
}
J'ai effectué ce test avec OpenJDK 8u5-b31 et j'ai obtenu le résultat ci-dessous :
10: Interpreted:0.1845936372497491^0.01608930867480518=0.9731817015518033
10: Strict pow : 0.1845936372497491^0.01608930867480518=0.9731817015518032
41: Interpreted:0.7281259501809544^0.9414406865385655=0.7417808233050295
41: Strict pow : 0.7281259501809544^0.9414406865385655=0.7417808233050294
49: Interpreted:0.0727813262968815^0.09866028976654662=0.7721942440239148
49: Strict pow : 0.0727813262968815^0.09866028976654662=0.7721942440239149
70: Interpreted:0.6574309575966407^0.759887845481148=0.7270872740201638
70: Strict pow : 0.6574309575966407^0.759887845481148=0.7270872740201637
82: Interpreted:0.08662340816125613^0.4216580281197062=0.3564883826345057
82: Strict pow : 0.08662340816125613^0.4216580281197062=0.3564883826345058
92: Interpreted:0.20224488115245098^0.7158182878844233=0.31851834311978916
92: Strict pow : 0.20224488115245098^0.7158182878844233=0.3185183431197892
10: compiled :0.1845936372497491^0.01608930867480518=0.9731817015518033
10: Strict pow :0.1845936372497491^0.01608930867480518=0.9731817015518032
41: compiled :0.7281259501809544^0.9414406865385655=0.7417808233050295
41: Strict pow :0.7281259501809544^0.9414406865385655=0.7417808233050294
49: compiled :0.0727813262968815^0.09866028976654662=0.7721942440239148
49: Strict pow :0.0727813262968815^0.09866028976654662=0.7721942440239149
70: compiled :0.6574309575966407^0.759887845481148=0.7270872740201638
70: Strict pow :0.6574309575966407^0.759887845481148=0.7270872740201637
82: compiled :0.08662340816125613^0.4216580281197062=0.3564883826345057
82: Strict pow :0.08662340816125613^0.4216580281197062=0.3564883826345058
92: compiled :0.20224488115245098^0.7158182878844233=0.31851834311978916
92: Strict pow :0.20224488115245098^0.7158182878844233=0.3185183431197892
290 ms
Veuillez noter que Random
est utilisé pour générer les valeurs x et y, donc votre kilométrage variera d'une exécution à l'autre. Mais la bonne nouvelle est qu'au moins les résultats de la version compilée de Math.pow
correspondent à celles de la version interprétée de Math.pow
. (Hors sujet : même cette cohérence n'a été mise en application qu'en 2012 avec une série de corrections de bugs du côté d'OpenJDK).
La raison ?
Eh bien, c'est parce que OpenJDK utilise des fonctions intrinsèques et des fonctions d'exécution pour mettre en oeuvre Math.pow
(et autres fonctions mathématiques), au lieu de simplement exécuter le code Java. L'objectif principal est de tirer parti des instructions x87 afin d'augmenter les performances du calcul. En conséquence, StrictMath.pow
n'est jamais appelé depuis Math.pow
au moment de l'exécution (pour la version d'OpenJDK que nous venons d'utiliser, pour être précis).
Et cet arrangement est tout à fait légitime selon la Javadoc de Math
(également cité par @coobird ci-dessus) :
La classe Math contient des méthodes permettant d'effectuer des opérations numériques de base, telles que les fonctions exponentielles, logarithmiques, racine carrée et trigonométriques élémentaires.
Contrairement à certaines des méthodes numériques de la classe StrictMath, toutes les implémentations des fonctions équivalentes de la classe Math ne sont pas définies pour retourner les mêmes résultats bit à bit. Cet assouplissement permet des implémentations plus performantes lorsque la reproductibilité stricte n'est pas requise.
Par défaut, de nombreuses méthodes mathématiques appellent simplement la méthode équivalente dans StrictMath pour leur mise en œuvre. Les générateurs de code sont encouragés à utiliser des bibliothèques natives spécifiques à la plate-forme ou des instructions de microprocesseur, lorsqu'elles sont disponibles, pour fournir des implémentations plus performantes des méthodes Math. Ces implémentations plus performantes doivent néanmoins se conformer à la spécification de Math.
Et la conclusion ? Eh bien, pour les langages avec génération de code dynamique tels que Java, assurez-vous que ce que vous voyez du code "statique" correspond à ce qui est exécuté au moment de l'exécution. Vos yeux peuvent parfois vous induire en erreur.
6 votes
La réponse à cette question se trouve entièrement dans la Javadoc.
34 votes
@EJP - Je crois que, sur le SO, RTFM n'est jamais une bonne réponse.