Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

High-level API introduces large and unpredictable performance penalty #5

Open
malsyned opened this issue Mar 21, 2015 · 1 comment
Open

Comments

@malsyned
Copy link

I'm using usb4java 1.2.0 with a full-speed USB device that I'm developing.

When using the low-level libusb API, I am able to achieve throughput speeds near the theoretical maximum for a full-speed bulk endpoint (1114.112 KB/s).

If I switch to the high-level javax.usb API, my throughput drops by an amount that appears to depend not just on the machine I'm using, but on the port I'm connected to. I always see performance penalties of at least 50%.

I've developed a simple benchmarking tool. The device side reads a 4-byte network-byte-order integer from an OUT endpoint, then transmits that many bytes of garbage on an IN endpoint.

The low-level and high-level usb4java benchmark code is inlined below.

On one port on my development machine, I get the following results:

org.usb4java API benchmark
Writing header
4 bytes sent
Reading 1048576 bytes
1048576 bytes received
1048576 bytes in 1021 ms, 1002.9382957884428 KB/s

javax.usb API benchmark
Writing header
4 bytes sent
Reading 1048576 bytes
1048576 bytes received
1048576 bytes in 2311 ms, 443.09822587624404 KB/s

On another port, I get these results:

org.usb4java API benchmark
Writing header
4 bytes sent
Reading 1048576 bytes
1048576 bytes received
1048576 bytes in 971 ms, 1054.5829042224511 KB/s

javax.usb API benchmark
Writing header
4 bytes sent
Reading 1048576 bytes
1048576 bytes received
1048576 bytes in 16538 ms, 61.91800701414923 KB/s

As you can see, the performance hit is 56% on one port, and 94%(!) on the other.

I have seen this performance penalty on Windows 7 64-bit and Ubuntu Linux 32-bit and 64-bit.

UsbBench.java:

import org.usb4java.*;
import java.nio.*;
import java.io.*;

public class UsbBench
{
    public static final short idVendor = (short)0x03eb;
    public static final short idProduct = (short)0x2423;
    public static final byte ifaceId = (byte)0x00;
    public static final byte outEp = (byte)0x02;
    public static final byte inEp = (byte)0x81;
    public static final int timeout = 60000;

    public static void main(String args[])
        throws Exception
    {
        int result;
        DeviceHandle handle;
        int size = Integer.parseInt(args[0]);

        ByteArrayOutputStream headerStream = new ByteArrayOutputStream();
        DataOutputStream out = new DataOutputStream(headerStream);
        out.writeInt(size);
        byte[] header = headerStream.toByteArray();

        result = LibUsb.init(null);
        if (result != LibUsb.SUCCESS) throw new LibUsbException("Unable to initialize libusb.", result);
        handle = findDevice(idVendor, idProduct);
        if (handle == null) throw new RuntimeException("Device not found");

        try {
            result = LibUsb.claimInterface(handle, ifaceId);
            if (result != LibUsb.SUCCESS) throw new LibUsbException("Unable to claim interface", result);

            ByteBuffer buffer = ByteBuffer.allocateDirect(header.length);
            buffer.put(header);
            IntBuffer transfered = IntBuffer.allocate(1);

            System.out.printf("Writing header\n");
            result = LibUsb.bulkTransfer(handle, outEp, buffer, transfered, timeout); 
            if (result != LibUsb.SUCCESS) throw new LibUsbException("Bulk transfer failed", result);
            System.out.printf("%d bytes sent\n", transfered.get());

            buffer = ByteBuffer.allocateDirect(size);
            transfered = IntBuffer.allocate(1);

            System.out.printf("Reading %d bytes\n", size);
            long start = System.currentTimeMillis();
            result = LibUsb.bulkTransfer(handle, inEp, buffer, transfered, timeout); 
            long time = System.currentTimeMillis() - start;
            if (result != LibUsb.SUCCESS) throw new LibUsbException("Bulk transfer failed", result);
            int rx = transfered.get();
            System.out.printf("%d bytes received\n", rx);

            System.out.printf("%s bytes in %s ms, %s KB/s\n",
                              rx, time, (rx / (time / 1000.0)) / 1024.0);
        }
        finally
        {
            LibUsb.releaseInterface(handle, ifaceId);
            LibUsb.close(handle);
            LibUsb.exit(null);
        }
    }

