Skip to content

Commit 7777193

Browse files
committed
Update README
Fix extend in fixed_policy Add a performance test for different serialization methods
1 parent 6c9cc66 commit 7777193

File tree

5 files changed

+143
-25
lines changed

5 files changed

+143
-25
lines changed

README.md

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,13 @@ bool serialize_custom_type(Stream& stream, custom_type& value)
8686
}
8787

8888
byte_buffer<32> buffer;
89-
bit_writer writer(buffer);
89+
fixed_bit_writer writer(buffer);
9090

9191
custom_type in_value;
9292
serialize_custom_type(writer, in_value); // Serialize the value
9393

9494
uint32_t num_bits = writer.flush();
95-
bit_reader reader(buffer, num_bits);
95+
fixed_bit_reader reader(buffer, num_bits);
9696

9797
custom_type out_value;
9898
serialize_custom_type(reader, out_value); // Deserialize the value
@@ -119,7 +119,7 @@ Writing the first 5 bits of an int to the buffer, then reading it back:
119119
```cpp
120120
// Create a writer, referencing the buffer and its size
121121
alignas(uint32_t) uint8_t buffer[4]; // Buffer must be a multiple of 4 bytes / 32 bits and 4-byte-aligned
122-
bit_writer writer(buffer, 4);
122+
fixed_bit_writer writer(buffer, 4);
123123
124124
// Write the value
125125
uint32_t value = 27; // We can choose any value below 2^5. Otherwise we need more than 5 bits
@@ -129,7 +129,7 @@ writer.serialize_bits(value, 5);
129129
uint32_t num_bits = writer.flush();
130130
131131
// Create a reader, referencing the buffer and bits written
132-
bit_reader reader(buffer, num_bits);
132+
fixed_bit_reader reader(buffer, num_bits);
133133
134134
// Read the value back
135135
uint32_t out_value; // We don't have to initialize it yet
@@ -140,7 +140,7 @@ Writing a signed int to the buffer, within a range:
140140
```cpp
141141
// Create a writer, referencing the buffer and its size
142142
byte_buffer<4> buffer; // byte_bufer is just a wrapper for a 4-byte aligned buffer
143-
bit_writer writer(buffer);
143+
fixed_bit_writer writer(buffer);
144144

145145
// Write the value
146146
int32_t value = -45; // We can choose any value within the range below
@@ -150,7 +150,7 @@ writer.serialize<int32_t>(value, -90, 40); // A lower and upper bound which the
150150
uint32_t num_bits = writer.flush();
151151

152152
// Create a reader, referencing the buffer and bits written
153-
bit_reader reader(buffer, num_bits);
153+
fixed_bit_reader reader(buffer, num_bits);
154154

155155
// Read the value back
156156
int32_t out_value; // We don't have to initialize it yet
@@ -161,7 +161,7 @@ Writing a c-style string into the buffer:
161161
```cpp
162162
// Create a writer, referencing the buffer and its size
163163
byte_buffer<32> buffer;
164-
bit_writer writer(buffer);
164+
fixed_bit_writer writer(buffer);
165165
166166
// Write the value
167167
const char* value = "Hello world!";
@@ -171,7 +171,7 @@ writer.serialize<const char*>(value, 32U); // The second argument is the maximum
171171
uint32_t num_bits = writer.flush();
172172
173173
// Create a reader, referencing the buffer and bits written
174-
bit_reader reader(buffer, num_bits);
174+
fixed_bit_reader reader(buffer, num_bits);
175175
176176
// Read the value back
177177
char out_value[32]; // Set the size to the max size
@@ -182,7 +182,7 @@ Writing a std::string into the buffer:
182182
```cpp
183183
// Create a writer, referencing the buffer and its size
184184
byte_buffer<32> buffer;
185-
bit_writer writer(buffer);
185+
fixed_bit_writer writer(buffer);
186186

187187
// Write the value
188188
std::string value = "Hello world!";
@@ -192,7 +192,7 @@ writer.serialize<std::string>(value, 32U); // The second argument is the maximum
192192
uint32_t num_bits = writer.flush();
193193

194194
// Create a reader, referencing the buffer and bits written
195-
bit_reader reader(buffer, num_bits);
195+
fixed_bit_reader reader(buffer, num_bits);
196196

197197
// Read the value back
198198
std::string out_value; // The string will be resized if the output doesn't fit
@@ -203,7 +203,7 @@ Writing a float into the buffer with a bounded range and precision:
203203
```cpp
204204
// Create a writer, referencing the buffer and its size
205205
byte_buffer<4> buffer;
206-
bit_writer writer(buffer);
206+
fixed_bit_writer writer(buffer);
207207
208208
// Write the value
209209
bounded_range range(1.0f, 4.0f, 1.0f / 128.0f); // Min, Max, Precision
@@ -214,7 +214,7 @@ writer.serialize<bounded_range>(range, value);
214214
uint32_t num_bits = writer.flush();
215215
216216
// Create a reader, referencing the buffer and bits written
217-
bit_reader reader(buffer, num_bits);
217+
fixed_bit_reader reader(buffer, num_bits);
218218
219219
// Read the value back
220220
float out_value;
@@ -445,8 +445,8 @@ bool status_read = reader.serialize<smallest_three<quaternion, 12>>(out_value);
445445
446446
# Extensibility
447447
The library is made with extensibility in mind.
448-
The `bit_writer` and `bit_reader` use a template trait specialization of the given type to deduce how to serialize and deserialize the object.
449-
The only requirements of the trait is that it has (or can deduce) 2 static functions which take a `bit_writer&` and a `bit_reader&` respectively as their first argument.
448+
The `bit_writer<T>` and `bit_reader<T>` use a template trait specialization of the given type to deduce how to serialize and deserialize the object.
449+
The only requirements of the trait is that it has (or can deduce) 2 static functions which take a `bit_writer<T>&` and a `bit_reader<T>&` respectively as their first argument.
450450
The 2 functions must also return a bool indicating whether the serialization was a success or not, but can otherwise take any number of additional arguments.
451451
452452
## Adding new serializables types
@@ -457,17 +457,21 @@ template<>
457457
struct serialize_traits<TRAIT_TYPE> // The type to use when referencing this specific trait
458458
{
459459
// Will be called when writing the object to a stream
460-
static bool serialize(bit_writer& stream, ...)
460+
template<typename Stream>
461+
typename utility::is_writing_t<Stream>
462+
static serialize(Stream& stream, ...)
461463
{ ... }
462464
463465
// Will be called when reading the object from a stream
464-
static bool serialize(bit_reader& stream, ...)
466+
template<typename Stream>
467+
typename utility::is_reading_t<Stream>
468+
static serialize(Stream& stream, ...)
465469
{ ... }
466470
};
467471
```
468472

469473
As with any functions, you are free to overload them if you want to serialize an object differently, depending on any parameters you pass.
470-
As long as their list of parameters starts with `bit_writer&` and `bit_reader&` respectively they will be able to be called.
474+
As long as the first parameter can be deduced to `bit_writer<T>&` and `bit_reader<T>&` respectively they will be able to be called.
471475

472476
## Unified serialization
473477
The serialization can also be unified with templating, if writing and reading look similar.
@@ -488,7 +492,7 @@ struct serialize_traits<TRAIT_TYPE> // The type to use when serializing
488492
}
489493

490494
// A variable that differs if the stream is writing or reading
491-
int value = Stream::reading ? 0 : 1;
495+
int value = Stream::reading ? 500 : 200;
492496

493497
...
494498
}
@@ -525,19 +529,23 @@ template<>
525529
struct serialize_traits<TRAIT_TYPE> // The type to use when referencing this specific trait
526530
{
527531
// The second argument is the same as TRAIT_TYPE (const and lvalue references are removed when deducing)
528-
static bool serialize(bit_writer& stream, const TRAIT_TYPE&, ...)
532+
template<typename Stream>
533+
typename utility::is_writing_t<Stream>
534+
static serialize(Stream& stream, const TRAIT_TYPE&, ...)
529535
{ ... }
530536

531537
// The second argument is the same as TRAIT_TYPE (lvalue is removed)
532-
static bool serialize(bit_reader& stream, TRAIT_TYPE&, ...)
538+
template<typename Stream>
539+
typename utility::is_reading_t<Stream>
540+
static serialize(Stream& stream, TRAIT_TYPE&, ...)
533541
{ ... }
534542
};
535543
```
536544
537545
The above trait could then be used when implicitly serializing an object of type `TRAIT_TYPE`:
538546
```cpp
539547
TRAIT_TYPE value;
540-
bool status = writer.serialize(value, ...);
548+
bool status = writer.serialize(value, ...); // No need for "serialize<TRAIT_TYPE>"
541549
```
542550

543551
It doesn't work on all types, and there is some guesswork involved relating to const qualifiers.

include/bitstream/stream/bit_reader.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ namespace bitstream
1717
{
1818
/**
1919
* @brief A stream for reading objects from a tightly packed buffer
20-
* @note Does not take ownership of the buffer
20+
* @tparam Policy The underlying representation of the buffer
2121
*/
2222
template<typename Policy>
2323
class bit_reader

include/bitstream/stream/bit_writer.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ namespace bitstream
1717
{
1818
/**
1919
* @brief A stream for writing objects tightly into a buffer
20-
* @note Does not take ownership of the buffer
20+
* @tparam Policy The underlying representation of the buffer
2121
*/
2222
template<typename Policy>
2323
class bit_writer

include/bitstream/stream/stream_traits.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,11 @@ namespace bitstream
5353

5454
bool extend(uint32_t num_bits)
5555
{
56-
bool status = can_serialize_bits(num_bits);
56+
if (!can_serialize_bits(num_bits))
57+
return false;
58+
5759
m_NumBitsSerialized += num_bits;
58-
return status;
60+
return true;
5961
}
6062

6163
uint32_t* m_Buffer;
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
#include "../shared/assert.h"
2+
#include "../shared/test.h"
3+
4+
#include <bitstream/stream/bit_reader.h>
5+
#include <bitstream/stream/bit_writer.h>
6+
#include <bitstream/utility/bits.h>
7+
8+
#include <bitstream/traits/integral_traits.h>
9+
#include <bitstream/traits/quantization_traits.h>
10+
#include <bitstream/traits/string_traits.h>
11+
12+
#include <chrono>
13+
#include <iostream>
14+
15+
namespace bitstream::test::performance
16+
{
17+
template<typename F>
18+
void profile_time(F&& func)
19+
{
20+
auto start = std::chrono::steady_clock::now();
21+
22+
bool status = func();
23+
24+
auto end = std::chrono::steady_clock::now();
25+
26+
auto start_time = std::chrono::time_point_cast<std::chrono::microseconds>(start).time_since_epoch();
27+
auto end_time = std::chrono::time_point_cast<std::chrono::microseconds>(end).time_since_epoch();
28+
29+
auto time = (end - start).count();
30+
31+
std::cout << " ->time spent: " << time << "us\n";
32+
33+
BS_TEST_ASSERT(status);
34+
}
35+
36+
// Byte copying will have the wrong endianness for the type
37+
// But at least it is fast
38+
BS_ADD_TEST(test_aligned_bytecopy_performance)
39+
{
40+
byte_buffer<4096> buffer;
41+
fixed_bit_writer writer(buffer);
42+
43+
uint32_t numbers[1024];
44+
for (uint32_t i = 0U; i < 1024U; i++)
45+
numbers[i] = 24U;
46+
47+
// Should just use straight memcpy
48+
profile_time([&]
49+
{
50+
return writer.serialize_bytes(reinterpret_cast<uint8_t*>(numbers), 1024U * 8U * sizeof(uint32_t));
51+
});
52+
}
53+
54+
BS_ADD_TEST(test_misaligned_bytecopy_performance)
55+
{
56+
byte_buffer<4100> buffer;
57+
fixed_bit_writer writer(buffer);
58+
59+
uint32_t numbers[1024];
60+
for (uint32_t i = 0U; i < 1024U; i++)
61+
numbers[i] = 24U;
62+
63+
// Force misalignment
64+
BS_TEST_ASSERT(writer.serialize_bits(0, 1));
65+
66+
// Has to use serialize_bits individually
67+
profile_time([&]
68+
{
69+
return writer.serialize_bytes(reinterpret_cast<uint8_t*>(numbers), 1024U * 8U * sizeof(uint32_t));
70+
});
71+
}
72+
73+
BS_ADD_TEST(test_aligned_performance)
74+
{
75+
byte_buffer<4096> buffer;
76+
fixed_bit_writer writer(buffer);
77+
78+
// Should use the fast-path of serialize_bits
79+
profile_time([&]
80+
{
81+
for (uint32_t i = 0U; i < 1024U; i++)
82+
{
83+
if (!writer.serialize_bits(24U, 32U))
84+
return false;
85+
}
86+
87+
return true;
88+
});
89+
}
90+
91+
BS_ADD_TEST(test_misaligned_performance)
92+
{
93+
byte_buffer<4096> buffer;
94+
fixed_bit_writer writer(buffer);
95+
96+
// Has to use the slow path of serialize_bits
97+
profile_time([&]
98+
{
99+
for (uint32_t i = 0U; i < 1024U; i++)
100+
{
101+
if (!writer.serialize_bits(24U, 31U))
102+
return false;
103+
}
104+
105+
return true;
106+
});
107+
}
108+
}

0 commit comments

Comments
 (0)