Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4cecacb0 authored by Niklas Brunlid's avatar Niklas Brunlid Committed by Steve Kondik
Browse files

Increase javax.obex performance

When a file is transferred over OBEX the javax.obex classes allocate
the same amount of memory multiple times simply to copy the same data
around internally, only to throw those allocations away when the next
data block is processed. In the Bluetooth OPP case that meant that
64Kb was allocated around 15 times for every 64Kb transferred. Since
the transfer speed is around 1.3Mbps that means almost 2.5Mb of
memory is garbage collected every second, increasing the time needed
for each block transfer.
Fixed by keeping data in reusable byte buffer objects instead.
The time to transfer a 39MB file is reduced by about 10-20%.

Change-Id: I182c0374c2915d1b37ca12200fb36de57dabc67e
parent fcc13c17
Loading
Loading
Loading
Loading
+22 −24
Original line number Diff line number Diff line
@@ -37,7 +37,6 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.ByteArrayOutputStream;

/**
 * This class implements the <code>Operation</code> interface. It will read and
@@ -72,6 +71,8 @@ public final class ClientOperation implements Operation, BaseStream {

    private boolean mEndOfBodySent;

    private ObexByteBuffer mOutBuffer;

    /**
     * Creates new OperationImpl to read and write data to a server
     * @param maxSize the maximum packet size
@@ -100,6 +101,8 @@ public final class ClientOperation implements Operation, BaseStream {

        mRequestHeader = new HeaderSet();

        mOutBuffer = new ObexByteBuffer(32);

        int[] headerList = header.getHeaderList();

        if (headerList != null) {
@@ -396,7 +399,7 @@ public final class ClientOperation implements Operation, BaseStream {
     */
    private boolean sendRequest(int opCode) throws IOException {
        boolean returnValue = false;
        ByteArrayOutputStream out = new ByteArrayOutputStream();

        int bodyLength = -1;
        byte[] headerArray = ObexHelper.createHeader(mRequestHeader, true);
        if (mPrivateOutput != null) {
@@ -437,9 +440,9 @@ public final class ClientOperation implements Operation, BaseStream {
                    throw new IOException("OBEX Packet exceeds max packet size");
                }

                byte[] sendHeader = new byte[end - start];
                System.arraycopy(headerArray, start, sendHeader, 0, sendHeader.length);
                if (!mParent.sendRequest(opCode, sendHeader, mReplyHeader, mPrivateInput)) {
                mOutBuffer.reset();
                mOutBuffer.write(headerArray, start, end - start);
                if (!mParent.sendRequest(opCode, mOutBuffer, mReplyHeader, mPrivateInput)) {
                    return false;
                }

@@ -456,7 +459,8 @@ public final class ClientOperation implements Operation, BaseStream {
                return false;
            }
        } else {
            out.write(headerArray);
            mOutBuffer.reset();
            mOutBuffer.write(headerArray);
        }

        if (bodyLength > 0) {
@@ -471,8 +475,6 @@ public final class ClientOperation implements Operation, BaseStream {
                bodyLength = mMaxPacketSize - headerArray.length - 6;
            }

            byte[] body = mPrivateOutput.readBytes(bodyLength);

            /*
             * Since this is a put request if the final bit is set or
             * the output stream is closed we need to send the 0x49
@@ -480,44 +482,40 @@ public final class ClientOperation implements Operation, BaseStream {
             */
            if ((mPrivateOutput.isClosed()) && (!returnValue) && (!mEndOfBodySent)
                    && ((opCode & 0x80) != 0)) {
                out.write(0x49);
                mOutBuffer.write((byte)0x49);
                mEndOfBodySent = true;
            } else {
                out.write(0x48);
                mOutBuffer.write((byte)0x48);
            }

            bodyLength += 3;
            out.write((byte)(bodyLength >> 8));
            out.write((byte)bodyLength);

            if (body != null) {
                out.write(body);
            }
            mOutBuffer.write((byte)(bodyLength >> 8));
            mOutBuffer.write((byte)bodyLength);
            mPrivateOutput.writeTo(mOutBuffer, bodyLength - 3);
        }

        if (mPrivateOutputOpen && bodyLength <= 0 && !mEndOfBodySent) {
            // only 0x82 or 0x83 can send 0x49
            if ((opCode & 0x80) == 0) {
                out.write(0x48);
                mOutBuffer.write((byte)0x48);
            } else {
                out.write(0x49);
                mOutBuffer.write((byte)0x49);
                mEndOfBodySent = true;

            }

            bodyLength = 3;
            out.write((byte)(bodyLength >> 8));
            out.write((byte)bodyLength);
            mOutBuffer.write((byte)(bodyLength >> 8));
            mOutBuffer.write((byte)bodyLength);
        }

        if (out.size() == 0) {
        if (mOutBuffer.getLength() == 0) {
            if (!mParent.sendRequest(opCode, null, mReplyHeader, mPrivateInput)) {
                return false;
            }
            return returnValue;
        }
        if ((out.size() > 0)
                && (!mParent.sendRequest(opCode, out.toByteArray(), mReplyHeader, mPrivateInput))) {
        if ((mOutBuffer.getLength() > 0)
                && (!mParent.sendRequest(opCode, mOutBuffer, mReplyHeader, mPrivateInput))) {
            return false;
        }

+62 −49
Original line number Diff line number Diff line
@@ -32,7 +32,6 @@

package javax.obex;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -62,11 +61,23 @@ public final class ClientSession extends ObexSession {

    private final OutputStream mOutput;

    private ObexByteBuffer mOutBuffer;

    private ObexByteBuffer mData;

    private ObexByteBuffer mBodyBuffer;

    private ObexByteBuffer mHeaderBuffer;

    public ClientSession(final ObexTransport trans) throws IOException {
        mInput = trans.openInputStream();
        mOutput = trans.openOutputStream();
        mOpen = true;
        mRequestActive = false;
        mOutBuffer = new ObexByteBuffer(32);
        mData = new ObexByteBuffer(32);
        mBodyBuffer = new ObexByteBuffer(32);
        mHeaderBuffer = new ObexByteBuffer(32);
    }

    public HeaderSet connect(final HeaderSet header) throws IOException {
@@ -97,19 +108,20 @@ public final class ClientSession extends ObexSession {
        * Byte 5&6: Max OBEX Packet Length (Defined in MAX_PACKET_SIZE)
        * Byte 7 to n: headers
        */
        byte[] requestPacket = new byte[totalLength];
        ObexByteBuffer requestPacket = new ObexByteBuffer(totalLength);

        // We just need to start at  byte 3 since the sendRequest() method will
        // handle the length and 0x80.
        requestPacket[0] = (byte)0x10;
        requestPacket[1] = (byte)0x00;
        requestPacket[2] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT >> 8);
        requestPacket[3] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT & 0xFF);
        requestPacket.write((byte)0x10);
        requestPacket.write((byte)0x00);
        requestPacket.write((byte)(ObexHelper.MAX_PACKET_SIZE_INT >> 8));
        requestPacket.write((byte)(ObexHelper.MAX_PACKET_SIZE_INT & 0xFF));
        if (head != null) {
            System.arraycopy(head, 0, requestPacket, 4, head.length);
            requestPacket.write(head);
        }

        // check with local max packet size
        if ((requestPacket.length + 3) > ObexHelper.MAX_PACKET_SIZE_INT) {
        if ((totalLength + 3) > ObexHelper.MAX_PACKET_SIZE_INT) {
            throw new IOException("Packet size exceeds max packet size");
        }

@@ -214,8 +226,14 @@ public final class ClientSession extends ObexSession {
            }
        }

        ObexByteBuffer headBuffer = null;
        if (head != null) {
            headBuffer = new ObexByteBuffer(head.length);
            headBuffer.write(head);
        }

        HeaderSet returnHeaderSet = new HeaderSet();
        sendRequest(ObexHelper.OBEX_OPCODE_DISCONNECT, head, returnHeaderSet, null);
        sendRequest(ObexHelper.OBEX_OPCODE_DISCONNECT, headBuffer, returnHeaderSet, null);

        /*
         * An OBEX DISCONNECT reply from the server:
@@ -340,11 +358,11 @@ public final class ClientSession extends ObexSession {
         * Byte 5: constants
         * Byte 6 & up: headers
         */
        byte[] packet = new byte[totalLength];
        packet[0] = (byte)flags;
        packet[1] = (byte)0x00;
        ObexByteBuffer packet = new ObexByteBuffer(totalLength);
        packet.write((byte)flags);
        packet.write((byte)0x00);
        if (headset != null) {
            System.arraycopy(head, 0, packet, 2, head.length);
            packet.write(head);
        }

        HeaderSet returnHeaderSet = new HeaderSet();
@@ -405,31 +423,30 @@ public final class ClientSession extends ObexSession {
     *        <code>false</code> if an authentication response failed to pass
     * @throws IOException if an IO error occurs
     */
    public boolean sendRequest(int opCode, byte[] head, HeaderSet header,
    public boolean sendRequest(int opCode, ObexByteBuffer head, HeaderSet header,
            PrivateInputStream privateInput) throws IOException {
        //check header length with local max size
        if (head != null) {
            if ((head.length + 3) > ObexHelper.MAX_PACKET_SIZE_INT) {
            if ((head.getLength() + 3) > ObexHelper.MAX_PACKET_SIZE_INT) {
                throw new IOException("header too large ");
            }
        }

        int bytesReceived;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        out.write((byte)opCode);
        mOutBuffer.reset();
        mOutBuffer.write((byte)opCode);

        // Determine if there are any headers to send
        if (head == null) {
            out.write(0x00);
            out.write(0x03);
            mOutBuffer.write((byte)0x00);
            mOutBuffer.write((byte)0x03);
        } else {
            out.write((byte)((head.length + 3) >> 8));
            out.write((byte)(head.length + 3));
            out.write(head);
            mOutBuffer.write((byte)((head.getLength() + 3) >> 8));
            mOutBuffer.write((byte)(head.getLength() + 3));
            mOutBuffer.write(head, 0);
        }

        // Write the request to the output stream and flush the stream
        mOutput.write(out.toByteArray());
        mOutBuffer.peek(mOutput, mOutBuffer.getLength());
        mOutput.flush();

        header.responseCode = mInput.read();
@@ -440,7 +457,7 @@ public final class ClientSession extends ObexSession {
            throw new IOException("Packet received exceeds packet size limit");
        }
        if (length > ObexHelper.BASE_PACKET_LENGTH) {
            byte[] data = null;
            mData.reset();
            if (opCode == ObexHelper.OBEX_OPCODE_CONNECT) {
                @SuppressWarnings("unused")
                int version = mInput.read();
@@ -454,31 +471,21 @@ public final class ClientSession extends ObexSession {
                }

                if (length > 7) {
                    data = new byte[length - 7];

                    bytesReceived = mInput.read(data);
                    while (bytesReceived != (length - 7)) {
                        bytesReceived += mInput.read(data, bytesReceived, data.length
                                - bytesReceived);
                    }
                    mData.write(mInput, length - 7);
                } else {
                    return true;
                }
            } else {
                data = new byte[length - 3];
                bytesReceived = mInput.read(data);

                while (bytesReceived != (length - 3)) {
                    bytesReceived += mInput.read(data, bytesReceived, data.length - bytesReceived);
                }
                mData.write(mInput, length - 3);
                if (opCode == ObexHelper.OBEX_OPCODE_ABORT) {
                    return true;
                }
            }

            byte[] body = ObexHelper.updateHeaderSet(header, data);
            if ((privateInput != null) && (body != null)) {
                privateInput.writeBytes(body, 1);
            ObexHelper.updateHeaderSet(header, mData, mBodyBuffer, mHeaderBuffer);

            if ((privateInput != null) && (mBodyBuffer.getLength() > 0)) {
                privateInput.writeBytes(mBodyBuffer, 1);
            }

            if (header.mConnectionID != null) {
@@ -497,17 +504,23 @@ public final class ClientSession extends ObexSession {
                    && (header.mAuthChall != null)) {

                if (handleAuthChall(header)) {
                    out.write((byte)HeaderSet.AUTH_RESPONSE);
                    out.write((byte)((header.mAuthResp.length + 3) >> 8));
                    out.write((byte)(header.mAuthResp.length + 3));
                    out.write(header.mAuthResp);

                    // This can't be a member variable and mOutBuffer can't be used here
                    // since this is a recursive call.
                    // That's OK since authentication should not happen very often.
                    ObexByteBuffer sendHeadersBuffer = new ObexByteBuffer(
                            (mOutBuffer.getLength() - 3) + header.mAuthResp.length + 3);
                    sendHeadersBuffer.write(mOutBuffer, 3);

                    sendHeadersBuffer.write((byte)HeaderSet.AUTH_RESPONSE);
                    sendHeadersBuffer.write((byte)((header.mAuthResp.length + 3) >> 8));
                    sendHeadersBuffer.write((byte)(header.mAuthResp.length + 3));
                    sendHeadersBuffer.write(header.mAuthResp);

                    header.mAuthChall = null;
                    header.mAuthResp = null;

                    byte[] sendHeaders = new byte[out.size() - 3];
                    System.arraycopy(out.toByteArray(), 3, sendHeaders, 0, sendHeaders.length);

                    return sendRequest(opCode, sendHeaders, header, privateInput);
                    return sendRequest(opCode, sendHeadersBuffer, header, privateInput);
                }
            }
        }
+1 −1
Original line number Diff line number Diff line
@@ -126,7 +126,7 @@ public final class HeaderSet {
    /**
     * Represents the OBEX End of BODY header.
     * <P>
     * The value of <code>BODY</code> is 0x49 (73).
     * The value of <code>END_OF_BODY</code> is 0x49 (73).
     */
    public static final int END_OF_BODY = 0x49;

+326 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package javax.obex;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

class ObexByteBuffer {
    private static final int REALLOC_EXTRA_SPACE = 24;

    private byte[] mBuffer;

    private int mIndex;

    private int mLength;

    public ObexByteBuffer(int initialSize) {
        mBuffer = new byte[initialSize];
        mIndex = 0;
        mLength = 0;
    }

    /**
     * Mark bytes at beginning or valid data as invalid.
     * @param numBytes Number of bytes to consume.
     */
    private void consume(int numBytes) {
        mLength -= numBytes;
        if (mLength > 0) {
            mIndex += numBytes;
        } else {
            mIndex = 0;
        }
    }

    /**
     * Make room in for new data (if needed).
     * @param numBytes Number of bytes to make room for.
     */
    private void aquire(int numBytes) {
        int remainingSpace = mBuffer.length - (mIndex + mLength);

        // Do we need to grow or shuffle?
        if (remainingSpace < numBytes) {
            int availableSpace = mBuffer.length - mLength;
            if (availableSpace < numBytes) {
                // Need to grow. Add some extra space to avoid small growth.
                byte[] newbuf = new byte[mLength + numBytes + REALLOC_EXTRA_SPACE];
                System.arraycopy(mBuffer, mIndex, newbuf, 0, mLength);
                mBuffer = newbuf;
            } else {
                // Need to shuffle
                System.arraycopy(mBuffer, mIndex, mBuffer, 0, mLength);
            }
            mIndex = 0;
        }
    }

    /**
     * Get the internal byte array. Use with care.
     * @return the internal byte array
     */
    public byte[] getBytes() {
        return mBuffer;
    }

    /**
     * Get number of written but not consumed bytes.
     * @return number of bytes
     */
    public int getLength() {
        return mLength;
    }

    /**
     * Discard all unconsumed bytes.
     */
    public void reset() {
        mIndex = 0;
        mLength = 0;
    }

    /**
     * Read and consume one byte.
     * @return Next unconsumed byte.
     */
    public byte read() {
        if (mLength == 0) {
            throw new ArrayIndexOutOfBoundsException();
        }
        mLength--;
        return mBuffer[mIndex++];
    }

    /**
     * Read and consume bytes, and write them into a byte array.
     * Will read (dest.length - destOffset) bytes.
     * @param dest Array to copy data into.
     * @param destOffset Where to start writing in dest.
     * @return number of read bytes.
     */
    public int read(byte[] dest, int destOffset) {
        return read(dest, destOffset, mLength);
    }

    /**
     * Read and consume bytes, and write them into a byte array.
     * Will read (length - destOffset) bytes.
     * @param dest Array to copy data into.
     * @param destOffset Where to start writing in dest.
     * @param length Number of bytes to read.
     * @return number of read bytes.
     */
    public int read(byte[] dest, int destOffset, int length) {
        peek(0, dest, destOffset, length);
        consume(length);
        return length;
    }

    /**
     * Read and consume bytes, and write them into another ObexByteBuffer.
     * @param dest ObexByteBuffer to copy data into.
     * @param length Number of bytes to read.
     * @return number of read bytes.
     */
    public int read(ObexByteBuffer dest, int length) {
        if (length > mLength) {
            throw new ArrayIndexOutOfBoundsException();
        }
        dest.write(mBuffer, mIndex, length);
        consume(length);

        return length;
    }

    /**
     * Read and consume all unconsumed bytes, and write them into an OutputStream.
     * @param dest OutputStream to copy data into.
     * @return number of read bytes.
     */
    public int read(OutputStream stream) throws IOException {
        return read(stream, mLength);
    }

    /**
     * Read and consume bytes, and write them into an OutputStream.
     * @param dest OutputStream to copy data into.
     * @param length Number of bytes to read.
     * @return number of read bytes.
     */
    public int read(OutputStream destStream, int length) throws IOException {
        peek(destStream, length);
        consume(length);
        return length;
    }

    /**
     * Read (but don't consume) one byte.
     * @param offset Offset into unconsumed bytes.
     * @return Requested unconsumed byte.
     */
    public byte peek(int offset) {
        if (offset > mLength) {
            throw new ArrayIndexOutOfBoundsException();
        }
        return mBuffer[mIndex + offset];
    }

    /**
     * Read (but don't consume) bytes and write them into a byte array.
     * Will read dest.length bytes.
     * @param offset Offset into unconsumed bytes.
     * @param dest Array to copy data into.
     */
    public void peek(int offset, byte[] dest) {
        peek(offset, dest, 0, dest.length);
    }

    /**
     * Read (but don't consume) bytes and write them into a byte array.
     * Will read (length - destOffset) bytes.
     * @param offset Offset into unconsumed bytes.
     * @param dest Array to copy data into.
     * @param destOffset Where to start writing in dest.
     * @param length Number of bytes to read.
     */
    public void peek(int offset, byte[] dest, int destOffset, int length) {
        if (offset > mLength || (offset + length) > mLength) {
            throw new ArrayIndexOutOfBoundsException();
        }
        System.arraycopy(mBuffer, mIndex + offset, dest, destOffset, length);
    }

    /**
     * Read (but don't consume) bytes, and write them into an OutputStream.
     * @param dest OutputStream to copy data into.
     * @param length Number of bytes to read.
     */
    public void peek(OutputStream stream, int length) throws IOException {
        if (length > mLength) {
            throw new ArrayIndexOutOfBoundsException();
        }
        stream.write(mBuffer, mIndex, length);
    }

    /**
     * Write a new byte.
     * @param src Byte to write.
     */
    public void write(byte src) {
        aquire(1);
        mBuffer[mIndex + mLength] = src;
        mLength++;
    }

    /**
     * Read bytes from a byte array and add to unconsumed bytes.
     * Will read/write src.length bytes.
     * @param src Array to read from.
     */
    public void write(byte[] src) {
        write(src, 0, src.length);
    }

    /**
     * Read bytes from a byte array and add to unconsumed bytes.
     * Will read/write (src.length - srcOffset) bytes.
     * @param src Array to read from.
     * @param srcOffset Offset into source array.
     */
    public void write(byte[] src, int srcOffset) {
        write(src, srcOffset, src.length - srcOffset);
    }

    /**
     * Read bytes from a byte array and add to unconsumed bytes.
     * Will read/write (srcLength - srcOffset) bytes.
     * @param src Array to read from.
     * @param srcOffset Offset into source array.
     * @param srcLength Number of bytes to read/write.
     */
    public void write(byte[] src, int srcOffset, int srcLength) {
        // Make sure we have space.
        aquire(srcLength);

        // Add the new data at the end
        System.arraycopy(src, srcOffset, mBuffer, mIndex + mLength, srcLength);
        mLength += srcLength;
    }

    /**
     * Read bytes from another ObexByteBuffer and add to unconsumed bytes.
     * Will read/write src.getLength() bytes. The bytes in src will not be consumed.
     * @param src ObexByteBuffer to read from.
     * @param srcOffset Offset into source array.
     */
    public void write(ObexByteBuffer src) {
        write(src.mBuffer, 0, src.getLength());
    }

    /**
     * Read bytes from another ObexByteBuffer and add to unconsumed bytes.
     * Will read/write (src.getLength() - srcOffset) bytes. The bytes in src will not
     * be consumed.
     * @param src ObexByteBuffer to read from.
     * @param srcOffset Offset into source array.
     */
    public void write(ObexByteBuffer src, int srcOffset) {
        if (srcOffset > src.mLength) {
            throw new ArrayIndexOutOfBoundsException();
        }
        write(src.mBuffer, src.mIndex + srcOffset, src.mLength - src.mIndex - srcOffset);
    }

    /**
     * Read bytes from another ObexByteBuffer and add to unconsumed bytes.
     * Will read/write (srcLength - srcOffset) bytes. The bytes in src will not be
     * consumed.
     * @param src ObexByteBuffer to read from.
     * @param srcOffset Offset into source array.
     * @param srcLength Number of bytes to read/write.
     */
    public void write(ObexByteBuffer src, int srcOffset, int srcLength) {
        if (srcOffset > src.mLength || (srcOffset + srcLength) > src.mLength) {
            throw new ArrayIndexOutOfBoundsException();
        }
        write(src.mBuffer, src.mIndex + srcOffset, srcLength);
    }

    /**
     * Read bytes from an InputStream and add to unconsumed bytes.
     * @param src InputStream to read from
     * @param srcLength Number of bytes to read
     * @throws IOException
     */
    public void write(InputStream src, int srcLength) throws IOException {
        // First make sure we have space.
        aquire(srcLength);

        // Read data until the requested number of bytes have been read.
        int numBytes = 0;
        do {
            int readBytes = src.read(mBuffer, mIndex + mLength + numBytes, srcLength - numBytes);
            if (readBytes == -1) {
                throw new IOException();
            }
            numBytes += readBytes;
        } while (numBytes != srcLength);
        mLength += numBytes;
    }
}
+29 −29

File changed.

Preview size limit exceeded, changes collapsed.

Loading