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.
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.
Réponses:
Premièrement: vous ne devriez pas faire le choix entre statique et non statique en fonction des performances.
Deuxièmement: en pratique, cela ne fera aucune différence. Hotspot peut choisir d'optimiser de manière à accélérer les appels statiques pour une méthode et les appels non statiques plus rapidement pour une autre.
Troisièmement: une grande partie du mythe entourant le statique par rapport au non-statique est basée soit sur de très anciennes machines virtuelles Java (qui ne se rapprochent pas de l'optimisation que fait Hotspot), soit sur des anecdotes mémorisées sur C ++ (dans lequel un appel dynamique utilise un autre accès 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 un benchmark qui montre comment les différents types d'appels (virtuels, non virtuels, statiques) se comparent les uns aux autres.
Je l'ai couru 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éthodes virtuelles sont les plus lents, les appels de méthodes non virtuelles sont plus rapides et les appels de méthodes statiques sont encore plus rapides.
Ce à quoi je ne m'attendais pas, c'était que les différences soient si prononcées: les appels de méthodes virtuelles ont été mesurés pour s'exécuter à moins de la moitié de la vitesse des appels de méthodes non virtuelles, qui à leur tour ont été mesurés pour s'exécuter 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 une surcharge constante supplémentaire consistant à incrémenter une variable entière, à vérifier une variable booléenne et à 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 l'appel de méthodes sans paramètre. Tout autre code que vous avez entre les appels 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 entièrement par le fait que lethis
pointeur n'a pas à être passé à la méthode statique. Donc, il ne faudrait qu'une assez petite quantité de code pour faire 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 virtuelle 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 finissez par devoir ajouter jusqu'à un iota de code supplémentaire pour émuler leur fonctionnalité, alors la surcharge nette qui en résulte est liée ne pas être moins, mais plus. Très probablement, beaucoup, beaucoup, insondable beaucoup, plus.
VirtualTest | 488846733 -- NonVirtualTest | 480530022 -- StaticTest | 484353198
sur mon installation OpenJDK. FTR: C'est même vrai si je supprime le final
modificateur. Btw. J'ai dû faire le terminate
terrain volatile
, sinon le test ne s'est pas terminé.
VirtualTest | 12451872 -- NonVirtualTest | 12089542 -- StaticTest | 8181170
. Non seulement OpenJDK sur mon ordinateur portable parvient à effectuer 40 fois plus d'itérations, mais le test statique a toujours un débit inférieur d'environ 30%. Cela pourrait être un phénomène spécifique à l'ART, car j'obtiens un résultat attendu sur une tablette Android 4.4:VirtualTest | 138183740 -- NonVirtualTest | 142268636 -- StaticTest | 161388933
Eh bien, les appels statiques ne peuvent pas être remplacés (ils sont donc toujours candidats à l'inlining) et ne nécessitent aucune vérification de nullité. HotSpot fait un tas d'optimisations intéressantes, par exemple des méthodes qui peuvent bien annuler ces avantages, mais ce sont des raisons possibles pour lesquelles un appel statique peut être plus rapide.
Cependant, cela ne devrait pas affecter votre conception - code de la manière la plus lisible et la plus naturelle - et ne vous soucier de ce type de micro-optimisation que si vous avez une juste cause (ce que vous ne ferez presque jamais ).
Il est spécifique au compilateur / VM.
Par conséquent, cela ne vaut probablement pas la peine de se préoccuper à moins que vous n'ayez identifié cela comme un problème de performances vraiment critique dans votre application. L'optimisation prématurée est la racine de tout mal etc ...
Cependant , je l' ai vu cette optimisation donnent 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 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. Les programmeurs Java expérimentés remarqueront alors le modificateur static 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". Vous aurez donc communiqué efficacement le caractère statique de la méthode ...
Comme l'ont dit les affiches précédentes: 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 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 aucune recherche virtuelle au moment de l'exécution pour un appel de méthode statique. Cela peut entraîner une différence observable dans certaines circonstances.
La différence sur un code d'octet est qu'un appel de méthode non statique se fait à travers INVOKEVIRTUAL
, INVOKEINTERFACE
ou INVOKESPECIAL
pendant un appel de méthode statique se fait à travers INVOKESTATIC
.
invokespecial
car elle n'est pas virtuelle.
Il est incroyablement improbable qu'une différence de performance entre les 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".
7 ans plus tard ...
Je n'ai pas une grande confiance dans les résultats obtenus par Mike Nakis car ils ne résolvent pas certains problèmes courants liés aux optimisations Hotspot. J'ai instrumenté des benchmarks à l'aide de 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 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. Le résumé des résultats de mon benchmark JMH est le suivant;
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 / négligé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);
}
}
ops/s
principalement dans un environnement ART (par exemple, utilisation de la mémoire, taille de fichier .oat réduite, etc.). Connaissez-vous des outils / moyens relativement simples dont on pourrait essayer de comparer ces autres paramètres?
Pour décider si une méthode doit être statique, l'aspect performance ne doit pas être pertinent. Si vous avez un problème de performances, rendre statiques de nombreuses méthodes ne sauvera pas la mise. Cela dit, les méthodes statiques ne sont presque certainement pas plus lentes 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 à exécuter. Il s'agit d'un point discutable à l'ère de Hotspot, car Hotspot optimisera les appels de méthode d'instance qui n'ont qu'un seul site d'implémentation, de sorte qu'ils effectueront la même chose.
2.) Une autre différence subtile est que les méthodes statiques n'ont évidemment pas de référence à «cette». Il en résulte une trame de pile d'un slot plus petite que celle d'une méthode d'instance avec la même signature et le même corps ("this" est placé dans le slot 0 des variables locales au niveau du bytecode, tandis que pour les méthodes statiques, le slot 0 est utilisé pour le premier paramètre de la méthode).
Il peut y avoir une différence, et cela peut aller dans les deux sens pour un morceau de code particulier, et cela peut changer même avec une version mineure de la JVM.
Cela fait certainement partie des 97% de petites économies que vous devriez oublier .
TableView
des millions d'enregistrements.
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 Jon le note, les méthodes statiques ne peuvent pas être remplacées, donc simplement invoquer une méthode statique peut être - sur un runtime Java suffisamment naïf - plus rapide que l' appel d' une méthode d'instance.
Mais alors, même en supposant que vous êtes au point où vous vous souciez de gâcher votre conception pour gagner quelques nanosecondes, cela soulève juste 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 que vous vous retournez et implémentez votre propre répartiteur en plus de cela, le vôtre sera presque certainement moins efficace que celui construit dans votre environnement d'exécution Java déjà.
Je voudrais ajouter aux autres excellentes réponses ici que cela dépend également de votre flux, par exemple:
Public class MyDao {
private String sql = "select * from MY_ITEM";
public List<MyItem> getAllItems() {
springJdbcTemplate.query(sql, new MyRowMapper());
};
};
Faites attention que vous créez un nouvel objet 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 String sql = "select * from MY_ITEM";
public List<MyItem> getAllItems() {
springJdbcTemplate.query(sql, myRowMapper);
};
};