ladybird/Tests/AK/TestBitStream.cpp
Timothy Flynn eed956b473 AK: Increase LittleEndianOutputBitStream's buffer size and remove loops
This is very similar to the LittleEndianInputBitStream bit buffer change
from 8e834d4bb2.

We currently buffer one byte of data for the underlying stream. And when
we put bits onto that buffer, we do so 1 bit at a time.

This replaces the u8 buffer with a u64. And instead of looping at all,
we perform bitwise operations to write the desired number of bits.

Using the "enwik8" file as a test (100MB uncompressed, commonly used in
benchmarks: https://www.mattmahoney.net/dc/enwik8.zip), compression time
decreases from:

    13.62s to 10.9s on Serenity (cold)
    13.62s to 9.22s on Serenity (warm)
    2.93s to 2.32s on Linux

One caveat is that this requires explicitly flushing any leftover bits
when the caller is done with the stream. The byte buffer implementation
implicitly flushed its data every time the buffer was byte-aligned, as
doing so would always fill the byte. This is no longer the case. But for
now, this should be fine as the one user of this class, DEFLATE, already
has a "flush everything now that we're done" finalizer.
2023-04-02 10:54:37 +02:00

132 lines
5.1 KiB
C++

/*
* Copyright (c) 2023, Tim Schumacher <timschumi@gmx.de>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/BitStream.h>
#include <AK/MemoryStream.h>
#include <LibTest/TestCase.h>
// Note: This does not do any checks on the internal representation, it just ensures that the behavior of the input and output streams match.
TEST_CASE(little_endian_bit_stream_input_output_match)
{
auto memory_stream = make<AllocatingMemoryStream>();
// Note: The bit stream only ever reads from/writes to the underlying stream in one byte chunks,
// so testing with sizes that will not trigger a write will yield unexpected results.
LittleEndianOutputBitStream bit_write_stream { MaybeOwned<Stream>(*memory_stream) };
LittleEndianInputBitStream bit_read_stream { MaybeOwned<Stream>(*memory_stream) };
// Test two mirrored chunks of a fully mirrored pattern to check that we are not dropping bits.
{
MUST(bit_write_stream.write_bits(0b1111u, 4));
MUST(bit_write_stream.write_bits(0b1111u, 4));
MUST(bit_write_stream.flush_buffer_to_stream());
auto result = MUST(bit_read_stream.read_bits(4));
EXPECT_EQ(0b1111u, result);
result = MUST(bit_read_stream.read_bits(4));
EXPECT_EQ(0b1111u, result);
}
{
MUST(bit_write_stream.write_bits(0b0000u, 4));
MUST(bit_write_stream.write_bits(0b0000u, 4));
MUST(bit_write_stream.flush_buffer_to_stream());
auto result = MUST(bit_read_stream.read_bits(4));
EXPECT_EQ(0b0000u, result);
result = MUST(bit_read_stream.read_bits(4));
EXPECT_EQ(0b0000u, result);
}
// Test two mirrored chunks of a non-mirrored pattern to check that we are writing bits within a pattern in the correct order.
{
MUST(bit_write_stream.write_bits(0b1000u, 4));
MUST(bit_write_stream.write_bits(0b1000u, 4));
MUST(bit_write_stream.flush_buffer_to_stream());
auto result = MUST(bit_read_stream.read_bits(4));
EXPECT_EQ(0b1000u, result);
result = MUST(bit_read_stream.read_bits(4));
EXPECT_EQ(0b1000u, result);
}
// Test two different chunks to check that we are not confusing their order.
{
MUST(bit_write_stream.write_bits(0b1000u, 4));
MUST(bit_write_stream.write_bits(0b0100u, 4));
MUST(bit_write_stream.flush_buffer_to_stream());
auto result = MUST(bit_read_stream.read_bits(4));
EXPECT_EQ(0b1000u, result);
result = MUST(bit_read_stream.read_bits(4));
EXPECT_EQ(0b0100u, result);
}
// Test a pattern that spans multiple bytes.
{
MUST(bit_write_stream.write_bits(0b1101001000100001u, 16));
MUST(bit_write_stream.flush_buffer_to_stream());
auto result = MUST(bit_read_stream.read_bits(16));
EXPECT_EQ(0b1101001000100001u, result);
}
}
// Note: This does not do any checks on the internal representation, it just ensures that the behavior of the input and output streams match.
TEST_CASE(big_endian_bit_stream_input_output_match)
{
auto memory_stream = make<AllocatingMemoryStream>();
// Note: The bit stream only ever reads from/writes to the underlying stream in one byte chunks,
// so testing with sizes that will not trigger a write will yield unexpected results.
BigEndianOutputBitStream bit_write_stream { MaybeOwned<Stream>(*memory_stream) };
BigEndianInputBitStream bit_read_stream { MaybeOwned<Stream>(*memory_stream) };
// Test two mirrored chunks of a fully mirrored pattern to check that we are not dropping bits.
{
MUST(bit_write_stream.write_bits(0b1111u, 4));
MUST(bit_write_stream.write_bits(0b1111u, 4));
auto result = MUST(bit_read_stream.read_bits(4));
EXPECT_EQ(0b1111u, result);
result = MUST(bit_read_stream.read_bits(4));
EXPECT_EQ(0b1111u, result);
}
{
MUST(bit_write_stream.write_bits(0b0000u, 4));
MUST(bit_write_stream.write_bits(0b0000u, 4));
auto result = MUST(bit_read_stream.read_bits(4));
EXPECT_EQ(0b0000u, result);
result = MUST(bit_read_stream.read_bits(4));
EXPECT_EQ(0b0000u, result);
}
// Test two mirrored chunks of a non-mirrored pattern to check that we are writing bits within a pattern in the correct order.
{
MUST(bit_write_stream.write_bits(0b1000u, 4));
MUST(bit_write_stream.write_bits(0b1000u, 4));
auto result = MUST(bit_read_stream.read_bits(4));
EXPECT_EQ(0b1000u, result);
result = MUST(bit_read_stream.read_bits(4));
EXPECT_EQ(0b1000u, result);
}
// Test two different chunks to check that we are not confusing their order.
{
MUST(bit_write_stream.write_bits(0b1000u, 4));
MUST(bit_write_stream.write_bits(0b0100u, 4));
auto result = MUST(bit_read_stream.read_bits(4));
EXPECT_EQ(0b1000u, result);
result = MUST(bit_read_stream.read_bits(4));
EXPECT_EQ(0b0100u, result);
}
// Test a pattern that spans multiple bytes.
{
MUST(bit_write_stream.write_bits(0b1101001000100001u, 16));
auto result = MUST(bit_read_stream.read_bits(16));
EXPECT_EQ(0b1101001000100001u, result);
}
}