Pyjnius: PyJnius مقابل JPype

تم إنشاؤها على ١٦ يوليو ٢٠٢٠  ·  27تعليقات  ·  مصدر: kivy/pyjnius

أنا المؤلف الرئيسي لـ JPype. كجزء من تحديث وثائق JPype ، أضفت PyJnius إلى قائمة الرموز البديلة لـ JPype. لسوء الحظ بعد ساعتين من اللعب مع PyJnius ، لم أتمكن من التوصل إلى أي شيء يشبه ميزة PyJnius على JPype. كل جانب نظرت إليه من البروكسيات ، والمخصصات ، ومعالجة المصفوفات متعددة الأبعاد ، وتكامل جافادوك ، ومعالجة GC ، والمخازن المؤقتة ، وتكامل الكود العلمي (numpy ، و matplotlib ، وما إلى ذلك) ، والأساليب الحساسة للمتصل ، والتوثيق ، وحتى سرعة التنفيذ في معظم الحالات مغطى حاليًا في JPype بشكل كامل أكثر من PyJnius. ومع ذلك ، لكوني مؤلف JPype ، فربما أتطلع إلى الجوانب التي أقدرها بدلاً من تلك الخاصة بفريق PyJnius. هل يمكنك توضيح مزايا هذا المشروع بشكل أفضل؟ ما هي القيمة المقترحة لهذا المشروع ، وما هو جمهوره المستهدف ، وما الذي يستهدفه أن تفعل البدائل التي لا تغطيها بالفعل؟

التعليق الأكثر فائدة

لقد تمكنت من الوصول إلى جميع رموز جسر Java منذ عامين تقريبًا. لسوء الحظ ، يبدو أن كود PyJnius قد فات لأنه لم يظهر في بحثي. كنت سأفتقدها في هذه الجولة أيضًا ، إلا أنني كنت أبحث عن الصفحة التي أصدرت البيان الصحفي التقني الجميل في المرة الأخيرة وتعثرت في مدونة تناقش المشروعين. لست متأكدا كيف فاتني مشروع آخر نشط في نفس المنطقة لمدة عامين ، ولكن من الواضح أنه كان خطأي.

يبدو أنك اختلطت على JPype مع Py4J وهو رمز الجسر الرئيسي الآخر. يفعلون كل شيء باستخدام مآخذ مع كل من الفوائد والسلبيات التي تنطوي عليها. وبالمثل وجدت أن المشروع لا يلبي متطلباتي.

لم أقم بأي بحث حول ما هو مطلوب لدعم android. على الرغم من أنه إذا كان لدي بعض المواصفات الفنية ، فسيكون ذلك ممكنًا. لا يوجد شيء نقوم به خارج نطاق JNI الأصلي والمكالمات القديمة البسيطة إلى واجهة برمجة تطبيقات C الخاصة ببايثون.

بقدر ما هو النهج ، يستخدم JPype JNI حصريًا للزواج من JVM إلى Python باستخدام الأمر "startJVM ()". على الرغم من أن الإصدار التالي (2.0) سيوفر أيضًا القدرة على القيام بالعكس حيث يمكن بدء Python من داخل Java. يقوم بذلك من خلال نهج الطبقات. توجد طبقة Python تحتوي على جميع الفئات عالية المستوى التي تعمل كواجهة أمامية ، ووحدة خاصة CPython مع فئات أساسية تحمل نقاط الدخول ، وطبقة C ++ Backer التي تتعامل مع جميع تحويلات النوع والمطابقة بالإضافة إلى العمل كوحدة نمطية أصلية لمكتبة Java ، ومكتبة Java التي تقوم بجميع مهام الأداة المساعدة (الاحتفاظ بأعمار الكائنات ، وإنشاء فئات دعم للشرائح والاستثناءات ، ومستخلصات / عرض javadoc).

قبل 8 سنوات ، كان JPype في حالة من الفوضى. كان يحاول دعم كل من Ruby و Python لذا كانت طبقة C ++ عبارة عن مجموعة متشابكة من الأغلفة للعمل معها وكانت الواجهة الأمامية كلها في Python لذا كانت بطيئة جدًا. تم تقسيمه أيضًا على أنواع الإرجاع حيث يمكن تجميع الدعم غير المترابط مما يؤدي إلى إرجاع كائنات مختلفة. لقد احتاج إلى الكثير من فئات المحولات مثل JException للعمل كوكلاء حيث يختلف كائن Python و Java الأصلي. ولكن تم حل كل هذه المشكلات في 3 سنوات منذ أن انضممت إلى المشروع. الهدفان الأساسيان (بالنسبة لي) في JPype هو توفير بناء جملة بسيط بما يكفي لعلماء الفيزياء على دراية بالبرمجة ليكونوا قادرين على استخدام Java والحصول على مستويات عالية من التكامل مع كود Python العلمي. نقوم بذلك عن طريق جعل تلك الكائنات التي تدعمها Java تحتوي على أغلفة كائنات CPython "كل الرتوش". بدلاً من تحويل مصفوفة Java بدائية ، نجعل مصفوفة Java بدائية نوعًا أصليًا جديدًا من Python ينفذ جميع نقاط الإدخال نفسها مثل numpy وكلها مدعومة بنقل ذاكرة التخزين المؤقت. وبالتالي لدينا تحويلات سريعة عن طريق استدعاء list(jarray) أو np.array(jarray) .

لتكييفه مع Android ، من المحتمل أن يحتاج تسلسل بدء التشغيل إلى إعادة صياغة وستحتاج إلى استبدال رمز thunk الذي يستخدمه لتحميل مكتبته الداخلية بنموذج JNI أكثر تقليدية. لقد قمت بالفعل بإزالة رمز thunk في الإصدار التالي ، لذلك تم بالفعل استيفاء الإصدار الأحدث. ستكون مطلوبة فقط الأولى.

الاختلافات الرئيسية في النهج التي يمكنني رؤيتها هي أن PyJnius يحول المصفوفات من وإلى. قد يبدو هذا مانعًا جدًا للتشفير العلمي حيث يكون تمرير المصفوفات الكبيرة للخلف للأمام (غالبًا لا يتم تحويلها أبدًا) هو أسلوب JPype المفضل. بعد ذلك ، يفرض قرار طلب التحويل خيارات مثل تمرير القيمة والتمرير حسب المرجع ، ولكن من المحتمل أن يؤدي ذلك إلى مشكلات أكبر كما لو كان لديك استدعاء متعدد الحجج ، فأنت تختار سياسة واحدة لجميع الحجج. كما أنه سيجعل التعامل مع المصفوفات متعددة الأبعاد أمرًا صعبًا. (أعتقد أن فئة المحول المستخدمة مثل obj.method(1, jnius.byref(list1), list2) ستوفر تحكمًا أفضل إذا كان من الممكن تحقيق ذلك). بالإضافة إلى ذلك ، هناك الكثير من المشكلات التي قام JPype بحلها مثل ربط GC ، والطرق الحساسة للمتصل ، وما إلى ذلك. إذا لم يكن هناك شيء آخر ، فيرجى إلقاء نظرة على كود JPype ومعرفة ما إذا كانت هناك أي أفكار جيدة يمكنك استخدامها.

ال 27 كومينتر

شكرا على رسالتك!

قد أخطئ في التذكر لأنه مرت سنوات لم ألق نظرة على jpype ، لكنني على الرغم من أنها كانت تستخدم نهجًا مختلفًا ، للتحدث إلى JVM من خلال خادم (لذا IPC) بدلاً من الذاكرة المشتركة (لكن التمهيدي الخاص بك يلمح إلى عكس ذلك ، وأنا لا أرى تلميحات تدحض ذلك من نظرة سريعة على الكود ، لذلك ربما أكون مخطئًا تمامًا) ، وهذا النهج جعله غير قابل للتطبيق على android ، على سبيل المثال (والذي كان نوعًا من السبب الرئيسي لتطوير Pyjnius ، على الرغم من بعض الناس يستخدمونه على منصات سطح المكتب). من ناحية أخرى ، لا أرى الكثير من التلميحات حول دعم android في قاعدة بيانات JPype البرمجية ، إلا إذا كان هذا هو ما يدور حوله الدليل native ، حيث يبدو أن jni.h مأخوذ من AOSP؟

