Why is strlen so complex in C?
stackoverflow.com
stackoverflow.com
I would never take an answer on SO w/o a grain of salt w/o reading comments.
This is tangential to the meat of your comment but I feel compelled to nitpick based entirely on personal anecdote: I have been helped hundreds of times on StackOverflow by people who had nothing to gain from it and yet provided in depth, insightful answers. In my experience, the farther you get away from the big, overwhelmed tags, the better the quality is. But you don't want to get so obscure that you are just ignored.
The Racket community on SO is particularly fantastic, and (in my experience) the more 'web' related your question is, the worse answers you get.
I didn't read it that way at all. Consider the audience: the questioner admits that they are new to C. Because of that, the author of the accepted answer wanted to make it crystal clear that code like this should never be written "especially if you're not a C compiler / standard library vendor". The answerer explicitly acknowledges that the code depends on implementation-defined behavior.
IMO, the answer is great. It starts with a strong warning to never write code like that in your own programs (which you shouldn't), and then explains how it works, and why it works in the context of where it's written.
Is it? We have to ask a few questions:
1. Is it actually faster on today's CPUs? (Any linear memory access pattern is going to be detected by the prefetcher)
2. Would the compiler do the same or better? eg, could it be auto-vectorizing the loop if we didn't play tricks?
3. If we really care about trading complexity for speed, wouldn't a hand-written SSE/SSE2/AVX implementation be even faster? Why not spend our complexity budget there?
In other words, there actually is an SSE/SSE2/AVX implementation in glibc.
This copy in plain C is the fall-back to if the porter didn't implement strlen in assembly for the architecture they were porting to.
It is a reasonable guess at a somewhat architecturally-independent fast implementation. If someone really cares about squeezing all the performance they could out of strlen, they implement the assembly version.
Beyond that, many architectures that gcc supports don't include prefetchers, vector units and all the rest. The world is not all x86.
2) Mostly not. gcc and clans don't vectorize "search loops" i.e. loops that terminate early based on some condition. These loops are tricky to vectorize at least partly because of the reading-beyond-the-end issue mentioned in the accepted answer. icc does vectorize some search loops, not sure if it would do a good job on this one. I'm not sure about MSVC, but it's usually way behind on everything optimization related so I think we can assume not.
3. Yes definitely. glibc has such implementations for popular platforms like x86, so this code isn't actually being used on your PC say.
For deep in the bowels of a compiler or libc, where developers might think it's useful to optimize code that has to be run in a huge variety of impossible-to-predict types of workloads, I'm more sympathetic, but it's objectively not good code. It might be useful and necessary in this very very narrow context, but it's far too clever to be good.
The answerer may have been a bit heavy-handed in knocking it down, but I think it was entirely appropriate when helping to educate someone who is new to C.
I agree that the other answer you link to is probably better, with the caveat that I'm disappointed that there's no warning against writing code like that in the general case. The sentence that starts with "They make assumptions..." is IMO not strong enough to point out that the strlen() code referenced relies on implementation-defined behavior and is not even remotely portable C.
There is nothing wrong with the simpler version, but it’s not as fast (for longer strings).
word_t strnlen(const char *s, word_t maxlen)
{
word_t len;
for (len = 0; len < maxlen && s[len]; len++);
return len;
}
http://sel4.systems/https://github.com/seL4/seL4 (from file src/string.c)
A good implementation would stick to the standard (size_t nor word_t), and would inc the pointer, not the counter. Formatting would be better (the final ; ouch).
This is junior stuff.
Having worked on a variety of code bases and coding styles, I think if there are coding rules and they are consistent across all the source files, it becomes easy to read even with wierdnesses. I hated perl with @_ and $_, but after working with it enough they became known quantities.
And you can probably read all the lines of code in your lifetime, since the whole microkernel is ~30k lines of code.
But it's probably irrelevant to most systems today, as the assembly version is almost always used, e.g.
* i386: https://github.com/bminor/glibc/blob/master/sysdeps/i386/str...
* i586/i686: https://github.com/bminor/glibc/blob/master/sysdeps/i386/i58...
* i686 w/ SSE2: https://github.com/bminor/glibc/blob/master/sysdeps/i386/i68...
* i686 w/ SSE2 + BSF: https://github.com/bminor/glibc/blob/master/sysdeps/i386/i68...
* x86_64 w/ SSE2: https://github.com/bminor/glibc/blob/master/sysdeps/x86_64/s...
* ARM: https://github.com/bminor/glibc/blob/master/sysdeps/arm/strl...
* ARMv6: https://github.com/bminor/glibc/blob/master/sysdeps/arm/armv...
* ARMv6T2: https://github.com/bminor/glibc/blob/master/sysdeps/arm/armv...
* ARMv8a/AArch64: https://github.com/bminor/glibc/blob/master/sysdeps/aarch64/...
* ARMv8a/AArch64 w/ ASIMD: https://github.com/bminor/glibc/blob/master/sysdeps/aarch64/...
etc.
There isn't any auto-vectorization. Clang & GCC both do basically the same thing here.
Possibly it might also be an awkward, complex, or dangerous (as in risk of unintended consequences) optimization to selectively violate the memory model that way. But I'm not familiar with the internals of optimizing compilers, so that's just conjecture.
Writing in C shouldn't have to be so painful, but I guess they were the pioneers in a lot of things and the "high level assembly" idea stuck. (Premature optimization?)
Also having objects and method calls, even if it's syntactic sugar deep down, is the best kind of syntactic sugar
If handling strings in plain C is hard, handling them in assembly is 10x as hard.
No it doesn't. It didn't in the past, it was a high-level abstraction. And it doesn't today, because CPUs don't behave at all like C's defined virtual machine.
What "virtual machine" are you referring to?
Data structures to most programmers are things you import into your Rube Goldberg machine. They don't understand that good programs are made of layers of meticulously crafted, bespoke data structures where the final product is effectively one giant data structure tailored for a particular task, with interfaces to match.
It follows that good C programs don't do very much strings munging even if all they do is process text. The text is systematically atomized from the very beginning. The same is true for good programs in any other language.
Strings are "unnatural" to the binary system, it's a human concept.
It is generally quite easy to compile a C program with a C++ compiler, and then the sky's the limit on code improvements. I recommend it, and suggest starting with improvements to memory safety.
There is really no excuse for writing a new C program anymore.
Your 32-bit ARM M3/M4 cores are probably crossing that line where the memory cost of using C++ is worth it, but I would say there are still real world reasons to use C.
Though I would love to see more microcontroller vendors (or any) support C++ or Rust as part of they HAL libraries, but I think you will still find C to be perfectly viable for the low cost microcontroller application.
On ARM it is no contest, at any size.
Funny little $.10 PICcy things may not have a C++ compiler available. Then, the C compiler is better than asm, if you can fit. Otherwise, no excuse.
I don't think C is always a win, but to say to never use for new projects seems overly biased. Why is your opinion about C so strong? I am curious.
But a C compiler restricts you to the C subset. Many of the most useful features of C++ generate no extra instructions, but make the code more maintainable. There are libraries that compile to no code except exactly what you call, optimized down to exactly the circumstances of the call, wholly inexpressible in C, that you would have to open-code directly in C, gaining no benefit from the maturity of a library.
In other words, in the absolute worst case, you write the program using only C features, and get the same program. But there is never a reason to opt for the absolute worst case when you have a better choice.
So, no, there is never a reason to confine yourself to the C subset, in a new program. "for (auto e : v)" is universally better than "for (i = 0; i != N; ++i)". Zero cost, better code.
But I would never, ever suggest using an IDE, under any circumstances, for any language.
- BSTR
- CString
- bstr_t
- basic_string
- and a bunch of typedefs...
There are many technical details to be worked out and choices to be made but there is no fundamental intractable problem preventing it. We'd have incremental adoption over many years. Maybe some legacy code would never be updated.
The primary blocker is getting the committee members to admit this is a problem that should be solved. That is difficult enough here on HN, where some people are still arguing that C is fine as-is and we just need to invent better programmers / better tools.
Ultimately C's lack of better string support comes from its lack of better array support. That comes from a certain subset of the programmer community that believes the status-quo is fine and isn't interested in such changes.
Unfortunately, C is dead. Microsoft thinks C is dead, and intentionally keeps their compiler awful. Even ISO WG doesn't seem to have a vision for C's future and have been rearranging deck chairs for the last 20 years.
The people using old C are self-selected as those who like it the way it is. They accepted atomics because of new hardware. The place to ask for new, better stuff is at the C++ desk, and it has lots already.
...it's the philosophy of the C language: There's minimal bloat on the upside, but you have to know what you're doing and how to be able get it on the downside.
Slices greatly improve interfaces. Especially if you want to return a (sub)slice of something, C doesn't have any non-clunky solution.
The new type wouldn't be usable either, because it would have to be converted down to another representation all the time when calling functions and libraries that are not aware of it. Think of C string to C++ string to boost string to whatever.
I think the closest usable thing is QString if you are using Qt.
Let's say that all the stars align and you process an entire iteration of the byte-by-byte loop in 1 cycle. On a 4 GHz machine thats ... 4 GB/s. That's paltry compared to main memory bandwidth of 30-100 GB/s [1], not to mention say L1 bandwidth of ~ 256 GB/s (available to a single core).
This idea that everything is memory limited it just false: it's pretty hard to write code efficient enough that it can be limited by memory _bandwidth_ on a single core.
---
[1] Admittedly, a single core can usually only access 20-30 GB/s of that, but that's still >> 4 GB/s.
You may have to unroll both to get max speed.
If you could share your benchmark I would be interested to look at it.