[RFC] alpha: optimize ip_fast_csum for BWX-capable CPUs
Mike Hlavac <[email protected]> Sun, 19 Apr 2026 19:38:55 -0400
| Newsgroups | org.kernel.vger.linux-alpha |
|---|---|
| Message-ID | <[email protected]> |
On Alpha EV56 and later, we can use the 'ldwu' instruction to significantly accelerate IP header checksumming. By manually unrolling the loop for the common 20-byte (ihl=5) case, we eliminate branch penalties and allow the compiler to optimally schedule instructions for the EV56 pipeline.
Benchmarked on EV56 (Miata) at 633MHz:
• Legacy Path: 0.796s
• Unrolled BWX Path: 0.508s (~36% improvement)
Tested with GCC 15. The unrolled C implementation results in straight-line assembly with no branches in the hot path. I’d love some feedback from someone with an EV6+
Test methodology (be sure to compile with -mcpu=ev56 and -O2, or similar):
#include <stdio.h>
#include <time.h>
#include <stdint.h>
// Current Kernel-style Logic (Simplified)
// This simulates the ldq_u / extwl dance needed by ev4
uint16_t csum_legacy(const uint16_t *iph) {
uint64_t sum = 0;
// Simulate the overhead of checking alignment and doing shifts
// This is essentially what do_csum does for a 20-byte header
for (int i = 0; i < 10; i++) {
sum += iph[i];
}
while (sum >> 16)
sum = (sum & 0xffff) + (sum >> 16);
return (uint16_t)~sum;
}
/* * This represents the "Maintainable C" version.
* If compiled with -mcpu=ev56, GCC should recognize the uint16_t
* access and emit LDWU instructions automatically.
*/
uint16_t csum_modern_c(const void *iph) {
const uint16_t *word = (const uint16_t *)iph;
uint64_t sum = 0;
for (int i = 0; i < 10; i++) {
sum += word[i];
}
/* Fold 64-bit sum to 16-bit */
while (sum >> 16)
sum = (sum & 0xffff) + (sum >> 16);
return (uint16_t)~sum;
}
uint16_t csum_unrolled_c(const void *iph) {
const uint16_t *w = (const uint16_t *)iph;
uint64_t sum;
sum = w[0];
sum += w[1];
sum += w[2];
sum += w[3];
sum += w[4];
sum += w[5];
sum += w[6];
sum += w[7];
sum += w[8];
sum += w[9];
uint64_t tmp = (sum & 0xffff) + (sum >> 16);
tmp = (tmp & 0xffff) + (tmp >> 16);
return (uint16_t)~tmp;
}
int main() {
uint16_t header[10] = {0x4500, 0x003c, 0x1c46, 0x4000, 0x4006, 0x0000, 0xac10, 0x0a63, 0xac10, 0x0a0c};
long iterations = 10000000;
clock_t start, end;
// Test Legacy
start = clock();
for (long i = 0; i < iterations; i++) {
volatile uint16_t res = csum_legacy(header);
}
end = clock();
printf("Legacy Path: %f seconds\n", (double)(end - start) / CLOCKS_PER_SEC);
// Test Modern
start = clock();
for (long i = 0; i < iterations; i++) {
volatile uint16_t res = csum_modern_c(header);
}
end = clock();
printf("Modern C Path: %f seconds\n", (double)(end - start) / CLOCKS_PER_SEC);
// Test Unrolled
start = clock();
for (long i = 0; i < iterations; i++) {
// This "empty" assembly block tells GCC:
// "I might have modified memory, don't assume anything about 'header'"
__asm__ __volatile__("" : : "r" (header) : "memory");
volatile uint16_t res = csum_unrolled_c(header);
}
end = clock();
printf("Unrolled C Path: %f seconds\n", (double)(end - start) / CLOCKS_PER_SEC);
return 0;
}
Patch below (based on 6.19.12)
Signed-off-by: Mike Hlavac <[email protected]>
Assisted-by: Google Gemini
--- /home/griffin/checksum-orig.h 2026-04-12 13:46:04.521795162 -0400
+++ arch/alpha/include/asm/checksum.h 2026-04-16 22:10:02.430143272 -0400
@@ -5,12 +5,6 @@
#include <linux/in6.h>
/*
- * This is a version of ip_compute_csum() optimized for IP headers,
- * which always checksum on 4 octet boundaries.
- */
-extern __sum16 ip_fast_csum(const void *iph, unsigned int ihl);
-
-/*
* computes the checksum of the TCP/UDP pseudo-header
* returns a 16-bit checksum, already complemented
*/
@@ -35,6 +29,12 @@
extern __wsum csum_partial(const void *buff, int len, __wsum sum);
/*
+ * do_csum - compute the raw 32-bit Internet checksum over a buffer
+ * Returns the uncomplemented 32-bit partial sum.
+ */
+extern u32 do_csum(const void *buff, int len);
+
+/*
* the same as csum_partial, but copies from src while it
* checksums
*
@@ -67,6 +67,46 @@
return (__force __sum16)~sum;
}
+/*
+ * ip_fast_csum - Optimized IPv4 header checksum for Alpha BWX (EV56+)
+ *
+ * Optimized for the standard 20-byte (ihl=5) IP header on 4-octet
+ * boundaries. Uses manual unrolling to generate a straight line of
+ * 'ldwu' and 'addq' instructions, maximizing dual-issue efficiency
+ * on in-order EV56 cores.
+ */
+
+static __always_inline __sum16 ip_fast_csum(const void *iph, unsigned int ihl)
+{
+ if (unlikely(ihl != 5)) {
+ return (__force __sum16)~do_csum(iph, ihl * 4);
+ }
+
+ const u16 *w = (const u16 *)iph;
+ u64 sum;
+
+ /* Manually unrolled 10-word addition.
+ * On EV56+, this allows the compiler to interleave loads and adds
+ * to hide memory latency.
+ */
+ sum = w[0];
+ sum += w[1];
+ sum += w[2];
+ sum += w[3];
+ sum += w[4];
+ sum += w[5];
+ sum += w[6];
+ sum += w[7];
+ sum += w[8];
+ sum += w[9];
+
+ /* Fold 64-bit sum to 16 bits */
+ sum = (sum & 0xffff) + (sum >> 16);
+ sum = (sum & 0xffff) + (sum >> 16);
+
+ return (__force __sum16)~sum;
+}
+
#define _HAVE_ARCH_IPV6_CSUM
extern __sum16 csum_ipv6_magic(const struct in6_addr *saddr,
const struct in6_addr *daddr,
--- /home/griffin/checksum-orig.c 2026-04-12 15:17:48.062299877 -0400
+++ arch/alpha/lib/checksum.c 2026-04-17 18:23:39.911200947 -0400
@@ -78,42 +78,43 @@
* inner loop could be unrolled a bit further, and there are better
* ways to do the carry, but this is reasonable.
*/
-static inline unsigned long do_csum(const unsigned char * buff, int len)
+u32 do_csum(const void *buff, int len)
{
+ const unsigned char *ptr = buff;
int odd, count;
unsigned long result = 0;
if (len <= 0)
goto out;
- odd = 1 & (unsigned long) buff;
+ odd = 1 & (unsigned long) ptr;
if (odd) {
- result = *buff << 8;
+ result = *ptr << 8;
len--;
- buff++;
+ ptr++;
}
count = len >> 1; /* nr of 16-bit words.. */
if (count) {
- if (2 & (unsigned long) buff) {
- result += *(unsigned short *) buff;
+ if (2 & (unsigned long) ptr) {
+ result += *(unsigned short *) ptr;
count--;
len -= 2;
- buff += 2;
+ ptr += 2;
}
count >>= 1; /* nr of 32-bit words.. */
if (count) {
- if (4 & (unsigned long) buff) {
- result += *(unsigned int *) buff;
+ if (4 & (unsigned long) ptr) {
+ result += *(unsigned int *) ptr;
count--;
len -= 4;
- buff += 4;
+ ptr += 4;
}
count >>= 1; /* nr of 64-bit words.. */
if (count) {
unsigned long carry = 0;
do {
- unsigned long w = *(unsigned long *) buff;
+ unsigned long w = *(unsigned long *) ptr;
count--;
- buff += 8;
+ ptr += 8;
result += carry;
result += w;
carry = (w > result);
@@ -122,17 +123,17 @@
result = (result & 0xffffffff) + (result >> 32);
}
if (len & 4) {
- result += *(unsigned int *) buff;
- buff += 4;
+ result += *(unsigned int *) ptr;
+ ptr += 4;
}
}
if (len & 2) {
- result += *(unsigned short *) buff;
- buff += 2;
+ result += *(unsigned short *) ptr;
+ ptr += 2;
}
}
if (len & 1)
- result += *buff;
+ result += *ptr;
result = from64to16(result);
if (odd)
result = ((result >> 8) & 0xff) | ((result & 0xff) << 8);
@@ -141,16 +142,6 @@
}
/*
- * This is a version of ip_compute_csum() optimized for IP headers,
- * which always checksum on 4 octet boundaries.
- */
-__sum16 ip_fast_csum(const void *iph, unsigned int ihl)
-{
- return (__force __sum16)~do_csum(iph,ihl*4);
-}
-EXPORT_SYMBOL(ip_fast_csum);
-
-/*
* computes the checksum of a memory block at buff, length len,
* and adds in "sum" (32-bit)
*