Skip to content

Commit c469ca7

Browse files
Sherin Joseph Royclaude
andcommitted
perf: vectorize counter/checksum detection (183s -> 2.8s on 500k frames)
Found by testing on a real 500k-frame Alfa Romeo Giulia capture (ReCAN dataset): the iterrows × algorithms × bytes loop took 183s. Replaced with a per-ID numpy pass — build the (N,8) byte matrix once and compute every algorithm for every byte via total_xor ^ byte / (total_sum - byte) etc. (XOR is self-inverse; sums are cumulative). Identical results (30 counters, 32 checksums, 34 IDs), 65x faster. Existing NaN-safety tests still pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 59c550a commit c469ca7

1 file changed

Lines changed: 49 additions & 4 deletions

File tree

canlab/core/counter_checksum_detector.py

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,53 @@ def _detect_checksum_byte(frames: pd.DataFrame, byte_idx: int,
169169
return max(candidates, key=lambda x: x["confidence"])
170170

171171

172+
def _detect_checksums_vectorized(frames: pd.DataFrame, msg_id_int: int = 0,
173+
min_conf: float = 0.90) -> list[dict]:
174+
"""Vectorized replacement for per-byte _detect_checksum_byte over one ID.
175+
176+
Builds the (N,8) byte matrix once and computes every algorithm for every
177+
byte with numpy, instead of iterrows × algorithms × bytes. XOR is its own
178+
inverse and SUM/NIBBLE_SUM are cumulative, so "checksum over the other 7
179+
bytes" is (total ⊕/− this byte) — no Python per-row loop needed.
180+
"""
181+
cols = [c for c in BYTE_COLS if c in frames.columns]
182+
if len(cols) < 2:
183+
return []
184+
mat = frames[BYTE_COLS].to_numpy(dtype=np.float64) # NaN for missing bytes
185+
valid = ~np.isnan(mat).any(axis=1)
186+
mat = mat[valid].astype(np.int64)
187+
n = len(mat)
188+
if n < 5:
189+
return []
190+
191+
total_xor = np.zeros(n, dtype=np.int64)
192+
for k in range(8):
193+
total_xor ^= mat[:, k]
194+
total_sum = mat.sum(axis=1)
195+
nib = (mat & 0x0F) + ((mat >> 4) & 0x0F)
196+
total_nib = nib.sum(axis=1)
197+
hy_const = (msg_id_int >> 4) & 0xFF
198+
199+
out = []
200+
for k in range(8):
201+
col = mat[:, k]
202+
algos = {
203+
"XOR8": (total_xor ^ col),
204+
"SUM8": ((total_sum - col) & 0xFF),
205+
"NIBBLE_SUM": ((total_nib - nib[:, k]) & 0xFF),
206+
}
207+
if msg_id_int > 0:
208+
algos["HYUNDAI_XOR"] = ((total_xor ^ col) ^ hy_const)
209+
best = None
210+
for name, expected in algos.items():
211+
conf = float(np.mean(expected == col))
212+
if conf > min_conf and (best is None or conf > best["confidence"]):
213+
best = {"algorithm": name, "confidence": round(conf, 3)}
214+
if best:
215+
out.append({"byte": k, "col": f"B{k}", **best})
216+
return out
217+
218+
172219
# ── Main API ──────────────────────────────────────────────────────────────────
173220

174221
def detect_counters_and_checksums(df: pd.DataFrame) -> dict:
@@ -204,14 +251,12 @@ def detect_counters_and_checksums(df: pd.DataFrame) -> dict:
204251
series = frames[col].dropna()
205252
if series.empty:
206253
continue
207-
208254
ctr = _detect_counter_byte(series)
209255
if ctr:
210256
counters.append({"byte": i, "col": col, **ctr})
211257

212-
chk = _detect_checksum_byte(frames, i, mid_int)
213-
if chk:
214-
checksums.append({"byte": i, "col": col, **chk})
258+
# Checksums for all 8 bytes in one vectorized pass (was iterrows per byte).
259+
checksums = _detect_checksums_vectorized(frames, mid_int)
215260

216261
if counters or checksums:
217262
results[can_id] = {"counters": counters, "checksums": checksums}

0 commit comments

Comments
 (0)