Re: [RFC] alpha: optimize ip_fast_csum for BWX-capable CPUs

Mike Hlavac <[email protected]> Fri, 15 May 2026 16:18:41 -0400
Newsgroups org.kernel.vger.linux-alpha
Message-ID <[email protected]>

> On Apr 23, 2026, at 5:30 AM, Magnus Lindholm <[email protected]> wrote:
> 
> On Mon, Apr 20, 2026 at 1:44 AM Mike Hlavac <[email protected]> wrote:
>> 
>> 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+
>> 
> 
> Mike,
> 
> Thanks for working on this.  I tested the benchmark here with GCC
> 15.2.1 at -O2 on several CPU targets.  There does seem to be a real
> optimization opportunity, but I do not think this patch is the right
> shape yet.
> 
> Results here (EV67 833 MHz system) were roughly:
> 
>  ev4:  legacy ~0.365s, modern ~0.83s,  unrolled ~0.230s
>  ev5:  legacy ~0.394s, modern ~0.716s, unrolled ~0.230s
>  ev56: legacy ~0.304s, modern ~0.304s, unrolled ~0.171s
>  ev6:  legacy ~0.314s, modern ~0.313s, unrolled ~0.177s
> 
> The measured win comes from the manually unrolled ihl==5 case becoming
> straight-line code with no inner loop branch, not from the "modern C"
> rewrite by itself.  On ev56/ev6, the looped legacy and looped modern-C
> variants compile to essentially the same code, while on ev4/ev5 the
> plain uint16_t loop is actually much worse than legacy.
> 
> One caveat is that the benchmark repeatedly checksums the same small
> static header, so it mainly measures code shape with hot data.  That
> is useful, but it does not say much by itself about the end-to-end
> gain on real traffic. Touching old and stable code is alway a risk to
> introduce new problems.
> 
> I also think the interface change is a problem.  Today Alpha has an
> out-of-line ip_fast_csum() symbol.  This patch turns it into a header
> inline and makes the fallback path call do_csum(), but do_csum() is
> not exported, which looks wrong for modules.
> 
> The optimization itself does not seem out of line; other archs also
> treat ip_fast_csum() as a specialized IPv4-header routine.  What
> would seem safer here would be to keep the Alpha-specific fast path
> behind the existing ip_fast_csum() implementation, with the current
> checksum code as the fallback for larger ihl values and non-BWX
> friendly builds.
> 
> Thanks,
> Magnus

Magnus,

Thanks for the feedback.   I’ve re-worked the patch and micro-benchmark based on your comments.   The benchmark has more overhead from the XOR operations, but it gives a different view of the same picture with a non-static header.

Benchmark results on my machine (updated version):

Legacy Path: 2.648437 seconds
Unrolled C Path: 2.429688 seconds

Updated benchmark:

#include <stdio.h>
#include <time.h>
#include <stdint.h>

//generate more randomized header values
static inline uint32_t xorshift32(uint32_t *state)
{
    uint32_t x = *state;
    x ^= x << 13;
    x ^= x >> 17;
    x ^= x << 5;
    return *state = x;
}

// 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;
}

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};
   uint16_t header2[10] = {0x4500, 0x003c, 0x1c46, 0x4000, 0x4006, 0x0000, 0xac10, 0x0a63, 0xac10, 0x0a0c};
   long iterations = 10000000;
   uint32_t rng_state = 0x12345678;
   clock_t start, end;

// Test Legacy
   start = clock();
   for (long i = 0; i < iterations; i++) {
       for (int j = 0; j < 10; j++)
           header[j] ^= (uint16_t)xorshift32(&rng_state);
       volatile uint16_t res = csum_legacy(header);
   }
   end = clock();
   printf("Legacy Path: %f seconds\n", (double)(end - start) / CLOCKS_PER_SEC);

   rng_state = 0x12345678;

   // Test Unrolled
   start = clock();
   for (long i = 0; i < iterations; i++) {
       for (int j = 0; j < 10; j++)
           header2[j] ^= (uint16_t)xorshift32(&rng_state);
       volatile uint16_t res = csum_unrolled_c(header2);
   }
   end = clock();

   printf("Unrolled C Path: %f seconds\n", (double)(end - start) / CLOCKS_PER_SEC);
   return 0;
}

Updated patch:

Signed-off-by: Mike Hlavac <[email protected]>
Assisted-by: Google Gemini

--- /home/griffin/kernel-hacking/checksum/checksum-orig.c	2026-04-12 15:17:48.062299877 -0400
+++ arch/alpha/lib/checksum.c	2026-05-13 02:41:15.379142990 -0400
@@ -16,6 +16,7 @@

 #include <asm/byteorder.h>
 #include <asm/checksum.h>
+#include <asm/special_insns.h>

 static inline unsigned short from64to16(unsigned long x)
 {
@@ -142,12 +143,38 @@

 /*
  *	This is a version of ip_compute_csum() optimized for IP headers,
- *	which always checksum on 4 octet boundaries.
+ *	which always checksum on 4 octet boundaries.   Optimized version
+ *	provided for CPUs that see benefit for the unrolled logic.
  */
 __sum16 ip_fast_csum(const void *iph, unsigned int ihl)
 {
+    if (likely(ihl == 5) && !amask(AMASK_BWX)) {
+        const u16 *w = iph;
+        u64 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];
+
+        sum = (sum & 0xffff) + (sum >> 16);
+        sum = (sum & 0xffff) + (sum >> 16);
+
+        return (__force __sum16)~sum;
+    }
+	/* existing legacy implementation as fallback for:
+	*   - ihl != 5
+	*   - EV4/EV5 where the old code is better
+	*/
 	return (__force __sum16)~do_csum(iph,ihl*4);
 }
+
 EXPORT_SYMBOL(ip_fast_csum);

 /*