Pyjnius: PyJnius gegen JPype

Erstellt am 16. Juli 2020  ·  27Kommentare  ·  Quelle: kivy/pyjnius

Ich bin der Hauptautor von JPype. Als Teil der Aktualisierung der Dokumentation von JPype habe ich PyJnius in die Liste der alternativen Codes zu JPype aufgenommen. Leider konnte ich nach zwei Stunden Herumspielen mit PyJnius nichts finden, was wie ein Vorteil von PyJnius gegenüber JPype aussah. Jeder Aspekt, den ich von Proxies, Customizern, mehrdimensionalem Array-Handling, Javadoc-Integration, GC-Handling, Puffern, wissenschaftlicher Code-Integration (numpy, matplotlib usw.), anrufsensitiven Methoden, Dokumentation und in den meisten Fällen sogar Ausführungsgeschwindigkeit betrachtet habe, wird derzeit behandelt in JPype vollständiger als PyJnius. Als Autor von JPype betrachte ich jedoch vielleicht eher Aspekte, die ich schätze, als die des PyJnius-Teams. Können Sie die Vorteile dieses Projekts besser formulieren? Was ist das Wertversprechen dieses Projekts, was ist seine Zielgruppe und was soll es tun, was Alternativen nicht bereits abdecken?

Hilfreichster Kommentar

Ich habe vor etwa 2 Jahren alle Java-Bridge-Codes erreicht. Leider wurde der PyJnius-Code anscheinend übersehen, da er bei meiner Suche nie auftauchte. Ich hätte es auch in dieser Runde verpasst, es sei denn, ich habe nach der Seite gesucht, die letztes Mal die nette Tech-Pressemitteilung veröffentlicht hat, und bin über einen Blog gestolpert, in dem die beiden Projekte diskutiert werden. Ich bin mir nicht sicher, wie ich zwei Jahre lang ein weiteres aktives Projekt im selben Bereich verpasst habe, aber es war eindeutig meine Schuld.

Klingt so, als hätten Sie JPype mit Py4J verwechselt, dem anderen wichtigen Bridge-Code. Sie tun alles mit Steckdosen mit den damit verbundenen Vor- und Nachteilen. Ich stellte auch fest, dass das Projekt nicht meinen Anforderungen entsprach.

Ich habe nicht recherchiert, was erforderlich ist, um Android zu unterstützen. Wenn ich jedoch einige technische Daten habe, sollte es möglich sein. Es gibt nichts, was wir außerhalb von nativem JNI und einfachen alten Aufrufen der C-API von Python tun.

Was den Ansatz angeht, verwendet JPype ausschließlich JNI, um eine JVM mit Python mit dem Befehl "startJVM()" zu verbinden. Die nächste Version (2.0) bietet jedoch auch die Möglichkeit, das Gegenteil zu tun, indem Python aus Java heraus gestartet werden kann. Dies geschieht durch einen Schichtenansatz. Es gibt eine Python-Schicht, die alle High-Level-Klassen enthält, die als Front-End dienen, ein CPython-Privatmodul mit Basisklassen, die die Einstiegspunkte halten, eine C++-Backer-Schicht, die alle Typkonvertierungen und den Abgleich übernimmt und als natives Modul fungiert für Java-Bibliothek und eine Java-Bibliothek, die alle Dienstprogrammaufgaben enthält (Lebensdauern von Objekten halten, Unterstützungsklassen für Slices und Ausnahmen erstellen und Javadoc-Extraktoren/-Rendering).

Vor 8 Jahren war JPype ein bisschen durcheinander. Es wurde versucht, sowohl Ruby als auch Python zu unterstützen, daher war die C++-Schicht ein Wirrwarr von Wrappern, mit denen man arbeiten musste, und das Frontend war alles in Python, also war es sehr langsam. Es wurde auch auf die Rückgabetypen aufgeteilt, da numpy-Unterstützung kompiliert werden konnte, was dazu führte, dass andere Objekte zurückgegeben wurden. Es brauchte viele Adapterklassen wie JException, um als Proxys zu fungieren, wo sich das native Python- und das Java-Objekt unterschieden. Aber all diese Probleme wurden in den 3 Jahren, seit ich dem Projekt beigetreten bin, gelöst. Die beiden Hauptziele (für mich) in JPype besteht darin, eine Syntax bereitzustellen, die einfach genug ist, damit Physiker mit der Programmierung vertraut sind, um Java verwenden zu können und ein hohes Maß an Integration mit wissenschaftlichem Python-Code zu erreichen. Wir tun dies, indem wir die Objekte, die von Java unterstützt werden, so einrichten, dass sie "allen Schnickschnack" CPython-Objekt-Wrapper haben. Anstatt ein primitives Java-Array umzuwandeln, machen wir ein primitives Java-Array zu einem neuen nativen Python-Typ, der dieselben Einstiegspunkte wie numpy implementiert und alle diese durch Speicherpuffertransfers unterstützt werden. Daher haben wir schnelle Konvertierungen, indem wir list(jarray) oder np.array(jarray) aufrufen.

Um es an Android anzupassen, müsste die Startsequenz wahrscheinlich überarbeitet und der Thunk-Code, mit dem es seine interne Bibliothek lädt, durch ein traditionelleres JNI-Modell ersetzt werden. Ich habe den Thunk-Code bereits in der nächsten Version entfernt, so dass die spätere bereits erfüllt ist. Nur ersteres wäre erforderlich.

Die wichtigsten Unterschiede im Ansatz, die ich sehen kann, sind, dass PyJnius Arrays konvertiert, die von und zu gehen. Dies scheint für die wissenschaftliche Codierung sehr unerschwinglich zu sein, da die Übergabe großer Arrays (die oft nie konvertiert werden) der bevorzugte JPype-Stil ist. Die Entscheidung, eine Konvertierung zu erfordern, erzwingt dann Optionen wie Wertübergabe und Referenzübergabe, was jedoch möglicherweise zu größeren Problemen führt, als wenn Sie einen Aufruf mit mehreren Argumenten haben, wählen Sie eine Richtlinie für alle Argumente aus. Es würde auch den Umgang mit mehrdimensionalen Arrays erschweren. (Ich denke, eine Adapterklasse wie obj.method(1, jnius.byref(list1), list2) hätte eine bessere Kontrolle ermöglicht, wenn sie erreicht werden kann). Darüber hinaus gibt es viele Probleme, die JPype gelöst hat, wie z. B. GC-Verknüpfung, aufrufersensitive Methoden und dergleichen. Wenn nichts anderes, sehen Sie sich bitte den JPype-Code an und sehen Sie, ob es gute Ideen gibt, die Sie verwenden können.

Alle 27 Kommentare

Danke, dass Sie sich gemeldet haben!

Ich erinnere mich vielleicht falsch, weil ich mich seit Jahren nicht mehr mit jpype befasst habe, aber ich dachte, dass es einen anderen Ansatz verwendet, nämlich über einen Server (also IPC) mit der JVM zu sprechen, anstatt über einen gemeinsamen Speicher (aber Ihre Readme-Hinweise weisen auf das Gegenteil hin. und ich sehe keine Hinweise, die dies bei einem kurzen Blick auf den Code widerlegen, also liege ich wahrscheinlich völlig falsch), und dieser Ansatz machte es zum Beispiel auf Android nicht praktikabel (was der Hauptgrund für die Entwicklung von Pyjnius war, obwohl einige Leute verwenden es auf Desktop-Plattformen). Auf der anderen Seite sehe ich in der JPype-Codebasis nicht viele Hinweise auf Android-Unterstützung, es sei denn, es geht um das Verzeichnis native , da seine jni.h anscheinend von AOSP stammt?

Aber ehrlich gesagt war uns JPype zu Beginn des Projekts überhaupt nicht bewusst. Ich glaube, @tito hat später, als wir davon Nachforschungen angestellt , um zu vergleichen, aber ich erinnere mich nicht, ob er bestimmte Gründe sah, nicht zu wechseln.

