Skip to content

Conditionally const version of strlen #2644

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed

Conversation

alexcu2718
Copy link

@alexcu2718 alexcu2718 commented Jun 17, 2025

(Firstly, I apologise, this is my first issue on a crate and I wanted to share it, please inform me of etiquette if missed!)

(I VERIFIED ALL TESTS LOCALLY BEFORE PUSHING, ALL WORKED)

What does this PR do

This PR offers an version of strlen for filesystem operations and I believe is

  1. Constant time (thus no branching)
  2. No SIMD (works on x86-64 which is a good portion of nix users)
  3. No overreads from strlen.
  4. Evaluable at compile time (for whatever vague reasons I don't understand is useful)
  5. Faster than libc::strlen

NOTES:

I've changed functions around my the definitions in my own crate, I've exposed the function in this PR as a new function but ideally the function could be placed wherever (it maybe too unsafe to have as a general function)

Even though this is esoteric, this is a very low level thing to optimise, I thing downstream impacts in some cases could be notable.

It needs to be heavily tested to know where it works/what needs to be modified,

I'd like to know simply if the nix maintainers would like this functionality in some form.

my comments are explained in the function definition

PROBLEMS:

Little Endianness, strange edge case versions of unix.

In my crate you can see benchmarks:

https://github.com/alexcu2718/fdf/blob/main/const_str_benchmark.txt

(just clone it and cargo bench)

The benchmarks are pretty impressive,

EMPTY STRINGS


 strlen_by_length/const_time_swar/empty
                        time:   [994.53 ps 997.11 ps 999.71 ps]
strlen_by_length/libc_strlen/empty
                          time:   [1.6360 ns 1.6408 ns 1.6455 ns]
LONGSTRINGS(~128-255)

 strlen_by_length/const_time_swar/xlarge (129-255)
                       time:   [1.0687 ns 1.0719 ns 1.0750 ns]
 strlen_by_length/libc_strlen/xlarge (129-255)
                       time:   [4.2938 ns 4.3054 ns 4.3171 ns]

Checklist:

  • [ YES I HAVE] I have read CONTRIBUTING.md
  • [ YES] I have written necessary tests and rustdoc comments
  • A change log has been added if this PR modifies nix's API

alexcu2718 and others added 8 commits June 17, 2025 17:54
@alexcu2718
Copy link
Author

^Seeing 20 failures in CI is not fun haha

Copy link
Member

@asomers asomers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a very premature optimization to me. It's high-maintenance but low benefit. And specifically:

  • Why is it Linux and amd64 only?
  • You can't assume that the kernel will always nul-terminate the field. The structure might not even have been written by the kernel.
  • Despite all of the fancy SWAR, it's really just reading the d_reclen field. What benefit does all of the SWAR stuff provide, if you assume that d_reclen is valid?
  • I'm pretty sure that std::CStr::from_ptr doesn't even do strlen internally. So your complicated function doesn't provide any improvement.

I do not approve of merging this PR.

@alexcu2718
Copy link
Author

alexcu2718 commented Jun 17, 2025

I'm not sure what it's actually applicable on(architecture wise) I just restrained to the use case as proof of concept.

Linux specific is basically because of kernel alignment guarantees, it may apply generally to POSIX, not sure.

(Not that I know of any big endian systems on that note too)

-Pointless on macos/bsd because of inclusion of d_namelen in struct (IIRC)

-You cannot assume d_reclen is valid, that's why I have to purposely detect null padding.

I thought a constant function O(1) implementation for a very hot function was interesting at least, so I thought if it was of interest,



  pub const unsafe fn from_ptr<'a>(ptr: *const c_char) -> &'a CStr {

        // SAFETY: The caller has provided a pointer that points to a valid C

        // string with a NUL terminator less than `isize::MAX` from `ptr`.

        let len = unsafe { strlen(ptr) };


        // SAFETY: The caller has provided a valid pointer with length less than

        // `isize::MAX`, so `from_raw_parts` is safe. The content remains valid

        // and doesn't change for the lifetime of the returned `CStr`. This

        // means the call to `from_bytes_with_nul_unchecked` is correct.

        //

        // The cast from c_char to u8 is ok because a c_char is always one byte.

        unsafe { Self::from_bytes_with_nul_unchecked(slice::from_raw_parts(ptr.cast(), len + 1)) }

@alexcu2718
Copy link
Author

I'm going to close this however as maybe premature in scope of the generality I guess.

Sorry.

@alexcu2718 alexcu2718 closed this Jun 17, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants