Skip to content

Commit 2012295

Browse files
better coalescence
1 parent 003f9b9 commit 2012295

File tree

3 files changed

+48
-17
lines changed

3 files changed

+48
-17
lines changed

libcanard/canard.c

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1640,22 +1640,37 @@ static canard_filter_t rx_filter_fuse(const canard_filter_t a, const canard_filt
16401640
// Filter selectivity metric; see the Cyphal/CAN specification. Greater values ==> stronger filter.
16411641
static byte_t rx_filter_rank(const canard_filter_t a) { return popcount(a.extended_mask); }
16421642

1643-
// Modifies the filter array such that the new filter is accepted. See Cyphal/CAN Spec.
1643+
// Modifies the filter array such that the new filter is accepted. See Cyphal/CAN Spec. Requires count>0.
16441644
static void rx_filter_coalesce_into(const size_t count, canard_filter_t* const into, const canard_filter_t new)
16451645
{
1646-
// Currently we're using a simplified fast algorithm that doesn't attempt fusing existing entries with each other.
1647-
// This may result in suboptimal configurations but is faster.
1648-
size_t index = 0;
1649-
byte_t best_rank = 0;
1646+
CANARD_ASSERT((count > 0U) && (into != NULL));
1647+
// Find the most similar pair by trying all pair combinations (including the incoming filter).
1648+
// This is O(N^2) but yields better final filter quality than fusing only with the incoming filter.
1649+
// The complexity is acceptable because N is controlled by the application and is small;
1650+
// plus, this behavior can be disabled completely by using at least as many filters as there are subscriptions.
1651+
bool initialized = false;
1652+
size_t best_i = 0;
1653+
size_t best_j = count;
1654+
byte_t best_rank = 0;
1655+
canard_filter_t best_fuse = { 0 };
16501656
for (size_t i = 0; i < count; i++) {
1651-
const byte_t fused_rank = rx_filter_rank(rx_filter_fuse(into[i], new));
1652-
if (fused_rank >= best_rank) { // use >= to prefer later filters where v0 is
1653-
best_rank = fused_rank;
1654-
index = i;
1657+
for (size_t j = i + 1U; j <= count; j++) { // j==count denotes the incoming filter
1658+
const canard_filter_t f = rx_filter_fuse(into[i], (j < count) ? into[j] : new);
1659+
const byte_t r = rx_filter_rank(f);
1660+
if ((!initialized) || (r >= best_rank)) { // use >= to prefer later filters where v0 is
1661+
initialized = true;
1662+
best_i = i;
1663+
best_j = j;
1664+
best_rank = r;
1665+
best_fuse = f;
1666+
}
16551667
}
16561668
}
1657-
CANARD_ASSERT(index < count);
1658-
into[index] = rx_filter_fuse(into[index], new);
1669+
CANARD_ASSERT(initialized && (best_i < count) && (best_j <= count));
1670+
into[best_i] = best_fuse;
1671+
if (best_j < count) {
1672+
into[best_j] = new;
1673+
}
16591674
}
16601675

16611676
// Recompute the filter configuration and apply. Returns true on success, false on driver error.

libcanard/canard.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -386,12 +386,12 @@ struct canard_t
386386
/// and collisions, and will automatically migrate to a free node-ID shall a collision be detected.
387387
/// If manual allocation is desired, use the corresponding function to set the node-ID after initialization.
388388
///
389-
/// The filter count is the number of CAN acceptance filters that the library can utilize. If there are fewer filters
390-
/// than subscriptions, similar filters will be coalesced. It is possible to pass zero filters if filtering is
391-
/// unneeded/unsupported. When the number of active subscriptions exceeds the number of available filters,
392-
/// filter coalescence is performed, which however has a high complexity bound; it is thus recommended that the number
393-
/// of filters is either large enough to accommodate all subscriptions, or small enough in the single digits where
394-
/// the load remains low.
389+
/// The filter count is the number of CAN acceptance filters that the stack can utilize. It is possible to pass zero
390+
/// filters if filtering is unneeded/unsupported. When the number of active subscriptions exceeds the number of
391+
/// available filters, filter coalescence is performed, which however has a high complexity bound; it is thus
392+
/// recommended that the number of filters is either large enough to accommodate all subscriptions,
393+
/// or small enough in the single digits where the coalescence load remains low. The filter configuration is
394+
/// recomputed and applied on every poll() following a change in the subscription set or local node-ID.
395395
///
396396
/// CAN FD mode is selected by default for outgoing frames; override the fd flag to change the mode if needed.
397397
///

tests/src/test_intrusive_rx_filter.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,21 @@ static void test_rx_filter_coalesce_into_tie_prefers_later_index(void)
236236
TEST_ASSERT_EQUAL_HEX32(fused_index1.extended_mask, into[1].extended_mask);
237237
}
238238

239+
static void test_rx_filter_coalesce_into_merges_existing_pair_when_best(void)
240+
{
241+
canard_filter_t into[2] = {
242+
{ .extended_can_id = 0x0UL, .extended_mask = 0xFUL },
243+
{ .extended_can_id = 0x1UL, .extended_mask = 0xFUL },
244+
};
245+
const canard_filter_t new_filter = { .extended_can_id = 0xFUL, .extended_mask = 0xFUL };
246+
const canard_filter_t fused_existing = rx_filter_fuse(into[0], into[1]);
247+
rx_filter_coalesce_into(2U, into, new_filter);
248+
TEST_ASSERT_EQUAL_HEX32(fused_existing.extended_can_id, into[0].extended_can_id);
249+
TEST_ASSERT_EQUAL_HEX32(fused_existing.extended_mask, into[0].extended_mask);
250+
TEST_ASSERT_EQUAL_HEX32(new_filter.extended_can_id, into[1].extended_can_id);
251+
TEST_ASSERT_EQUAL_HEX32(new_filter.extended_mask, into[1].extended_mask);
252+
}
253+
239254
static void test_rx_filter_coalesce_into_single_entry(void)
240255
{
241256
canard_filter_t into[1] = { { .extended_can_id = 0x3UL, .extended_mask = 0xFUL } };
@@ -268,6 +283,7 @@ int main(void)
268283

269284
RUN_TEST(test_rx_filter_coalesce_into_selects_best_rank);
270285
RUN_TEST(test_rx_filter_coalesce_into_tie_prefers_later_index);
286+
RUN_TEST(test_rx_filter_coalesce_into_merges_existing_pair_when_best);
271287
RUN_TEST(test_rx_filter_coalesce_into_single_entry);
272288

273289
return UNITY_END();

0 commit comments

Comments
 (0)