Y a-t-il un avantage de performance d'une manière ou d'une autre? Est-ce spécifique au compilateur/VM? 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 rapidement pour une autre.
Troisièmement: une grande partie des mythes entourant les statiques par rapport aux non-statiques sont basés soit sur de très anciennes machines virtuelles Java (qui ne se situaient pas du tout près de l'optimisation que fait Hotspot), soit sur quelques anecdotes sur le C++ (dans lesquelles un appel dynamique utilise n plus d'accès à la mémoire 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 une référence qui montre comment les différents types d'appels (virtuels, non virtuels, statiques) se comparent les uns aux autres.
Je l'ai exécuté sur ideone , et voici ce que j'ai obtenu:
(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éthode virtuelle sont les plus lents, les appels de méthode non virtuelle sont plus rapides et les appels de méthode statique sont encore plus rapides.
Je ne m'attendais pas à ce que les différences soient si prononcées: les appels de méthode virtuelle ont été mesurés pour s'exécuter à moins de la moitié de la vitesse des appels de méthode non virtuelle , qui à leur tour ont été mesurés pour s'exécuter dans leur ensemble 15% plus lentement que les appels statiques. C'est ce que montrent ces mesures; les différences réelles doivent en fait être légèrement plus prononcées, car pour chaque appel de méthode virtuelle, non virtuelle et statique, mon code d'analyse comparative a un surcoût constant supplémentaire d'incrémenter une variable entière, de vérifier une variable booléenne et de boucler si ce n'est pas vrai.
Je suppose que les résultats varieront 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( String[] 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 convient de noter que cette différence de performances ne s'applique qu'au code qui ne fait rien d'autre que d'invoquer des méthodes sans paramètre. Tout autre code que vous avez entre les invocations diluera les différences, et cela inclut le passage de paramètres. En fait, la différence de 15% entre les appels statiques et non virtuels s'explique probablement en entier par le fait que le pointeur this
n'a pas besoin d'être passé à la méthode statique. Ainsi, il ne faudrait qu'une quantité assez petite de code faisant des choses triviales entre les appels pour que la différence entre les différents types d'appels soit diluée au point de n'avoir aucun impact net.
En outre, les appels de méthode virtuels 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 CPU.) Si, dans votre désir de les éliminer en les remplaçant par des appels non virtuels ou statiques, vous finissez par ajouter autant qu'un iota de code supplémentaire pour émuler leur fonctionnalité, alors votre surcharge nette résultante est liée pas moins, mais plus. Très probablement, beaucoup, beaucoup, insondablement beaucoup plus.
Eh bien, les appels statiques ne peuvent pas être remplacés (sont donc toujours candidats à l'inline), et ne nécessitent aucun contrôle de nullité. HotSpot fait un tas d'optimisations intéressantes pour les méthodes d'instance qui pourraient bien annuler ces avantages, mais ce sont possibles raisons pour lesquelles un appel statique peut être plus rapide.
Cependant, cela ne devrait pas affecter votre conception - coder de la manière la plus lisible et la plus naturelle - et ne vous soucier de ce type de micro-optimisation que si vous en avez la cause (ce que vous avez presque jamais).
Il est spécifique au compilateur/VM.
Par conséquent, cela ne vaut probablement pas la peine d'être dérangé à moins que vous n'ayez identifié cela comme un problème de performance vraiment critique dans votre application. L'optimisation prématurée est la racine de tout mal etc ...
Cependant, j'ai vu cette optimisation donner une augmentation substantielle des performances dans la situation suivante:
Si ce qui précède s'applique à vous, cela peut valoir la peine d'être testé.
Il y a aussi une autre bonne raison (et potentiellement encore plus importante!) D'utiliser une méthode statique - si la méthode a réellement une sémantique statique (c'est-à-dire qu'elle n'est pas connectée logiquement à une instance donnée de la classe), alors il est logique de la rendre statique pour refléter ce fait. Expérimenté Java remarqueront alors le modificateur statique et penseront immédiatement "aha! Cette méthode est statique donc elle n'a pas besoin d'une instance et ne manipule probablement pas l'état spécifique de l'instance". ont communiqué efficacement la nature statique de la méthode ...
Comme les affiches précédentes l'ont dit: Cela semble être une optimisation prématurée.
Cependant, il y a une différence (en partie du fait que les invocations non statiques nécessitent une poussée supplémentaire d'un objet appelé sur la pile d'opérandes):
Étant donné que les méthodes statiques ne peuvent pas être remplacées, il n'y aura pas de recherche virtuelle dans le runtime 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 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 se fait via INVOKESTATIC
.
Il est incroyablement improbable qu'une différence dans les performances des appels statiques et non statiques fasse une différence dans votre application. N'oubliez pas que "l'optimisation prématurée est la racine de tout mal".
Pour décider si une méthode doit être statique, l'aspect de la performance ne doit pas être pertinent. Si vous avez un problème de performances, rendre statique de nombreuses méthodes ne vous fera pas gagner du temps. Cela dit, les méthodes statiques sont presque certainement pas plus lent que toute méthode d'instance, dans la plupart des cas légèrement plus rapide:
1.) Les méthodes statiques ne sont pas polymorphes, donc la JVM a moins de décisions à prendre pour trouver le code réel à exécuter. Il s'agit d'un point discutable dans l'ère de Hotspot, car Hotspot optimisera les appels de méthode d'instance qui n'ont qu'un seul site d'implémentation, ils effectueront donc 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 cadre de pile un emplacement plus petit que celui d'une méthode d'instance avec la même signature et le même corps ("ceci" est placé dans l'emplacement 0 des variables locales au niveau du bytecode, tandis que pour les méthodes statiques, l'emplacement 0 est utilisé pour le premier paramètre de la méthode).
7 ans plus tard ...
Je n'ai pas une grande confiance dans les résultats trouvés par Mike Nakis car ils ne résolvent pas certains problèmes courants liés aux optimisations Hotspot. J'ai instrumenté des tests de performance en utilisant JMH et j'ai trouvé que les frais généraux d'une méthode d'instance étaient d'environ 0,75% sur ma machine par rapport à un appel statique. Compte tenu de cette faible surcharge, je pense que sauf dans les 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 sommaires 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 consulter 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 manquées/ignorées et ces résultats sont susceptibles de varier selon la version JVM et le 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(i.a, i.b);
blackhole.consume(sum);
}
@Benchmark
public void testInstanceMethod(Input i, Blackhole blackhole) {
int sum = impl.sum(i.a, i.b);
blackhole.consume(sum);
}
}
Il pourrait y avoir une différence, et cela pourrait aller dans les deux sens pour n'importe quel morceau de code particulier, et cela pourrait changer même avec une version mineure de la JVM.
Cela fait certainement partie de les 97% de petites efficacités que vous devez oublier .
En théorie, moins cher.
L'initialisation statique va être effectuée même si vous créez une instance de l'objet, tandis que les méthodes statiques ne feront aucune initialisation normalement effectuée dans un constructeur.
Cependant, je n'ai pas testé cela.
Comme le note Jon, les méthodes statiques ne peuvent pas être remplacées, donc simplement appel une méthode statique peut être - sur un runtime suffisamment naïf Java - plus rapide que - appelant une méthode d'instance.
Mais alors, même en supposant que vous vous souciez de gâcher votre conception pour économiser quelques nanosecondes, cela soulève une autre question: aurez-vous besoin d'une méthode qui vous remplace? Si vous changez votre code pour transformer une méthode d'instance en une méthode statique pour économiser une nanoseconde ici et là, puis faites demi-tour et implémentez votre propre répartiteur en plus de cela, le vôtre sera presque certainement moins efficace que celui construit dans votre Java runtime déjà.