Hi. Ich erinnere mich an den Namen JPype, aber als ich suchte, was wir verwenden könnten, kann ich mich nicht erinnern, warum er vor 8 Jahren ehrlich gesagt nicht verwendet wurde :) Das einzige Ziel am Anfang war es, mit Android kommunizieren zu können API, aber keinen mittleren RPC-Server verwenden, wie es das P4A-Projekt zu dieser Zeit tat.

Ich habe vor etwa 2 Jahren alle Java-Bridge-Codes erreicht. Leider wurde der PyJnius-Code anscheinend übersehen, da er bei meiner Suche nie auftauchte. Ich hätte es auch in dieser Runde verpasst, es sei denn, ich habe nach der Seite gesucht, die letztes Mal die nette Tech-Pressemitteilung veröffentlicht hat, und bin über einen Blog gestolpert, in dem die beiden Projekte diskutiert werden. Ich bin mir nicht sicher, wie ich zwei Jahre lang ein weiteres aktives Projekt im selben Bereich verpasst habe, aber es war eindeutig meine Schuld.

Klingt so, als hätten Sie JPype mit Py4J verwechselt, dem anderen wichtigen Bridge-Code. Sie tun alles mit Steckdosen mit den damit verbundenen Vor- und Nachteilen. Ich stellte auch fest, dass das Projekt nicht meinen Anforderungen entsprach.

Ich habe nicht recherchiert, was erforderlich ist, um Android zu unterstützen. Wenn ich jedoch einige technische Daten habe, sollte es möglich sein. Es gibt nichts, was wir außerhalb von nativem JNI und einfachen alten Aufrufen der C-API von Python tun.

Was den Ansatz angeht, verwendet JPype ausschließlich JNI, um eine JVM mit Python mit dem Befehl "startJVM()" zu verbinden. Die nächste Version (2.0) bietet jedoch auch die Möglichkeit, das Gegenteil zu tun, indem Python aus Java heraus gestartet werden kann. Dies geschieht durch einen Schichtenansatz. Es gibt eine Python-Schicht, die alle High-Level-Klassen enthält, die als Front-End dienen, ein CPython-Privatmodul mit Basisklassen, die die Einstiegspunkte halten, eine C++-Backer-Schicht, die alle Typkonvertierungen und den Abgleich übernimmt und als natives Modul fungiert für Java-Bibliothek und eine Java-Bibliothek, die alle Dienstprogrammaufgaben enthält (Lebensdauern von Objekten halten, Unterstützungsklassen für Slices und Ausnahmen erstellen und Javadoc-Extraktoren/-Rendering).

Vor 8 Jahren war JPype ein bisschen durcheinander. Es wurde versucht, sowohl Ruby als auch Python zu unterstützen, daher war die C++-Schicht ein Wirrwarr von Wrappern, mit denen man arbeiten musste, und das Frontend war alles in Python, also war es sehr langsam. Es wurde auch auf die Rückgabetypen aufgeteilt, da numpy-Unterstützung kompiliert werden konnte, was dazu führte, dass andere Objekte zurückgegeben wurden. Es brauchte viele Adapterklassen wie JException, um als Proxys zu fungieren, wo sich das native Python- und das Java-Objekt unterschieden. Aber all diese Probleme wurden in den 3 Jahren, seit ich dem Projekt beigetreten bin, gelöst. Die beiden Hauptziele (für mich) in JPype besteht darin, eine Syntax bereitzustellen, die einfach genug ist, damit Physiker mit der Programmierung vertraut sind, um Java verwenden zu können und ein hohes Maß an Integration mit wissenschaftlichem Python-Code zu erreichen. Wir tun dies, indem wir die Objekte, die von Java unterstützt werden, so einrichten, dass sie "allen Schnickschnack" CPython-Objekt-Wrapper haben. Anstatt ein primitives Java-Array umzuwandeln, machen wir ein primitives Java-Array zu einem neuen nativen Python-Typ, der dieselben Einstiegspunkte wie numpy implementiert und alle diese durch Speicherpuffertransfers unterstützt werden. Daher haben wir schnelle Konvertierungen, indem wir list(jarray) oder np.array(jarray) aufrufen.

Um es an Android anzupassen, müsste die Startsequenz wahrscheinlich überarbeitet und der Thunk-Code, mit dem es seine interne Bibliothek lädt, durch ein traditionelleres JNI-Modell ersetzt werden. Ich habe den Thunk-Code bereits in der nächsten Version entfernt, so dass die spätere bereits erfüllt ist. Nur ersteres wäre erforderlich.

Die wichtigsten Unterschiede im Ansatz, die ich sehen kann, sind, dass PyJnius Arrays konvertiert, die von und zu gehen. Dies scheint für die wissenschaftliche Codierung sehr unerschwinglich zu sein, da die Übergabe großer Arrays (die oft nie konvertiert werden) der bevorzugte JPype-Stil ist. Die Entscheidung, eine Konvertierung zu erfordern, erzwingt dann Optionen wie Wertübergabe und Referenzübergabe, was jedoch möglicherweise zu größeren Problemen führt, als wenn Sie einen Aufruf mit mehreren Argumenten haben, wählen Sie eine Richtlinie für alle Argumente aus. Es würde auch den Umgang mit mehrdimensionalen Arrays erschweren. (Ich denke, eine Adapterklasse wie obj.method(1, jnius.byref(list1), list2) hätte eine bessere Kontrolle ermöglicht, wenn sie erreicht werden kann). Darüber hinaus gibt es viele Probleme, die JPype gelöst hat, wie z. B. GC-Verknüpfung, aufrufersensitive Methoden und dergleichen. Wenn nichts anderes, sehen Sie sich bitte den JPype-Code an und sehen Sie, ob es gute Ideen gibt, die Sie verwenden können.

@Thrameos Eine neue Sache, die wir zusammenführen https://github.com/kivy/pyjnius/pull/515 ; Ich habe das in JPype nicht gesehen?

JPype unterstützt ab 1.0.0 Lambdas von funktionalen Schnittstellen. Es war Teil der 30 Ziehungen in 30 Tagen, die im März auf 1,0 verschoben wurden.

JPype hat eine lange Inkubationszeit hinter sich. Ursprünglich im Jahr 2004 gestartet und bis 2007 gelaufen. Es hatte dann einen großen Schub, als die Gruppe von Benutzern es wiederbelebte, um es um 2015 auf Python 3 zu portieren. Dann wurde es 2017 für die Verwendung in einem nationalen Labor abgeholt, das es ab 0.6 trug. 3 bis 0.7.2. Während dieser Zeit konzentrierten sich alle Bemühungen auf die Verbesserung und Härtung der Kerntechnologie, die die Schnittstelle bereitstellt. Aber das wurde im März nach dem zweiten Core-Rewrite endlich abgeschlossen, sodass wir endlich den Vorstoß für 1.0.0 machen konnten. Seitdem fügen wir alles hinzu, was bei meiner Wunschlisten-Aktion "30 Pulls in 30 Nights" "fehlte". Der Rückstand von allem, was ich einfach nicht implementieren konnte, weil es zu viel Arbeit wäre, wurde endlich beseitigt (Probleme von 50 auf 20 reduziert, Benutzer auffordern, nach dem zu fragen, was sie brauchen usw.). Es kann also eine Reihe von Funktionen geben, die Sie in früheren Versionen nicht gefunden haben, die jetzt verfügbar sind. Ich habe die meisten Funktionsanfragen in weniger als einer Woche bearbeitet, sodass mir nur noch die großen 3 (Reverse Bridge, Klassen in Python erweitern, Möglichkeit zum Starten einer zweiten JVM) übrig sind.