لكن بصراحة ، لم نكن على دراية بـ JPype على الإطلاق عند بدء المشروع ، لقد كانت مجرد خطوة للأمام من الحاجة إلى كتابة كود jni يدويًا للواجهة مع فئات محددة من android لدعم kivy. أعتقد أن tito قام ببعض التنقيب للمقارنة لاحقًا ، عندما عرفنا ذلك ، لكنني لا أتذكر ما إذا كان قد رأى أسبابًا محددة لعدم محاولة التبديل.

أهلا. أتذكر اسم JPype ، ولكن في وقت البحث عما يمكن أن نستخدمه ، لا أتذكر لماذا لم يتم استخدامه على الإطلاق بأمانة ، منذ 8 سنوات :) كان الهدف الوحيد في البداية هو أن تكون قادرًا على التواصل مع Android API ، ولكن ليس باستخدام خادم RPC متوسط ​​كما كان يفعل مشروع P4A في ذلك الوقت.

لقد تمكنت من الوصول إلى جميع رموز جسر Java منذ عامين تقريبًا. لسوء الحظ ، يبدو أن كود PyJnius قد فات لأنه لم يظهر في بحثي. كنت سأفتقدها في هذه الجولة أيضًا ، إلا أنني كنت أبحث عن الصفحة التي أصدرت البيان الصحفي التقني الجميل في المرة الأخيرة وتعثرت في مدونة تناقش المشروعين. لست متأكدا كيف فاتني مشروع آخر نشط في نفس المنطقة لمدة عامين ، ولكن من الواضح أنه كان خطأي.

يبدو أنك اختلطت على JPype مع Py4J وهو رمز الجسر الرئيسي الآخر. يفعلون كل شيء باستخدام مآخذ مع كل من الفوائد والسلبيات التي تنطوي عليها. وبالمثل وجدت أن المشروع لا يلبي متطلباتي.

لم أقم بأي بحث حول ما هو مطلوب لدعم android. على الرغم من أنه إذا كان لدي بعض المواصفات الفنية ، فسيكون ذلك ممكنًا. لا يوجد شيء نقوم به خارج نطاق JNI الأصلي والمكالمات القديمة البسيطة إلى واجهة برمجة تطبيقات C الخاصة ببايثون.

بقدر ما هو النهج ، يستخدم JPype JNI حصريًا للزواج من JVM إلى Python باستخدام الأمر "startJVM ()". على الرغم من أن الإصدار التالي (2.0) سيوفر أيضًا القدرة على القيام بالعكس حيث يمكن بدء Python من داخل Java. يقوم بذلك من خلال نهج الطبقات. توجد طبقة Python تحتوي على جميع الفئات عالية المستوى التي تعمل كواجهة أمامية ، ووحدة خاصة CPython مع فئات أساسية تحمل نقاط الدخول ، وطبقة C ++ Backer التي تتعامل مع جميع تحويلات النوع والمطابقة بالإضافة إلى العمل كوحدة نمطية أصلية لمكتبة Java ، ومكتبة Java التي تقوم بجميع مهام الأداة المساعدة (الاحتفاظ بأعمار الكائنات ، وإنشاء فئات دعم للشرائح والاستثناءات ، ومستخلصات / عرض javadoc).

قبل 8 سنوات ، كان JPype في حالة من الفوضى. كان يحاول دعم كل من Ruby و Python لذا كانت طبقة C ++ عبارة عن مجموعة متشابكة من الأغلفة للعمل معها وكانت الواجهة الأمامية كلها في Python لذا كانت بطيئة جدًا. تم تقسيمه أيضًا على أنواع الإرجاع حيث يمكن تجميع الدعم غير المترابط مما يؤدي إلى إرجاع كائنات مختلفة. لقد احتاج إلى الكثير من فئات المحولات مثل JException للعمل كوكلاء حيث يختلف كائن Python و Java الأصلي. ولكن تم حل كل هذه المشكلات في 3 سنوات منذ أن انضممت إلى المشروع. الهدفان الأساسيان (بالنسبة لي) في JPype هو توفير بناء جملة بسيط بما يكفي لعلماء الفيزياء على دراية بالبرمجة ليكونوا قادرين على استخدام Java والحصول على مستويات عالية من التكامل مع كود Python العلمي. نقوم بذلك عن طريق جعل تلك الكائنات التي تدعمها Java تحتوي على أغلفة كائنات CPython "كل الرتوش". بدلاً من تحويل مصفوفة Java بدائية ، نجعل مصفوفة Java بدائية نوعًا أصليًا جديدًا من Python ينفذ جميع نقاط الإدخال نفسها مثل numpy وكلها مدعومة بنقل ذاكرة التخزين المؤقت. وبالتالي لدينا تحويلات سريعة عن طريق استدعاء list(jarray) أو np.array(jarray) .

لتكييفه مع Android ، من المحتمل أن يحتاج تسلسل بدء التشغيل إلى إعادة صياغة وستحتاج إلى استبدال رمز thunk الذي يستخدمه لتحميل مكتبته الداخلية بنموذج JNI أكثر تقليدية. لقد قمت بالفعل بإزالة رمز thunk في الإصدار التالي ، لذلك تم بالفعل استيفاء الإصدار الأحدث. ستكون مطلوبة فقط الأولى.

الاختلافات الرئيسية في النهج التي يمكنني رؤيتها هي أن PyJnius يحول المصفوفات من وإلى. قد يبدو هذا مانعًا جدًا للتشفير العلمي حيث يكون تمرير المصفوفات الكبيرة للخلف للأمام (غالبًا لا يتم تحويلها أبدًا) هو أسلوب JPype المفضل. بعد ذلك ، يفرض قرار طلب التحويل خيارات مثل تمرير القيمة والتمرير حسب المرجع ، ولكن من المحتمل أن يؤدي ذلك إلى مشكلات أكبر كما لو كان لديك استدعاء متعدد الحجج ، فأنت تختار سياسة واحدة لجميع الحجج. كما أنه سيجعل التعامل مع المصفوفات متعددة الأبعاد أمرًا صعبًا. (أعتقد أن فئة المحول المستخدمة مثل obj.method(1, jnius.byref(list1), list2) ستوفر تحكمًا أفضل إذا كان من الممكن تحقيق ذلك). بالإضافة إلى ذلك ، هناك الكثير من المشكلات التي قام JPype بحلها مثل ربط GC ، والطرق الحساسة للمتصل ، وما إلى ذلك. إذا لم يكن هناك شيء آخر ، فيرجى إلقاء نظرة على كود JPype ومعرفة ما إذا كانت هناك أي أفكار جيدة يمكنك استخدامها.

Thrameos أحد الأشياء الحديثة التي نتطلع إلى دمجها هو استخدام Python lambdas لواجهات Java الوظيفية. انظر https://github.com/kivy/pyjnius/pull/515 ؛ لم أر ذلك في JPype؟

يحتوي JPype على دعم لامبدا من واجهات وظيفية تبدأ من 1.0.0. كان جزءًا من 30 عملية سحب في 30 يومًا في مارس دفع إلى 1.0.

