Skip to content

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

@malsyned

Description

@malsyned

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions