Each new program under examples/ isolates one distinct feature rather than being a general purpose tool like the existing rxgrep.c: binary_scan.c (raw byte-range classes including an embedded NUL and an embedded 0x0A), utf8_scripts.c (\w across Latin/Greek/Cyrillic/CJK text, code point versus byte offsets), ascii_logparse.c (named groups against structured log text), redos_atomic.c (atomic groups and possessive quantifiers timed directly against the unprotected form of the textbook (a+)+b ReDoS shape), empty_match_rule.c (CPython's undocumented empty-match retry rule, verified: \d*? against "123abc456" gives 16 matches, not 9), and large_file_search.c (Input_from_file's mmap-backed reading on a generated 100MB file, with elapsed time and peak RSS printed). Every example was compiled and run while writing it; the claims in each file's top comment are checked against its own output, not written by hand and left unverified. Also adds examples/bench_vs_posix.c, a direct, honestly reported comparison against the C standard library's own <regex.h> (regcomp/regexec) on six scenarios at multi-megabyte or multi-hundred-thousand-line scale, using only pattern syntax valid for both engines so they run the identical pattern text. glibc's DFA-backed engine wins five of six scenarios by 2x-35x, which is the expected outcome of a roughly 2000-line backtracking interpreter built for Python `re` compatibility competing against a mature, heavily optimized engine with a much smaller feature set; the sixth scenario has no POSIX equivalent at all (an atomic group). Every scenario's match count is cross-checked between the two engines as an independent correctness signal beyond the existing CPython-derived test suite. Two real issues were found and fixed while building this benchmark, not left in: iterating regexec() over an advancing string pointer is quadratic in practice (no way to bound the search without an implicit NUL-scan on every call), fixed by using REG_STARTEND instead; and a signed integer overflow (undefined behavior, caught by UBSan) in the benchmark's own pseudo-random text generator, fixed by using an unsigned accumulator. README.md and USAGE.md gain pointers to examples/README.md (the new per-example index) and a "Benchmarks" section summarizing the POSIX comparison honestly, including where it loses. The Makefile gains a `make examples` target building all seven programs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EjuMk8kY9SDus1wWe2K9xY
101 lines
3.7 KiB
C
101 lines
3.7 KiB
C
/* large_file_search - demonstrates Input_from_file's mmap-backed reading
|
|
* (README.md "Memory footprint") on a file large enough that the
|
|
* difference between mapping it and copying it into a fresh malloc'd
|
|
* buffer is not theoretical. The example generates its own input (so the
|
|
* program is self-contained and does not depend on a file existing on the
|
|
* machine it runs on), searches it for a pattern that does not occur
|
|
* anywhere (the worst case for a search: every position is tried, since
|
|
* none of them succeeds early), and reports both the elapsed time and,
|
|
* where /proc is available, the process's peak resident memory, so the
|
|
* two numbers can be compared directly against the measured figures in
|
|
* README.md rather than taken on faith.
|
|
*/
|
|
#define _POSIX_C_SOURCE 199309L
|
|
#include "regexx.h"
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
|
|
static double now_seconds(void) {
|
|
struct timespec ts;
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9;
|
|
}
|
|
|
|
static void print_peak_rss(void) {
|
|
FILE *f = fopen("/proc/self/status", "r");
|
|
if (!f) { printf("(peak RSS: /proc not available on this platform)\n"); return; }
|
|
char line[256];
|
|
while (fgets(line, sizeof line, f)) {
|
|
if (strncmp(line, "VmHWM:", 6) == 0) {
|
|
printf("peak RSS: %s", line + 6);
|
|
break;
|
|
}
|
|
}
|
|
fclose(f);
|
|
}
|
|
|
|
int main(void) {
|
|
const char *path = "/tmp/regexx_large_file_search_example.dat";
|
|
size_t size = 100u * 1024 * 1024; /* 100MB: large enough to be a real
|
|
* measurement, small enough to run
|
|
* in a few seconds on ordinary
|
|
* hardware. */
|
|
|
|
printf("Generating a %zu MB file of non-matching text at %s ...\n", size / (1024 * 1024), path);
|
|
{
|
|
FILE *f = fopen(path, "wb");
|
|
if (!f) { perror("fopen"); return 1; }
|
|
/* Repeating, harmless filler text: never contains the digit
|
|
* sequence the search pattern below looks for. */
|
|
static const char chunk[] =
|
|
"the quick brown fox jumps over the lazy dog. ";
|
|
size_t chunklen = sizeof(chunk) - 1;
|
|
size_t written = 0;
|
|
while (written < size) {
|
|
size_t n = fwrite(chunk, 1, chunklen, f);
|
|
if (n == 0) break;
|
|
written += n;
|
|
}
|
|
fclose(f);
|
|
}
|
|
|
|
const char *pattern = "\\d{10,}"; /* ten or more consecutive digits: not present */
|
|
PatternError err; memset(&err, 0, sizeof err);
|
|
Pattern *pat = re_compile(pattern, strlen(pattern), ASCII, &err);
|
|
if (!pat) {
|
|
fprintf(stderr, "compile error: %s\n", err.msg);
|
|
PatternError_free(&err);
|
|
remove(path);
|
|
return 1;
|
|
}
|
|
|
|
Input *in = Input_from_file(path, &err);
|
|
if (!in) {
|
|
fprintf(stderr, "cannot read file: %s\n", err.msg);
|
|
PatternError_free(&err);
|
|
Pattern_free(pat);
|
|
remove(path);
|
|
return 1;
|
|
}
|
|
|
|
printf("Searching (Input_from_file mmaps this file rather than copying it,\n"
|
|
"so this process's own heap never holds a private copy of the 100MB\n"
|
|
"file content itself; it still allocates the search tables README.md\n"
|
|
"\"Memory footprint\" measures, roughly 8.4x the input length)...\n");
|
|
Match m; memset(&m, 0, sizeof m);
|
|
double t0 = now_seconds();
|
|
int rc = Pattern_search(pat, in, 0, -1, &m);
|
|
double t1 = now_seconds();
|
|
printf("result: rc=%d (0 = no match, as expected) in %.3fs\n", rc, t1 - t0);
|
|
print_peak_rss();
|
|
|
|
if (rc == 1) Match_free(&m);
|
|
Input_free(in);
|
|
Pattern_free(pat);
|
|
remove(path);
|
|
printf("(temporary file removed)\n");
|
|
return 0;
|
|
}
|