Das Projekt wird wieder schlummern, da ich 2 Monate in einer 6-monatigen Anstrengung bin, den Reverse-Bridge-Code zu vervollständigen, der es Java ermöglicht, Python aufzurufen und Stubs für Python-Bibliotheken zu generieren, damit sie als native Java-Bibliotheken verwendet werden können. Es verwendet ASM, um Java-Klassen im laufenden Betrieb zu erstellen, sodass eine native Unterstützung für Python erreicht werden kann. Immer noch nicht vollständig integriert wie Jython, aber vielleicht nahe genug, dass es keinen großen Unterschied geben wird.

Vielen Dank für die detaillierten Erklärungen, und wenn überhaupt, gibt es sicherlich Ideen, die wir verwenden könnten, und es wäre es wert, den Code zu studieren das Projekt, also herzlichen Glückwunsch zu all der Arbeit. Sie haben Recht mit meiner Verwechslung mit Py4J, ich denke, als ich mir JPype ansah, muss es sich in dem von Ihnen beschriebenen chaotischen Zustand befunden haben, und die Verwendung muss viel komplizierter gewesen sein als PyJNIus zu diesem Zeitpunkt.

Ihre Punkte zur Übergabe von Werten / Konvertierungen sind sehr wahr und haben auch hier einige aktuelle Diskussionen ausgelöst, da @hx2A untersucht hat, wie die Leistung verbessert werden kann, und die Konvertierung zurück zu Python-Typen optional gemacht hat (als Übergabe einer Wegwerfliste an Java, um sie zu erhalten in eine Java-Liste umgewandelt, von Java modifiziert oder nicht, und zurück in Python umgewandelt, nur um sie zu sammeln, war sicherlich suboptimal, wir können jetzt zumindest den zweiten Teil vermeiden, auf Kosten der Verwendung von Schlüsselwortargumenten, was sicher ist, da Java unterstützt sie nicht, daher gibt es keinen Signaturkonflikt, aber die Syntax ist sicherlich etwas verrauschter).

Was einen möglichen Unterschied zwischen JPype und PyJNIus angeht, können wir Java-Schnittstellen mithilfe von Python-Klassen implementieren und sie an Java übergeben, um sie als Rückrufe zu verwenden. Andererseits müssten wir tatsächlich Java-Bytecode generieren, wenn wir Java-Klassen erweitern wollten von Python, da es eine Voraussetzung ist, eine Android-API zu verwenden, die wir im Moment nicht abdecken können, bin ich mir nicht sicher, ob ich richtig aus Ihrem Kommentar folge, aber vielleicht haben Sie nicht die Möglichkeit, Java-Klassen anrufen zu lassen Python-Code wie dieser (unter Verwendung von Schnittstellen).

JPype kann Schnittstellen in Python implementieren. Fügen Sie einfach Dekoratoren zu gewöhnlichen Python-Klassen hinzu.

from java.util.function import Consumer

@jpype.JImplements(Consumer)
class MyConsumer:
   @jpype.JOverride
   def apply(self, obj):
       pass

Verwenden Sie einen String in @JImplements, wenn die Klasse vor dem Start der JVM definiert wird.

(Bearbeiten: Der Grund, warum wir die Python-Vererbung nicht verwendet haben, ist, dass wir zu dem Zeitpunkt, als dies hinzugefügt wurde, noch Python 2 unterstützten, was viele Metaklassenprobleme verursachte. Wir werden weiter aufräumen, sobald Klassenerweiterungen vorhanden sind. Also Anmerkung, die einmal zur Deklarationszeit auswerten waren sauberer.)

Wir verwenden das gleiche System, um Customizer (dunder?) für Klassen zu implementieren

@jpype.JImplementationFor("java.util.ArrayList")
class ArrayListImpl:
    def __getitem__(self, i):
        return self.get(i)
    @jpype.JOverride
    def addAll(self, list):
        # Decide if we need to convert or can call directly.
        ...

Wir verwenden auch Annotationen, um die impliziten Konverter zu definieren, wie z. B. "Alle Python-Pfadobjekte werden in java.io.File konvertiert".

 @jpype.JConversion("java.io.File", instanceof=pathlib.PurePath)
 def _JFileConvert(jcls, obj):
       Paths = jpype.JClass("java.nio.file.Paths")
       return Paths.get(str(obj))

Offensichtlich ist die Verwendung dieser Art von Logik etwas langsamer als spezielle C-Methoden, aber sie hält die Lesbarkeit und Flexibilität hoch. Ich habe kritische Pfade, die sich als Engpässe erwiesen haben, nach Bedarf zurück nach C verschoben.

Wir stellen auch etwas Syntaxzucker bereit, um die Dinge sauber zu machen MyJavaClass@obj => Cast in MyJavaClass (Java-Äquivalent (MyJavaClass)obj ) oder cls=JInt[:] => Erstellen Sie einen Array-Typ ( cls=int[].class ) oder a=JDouble[10][5] => Erstellen Sie ein Multidim-Array ( double[][] a = new double[10][5] ).

Ich habe an dem Prototyp für die Erweiterung von Klassen von JPype gearbeitet. Wir haben das gleiche Problem, da einige Swing-Klassen und andere APIs Erweiterungsklassen erfordern. Die Lösung, die ich bisher ausgedacht habe, besteht darin, für jede der überschriebenen Methoden eine erweiterte Klasse mit einer HashMap zu erstellen. Wenn die Hashmap für diesen Einstiegspunkt nichts enthält, übergibt es an super, andernfalls ruft es den Proxy-Methodenhandler auf. Aber ich entscheide, dass dies am einfachsten zu implementieren ist, nachdem die Reverse Bridge abgeschlossen ist, damit Java tatsächlich Python-Methoden verarbeiten kann, anstatt die Java-Proxy-Methode zu durchlaufen. Es dauert also noch etwa 6 Monate, bis der Prototyp funktioniert. Sie können sich den epypj-Zweig (mein Name für die Reverse Bridge) ansehen, um zu sehen, wie der Aufruf von Java zurück zu Python funktioniert, sowie Muster zum Generieren eines Aufrufers mit ASM, um Java-Klassen im laufenden Betrieb zu erstellen.

Was die Verwaltung der Garbage Collection angeht, gibt es nur wenige Teile von JPype, die diese Funktion erfüllen. Zum einen die JPypeReferenceQueue (native/java/org/jpype/ref/JPypeReferenceQueue), die das Leben eines Python-Objekts an ein Java-Objekt bindet. Dies wird zum Erstellen von Puffern und anderen Dingen verwendet, bei denen Java eine Zeit lang auf ein Python-Konzept zugreifen muss. Die zweite ist die Verwendung globaler Referenzen, damit Python ein Java-Objekt im Geltungsbereich halten kann. Dies erfordert den Garbage Collector-Link (native/common/jp_gc.cpp), der auf jedes System wartet, um einen GC auszulösen, und ihn unter bestimmten Bedingungen (Größe der Pools, relatives Wachstum) an das andere pingt. Letzte Proxys müssen schwache Referenzen verwenden, da sie sonst Schleifen bilden würden (da der Proxy eine Referenz auf die Java-Hälfte enthält und die Java-Hälfte zurück auf die Python-Implementierung verweist). Schließlich beabsichtige ich, einen Agenten zu verwenden, damit Python Java durchqueren kann, aber das ist auf dem Weg.

Ich bin einer der Leute, die pyjnius auf dem Desktop und nicht auf Android verwenden. Ich wusste nichts über JPype, als ich mit dem Bau meines Projekts begann, aber ich habe einige Nachforschungen angestellt, um die Unterschiede zu sehen.

Eine einzigartige Funktion in pyjnius ist, dass der Aufrufer entscheiden kann, ob er die geschützten und privaten Methoden und Felder einbezieht oder nicht. Ich bevorzuge nur öffentliche, aber ich verstehe das Argument, dass es nützlich ist, die nicht öffentlichen Felder und Methoden zur Verfügung zu stellen.

Leistung ist für mein Projekt entscheidend. Ich habe einige Tests mit der folgenden Klasse gemacht:

package org.pkg;

public class MyClass {

  public MyClass() {
  }

  public int number = 42;

  public float add1(float x, float y) {
    return x + y;
  }

  public float add2(float x, float y) {
    return x + y;
  }

  public float add2(int x, int y) {
    return x + y;
  }
}

In JPEG:

In [1]: import jpype
   ...: import jpype.imports
   ...: jpype.startJVM()
   ...: from org.pkg import MyClass
   ...: myInstance = MyClass()
   ...:

In [2]: %timeit myInstance.number
640 ns ± 2.65 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [3]: %timeit myInstance.add1(10.3, 20.5)
2.13 µs ± 24.8 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [4]: %timeit myInstance.add2(10.3, 20.5)
2.19 µs ± 9.41 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Im Pyjnius:

In [1]: import jnius

In [2]: MyClass = jnius.autoclass('org.pkg.MyClass')

In [3]: myInstance = MyClass()

In [4]: %timeit myInstance.number
161 ns ± 0.104 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [5]: %timeit myInstance.add1(10.3, 20.5)
1.04 µs ± 8.16 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [6]: %timeit myInstance.add2(10.3, 20.5)
2.71 µs ± 11.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Pyjnius ist um einiges schneller, mit Ausnahme der überladenen Methode. Wir sollten die Hinweise vergleichen, wie entschieden wird, welche Methode aufgerufen wird, wenn die Methode überladen ist. Pyjnius hat diesen Scoring-Mechanismus, der eine Menge Overhead zu verursachen scheint. JPype trifft diese Entscheidung viel schneller.

Abschließend zu Benchmark-Zwecken:

In [9]: def add(x, y):
   ...:     return x + y
   ...:

In [10]: %timeit add(10.3, 20.5)
82.9 ns ± 0.187 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

Natürlich sind Unterschiede von wenigen µs trivial, aber das summiert sich, wenn man sehr schnell Tausende von kleinen Anrufen tätigt, was ich tun muss.

Die Integration von JPype mit numpy ist recht nett und einfach zu bedienen. Ich kann sehen, wie Forscher dies nutzen könnten, um Skripte zu schreiben, die große Arrays ohne komplizierte Syntax an Java-Bibliotheken übergeben. Ich muss auch große Arrays übergeben und das mit tobytes() und speziellem Java-Code, der die Bytes empfangen kann, aber das ist offensichtlich nicht so ordentlich oder bequem.

Die JPype-Geschwindigkeit ist leider eine Art Ist-was-ist-Aussicht. JPype ist sehr defensiv und versucht, sich vor schlechten Dingen zu schützen, was bedeutet, dass es eine Menge nicht trivialer Overhead gibt. Das bedeutet zum Beispiel, dass bei jedem Java-Aufruf sichergestellt wird, dass der Thread verbunden ist, damit wir keinen Segfault verursachen, wenn er von einem externen Thread wie einer IDE aufgerufen wird. Meine lokale Benutzergruppe besteht aus Wissenschaftlern, daher sind die Einstiegspunkte alle sehr gut gegen eine ziemlich schreckliche Querverdrahtung geschützt. Wenn sie jemals einen Segfault haben (egal wie verrückt es war), dann habe ich versagt. (Was die 1500 Tests erklären würde, einschließlich der bewussten Konstruktion schlechter Objekte.)

Zweitens variiert die Geschwindigkeit einzelner Aktionen wahrscheinlich sehr stark bei sehr kleinen Unterschieden in der ausgeführten Arbeit. Das triviale Beispiel, das Sie angeführt haben, war einer der schlimmsten Fälle. Je nachdem, auf welche Art von Feld zugegriffen wird, kann sich die Zugriffsgeschwindigkeit in einigen Fällen tatsächlich ändern.

Für Ihr Geschwindigkeitsbeispiel fordern Sie ein int-Feld an.

  • In PyJnius erstellt es einen Deskriptor für die Objektsuche, greift auf das Feld zu, erstellt ein neues Python-Long und gibt es dann zurück.
  • In JPype erstellt es einen Deskriptor für die Objektsuche, greift auf das Feld zu, erstellt ein neues Python-Long, erstellt dann einen Wrapper-Typ für ein Java-Int, kopiert den Python-Long-Speicher in das JInt (da Python keine Möglichkeit zum Erstellen eines abgeleiteten integer-Klasse direkt), bindet dann den Slot mit einem Java-Wert und gibt schließlich das resultierende JInt zurück.

Selbst etwas so Triviales wie Geschwindigkeits-Benchmarking beim Zugriff auf ein Feld ist nicht wirklich so trivial.
Der Unterschied besteht darin, dass einer einen Python-Long und der andere eine tatsächliche Java-Ganzzahl (die den Java-Konvertierungsregeln folgt) zurückgegeben hat, die an eine andere überladene Methode übergeben und ordnungsgemäß gebunden werden könnte. Die Arbeit, einen Wrapper-Typ zurückzugeben, ist viel mehr als nur die Rückgabe eines Python-Typs, daher ein enormer Geschwindigkeitsunterschied.

Ich habe versucht, dies zu demonstrieren, indem ich einige verschiedene Feldtypen getestet habe. Leider hat jnius beim Testen von Objektfeldern einen Fehler im Code "harness.objectField = Harness" gemacht. Ich bin mir nicht sicher, warum dieses bestimmte Stück Querverdrahtung ein Problem verursacht hat, aber es ist bei mir fehlgeschlagen. Ich habe mich für JPype nicht sehr für die Geschwindigkeit interessiert, außer die groben Täter zu entfernen, was einen Faktor von 3-5 Beschleunigung für Anrufe und 300-fache für bestimmte Array-Zugriffe ergab. Aber vielleicht sollte ich überprüfen und sehen, welche Bereiche verbessert werden können. Ich bezweifle jedoch, dass ich es wie PyJnius auf die nackten Knochen reduzieren kann, ohne die Sicherheit zu entfernen oder Rückholverträge zu entfernen (was ich nicht tun kann). Es sind höchstens noch 10-30% Beschleunigung möglich,

Was die Funktion des Zugriffs auf private und geschützte Felder betrifft, ist dies sicherlich möglich. Ich bevorzuge es, dass die Benutzer Reflektion oder andere interne Zugriffsmethoden verwenden, anstatt das Objekt direkt freizugeben. Wenn ich so etwas bereitstellen müsste, würde ich wahrscheinlich ein Feld namens _private erstellen, das Felder enthält, die nicht öffentlich zugänglich sind. JPype bietet nur einen Klassen-Wrapper für jeden Typ, daher habe ich nicht viel Feingranulat-Steuerelemente. Sie konnten also nicht auswählen, eine Klasse mit privatem Zugriff zu erstellen und dann ein zweites Objekt desselben Typs zu erstellen, ohne dass die privaten Daten offengelegt werden. Ich bin diesen Weg mit der Stringkonvertierung gegangen, und es war eine Katastrophe, da einige Bibliotheken eine Richtlinie wählten und andere eine andere, was zu Inkompatibilitäten führte.

Ich habe einige Tests mit Array-Liste durchgeführt.

import jpype
import timeit
jpype.startJVM()
ArrayList = jpype.JClass("java.util.ArrayList")

def pack():
    ja = ArrayList()
    for i in range(1000):
        ja.add(i)

def iter(ja):
    u = 0
    for i in ja:
        u+=i

def access(ja):
    u = 0
    for i in range(len(ja)):
        u+=ja.get(i)

def access2(ja):
    u = 0
    for i in range(len(ja)):
        u+=ja[i]


ja = ArrayList()
for i in range(1000):
   ja.add(i)

print("Pack arraylist %e"%( timeit.timeit("pack()", globals=globals(), number=1000)/1e6))
print("Iterate arraylist %e"%(timeit.timeit("iter(ja)", globals=globals(), number=1000)/1e6))
# Get is a direct call
print("Access(get) arraylist %e"%(timeit.timeit("access(ja)", globals=globals(), number=1000)/1e6))
# [] is emulated
print("Access([]) arraylist %e"%(timeit.timeit("access2(ja)", globals=globals(), number=1000)/1e6))