تتمتع JPype بفترة حضانة طويلة. بدأت في الأصل في عام 2004 وتستمر حتى عام 2007. ثم حصلت على دفعة كبيرة حيث أعادت مجموعة المستخدمين إحيائها لنقلها إلى Python 3 في حوالي عام 2015. ثم في عام 2017 ، تم التقاطها لاستخدامها في مختبر وطني حملها من 0.6. 3 إلى 0.7.2. خلال تلك الفترة ، تركزت جميع الجهود على تحسين وتقوية التكنولوجيا الأساسية التي توفر الواجهة. ولكن تم الانتهاء من ذلك أخيرًا في مارس بعد إعادة كتابة النواة الثانية حتى نتمكن أخيرًا من دفع 1.0.0. منذ ذلك الحين ونحن نضيف كل ما كان "مفقودًا" خلال حملة قائمة أمنياتي "30 عملية سحب في 30 ليلة". تم أخيرًا تصفية تراكم كل شيء لم أتمكن من تنفيذه لأنه سيكون كثيرًا من العمل (تم إسقاط المشكلات من 50 إلى 20 ، واطلب من المستخدمين السؤال عما يحتاجون إليه ، وما إلى ذلك). لذلك قد يكون هناك عدد من الميزات التي لم تجدها في الإصدارات السابقة المتوفرة الآن. لقد قمت بتحويل معظم طلبات الميزات على مدار أقل من أسبوع بحيث كل ما تبقى لدي هو الثلاثة الكبار (جسر عكسي ، تمديد الفصول في Python ، القدرة على بدء JVM ثاني).

سيعود المشروع إلى النوم حيث إنني أمضي شهرين في جهد مدته 6 أشهر لإكمال رمز الجسر العكسي الذي سيسمح لجافا باستدعاء Python وإنشاء أبتر لمكتبات Python بحيث يمكن استخدامها كمكتبات Java أصلية. يستخدم ASM لبناء فئات Java على الطاير بحيث يمكن تحقيق الدعم الأصلي لـ Python. لا يزال غير متكامل تمامًا مثل Jython ولكن ربما يكون قريبًا بدرجة كافية بحيث لا يكون هناك فرق كبير.

شكرًا جزيلاً على التفسيرات التفصيلية ، وفي الواقع ، إذا كان هناك أي شيء ، فبالتأكيد هناك أفكار يمكننا استخدامها ، وسيكون الأمر يستحق دراسة الكود ، والنظر إلى الكود مسبقًا ، ما رأيته يبدو نظيفًا تمامًا من حيث جودة الكود وهيكله المشروع ، فتهانينا على كل العمل. أنت محق بشأن الاختلاط الخاص بي مع Py4J ، أعتقد أنه عندما نظرت إلى JPype ، لا بد أنه كان في الحالة الفوضوية التي وصفتها ، ويجب أن يكون استخدامه أكثر تعقيدًا من PyJNIus في هذه المرحلة.

نقاطك حول تمرير القيمة / التحويل صحيحة جدًا ، وأثارت بعض المناقشات الأخيرة هنا أيضًا ، حيث بحث @ hx2A في كيفية تحسين الأداء ، وجعل التحويل مرة أخرى إلى أنواع python أمرًا اختياريًا (مثل تمرير قائمة رمزية إلى جافا ، والحصول عليها تم تحويلها إلى قائمة جافا ، تم تعديلها بواسطة جافا أم لا ، وتم تحويلها مرة أخرى إلى لغة python ، فقط لجمع القمامة ، كان بالتأكيد دون المستوى الأمثل ، يمكننا الآن على الأقل تجنب الجزء الثاني ، على حساب استخدام وسيطات الكلمات الرئيسية ، وهو أمر آمن منذ ذلك الحين جافا لا تدعمهم ، لذلك لا يوجد صدام توقيع ، لكنه بالتأكيد أكثر صخبًا في بناء الجملة).

بالنسبة للاختلاف المحتمل بين JPype و PyJNIus ، يمكننا تنفيذ واجهات جافا باستخدام فئات بايثون وتمريرها إلى جافا لاستخدامها كعمليات رد نداء ، من ناحية أخرى ، سنحتاج بالفعل إلى إنشاء كود java bytecode ، إذا أردنا توسيع فئات جافا من python ، نظرًا لأنه من الضروري استخدام بعض واجهات برمجة تطبيقات android ، والتي لا يمكننا تغطيتها في الوقت الحالي ، لست متأكدًا مما إذا كنت أستنتج بشكل صحيح من تعليقك ، ولكن ربما لا تكون لديك هذه القدرة على جعل فصول java تتصل بك كود بيثون مثل هذا (باستخدام الواجهات).

يمكن لـ JPype تنفيذ واجهات في Python. ما عليك سوى إضافة المصممين إلى صفوف بايثون العادية.

from java.util.function import Consumer

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

استخدم سلسلة في JImplements إذا تم تعريف الفئة قبل بدء تشغيل JVM ، يمكن تنفيذ واجهات متعددة في وقت واحد ولكن يتم فحص جميع الطرق لمعرفة ما إذا تم تنفيذها. يتمثل الاختلاف الرئيسي في أن JPype يستخدم طرق الإرسال (جميع الأحمال الزائدة تنتقل إلى نفس الطريقة) بدلاً من الطرق المحملة بشكل زائد. هذا لأننا نحب أن نكون قادرين على استدعاء الطرق من كل من Java و Python. يمكنني إضافة أحمال زائدة فردية إذا كانت هذه ميزة مرغوبة ، لكن لم يطلبها أحد.

(تعديل: السبب في عدم استخدامنا لميراث Python هو أنه في الوقت الذي تمت فيه إضافة هذا ، كنا لا نزال ندعم Python 2 مما تسبب في الكثير من مشكلات الطبقة الوصفية ، وسنقوم بالتنظيف مرة أخرى بمجرد وضع امتدادات الفصل في مكانها. التقييم مرة واحدة في وقت الإعلان كان أكثر نظافة.)

نحن نستخدم نفس النظام لتنفيذ أدوات التخصيص (dunder؟) للفئات

@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.
        ...

نستخدم أيضًا التعليقات التوضيحية لتحديد المحولات الضمنية مثل "تحويل جميع كائنات مسار Python إلى java.io.File"

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

من الواضح أن استخدام هذا النوع من المنطق أبطأ قليلاً من طرق C الخاصة ، لكنه يحافظ على قابلية القراءة والمرونة عالية. لقد كنت أقوم بدفع المسارات الحرجة التي أثبتت أنها اختناقات إلى C حسب الحاجة.

ونحن نقدم أيضا بعض السكر في بناء الجملة لجعل الامور نظيفة MyJavaClass@obj => المدلى بها لMyJavaClass (جافا يعادل (MyJavaClass)obj ) أو cls=JInt[:] => خلق نوع مصفوفة ( cls=int[].class ) أو a=JDouble[10][5] => أنشئ مصفوفة متعددة الوسائط ( double[][] a = new double[10][5] ).

لقد كنت أعمل على النموذج الأولي لتوسيع الفصول من JPype. لدينا نفس المشكلة التي تواجهها بعض فئات Swing وواجهات برمجة التطبيقات الأخرى التي تتطلب تمديد الفئات. الحل الذي قمت بإعداده حتى الآن هو إنشاء فئة موسعة باستخدام HashMap لكل من الطرق التي تم تجاوزها. إذا لم يكن هناك أي شيء في علامة التجزئة لنقطة الإدخال هذه ، فقم بتمريره إلى super ، وإلا فإنه يستدعي معالج طريقة الوكيل. لكنني قررت أن هذا سيكون أسهل في التنفيذ بعد اكتمال الجسر العكسي حتى تتمكن Java بالفعل من التعامل مع طرق Python بدلاً من المرور عبر طريقة وكيل Java. لذلك لا يزال أمامنا حوالي 6 أشهر قبل أن يعمل النموذج الأولي. يمكنك إلقاء نظرة على فرع epypj (اسمي للجسر العكسي) لترى كيف يعمل الاتصال من Java مرة أخرى إلى Python بالإضافة إلى أنماط إنشاء مدعو باستخدام ASM لإنشاء فئات Java أثناء التنقل.

