Generic Methoden vs. Casting in Java

vorhergehende Artikel in: Java
26.03.2022

Ich habe hier immer mal wieder über das Spannungsfeld zwischen Syntactic Sugar und sinnvollen Spracherweiterungen ganz speziell mit Blick auf Java ausgelassen - neulich hatte ich eine weitere Frage, die sich nur im Experiment klären ließ...

Die Frage stellte sich mir, als ich von einer Tätigkeit, die ich zum Erwerb der Kuchenzutaten ausüben muss zu einer anderen übergegangen bin: Beide Projekte benutzen Objekt-Factory-Klassen, wobei die eine so umgesetzt war, dass aus der Factory zurückgegebene Objekte erst gecastet werden mussten:

GenericsVsCasting1 obj =  (GenericsVsCasting1)createObject(cls[rand.nextInt(3)].getSimpleName());

während beim anderen Projekt die entsprechenden Factory-Methoden generifiziert waren und immer den korrekten Typ zurückgaben:

GenericsVsCasting1 obj = createGObject(cls[rand.nextInt(3)].getSimpleName());

Die Frage, die sich mir nun stellte war: Es ist sicher, dass es für den Entwickler, der die Methoden benutzt sicher angenehmer ist, die zweite Art zu benutzen - abgesehen davcon, dass der Quelltext nicht mir hunderten unnötigeer Casts verunreinigt wird. Aber gibt es eventuell auch handfeste Performancevorteile?

Die Antwort überraschte mich über alle Maßen: Die generische Methode, deren Rückgabewert man nicht casten muss war performanter als die althergebrachte - ich gebe hier als Beispiel Messwerte an, in denen ich jeweils 10.000.000 mal Objekte einer aus drei zur Verfügung stehenden Klassen instantiiert habe und anschließend eine Methode an den zurückgegebenen Instanzen aufrief um etwaige Optimierungen des Compilers bzw. der JVM zu verhindern:

generics 00:00:01.261459
casting 00:00:01.257609

Der Unterschied ist zugegebenermaßen gering - aber dennoch über mehrere Tests persistent und so groß, dass ich mich entschied, mir den generierten Bytecode für beide Varianten im Detail anzusehen, denn naiv müsste das derselbe sein - der Cast befindet sich einfach an anderer Stelle - richtig?

Zunächst der ByteCode der beiden Aufrufe, beginnend mit dem expliztiten Cast und gefolgt von der Variante mit dem Aufruf der generifizierten Factory-Methode:

       0: getstatic     #3                  // Field cls:[Ljava/lang/Class;
       3: getstatic     #4                  // Field rand:Ljava/util/Random;
       6: iconst_3
       7: invokevirtual #5                  // Method java/util/Random.nextInt:(I)I
      10: aaload
      11: invokevirtual #6                  // Method java/lang/Class.getSimpleName:()Ljava/lang/String;
      14: invokestatic  #7                  // Method createObject:(Ljava/lang/String;)Ljava/lang/Object;
      17: checkcast     #8                  // class de/elbosso/scratch/misc/GenericsVsCasting1
      20: astore_1
      21: getstatic     #3                  // Field cls:[Ljava/lang/Class;
      24: getstatic     #4                  // Field rand:Ljava/util/Random;
      27: iconst_3
      28: invokevirtual #5                  // Method java/util/Random.nextInt:(I)I
      31: aaload
      32: invokevirtual #6                  // Method java/lang/Class.getSimpleName:()Ljava/lang/String;
      35: invokestatic  #9                  // Method createGObject:(Ljava/lang/String;)Lde/elbosso/scratch/misc/GenericsVsCasting1;
      38: astore_2

Man sieht hier zunächst die naive Ansicht bestätigt: Der Aufruf mit Cast benötigt eine Byte-Code-Instruktion mehr: checkcast - dann muss der Grund für die schlechtere Performanz der Variante mit Generics in den eigentlichen Factory-Methoden liegen? Schauen wir uns zunächst die Variante ohne Generics an:

  private static java.lang.Object createObject(java.lang.String) throws java.lang.InstantiationException, java.lang.IllegalAccessException;
    Code:
       0: getstatic     #10                 // Field map1:Ljava/util/Map;
       3: aload_0
       4: invokeinterface #11,  2           // InterfaceMethod java/util/Map.get:(Ljava/lang/Object;)Ljava/lang/Object;
       9: checkcast     #12                 // class java/lang/Class
      12: invokevirtual #13                 // Method java/lang/Class.newInstance:()Ljava/lang/Object;
      15: areturn