JPype

Arrayliste packen 2.768904e-06
Arrayliste wiederholen 5.208071e-06
Access(get) arraylist 4.037985e-06
Access([]) Arrayliste 4.690264e-06

Jnius

Arrayliste packen 3.322248e-06
Arrayliste iterieren 4.099314e-06
Access(get) arraylist 5.653444e-06
Access([]) Arrayliste 7.762727e-06

Es ist keine sehr konsistente Geschichte, außer zu sagen, dass sie sich wahrscheinlich trivial unterscheiden, es sei denn, sie bieten sehr unterschiedliche Funktionen. Wenn Sie nur auf Methoden zugreifen, sind sie wahrscheinlich ziemlich ähnlich. Vorkonvertierte Arrays übergeben JPype ist 100 mal schneller, Konvertieren von Listen und Tupeln JPype ist 2 mal langsamer (wir verwenden derzeit keinen Vektorzugriff oder spezielle Umgehungen für Tupel). Das Endergebnis hängt also von Ihrem Codierungsstil ab, es kann mit dem einen oder anderen sehr schnell gehen. Aber dann wählen meine Benutzer normalerweise JPype aus Gründen der Benutzerfreundlichkeit und der kugelsicheren Konstruktion anstatt der Geschwindigkeit. (Ah, wen mache ich Witze! Sie stolpern genauso wahrscheinlich aus dem Internet, weil JPype zufällig der erste Link war, den sie bei Google gefunden haben.)

Wie JPype die Methodenbindung durchführt, sollte dieses Detail im Entwickler- / Benutzerhandbuch finden. Die Methodenbindung beginnt damit, dass die Liste der Methoden für den Versand gemäß den in der Java-Spezifikation angegebenen Regeln vorsortiert wird, sodass, wenn etwas etwas anderes verbirgt, es zuerst in der Liste erscheint (Code kann in native/java/org/jpype gefunden werden, wie wir Verwenden Sie eine Java-Dienstprogrammklasse, um die Sortierung durchzuführen, wenn der Dispatch zum ersten Mal erstellt wird). Außerdem wird jeder Methode eine Vorzugsliste gegeben, welche Methode eine andere verbirgt. Die Auflösung beginnt damit, dass zuerst jedes Argument überprüft wird, um zu sehen, ob es einen "Java-Slot" für das Argument gibt. Java-Slots verweisen auf vorhandene Objekte, die keine Konvertierung benötigen. Wenn Sie diese also vor dem Abgleich aus dem Weg räumen, können wir direkte Regeln anstelle von impliziten verwenden. Es ordnet dann Argumente basierend auf den Typen in 4 Ebenen (exakt, implizit, explizit, keine) zu. Es kürzt explizit und keine ab, um zur nächsten Methode zu springen. Wenn es jemals ein genaues Ergebnis erhält, kürzt es den gesamten Prozess ab, um zum Anruf zu springen. Wenn es eine Übereinstimmung gibt, werden die Methoden mit weniger spezifischer Bindung ausgeblendet. Wenn zwei implizite Übereinstimmungen gefunden werden, die nicht ausgeblendet wurden, geht es zu einem TypeError. Sobald alle Übereinstimmungen erschöpft sind, werden die Umwandlungsroutinen ausgeführt. Es gibt dann die globale Python-Sperre frei, führt den Aufruf durch und ruft die globale Sperre erneut ab. Der Rückgabetyp wird gesucht und ein neuer Python-Wrapper wird basierend auf dem zurückgegebenen Typ erstellt (er verwendet kovariante Rückgaben, sodass der zurückgegebene Typ der am meisten abgeleitete und nicht der Typ aus der Methode ist). Dies ist größtenteils linear mit der Anzahl der Überladungen, obwohl es einige Komplexitäten für variadische Argumente gibt, aber die vorkonstruierten Tabellen bedeuten, dass es foo(long, long) versuchen würde, bevor es foo(double, double) und einen Treffer auf (long .) versuchen würde , long) würde verhindern, dass double, double aufgrund der Auflösungsregeln für Java-Methoden gefunden wird. Es gibt noch einige Beschleunigungen, die ich implementieren kann, aber das würde zusätzliche Caching-Tabellen erfordern.

Ich habe das Bestellsystem mit Abkürzungen geerbt, als ich 2017 mit dem Projekt begann. Ich habe die Methode zum Verstecken von Cache und Java-Slots hinzugefügt, um den größten Teil unseres Overheads zu verdrängen.

Ich habe den Ausführungspfad für Methoden optimiert. Die überarbeiteten Zahlen für JPype sind

Arrayliste packen 2.226081e-06
Arrayliste iterieren 4.082152e-06
Access(get) arraylist 2.962606e-06
Access([]) Arrayliste 3.644642e-06

Meine lokale Benutzergruppe besteht aus Wissenschaftlern, daher sind die Einstiegspunkte alle sehr gut gegen eine ziemlich schreckliche Querverdrahtung geschützt. Wenn sie jemals einen Segfault haben (egal wie verrückt es war), dann habe ich versagt.

Ja, Segfaults sind schrecklich und ich habe Hunderte davon bekommen, als ich anfing, pyjnius zu verwenden. Ich habe jedoch schon lange keine mehr bekommen, weil ich vielleicht die Sicherheitsprobleme herausgearbeitet und in meinen Code eingebaut habe. Jetzt funktioniert alles zuverlässig. Ich verstehe deinen Anwendungsfall aber. Wenn Ihre Benutzer Wissenschaftler sind, die direkt mit den Java-Objekten arbeiten, um Datenanalysen mit verschiedenen Java-Bibliotheken durchzuführen, würde ein Segfault dazu führen, dass sie ihre gesamte Arbeit verlieren. JPype scheint besser für wissenschaftliche Arbeiten konzipiert zu sein, bei denen Endbenutzer direkt mit den Java-Objekten über Python arbeiten. Der Hauptanwendungsfall für pyjnius ist jedoch ein anderer, nämlich die Kommunikation mit Android. In diesem Fall sind die Sicherheitsprobleme das Problem des Entwicklers, daher sind möglicherweise unterschiedliche Entscheidungen bezüglich Sicherheit und Geschwindigkeit angemessen.

Ich gebe zu, dass ich kein großer Fan von "Es ist sicher, solange Sie diese Felder in dieser Reihenfolge betreten". Als ich anfing, an JPype zu arbeiten, brauchte ich fast ein Jahr, um alle Einstiegspunkte so kugelsicher zu machen, dass ich den Code an meine lokale Gruppe weitergeben konnte. Und ich habe seitdem zwei weitere Jahre API-Rüstung hinzugefügt. Abgesehen von einigen seltenen Leuten, deren JVM nicht geladen werden kann (was sehr schwer zu lösen ist), gibt es nur wenige verbleibende Probleme, da JPype auf Produktionscodestandards gebracht wurde.

Was Geschwindigkeit und Sicherheit angeht, ist Geschwindigkeit großartig, aber wenn Sie Geschwindigkeit erreichen, indem Sie bei sicheren Vorgängen sparen, ist dies normalerweise ein schlechter Kompromiss für die meisten Benutzer. Unabhängig davon, ob Sie Prototyping-Code umdrehen oder ein Produktionssystem schreiben, die Arbeit unterbrechen und versuchen zu müssen, einen Segfault zu umgehen, ist eine Ablenkung, mit der Benutzer nicht konfrontiert werden sollten.

Wenn mir jemand einige Beispiele zum Testen von JPype auf einem Android-Emulator geben möchte, kann ich die erforderlichen Änderungen vornehmen.

Um auf Android zu verwenden, verpacken wir pyjnius als Kunst einer Python-Distribution, die von python-for-android erstellt wurde (oft mit buildozer als einfachere Schnittstelle, aber es ist dasselbe) und bauen dann eine Python-Anwendung, die diese Distribution ausliefert. dann kann Ihr Python-Code pyjnius oder jede andere Python-Bibliothek importieren, die in der Python-Distribution erstellt wurde, wenn der Benutzer die App ausführt.

