C'est une question qui me vient à l'esprit lorsque je trouve la différence entre classe abstraite et interface. Dans ce post, j'ai appris que les interfaces sont lentes car elles nécessitaient un indirection supplémentaire. Mais je ne comprends pas quel type d'indirection est requis par l'interface et non par la classe abstraite ou la classe concrète.Veuillez clarifier ce point. Merci d'avance
Réponses
Trop de publicités?Il existe de nombreux mythes, et certains ont sans doute vrai il y a plusieurs années, et certains peut-être encore vrai sur les machines virtuelles qui n'ont pas de JIT.
La Android la documentation (rappelez-vous que Android n'avez pas de JVM, ils ont Dalvik VM) l'habitude de dire que l'invocation d'une méthode sur une des interfaces a été plus lente qu'en l'invoquant à une classe, de sorte qu'ils contribuent à la diffusion du mythe (il est aussi possible qu'il ait été plus lent sur le Dalvik VM avant de se tourner sur le JIT). La documentation ne disent maintenant:
La Performance Des Mythes
Les versions précédentes de ce document, faite de diverses allégations trompeuses. Nous l'adresse de certains d'entre eux ici.
Sur les appareils sans JIT, il est vrai que l'invocation de méthodes via un variable avec un type précis plutôt qu'une interface est un peu plus efficace. (Ainsi, par exemple, il était moins cher d'appeler des méthodes sur un HashMap carte de une Carte une carte, même si dans les deux cas, la carte est un HashMap.) Ce n'était pas le cas, que c'était 2x plus lent; le la différence était plus comme 6% plus lent. En outre, l'équipe fait les deux effectivement impossible de distinguer.
Source: la Conception de la performance sur Android
La même chose est probablement vrai pour le JIT dans la JVM, il serait très étrange sinon.
En cas de doute, de les mesurer. Mes résultats n'ont montré aucune différence significative. Lorsqu'il est exécuté, le programme suivant se produit:
7421714
5840702
7621523
5929049
Mais lorsque j'ai changé les lieux des deux boucles:
7887080
5573605
7986213
5609046
Il semble que les classes abstraites sont légèrement plus rapide, mais qui ne devrait pas être perceptible. Ce sont des nanosecondes. 7887080 nanosecondes sont ~7 millisecondes. Que fait-il une différence de 0,1 millis par 40k invocations (version de Java: 1.6.20)
Voici le code:
public class ClassTest {
public static void main(String[] args) {
Random random = new Random();
List<Foo> foos = new ArrayList<Foo>(40000);
List<Bar> bars = new ArrayList<Bar>(40000);
for (int i = 0; i < 40000; i++) {
foos.add(random.nextBoolean() ? new Foo1Impl() : new Foo2Impl());
bars.add(random.nextBoolean() ? new Bar1Impl() : new Bar2Impl());
}
long start = System.nanoTime();
for (Bar bar : bars) {
bar.bar();
}
System.out.println(System.nanoTime() - start);
start = System.nanoTime();
for (Foo foo : foos) {
foo.foo();
}
System.out.println(System.nanoTime() - start);
}
abstract static class Foo {
public abstract int foo();
}
static interface Bar {
int bar();
}
static class Foo1Impl extends Foo {
@Override
public int foo() {
int i = 10;
i++;
return i;
}
}
static class Foo2Impl extends Foo {
@Override
public int foo() {
int i = 10;
i++;
return i;
}
}
static class Bar1Impl implements Bar {
@Override
public int bar() {
int i = 10;
i++;
return i;
}
}
static class Bar2Impl implements Bar {
@Override
public int bar() {
int i = 10;
i++;
return i;
}
}
}
Un objet a un "vtable pointeur" d'une certaine sorte, qui évoque un "vtable" (méthode pointeur de la table) pour sa classe ("vtable" peut-être pas la bonne terminologie, mais ce n'est pas important). La vtable a des pointeurs vers tous les implémentations de méthodes; chaque méthode a un index qui correspond à une entrée de la table. Donc, pour appeler une méthode de classe, vous il suffit de regarder la méthode correspondante (à l'aide de son index) dans la vtable. Si une classe étend une autre, il a juste plus vtable avec plus d'entrées; l'appel d'une méthode de la classe de base utilise toujours la même procédure: c'est de chercher la méthode par son index.
Cependant, dans l'appel d'une méthode d'une interface via une interface de référence, il doit y avoir un mécanisme alternatif de trouver la méthode de la mise en œuvre de pointeur. Parce qu'une classe peut implémenter plusieurs interfaces, il n'est pas possible pour la méthode pour avoir toujours le même indice dans la vtable (par exemple). Il y a plusieurs façons de résoudre ce problème, mais pas de façon tout à fait aussi efficace que simple vtable de l'expédition.
Cependant, comme mentionné dans les commentaires, il ne sera probablement pas faire beaucoup de différence avec un moderne de la machine virtuelle Java de mise en œuvre.
C'est une variation sur Bozho exemple. Il fonctionne plus et ré-utilise les mêmes objets de sorte que la taille de la mémoire cache n'a pas tellement d'importance. J'ai aussi utiliser un tableau donc il n'y a pas de frais généraux de l'itérateur.
public static void main(String[] args) {
Random random = new Random();
int testLength = 200 * 1000 * 1000;
Foo[] foos = new Foo[testLength];
Bar[] bars = new Bar[testLength];
Foo1Impl foo1 = new Foo1Impl();
Foo2Impl foo2 = new Foo2Impl();
Bar1Impl bar1 = new Bar1Impl();
Bar2Impl bar2 = new Bar2Impl();
for (int i = 0; i < testLength; i++) {
boolean flip = random.nextBoolean();
foos[i] = flip ? foo1 : foo2;
bars[i] = flip ? bar1 : bar2;
}
long start;
start = System.nanoTime();
for (Foo foo : foos) {
foo.foo();
}
System.out.printf("The average abstract method call was %.1f ns%n", (double) (System.nanoTime() - start) / testLength);
start = System.nanoTime();
for (Bar bar : bars) {
bar.bar();
}
System.out.printf("The average interface method call was %.1f ns%n", (double) (System.nanoTime() - start) / testLength);
}
imprime
The average abstract method call was 4.2 ns
The average interface method call was 4.1 ns
si vous modifiez l'ordre d'exécution des tests, vous obtenez
The average interface method call was 4.2 ns
The average abstract method call was 4.1 ns
Il n'y a plus de différence dans la façon dont vous exécutez le test de celui qui vous a choisi.
J'ai obtenu le même résultat avec Java 6 update 26 et OpenJDK 7.
BTW: Si vous ajoutez une boucle qui ne doit appeler la même objet à chaque fois, vous obtenez
The direct method call was 2.2 ns
J'ai essayé d'écrire un test qui permettrait de quantifier les diverses méthodes peuvent être invoquées. Mes résultats montrent qu'il n'est pas de savoir si une méthode est une méthode d'interface ou pas qui compte, mais plutôt le type de la référence à travers lequel vous êtes en l'appelant. L'appel d'une méthode de l'interface par une classe de référence est beaucoup plus rapide (par rapport au nombre d'appels) que de faire appel à la même méthode sur la même classe par l'intermédiaire d'une interface de référence.
Les résultats de 1 000 000 appels sont...
la méthode de l'interface via l'interface de référence: (nanos, millis) 5172161.0, 5.0
la méthode de l'interface via la référence abstraite: (nanos, millis) 1893732.0, 1.8
la méthode de l'interface via un interprète de référence dérivés: (nanos, millis) 1841659.0, 1.8
Méthode concrète via la classe de béton de référence: (nanos, millis) 1822885.0, 1.8
Notez que les deux premières lignes de résultats sont des appels à exactement la même méthode, mais par l'intermédiaire de références différentes.
Et voici le code...
package interfacetest;
/**
*
* @author rpbarbat
*/
public class InterfaceTest
{
static public interface ITest
{
public int getFirstValue();
public int getSecondValue();
}
static abstract public class ATest implements ITest
{
int first = 0;
@Override
public int getFirstValue()
{
return first++;
}
}
static public class TestImpl extends ATest
{
int second = 0;
@Override
public int getSecondValue()
{
return second++;
}
}
static public class Test
{
int value = 0;
public int getConcreteValue()
{
return value++;
}
}
static int loops = 1000000;
/**
* @param args the command line arguments
*/
public static void main(String[] args)
{
// Get some various pointers to the test classes
// To Interface
ITest iTest = new TestImpl();
// To abstract base
ATest aTest = new TestImpl();
// To impl
TestImpl testImpl = new TestImpl();
// To concrete
Test test = new Test();
System.out.println("Method call timings - " + loops + " loops");
StopWatch stopWatch = new StopWatch();
// Call interface method via interface reference
stopWatch.start();
for (int i = 0; i < loops; i++)
{
iTest.getFirstValue();
}
stopWatch.stop();
System.out.println("interface method via interface reference: (nanos, millis)" + stopWatch.getElapsedNanos() + ", " + stopWatch.getElapsedMillis());
// Call interface method via abstract reference
stopWatch.start();
for (int i = 0; i < loops; i++)
{
aTest.getFirstValue();
}
stopWatch.stop();
System.out.println("interface method via abstract reference: (nanos, millis)" + stopWatch.getElapsedNanos() + ", " + stopWatch.getElapsedMillis());
// Call derived interface via derived reference
stopWatch.start();
for (int i = 0; i < loops; i++)
{
testImpl.getSecondValue();
}
stopWatch.stop();
System.out.println("interface via toplevel derived reference: (nanos, millis)" + stopWatch.getElapsedNanos() + ", " + stopWatch.getElapsedMillis());
// Call concrete method in concrete class
stopWatch.start();
for (int i = 0; i < loops; i++)
{
test.getConcreteValue();
}
stopWatch.stop();
System.out.println("Concrete method via concrete class reference: (nanos, millis)" + stopWatch.getElapsedNanos() + ", " + stopWatch.getElapsedMillis());
}
}
package interfacetest;
/**
*
* @author rpbarbat
*/
public class StopWatch
{
private long start;
private long stop;
public StopWatch()
{
start = 0;
stop = 0;
}
public void start()
{
stop = 0;
start = System.nanoTime();
}
public void stop()
{
stop = System.nanoTime();
}
public float getElapsedNanos()
{
return (stop - start);
}
public float getElapsedMillis()
{
return (stop - start) / 1000;
}
public float getElapsedSeconds()
{
return (stop - start) / 1000000000;
}
}
Ce fut à l'aide de la Oracles JDK 1.6_24. Espérons que cela aide à mettre cette question au lit...
En ce qui concerne,
Rodney Barbati