    private static DeviceHandle findDevice(short vendorId, short productId)
    {
        // Read the USB device list
        DeviceList list = new DeviceList();
        int result = LibUsb.getDeviceList(null, list);
        if (result < 0) throw new LibUsbException("Unable to get device list", result);

        try
        {
            // Iterate over all devices and scan for the right one
            for (Device device: list)
            {
                DeviceDescriptor descriptor = new DeviceDescriptor();
                result = LibUsb.getDeviceDescriptor(device, descriptor);
                if (result != LibUsb.SUCCESS) throw new LibUsbException("Unable to read device descriptor", result);
                if (descriptor.idVendor() == vendorId && descriptor.idProduct() == productId)
                {
                    DeviceHandle handle = new DeviceHandle();
                    result = LibUsb.open(device, handle);
                    if (result != LibUsb.SUCCESS) throw new LibUsbException("Unable to open USB device", result);
                    return handle;
                }
            }
        }
        finally
        {
            // Ensure the allocated device list is freed
            LibUsb.freeDeviceList(list, true);
        }

        // Device not found
        return null;
    }
}

UsbBenchJavax:

import javax.usb.*;
import java.io.*;
import java.util.*;

public class UsbBenchJavax
{
    public static final short idVendor = (short)0x03eb;
    public static final short idProduct = (short)0x2423;
    public static final byte ifaceId = (byte)0x00;
    public static final byte outEp = (byte)0x02;
    public static final byte inEp = (byte)0x81;

    public static void main(String[] args)
        throws Exception
    {
        int size = Integer.parseInt(args[0]);

        ByteArrayOutputStream headerStream = new ByteArrayOutputStream();
        DataOutputStream out = new DataOutputStream(headerStream);
        out.writeInt(size);
        byte[] header = headerStream.toByteArray();

        UsbServices services = UsbHostManager.getUsbServices();
        UsbDevice usbdev = findDevice(services.getRootUsbHub(),
                                      idVendor, idProduct);
        UsbConfiguration config = usbdev.getActiveUsbConfiguration();
        UsbInterface iface = config.getUsbInterface(ifaceId);
        UsbPipe inPipe = iface.getUsbEndpoint(inEp).getUsbPipe();
        UsbPipe outPipe = iface.getUsbEndpoint(outEp).getUsbPipe();
        iface.claim();
        inPipe.open();
        outPipe.open();

        System.out.printf("Writing header\n");
        int tx = outPipe.syncSubmit(header);
        System.out.printf("%d bytes sent\n", tx);

        byte[] buffer = new byte[size];
        System.out.printf("Reading %d bytes\n", size);
        long start = System.currentTimeMillis();
        int rx = inPipe.syncSubmit(buffer);
        long time = System.currentTimeMillis() - start;
        System.out.printf("%d bytes received\n", rx);

        System.out.printf("%s bytes in %s ms, %s KB/s\n",
                          rx, time, (rx / (time / 1000.0)) / 1024.0);
    }

    @SuppressWarnings("unchecked")
    private static UsbDevice findDevice(UsbHub hub,
                                        short idVendor, short idProduct)
        throws UsbException
    {
        for (UsbDevice device : (List<UsbDevice>) hub.getAttachedUsbDevices())
        {
            UsbDeviceDescriptor desc = device.getUsbDeviceDescriptor();
            if (desc.idVendor() == idVendor && desc.idProduct() == idProduct)
                return device;

            if (device.isUsbHub())
            {
                device = findDevice((UsbHub) device, idVendor, idProduct);
                if (device != null) return device;
            }
        }
        return null;
    }
}
@kayahr
Copy link
Member

kayahr commented Oct 21, 2018

Most likely I misunderstood the JSR-80 spec and implemented the data transfer not ideally. Or maybe I did it correctly and the JSR-80 spec sucks... I'm open for pull requests here because of lack of time and know-how.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants