Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 43 additions & 45 deletions benchmarks/prefix_optimization.zig
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
const std = @import("std");
const Regex = @import("regex").Regex;

pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();

pub fn main(init: std.process.Init) !void {
std.debug.print("=== Literal Prefix Optimization Benchmarks ===\n\n", .{});

// Create a long text where the pattern appears at the end
Expand All @@ -14,7 +10,7 @@ pub fn main() !void {
// Test 1: Pattern with literal prefix (should use optimization)
{
std.debug.print("Test 1: Literal prefix optimization (FOUND_IT!)...\n", .{});
var regex = try Regex.compile(allocator, "FOUND_IT!");
var regex = try Regex.compile(init.gpa, "FOUND_IT!");
defer regex.deinit();

// Print optimization info
Expand All @@ -24,31 +20,32 @@ pub fn main() !void {
std.debug.print(" ✗ No literal prefix found\n", .{});
}

var timer = try std.time.Timer.start();
const start = timer.read();
const start = std.Io.Timestamp.now(init.io, .real);

const iterations: usize = 10000;
var i: usize = 0;
while (i < iterations) : (i += 1) {
if (try regex.find(long_text)) |match| {
var mut_match = match;
mut_match.deinit(allocator);
mut_match.deinit(init.gpa);
}
}

const elapsed = timer.read() - start;
const avg_ns = elapsed / iterations;
const end = std.Io.Timestamp.now(init.io, .real);
const elapsed = start.durationTo(end);
const avg_ns = @divTrunc(elapsed.nanoseconds, iterations);

std.debug.print(" {d} iterations in {d:.2}ms ({d:.2}µs/op)\n\n", .{
iterations,
@as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
@as(f64, @floatFromInt(avg_ns)) / 1000.0,
@as(f64, @floatFromInt(elapsed.toMilliseconds())),
@as(f64, @floatFromInt(avg_ns)) / std.time.us_per_ms,
});
}

// Test 2: Pattern without useful prefix (no optimization) - using fewer iterations
{
std.debug.print("Test 2: No prefix optimization (.*FOUND)...\n", .{});
var regex = try Regex.compile(allocator, ".*FOUND");
var regex = try Regex.compile(init.gpa, ".*FOUND");
defer regex.deinit();

if (regex.opt_info.literal_prefix) |prefix| {
Expand All @@ -57,33 +54,34 @@ pub fn main() !void {
std.debug.print(" ✗ No literal prefix found (slower performance expected)\n", .{});
}

var timer = try std.time.Timer.start();
const start = timer.read();
const start = std.Io.Timestamp.now(init.io, .real);

// Use fewer iterations since this is much slower without optimization
const iterations: usize = 100;
var i: usize = 0;
while (i < iterations) : (i += 1) {
if (try regex.find(long_text)) |match| {
var mut_match = match;
mut_match.deinit(allocator);
mut_match.deinit(init.gpa);
}
}

const elapsed = timer.read() - start;
const avg_ns = elapsed / iterations;
const end = std.Io.Timestamp.now(init.io, .real);
const elapsed = start.durationTo(end);
const avg_ns = @divTrunc(elapsed.nanoseconds, iterations);

std.debug.print(" {d} iterations in {d:.2}ms ({d:.2}µs/op)\n\n", .{
iterations,
@as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
@as(f64, @floatFromInt(avg_ns)) / 1000.0,
@as(f64, @floatFromInt(elapsed.toMilliseconds())),
@as(f64, @floatFromInt(avg_ns)) / std.time.us_per_ms,
});
}

// Test 3: Pattern with prefix that allows skipping ahead
{
std.debug.print("Test 3: Prefix with complex suffix (hello.*world)...\n", .{});
const text_with_hello = "blah blah " ** 50 ++ "hello there world";
var regex = try Regex.compile(allocator, "hello.*world");
var regex = try Regex.compile(init.gpa, "hello.*world");
defer regex.deinit();

if (regex.opt_info.literal_prefix) |prefix| {
Expand All @@ -92,31 +90,31 @@ pub fn main() !void {
std.debug.print(" ✗ No literal prefix found\n", .{});
}

var timer = try std.time.Timer.start();
const start = timer.read();
const start = std.Io.Timestamp.now(init.io, .real);

const iterations: usize = 10000;
var i: usize = 0;
while (i < iterations) : (i += 1) {
if (try regex.find(text_with_hello)) |match| {
var mut_match = match;
mut_match.deinit(allocator);
mut_match.deinit(init.gpa);
}
}

const elapsed = timer.read() - start;
const avg_ns = elapsed / iterations;
const end = std.Io.Timestamp.now(init.io, .real);
const elapsed = start.durationTo(end);
const avg_ns = @divTrunc(elapsed.nanoseconds, iterations);
std.debug.print(" {d} iterations in {d:.2}ms ({d:.2}µs/op)\n\n", .{
iterations,
@as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
@as(f64, @floatFromInt(avg_ns)) / 1000.0,
@as(f64, @floatFromInt(elapsed.toMilliseconds())),
@as(f64, @floatFromInt(avg_ns)) / std.time.us_per_ms,
});
}

// Test 4: Anchored patterns
{
std.debug.print("Test 4: Start-anchored pattern (^hello)...\n", .{});
var regex = try Regex.compile(allocator, "^hello");
var regex = try Regex.compile(init.gpa, "^hello");
defer regex.deinit();

if (regex.opt_info.anchored_start) {
Expand All @@ -127,53 +125,53 @@ pub fn main() !void {
}

const text = "hello world";
var timer = try std.time.Timer.start();
const start = timer.read();
const start = std.Io.Timestamp.now(init.io, .real);

const iterations: usize = 10000;
var i: usize = 0;
while (i < iterations) : (i += 1) {
_ = try regex.isMatch(text);
}

const elapsed = timer.read() - start;
const avg_ns = elapsed / iterations;
const end = std.Io.Timestamp.now(init.io, .real);
const elapsed = start.durationTo(end);
const avg_ns = @divTrunc(elapsed.nanoseconds, iterations);
std.debug.print(" {d} iterations in {d:.2}ms ({d:.2}µs/op)\n\n", .{
iterations,
@as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
@as(f64, @floatFromInt(avg_ns)) / 1000.0,
@as(f64, @floatFromInt(elapsed.toMilliseconds())),
@as(f64, @floatFromInt(avg_ns)) / std.time.us_per_ms,
});
}

// Test 5: Email-like pattern with literal prefix
{
std.debug.print("Test 5: Email pattern (user@example.com)...\n", .{});
const email_text = "Contact us at " ++ ("some filler text here. " ** 20) ++ "user@example.com for support";
var regex = try Regex.compile(allocator, "user@example.com");
var regex = try Regex.compile(init.gpa, "user@example.com");
defer regex.deinit();

if (regex.opt_info.literal_prefix) |prefix| {
std.debug.print(" ✓ Using literal prefix: \"{s}\" (min_len={d})\n", .{prefix, regex.opt_info.min_length});
std.debug.print(" ✓ Using literal prefix: \"{s}\" (min_len={d})\n", .{ prefix, regex.opt_info.min_length });
}

var timer = try std.time.Timer.start();
const start = timer.read();
const start = std.Io.Timestamp.now(init.io, .real);

const iterations: usize = 10000;
var i: usize = 0;
while (i < iterations) : (i += 1) {
if (try regex.find(email_text)) |match| {
var mut_match = match;
mut_match.deinit(allocator);
mut_match.deinit(init.gpa);
}
}

const elapsed = timer.read() - start;
const avg_ns = elapsed / iterations;
const end = std.Io.Timestamp.now(init.io, .real);
const elapsed = start.durationTo(end);
const avg_ns = @divTrunc(elapsed.nanoseconds, iterations);
std.debug.print(" {d} iterations in {d:.2}ms ({d:.2}µs/op)\n\n", .{
iterations,
@as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
@as(f64, @floatFromInt(avg_ns)) / 1000.0,
@as(f64, @floatFromInt(elapsed.toMilliseconds())),
@as(f64, @floatFromInt(avg_ns)) / std.time.us_per_ms,
});
}

Expand Down
68 changes: 34 additions & 34 deletions benchmarks/simple.zig
Original file line number Diff line number Diff line change
@@ -1,106 +1,106 @@
const std = @import("std");
const Regex = @import("regex").Regex;

pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();

pub fn main(init: std.process.Init) !void {
std.debug.print("=== Simple Regex Benchmarks ===\n\n", .{});

// Test 1: Literal matching
{
std.debug.print("Test 1: Literal matching...\n", .{});
var regex = try Regex.compile(allocator, "hello");
var regex = try Regex.compile(init.gpa, "hello");
defer regex.deinit();

var timer = try std.time.Timer.start();
const start = timer.read();

const start = std.Io.Timestamp.now(init.io, .real);
const iterations: usize = 10000;
var i: usize = 0;
while (i < iterations) : (i += 1) {
_ = try regex.isMatch("hello world");
}

const elapsed = timer.read() - start;
const avg_ns = elapsed / iterations;
const end = std.Io.Timestamp.now(init.io, .real);
const elapsed = start.durationTo(end);

const avg_ns = @divTrunc(elapsed.nanoseconds, iterations);

std.debug.print(" {d} iterations in {d:.2}ms ({d:.2}µs/op)\n\n", .{
iterations,
@as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
@as(f64, @floatFromInt(avg_ns)) / 1000.0,
@as(f64, @floatFromInt(elapsed.toMilliseconds())),
@as(f64, @floatFromInt(avg_ns)) / std.time.us_per_ms,
});
}

// Test 2: Quantifiers
{
std.debug.print("Test 2: Quantifiers (a+)...\n", .{});
var regex = try Regex.compile(allocator, "a+");
var regex = try Regex.compile(init.gpa, "a+");
defer regex.deinit();

var timer = try std.time.Timer.start();
const start = timer.read();
const start = std.Io.Timestamp.now(init.io, .real);

const iterations: usize = 10000;
var i: usize = 0;
while (i < iterations) : (i += 1) {
_ = try regex.isMatch("aaaa");
}

const elapsed = timer.read() - start;
const avg_ns = elapsed / iterations;
const end = std.Io.Timestamp.now(init.io, .real);
const elapsed = start.durationTo(end);
const avg_ns = @divTrunc(elapsed.nanoseconds, iterations);

std.debug.print(" {d} iterations in {d:.2}ms ({d:.2}µs/op)\n\n", .{
iterations,
@as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
@as(f64, @floatFromInt(avg_ns)) / 1000.0,
@as(f64, @floatFromInt(elapsed.toMilliseconds())),
@as(f64, @floatFromInt(avg_ns)) / std.time.us_per_ms,
});
}

// Test 3: Character classes
{
std.debug.print("Test 3: Digit matching (\\d+)...\n", .{});
var regex = try Regex.compile(allocator, "\\d+");
var regex = try Regex.compile(init.gpa, "\\d+");
defer regex.deinit();

var timer = try std.time.Timer.start();
const start = timer.read();
const start = std.Io.Timestamp.now(init.io, .real);

const iterations: usize = 10000;
var i: usize = 0;
while (i < iterations) : (i += 1) {
_ = try regex.isMatch("12345");
}

const elapsed = timer.read() - start;
const avg_ns = elapsed / iterations;
const end = std.Io.Timestamp.now(init.io, .real);
const elapsed = start.durationTo(end);
const avg_ns = @divTrunc(elapsed.nanoseconds, iterations);

std.debug.print(" {d} iterations in {d:.2}ms ({d:.2}µs/op)\n\n", .{
iterations,
@as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
@as(f64, @floatFromInt(avg_ns)) / 1000.0,
@as(f64, @floatFromInt(elapsed.toMilliseconds())),
@as(f64, @floatFromInt(avg_ns)) / std.time.us_per_ms,
});
}

// Test 4: Case-insensitive
{
std.debug.print("Test 4: Case-insensitive matching...\n", .{});
var regex = try Regex.compileWithFlags(allocator, "hello", .{ .case_insensitive = true });
var regex = try Regex.compileWithFlags(init.gpa, "hello", .{ .case_insensitive = true });
defer regex.deinit();

var timer = try std.time.Timer.start();
const start = timer.read();
const start = std.Io.Timestamp.now(init.io, .real);

const iterations: usize = 10000;
var i: usize = 0;
while (i < iterations) : (i += 1) {
_ = try regex.isMatch("HELLO");
}

const elapsed = timer.read() - start;
const avg_ns = elapsed / iterations;
const end = std.Io.Timestamp.now(init.io, .real);
const elapsed = start.durationTo(end);
const avg_ns = @divTrunc(elapsed.nanoseconds, iterations);

std.debug.print(" {d} iterations in {d:.2}ms ({d:.2}µs/op)\n\n", .{
iterations,
@as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
@as(f64, @floatFromInt(avg_ns)) / 1000.0,
@as(f64, @floatFromInt(elapsed.toMilliseconds())),
@as(f64, @floatFromInt(avg_ns)) / std.time.us_per_ms,
});
}

Expand Down
1 change: 1 addition & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pub fn build(b: *std.Build) void {
// Later on we'll use this module as the root module of a test executable
// which requires us to specify a target.
.target = target,
.link_libc = true,
});

// Here we define an executable. An executable needs to have a root module
Expand Down
Loading