Skip to content

Commit

Permalink
Merge pull request #542 from hx2A/master
Browse files Browse the repository at this point in the history
Improve performance of byte array parameters
  • Loading branch information
tshirtman authored Jun 13, 2020
2 parents 9f8774f + 6dea881 commit 3371e10
Show file tree
Hide file tree
Showing 5 changed files with 305 additions and 14 deletions.
40 changes: 38 additions & 2 deletions docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,15 @@ Reflection classes
Reflection functions
--------------------

.. function:: autoclass(name)
.. function:: autoclass(name, include_protected=True, include_private=True)

Return a :class:`JavaClass` that represents the class passed from `name`.
The name must be written in the format `a.b.c`, not `a/b/c`.

By default, autoclass will include all fields and methods at all levels of
the inheritance hierarchy. Use the `include_protected` and `include_private`
parameters to limit visibility.

>>> from jnius import autoclass
>>> autoclass('java.lang.System')
<class 'jnius.reflect.java.lang.System'>
Expand All @@ -185,6 +189,10 @@ Reflection functions
>>> autoclass('android.provider.Settings$Secure')
<class 'jnius.reflect.android.provider.Settings$Secure'>

.. note::
If a field and a method have the same name, the field will take
precedence.

.. note::
There are sometimes cases when a Java class contains a member that is
a Python keyword (such as `from`, `class`, etc). You will need to use
Expand Down Expand Up @@ -223,7 +231,7 @@ Java class implementation in Python
You need to define at minimum the :data:`__javainterfaces__` attribute, and
declare java methods with the :func:`java_method` decorator.

.. notes::
.. note::

Static methods and static fields are not supported.

Expand Down Expand Up @@ -371,6 +379,34 @@ steps::
2. $ cd platforms/android-xx/ # Replace xx with your android version
3. $ javap -s -classpath android.jar android.app.Activity # Replace android.app.Activity with any android class whose methods' signature you want to see

Passing Variables: By Reference or By Value
-------------------------------------------

When Python objects such as `lists` or `bytearrays` are passed to Java Functions, they are converted
to Java arrays. Since Python does not share the same memory space as the JVM, a copy of the data
needs to be made to pass the data.

Consider that the Java method might change values in the Java array. If the Java method had been
called from another Java method, the other Java method would see the value changes because the
parameters are passed by reference. The two methods share the same memory space. Only one copy of
the array data exists.

In Pyjnius, Python calls to Java methods simulate pass by reference by copying the variable values
from the JVM back to Python. This extra copying will have a performance impact for large data
structures. To skip the extra copy and pass by value, use the named parameter `pass_by_reference`.

obj.method(param1, param2, param3, pass_by_reference=False)

Since Java does not have function named parameters like Python does, they are interpreted by Pyjnius
and are not passed to the Java method.

In the above example, the `pass_by_reference` parameter will apply to all the parameters. For more
control you can pass a `list` or `tuple` instead.

obj.method(param1, param2, param3, pass_by_reference=(False, True, False))

If the passed `list` or `tuple` is too short, the final value in the series is used for the
remaining parameters.

JVM options and the class path
------------------------------
Expand Down
23 changes: 16 additions & 7 deletions jnius/jnius_conversion.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ cdef jstringy_arg(argtype):
'Ljava/lang/CharSequence;',
'Ljava/lang/Object;')

cdef void release_args(JNIEnv *j_env, tuple definition_args, jvalue *j_args, args) except *:
cdef void release_args(JNIEnv *j_env, tuple definition_args, pass_by_reference, jvalue *j_args, args) except *:
# do the conversion from a Python object to Java from a Java definition
cdef JavaObject jo
cdef JavaClass jc
cdef int index
cdef int last_pass_by_ref_index

last_pass_by_ref_index = len(pass_by_reference) - 1

