authorgravatar for david@vortan.devDavid Rubin <david@vortan.dev> 2024-01-09 08:50:22+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-01-09 02:50:22-05:00
log2f8e4347b1f9c2bf093ac48a636576c22359c1ff
treebf4f9e69c3de4f757e63e24eae59acc9701c7cea
parent8c9efc95a1892276fee910546a019bee37b3726d
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Optimized `std.mem.eql` with SIMD (#18389)

* optimized memeql * add `sched_setaffinity` to `std.os.linux` Co-authored-by: Protty <45520026+kprotty@users.noreply.github.com> Co-authored-by: Ryan Liptak <squeek502@hotmail.com>

2 files changed, 74 insertions(+), 1 deletions(-)

lib/std/mem.zig+64-1
...@@ -634,14 +634,77 @@ test "lessThan" {...@@ -634,14 +634,77 @@ test "lessThan" {
634634
635/// Compares two slices and returns whether they are equal.635/// Compares two slices and returns whether they are equal.
636pub fn eql(comptime T: type, a: []const T, b: []const T) bool {636pub fn eql(comptime T: type, a: []const T, b: []const T) bool {
637 if (@sizeOf(T) == 0) return true;
638 if (!@inComptime() and std.meta.hasUniqueRepresentation(T)) return eqlBytes(sliceAsBytes(a), sliceAsBytes(b));
639
637 if (a.len != b.len) return false;640 if (a.len != b.len) return false;
638 if (a.ptr == b.ptr) return true;641 if (a.len == 0 or a.ptr == b.ptr) return true;
642
639 for (a, b) |a_elem, b_elem| {643 for (a, b) |a_elem, b_elem| {
640 if (a_elem != b_elem) return false;644 if (a_elem != b_elem) return false;
641 }645 }
642 return true;646 return true;
643}647}
644648
649/// std.mem.eql heavily optimized for slices of bytes.
650fn eqlBytes(a: []const u8, b: []const u8) bool {
651 if (a.len != b.len) return false;
652 if (a.len == 0 or a.ptr == b.ptr) return true;
653
654 if (a.len <= 16) {
655 if (a.len < 4) {
656 const x = (a[0] ^ b[0]) | (a[a.len - 1] ^ b[a.len - 1]) | (a[a.len / 2] ^ b[a.len / 2]);
657 return x == 0;
658 }
659 var x: u32 = 0;
660 for ([_]usize{ 0, a.len - 4, (a.len / 8) * 4, a.len - 4 - ((a.len / 8) * 4) }) |n| {
661 x |= @as(u32, @bitCast(a[n..][0..4].*)) ^ @as(u32, @bitCast(b[n..][0..4].*));
662 }
663 return x == 0;
664 }
665
666 // Figure out the fastest way to scan through the input in chunks.
667 // Uses vectors when supported and falls back to usize/words when not.
668 const Scan = if (std.simd.suggestVectorSize(u8)) |vec_size|
669 struct {
670 pub const size = vec_size;
671 pub const Chunk = @Vector(size, u8);
672 pub inline fn isNotEqual(chunk_a: Chunk, chunk_b: Chunk) bool {
673 return @reduce(.Or, chunk_a != chunk_b);
674 }
675 }
676 else
677 struct {
678 pub const size = @sizeOf(usize);
679 pub const Chunk = usize;
680 pub inline fn isNotEqual(chunk_a: Chunk, chunk_b: Chunk) bool {
681 return chunk_a != chunk_b;
682 }
683 };
684
685 inline for (1..6) |s| {
686 const n = 16 << s;
687 if (n <= Scan.size and a.len <= n) {
688 const V = @Vector(n / 2, u8);
689 var x = @as(V, a[0 .. n / 2].*) ^ @as(V, b[0 .. n / 2].*);
690 x |= @as(V, a[a.len - n / 2 ..][0 .. n / 2].*) ^ @as(V, b[a.len - n / 2 ..][0 .. n / 2].*);
691 const zero: V = @splat(0);
692 return !@reduce(.Or, x != zero);
693 }
694 }
695 // Compare inputs in chunks at a time (excluding the last chunk).
696 for (0..(a.len - 1) / Scan.size) |i| {
697 const a_chunk: Scan.Chunk = @bitCast(a[i * Scan.size ..][0..Scan.size].*);
698 const b_chunk: Scan.Chunk = @bitCast(b[i * Scan.size ..][0..Scan.size].*);
699 if (Scan.isNotEqual(a_chunk, b_chunk)) return false;
700 }
701
702 // Compare the last chunk using an overlapping read (similar to the previous size strategies).
703 const last_a_chunk: Scan.Chunk = @bitCast(a[a.len - Scan.size ..][0..Scan.size].*);
704 const last_b_chunk: Scan.Chunk = @bitCast(b[a.len - Scan.size ..][0..Scan.size].*);
705 return !Scan.isNotEqual(last_a_chunk, last_b_chunk);
706}
707
645/// Compares two slices and returns the index of the first inequality.708/// Compares two slices and returns the index of the first inequality.
646/// Returns null if the slices are equal.709/// Returns null if the slices are equal.
647pub fn indexOfDiff(comptime T: type, a: []const T, b: []const T) ?usize {710pub fn indexOfDiff(comptime T: type, a: []const T, b: []const T) ?usize {
lib/std/os/linux.zig+10
...@@ -1553,6 +1553,16 @@ pub fn sched_getaffinity(pid: pid_t, size: usize, set: *cpu_set_t) usize {...@@ -1553,6 +1553,16 @@ pub fn sched_getaffinity(pid: pid_t, size: usize, set: *cpu_set_t) usize {
1553 return 0;1553 return 0;
1554}1554}
15551555
1556pub fn sched_setaffinity(pid: pid_t, set: *const cpu_set_t) !void {
1557 const size = @sizeOf(cpu_set_t);
1558 const rc = syscall3(.sched_setaffinity, @as(usize, @bitCast(@as(isize, pid))), size, @intFromPtr(set));
1559
1560 switch (std.os.errno(rc)) {
1561 .SUCCESS => return,
1562 else => |err| return std.os.unexpectedErrno(err),
1563 }
1564}
1565
1556pub fn epoll_create() usize {1566pub fn epoll_create() usize {
1557 return epoll_create1(0);1567 return epoll_create1(0);
1558}1568}