Transcoding Unicode strings at crazy speeds with AVX-512
lemire.me
lemire.me
Yes, I realize that this can be done by hand using simdutf’s API. No, users should not be expected to roll it themselves.
We had considered adding a variant that could automatically fix up malformed input, but as there are multiple mutually incompatible conventions for dealing with encoding errors, we decided to ignore this case for now. Perhaps a future version of simdutf may provide support for best-effort transcoding of errorneous input.
For an example of how unsafe and error-prone C++ string manipulation is, from simdutf's very own documentation:
> simdutf_warn_unused size_t convert_latin1_to_utf8(const char * input, size_t length, char* utf8_output) noexcept;
Eww, this literally cannot be safe in the sense of "free from memory-safety errors when called carelessly" (like Rust or almost any GC language would expect). Look, takes a single pointer to an output array with no length or other limit. It overruns the buffer unless you get it exactly right. (ThePhD has a very on-point rant or two [0] about this, and ztd.text IMO (and in his opinion) gets this right.) Also, for all that simdutf is really quite fast, it's really not obvious how to transcode latin-1 to utf-8 in a single pass, because you apparently need to compute the length first.
I think one of the great strengths of safer languages is that they fight back when you try to design an API like this.
> Also, for all that simdutf is really quite fast, it's really not obvious how to transcode latin-1 to utf-8 in a single pass, because you apparently need to compute the length first.
You do this by having the caller pre-allocate a buffer that will always be long enough (twice the length of the input). Some bytes of the buffer may end up being unused, but that's usually acceptable.
I know. When I was less experienced, I believed more in this kind of isolated performance. But...
> You can call utf8_length_from_latin1 to find the required buffer length
For smallish input (that fits in L1 cache, for example), performance might be dominated by the cost of filling the cache, in which case two passes might not be that bad. For large inputs, especially at the kinds of speeds that simdutf8 achieves, memory bandwidth is likely to dominate or come close, and two passes is not so great. In either case, I bet that a high quality length-checked implementation of a function like convert_latin1_to_utf8 could be considerably faster than calling utf8_length_from_latin1 and then convert_latin1_to_utf8.
> or provide a buffer that is twice as large as the input buffer, which will always suffice.
and will always be fast as long as wasting up to half the memory is not a big deal. But in many cases, that factor of 2 of memory usage is a big deal.
So I hate to say it, but I tend to think that simdutf's transcoding design is based on benchmarking the wrong thing. (The validation benchmark design is fine.)
OSes generally implement overcommit - allocating a bunch of memory doesn't mean it's actually being used.
I doubt this workload exists. Overcommit is kind of nifty but not at all magical.
I bet a really good AVX-512 transcoder with output size checking could be almost as fast as the OP.
You don't free up the memory you're not using? Sounds like you're doing it wrong then.
> I bet a really good AVX-512 transcoder with output size checking could be almost as fast as the OP.
Submit your pull request and we can evaluate your bet.