for index, argtype in enumerate(definition_args):
py_arg = args[index]
if argtype[0] == 'L':
Expand All @@ -21,11 +25,12 @@ cdef void release_args(JNIEnv *j_env, tuple definition_args, jvalue *j_args, arg
jstringy_arg(argtype):
j_env[0].DeleteLocalRef(j_env, j_args[index].l)
elif argtype[0] == '[':
ret = convert_jarray_to_python(j_env, argtype[1:], j_args[index].l)
try:
args[index][:] = ret
except TypeError:
pass
if pass_by_reference[min(index, last_pass_by_ref_index)] and hasattr(args[index], '__setitem__'):
ret = convert_jarray_to_python(j_env, argtype[1:], j_args[index].l)
try:
args[index][:] = ret
except TypeError:
pass
j_env[0].DeleteLocalRef(j_env, j_args[index].l)

cdef void populate_args(JNIEnv *j_env, tuple definition_args, jvalue *j_args, args) except *:
Expand Down Expand Up @@ -533,6 +538,7 @@ cdef jobject convert_pyarray_to_java(JNIEnv *j_env, definition, pyarray) except
cdef unsigned char c_tmp
cdef jboolean j_boolean
cdef jbyte j_byte
cdef const_jbyte* j_bytes
cdef jchar j_char
cdef jshort j_short
cdef jint j_int
Expand All @@ -546,7 +552,6 @@ cdef jobject convert_pyarray_to_java(JNIEnv *j_env, definition, pyarray) except

cdef ByteArray a_bytes


if definition == 'Ljava/lang/Object;' and len(pyarray) > 0:
# then the method will accept any array type as param
# let's be as precise as we can
Expand Down Expand Up @@ -585,6 +590,10 @@ cdef jobject convert_pyarray_to_java(JNIEnv *j_env, definition, pyarray) except
a_bytes = pyarray
j_env[0].SetByteArrayRegion(j_env,
ret, 0, array_size, <const_jbyte *>a_bytes._buf)
elif isinstance(pyarray, (bytearray, bytes)):
j_bytes = <signed char *>pyarray
j_env[0].SetByteArrayRegion(j_env,
ret, 0, array_size, j_bytes)
else:
for i in range(array_size):
c_tmp = pyarray[i]
Expand Down
18 changes: 13 additions & 5 deletions jnius/jnius_export_class.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -360,12 +360,16 @@ cdef class JavaClass(object):
raise JavaException('Unable to found the constructor'
' for {0}'.format(self.__javaclass__))

# determine pass by reference choices
pass_by_reference = kwargs.get('pass_by_reference', True)
pass_by_reference = pass_by_reference if isinstance(pass_by_reference, (tuple, list)) else [pass_by_reference]

# create the object
j_self = j_env[0].NewObjectA(j_env, self.j_cls,
constructor, j_args)

# release our arguments
release_args(j_env, d_args, j_args, args_)
release_args(j_env, d_args, pass_by_reference, j_args, args_)

check_exception(j_env)
if j_self == NULL:
Expand Down Expand Up @@ -817,7 +821,7 @@ cdef class JavaMethod(object):
self.j_self = jc.j_self
return self

def __call__(self, *args):
def __call__(self, *args, **kwargs):
# argument array to pass to the method
cdef jvalue *j_args = NULL
cdef tuple d_args = self.definition_args
Expand All @@ -835,6 +839,10 @@ cdef class JavaMethod(object):
self.classname, self.name)
)

# determine pass by reference choices
pass_by_reference = kwargs.get('pass_by_reference', True)
pass_by_reference = pass_by_reference if isinstance(pass_by_reference, (tuple, list)) else [pass_by_reference]

if not self.is_static and j_env == NULL:
raise JavaException(
'Cannot call instance method on a un-instanciated class'
Expand All @@ -856,7 +864,7 @@ cdef class JavaMethod(object):
return self.call_staticmethod(j_env, j_args)
return self.call_method(j_env, j_args)
finally:
release_args(j_env, self.definition_args, j_args, args)
release_args(j_env, self.definition_args, pass_by_reference, j_args, args)

finally:
if j_args != NULL:
Expand Down Expand Up @@ -1100,7 +1108,7 @@ cdef class JavaMultipleMethod(object):
jm.set_resolve_info(j_env, j_cls, None, name, classname)
self.instance_methods[signature] = jm

def __call__(self, *args):
def __call__(self, *args, **kwargs):
# try to match our args to a signature
cdef JavaMethod jm
cdef list scores = []
Expand Down Expand Up @@ -1142,7 +1150,7 @@ cdef class JavaMultipleMethod(object):

jm = methods[signature]
jm.j_self = self.j_self
return jm.__call__(*args)
return jm.__call__(*args, **kwargs)


class JavaStaticMethod(JavaMethod):
Expand Down
47 changes: 47 additions & 0 deletions tests/java-src/org/jnius/VariablePassing.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.jnius;

public class VariablePassing {

public VariablePassing() {

}

public VariablePassing(int[] numbers) {
squareNumbers(numbers);
}

public VariablePassing(int[] numbers1, int[] numbers2, int[] numbers3, int[] numbers4) {
squareNumbers(numbers1);
squareNumbers(numbers2);
squareNumbers(numbers3);
squareNumbers(numbers4);
}

private static void squareNumbers(int[] numbers) {
for (int i = 0; i < numbers.length; i++) {
numbers[i] = i * i;
}
}

public static void singleParamStatic(int[] numbers) {
squareNumbers(numbers);
}

public static void multipleParamsStatic(int[] numbers1, int[] numbers2, int[] numbers3, int[] numbers4) {
squareNumbers(numbers1);
squareNumbers(numbers2);
squareNumbers(numbers3);
squareNumbers(numbers4);
}

public void singleParam(int[] numbers) {
squareNumbers(numbers);
}

public void multipleParams(int[] numbers1, int[] numbers2, int[] numbers3, int[] numbers4) {
squareNumbers(numbers1);
squareNumbers(numbers2);
squareNumbers(numbers3);
squareNumbers(numbers4);
}
}
Loading

0 comments on commit 3371e10

Please sign in to comment.