14inline std::vector<std::uint32_t>
delta_encode(
const std::vector<VertexId>& ids) {
15 std::vector<std::uint32_t> out;
16 out.reserve(ids.size());
18 for (std::size_t i = 0; i < ids.size(); ++i) {
19 if (i && ids[i] < ids[i - 1])
throw std::invalid_argument(
"adjacency must be sorted");
20 out.push_back(i ? ids[i] - prev : ids[i]);
26inline std::vector<VertexId>
delta_decode(
const std::vector<std::uint32_t>& deltas) {
27 std::vector<VertexId> out;
28 out.reserve(deltas.size());
30 for (std::size_t i = 0; i < deltas.size(); ++i) {
34 if (deltas[i] > std::numeric_limits<VertexId>::max() - value)
35 throw std::overflow_error(
"delta decode overflow");
44 while (value >= 0x80U) {
45 out.push_back(
static_cast<std::uint8_t
>((value & 0x7FU) | 0x80U));
48 out.push_back(
static_cast<std::uint8_t
>(value));
52 std::size_t& offset) {
53 std::uint32_t value = 0;
55 while (offset < data.size()) {
56 const auto byte = data[offset++];
57 if (shift >= 32U && (
byte & 0x7FU) != 0U)
throw std::overflow_error(
"varbyte decode overflow");
58 value |=
static_cast<std::uint32_t
>(
byte & 0x7FU) << shift;
59 if ((
byte & 0x80U) == 0U)
return value;
61 if (shift > 28U)
throw std::overflow_error(
"invalid varbyte uint32");
63 throw std::invalid_argument(
"truncated varbyte stream");
68 std::vector<std::uint8_t> out;
69 out.reserve(deltas.size() * 2U);
75 std::vector<std::uint32_t> deltas;
76 std::size_t offset = 0;
89 std::size_t block_size = 128) {
90 if (block_size == 0)
throw std::invalid_argument(
"block size must be positive");
96 for (std::size_t first = 0; first < ids.size(); first += block_size) {
97 const std::size_t last = (first + block_size < ids.size()) ? first + block_size : ids.size();
98 std::vector<VertexId> block(ids.begin() +
static_cast<std::ptrdiff_t
>(first),
99 ids.begin() +
static_cast<std::ptrdiff_t
>(last));
101 result.
payload.insert(result.
payload.end(), encoded.begin(), encoded.end());
109 throw std::invalid_argument(
"invalid blocked adjacency offsets");
110 std::vector<VertexId> out;
112 for (std::size_t block = 0; block + 1 < encoded.
block_offsets.size(); ++block) {
115 if (begin > end || end > encoded.
payload.size())
throw std::invalid_argument(
"invalid block bounds");
116 std::vector<std::uint8_t> bytes(encoded.
payload.begin() +
static_cast<std::ptrdiff_t
>(begin),
117 encoded.
payload.begin() +
static_cast<std::ptrdiff_t
>(end));
119 out.insert(out.end(), decoded.begin(), decoded.end());
121 if (out.size() != encoded.
value_count)
throw std::invalid_argument(
"blocked adjacency count mismatch");
135 std::vector<FixedWidthDeltaBlock>
blocks;
140 if (max_delta <= std::numeric_limits<std::uint8_t>::max())
return 1;
141 if (max_delta <= std::numeric_limits<std::uint16_t>::max())
return 2;
146 std::vector<std::uint8_t>& out) {
147 for (std::uint8_t
byte = 0;
byte < lane_bytes; ++byte)
148 out.push_back(
static_cast<std::uint8_t
>((value >> (8U *
byte)) & 0xFFU));
153 std::uint8_t lane_bytes) {
154 if (lane_bytes != 1 && lane_bytes != 2 && lane_bytes != 4)
155 throw std::invalid_argument(
"invalid fixed-width lane size");
156 if (offset > payload.size() || lane_bytes > payload.size() - offset)
157 throw std::invalid_argument(
"truncated fixed-width payload");
158 std::uint32_t value = 0;
159 for (std::uint8_t
byte = 0;
byte < lane_bytes; ++byte)
160 value |=
static_cast<std::uint32_t
>(payload[offset +
byte]) << (8U * byte);
165 std::size_t block_size = 128) {
166 if (block_size == 0)
throw std::invalid_argument(
"block size must be positive");
167 if (!std::is_sorted(ids.begin(), ids.end()))
throw std::invalid_argument(
"adjacency must be sorted");
173 for (std::size_t first = 0; first < ids.size(); first += block_size) {
174 const std::size_t last = std::min(ids.size(), first + block_size);
175 std::vector<std::uint32_t> deltas;
176 deltas.reserve(last - first);
177 std::uint32_t max_delta = 0;
178 for (std::size_t i = first; i < last; ++i) {
179 const std::uint32_t delta = (i == first) ? ids[i] : ids[i] - ids[i - 1];
180 deltas.push_back(delta);
181 max_delta = std::max(max_delta, delta);
184 encoded.
blocks.push_back({first, last - first, encoded.
payload.size(), lane_bytes});
185 encoded.
payload.reserve(encoded.
payload.size() + deltas.size() * lane_bytes);
193 std::size_t expected_value_offset = 0;
194 std::size_t expected_payload_offset = 0;
196 for (
const auto& block : encoded.
blocks) {
197 if (block.value_offset != expected_value_offset || block.payload_offset != expected_payload_offset)
198 throw std::invalid_argument(
"non-contiguous fixed-width block metadata");
199 if (block.value_count == 0 || block.value_offset + block.value_count > encoded.
value_count)
200 throw std::invalid_argument(
"invalid fixed-width block value bounds");
201 if (block.lane_bytes != 1 && block.lane_bytes != 2 && block.lane_bytes != 4)
202 throw std::invalid_argument(
"invalid fixed-width lane size");
203 const std::size_t block_bytes = block.value_count *
static_cast<std::size_t
>(block.lane_bytes);
204 if (block.payload_offset > encoded.
payload.size() ||
205 block_bytes > encoded.
payload.size() - block.payload_offset)
206 throw std::invalid_argument(
"truncated fixed-width block");
209 for (std::size_t i = 0; i < block.value_count; ++i) {
211 encoded.
payload, block.payload_offset + i * block.lane_bytes, block.lane_bytes);
215 if (delta > std::numeric_limits<VertexId>::max() - value)
216 throw std::overflow_error(
"fixed-width delta decode overflow");
219 out[block.value_offset + i] = value;
221 expected_value_offset += block.value_count;
222 expected_payload_offset += block_bytes;
225 if (expected_value_offset != encoded.
value_count || expected_payload_offset != encoded.
payload.size())
226 throw std::invalid_argument(
"fixed-width adjacency metadata mismatch");
231 const std::vector<std::uint8_t>& encoded)
noexcept {
232 if (ids.empty())
return encoded.empty() ? 1.0 : 0.0;
233 const auto raw_bytes =
static_cast<double>(ids.size() *
sizeof(
VertexId));
234 return raw_bytes /
static_cast<double>(encoded.empty() ? 1 : encoded.size());
239 if (ids.empty())
return encoded.payload.empty() ? 1.0 : 0.0;
240 const auto raw_bytes =
static_cast<double>(ids.size() *
sizeof(
VertexId));
241 return raw_bytes /
static_cast<double>(encoded.payload.empty() ? 1 : encoded.payload.size());
double compression_ratio_bytes(const std::vector< VertexId > &ids, const std::vector< std::uint8_t > &encoded) noexcept
BlockedAdjacency blocked_variable_byte_encode(const std::vector< VertexId > &ids, std::size_t block_size=128)
std::vector< VertexId > delta_decode(const std::vector< std::uint32_t > &deltas)
void variable_byte_encode_uint32(std::uint32_t value, std::vector< std::uint8_t > &out)
std::vector< std::uint32_t > delta_encode(const std::vector< VertexId > &ids)
std::vector< VertexId > blocked_variable_byte_decode(const BlockedAdjacency &encoded)
std::vector< VertexId > variable_byte_delta_decode(const std::vector< std::uint8_t > &data)
std::uint32_t variable_byte_decode_uint32(const std::vector< std::uint8_t > &data, std::size_t &offset)
SimdFriendlyAdjacency simd_friendly_delta_encode(const std::vector< VertexId > &ids, std::size_t block_size=128)
std::vector< VertexId > simd_friendly_delta_decode(const SimdFriendlyAdjacency &encoded)
std::vector< std::uint8_t > variable_byte_delta_encode(const std::vector< VertexId > &ids)
std::uint8_t required_lane_bytes(std::uint32_t max_delta) noexcept
void append_fixed_width_uint32(std::uint32_t value, std::uint8_t lane_bytes, std::vector< std::uint8_t > &out)
std::uint32_t read_fixed_width_uint32(const std::vector< std::uint8_t > &payload, std::size_t offset, std::uint8_t lane_bytes)
std::vector< std::uint8_t > payload
std::vector< std::size_t > block_offsets
std::size_t payload_offset
std::vector< FixedWidthDeltaBlock > blocks
std::vector< std::uint8_t > payload