Les appels statiques Java sont-ils plus ou moins coûteux que les appels non statiques?

Y a-t-il un avantage de performance d’une manière ou d’une autre? Est-ce que le compilateur / VM est spécifique? J’utilise Hotspot.

Premièrement, vous ne devriez pas faire le choix entre statique et non-statique sur la base des performances.

Deuxièmement: en pratique, cela ne fera aucune différence. Hotspot peut choisir d’optimiser de manière à rendre les appels statiques plus rapides pour une méthode, les appels non statiques plus rapides pour une autre.

Troisièmement: une grande partie des mythes entourant la statique par rapport à la non-statique sont basés soit sur d’anciennes machines virtuelles (qui ne se rapprochent pas de l’optimisation de Hotspot), soit sur des anecdotes sur C ++ (dans lesquelles un appel dynamic utilise un access mémoire supplémentaire) qu’un appel statique).

Quatre ans plus tard…

D’accord, dans l’espoir de régler cette question une fois pour toutes, j’ai écrit un benchmark qui montre comment les différents types d’appels (virtuels, non virtuels, statiques) se comparent.

Je l’ai couru sur ideone , et c’est ce que j’ai eu:

(Un plus grand nombre d’itérations est préférable.)

Success time: 3.12 memory: 320576 signal:0 Name | Iterations VirtualTest | 128009996 NonVirtualTest | 301765679 StaticTest | 352298601 Done. 

Comme prévu, les appels de méthodes virtuelles sont les plus lents, les appels de méthodes non virtuels sont plus rapides et les appels de méthodes statiques sont encore plus rapides.

Je ne m’attendais pas à ce que les différences soient aussi prononcées: les appels de méthodes virtuelles étaient moins rapides que la moitié des appels de méthodes non virtuels, qui à leur tour étaient mesurés 15% plus lentement que les appels statiques. C’est ce que montrent ces mesures; les différences réelles doivent en effet être légèrement plus prononcées, car pour chaque appel de méthode virtuel, non virtuel et statique, mon code de comparaison a un surcoût constant supplémentaire consistant à incrémenter une variable entière, à vérifier une variable booléenne et à

