VeloGraphX
High-performance dynamic graph analytics in C++20
Loading...
Searching...
No Matches
compressed_decode_simd.hpp
Go to the documentation of this file.
1#pragma once
2
4
5#include <array>
6#include <cstddef>
7#include <cstdint>
8#include <limits>
9#include <stdexcept>
10#include <vector>
11
12#if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86)
13#include <immintrin.h>
14#endif
15#if defined(__aarch64__) || defined(__ARM_NEON)
16#include <arm_neon.h>
17#endif
18
19namespace velographx::storage {
20
21namespace detail {
22
23inline void accumulate_deltas(const std::uint32_t* deltas, std::size_t count,
24 VertexId* out) {
25 VertexId value = 0;
26 for (std::size_t i = 0; i < count; ++i) {
27 const auto delta = deltas[i];
28 if (i == 0) {
29 value = delta;
30 } else {
31 if (delta > std::numeric_limits<VertexId>::max() - value)
32 throw std::overflow_error("vectorized fixed-width delta decode overflow");
33 value += delta;
34 }
35 out[i] = value;
36 }
37}
38
39inline std::size_t unpack_scalar(const std::uint8_t* input, std::size_t count,
40 std::uint8_t lane_bytes, std::uint32_t* deltas) {
41 for (std::size_t i = 0; i < count; ++i) {
42 std::uint32_t value = 0;
43 for (std::uint8_t b = 0; b < lane_bytes; ++b)
44 value |= static_cast<std::uint32_t>(input[i * lane_bytes + b]) << (8U * b);
45 deltas[i] = value;
46 }
47 return count;
48}
49
50#if (defined(__GNUC__) || defined(__clang__)) && \
51 (defined(__x86_64__) || defined(__i386__))
52__attribute__((target("avx2")))
53inline std::size_t unpack_avx2(const std::uint8_t* input, std::size_t count,
54 std::uint8_t lane_bytes, std::uint32_t* deltas) {
55 std::size_t i = 0;
56 if (lane_bytes == 1) {
57 for (; i + 8 <= count; i += 8) {
58 const __m128i bytes = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(input + i));
59 const __m256i values = _mm256_cvtepu8_epi32(bytes);
60 _mm256_storeu_si256(reinterpret_cast<__m256i*>(deltas + i), values);
61 }
62 } else if (lane_bytes == 2) {
63 for (; i + 8 <= count; i += 8) {
64 const __m128i words = _mm_loadu_si128(reinterpret_cast<const __m128i*>(input + i * 2));
65 const __m256i values = _mm256_cvtepu16_epi32(words);
66 _mm256_storeu_si256(reinterpret_cast<__m256i*>(deltas + i), values);
67 }
68 } else if (lane_bytes == 4) {
69 for (; i + 8 <= count; i += 8) {
70 const __m256i values = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(input + i * 4));
71 _mm256_storeu_si256(reinterpret_cast<__m256i*>(deltas + i), values);
72 }
73 }
74 if (i < count) unpack_scalar(input + i * lane_bytes, count - i, lane_bytes, deltas + i);
75 return count;
76}
77
78inline bool avx2_available() noexcept {
79#if defined(__GNUC__) || defined(__clang__)
80 return __builtin_cpu_supports("avx2");
81#else
82 return false;
83#endif
84}
85#else
86inline bool avx2_available() noexcept { return false; }
87#endif
88
89#if defined(__aarch64__) || defined(__ARM_NEON)
90inline std::size_t unpack_neon(const std::uint8_t* input, std::size_t count,
91 std::uint8_t lane_bytes, std::uint32_t* deltas) {
92 std::size_t i = 0;
93 if (lane_bytes == 1) {
94 for (; i + 8 <= count; i += 8) {
95 const uint8x8_t b = vld1_u8(input + i);
96 const uint16x8_t w = vmovl_u8(b);
97 vst1q_u32(deltas + i, vmovl_u16(vget_low_u16(w)));
98 vst1q_u32(deltas + i + 4, vmovl_u16(vget_high_u16(w)));
99 }
100 } else if (lane_bytes == 2) {
101 for (; i + 8 <= count; i += 8) {
102 const uint16x8_t w = vld1q_u16(reinterpret_cast<const std::uint16_t*>(input + i * 2));
103 vst1q_u32(deltas + i, vmovl_u16(vget_low_u16(w)));
104 vst1q_u32(deltas + i + 4, vmovl_u16(vget_high_u16(w)));
105 }
106 } else if (lane_bytes == 4) {
107 for (; i + 8 <= count; i += 8) {
108 vst1q_u32(deltas + i, vld1q_u32(reinterpret_cast<const std::uint32_t*>(input + i * 4)));
109 vst1q_u32(deltas + i + 4, vld1q_u32(reinterpret_cast<const std::uint32_t*>(input + (i + 4) * 4)));
110 }
111 }
112 if (i < count) unpack_scalar(input + i * lane_bytes, count - i, lane_bytes, deltas + i);
113 return count;
114}
115#endif
116
117} // namespace detail
118
120
122#if (defined(__GNUC__) || defined(__clang__)) && \
123 (defined(__x86_64__) || defined(__i386__))
125#endif
126#if defined(__aarch64__) || defined(__ARM_NEON)
128#else
130#endif
131}
132
133inline std::vector<VertexId> simd_friendly_delta_decode_vectorized(
134 const SimdFriendlyAdjacency& encoded) {
135 std::vector<VertexId> out(encoded.value_count);
136 std::size_t expected_value_offset = 0;
137 std::size_t expected_payload_offset = 0;
138 const auto backend = vector_decode_backend();
139
140 for (const auto& block : encoded.blocks) {
141 if (block.value_offset != expected_value_offset || block.payload_offset != expected_payload_offset)
142 throw std::invalid_argument("non-contiguous fixed-width block metadata");
143 if (block.value_count == 0 || block.value_offset + block.value_count > encoded.value_count)
144 throw std::invalid_argument("invalid fixed-width block value bounds");
145 if (block.lane_bytes != 1 && block.lane_bytes != 2 && block.lane_bytes != 4)
146 throw std::invalid_argument("invalid fixed-width lane size");
147 const std::size_t block_bytes = block.value_count * static_cast<std::size_t>(block.lane_bytes);
148 if (block.payload_offset > encoded.payload.size() || block_bytes > encoded.payload.size() - block.payload_offset)
149 throw std::invalid_argument("truncated fixed-width block");
150
151 std::vector<std::uint32_t> deltas(block.value_count);
152 const auto* input = encoded.payload.data() + block.payload_offset;
153 if (backend == VectorDecodeBackend::avx2) {
154#if (defined(__GNUC__) || defined(__clang__)) && \
155 (defined(__x86_64__) || defined(__i386__))
156 detail::unpack_avx2(input, block.value_count, block.lane_bytes, deltas.data());
157#else
158 detail::unpack_scalar(input, block.value_count, block.lane_bytes, deltas.data());
159#endif
160 } else if (backend == VectorDecodeBackend::neon) {
161#if defined(__aarch64__) || defined(__ARM_NEON)
162 detail::unpack_neon(input, block.value_count, block.lane_bytes, deltas.data());
163#else
164 detail::unpack_scalar(input, block.value_count, block.lane_bytes, deltas.data());
165#endif
166 } else {
167 detail::unpack_scalar(input, block.value_count, block.lane_bytes, deltas.data());
168 }
169
170 detail::accumulate_deltas(deltas.data(), block.value_count, out.data() + block.value_offset);
171 expected_value_offset += block.value_count;
172 expected_payload_offset += block_bytes;
173 }
174
175 if (expected_value_offset != encoded.value_count || expected_payload_offset != encoded.payload.size())
176 throw std::invalid_argument("fixed-width adjacency metadata mismatch");
177 return out;
178}
179
180} // namespace velographx::storage
std::size_t unpack_scalar(const std::uint8_t *input, std::size_t count, std::uint8_t lane_bytes, std::uint32_t *deltas)
void accumulate_deltas(const std::uint32_t *deltas, std::size_t count, VertexId *out)
VectorDecodeBackend vector_decode_backend() noexcept
std::vector< VertexId > simd_friendly_delta_decode_vectorized(const SimdFriendlyAdjacency &encoded)
std::vector< FixedWidthDeltaBlock > blocks