Project

General

Profile

Feature #22463 » memchr_swar.c

watson1978 (Shizuo Fujita), 10/22/2026 04:24 AM

 
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <emmintrin.h>
#include <windows.h>

typedef const void *(*fn_t)(const void *, int, size_t);

#define ONES UINT64_C(0x0101010101010101)
#define HIGHS UINT64_C(0x8080808080808080)
#define HASZERO(x) (((x) - ONES) & ~(x) & HIGHS)

static inline uint64_t load64(const unsigned char *p) {
uint64_t v;
memcpy(&v, p, 8);
return v;
}

__attribute__((noinline)) static const void *naive_memchr(const void *s, int c, size_t n) {
const unsigned char *p = s;
for (size_t i = 0; i < n; i++) if (p[i] == (unsigned char)c) return p + i;
return NULL;
}

__attribute__((noinline)) static const void *swar_memchr(const void *s, int c, size_t n) {
const unsigned char *p = s;
uint64_t k = ONES * (unsigned char)c;
while (n >= 8) {
uint64_t m = HASZERO(load64(p) ^ k);
if (m) return p + (__builtin_ctzll(m) >> 3);
p += 8;
n -= 8;
}
for (; n; p++, n--) if (*p == (unsigned char)c) return p;
return NULL;
}

__attribute__((noinline)) static const void *swar2_memchr(const void *s, int c, size_t n) {
const unsigned char *p = s;
uint64_t k = ONES * (unsigned char)c;
while (n >= 16) {
uint64_t m0 = HASZERO(load64(p) ^ k);
uint64_t m1 = HASZERO(load64(p + 8) ^ k);
if (m0 | m1) {
if (m0) return p + (__builtin_ctzll(m0) >> 3);
return p + 8 + (__builtin_ctzll(m1) >> 3);
}
p += 16;
n -= 16;
}
return swar_memchr(p, c, n);
}

__attribute__((noinline)) static const void *sse2_memchr(const void *s, int c, size_t n) {
const unsigned char *p = s;
__m128i needle = _mm_set1_epi8((char)c);
while (n >= 16) {
int mask = _mm_movemask_epi8(_mm_cmpeq_epi8(_mm_loadu_si128((const __m128i *)p), needle));
if (mask) return p + __builtin_ctz(mask);
p += 16;
n -= 16;
}
for (; n; p++, n--) if (*p == (unsigned char)c) return p;
return NULL;
}

__attribute__((noinline)) static const void *sse2x4_memchr(const void *s, int c, size_t n) {
const unsigned char *p = s;
__m128i needle = _mm_set1_epi8((char)c);
while (n >= 64) {
__m128i e0 = _mm_cmpeq_epi8(_mm_loadu_si128((const __m128i *)p), needle);
__m128i e1 = _mm_cmpeq_epi8(_mm_loadu_si128((const __m128i *)(p + 16)), needle);
__m128i e2 = _mm_cmpeq_epi8(_mm_loadu_si128((const __m128i *)(p + 32)), needle);
__m128i e3 = _mm_cmpeq_epi8(_mm_loadu_si128((const __m128i *)(p + 48)), needle);
if (_mm_movemask_epi8(_mm_or_si128(_mm_or_si128(e0, e1), _mm_or_si128(e2, e3)))) {
int m;
if ((m = _mm_movemask_epi8(e0))) return p + __builtin_ctz(m);
if ((m = _mm_movemask_epi8(e1))) return p + 16 + __builtin_ctz(m);
if ((m = _mm_movemask_epi8(e2))) return p + 32 + __builtin_ctz(m);
return p + 48 + __builtin_ctz(_mm_movemask_epi8(e3));
}
p += 64;
n -= 64;
}
return sse2_memchr(p, c, n);
}

static double run(fn_t f, const unsigned char *buf, size_t n, long iters) {
LARGE_INTEGER freq, t0, t1;
size_t sum = 0;
QueryPerformanceFrequency(&freq);
QueryPerformanceCounter(&t0);
for (long i = 0; i < iters; i++) {
__asm__ volatile("" : : "r"(buf) : "memory");
sum += (size_t)((const unsigned char *)f(buf, '\n', n) - buf);
}
QueryPerformanceCounter(&t1);
if (sum != (size_t)iters * (n - 1)) printf("bad sum\n");
return (double)(t1.QuadPart - t0.QuadPart) / freq.QuadPart;
}

int main(void) {
const char *names[] = {"ucrtbase memchr", "naive", "swar", "swar x2", "sse2", "sse2 x4"};
fn_t fns[6];
fns[0] = (fn_t)GetProcAddress(LoadLibraryA("ucrtbase.dll"), "memchr");
fns[1] = naive_memchr;
fns[2] = swar_memchr;
fns[3] = swar2_memchr;
fns[4] = sse2_memchr;
fns[5] = sse2x4_memchr;

/* correctness against the naive loop */
unsigned char rbuf[512];
srand(1);
for (int t = 0; t < 200000; t++) {
size_t len = rand() % 300;
size_t off = rand() % 64;
for (size_t i = 0; i < len; i++) rbuf[off + i] = "ab\n\x80\xff"[rand() % 5];
int c = (unsigned char)("\n" "\x80" "a"[rand() % 3]);
const void *want = naive_memchr(rbuf + off, c, len);
for (int k = 0; k < 6; k++) {
if (fns[k](rbuf + off, c, len) != want) {
printf("MISMATCH %s len=%zu off=%zu\n", names[k], len, off);
return 1;
}
}
}
printf("correctness: ok (200000 random cases)\n");

size_t sizes[] = {16, 64, 256, 1040, 65536, 1 << 20};
size_t maxn = 1 << 20;
unsigned char *buf = malloc(maxn);
printf("%-16s", "size");
for (int s = 0; s < 6; s++) printf("%14zu", sizes[s]);
printf("\n");
for (int k = 0; k < 6; k++) {
printf("%-16s", names[k]);
for (int s = 0; s < 6; s++) {
size_t n = sizes[s];
memset(buf, 'a', n - 1);
buf[n - 1] = '\n';
long iters = (long)((512ULL << 20) / n);
double t = run(fns[k], buf, n, iters);
printf("%9.1f GB/s", (double)n * iters / t / 1e9);
}
printf("\n");
}

/* edge cases: ns per call. pos < 0 means no match. */
struct { const char *label; size_t n; long pos; } cases[] = {
{"n=1, match", 1, 0}, {"n=7, match at end", 7, 6},
{"n=15, match at end", 15, 14}, {"n=1040, match at 0", 1040, 0},
{"n=16, no match", 16, -1}, {"n=64, no match", 64, -1},
{"n=1040, no match", 1040, -1},
};
printf("\n%-22s", "ns per call");
for (int k = 0; k < 6; k++) printf("%16s", names[k]);
printf("\n");
for (size_t ci = 0; ci < sizeof(cases) / sizeof(cases[0]); ci++) {
size_t n = cases[ci].n;
memset(buf, 'a', n);
if (cases[ci].pos >= 0) buf[cases[ci].pos] = '\n';
const unsigned char *want = cases[ci].pos >= 0 ? buf + cases[ci].pos : NULL;
long iters = 20000000;
printf("%-22s", cases[ci].label);
for (int k = 0; k < 6; k++) {
LARGE_INTEGER freq, t0, t1;
size_t bad = 0;
QueryPerformanceFrequency(&freq);
QueryPerformanceCounter(&t0);
for (long i = 0; i < iters; i++) {
__asm__ volatile("" : : "r"(buf) : "memory");
bad += fns[k](buf, '\n', n) != want;
}
QueryPerformanceCounter(&t1);
double ns = (double)(t1.QuadPart - t0.QuadPart) / freq.QuadPart / iters * 1e9;
if (bad) printf("%16s", "WRONG");
else printf("%16.2f", ns);
}
printf("\n");
}
return 0;
}
(2-2/5)