بالنسبة لإدارة جمع البيانات المهملة ، هناك عدد قليل من قطع JPype التي تقوم بهذا العمل. الأول هو JPypeReferenceQueue (أصلي / java / org / jpype / ref / JPypeReferenceQueue) الذي يربط حياة كائن Python بكائن Java. يستخدم هذا لإنشاء مخازن مؤقتة وأشياء أخرى حيث تحتاج Java للوصول إلى مفهوم Python لمدة. والثاني هو استخدام المراجع العالمية حتى تتمكن بايثون من الاحتفاظ بجسم Java في النطاق. يتطلب هذا ارتباط جامع البيانات المهملة (أصلي / مشترك / jp_gc.cpp) الذي يستمع لأي نظام لتشغيل GC ويصغه إلى الآخر عند ظروف معينة (حجم التجمعات ، النمو النسبي). تحتاج الوكلاء الأخيرون إلى استخدام مراجع ضعيفة لأنها ستشكل حلقات (حيث يحتفظ الوكيل بإشارة إلى نصف Java ويعود نصف Java إلى تنفيذ Python). في النهاية ، أنوي استخدام عامل للسماح لـ Python باجتياز Java ولكن هذا في الطريق.

أنا أحد الأشخاص الذين يستخدمون Pyjnius على سطح المكتب وليس على Android. لم أكن أعرف شيئًا عن JPype عندما بدأت في إنشاء مشروعي ، لكنني أجريت بعض التحقيقات لمعرفة الاختلافات.

إحدى الميزات الفريدة في Pyjnius هي أنه يمكن للمتصل أن يقرر ما إذا كان سيتم تضمين الطرق والحقول المحمية والخاصة أم لا. أفضّل للجمهور فقط ، لكنني أفهم الحجة القائلة بأن إتاحة المجالات والأساليب غير العامة أمر مفيد.

الأداء أمر بالغ الأهمية لمشروعي. لقد أجريت بعض الاختبارات مع الفصل أدناه:

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;
  }
}

في JPype:

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)

في 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 أسرع قليلاً ، باستثناء طريقة التحميل الزائد. يجب أن نقارن الملاحظات حول كيفية تحديد الطريقة التي يجب الاتصال بها عندما تكون الطريقة محملة بشكل زائد. لدى Pyjnius آلية التسجيل هذه والتي يبدو أنها تضيف الكثير من النفقات العامة. يجعل JPype هذا القرار أسرع بكثير.

أخيرًا ، لأغراض القياس:

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)

بالطبع ، الاختلافات بين عدد قليل من الكلمات تافهة ، لكن هذا يتزايد إذا أجرى المرء آلاف المكالمات الصغيرة بسرعة كبيرة ، وهو ما أحتاج إلى القيام به.

يعد تكامل JPype مع numpy أمرًا رائعًا وسهل الاستخدام. أستطيع أن أرى كيف يمكن للباحثين استخدام هذا لكتابة نصوص تمرر مصفوفات كبيرة إلى مكتبات جافا بدون بناء جملة معقد. أحتاج أيضًا إلى تمرير مصفوفات كبيرة وأقوم بذلك باستخدام tobytes() ورمز Java خاص يمكنه استقبال وحدات البايت ، ولكن من الواضح أن هذا ليس أنيقًا أو ملائمًا.

لسوء الحظ ، فإن سرعة JPype هي نوع من الاحتمالات. إن JPype دفاعي للغاية في محاولة للحماية من الأشياء السيئة مما يعني أن هناك الكثير من النفقات العامة غير التافهة. هذا يعني على سبيل المثال عندما يتم إجراء مكالمة Java ، فإنه يتأكد من أن الخيط متصل حتى لا نقطع إذا تم استدعاؤه من مؤشر ترابط خارجي مثل IDE. مجموعة المستخدمين المحليين الخاصة بي كلها علماء ، لذا فإن جميع نقاط الدخول محمية للغاية ضد بعض الأسلاك المتقاطعة الرهيبة. إذا أصابهم شيء ما (بغض النظر عن مدى جنونه) ، فقد فشلت. (وهو ما يفسر 1500 اختبار بما في ذلك البناء المتعمد للأشياء السيئة.)

ثانيًا ، من المحتمل أن تختلف سرعة الإجراءات الفردية كثيرًا باختلافات صغيرة جدًا في العمل الذي يتم تنفيذه. المثال التافه الذي قدمته كان من أسوأ الحالات. اعتمادًا على نوع الحقل الذي يتم الوصول إليه ، قد يؤدي في الواقع إلى تغيير سرعة الوصول في بعض الحالات.

على سبيل المثال السرعة الخاصة بك ، أنت تطلب حقل كثافة العمليات.

  • في PyJnius ، يقوم بإنشاء واصف للبحث عن الكائن ، والوصول إلى الحقل ، وإنشاء Python جديدة طويلة ثم إعادتها.
  • في JPype ، يقوم بإنشاء واصف للبحث عن الكائن ، والوصول إلى الحقل ، وإنشاء Python جديدة طويلة ، ثم إنشاء نوع مجمّع لـ Java int ، ونسخ ذاكرة Python الطويلة إلى JInt (لأن Python تفتقر إلى طريقة لإنشاء مشتق فئة عدد صحيح مباشرة) ، ثم تربط الفتحة بقيمة Java ، وتعيد أخيرًا JInt الناتج.

لذا ، فحتى الشيء التافه مثل إجراء قياس السرعة عند الوصول إلى مجال ما ، ليس تافهًا حقًا.
الاختلاف هو أن أحدهما أعاد Python طويلًا والآخر أعاد عددًا صحيحًا من Java (يتبع قواعد تحويل Java) والذي يمكن تمريره إلى طريقة أخرى محملة بشكل زائد وربطها بشكل صحيح. إن العمل على إرجاع نوع الغلاف هو أكثر بكثير من مجرد إرجاع نوع Python ، وبالتالي هناك فرق كبير في السرعة.

حاولت إثبات ذلك من خلال اختبار بعض أنواع الحقول المختلفة. لسوء الحظ ، عندما اختبرت حقول الكائن ، عطل jnius على الكود "harness.objectField = harness". لست متأكدًا من سبب تسبب قطعة الأسلاك المتقاطعة هذه في حدوث مشكلة ، لكنها فشلت بالنسبة لي. لم أكن مهتمًا كثيرًا بسرعة JPype بخلاف إزالة المخالفين الجسيمين مما أعطى عاملًا من 3-5 سرعة للاتصال و 300 مرة لوصول مصفوفة معينة. لكن ربما ينبغي أن أراجع وأرى المجالات التي يمكن تحسينها. أشك في أنه يمكنني تقليله إلى العظام المجردة مثل PyJnius دون إزالة الأمان أو إزالة عقود الإرجاع (وهو ما لا يمكنني فعله). لا يزال من الممكن تسريع 10-30٪ على الأكثر ،

بالنسبة لميزة الوصول إلى الحقول الخاصة والمحمية ، فمن الممكن بالتأكيد القيام بذلك. أفضل أن يستخدم المستخدمون انعكاسًا أو طرق وصول داخلية أخرى بدلاً من تعريض الكائن مباشرةً. إذا اضطررت إلى تقديم شيء من هذا القبيل ، فمن المحتمل أن أقوم بإنشاء حقل يسمى _private والذي يحتوي على حقول لم يتم عرضها بشكل عام. يوفر JPype غلافًا واحدًا لفئة واحدة فقط لكل نوع ، لذلك ليس لدي الكثير فيما يتعلق بعناصر التحكم الدقيقة في الحبوب. لذلك لا يمكنك تحديد إنشاء فئة لها وصول خاص ثم إنشاء كائن ثان من نفس النوع ولا ينتهي بك الأمر مع الكشف عن العناصر الخاصة. ذهبت في هذا الطريق مع تحويل السلسلة ، وكانت كارثة مع اختيار بعض المكتبات لسياسة واحدة وأخرى مختلفة مما أدى إلى عدم التوافق.

أجريت بعض الاختبارات باستخدام قائمة المصفوفة.

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))

جايبي

حزمة arraylist 2.768904e-06
مصفوفة التكرار 5.208071e-06
الوصول (الحصول) Arraylist 4.037985e-06
الوصول ([]) arraylist 4.690264e-06

Jnius

حزمة arraylist 3.322248e-06
مكرر Arraylist 4.099314e-06
الوصول (الحصول) Arraylist 5.653444e-06
الوصول ([]) arraylist 7.762727e-06

إنها ليست قصة متسقة للغاية بخلاف القول بأنها ربما تكون مختلفة بشكل تافه إلا إذا كانت توفر وظائف مختلفة للغاية. إذا كان كل ما تفعله هو الوصول إلى الأساليب ، فمن المحتمل أن تكون متشابهة جدًا. المصفوفات الممررة التي تم تحويلها مسبقًا إلى JPype أسرع 100 مرة ، وتحويل القوائم و tuples JPype أبطأ مرتين (نحن لا نستخدم وصول المتجه أو لدينا تجاوزات خاصة لـ tuples حاليًا). لذا فإن المحصلة النهائية تعتمد على أسلوب الترميز الخاص بك ، فقد يكون سريعًا جدًا مع أحدهما أو الآخر. ولكن بعد ذلك ، يختار المستخدمون عادةً JPype لسهولة الاستخدام وبناء مقاوم للرصاص بدلاً من السرعة. (آه ، من أنا أمزح! من المحتمل أن يتعثروا خارج الإنترنت لأن JPype صادف أن يكون الرابط الأول الذي عثروا عليه على Google.)

بالنسبة لكيفية قيام JPype بربط الطريقة ، يجب أن تكون التفاصيل في دليل المطور / المستخدم. يبدأ ربط الطريقة من خلال الترتيب المسبق لقائمة طرق الإرسال وفقًا للقواعد الواردة في مواصفات Java بحيث إذا كان هناك شيء يخفي شيئًا آخر فإنه يظهر أولاً في القائمة (يمكن العثور على الكود في original / java / org / jpype كما نحن استخدم فئة Java المساعدة لإجراء الفرز عند إنشاء الإرسال في المرة الأولى). بالإضافة إلى ذلك ، يتم إعطاء كل طريقة قائمة تفضيلات للطريقة التي تخفي طريقة أخرى. يبدأ الحل أولاً بفحص كل وسيطة لمعرفة ما إذا كانت هناك "فتحة Java" للوسيطة. تشير فتحات Java إلى الكائنات الموجودة التي لا تحتاج إلى تحويل ، لذا فإن إخراجها من الطريق قبل المطابقة يعني أنه يمكننا استخدام القواعد المباشرة بدلاً من القواعد الضمنية. ثم يطابق الحجة القائمة على الأنواع في 4 مستويات (دقيق ، ضمني ، صريح ، بلا). إنها اختصارات صريحة ولا شيء للانتقال إلى الطريقة التالية. إذا كان الأمر دقيقًا ، فإنه يختصر العملية برمتها للانتقال إلى المكالمة. إذا كان هناك تطابق ، فسيخفي الطرق التي لها ارتباط أقل تحديدًا. إذا تم العثور على مطابقتين ضمنيتين لم يتم إخفاؤهما ، فسيتم الانتقال إلى TypeError. بمجرد استنفاد جميع المطابقات ، يتم تنفيذ إجراءات التحويل. يقوم بعد ذلك بتحرير قفل Python العام وإجراء المكالمة واستعادة القفل العام. يتم البحث عن نوع الإرجاع ويتم إنشاء غلاف Python جديد بناءً على النوع الذي تم إرجاعه (يستخدم إرجاع متغير بحيث يكون النوع الذي يتم إرجاعه هو الأكثر اشتقاقًا وليس النوع من الطريقة). يكون هذا في الغالب خطيًا مع عدد الأحمال الزائدة على الرغم من وجود بعض التعقيدات للحجج المتغيرة ، لكن الجداول سابقة الإنشاء تعني أنها ستحاول foo (طويلة ، طويلة) قبل أن تجرب foo (مزدوج ، مزدوج) وضربة (طويلة) ، long) من شأنه أن يمنع المضاعفة والمضاعفة من كل المطابقة بسبب قواعد دقة طريقة Java. لا يزال هناك عدد قليل من عمليات زيادة السرعة التي يمكنني تنفيذها ولكن ذلك سيتطلب جداول تخزين مؤقت إضافية.

لقد ورثت نظام الطلب باختصارات عندما بدأت في المشروع في عام 2017. أضفت طريقة إخفاء ذاكرة التخزين المؤقت وفتحات جافا لدفع معظم النفقات العامة.

لقد قمت بتحسين مسار التنفيذ للطرق. الأرقام المنقحة لـ JPype هي

حزمة Arraylist 2.226081e-06
مكرر Arraylist 4.082152e-06
الوصول (الحصول) على Arraylist 2.962606e-06
الوصول ([]) arraylist 3.644642e-06

مجموعة المستخدمين المحليين الخاصة بي كلها علماء ، لذا فإن جميع نقاط الدخول محمية للغاية ضد بعض الأسلاك المتقاطعة الرهيبة. إذا أصابهم شيء ما (بغض النظر عن مدى جنونه) ، فقد فشلت.

نعم ، segfaults مرعب وحصلت على المئات منهم عندما بدأت في استخدام Pyjnius. لم أحصل على أي شيء منذ وقت طويل لأنني ربما عملت على حل مشكلات الأمان وقمت بتضمينها في الكود الخاص بي. الآن كل شيء يعمل بشكل موثوق. أنا أفهم حالة الاستخدام الخاصة بك بالرغم من ذلك. إذا كان المستخدمون من العلماء الذين يعملون مع كائنات Java مباشرةً لإجراء تحليل البيانات مع مكتبات Java المختلفة ، فإن segfault قد يتسبب في فقدهم جميع أعمالهم. يبدو أن JPype مصمم بشكل أفضل للقيام بالعمل العلمي حيث يعمل المستخدمون النهائيون مباشرة مع كائنات Java من خلال Python. ومع ذلك ، فإن حالة الاستخدام الرئيسية لـ pyjnius مختلفة ، وهي التواصل مع Android. في هذه الحالة ، تكون مشكلات السلامة هي مشكلة المطور ، لذلك ربما يكون اتخاذ خيارات مختلفة بشأن السلامة مقابل السرعة أمرًا مناسبًا.

أعترف أنني لست من أشد المعجبين بـ "من الآمن أن تدوس على هذه المربعات بهذا الترتيب". عندما بدأت العمل على JPype ، استغرق الأمر ما يقرب من عام لإثبات جميع نقاط الدخول بما يكفي لتمرير الرمز إلى مجموعتي المحلية. وقد أضفت عامين إضافيين من درع API منذ ذلك الحين. بخلاف عدد قليل من الأشخاص النادرين الذين فشل تحميل JVM (وهو أمر يصعب حله) ، هناك عدد قليل من المشكلات المتبقية حيث تم ترقية JPype إلى معايير كود الإنتاج.

بالنسبة لمقايضة السرعة والأمان ، فإن السرعة رائعة ولكن إذا كنت تحصل على السرعة من خلال التقاعس عن العمليات الآمنة ، فعادة ما تكون صفقة سيئة بالنسبة لمعظم المستخدمين. سواء كنت تستدير حول رمز النماذج الأولية أو تكتب نظامًا للإنتاج ، فإن الاضطرار إلى التوقف عن العمل ومحاولة حل مشكلة Segfault يعد مصدر إلهاء يجب ألا يواجهه المستخدمون.

إذا كان شخص ما على استعداد لإعطائي بعض الأمثلة حول كيفية اختبار JPype على محاكي Android ، فيمكنني معرفة كيفية إجراء التعديلات المطلوبة.

