VeloGraphX
High-performance dynamic graph analytics in C++20
Loading...
Searching...
No Matches
intersection.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <bit>
5#include <cstddef>
6#include <cstdint>
7#include <span>
8#include <vector>
9
11
12#if defined(__x86_64__) || defined(_M_X64)
13#include <immintrin.h>
14#endif
15
16#if defined(__ARM_NEON) || defined(__ARM_NEON__)
17#include <arm_neon.h>
18#endif
19
20namespace velographx::kernels {
21
22using VertexId = std::uint32_t;
23
25
26inline std::size_t scalar_intersection(std::span<const VertexId> a,
27 std::span<const VertexId> b) {
28 std::size_t i = 0, j = 0, count = 0;
29 while (i < a.size() && j < b.size()) {
30 if (a[i] == b[j]) {
31 ++count;
32 ++i;
33 ++j;
34 } else if (a[i] < b[j]) {
35 ++i;
36 } else {
37 ++j;
38 }
39 }
40 return count;
41}
42
43inline std::size_t galloping_intersection(std::span<const VertexId> smaller,
44 std::span<const VertexId> larger) {
45 std::size_t count = 0;
46 for (const auto value : smaller) {
47 count += std::binary_search(larger.begin(), larger.end(), value) ? 1u : 0u;
48 }
49 return count;
50}
51
52inline std::size_t bitmap_intersection(std::span<const VertexId> a,
53 std::span<const VertexId> b) {
54 if (a.empty() || b.empty()) return 0;
55 const auto max_value = std::max(a.back(), b.back());
56 const std::size_t word_count = static_cast<std::size_t>(max_value) / 64u + 1u;
57 std::vector<std::uint64_t> bitmap(word_count, 0);
58 for (const auto value : a) {
59 bitmap[value >> 6u] |= (std::uint64_t{1} << (value & 63u));
60 }
61 std::size_t count = 0;
62 for (const auto value : b) {
63 count += (bitmap[value >> 6u] >> (value & 63u)) & 1u;
64 }
65 return count;
66}
67
68#if (defined(__GNUC__) || defined(__clang__)) && (defined(__x86_64__) || defined(_M_X64))
69__attribute__((target("avx2"))) inline std::size_t avx2_intersection_impl(
70 std::span<const VertexId> a, std::span<const VertexId> b) {
71 constexpr std::size_t lanes = 8;
72 std::size_t i = 0, j = 0, count = 0;
73
74 while (i + lanes <= a.size() && j + lanes <= b.size()) {
75 const auto vb = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(b.data() + j));
76 std::uint32_t matched_mask = 0;
77 for (std::size_t lane = 0; lane < lanes; ++lane) {
78 const auto va = _mm256_set1_epi32(static_cast<int>(a[i + lane]));
79 const auto eq = _mm256_cmpeq_epi32(va, vb);
80 const auto mask = static_cast<unsigned>(_mm256_movemask_ps(_mm256_castsi256_ps(eq)));
81 if (mask != 0) matched_mask |= static_cast<std::uint32_t>(1u << lane);
82 }
83 count += std::popcount(matched_mask);
84 const auto a_last = a[i + lanes - 1];
85 const auto b_last = b[j + lanes - 1];
86 if (a_last <= b_last) i += lanes;
87 if (b_last <= a_last) j += lanes;
88 }
89 count += scalar_intersection(a.subspan(i), b.subspan(j));
90 return count;
91}
92
93__attribute__((target("avx512f"))) inline std::size_t avx512_intersection_impl(
94 std::span<const VertexId> a, std::span<const VertexId> b) {
95 constexpr std::size_t lanes = 16;
96 std::size_t i = 0, j = 0, count = 0;
97
98 while (i + lanes <= a.size() && j + lanes <= b.size()) {
99 const auto vb = _mm512_loadu_si512(reinterpret_cast<const void*>(b.data() + j));
100 std::uint32_t matched_mask = 0;
101 for (std::size_t lane = 0; lane < lanes; ++lane) {
102 const auto va = _mm512_set1_epi32(static_cast<int>(a[i + lane]));
103 if (_mm512_cmpeq_epu32_mask(va, vb) != 0) {
104 matched_mask |= static_cast<std::uint32_t>(1u << lane);
105 }
106 }
107 count += std::popcount(matched_mask);
108 const auto a_last = a[i + lanes - 1];
109 const auto b_last = b[j + lanes - 1];
110 if (a_last <= b_last) i += lanes;
111 if (b_last <= a_last) j += lanes;
112 }
113 count += scalar_intersection(a.subspan(i), b.subspan(j));
114 return count;
115}
116#endif
117
118inline std::size_t avx2_intersection(std::span<const VertexId> a,
119 std::span<const VertexId> b) {
120#if (defined(__GNUC__) || defined(__clang__)) && (defined(__x86_64__) || defined(_M_X64))
121 if (detect_cpu_features().avx2) return avx2_intersection_impl(a, b);
122#endif
123 return scalar_intersection(a, b);
124}
125
126inline std::size_t avx512_intersection(std::span<const VertexId> a,
127 std::span<const VertexId> b) {
128#if (defined(__GNUC__) || defined(__clang__)) && (defined(__x86_64__) || defined(_M_X64))
129 if (detect_cpu_features().avx512f) return avx512_intersection_impl(a, b);
130#endif
131 return scalar_intersection(a, b);
132}
133
134inline std::size_t neon_intersection(std::span<const VertexId> a,
135 std::span<const VertexId> b) {
136#if defined(__ARM_NEON) || defined(__ARM_NEON__)
137 constexpr std::size_t lanes = 4;
138 std::size_t i = 0, j = 0, count = 0;
139 while (i + lanes <= a.size() && j + lanes <= b.size()) {
140 const auto vb = vld1q_u32(b.data() + j);
141 for (std::size_t lane = 0; lane < lanes; ++lane) {
142 const auto va = vdupq_n_u32(a[i + lane]);
143 const auto eq = vceqq_u32(va, vb);
144 const auto pair = vpaddq_u32(eq, eq);
145 const auto reduced = vpaddq_u32(pair, pair);
146 if (vgetq_lane_u32(reduced, 0) != 0) ++count;
147 }
148 const auto a_last = a[i + lanes - 1];
149 const auto b_last = b[j + lanes - 1];
150 if (a_last <= b_last) i += lanes;
151 if (b_last <= a_last) j += lanes;
152 }
153 count += scalar_intersection(a.subspan(i), b.subspan(j));
154 return count;
155#else
156 return scalar_intersection(a, b);
157#endif
158}
159
160inline bool bitmap_is_efficient(std::span<const VertexId> a,
161 std::span<const VertexId> b) {
162 if (a.empty() || b.empty()) return false;
163 const auto max_value = std::max(a.back(), b.back());
164 const auto combined = a.size() + b.size();
165 return combined >= 1024 && static_cast<std::size_t>(max_value) <= combined * 8u;
166}
167
168inline IntersectionKernel select_intersection(std::span<const VertexId> a,
169 std::span<const VertexId> b) {
170 const auto min_size = std::min(a.size(), b.size());
171 const auto max_size = std::max(a.size(), b.size());
172 if (min_size == 0) return IntersectionKernel::scalar_merge;
174 if (max_size > 16 * min_size) return IntersectionKernel::galloping;
175
176 const auto features = detect_cpu_features();
177 if (features.avx512f && min_size >= 64) return IntersectionKernel::avx512;
178 if (features.avx2 && min_size >= 32) return IntersectionKernel::avx2;
179 if (features.neon && min_size >= 32) return IntersectionKernel::neon;
181}
182
183inline std::size_t adaptive_intersection(std::span<const VertexId> a,
184 std::span<const VertexId> b) {
185 switch (select_intersection(a, b)) {
187 return a.size() <= b.size() ? galloping_intersection(a, b)
190 return bitmap_intersection(a, b);
192 return avx2_intersection(a, b);
194 return avx512_intersection(a, b);
196 return neon_intersection(a, b);
197 default:
198 return scalar_intersection(a, b);
199 }
200}
201
202} // namespace velographx::kernels
std::size_t avx512_intersection(std::span< const VertexId > a, std::span< const VertexId > b)
std::size_t galloping_intersection(std::span< const VertexId > smaller, std::span< const VertexId > larger)
std::size_t adaptive_intersection(std::span< const VertexId > a, std::span< const VertexId > b)
std::size_t neon_intersection(std::span< const VertexId > a, std::span< const VertexId > b)
CpuFeatures detect_cpu_features()
std::size_t avx2_intersection(std::span< const VertexId > a, std::span< const VertexId > b)
std::uint32_t VertexId
bool bitmap_is_efficient(std::span< const VertexId > a, std::span< const VertexId > b)
IntersectionKernel select_intersection(std::span< const VertexId > a, std::span< const VertexId > b)
std::size_t bitmap_intersection(std::span< const VertexId > a, std::span< const VertexId > b)
std::size_t scalar_intersection(std::span< const VertexId > a, std::span< const VertexId > b)
std::uint32_t VertexId
Definition frontier.hpp:6