2009-02-13 6 views
8

Je ne connais pas vraiment les internes du compilateur et les optimisations JIT, mais j'essaie généralement d'utiliser le "bon sens" pour deviner ce qui pourrait être optimisé et ce qui ne le pourrait pas. Donc là j'écrivais une méthode de test unitaire simple aujourd'hui:Comment écrire (tester) du code qui ne sera pas optimisé par le compilateur/JIT?

@Test // [Test] in C# 
public void testDefaultConstructor() { 
    new MyObject(); 
} 

Cette méthode est réellement tout ce dont j'ai besoin. Il vérifie que le constructeur par défaut existe et s'exécute sans exceptions.

Mais j'ai commencé à réfléchir à l'effet des optimisations du compilateur/JIT. Le compilateur/JIT pourrait-il optimiser cette méthode en éliminant complètement l'instruction new MyObject();? Bien sûr, il faudrait déterminer que le graphe d'appel n'a pas d'effets secondaires sur les autres objets, ce qui est le cas typique d'un constructeur normal qui initialise simplement l'état interne de l'objet.

Je présume que seul le JIT serait autorisé à effectuer une telle optimisation. Cela signifie probablement que ce n'est pas quelque chose dont je devrais m'inquiéter, car la méthode de test n'est effectuée qu'une seule fois. Mes hypothèses sont-elles correctes?

Néanmoins, j'essaie de penser au sujet général. Quand j'ai pensé à comment empêcher cette méthode d'être optimisée, j'ai pensé que je pouvais assertTrue(new MyObject().toString() != null), mais cela dépend beaucoup de l'implémentation réelle de la méthode toString(), et même alors, le JIT peut déterminer que la méthode toString() retourne toujours un non-null chaîne (par exemple, si en réalité Object.toString() est appelée), et ainsi optimiser la branche entière. Donc, cette façon ne fonctionnerait pas.

Je sais que dans C# je peux utiliser [MethodImpl(MethodImplOptions.NoOptimization)], mais ce n'est pas ce que je cherche réellement. J'espère trouver une façon (indépendante de la langue) de m'assurer que certaines parties spécifiques de mon code fonctionneront réellement comme je le souhaite, sans que le JIT n'interfère dans ce processus.

De plus, y a-t-il des cas d'optimisation typiques que je devrais connaître lors de la création de mes tests unitaires?

Merci beaucoup!

Répondre

4

Ne vous inquiétez pas pour le reste. Il n'est pas permis d'optimiser tout ce qui peut faire la différence pour votre système (sauf pour la vitesse). Si vous créez un objet, le code est appelé, la mémoire est allouée, il doit fonctionner.

Si vous l'aviez protégé par un if (faux), où le faux est une finale, il pourrait être optimisé complètement du système, puis il pourrait détecter que la méthode ne fait rien et optimiser la TI (en théorie).

Edit: par ailleurs, il peut aussi être assez intelligent pour déterminer que cette méthode:

newIfTrue(boolean b) { 
    if(b) 
     new ThisClass(); 
} 

fera toujours rien si b est faux, et finalement comprendre que à un moment donné dans votre code B est toujours faux et compile complètement cette routine à partir de ce code.

C'est là que le JIT peut faire des choses qui sont pratiquement impossibles dans n'importe quel langage non-géré.

+0

Merci. Je me demande pourquoi le JIT se comporte de cette façon? Si une allocation d'objet est inutile (comme cela peut être déterminé par l'analyse statique dans certains cas), pourquoi le JIT ne l'optimiserait-il pas? –

+0