لاستخدامها على android ، نقوم بتجميع pyjnius كفن لتوزيع python تم إنشاؤه بواسطة python-for-android ، (غالبًا ما نستخدم buildozer كواجهة أسهل له ، لكنه هو نفسه) ، ثم نبني تطبيق python يشحن هذا التوزيع ، عندئذٍ يمكن لرمز python الخاص بك استيراد pyjnius أو أي مكتبة Python أخرى تم إنشاؤها في توزيع python عندما يقوم المستخدم بتشغيل التطبيق.

وبالتالي ، فإن الخطوة الأولى هي تجميع jpype في توزيعة ، يتم إجراؤه بواسطة p4a (https://github.com/kivy/python-for-android/) عندما تخبره أنه جزء من متطلباتك ، عادةً ( ولكن ليس دائمًا) هناك حاجة إلى "وصفة" لشرح p4a كيفية إنشاء مكتبات ليست بيثون خالص ، ويمكن العثور على pyjnius هنا https://github.com/kivy/python-for-android/blob/ تطوير / pythonforandroid / recipes / pyjnius / __ init__.py كمثال. إذا كنت تستخدم buildozer ، فيمكنك استخدام الإعداد p4a.local_recipes في buildozer.spec للإعلان عن دليل يمكن العثور فيه على وصفات للمتطلبات ، لذلك لا تحتاج إلى تفرع python-for-android إلى تستخدم الوصفة الخاصة بك.

أنصح باستخدام buildozer ، لأنه يعمل على أتمتة المزيد من الأشياء https://buildozer.readthedocs.io/en/latest/installation.html#targeting -android ولا يزال بإمكانك إعداد وصفاتك المحلية لتجربة الأشياء. سيستغرق الإصدار الأول وقتًا ، حيث يحتاج إلى إنشاء Python وعدد من التبعيات لـ arm ، وسيحتاج إلى تنزيل android ndk و sdk من أجله. ربما يمكنك استخدام kivy bootstrap الافتراضي للتطبيق ، وإنشاء تطبيق مثل "hello world" ، والذي من شأنه أن يستورد jpype ويعرض فقط نتيجة بعض التعليمات البرمجية في ملصق ، أو حتى في logcat باستخدام الطباعة ، لا أفعل تذكر مدى جودة تشغيل kivy في محاكي android ، لم أستخدم ذلك مطلقًا ، لكنني أعتقد أن بعض المستخدمين فعلوا ذلك ، ومع إعداد التسريع ، يجب أن يعمل ، afaik ، وإلا يمكنك استخدام غلاف sdl2 ، أو webview one ، واستخدام قارورة أو خادم الزجاجة لعرض الأشياء ، سأحاول أولاً مع kivy لأنه الأكثر اختبارًا حتى الآن.

ستحتاج إلى جهاز يعمل بنظام التشغيل Linux أو osx (VM جيد ، و WSL على نظام التشغيل Windows 10 جيد) ، لإنشاء نظام Android.

إذا بدأت العمل على وصفة jpype لـ python-for-android ، فسيكون موضع ترحيب لفتح علاقات عامة قيد التقدم حول هذا الموضوع لأي مناقشة قد تطرأ. سيكون من الرائع أن ينجح الأمر ، خاصة إذا كان بإمكانه بالفعل حل بعض قيود Pyjnius طويلة الأمد. كما تمت مناقشته سابقًا في الموضوع ، فإن Pyjnius يغطي بشكل أساسي متطلباتنا الأساسية لاستخدام kivy ، ولكن ليس لديه ما يكفي من جاذبية التطوير لتجاوز ذلك بشكل كبير.

inclement قمت بإعداد PR لمنفذ android في jpype-project / jpype # 799. لسوء الحظ ، لست متأكدًا حقًا إلى أين أذهب من هنا. يبدو أنه يحاول تشغيل gradle وهو في الحقيقة ليس مسار البناء الصحيح.

الإجراءات التي يجب القيام بها هي كما يلي:

  • [x] قم بتضمين جميع ملفات jpype / *. py في الإنشاء (أو الإصدارات المجمعة مسبقًا منها).
  • [x] قم بتشغيل Apache ant على native / build.xml وضع ملف الجرة الناتج في مكان ما حيث يمكن الوصول إليه.
  • [x] قم بتضمين ملف الجرة (أو ما يعادله) في البناء.
  • [x] قم بترجمة كود C ++ من لغة البرمجة الأصلية / الشائعة والأصلية / python إلى وحدة نمطية تسمى _jpype ليتم تضمينها في الإنشاء.
  • [x] قم بتضمين ملف main.py والذي يقوم فقط بتشغيل غلاف تفاعلي حتى نتمكن من اختبار ذلك يدويًا في الوقت الحالي.
  • [] في المستقبل ، أحتاج إلى تضمين "ASM" أو أي شيء يعمل مثله لنظام Android حتى يمكنني تحميل الفئات التي تم إنشاؤها ديناميكيًا.
  • [x] قم بتصحيح كود C ++ بحيث يستخدم تمهيد مخصص لتحميل ملف JVM وملف الجرة المصاحب وربط جميع الطرق الأصلية.
  • [] قم بتثبيت jvmfinder بشيء يعمل على android ويتم استدعاء "startJVM" تلقائيًا بدلاً من بداية main.
  • [] Patch org.jpype بحيث يمكن أن يعمل نظام الملاحة jar (وهو كيفية عمل الواردات) على android.

لقد ألقيت نظرة على بعض المستندات ولكن لم يبرز شيء حقًا فيما يتعلق بكيفية إنجاز ذلك. يختلف تخطيط مشروعي إلى حد ما عن المعتاد لأننا لا نضع كل شيء تحت الوحدة الرئيسية (حيث إننا نقوم بالفعل ببناء 3 وحدات تشكل النظام. jpype و _jpype و org.jpype.) ربما أحتاج إلى وصفة مخصصة لـ قم بتنفيذ كل هذه الإجراءات بالإضافة إلى تعطيل الأنماط غير المرغوب فيها مثل تشغيل gradle (ما لم يكن يفعل شيئًا مفيدًا لا يمكنني تحديده.)

يبدو أنه يحاول تشغيل gradle وهو في الحقيقة ليس مسار البناء الصحيح.

Gradle هي أداة الإنشاء المستخدمة كخطوة أخيرة لتعبئة ملف APK ، وربما لا تتعلق بتضمينك لـ jpype.

قم بتضمين جميع ملفات jpype / *. py في الإنشاء (أو الإصدارات المترجمة مسبقًا منها).

بشكل عام ، إذا كان jpype يعمل بشكل أساسي كوحدة نمطية لبايثون عادية ، فمن المحتمل أن تؤدي محاولة الوصفة الأولى الخاصة بك هناك معظم الرفع الثقيل - يقوم CppCompiledComponentsPythonRecipe بشيء مثل تشغيل python setup.py build_ext و python setup.py install باستخدام بيئة NDK. يجب أن يقوم هذا بتثبيت حزمة jpype python داخل بيئة python التي يتم إنشاؤها لإدراجها داخل التطبيق.

قم بتضمين ملف الجرة (أو ما يعادله) في البناء.

ربما تكون هذه خطوة إضافية ستحتاج الوصفة إلى القيام بها ، وستنتقل إلى نسخ ملف الجرة (أو أي شيء تحتاجه) في مكان مناسب داخل مشروع Android الذي تقوم python-for-android ببنائه.

قم بتجميع كود C ++ من لغة أصلية / عامة وأصلية / بيثون إلى وحدة نمطية تسمى _jpype ليتم تضمينها في الإنشاء.

إذا تم التعامل مع هذا بواسطة setup.py ، فمن المفترض أن يعمل هذا بالفعل ، ولكن قد يحتاج إلى بعض التغيير والتبديل. إذا لم يكن الأمر كذلك ، فيمكنك تضمين أوامر الترجمة الخاصة بك في الوصفة (وجعلها تُنشئ لبيئة android عن طريق تعيين متغيرات بيئة مناسبة ، كما سترى تم استخدام self.get_env في وصفات أخرى).

قم بتصحيح كود C ++ بحيث يستخدم التمهيد المخصص لتحميل ملف JVM وملف الجرة المصاحب وربط جميع الطرق الأصلية.

آمل أن يكون هذا الجزء واضحًا إلى حد ما ، اعتمادًا فقط على استخدام وظيفة واجهة android JNI الصحيحة. نقوم بذلك بطريقة مخادعة قليلاً عن طريق ترقيع pyjnius بدلاً من القيام ببعض التجميع الشرطي المناسب ، حيث توفر مكتبات المساعدة المختلفة أغلفة مختلفة ، كما هو موضح في هذا التصحيح على سبيل المثال. لن يحتاج هذا التعقيد إلى التأثير عليك ، يمكنك فقط استدعاء وظيفة android api المناسبة.

قم بتثبيت jvmfinder بشيء يعمل على android ويتم استدعاء "startJVM" تلقائيًا بدلاً من بداية main.

لست على دراية كافية بـ JVM لأعرف حقًا ، لكنني أعتقد أن Android يريدك دائمًا الوصول إلى ملف jvm موجود ولا يمكنك بدء مثيل جديد. هل هذا مهم (أم أنه خطأ فقط؟)؟

قم بتشغيل Apache ant على native / build.xml وضع ملف الجرة الناتج في مكان ما حيث يمكن الوصول إليه.

لست متأكدًا أيضًا من هذا بسبب عدم الإلمام ، فهل هذه مجرد خطوة بناء jpype داخلية؟ لست متأكدًا من كيفية تفاعل إصدارات جافا ، ولكن أعتقد أن استخدام نملة النظام الأصلي يجب أن يكون جيدًا هنا.

لقد أعطت تحذيرًا أن buildozer لا يحترم setup.py لذا تم تخطيه مباشرة من الأعلى إلى خطوة التدرج. ومن هنا تأتي الحاجة إلى إضافة تلك الخطوات المتوسطة يدويًا. يقوم ملف setup.py الخاص بنا بمجموعة من خطوات التجميع المخصصة مثل إنشاء ملف الخطافات لدمج ملف jar مع dll والذي من المحتمل ألا يكون قابلاً للتطبيق هنا ، لذا كان من الجيد أنه تم تخطيه ، ولكنه فشل أيضًا في تحديد موقع ملفات cpp و java التي تم تحديدها في setup.py. جزء من المشكلة هو أن setup.py الخاص بي كان كبيرًا جدًا لدرجة أنني اضطررت إلى الانقسام إلى نص إعداد الوحدة النمطية.

أعتقد أولاً أنني بحاجة إلى معرفة كيف يمكنني تشغيل أمر نظيف حتى أتمكن من تشغيل العملية مرة واحدة وإظهار السجل. (قمت بتشغيلها في المرة الأولى عندما كنت ألعب. لذلك ليس لدي سجل نظيف.) كما نود نقل هذه المحادثة إلى العلاقات العامة حتى نتمكن من البقاء أكثر على موضوع الموضوع.

FWIW لقد تمكنت من نقل جوهر مشروعي الذي لا يعمل بنظام Android إلى JPype. كان هناك نوعان من الصعوبات أو الاختلافات البسيطة:

  1. يبدو أن classpath kwarg في jpype.startJVM() يتم تجاهله بغض النظر عما أفعله. على الرغم من ذلك ، تعمل وظيفة addClassPath() . قد يحتاج المطورون الذين يهتمون بترتيب مسار الفصل إلى إجراء بعض التعديلات مقارنة بكيفية استخدامهم jnius_config.add_classpath() .

  2. يعمل تطبيق Java Interfaces بشكل رائع ولكنه مختلف قليلاً. في الكود الخاص بي ، كان لدي وظيفة تعيد قائمة بسلاسل Python. في وقت لاحق ، ربما كان ينبغي عليّ تحويل كل سلسلة إلى سلسلة Java ، لكنني لم أفكر في ذلك وجعلت تعريف وظيفة الواجهة يعرض قائمة بكائنات Java لجعل هذا يعمل. يعمل هذا بشكل جيد في Pyjnius ، مما يجعل سلاسل Python فئة فرعية من كائنات Java. هذا لا يعمل في JPype ، لكنني أصلحت ذلك بسهولة عن طريق تحويل السلاسل باستخدام فئة JPype JString .

تعد كتابة @JOverride للطرق المنفذة طريقة أبسط من التعليمات البرمجية مثل @java_method('(Ljava/lang/String;[Ljava/lang/Object;)V') .

بمجرد أن تعاملت مع هاتين المسألتين ، كان كل شيء يعمل بشكل جيد. لدي بعض التعليمات البرمجية التي تمر عبر مصفوفات البايت التي سيتعين تغييرها ولكن JPype لديها دعم جيد لذلك. أيضًا ، تحويلات نوع JPype الضمنية مريحة للغاية ويمكن أن تحل محل الكثير من الزخارف التي كان علي رشها في كل مكان.

Thrameos أنا

شكرا على التعليقات.

لست متأكدًا مما يمكن أن يكون خطأ في Classpath على الرغم من أنني إذا اضطررت إلى تخمين أنه سيكون المكان الذي بدأت منه Python. في بعض الأحيان ، يبدأ الأشخاص تشغيل JVM من وحدة نمطية ولأن قواعد مسارات Java ومسارات Python فيما يتعلق بدليل البداية مختلفة ، فقد يتسبب ذلك في فقدان الجرار. (يوجد قسم في دليل المستخدمين بخصوص هذه المشكلة).

أستخدم ميزة classpath بشكل يومي وهي جزء من أنماط الاختبار لدينا. لذلك إذا لم يتم احترام مسارات الفصل ، فسيؤدي ذلك إلى حدوث بعض الإخفاقات الكبيرة جدًا. ومع ذلك ، لن تكون أول من يواجه صعوبات في العثور على المجموعة السحرية من المواقع والمسارات المطلقة المطلوبة للحصول على نمط معقد للعمل.

فيما يلي مثال على بدء تشغيل نظام الاختبار الخاص بي من دليل Py بالنسبة إلى منطقة التطوير.

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)

