clean up memory allocation in bitfield (#910)

clean up memory allocation in bitfield
This commit is contained in:
Arvid Norberg 2016-07-16 14:31:25 -07:00 committed by GitHub
parent e72f95e0d9
commit 31fbbab89c
4 changed files with 157 additions and 100 deletions

View File

@ -35,12 +35,12 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/assert.hpp"
#include "libtorrent/config.hpp"
#include "libtorrent/buffer.hpp"
#include "libtorrent/aux_/byteswap.hpp"
#include <cstring> // for memset and memcpy
#include <cstdlib> // for malloc, free and realloc
#include <cstdint> // uint32_t
#include <algorithm> // for min()
#include <memory> // for unique_ptr
namespace libtorrent
{
@ -56,22 +56,12 @@ namespace libtorrent
// The constructor taking a pointer ``b`` and ``bits`` copies a bitfield
// from the specified buffer, and ``bits`` number of bits (rounded up to
// the nearest byte boundary).
bitfield(): m_buf(nullptr) {}
bitfield(int bits): m_buf(nullptr)
{ resize(bits); }
bitfield(int bits, bool val): m_buf(nullptr)
{ resize(bits, val); }
bitfield(char const* b, int bits): m_buf(nullptr)
{ assign(b, bits); }
bitfield(bitfield const& rhs): m_buf(nullptr)
{ assign(rhs.data(), rhs.size()); }
#if __cplusplus > 199711L
bitfield(bitfield&& rhs): m_buf(rhs.m_buf)
{ rhs.m_buf = nullptr; }
#endif
// hidden
~bitfield() { dealloc(); }
bitfield() = default;
bitfield(int bits) { resize(bits); }
bitfield(int bits, bool val) { resize(bits, val); }
bitfield(char const* b, int bits) { assign(b, bits); }
bitfield(bitfield const& rhs) { assign(rhs.data(), rhs.size()); }
bitfield(bitfield&& rhs) = default;
// copy bitfield from buffer ``b`` of ``bits`` number of bits, rounded up to
// the nearest byte boundary.
@ -80,7 +70,7 @@ namespace libtorrent
resize(bits);
if (bits > 0)
{
std::memcpy(m_buf, b, size_t((bits + 7) / 8));
std::memcpy(buf(), b, size_t((bits + 7) / 8));
clear_trailing_bits();
}
}
@ -93,7 +83,7 @@ namespace libtorrent
{
TORRENT_ASSERT(index >= 0);
TORRENT_ASSERT(index < size());
return (m_buf[index / 32] & aux::host_to_network((0x80000000 >> (index & 31)))) != 0;
return (buf()[index / 32] & aux::host_to_network((0x80000000 >> (index & 31)))) != 0;
}
// set bit at ``index`` to 0 (clear_bit) or 1 (set_bit).
@ -101,13 +91,13 @@ namespace libtorrent
{
TORRENT_ASSERT(index >= 0);
TORRENT_ASSERT(index < size());
m_buf[index / 32] &= aux::host_to_network(~(0x80000000 >> (index & 31)));
buf()[index / 32] &= aux::host_to_network(~(0x80000000 >> (index & 31)));
}
void set_bit(int index)
{
TORRENT_ASSERT(index >= 0);
TORRENT_ASSERT(index < size());
m_buf[index / 32] |= aux::host_to_network((0x80000000 >> (index & 31)));
buf()[index / 32] |= aux::host_to_network((0x80000000 >> (index & 31)));
}
// returns true if all bits in the bitfield are set
@ -116,9 +106,10 @@ namespace libtorrent
bool none_set() const
{
const int words = num_words();
std::uint32_t const* b = buf();
for (int i = 0; i < words; ++i)
{
if (m_buf[i] != 0) return false;
if (b[i] != 0) return false;
}
return true;
}
@ -126,7 +117,7 @@ namespace libtorrent
// returns the size of the bitfield in bits.
int size() const
{
return m_buf == nullptr ? 0 : int(m_buf[-1]);
return m_buf == nullptr ? 0 : int(m_buf[0]);
}
int num_words() const
@ -135,28 +126,29 @@ namespace libtorrent
}
// returns true if the bitfield has zero size.
bool empty() const { return m_buf == nullptr ? true : m_buf[-1] == 0; }
bool empty() const { return m_buf == nullptr ? true : m_buf[0] == 0; }
// returns a pointer to the internal buffer of the bitfield.
char const* data() const { return reinterpret_cast<char const*>(m_buf); }
char const* data() const { return m_buf ? reinterpret_cast<char const*>(&m_buf[1]) : nullptr; }
char* data() { return m_buf ? reinterpret_cast<char*>(&m_buf[1]) : nullptr; }
#ifndef TORRENT_NO_DEPRECATE
TORRENT_DEPRECATED
char const* bytes() const { return data(); }
#endif
// copy operator
// assignment operator
bitfield& operator=(bitfield const& rhs)
{
assign(rhs.data(), rhs.size());
return *this;
}
bitfield& operator=(bitfield&& rhs) = default;
void swap(bitfield& rhs)
{
std::uint32_t* tmp = m_buf;
m_buf = rhs.m_buf;
rhs.m_buf = tmp;
std::swap(m_buf, rhs.m_buf);
}
// count the number of bits in the bitfield that are set to 1.
@ -220,49 +212,44 @@ namespace libtorrent
std::uint32_t bit;
};
const_iterator begin() const { return const_iterator(m_buf, 0); }
const_iterator begin() const { return const_iterator(m_buf ? buf() : nullptr, 0); }
const_iterator end() const { return const_iterator(
m_buf + num_words() - (((size() & 31) == 0) ? 0 : 1), size() & 31); }
m_buf ? buf() + num_words() - (((size() & 31) == 0) ? 0 : 1) : nullptr, size() & 31); }
// set the size of the bitfield to ``bits`` length. If the bitfield is extended,
// the new bits are initialized to ``val``.
void resize(int bits, bool val);
void resize(int bits);
// set all bits in the bitfield to 1 (set_all) or 0 (clear_all).
void set_all()
{
std::memset(m_buf, 0xff, size_t(num_words() * 4));
if (m_buf == nullptr) return;
std::memset(buf(), 0xff, size_t(num_words() * 4));
clear_trailing_bits();
}
void clear_all()
{
std::memset(m_buf, 0x00, size_t(num_words() * 4));
if (m_buf == nullptr) return;
std::memset(buf(), 0x00, size_t(num_words() * 4));
}
// make the bitfield empty, of zero size.
void clear() { dealloc(); }
void clear() { m_buf.reset(); }
private:
std::uint32_t const* buf() const { TORRENT_ASSERT(m_buf); return &m_buf[1]; }
std::uint32_t* buf() { TORRENT_ASSERT(m_buf); return &m_buf[1]; }
void clear_trailing_bits()
{
// clear the tail bits in the last byte
if (size() & 31) m_buf[num_words() - 1] &= aux::host_to_network(0xffffffff << (32 - (size() & 31)));
}
void dealloc()
{
if (m_buf) std::free(m_buf-1);
m_buf = nullptr;
if (size() & 31) buf()[num_words() - 1] &= aux::host_to_network(0xffffffff << (32 - (size() & 31)));
}
// the first element is not part of the bitfield, it's the
// number of bits. For this purpose, the m_buf actually points
// the element 1, not 0. To access the size (in bits), access
// m_buf[-1]
std::uint32_t* m_buf;
// number of bits.
std::unique_ptr<std::uint32_t[]> m_buf;
};
}

View File

@ -135,7 +135,7 @@ public:
// allocate an uninitialized buffer of the specified size
buffer(std::size_t size = 0)
{
TORRENT_ASSERT(size < std::size_t(std::numeric_limits<std::int32_t>::max()));
TORRENT_ASSERT(size < std::size_t((std::numeric_limits<std::int32_t>::max)()));
if (size == 0) return;
@ -172,7 +172,7 @@ public:
TORRENT_ASSERT(initialize.size() <= size);
if (initialize.size() > 0)
{
memcpy(m_begin, initialize.data(), std::min(initialize.size(), size));
memcpy(m_begin, initialize.data(), (std::min)(initialize.size(), size));
}
}

View File

@ -45,16 +45,16 @@ namespace libtorrent
{
bool bitfield::all_set() const
{
const int words = size() / 32;
for (int i = 0; i < words; ++i)
int const words = size() / 32;
for (int i = 1; i < words + 1; ++i)
{
if (m_buf[i] != 0xffffffff) return false;
}
int rest = size() & 31;
int const rest = size() & 31;
if (rest > 0)
{
std::uint32_t mask = aux::host_to_network(0xffffffff << (32-rest));
if ((m_buf[words] & mask) != mask) return false;
std::uint32_t const mask = aux::host_to_network(0xffffffff << (32-rest));
if ((m_buf[words+1] & mask) != mask) return false;
}
return true;
}
@ -62,11 +62,11 @@ namespace libtorrent
int bitfield::count() const
{
int ret = 0;
const int words = num_words();
int const words = num_words();
#if TORRENT_HAS_SSE
if (aux::mmx_support)
{
for (int i = 0; i < words; ++i)
for (int i = 1; i < words+1; ++i)
{
#ifdef __GNUC__
std::uint32_t cnt = 0;
@ -79,6 +79,8 @@ namespace libtorrent
#endif
}
TORRENT_ASSERT(ret <= size());
TORRENT_ASSERT(ret >= 0);
return ret;
}
#endif // TORRENT_HAS_SSE
@ -86,7 +88,7 @@ namespace libtorrent
#if TORRENT_HAS_ARM_NEON
if (aux::arm_neon_support)
{
for (int i = 0; i < words; ++i)
for (int i = 1; i < words + 1; ++i)
{
uint8x8_t const in_val = vld1_u8((unsigned char *) &m_buf[i]);
uint8x8_t const cnt8x8_val = vcnt_u8(in_val);
@ -98,13 +100,15 @@ namespace libtorrent
ret += cnt;
}
TORRENT_ASSERT(ret <= size());
TORRENT_ASSERT(ret >= 0);
return ret;
}
#endif // TORRENT_HAS_ARM_NEON
for (int i = 0; i < words; ++i)
for (int i = 1; i < words + 1; ++i)
{
std::uint32_t v = m_buf[i];
std::uint32_t const v = m_buf[i];
// from:
// http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel
static const int S[] = {1, 2, 4, 8, 16}; // Magic Binary Numbers
@ -116,6 +120,7 @@ namespace libtorrent
c = ((c >> S[3]) + c) & B[3];
c = ((c >> S[4]) + c) & B[4];
ret += c;
TORRENT_ASSERT(ret <= size());
}
TORRENT_ASSERT(ret <= size());
@ -127,24 +132,24 @@ namespace libtorrent
{
if (bits == size()) return;
int s = size();
int b = size() & 31;
int const s = size();
int const b = size() & 31;
resize(bits);
if (s >= size()) return;
int old_size_words = (s + 31) / 32;
int new_size_words = num_words();
int const old_size_words = (s + 31) / 32;
int const new_size_words = num_words();
if (val)
{
if (old_size_words && b) m_buf[old_size_words - 1] |= aux::host_to_network((0xffffffff >> b));
if (old_size_words && b) buf()[old_size_words - 1] |= aux::host_to_network((0xffffffff >> b));
if (old_size_words < new_size_words)
std::memset(m_buf + old_size_words, 0xff
std::memset(buf() + old_size_words, 0xff
, size_t((new_size_words - old_size_words) * 4));
clear_trailing_bits();
}
else
{
if (old_size_words < new_size_words)
std::memset(m_buf + old_size_words, 0x00
std::memset(buf() + old_size_words, 0x00
, size_t((new_size_words - old_size_words) * 4));
}
TORRENT_ASSERT(size() == bits);
@ -155,37 +160,35 @@ namespace libtorrent
if (bits == size()) return;
TORRENT_ASSERT(bits >= 0);
const int b = (bits + 31) / 32;
if (bits == 0)
{
if (m_buf != nullptr)
{
std::free(m_buf-1);
m_buf = nullptr;
}
m_buf.reset();
return;
}
if (m_buf)
int const new_size_words = (bits + 31) / 32;
int const cur_size_words = num_words();
if (cur_size_words != new_size_words)
{
std::uint32_t* tmp = static_cast<std::uint32_t*>(std::realloc(m_buf-1, (b+1) * 4));
#ifndef BOOST_NO_EXCEPTIONS
if (tmp == nullptr) throw std::bad_alloc();
std::unique_ptr<std::uint32_t[]> b(new std::uint32_t[new_size_words + 1]);
#ifdef BOOST_NO_EXCEPTIONS
if (b == nullptr) std::terminate();
#endif
m_buf = tmp + 1;
m_buf[-1] = bits;
b[0] = bits;
if (m_buf) memcpy(&b[1], buf(), (std::min)(new_size_words, cur_size_words) * 4);
if (new_size_words > cur_size_words)
{
memset(&b[1 + cur_size_words], 0
, (new_size_words - cur_size_words) * 4);
}
m_buf = std::move(b);
}
else
{
// +1 because the first word is the size (in bits)
std::uint32_t* tmp = static_cast<std::uint32_t*>(std::malloc((b+1) * 4));
#ifndef BOOST_NO_EXCEPTIONS
if (tmp == nullptr) throw std::bad_alloc();
#endif
m_buf = tmp + 1;
m_buf[-1] = bits;
m_buf[0] = bits;
}
clear_trailing_bits();
TORRENT_ASSERT(size() == bits);
}
}

View File

@ -40,10 +40,9 @@ using namespace libtorrent;
void print_bitfield(bitfield const& b)
{
std::string out;
for (int i = 0; i < b.size(); ++i)
{
out += b.get_bit(i) ? "1" : "0";
}
out.reserve(b.size());
for (bool bit : b)
out += bit ? '1' : '0';
std::printf("%s\n", out.c_str());
}
@ -116,16 +115,16 @@ TORRENT_TEST(bitfield)
test1.resize(100, true);
TEST_CHECK(test1.all_set() == true);
TEST_CHECK(test1.count() == 100);
TEST_EQUAL(test1.count(), 100);
test1.resize(200, false);
TEST_CHECK(test1.all_set() == false);
TEST_CHECK(test1.count() == 100);
TEST_EQUAL(test1.count(), 100);
test1.resize(50, false);
TEST_CHECK(test1.all_set() == true);
TEST_CHECK(test1.count() == 50);
TEST_EQUAL(test1.count(), 50);
test1.resize(101, true);
TEST_CHECK(test1.all_set() == true);
TEST_CHECK(test1.count() == 101);
TEST_EQUAL(test1.count(), 101);
std::uint8_t b1[] = { 0x08, 0x10 };
test1.assign((char*)b1, 14);
@ -149,7 +148,11 @@ TORRENT_TEST(bitfield)
TEST_EQUAL(test1.get_bit(0), true);
TEST_EQUAL(test1.get_bit(1), false);
TEST_EQUAL(test1.get_bit(2), true);
}
TORRENT_TEST(test_assign3)
{
bitfield test1;
std::uint8_t b2[] = { 0x08, 0x10, 0xff, 0xff, 0xff, 0xff, 0xf, 0xc, 0x7f };
test1.assign((char*)b2, 72);
print_bitfield(test1);
@ -159,31 +162,42 @@ TORRENT_TEST(bitfield)
test1.assign((char*)b3, 64);
print_bitfield(test1);
TEST_EQUAL(test1.count(), 40);
}
TORRENT_TEST(test_iterators)
{
bitfield test1;
for (int i = 0; i < 100; ++i)
{
test1.resize(i, false);
test_iterators(test1);
}
}
// test alignment
std::uint32_t buffer[4];
char* b = (char*)buffer;
TORRENT_TEST(test_assign)
{
std::array<char, 16> b;
bitfield test1;
for (int i = 0; i < 4; ++i)
{
b[i] = char(0xc0);
test1.assign(b + i, 2);
test1.assign(&b[i], 2);
print_bitfield(test1);
TEST_EQUAL(test1.count(), 2);
TEST_EQUAL(test1.all_set(), true);
}
}
TORRENT_TEST(test_assign2)
{
std::array<char, 16> b;
bitfield test1;
for (int i = 0; i < 4; ++i)
{
memset(b + i, 0xff, 5);
memset(&b[i], 0xff, 5);
b[i + 5] = char(0xc0);
test1.assign(b + i, 32 + 8 + 2);
test1.assign(&b[i], 32 + 8 + 2);
print_bitfield(test1);
TEST_EQUAL(test1.count(), 32 + 8 + 2);
TEST_EQUAL(test1.all_set(), true);
@ -195,3 +209,56 @@ TORRENT_TEST(bitfield)
TORRENT_ASSERT(!aux::arm_neon_support);
#endif
}
TORRENT_TEST(test_resize_val)
{
std::array<char, 8> b;
b.fill(0xcc);
bitfield test1(b.data(), 8 * 8);
print_bitfield(test1);
TEST_EQUAL(test1.size(), 8 * 8);
TEST_EQUAL(test1.count(), 4 * 8);
for (int i = 1; i < 4 * 8; ++i)
{
test1.resize(8 * 8 + i, true);
print_bitfield(test1);
TEST_EQUAL(test1.count(), 4 * 8 + i);
}
}
TORRENT_TEST(test_resize_up)
{
std::array<char, 8> b;
b.fill(0xcc);
bitfield test1(b.data(), 8 * 8);
print_bitfield(test1);
TEST_EQUAL(test1.size(), 8 * 8);
TEST_EQUAL(test1.count(), 4 * 8);
for (int i = 1; i < 5 * 8; ++i)
{
test1.resize(8 * 8 + i);
print_bitfield(test1);
TEST_EQUAL(test1.size(), 8 * 8 + i);
TEST_EQUAL(test1.count(), 4 * 8);
}
}
TORRENT_TEST(test_resize_down)
{
std::array<char, 8> b;
b.fill(0x55);
bitfield test1(b.data(), 8 * 8);
for (int i = 8 * 8; i > -1; --i)
{
test1.resize(i);
print_bitfield(test1);
TEST_EQUAL(test1.size(), i);
TEST_EQUAL(test1.count(), i / 2);
}
}