Keine wirkliche Überraschung hier - sehen wir uns also die generische Factory-Methode als ByteCode an:

  private static <T extends de.elbosso.scratch.misc.GenericsVsCasting1> T createGObject(java.lang.String) throws java.lang.InstantiationException, java.lang.IllegalAccessException;
    Code:
       0: getstatic     #10                 // Field map1:Ljava/util/Map;
       3: aload_0
       4: invokeinterface #11,  2           // InterfaceMethod java/util/Map.get:(Ljava/lang/Object;)Ljava/lang/Object;
       9: checkcast     #12                 // class java/lang/Class
      12: invokevirtual #13                 // Method java/lang/Class.newInstance:()Ljava/lang/Object;
      15: checkcast     #8                  // class de/elbosso/scratch/misc/GenericsVsCasting1
      18: areturn

Das ist unbefriedigend - die Instruktion checkcast ist wie vermutet einfach in die Factory-Methode gerutscht. Woher die schlechtere Performanz der generischen Factory-Methode rührt bleibt also vorerst einmal ein Geheimnis. Ich würde mich aber über Feedback Interesierter freuen - vielleicht liegt es ja auch an der verwendeten JVM oder dem unterliegenden Betriebssystem?

Artikel, die hierher verlinken

Swap zweier Variablen ohne zusätzliche Variable

26.03.2022

Ich bin neulich über einen Artikel gestolpert der erklärte, wie man in Programmen die Werte zweier Variablen vertauschen kann, ohne dazu eine extra Variable zur Zwischenspeicherung zu benötigen.

Alle Artikel rss Wochenübersicht Monatsübersicht Github Repositories Gitlab Repositories Mastodon Über mich home xmpp


Vor 5 Jahren hier im Blog

  • Certstream, InfluxDB, Grafana und Netflix

    16.04.2019

    Nachdem ich vor kurzem über mein erstes Spielen mit dem certstream berichtete, habe ich weitere Experimente gemacht und die Daten zur besseren Auswertung in eine InfluxDB gepackt, um sie mit Grafana untersuchen zu können.

    Weiterlesen...

Neueste Artikel

  • Die sQLshell ist nun cloudnative!

    Die sQLshell hat eine weitere Integration erfahren - obwohl ich eigentlich selber nicht viel dazu tun musste: Es existiert ein Projekt/Produkt namens steampipe, dessen Slogan ist select * from cloud; - Im Prinzip eine Wrapperschicht um diverse (laut Eigenwerbung mehr als 140) (cloud) data sources.

    Weiterlesen...
  • LinkCollections 2024 III

    Nach der letzten losen Zusammenstellung (für mich) interessanter Links aus den Tiefen des Internet von 2024 folgt hier gleich die nächste:

    Weiterlesen...
  • Funktionen mit mehreren Rückgabewerten in Java

    Da ich seit nunmehr einem Jahr bei meinem neeun Arbeitgeber beschäftigt und damit seit ungefähr derselben Zeit für Geld mit Python arbeite, haben sich gewisse Antipathien gegenüber Python vertieft (ich kann mit typlosen Sprachen einfach nicht umgehen) - aber auch einige meiner Gründe, Python zu lieben sind ebenso stärker geworden. Einer davon ist der Fakt, dass eine Methode in Python mehr als einen Wert zurückgeben kann.

    Weiterlesen...

Manche nennen es Blog, manche Web-Seite - ich schreibe hier hin und wieder über meine Erlebnisse, Rückschläge und Erleuchtungen bei meinen Hobbies.

Wer daran teilhaben und eventuell sogar davon profitieren möchte, muß damit leben, daß ich hin und wieder kleine Ausflüge in Bereiche mache, die nichts mit IT, Administration oder Softwareentwicklung zu tun haben.

Ich wünsche allen Lesern viel Spaß und hin und wieder einen kleinen AHA!-Effekt...

PS: Meine öffentlichen GitHub-Repositories findet man hier - meine öffentlichen GitLab-Repositories finden sich dagegen hier.