تعمل المسارات النسبية والمطلقة ، ولكن هذا يعتمد كثيرًا على المكان الذي تبدأ منه. يحتوي addClassPath على سحر خاص للتأكد من أن جميع المسارات تتعلق بموقع المتصل. أعتقد أن نفس المنطق مطلوب في وسيطات الكلمات الأساسية في classpath.

يمكنني رؤية مشكلة إعادة قائمة السلاسل. سيعتمد على نوع العائد بالنسبة للسلوك الذي يتم تشغيله. إذا تم الإعلان عن الطريقة كإرجاع String[] أتوقع فرض كل سلسلة Python في Java عند العودة. إذا تم التصريح عن الطريقة كإرجاع List<String> فستكون هناك مشكلة حيث تقوم تطبيقات Java Gener بإزالتها ، لذا ستكون List<Object> . اعتمادًا على كيفية عرض JPype للقائمة ، قد يحاول أو لا يحاول التحويل ولكن يجب تحديد هذا السلوك لذلك سأتحقق منه. الحل الآمن هو قائمة الفهم التي يجب أن تكون قادرة على تحويل جميع العناصر للإرجاع.

لست متأكدًا مما يمكن أن يكون خطأ في Classpath على الرغم من أنني إذا اضطررت إلى تخمين أنه سيكون المكان الذي بدأت منه Python. أحيانًا يبدأ الأشخاص في JVM من وحدة نمطية

هذا ما كنت أفعله!

أستخدم ميزة classpath بشكل يومي وهي جزء من أنماط الاختبار لدينا. لذلك إذا لم يتم احترام مسارات الفصل ، فسيؤدي ذلك إلى حدوث بعض الإخفاقات الكبيرة جدًا. ومع ذلك ، لن تكون أول من يواجه صعوبات في العثور على المجموعة السحرية من المواقع والمسارات المطلقة المطلوبة للحصول على نمط معقد للعمل.

