Most efficient way to call a function that takes (struct*)

113 views
Skip to first unread message

Kustaa Nyholm

unread,
Nov 4, 2012, 2:57:51 AM11/4/12
to jna-...@googlegroups.com

Hi,

now having a go at optimizing my PureJavaSerial port
library for RaspberryPI.

I have this in my C API :

static public class PollStruct extends Structure {
public int fd;
public short events;
public short revents;
}



public class ClibraryClass { // lots of detail omitted
public native int poll(PollStruct fds, int nfds, int timeout);



but I'm converting this to use just native types, so which
is faster:?

public native int poll(byte[] fds, int nfds, int timeout);
public native int poll(Pointer, int nfds, int timeout);






The actual C signature is

int poll(struct pollfd *fds, nfds_t nfds, int timeout);


where in my case nfds is always 1 so fds always points
to a single structure instance, which contents rarely
change.


My knee jerk reaction is to go with the Pointer, so I
would do something like this:


PollStruct struct = new PollStruct();

Pointer ptr = struct.getPointer();

while (true) {
if (struct has changed) // rare, I hope
struct.write();
clib.poll(ptr,1,timeout);
struct.read();
...handle things...
}

Should that work, but then again this is not much
different from passing a PollStruct directly
in the first place. So I guess this is not
the way to go for fastest possible execution.

And if the struct keeps changing I should
go with the poll(byte[]) approach, in which
case I would need handle the endian issues myself.

Are there public utility functions in JNA that can
be used to hand endian issues?


One More Thing:

Since the 'struct pollfd' in C is laid out like this:

int fd;
short events;
short revents;

and in my case 'fd' never changes, I only set 'events'
and get back 'revents', it would seem natural to define
the function signature in JNA as:

public native int poll(short[] fds, int nfds, int timeout);

but this probably forces JNA to handle endian issue of
'short[] fds' so this is potentially slower than just
using a byte array?

br Kusti










Timothy Wall

unread,
Nov 4, 2012, 5:48:01 AM11/4/12
to jna-...@googlegroups.com

On Nov 4, 2012, at 2:57 AM, Kustaa Nyholm wrote:

>
> Hi,
>
> now having a go at optimizing my PureJavaSerial port
> library for RaspberryPI.
>
> I have this in my C API :
>
> static public class PollStruct extends Structure {
> public int fd;
> public short events;
> public short revents;
> }
>
>
>
> public class ClibraryClass { // lots of detail omitted
> public native int poll(PollStruct fds, int nfds, int timeout);
>
>
>
> but I'm converting this to use just native types, so which
> is faster:?
>
> public native int poll(byte[] fds, int nfds, int timeout);
> public native int poll(Pointer, int nfds, int timeout);

As for the poll call itself, passing the pointer peer as primitive will be fastest:

public native int poll(int, int, int);
public native int poll(long, int, int);

However, if you also need to account for setting the FD_SET memory on each call,
the byte[] version will probably be faster. Again, back it up with real numbers.


>
> The actual C signature is
>
> int poll(struct pollfd *fds, nfds_t nfds, int timeout);
>
>
> where in my case nfds is always 1 so fds always points
> to a single structure instance, which contents rarely
> change.
>
>
> My knee jerk reaction is to go with the Pointer, so I
> would do something like this:
>
>
> PollStruct struct = new PollStruct();
>
> Pointer ptr = struct.getPointer();
>
> while (true) {
> if (struct has changed) // rare, I hope
> struct.write();
> clib.poll(ptr,1,timeout);
> struct.read();
> ...handle things...
> }
>
> Should that work, but then again this is not much
> different from passing a PollStruct directly
> in the first place. So I guess this is not
> the way to go for fastest possible execution.
>
> And if the struct keeps changing I should
> go with the poll(byte[]) approach, in which
> case I would need handle the endian issues myself.

int[] of size 2 would probably be more efficient that writing bytes.
then you just need to combine the two shorts.

>
> Are there public utility functions in JNA that can
> be used to hand endian issues?

If your struct is normally packed, you should be able to write
it as two 32-bit quantities, not endian issues involved.

>
>
> One More Thing:
>
> Since the 'struct pollfd' in C is laid out like this:
>
> int fd;
> short events;
> short revents;
>
> and in my case 'fd' never changes, I only set 'events'
> and get back 'revents', it would seem natural to define
> the function signature in JNA as:
>
> public native int poll(short[] fds, int nfds, int timeout);

Just use int[], with an array size double the number of expected FDs.
In most cases, the native code will end up writing revents directly into
primitive array memory, where you can read it. This will be faster
than a subsequent read from memory (which requires another JNI round-trip).

Note that in cases where you need to read only a single field from a Structure,
you can turn off auto-read and call Structure.readField(String name).

>
> but this probably forces JNA to handle endian issue of
> 'short[] fds' so this is potentially slower than just
> using a byte array?

I may be overlooking something, but I don't think endianness will be an issue.


>
> br Kusti
>

Kustaa Nyholm

unread,
Nov 10, 2012, 3:11:54 AM11/10/12
to jna-...@googlegroups.com
On 4.11.2012 12.48, "Timothy Wall" <twal...@java.net> wrote:

>>
>>but this probably forces JNA to handle endian issue of
>>'short[] fds' so this is potentially slower than just
>>using a byte array?
>
>I may be overlooking something, but I don't think endianness will be an
>issue.
>
>


Can we elaborate on the details a bit (pun intended)?

Java is big indian, so in Java 'short fds[]' would be
laid out like

fds[0]
fds[1]
fds[2]
fds[3]

now it this is overlaid with C struct

struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};


Now the Java fds[] is passed directly to C
or not?

If it is passed directly would not the
'events' have its hi/lo bytes in wrong
order as JVM stored the value using
bigend semantics but the C code will
use little endian semantics? And so on.

If on the otherhand JNA swaps the
hi/lo bytes of the shorts in Java fds[]
before passing them to C,
(as I guess it must as I can
use int and short everywhere
without regards to the endian issue)
then 'events' and 'revents' would be
ok but the hi and low 16 words of
'C struct pollfd.fd' would be in
wrong order? In other words it would
depend on the endianness of the
CPU weather Java short fds[0] is the high
word or low word the C int fd?

I'm confused and would be grateful for
some detailed explanation...

Related to that how do I find out
the endianness and sizeof(long)
in Java... JNA surely has
this info somewhere ... alright,
while waiting for answer to the
confusion I'll go and have a look
at the JNA documentation myself...;-)