Je peux maintenant penser à un cas de coin, mais je pense que c'est assez rare.Si l'allocation d'objet a été faite par exemple pour s'assurer que suffisamment de mémoire est disponible pour un ou plusieurs autres objets (et même en s'assurant qu'il n'y aura pas de pagination), l'optimisation invaliderait l'hypothèse. –

+0

Il est nécessaire de laisser la machine virtuelle Java (JVM) dans un état cohérent avec l'exécution du code de programme dans la JVM conformément au modèle de mémoire Java. Il n'est pas nécessaire d'exécuter réellement un code spécifique ou d'allouer de la mémoire dans le cas où le JIT peut prouver que le code n'a aucun effet sur l'état de programme observable. –

5

Je pense que si vous craignez d'être optimisé, vous risquez de faire un peu de test.

Dans un langage statique, j'ai tendance à considérer le compilateur comme un test. S'il passe la compilation, cela signifie que certaines choses sont là (comme les méthodes). Si vous n'avez pas d'autre test qui exerce votre constructeur par défaut (ce qui prouvera qu'il ne lancera pas d'exceptions), vous voudrez peut-être réfléchir à la raison pour laquelle vous écrivez ce constructeur par défaut (YAGNI et tout ça). Je sais qu'il y a des gens qui ne sont pas d'accord avec moi, mais j'ai l'impression que ce genre de chose est juste quelque chose qui va gonfler votre nombre de tests sans raison valable, même en regardant à travers les lunettes TDD.

+0

Oui, test de surpuissance ftl. – MichaelGG

+0

Je suis d'accord avec vous. Quand j'y ai réfléchi (après avoir lu votre réponse), il doit y avoir d'autres tests qui utilisent le constructeur par défaut si ce test est plus important que d'être un test "compilé". Merci! –

+0

Dans un langage comme Java, les environnements de compilation et d'exécution peuvent varier considérablement. Un test comme celui-ci pourrait avoir un sens si la classe en cours de construction est située séparément du code de test. Il serait particulièrement intéressant qu'un chargeur de classes personnalisées charge la classe à la volée à partir d'une forme de stockage dynamique - ce code garantirait que la recherche fonctionne réellement. –

0

Pourquoi est-ce important? Si le compilateur/JIT peut déterminer de manière statique qu'aucune assertion ne va être touchée (ce qui pourrait provoquer des effets secondaires), alors tout va bien.

+0

Il doit également vérifier que le constructeur existe, n'affecte pas l'état du programme statique et ne peut pas déclencher une exception implicitement requise par la machine virtuelle Java, par exemple 'NullPointerException'. –

2

Pensez-y de cette façon:

laisse supposer que le compilateur peut déterminer que le graphe d'appel n'a pas d'effets secondaires (je ne pense pas qu'il soit possible, je me rappelle vaguement quelque chose P = NP de mes cours de CS). Il optimisera toute méthode qui n'a pas d'effets secondaires. Puisque la plupart des tests n'ont pas et ne devraient avoir aucun effet secondaire, le compilateur peut les optimiser tous.

+0

Bonne idée! Je n'y ai pas pensé. :) –

1

Il semble que dans C# je pouvais faire ceci:

[Test] 
public void testDefaultConstructor() { 
    GC.KeepAlive(new MyObject()); 
} 

AFAIU, la méthode GC.KeepAlive ne sera pas inline par le JIT, de sorte que le code sera garanti pour fonctionner comme prévu. Cependant, je ne connais pas de construction similaire en Java.

+1

-1: Trompeur. Ce n'est pas le cas où 'GC.KeepAlive' est requis (ou offre même un avantage quelconque). –

0

Chaque E/S est un effet secondaire, de sorte que vous pouvez simplement mettre

Object obj = new MyObject(); 
System.out.println(obj.toString()); 

et vous êtes bien.

+0

Oui, c'est certainement une façon de le faire. Mais les tests unitaires ne devraient généralement pas avoir d'instructions de sortie. –

+0

Je pense que tant que vous ne comptez pas sur les E/S pour déterminer si le test a réussi ou échoué, tout va bien. –

+1

-1: Trompeur. Il va bien sans les E/S. Cette réponse donne l'impression que les tests unitaires de cette forme ont besoin des E/S pour garantir l'exactitude. –

2

Le JIT est uniquement autorisé à effectuer des opérations qui n'affectent pas la sémantique garantie de la langue. Théoriquement, il pourrait supprimer l'allocation et appeler le constructeur MyObjectsi il peut garantir que l'appel n'a pas d'effets secondaires et ne peut jamais lever une exception (sans compter OutOfMemoryError). En d'autres termes, si le JIT optimise l'appel hors de votre test, alors votre test aurait passé de toute façon.

PS: Notez que cela vaut parce que vous faites fonctionnalité test par opposition à la performance tests. Dans les tests de performance, il est important de s'assurer que le JIT n'optimise pas l'opération que vous mesurez, sinon vos résultats deviennent inutiles.

+0

Superbe vue sur le sujet. Un ajout: Lorsque vous utilisez un test de performance pour la fonctionnalité (par exemple, essayer de comprendre la taille du cache de l'UC), nous devons ajouter explicitement '[MethodImpl (MethodImplOptions.NoOptimization)]' pour éviter que le JIT ne fasse des tours. –

Questions connexes