Optimize ParsableBitArray

ParsableBitArray.readBit in particular was doing an excessive
amount of work. The new implementation is ~20% faster on desktop.

Issue: #3040

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=161666420
This commit is contained in:
olly 2017-07-12 08:12:11 -07:00 committed by Oliver Woodman
parent 2b1614cc7b
commit 01c0ccbdbd
2 changed files with 167 additions and 45 deletions

View File

@ -0,0 +1,139 @@
/*
* Copyright (C) 2017 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 com.google.android.exoplayer2.util;
import android.test.MoreAsserts;
import junit.framework.TestCase;
/**
* Tests for {@link ParsableBitArray}.
*/
public final class ParsableBitArrayTest extends TestCase {
private static final byte[] TEST_DATA = new byte[] {0x3C, (byte) 0xD2, (byte) 0x5F, (byte) 0x01,
(byte) 0xFF, (byte) 0x14, (byte) 0x60, (byte) 0x99};
public void testReadAllBytes() {
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
byte[] bytesRead = new byte[TEST_DATA.length];
testArray.readBytes(bytesRead, 0, TEST_DATA.length);
MoreAsserts.assertEquals(TEST_DATA, bytesRead);
assertEquals(TEST_DATA.length * 8, testArray.getPosition());
assertEquals(TEST_DATA.length, testArray.getBytePosition());
}
public void testReadBit() {
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
assertReadBitsToEnd(0, testArray);
}
public void testReadBits() {
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
assertEquals(getTestDataBits(0, 5), testArray.readBits(5));
assertEquals(getTestDataBits(5, 3), testArray.readBits(3));
assertEquals(getTestDataBits(8, 16), testArray.readBits(16));
assertEquals(getTestDataBits(24, 3), testArray.readBits(3));
assertEquals(getTestDataBits(27, 18), testArray.readBits(18));
assertEquals(getTestDataBits(45, 5), testArray.readBits(5));
assertEquals(getTestDataBits(50, 14), testArray.readBits(14));
}
public void testRead32BitsByteAligned() {
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
assertEquals(getTestDataBits(0, 32), testArray.readBits(32));
assertEquals(getTestDataBits(32, 32), testArray.readBits(32));
}
public void testRead32BitsNonByteAligned() {
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
assertEquals(getTestDataBits(0, 5), testArray.readBits(5));
assertEquals(getTestDataBits(5, 32), testArray.readBits(32));
}
public void testSkipBytes() {
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
testArray.skipBytes(2);
assertReadBitsToEnd(16, testArray);
}
public void testSkipBitsByteAligned() {
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
testArray.skipBits(16);
assertReadBitsToEnd(16, testArray);
}
public void testSkipBitsNonByteAligned() {
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
testArray.skipBits(5);
assertReadBitsToEnd(5, testArray);
}
public void testSetPositionByteAligned() {
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
testArray.setPosition(16);
assertReadBitsToEnd(16, testArray);
}
public void testSetPositionNonByteAligned() {
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
testArray.setPosition(5);
assertReadBitsToEnd(5, testArray);
}
public void testByteAlignFromNonByteAligned() {
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
testArray.setPosition(11);
testArray.byteAlign();
assertEquals(2, testArray.getBytePosition());
assertEquals(16, testArray.getPosition());
assertReadBitsToEnd(16, testArray);
}
public void testByteAlignFromByteAligned() {
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
testArray.setPosition(16);
testArray.byteAlign(); // Should be a no-op.
assertEquals(2, testArray.getBytePosition());
assertEquals(16, testArray.getPosition());
assertReadBitsToEnd(16, testArray);
}
private static void assertReadBitsToEnd(int expectedStartPosition, ParsableBitArray testArray) {
int position = testArray.getPosition();
assertEquals(expectedStartPosition, position);
for (int i = position; i < TEST_DATA.length * 8; i++) {
assertEquals(getTestDataBit(i), testArray.readBit());
assertEquals(i + 1, testArray.getPosition());
}
}
private static int getTestDataBits(int bitPosition, int length) {
int result = 0;
for (int i = 0; i < length; i++) {
result = result << 1;
if (getTestDataBit(bitPosition++)) {
result |= 0x1;
}
}
return result;
}
private static boolean getTestDataBit(int bitPosition) {
return (TEST_DATA[bitPosition / 8] & (0x80 >>> (bitPosition % 8))) != 0;
}
}

View File

@ -110,14 +110,26 @@ public final class ParsableBitArray {
assertValidOffset();
}
/**
* Skips a single bit.
*/
public void skipBit() {
if (++bitOffset == 8) {
bitOffset = 0;
byteOffset++;
}
assertValidOffset();
}
/**
* Skips bits and moves current reading position forward.
*
* @param n The number of bits to skip.
* @param numBits The number of bits to skip.
*/
public void skipBits(int n) {
byteOffset += (n / 8);
bitOffset += (n % 8);
public void skipBits(int numBits) {
int numBytes = numBits / 8;
byteOffset += numBytes;
bitOffset += numBits - (numBytes * 8);
if (bitOffset > 7) {
byteOffset++;
bitOffset -= 8;
@ -131,7 +143,9 @@ public final class ParsableBitArray {
* @return Whether the bit is set.
*/
public boolean readBit() {
return readBits(1) == 1;
boolean returnValue = (data[byteOffset] & (0x80 >> bitOffset)) != 0;
skipBit();
return returnValue;
}
/**
@ -141,48 +155,18 @@ public final class ParsableBitArray {
* @return An integer whose bottom n bits hold the read data.
*/
public int readBits(int numBits) {
if (numBits == 0) {
return 0;
}
int returnValue = 0;
// Read as many whole bytes as we can.
int wholeBytes = (numBits / 8);
for (int i = 0; i < wholeBytes; i++) {
int byteValue;
if (bitOffset != 0) {
byteValue = ((data[byteOffset] & 0xFF) << bitOffset)
| ((data[byteOffset + 1] & 0xFF) >>> (8 - bitOffset));
} else {
byteValue = data[byteOffset];
}
numBits -= 8;
returnValue |= (byteValue & 0xFF) << numBits;
bitOffset += numBits;
while (bitOffset > 8) {
bitOffset -= 8;
returnValue |= (data[byteOffset++] & 0xFF) << bitOffset;
}
returnValue |= (data[byteOffset] & 0xFF) >> 8 - bitOffset;
returnValue &= 0xFFFFFFFF >>> (32 - numBits);
if (bitOffset == 8) {
bitOffset = 0;
byteOffset++;
}
// Read any remaining bits.
if (numBits > 0) {
int nextBit = bitOffset + numBits;
byte writeMask = (byte) (0xFF >> (8 - numBits));
if (nextBit > 8) {
// Combine bits from current byte and next byte.
returnValue |= ((((data[byteOffset] & 0xFF) << (nextBit - 8)
| ((data[byteOffset + 1] & 0xFF) >> (16 - nextBit))) & writeMask));
byteOffset++;
} else {
// Bits to be read only within current byte.
returnValue |= (((data[byteOffset] & 0xFF) >> (8 - nextBit)) & writeMask);
if (nextBit == 8) {
byteOffset++;
}
}
bitOffset = nextBit % 8;
}
assertValidOffset();
return returnValue;
}
@ -231,7 +215,6 @@ public final class ParsableBitArray {
private void assertValidOffset() {
// It is fine for position to be at the end of the array, but no further.
Assertions.checkState(byteOffset >= 0
&& (bitOffset >= 0 && bitOffset < 8)
&& (byteOffset < byteLimit || (byteOffset == byteLimit && bitOffset == 0)));
}