VeloGraphX
High-performance dynamic graph analytics in C++20
Loading...
Searching...
No Matches
compressed_adjacency.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <cstddef>
5#include <cstdint>
6#include <limits>
7#include <stdexcept>
8#include <vector>
9
11
12using VertexId = std::uint32_t;
13
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());
17 VertexId prev = 0;
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]);
21 prev = ids[i];
22 }
23 return out;
24}
25
26inline std::vector<VertexId> delta_decode(const std::vector<std::uint32_t>& deltas) {
27 std::vector<VertexId> out;
28 out.reserve(deltas.size());
29 VertexId value = 0;
30 for (std::size_t i = 0; i < deltas.size(); ++i) {
31 if (i == 0) {
32 value = deltas[i];
33 } else {
34 if (deltas[i] > std::numeric_limits<VertexId>::max() - value)
35 throw std::overflow_error("delta decode overflow");
36 value += deltas[i];
37 }
38 out.push_back(value);
39 }
40 return out;
41}
42
43inline void variable_byte_encode_uint32(std::uint32_t value, std::vector<std::uint8_t>& out) {
44 while (value >= 0x80U) {
45 out.push_back(static_cast<std::uint8_t>((value & 0x7FU) | 0x80U));
46 value >>= 7U;
47 }
48 out.push_back(static_cast<std::uint8_t>(value));
49}
50
51inline std::uint32_t variable_byte_decode_uint32(const std::vector<std::uint8_t>& data,
52 std::size_t& offset) {
53 std::uint32_t value = 0;
54 unsigned shift = 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;
60 shift += 7U;
61 if (shift > 28U) throw std::overflow_error("invalid varbyte uint32");
62 }
63 throw std::invalid_argument("truncated varbyte stream");
64}
65
66inline std::vector<std::uint8_t> variable_byte_delta_encode(const std::vector<VertexId>& ids) {
67 const auto deltas = delta_encode(ids);
68 std::vector<std::uint8_t> out;
69 out.reserve(deltas.size() * 2U);
70 for (auto delta : deltas) variable_byte_encode_uint32(delta, out);
71 return out;
72}
73
74inline std::vector<VertexId> variable_byte_delta_decode(const std::vector<std::uint8_t>& data) {
75 std::vector<std::uint32_t> deltas;
76 std::size_t offset = 0;
77 while (offset < data.size()) deltas.push_back(variable_byte_decode_uint32(data, offset));
78 return delta_decode(deltas);
79}
80
82 std::size_t block_size{128};
83 std::vector<std::size_t> block_offsets;
84 std::vector<std::uint8_t> payload;
85 std::size_t value_count{0};
86};
87
88inline BlockedAdjacency blocked_variable_byte_encode(const std::vector<VertexId>& ids,
89 std::size_t block_size = 128) {
90 if (block_size == 0) throw std::invalid_argument("block size must be positive");
91 BlockedAdjacency result;
92 result.block_size = block_size;
93 result.value_count = ids.size();
94 result.block_offsets.push_back(0);
95
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));
100 const auto encoded = variable_byte_delta_encode(block);
101 result.payload.insert(result.payload.end(), encoded.begin(), encoded.end());
102 result.block_offsets.push_back(result.payload.size());
103 }
104 return result;
105}
106
107inline std::vector<VertexId> blocked_variable_byte_decode(const BlockedAdjacency& encoded) {
108 if (encoded.block_offsets.empty() || encoded.block_offsets.front() != 0)
109 throw std::invalid_argument("invalid blocked adjacency offsets");
110 std::vector<VertexId> out;
111 out.reserve(encoded.value_count);
112 for (std::size_t block = 0; block + 1 < encoded.block_offsets.size(); ++block) {
113 const auto begin = encoded.block_offsets[block];
114 const auto end = encoded.block_offsets[block + 1];
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));
118 const auto decoded = variable_byte_delta_decode(bytes);
119 out.insert(out.end(), decoded.begin(), decoded.end());
120 }
121 if (out.size() != encoded.value_count) throw std::invalid_argument("blocked adjacency count mismatch");
122 return out;
123}
124
126 std::size_t value_offset{0};
127 std::size_t value_count{0};
128 std::size_t payload_offset{0};
129 std::uint8_t lane_bytes{4};
130};
131
133 std::size_t block_size{128};
134 std::size_t value_count{0};
135 std::vector<FixedWidthDeltaBlock> blocks;
136 std::vector<std::uint8_t> payload;
137};
138
139inline std::uint8_t required_lane_bytes(std::uint32_t max_delta) noexcept {
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;
142 return 4;
143}
144
145inline void append_fixed_width_uint32(std::uint32_t value, std::uint8_t lane_bytes,
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));
149}
150
151inline std::uint32_t read_fixed_width_uint32(const std::vector<std::uint8_t>& payload,
152 std::size_t offset,
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);
161 return value;
162}
163
164inline SimdFriendlyAdjacency simd_friendly_delta_encode(const std::vector<VertexId>& ids,
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");
168
169 SimdFriendlyAdjacency encoded;
170 encoded.block_size = block_size;
171 encoded.value_count = ids.size();
172
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);
182 }
183 const auto lane_bytes = required_lane_bytes(max_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);
186 for (auto delta : deltas) append_fixed_width_uint32(delta, lane_bytes, encoded.payload);
187 }
188 return encoded;
189}
190
191inline std::vector<VertexId> simd_friendly_delta_decode(const SimdFriendlyAdjacency& encoded) {
192 std::vector<VertexId> out(encoded.value_count);
193 std::size_t expected_value_offset = 0;
194 std::size_t expected_payload_offset = 0;
195
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");
207
208 VertexId value = 0;
209 for (std::size_t i = 0; i < block.value_count; ++i) {
210 const auto delta = read_fixed_width_uint32(
211 encoded.payload, block.payload_offset + i * block.lane_bytes, block.lane_bytes);
212 if (i == 0) {
213 value = delta;
214 } else {
215 if (delta > std::numeric_limits<VertexId>::max() - value)
216 throw std::overflow_error("fixed-width delta decode overflow");
217 value += delta;
218 }
219 out[block.value_offset + i] = value;
220 }
221 expected_value_offset += block.value_count;
222 expected_payload_offset += block_bytes;
223 }
224
225 if (expected_value_offset != encoded.value_count || expected_payload_offset != encoded.payload.size())
226 throw std::invalid_argument("fixed-width adjacency metadata mismatch");
227 return out;
228}
229
230inline double compression_ratio_bytes(const std::vector<VertexId>& ids,
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());
235}
236
237inline double compression_ratio_bytes(const std::vector<VertexId>& ids,
238 const SimdFriendlyAdjacency& encoded) noexcept {
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());
242}
243
244} // namespace velographx::storage
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< FixedWidthDeltaBlock > blocks