cheers Kusti





Kustaa Nyholm

unread,
Nov 10, 2012, 4:00:23 AM11/10/12
to jna-...@googlegroups.com
On 10.11.2012 10.11, "Kustaa Nyholm" <Kustaa...@planmeca.com> wrote:

>On 4.11.2012 12.48, "Timothy Wall" <twal...@java.net> wrote:
>
>>>
>>>but this probably forces JNA to handle endian issue of
>>>'short[] fds' so this is potentially slower than just
>>>using a byte array?
>>
>>I may be overlooking something, but I don't think endianness will be an
>>issue.
>>
>>



While waiting for THE definite explanation I experiment
a bit (haha) on my Intel (little endian) Mac:

package purejavacomm.testsuite;

import com.sun.jna.Native;
import com.sun.jna.NativeLong;
import com.sun.jna.Pointer;

public class ExperimentWithJNAEndianness {

public static class CLib {
native public Pointer malloc();

native public long memcpy(byte[] dst, byte[] src, long n);

native public long memcpy(short[] dst, byte[] src, long n);

native public long memcpy(int[] dst, byte[] src, long n);

native public long memcpy(long[] dst, byte[] src, long n);

static {
Native.register("c");
}
}

static CLib C = new CLib();

public static void main(String[] args) {
byte[] src = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 };
byte[] bdst = new byte[8];
short[] sdst = new short[4];
int[] idst = new int[2];
long[] ldst = new long[1];
C.memcpy(bdst, src, 8);
C.memcpy(sdst, src, 8);
C.memcpy(idst, src, 8);
C.memcpy(ldst, src, 8);
for (int i = 0; i < 8; i++)
System.out.printf("0x%02X ", 0xFF & bdst[i]);
System.out.println();
for (int i = 0; i < 4; i++)
System.out.printf("0x%04X ", 0xFFFF & sdst[i]);
System.out.println();
for (int i = 0; i < 2; i++)
System.out.printf("0x%08X ", 0xFFFFFF & idst[i]);
System.out.println();
for (int i = 0; i < 1; i++)
System.out.printf("0x%016X ", 0xFFFFFFFF & ldst[i]);
System.out.println();

System.out.println("sizeof(long) "+NativeLong.SIZE);
}

}





this outputs:

0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07
0x0100 0x0302 0x0504 0x0706
0x00020100 0x00060504
0x0706050403020100
sizeof(long) 8


So, what's my conclusion?

Obviously finding out the size of long is trivial if
one bothers to look at JNA javadoc.

The C memcpy never understand anything but bytes
so it is just copying bytes verbatim without
regards to endianness. Now I've filled the
source array with bytes in the order so that
the lower values should be lower in memory.
Now a big endian CPU should fetch the data
as follows from the start of those 8 bytes:

16 bits => 0x0001
32 bits => 0x00010203
64 bites => 0x0001020304050607

as Java is bigendian this is what the
should have happened without JNA magic,
since this is NOT what happened my conclusion
is that JNA did the endian conversion here,
as is obvious in retrospect, otherwise
the Java to C mapping as performed by JNA
would not work if users would have to worry
about the endian issue.

So back to my original C struct:

struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};


If this is passed as four shorts I only need
to worry about the swapping of hi/lo 16 bits
of 'fd' field, right?

If I pass this as two ints I will have to
worry about the two short fields 'events'
and 'revents', right?

If I pass this as long that is most effiecient
right?

If I pass this as long I need to worry about
aligning the fields within the long according
to the endianness, right?

A few questions are raised by this experiment.

Obviously this sort of experiment can be used
to find out the endianness of the C interface,
is that the preferred way or JNA has a public
method for this?

I changed the length to memcpy from int to long and
both work, how come?

In C the type of length is 'size_t'
which I presume is 64 bit. Ok it actually depends
on the JVM mode, 32 bit or 64 bit but experimenting
with -d32 and -d64 shows that the Native.SIZE
changes but using long as the return value
and length to memcpy always works, now I
don't understand what is going on?

I would have expected a crash somewhere down the line...

I experimented with leaving out the return value
of memcpu from the signature and with int and
long as well, always works. Is it safe to ignore
the return value?

br Kusti




Timothy Wall

unread,
Nov 10, 2012, 6:55:34 AM11/10/12
to jna-...@googlegroups.com

On Nov 10, 2012, at 4:00 AM, Kustaa Nyholm wrote:

>>>
>>> I may be overlooking something, but I don't think endianness will be an
>>> issue.
>>>
>

I'm not sure where the JVM handles the conversion, but primitive arrays can *usually* be accessed directly by native code. The reasonable place to do the byte shuffling would probably be in the Java-side access, with the actual backing being stored in the native-endian format.

You *will* have to do ordering/packing of your own if you write values smaller than what is native in the struct, and you also have to be aware of padding, but it's fairly straightforward to write setters and getters to do the right thing. Some of these issues are what inspired Sam Audet to write special structure handling in his JNAerator project.

As for returning a 64-bit vs 32-bit value, the return value comes in a register, so on 64-bit it doesn't matter if you pick the wrong size. On 32-bit little endian, the lower portion is stored in the same place, so again, it doesn't matter if you pick the wrong size. If you were to pick the wrong size on a big-endian CPU you'd probably have issues.

As for the size of the last argument, the MS part of the 64-bit value just runs off the stack and is ignored (on 32-bit); you *might* get garbage on 64-bit passing a 32-bit value.

Kustaa Nyholm

unread,
Nov 10, 2012, 7:02:05 AM11/10/12
to jna-...@googlegroups.com
On 10.11.2012 13.55, "Timothy Wall" <twal...@java.net> wrote:

>
>On Nov 10, 2012, at 4:00 AM, Kustaa Nyholm wrote:
>
>>>>
>>>> I may be overlooking something, but I don't think endianness will be
>>>>an
>>>> issue.
>>>>
>>
>
>I'm not sure where the JVM handles the conversion, but primitive arrays
>can *usually* be accessed directly by native code. The reasonable place
>to do the byte shuffling would probably be in the Java-side access, with
>the actual backing being stored in the native-endian format.

Ah, so JVM being big endian is actually on illusion?

Makes kind of sense! Anyway, I was just being curious.

Thanks,

>
>You *will* have to do ordering/packing of your own if you write values
>smaller than what is native in the struct, and you also have to be aware
>of padding, but it's fairly straightforward to write setters and getters
>to do the right thing. Some of these issues are what inspired Sam Audet
>to write special structure handling in his JNAerator project.

Ok, I think I've got it all now.

>
>As for returning a 64-bit vs 32-bit value, the return value comes in a
>register, so on 64-bit it doesn't matter if you pick the wrong size. On
>32-bit little endian, the lower portion is stored in the same place, so
>again, it doesn't matter if you pick the wrong size. If you were to pick
>the wrong size on a big-endian CPU you'd probably have issues.
>
>As for the size of the last argument, the MS part of the 64-bit value
>just runs off the stack and is ignored (on 32-bit); you *might* get
>garbage on 64-bit passing a 32-bit value.

Ok, that explains all that and I need to mind my P's and Q's
when writing my signatures.


Thanks again.

br Kusti





Reply all
Reply to author
Forward
0 new messages