Der erste Schritt besteht also darin, jpype in einer Distribution kompilieren zu lassen, was von p4a (https://github.com/kivy/python-for-android/) erledigt wird, wenn Sie ihm sagen, dass es Teil Ihrer Anforderungen ist, normalerweise ( aber nicht immer) wird ein "Rezept" benötigt, um p4a zu erklären, wie man Bibliotheken erstellt, die nicht reines Python sind, das für pyjnius finden Sie hier https://github.com/kivy/python-for-android/blob/ Develop/pythonforandroid/recipes/pyjnius/__init__.py als Beispiel . Wenn Sie buildozer verwenden, können Sie die Einstellung p4a.local_recipes in buildozer.spec , um ein Verzeichnis zu deklarieren, in dem Rezepte für Anforderungen gefunden werden können lass dein Rezept verwenden.

Ich würde empfehlen, buildozer zu verwenden, da es mehr Dinge automatisiert https://buildozer.readthedocs.io/en/latest/installation.html#targeting -android und Sie können immer noch Ihre lokalen Rezepte einrichten, um Dinge auszuprobieren. Der erste Build wird einige Zeit in Anspruch nehmen, da Python und eine Reihe von Abhängigkeiten für arm erstellt werden müssen und dafür Android ndk und sdk heruntergeladen werden müssen. Sie können wahrscheinlich den standardmäßigen kivy-Bootstrap für die App verwenden und eine "Hallo Welt"-ähnliche App erstellen, die jpype importiert und nur das Ergebnis eines Codes in einem Etikett oder sogar im Logcat mit print anzeigt Erinnere dich daran, wie gut Kivy im Android-Emulator läuft, ich habe das nie verwendet, aber ich denke, einige Benutzer haben es getan, und mit Beschleunigungs-Setup sollte es funktionieren, ansonsten könntest du den sdl2-Wrapper oder den Webview-Wrapper verwenden und eine Flasche verwenden oder Flaschenserver, um Dinge anzuzeigen, würde ich zuerst mit dem Kivy-Server versuchen, da er mit Abstand am meisten getestet wurde.

Sie benötigen einen Linux- oder Osx-Computer (VM ist in Ordnung und WSL unter Windows 10 ist in Ordnung), um für Android zu erstellen.

Wenn Sie mit der Arbeit an einem jpype-Rezept für Python-für-Android beginnen, können Sie gerne eine laufende PR darüber für jede mögliche Diskussion öffnen. Es wäre großartig, wenn es funktionieren würde, insbesondere wenn es tatsächlich einige langjährige Pyjnius-Einschränkungen auflösen könnte. Wie bereits weiter oben in diesem Thread besprochen, deckt pyjnius im Wesentlichen unsere Kernanforderungen für die Verwendung von kivy ab, aber es hat nicht genug Entwicklungskraft, um deutlich darüber hinauszugehen.

@inclement Ich habe einen PR für den Android-Port unter jpype-project/jpype#799 eingerichtet. Leider bin ich mir nicht ganz sicher, wohin ich von hier aus gehen soll. Es scheint zu versuchen, Gradle auszuführen, was wirklich nicht der richtige Build-Pfad ist.

Folgende Maßnahmen sind erforderlich:

  • [x] Schließen Sie alle jpype/*.py-Dateien in den Build ein (oder vorkompilierte Versionen davon).
  • [x] Führen Sie Apache ant auf native/build.xml aus und platzieren Sie die resultierende JAR-Datei an einem Ort, an dem darauf zugegriffen werden kann.
  • [x] Fügen Sie die JAR-Datei (oder eine gleichwertige Datei) in den Build ein.
  • [x] Kompilieren Sie den C++-Code von native/common und native/python in ein Modul namens _jpype, das in den Build aufgenommen werden soll.
  • [x] Fügen Sie eine main.py-Datei hinzu, die nur eine interaktive Shell startet, damit wir dies vorerst manuell testen können.
  • [ ] In Zukunft muss ich "ASM" oder etwas Ähnliches für Android einbinden, damit ich dynamisch erstellte Klassen laden kann.
  • [x] Patchen Sie den C++-Code, sodass er einen benutzerdefinierten Bootstrap verwendet, um die JVM- und Begleit-JAR-Datei zu laden und alle nativen Methoden anzuschließen.
  • [ ] Patchen Sie den jvmfinder mit etwas, das auf Android funktioniert und "startJVM" wird automatisch und nicht beim Start von main aufgerufen.
  • [ ] Patchen Sie org.jpype, damit das Jar-Navigationssystem (so funktionieren Importe) auf Android funktionieren kann.

Ich habe einige der Dokumente durchgesehen, aber nichts war wirklich herausragend, wie man dies erreicht. Mein Projektlayout ist etwas anders als normal, da wir nicht alles unter das Hauptmodul stellen (da wir tatsächlich 3 Module konstruieren, aus denen das System besteht. jpype, _jpype und org.jpype.) Ich brauche wahrscheinlich ein benutzerdefiniertes Rezept, um Führen Sie alle diese Aktionen aus und deaktivieren Sie unerwünschte Muster wie das Ausführen von Gradle (es sei denn, es tut etwas Nützliches, das ich nicht sagen kann.)

Es scheint zu versuchen, Gradle auszuführen, was wirklich nicht der richtige Build-Pfad ist.

Gradle ist das Build-Tool, das als letzter Schritt beim Verpacken eines APK verwendet wird. Es hat wahrscheinlich nichts mit Ihrer Einbeziehung von jpype zu tun.

Schließen Sie alle jpype/*.py-Dateien in den Build ein (oder vorkompilierte Versionen davon).

Im Allgemeinen, wenn jpype im Wesentlichen wie ein normales Python-Modul funktioniert, dann erledigt Ihr erster Rezeptversuch dort wahrscheinlich die meiste Arbeit - das CppCompiledComponentsPythonRecipe macht so etwas wie python setup.py build_ext und python setup.py install die NDK-Umgebung verwenden. Dies sollte das jpype-Python-Paket in der Python-Umgebung installieren, die für die Aufnahme in die App erstellt wird.

Fügen Sie die JAR-Datei (oder eine gleichwertige Datei) in den Build ein.

Dies ist wahrscheinlich ein zusätzlicher Schritt, den das Rezept ausführen muss. Es geht darum, Ihre JAR-Datei (oder was auch immer Sie benötigen) an einen geeigneten Ort innerhalb des Android-Projekts zu kopieren, das Python-für-Android erstellt.

Kompilieren Sie den C++-Code aus native/common und native/python in ein Modul namens _jpype, das in den Build aufgenommen werden soll.

Wenn dies von setup.py übernommen wird, sollte dies bereits funktionieren, aber möglicherweise müssen einige Anpassungen vorgenommen werden. Wenn dies nicht der Fall ist, können Sie Ihre Kompilierungsbefehle in das Rezept aufnehmen (und sie für die Android-Umgebung erstellen lassen, indem Sie entsprechende env vars setzen, wie Sie es mit self.get_env in anderen Rezepten sehen werden).

Patchen Sie den C++-Code so, dass er einen benutzerdefinierten Bootstrap verwendet, um die JVM- und Begleit-JAR-Datei zu laden und alle nativen Methoden anzuschließen.

Ich hoffe, dieser Teil sollte ziemlich einfach sein, nur abhängig von der Verwendung der richtigen Android-JNI-Schnittstellenfunktion. Wir tun dies auf eine etwas hackige Art, indem wir pyjnius patchen, anstatt eine geeignete bedingte Kompilierung durchzuführen, da verschiedene Hilfsbibliotheken unterschiedliche Wrappres bereitstellen, wie zB dieser Patch demonstriert. Diese Komplexität müsste Sie nicht beeinträchtigen, Sie können einfach die richtige Android-API-Funktion aufrufen.

Patchen Sie den jvmfinder mit etwas, das auf Android funktioniert, und "startJVM" wird automatisch und nicht beim Start von main aufgerufen.

Ich bin mit der JVM nicht vertraut genug, um es wirklich zu wissen, aber ich denke, Android möchte, dass Sie immer auf eine vorhandene JVM zugreifen und keine neue Instanz starten können. Spielt das eine Rolle (oder ist es einfach falsch?)?

Führen Sie Apache ant auf native/build.xml aus und platzieren Sie die resultierende JAR-Datei an einem Ort, an dem darauf zugegriffen werden kann.

Ich bin mir aufgrund von Unbekanntheit auch nicht sicher, ist dies nur ein interner JPYPE-Build-Schritt? Ich bin mir nicht sicher, wie Java-Versionen interagieren, aber ich denke, die Verwendung der systemnativen Ameise sollte hier in Ordnung sein.

Es gab eine Warnung, dass der Buildozer setup.py nicht respektiert, sodass er direkt von oben zum Gradle-Schritt übersprungen wurde. Daher müssen diese mittleren Schritte manuell hinzugefügt werden. Unsere setup.py führt eine Reihe von benutzerdefinierten Kompilierungsschritten durch, z. B. das Erstellen der Hooks-Datei, um die JAR-Datei mit der DLL zusammenzuführen, die hier wahrscheinlich nicht anwendbar sind die in setup.py definiert sind. Ein Teil des Problems ist, dass meine setup.py so groß war, dass ich mich in das Modul setupext aufteilen musste.

Ich denke, zuerst muss ich herausfinden, wie ich einen sauberen Befehl ausführen kann, damit ich den Prozess einmal ausführen und das Protokoll anzeigen kann. (Ich habe es beim ersten Mal während des Spielens Stückmahlzeit laufen lassen. Daher habe ich kein sauberes Protokoll.) Außerdem möchten wir dieses Gespräch in die PR verschieben, damit wir für den Thread mehr beim Thema bleiben können.

FWIW Ich konnte den Kern meines Nicht-Android-Projekts auf JPype portieren. Es gab zwei kleinere Schwierigkeiten oder Unterschiede:

  1. Der classpath Kwarg in jpype.startJVM() scheint ignoriert zu werden, egal was ich tue. Die Funktion addClassPath() hat jedoch funktioniert. Entwickler, denen die Klassenpfadreihenfolge wichtig ist, müssen möglicherweise einige Anpassungen im Vergleich zur Verwendung von jnius_config.add_classpath() vornehmen.

  2. Die Implementierung von Java-Schnittstellen funktioniert großartig, ist aber ein bisschen anders. In meinem Code hatte ich eine Funktion, die eine Liste von Python-Strings zurückgab. Im Nachhinein hätte ich wahrscheinlich jeden String in einen Java-String umwandeln sollen, aber ich habe nicht daran gedacht und die Schnittstellenfunktionsdefinition eine Liste von Java-Objekten zurückgeben lassen, damit dies funktioniert. Das funktioniert in pyjnius gut, wodurch Python-Strings effektiv zu einer Unterklasse von Java-Objekten werden. Das funktioniert in JPype nicht, aber ich habe das leicht behoben, indem ich die Strings mit der JString Klasse von JPype konvertiert habe.

Das Schreiben von @JOverride für die implementierten Methoden ist viel einfacher als Code wie @java_method('(Ljava/lang/String;[Ljava/lang/Object;)V') .

Nachdem ich mich mit diesen beiden Problemen befasst hatte, funktionierte im Grunde alles gut. Ich habe einige Codes, die Byte-Arrays übergeben, die sich ändern müssen, aber JPype bietet gute Unterstützung dafür. Außerdem sind die impliziten Typkonvertierungen von JPype super praktisch und könnten viele der Dekorateure ersetzen, die ich überall verteilen musste.

@Thrameos Ich respektiere deine Entschlossenheit hier. Viel Glück, dass JPype auf Android funktioniert.

Danke für die Kommentare.

Ich bin mir jedoch nicht sicher, was im Klassenpfad falsch sein könnte, wenn ich erraten müsste, dass Sie Python von dort aus gestartet haben. Manchmal starten Leute die JVM von einem Modul aus und da die Regeln für Java-Pfade und Python-Pfade in Bezug auf das Startverzeichnis unterschiedlich sind, kann dies dazu führen, dass Jars übersehen werden. (Es gibt einen Abschnitt im Benutzerhandbuch zu diesem Problem).

Ich verwende die Classpath-Funktion täglich und sie ist Teil unserer Testmuster. Wenn Klassenpfade nicht respektiert würden, würde dies zu einigen ziemlich großen Fehlern führen. Das heißt, Sie wären nicht die ersten, die Schwierigkeiten haben, die magischen Orte und absoluten Pfade zu finden, die erforderlich sind, um ein komplexes Muster zum Laufen zu bringen.

Hier ist ein Beispiel, bei dem ich mein Testsystem aus einem Py-Verzeichnis relativ zum Entwicklungsbereich starte.

import jpype
import os

devel = os.path.dirname(__file__)
devel = os.path.join(devel, '..', '..')
devel = os.path.abspath(devel)  # Notice that I converted the path to absolute so that it doesn't matter where the
# PWD of Java will be when this script is called.   Otherwise, if I import this from a different location it will use the
# original PWD and Java will find nothing in the classpath.

classpath = [
    '%s/gov.llnl.math/dist/*' % devel,
    '%s/gov.llnl.rdak/dist/*' % devel,
    '%s/gov.llnl.rnak/dist/*' % devel,
    '%s/gov.llnl.rtk/dist/*' % devel,
    '%s/gov.llnl.rtk.gadras/dist/*' % devel,
    '%s/gov.llnl.rtk.response/dist/*' % devel,
    '%s/gov.llnl.utility/dist/*' % devel,
    ]

jpype.startJVM(classpath=classpath, convertStrings=False)

Relative Pfade und absolute Pfade funktionieren, aber dies hängt stark davon ab, wo Sie beginnen. Der addClassPath verfügt über eine spezielle Magie, um sicherzustellen, dass alle Pfade in Bezug auf den Standort des Aufrufers stehen. Ich denke, die gleiche Logik wird in den Schlüsselwortargumenten des Klassenpfads benötigt.

Ich kann das Problem mit der Rückgabe einer Liste von Zeichenfolgen sehen. Das Verhalten, das ausgelöst wird, hängt von der Art der Rückgabe ab. Wenn die Methode als Rückgabe von String[] deklariert wurde, würde ich erwarten, dass bei der Rückgabe jeder Python-String in ein Java-On gezwungen wird. Wenn die Methode als Rückgabe von List<String> deklariert wurde, würde ein Problem auftreten, da Java-Generika sie entfernen, sodass sie List<Object> . Je nachdem, wie JPype die Liste betrachtet, kann es versuchen, eine Konvertierung durchzuführen oder nicht, aber das sollte ein definiertes Verhalten sein, damit ich es überprüfen werde. Die sichere Lösung ist ein Listenverständnis, das alle Artikel für die Rücksendung umwandeln kann.

Ich bin mir jedoch nicht sicher, was im Klassenpfad falsch sein könnte, wenn ich erraten müsste, dass Sie Python von dort aus gestartet haben. Manchmal starten Leute die JVM von einem Modul aus

Das habe ich gemacht!

Ich verwende die Classpath-Funktion täglich und sie ist Teil unserer Testmuster. Wenn Klassenpfade nicht respektiert würden, würde dies zu einigen ziemlich großen Fehlern führen. Das heißt, Sie wären nicht die ersten, die Schwierigkeiten haben, die magischen Orte und absoluten Pfade zu finden, die erforderlich sind, um ein komplexes Muster zum Laufen zu bringen.

Ja, ich dachte mir, das würde einem sofort auffallen und ich war nicht der Erste, der dieses Problem hatte. Vielen Dank für das Codebeispiel, das ist hilfreich. Als Ergebnis habe ich einige Verbesserungen an meinem Code vorgenommen.

Die sichere Lösung ist ein Listenverständnis, das alle Artikel für die Rücksendung umwandeln kann.

Genau das habe ich gemacht und es funktioniert jetzt richtig. Vielen Dank!

Vor ein paar Jahren hatte ich einen Vergleich zwischen py4j und jnius gemacht und festgestellt, dass jnius viel schneller ist.

Wann immer ich jpype an Stellen erwähnt sah, nahm ich immer an, dass es sich auf http://jpype.sourceforge.net/ bezog, und hielt mich davon fern, weil es ungepflegt schien (habe das neue Projekt seltsamerweise nicht gesehen).

Beim Lesen dieses Threads habe ich meine Benchmarks erneut ausprobiert (mein primärer Testfall für Geschwindigkeit besteht darin, PMML-Dateien zu lesen und zu bewerten) - und fand, dass jpype ziemlich performant war.
Einige Ergebnisse:
https://gist.github.com/AbdealiJK/1dd5b7677435ba22f9ab3e26016bb3e7

# jpype
# createjvm: 0.550s
# loadmodel: tot=1.466451 max=1.064521s avg=0.014665s
# fields   : tot=0.019881 max=0.009795s avg=0.000199s
# score    : tot=0.033356 max=0.023338s avg=0.000334s

# jnius
# createjvm: 0.249s
# loadmodel: tot=1.773011 max=1.385274s avg=0.017730s
# fields   : tot=0.039058 max=0.012234s avg=0.000391s
# score    : tot=0.067590 max=0.031904s avg=0.000676s

# py4j
# createjvm: 0.222s
# loadmodel: tot=0.616913 max=0.027464s avg=0.006169s
# fields   : tot=0.699152 max=0.026426s avg=0.006992s
# score    : tot=0.389583 max=0.017620s avg=0.003896s

Um fair zu sein, hatte JPype seine Frontend-API bis März 2020 in Python (im Gegensatz zu CPython) geschrieben. Es gibt also viele ältere Benchmarks, die zeigen, dass es für das, was es bietet, eher langsam war. Es gab einfach so viele Backend-Probleme, die mit der Speicherverwaltung gelöst werden mussten, indem ganze Multilayer-Wrapper entfernt wurden, um Ruby, Multithreading und dergleichen zu unterstützen, dass die Geschwindigkeit das letzte war, woran gearbeitet werden musste.

An dieser Stelle sollte es ziemlich schnell gehen. Wenn Sie jedoch etwas finden, das im Vergleich zu anderen Paketen zusätzliche Arbeit erfordert, hinterlassen Sie einfach eine Notiz in den Problemen. Aufgrund von Rückgabeverträgen gibt es einige Einschränkungen, aber die primären Pfade für Aufrufe, Array-Transfers und Speicherpufferung werden an dieser Stelle ziemlich überarbeitet.

Ich erziele auch gute Ergebnisse mit JPype. Insbesondere bekomme ich einen guten Wert aus der Integration mit numpy Arrays. Ich konnte neue Funktionen hinzufügen, die mir vorher nicht möglich waren und die es mir ermöglicht haben, die Leistung auf sinnvolle Weise zu verbessern.

Wenn jemand so freundlich sein kann, zu versuchen, das kivy-remote-shell-Tool mit zu aktualisieren, um es mit dem aktuellen Python3 und Buildozer zu erstellen und die Anweisungen Schritt für Schritt zu verbessern, würde dies bei der JPype-Portierung helfen sehr. Ich habe alle erforderlichen Schritte bis zur Boot- und Testphase abgeschlossen. Aber ohne eine Umgebung, die den Bootstrapping-Prozess abschließt, wird es schwierig, Fortschritte zu machen. Ich kann dieses Wochenende erneut versuchen, das Remote-Shell-Tool zu aktualisieren (und vielleicht erfolgreich sein) oder eine Interessengruppe mit besseren Kenntnissen von Kivy kann diese erforderliche Aufgabe erledigen und dann kann ich das Wochenende damit verbringen, die technischen Arbeiten abzuschließen, für die ich am besten qualifiziert bin . Obwohl ich meine Zeit frei zur Verfügung stelle, um anderen zu helfen, ist es eine begrenzte Ressource, und jede Arbeit, die ich an Android-Portierungsbemühungen mache, ist eine Verzögerung der Python-von-Java-Brücke, an der auch eine Reihe anderer Leute interessiert sind.

Ich hoffe, dass die Android-Portierungsbemühungen vermeiden können, den Weg der PyPy-Portierungsbemühungen zu gehen, bei denen ich mehrere Wochen damit verbracht habe, den Kerncode zu überarbeiten, um mit den Unterschieden umgehen zu können, aber dann auf ein technisches Problem gestoßen bin, bei dem ein trivialer Unterschied im Objektsystem einen Fehler verursachte. und ich konnte niemanden finden, der mir helfen konnte, herauszufinden, wie man einen im generierten Code generierten Fehlerbericht debuggen kann. Obwohl ich nicht über verschüttete Milch weine und all diese Anstrengungen unternommen wurden, um den JPype-Code auf andere sinnvolle Weise zu verbessern, blieben die Benutzer, die JPype verwenden wollten, am Ende des Tages völlig trocken. Wenn dieser Versuch fehlschlägt, ist nicht alles verloren, da ich zurück zu ihm radle, aber sobald etwas am Ende der Warteschlange ist, habe ich es 6 Monate lang schwer, es wieder aufzunehmen, es sei denn, jemand kann mir die Zeit nehmen, um mir zu helfen .

Fortschrittsupdate für Interessierte.

Ich habe JPype erfolgreich dazu gebracht, von Pythonforandroid aus zu booten und konnte die grundlegende Funktionalität testen. Obwohl einige der erweiterten Funktionen aufgrund von Unterschieden in der JVM möglicherweise nicht möglich sind, glaube ich, dass die überwiegende Mehrheit von JPype für die Verwendung auf der Android-Plattform verfügbar sein wird. Die Portierung dauerte eine Weile, da einige Upgrades für die Buildozer- und Pythonforandroid-Projekte erforderlich waren (also viel durch die Quelle gelesen und um Hilfe gebeten). Vielen Dank an die Entwickler hier für ihre Reaktionsfähigkeit, damit ich den schwierigsten Teil des Prozesses an einem Wochenende abschließen konnte. Ohne Ihren Beitrag wäre das nicht möglich gewesen. Ich habe die entsprechenden Änderungen als PR eingefügt, aber wenn man sich den Rückstand bei PR ansieht, kann es eine Weile dauern, bis er in Betracht gezogen wird. Jetzt, da ich die wichtigsten technischen Spezifikationen habe, die ich benötige, sollte ich in der Lage sein, sie zu integrieren und einen funktionierenden Release-Code irgendwo um JPype ,1.2 nominell für den Spätherbst im Kalender zu haben. Ich kann es vorantreiben, wenn großes Benutzerinteresse besteht, aber es konkurriert mit Python aus Java, was für andere Projekte ein wichtiges Feature ist.

Wenn jemand helfen möchte, den Aufwand zu beschleunigen, wird der nächste harte Schritt darin bestehen, herauszufinden, wie man ein Docker-Image erstellt, bei dem alles vorhanden ist, mit einem teilweise erstellten System, damit wir einen Build ausführen, einen Emulator laden und den Teststand ausführen können des Androids auf azurblauen Pipelines (oder einem anderen CI-System). Sobald wir eine funktionierende CI haben, die erkennen kann, was funktioniert und was nicht, werden wir der Bereitstellung als stabile Software viel näher sein. Ich bin mir nicht sicher, ob dies im JPype-Projekt untergebracht werden soll oder ob wir ein separates Android-Testprojekt haben sollten.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen