4 ms·
Yes except when no. Imagine you are writing performance sensitive code. You want to get a substring from a string, one that is not going to live outside your h
by IgorPartola 2y ago
Yes except when no.
Imagine you are writing performance sensitive code. You want to get a substring from a string, one that is not going to live outside your hot loop. In standard C you can just reference a part of a string with a pointer offset. All standard functions will continue working and you didn’t have to make any calls outside of your loop, not to the allocator, not to memcopy, nothing.
With strings being objects that are prefixed by a header cannot do this. At a minimum you need to allocate a new header, if not the whole string. Yes that’s the safer route but also a lot less performant.
Most crucially, you can build the header string implementation on top of C strings. You cannot do the opposite.
Realistically though C strings (aka null terminated strings) are just not a great thing because of the null termination. For my money, I would prefer to just use untermianted arrays and a separate size variable, as well as wide character strings for actual display stuff. This way all the interop must include string lengths (or some other way to determine length), and all internal stuff may be just ASCII but must not leave your internal logic and never be shown to the user.
- BiteCode_dev 2y agoYes but that's the rare case. The rare case should be possible, just not the default. In Rust, you would make custom string handling unsafe for the bottleneck.
- IgorPartola 2y agoRare for whom? Doing a lot of kernels or embedded development lately?
- BiteCode_dev 2y agokernels or embedded development is rare compared to web dev, app dev, cli tooling, automation, etc. In fact, it's pretty damn niche. And rust is a general language, so it favors the most common case, but let the niche case be possible.
- AaronFriel 2y ago> Imagine you are writing performance sensitive code. You want to get a substring from a string, one that is not going to live outside your hot loop. In standard C you can just reference a part of a string with a pointer offset. If you want your substring to terminate in the same place as the original, at a null terminator. But that sadly is almost never the case, and as many C practitioners know, references like this are often unsafe and so APIs that substring tend to copy. That's just what they have to do to pass address sanitizer and static analysis checks. If you want arbitrary views on a null terminated string, well, it's no longer null terminated and that's just the start of your problems in C. In languages like Rust and Go, taking a view of a string or array is safe and doesn't copy the underlying data or require an allocation. So if you are writing performance sensitive code where substrings are a major contributor to CPU cycles, best go with those language (or C++) rather than C.
- IgorPartola 2y agoThat’s fair: you won’t be able to use any libc functions that rely on null termination. But a lot of the time you don’t need to either. Think writing the substring to a socket or comparing it to a known constant.
- Tuna-Fish 2y agoIn Rust, you would do both of those with a &str, which works fine. Just works exactly as in C, with no calls to memcopy or allocator or anything. And you would also be able to do all the other things that in C use null termination, too.
- Tuna-Fish 2y agoThe solution in Rust is separate String and &str. &str is a reference to somewhere within String, and the length of the referred to region, and borrows from the String it refers to. Any function that does not need to modify a String takes a &str. Any function that does modify a String typically takes a String, which means they consume their input. (Because of utf-8, in-place modification is generally a pipedream.) Also, the headers are typically allocated on stack. Rust is a lot less shy about types that are larger than a pointer living inline whereever they are used, and this is something that seems to work a lot better than the alternative.
- IgorPartola 2y agoAllocating headers and strings separately blows your CPU cache. Hardly a performant way of doing hot loops.
- Tuna-Fish 2y agoHuh? The headers are either in registers or in stack. The top of stack is always in L1. There is no way in which this is inferior to handing over a pointer to a string and a length separately, other than requiring two additional words of storage in registers/stack.
- IgorPartola 2y agoHow is that? Say you are reading 1000 lines of stdin at once to process them. Which registers are your string and substring headers stored.
- Tuna-Fish 2y agoIf you are reading 1000 lines from stdin at once to separate Strings, you are already going to be accessing memory in 1000 places at the same time, and making it 1001 isn't meaningfully worse for your cache. (Implementation would be Vec<String>, which would lay out the 1000 headers contiguously.) But I genuinely have a hard time understanding for what kind of workload I would ever do that. If you want to read a 1000 lines of stdin, and cannot use an iterator and must work on them at the same time, I would likely much rather read them into a single string and then split that into a 1000 &str using the .lines() iterator.
- tsimionescu 2y agoNull terminated strings have a footer, so it is the exact same problem, just on the other end of the string. It is inherently impossible to substring an arbitrary string without copying and using the same memory layout for the full string and the substring(s). Of course, if your string type is a struct containing a size and a pointer, you can easily have multiple substrings pointing into the same byte array.
- samatman 2y ago> Imagine you are writing performance sensitive code. You want to get a substring from a string, one that is not going to live outside your hot loop. Zig uses slices for this (and everything else except interop): a pointer and a length, with some nicely ergonomic syntax for making another one, like `slice[start..][0..length]`. When you're building strings then you have an ArrayList, which keeps a capacity around, and maybe a pointer to an Allocator depending on what you want. It's trivial to get the slice out of this when you need it. Doing anything useful with a string requires knowing where it is (the pointer) and how much of it you have (the length) so keeping them together in one fat pointer is just good sense. It's remarkable how much easier this is to work with than C strings.
- GoblinSlayer 2y agoEfficient substring in C? Absolutely. Why don't we see real code? https://sourceware.org/git/?p=glibc.git;a=blob;f=stdlib/putenv.c;h=b234ce061284e61bd56da22e425fb6ecfd081d86;hb=refs/tags/glibc-2.39 https://sourceware.org/git/?p=glibc.git;a=blob;f=stdlib/pute...