Je suppose que les résultats varient d’un processeur à l’autre et de JVM à JVM, alors essayez-le et voyez ce que vous obtenez:

 import java.io.*; class StaticVsInstanceBenchmark { public static void main( Ssortingng[] args ) throws Exception { StaticVsInstanceBenchmark program = new StaticVsInstanceBenchmark(); program.run(); } static final int DURATION = 1000; public void run() throws Exception { doBenchmark( new VirtualTest( new ClassWithVirtualMethod() ), new NonVirtualTest( new ClassWithNonVirtualMethod() ), new StaticTest() ); } void doBenchmark( Test... tests ) throws Exception { System.out.println( " Name | Iterations" ); doBenchmark2( devNull, 1, tests ); //warmup doBenchmark2( System.out, DURATION, tests ); System.out.println( "Done." ); } void doBenchmark2( PrintStream printStream, int duration, Test[] tests ) throws Exception { for( Test test : tests ) { long iterations = runTest( duration, test ); printStream.printf( "%15s | %10d\n", test.getClass().getSimpleName(), iterations ); } } long runTest( int duration, Test test ) throws Exception { test.terminate = false; test.count = 0; Thread thread = new Thread( test ); thread.start(); Thread.sleep( duration ); test.terminate = true; thread.join(); return test.count; } static abstract class Test implements Runnable { boolean terminate = false; long count = 0; } static class ClassWithStaticStuff { static int staticDummy; static void staticMethod() { staticDummy++; } } static class StaticTest extends Test { @Override public void run() { for( count = 0; !terminate; count++ ) { ClassWithStaticStuff.staticMethod(); } } } static class ClassWithVirtualMethod implements Runnable { int instanceDummy; @Override public void run() { instanceDummy++; } } static class VirtualTest extends Test { final Runnable runnable; VirtualTest( Runnable runnable ) { this.runnable = runnable; } @Override public void run() { for( count = 0; !terminate; count++ ) { runnable.run(); } } } static class ClassWithNonVirtualMethod { int instanceDummy; final void nonVirtualMethod() { instanceDummy++; } } static class NonVirtualTest extends Test { final ClassWithNonVirtualMethod objectWithNonVirtualMethod; NonVirtualTest( ClassWithNonVirtualMethod objectWithNonVirtualMethod ) { this.objectWithNonVirtualMethod = objectWithNonVirtualMethod; } @Override public void run() { for( count = 0; !terminate; count++ ) { objectWithNonVirtualMethod.nonVirtualMethod(); } } } static final PrintStream devNull = new PrintStream( new OutputStream() { public void write(int b) {} } ); } 

Il est à noter que cette différence de performance ne s’applique qu’au code qui ne fait rien d’autre que d’invoquer des méthodes sans parameters. Quel que soit le code que vous avez entre les invocations, les différences seront diluées, et cela inclut le passage des parameters. En fait, la différence de 15% entre les appels statiques et non virtuels s’explique probablement par le fait que le pointeur this n’a pas besoin d’être transmis à la méthode statique. Ainsi, il suffirait d’une quantité de code assez réduite pour effectuer des opérations sortingviales entre les appels afin que la différence entre les différents types d’appels soit diluée au sharepoint n’avoir aucun impact net.

De plus, les appels de méthodes virtuelles existent pour une raison; ils ont un but à servir, et ils sont mis en œuvre en utilisant les moyens les plus efficaces fournis par le matériel sous-jacent. (Le jeu d’instructions du processeur.) Si, dans votre désir de les éliminer en les remplaçant par des appels non virtuels ou statiques, vous deviez append autant de code supplémentaire pour émuler leurs fonctionnalités, votre surcharge nette résultante est liée être pas moins, mais plus. Assez probablement, beaucoup, beaucoup, beaucoup plus.

Eh bien, les appels statiques ne peuvent pas être remplacés (ils sont donc toujours des candidats à l’inclusion), et ne nécessitent aucune vérification de nullité. HotSpot fait un tas d’optimisations intéressantes pour les méthodes d’instance, ce qui peut nier ces avantages, mais c’est peut- être une des raisons pour lesquelles un appel statique peut être plus rapide.

Cependant, cela ne devrait pas affecter votre code de conception de la manière la plus lisible et naturelle – et ne vous inquiétez que de ce type de micro-optimisation si vous avez des raisons justifiées (ce que vous ne ferez presque jamais ).

C’est spécifique au compilateur / VM.

  • En théorie , un appel statique peut être légèrement plus efficace car il n’a pas besoin de faire une recherche de fonction virtuelle, et il peut également éviter la surcharge du paramètre “this” caché.
  • En pratique , de nombreux compilateurs vont optimiser cela de toute façon.

Par conséquent, cela ne vaut probablement pas la peine de le faire, à moins que vous ayez identifié ce problème comme un problème de performance critique dans votre application. L’optimisation prématurée est la racine de tout mal, etc.

Cependant, j’ai vu cette optimisation augmenter considérablement les performances dans les situations suivantes:

  • Méthode effectuant un calcul mathématique très simple sans access mémoire
  • Méthode invoquée des millions de fois par seconde dans une boucle interne serrée
  • Application liée au processeur où chaque bit de performance est important

Si ce qui précède s’applique à vous, cela peut valoir la peine d’être testé.

Il y a aussi une autre bonne (et potentiellement encore plus importante!) Raison d’utiliser une méthode statique – si la méthode a réellement une sémantique statique (c’est-à-dire qu’elle n’est logiquement pas connectée à une instance donnée de la classe), il est logique de la rendre statique pour refléter ce fait. Les programmeurs Java expérimentés remarqueront alors le modificateur statique et penseront immédiatement “aha! Cette méthode est statique, elle n’a donc pas besoin d’instance et ne manipule probablement pas l’état spécifique à l’instance”. Donc, vous aurez communiqué la nature statique de la méthode efficacement ….

Comme les affiches précédentes l’ont dit: cela semble être une optimisation prématurée.

Cependant, il y a une différence (une partie du fait que les invocations non statiques nécessitent une poussée supplémentaire d’un object appelé sur la stack d’opérandes):

Comme les méthodes statiques ne peuvent pas être remplacées, il n’y aura pas de recherche virtuelle dans l’exécution pour un appel de méthode statique. Cela peut entraîner une différence observable dans certaines circonstances.

La différence au niveau du code d’octet est qu’un appel de méthode non statique est effectué via INVOKEVIRTUAL , INVOKEINTERFACE ou INVOKESPECIAL alors qu’un appel de méthode statique est effectué via INVOKESTATIC .

Il est incroyablement improbable que toute différence dans les performances des appels statiques et non statiques fasse une différence dans votre application. Rappelez-vous que “l’optimisation prématurée est la racine de tout mal”.

Pour décider si une méthode doit être statique, l’aspect des performances ne doit pas être pertinent. Si vous rencontrez un problème de performances, le fait de rendre statiques de nombreuses méthodes ne vous permettra pas de gagner du temps. Cela dit, les méthodes statiques ne sont certainement pas plus lentes que toutes les méthodes d’instance, dans la plupart des cas, légèrement plus rapides :

1.) Les méthodes statiques ne sont pas polymorphes, donc la JVM a moins de décisions à prendre pour trouver le code à exécuter. Ceci est un point discutable dans l’ère de Hotspot, puisque Hotspot optimisera les appels de méthode d’instance qui ont seulement un site d’implémentation, ainsi ils effectueront la même chose.

2.) Une autre différence subtile est que les méthodes statiques n’ont évidemment pas de “cette” référence. Il en résulte un frame de stack inférieur à un slot d’instance avec la même signature et le même corps (“this” est placé dans le slot 0 des variables locales au niveau bytecode, tandis que pour les méthodes statiques, slot 0 est utilisé pour le premier paramètre de la méthode).

Il pourrait y avoir une différence, et cela pourrait aller dans un sens ou dans l’autre pour un morceau de code particulier, et cela pourrait changer même avec une version mineure de la JVM.

Cela fait sans aucun doute partie des 97% de petites économies que vous devriez oublier .

7 ans plus tard …

Je n’ai pas une grande confiance dans les résultats que Mike Nakis a trouvés, car ils ne traitent pas certains problèmes courants liés aux optimisations de Hotspot. J’ai instrumenté des benchmarks en utilisant JMH et j’ai trouvé que la surcharge d’une méthode d’instance était d’environ 0,75% sur ma machine par rapport à un appel statique. Compte tenu de cette faible surcharge, je pense que, sauf dans le cas des opérations les plus sensibles à la latence, ce n’est sans doute pas la plus grande préoccupation dans la conception d’une application. Les résultats résumés de mon benchmark JMH sont les suivants:

 java -jar target/benchmark.jar # -- snip -- Benchmark Mode Cnt Score Error Units MyBenchmark.testInstanceMethod thrpt 200 414036562.933 ± 2198178.163 ops/s MyBenchmark.testStaticMethod thrpt 200 417194553.496 ± 1055872.594 ops/s 

Vous pouvez regarder le code ici sur Github;

https://github.com/nfisher/svsi

Le benchmark lui-même est assez simple mais vise à minimiser l’élimination du code mort et le pliage constant. Il y a peut-être d’autres optimisations que j’ai omises / ignorées et ces résultats sont susceptibles de varier en fonction de la version de JVM et du système d’exploitation.

 package ca.junctionbox.svsi; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.infra.Blackhole; class InstanceSum { public int sum(final int a, final int b) { return a + b; } } class StaticSum { public static int sum(final int a, final int b) { return a + b; } } public class MyBenchmark { private static final InstanceSum impl = new InstanceSum(); @State(Scope.Thread) public static class Input { public int a = 1; public int b = 2; } @Benchmark public void testStaticMethod(Input i, Blackhole blackhole) { int sum = StaticSum.sum(ia, ib); blackhole.consume(sum); } @Benchmark public void testInstanceMethod(Input i, Blackhole blackhole) { int sum = impl.sum(ia, ib); blackhole.consume(sum); } } 

En théorie, moins cher.

L’initialisation statique sera effectuée même si vous créez une instance de l’object, alors que les méthodes statiques ne feront aucune initialisation normalement effectuée dans un constructeur.

Cependant, je n’ai pas testé cela.

Comme le fait remarquer Jon, les méthodes statiques ne peuvent pas être remplacées. Ainsi, invoquer une méthode statique peut être – sur un runtime Java suffisamment naïf – plus rapide que d’ appeler une méthode d’instance.

Mais alors, même en supposant que vous vous sentiez à la hauteur de votre conception pour économiser quelques nanosecondes, cela ne fait que soulever une autre question: aurez-vous besoin de la méthode vous-même? Si vous modifiez votre code afin de transformer une méthode d’instance en une méthode statique pour économiser une nanoseconde ici et là, puis inverser la tendance et implémenter votre propre répartiteur, votre système sera certainement moins efficace que celui construit. dans votre runtime Java déjà.

Je voudrais append aux autres excellentes réponses ici que cela dépend aussi de votre stream, par exemple:

 Public class MyDao { private Ssortingng sql = "select * from MY_ITEM"; public List getAllItems() { springJdbcTemplate.query(sql, new MyRowMapper()); }; }; 

Faites attention à créer un nouvel object MyRowMapper pour chaque appel.
Au lieu de cela, je suggère d’utiliser ici un champ statique.

 Public class MyDao { private static RowMapper myRowMapper = new MyRowMapper(); private Ssortingng sql = "select * from MY_ITEM"; public List getAllItems() { springJdbcTemplate.query(sql, myRowMapper); }; };