نعم ، اعتقدت أن هذا شيء ستلاحظه على الفور وأنني لم أكن أول شخص يعاني من هذه المشكلة. شكرا لك على نموذج التعليمات البرمجية ، هذا مفيد. لقد أجريت بعض التحسينات على الكود الخاص بي نتيجة لذلك.

الحل الآمن هو قائمة الفهم التي يجب أن تكون قادرة على تحويل جميع العناصر للإرجاع.

هذا بالضبط ما فعلته وهو يعمل بشكل صحيح الآن. شكرا!

قبل بضع سنوات أجريت مقارنة بين py4j و jnius ووجدت أن jnius أسرع بكثير.

عندما رأيت jpype مذكورًا في بعض الأماكن ، افترضت دائمًا أنه كان يشير إلى - http://jpype.sourceforge.net/ وظل بعيدًا عنه لأنه بدا غير مستقر (لم أر المشروع الجديد بغرابة)

عند قراءة هذا الموضوع ، جربت المعايير الخاصة بي مرة أخرى (حقيبة الاختبار الأساسية الخاصة بي للسرعة هي قراءة ملفات PMML وتسجيلها) - ووجدت أن jpype كان أداءً جيدًا.
بعض النتائج:
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

لكي نكون منصفين ، كان لدى JPype واجهة برمجة تطبيقات للواجهة الأمامية مكتوبة بلغة Python (على عكس CPython) حتى مارس 2020. لذلك هناك الكثير من المعايير القديمة لإظهار أنها كانت بطيئة إلى حد ما بالنسبة لما قدمته. كان هناك ببساطة العديد من مشكلات الواجهة الخلفية التي كان لا بد من حلها من خلال إدارة الذاكرة ، وإزالة أغلفة متعددة الطبقات كاملة لدعم Ruby ، ​​وتعدد مؤشرات الترابط ، وما شابه ذلك ، وكانت هذه السرعة هي آخر شيء تم العمل عليه.

في هذه المرحلة يجب أن يكون سريعًا جدًا. ولكن إذا وجدت شيئًا يحتاج إلى عمل إضافي بالنسبة للحزم الأخرى ، فما عليك سوى ترك ملاحظة في المشكلات. بسبب عقود الإرجاع ، هناك بعض القيود ، ولكن المسارات الأولية للاتصال ، وعمليات نقل المصفوفة ، والتخزين المؤقت للذاكرة تعمل بشكل جيد في هذه المرحلة.

أنا أيضًا أحصل على نتائج جيدة مع JPype. على وجه الخصوص ، أحصل على قيمة جيدة من التكامل مع المصفوفات المعقدة. لقد تمكنت من إضافة ميزات جديدة لم أستطع فعلها من قبل والتي مكنتني من تحسين الأداء بطرق مفيدة.

إذا كان بإمكان أي شخص أن يتعامل بلطف مع محاولة تحديث أداة kivy-remote-shell من أجل الحصول عليها للبناء باستخدام Python3 و buildozer الحاليين وتحسين التعليمات لتكون خطوة بخطوة ، فسيساعد ذلك في جهد نقل JPype إلى حد كبير. لقد أكملت جميع الخطوات المطلوبة حتى مرحلة التمهيد والاختبار. ولكن بدون بيئة لإكمال عملية التمهيد ، سيكون من الصعب إحراز تقدم. يمكنني المحاولة مرة أخرى لتحديث أداة shell عن بُعد في نهاية هذا الأسبوع (وربما تنجح) أو يمكن لطرف مهتم يتمتع بمعرفة أفضل بـ kivy إكمال هذه المهمة المطلوبة مسبقًا وبعد ذلك يمكنني قضاء عطلة نهاية الأسبوع في إكمال العمل الفني الذي أنا الأكثر تأهيلًا لإكماله . على الرغم من أنني أقدم وقتي بحرية لمساعدة الآخرين ، إلا أنه موارد محدودة وأي عمل أقوم به في جهود نقل نظام android هو تأخير في Python من جسر Java الذي يهتم به عدد من الأشخاص الآخرين أيضًا.

آمل أن تتجنب جهود نقل android السير في طريق جهد نقل PyPy حيث أمضيت عدة أسابيع في إعادة صياغة الكود الأساسي حتى أتمكن من التعامل مع الاختلافات ولكن بعد ذلك واجهت مشكلة فنية حيث أدى الاختلاف البسيط في نظام الكائن إلى حدوث خطأ ، ولم أتمكن من العثور على أي شخص يمكنه مساعدتي في تتبع كيفية تصحيح تقرير خطأ تم إنشاؤه في التعليمات البرمجية التي تم إنشاؤها. على الرغم من أنني لا أبكي بشأن الحليب المسكوب وقد تم دمج كل هذا الجهد لتحسين كود JPype بطرق أخرى ذات مغزى ، إلا أنه في نهاية اليوم ، تم ترك هؤلاء المستخدمين الذين أرادوا استخدام JPype في مكان مرتفع وجاف. إذا فشل هذا الجهد ، فلن أضيع كل شيء لأنني سأعود إليه مرة أخرى ، ولكن بمجرد أن يكون هناك شيء ما في نهاية قائمة الانتظار ، فأنا مضغوط بشدة للعودة إليه لمدة 6 أشهر ما لم يكن شخص ما قادرًا على تخصيص الوقت المناسب لمساعدتي .

تحديث التقدم للأطراف المهتمة.

لقد نجحت في تشغيل JPype من داخل pythonforandroid وتمكنت من اختبار الوظائف الأساسية. على الرغم من أن بعض الميزات المتقدمة قد لا تكون ممكنة بسبب الاختلافات في JVM ، أعتقد أن الغالبية العظمى من JPype ستكون متاحة للاستخدام على نظام Android الأساسي. استغرق المنفذ بعض الوقت لأنه تطلب بعض الترقيات لمشاريع buildozer و pythonforandroid (وبالتالي الكثير من القراءة من خلال المصدر وطلب المساعدة). شكرًا جزيلاً للمطورين هنا على استجابتهم حتى أتمكن من إكمال الجزء الأصعب من العملية في عطلة نهاية أسبوع واحدة. لم يكن ممكنا بدون مدخلاتك. لقد وضعت التغييرات ذات الصلة في العلاقات العامة ولكن بالنظر إلى تراكم العلاقات العامة ، قد يستغرق الأمر بعض الوقت قبل أن يتم النظر فيه. الآن بعد أن حصلت على المواصفات الفنية الرئيسية التي أحتاجها ، يجب أن أكون قادرًا على دمجها ولديّ رمز إصدار عملي في مكان ما حول JPype ، 1.2 اسميًا في التقويم في أواخر الخريف. يمكنني دفعها إلى الأمام إذا كان هناك اهتمام كبير من المستخدم ولكنها تتنافس مع Python من Java وهي ميزة مهمة لمشاريع أخرى.

إذا كان شخص ما يرغب في المساعدة في تسريع الجهد ، فستكون الخطوة الصعبة التالية هي معرفة كيفية إنشاء صورة عامل إرساء مع وجود كل شيء في مكانه باستخدام نظام بناء جزئي حتى نتمكن من تنفيذ بناء وتحميل محاكي وتشغيل منصة الاختبار من android على خطوط الأنابيب اللازوردية (أو بعض أنظمة CI الأخرى). بمجرد أن يكون لدينا CI عاملاً يمكنه اكتشاف ما ينجح وما لا يصلح ، سنكون أقرب كثيرًا إلى القدرة على النشر كبرنامج مستقر. لست متأكدًا مما إذا كان يجب وضع هذا في مشروع JPype أو إذا كان يجب أن يكون لدينا مشروع اختبار android منفصل.

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات