A look at protocolbuffers/utf8_range: 21 stars on GitHub, written primarily in C.
Snapshot summary built from the project's own GitHub metadata — there's no written TopGit review yet. The page will update automatically when a full review is published.
WHY NO REVIEW YET
TopGit writes full reviews for the most-starred, most-requested repositories. This page is a snapshot until then — see the READ ME tab for the original README in full.
As of November 9, 2023, utf8_range has moved into the protobuf repo
here.
All further development is happening there, and this repo is no longer being
updated.
Fast UTF-8 validation with Range algorithm (NEON+SSE4+AVX2)
This is a brand new algorithm to leverage SIMD for fast UTF-8 string validation. Both NEON(armv8a) and SSE4 versions are implemented. AVX2 implementation contributed by ioioioio.
Four UTF-8 validation methods are compared on both x86 and Arm platforms. Benchmark result shows range base algorithm is the best solution on Arm, and achieves same performance as Lemire's approach on x86.
Range based algorithm
range-neon.c: NEON version
range-sse.c: SSE4 version
range-avx2.c: AVX2 version
range2-neon.c, range2-sse.c: Process two blocks in one iteration
Lemire's SIMD implementation
lemire-sse.c: SSE4 version
lemire-avx2.c: AVX2 version
lemire-neon.c: NEON porting
naive.c: Naive UTF-8 validation byte by byte
lookup.c: Lookup-table method
About the code
Run "make" to build. Built and tested with gcc-7.3.
Run "./utf8" to see all command line options.
Benchmark
Run "./utf8 bench" to bechmark all algorithms with default test file.
Run "./utf8 bench size NUM" to benchmark specified string size.
Run "./utf8 test" to test all algorithms with positive and negative test cases.
To benchmark or test specific algorithm, run something like "./utf8 bench range".
Benchmark result (MB/s)
Method
Generate UTF-8 test buffer per test file or buffer size.
Call validation sub-routines in a loop until 1G bytes are checked.
Calculate speed(MB/s) of validating UTF-8 strings.
NEON(armv8a)
Test case
naive
lookup
lemire
range
range2
UTF-demo.txt
562.25
412.84
1198.50
1411.72
1579.85
32 bytes
651.55
441.70
891.38
1003.95
1043.58
33 bytes
660.00
446.78
588.77
1009.31
1048.12
129 bytes
771.89
402.55
938.07
1283.77
1401.76
1K bytes
811.92
411.58
1188.96
1398.15
1560.23
8K bytes
812.25
412.74
1198.90
1412.18
1580.65
64K bytes
817.35
412.24
1200.20
1415.11
1583.86
1M bytes
815.70
411.93
1200.93
1415.65
1585.40
SSE4(E5-2650)
Test case
naive
lookup
lemire
range
range2
UTF-demo.txt
753.70
310.41
3954.74
3945.60
3986.13
32 bytes
1135.76
364.07
2890.52
2351.81
2173.02
33 bytes
1161.85
376.29
1352.95
2239.55
2041.43
129 bytes
1161.22
322.47
2742.49
3315.33
3249.35
1K bytes
1310.95
310.72
3755.88
3781.23
3874.17
8K bytes
1348.32
307.93
3860.71
3922.81
3968.93
64K bytes
1301.34
308.39
3935.15
3973.50
3983.44
1M bytes
1279.78
309.06
3923.51
3953.00
3960.49
Range algorithm analysis
Basic idea:
Load 16 bytes
Leverage SIMD to calculate value range for each byte efficiently
Depending on First Byte, one legal character can be 1, 2, 3, 4 bytes
For First Byte within C0..DF, character length = 2
For First Byte within E0..EF, character length = 3
For First Byte within F0..F4, character length = 4
C0, C1, F5..FF are not allowed
Second,Third,Fourth Bytes must lie in 80..BF.
There are four special cases for Second Byte, shown bold italic in above table.
Range table
Range table maps range index 0 ~ 15 to minimal and maximum values allowed. Our task is to observe input string, find the pattern and set correct range index for each byte, then validate input string.
C0,C1,F5..FF are not included in range table and will always be detected.
Illegal 80..BF will have range index 0(00..7F) and be detected.
Based on First Byte, according Second, Third and Fourth Bytes will have range index 1/2/3, to make sure they must lie in 80..BF.
If non-ASCII First Byte overlaps, above algorithm will set range index of the latter First Byte to 9,10,11, which are illegal ranges. E.g, Input = F1 80 C2 90 --> Range index = 8 3 10 1, where 10 indicates error. See table below.
Overlapped non-ASCII First Byte
Input
F1
80
C2
90
first_len
3
0
1
0
First Byte
8
0
8
0
Second Byte
0
3
0
1
Third Byte
0
0
2
0
Fourth Byte
0
0
0
1
Range index
8
3
10
1
Adjust Second Byte range for special cases
Range index adjustment for four special cases
First Byte
Second Byte
Before adjustment
Correct index
Adjustment
E0
A0..BF
2
4
2
ED
80..9F
2
5
3
F0
90..BF
3
6
3
F4
80..8F
3
7
4
Range index adjustment can be reduced to below problem:
Given 16 bytes, replace E0 with 2, ED with 3, F0 with 3, F4 with 4, others with 0.
A naive SIMD approach:
Compare 16 bytes with E0, get the mask for eacy byte (FF if equal, 00 otherwise)
And the mask with 2 to get adjustment for E0
Repeat step 1,2 for ED,F0,F4
At least eight operations are required for naive approach.
Observing special bytes(E0,ED,F0,F4) are close to each other, we can do much better using lookup table.
NEON
NEON tbl instruction is very convenient for table lookup:
Table can be up to 16x4 bytes in size
Return zero if index is out of range
Leverage these features, we can solve the problem with as few as two operations:
Precreate a 16x2 lookup table, where table[0]=2, table[13]=3, table[16]=3, table[20]=4, table[others]=0.
Substract input bytes with E0 (E0 -> 0, ED -> 13, F0 -> 16, F4 -> 20).
Use the substracted byte as index of lookup table and get range adjustment directly.
For indices less than 32, we get zero or required adjustment value per input byte
For out of bound indices, we get zero per tbl behaviour
SSE
SSE pshufb instruction is not as friendly as NEON tbl in this case:
Table can only be 16 bytes in size
Out of bound indices are handled this way:
If 7-th bit of index is 0, least four bits are used as index (E.g, index 0x73 returns 3rd element)
If 7-th bit of index is 1, return 0 (E.g, index 0x83 returns 0)
We can still leverage these features to solve the problem in five operations:
Substract input bytes with EF (E0 -> 241, ED -> 254, F0 -> 1, F4 -> 5) to get the temporary indices
Get range index for E0,ED
Saturate substract temporary indices with 240 (E0 -> 1, ED -> 14, all values below 240 becomes 0)
Use substracted indices to look up table_df, get the correct adjustment
Get range index for F0,F4
Saturate add temporary indices with 112(0x70) (F0 -> 0x71, F4 -> 0x75, all values above 16 will be larger than 128(7-th bit set))
Use added indices to look up table_ef, get the correct adjustment (index 0x71,0x75 returns 1st,5th elements, per pshufb behaviour)
Error handling
For overlapped non-ASCII First Byte, range index before adjustment is 9,10,11. After adjustment (adds 2,3,4 or 0), the range index will be 9 to 15, which is still illegal in range table. So the error will be detected.
Handling remaining bytes
For remaining input less than 16 bytes, we will fallback to naive byte by byte approach to validate them, which is actually faster than SIMD processing.
Look back last 16 bytes buffer to find First Byte. At most three bytes need to look back. Otherwise we either happen to be at character boundray, or there are some errors we already detected.
Validate string byte by byte starting from the First Byte.
Tests
It's necessary to design test cases to cover corner cases as more as possible.
Positive cases
Prepare correct characters
Validate correct characters
Validate long strings
Round concatenate characters starting from first character to 1024 bytes
Validate 1024 bytes string
Shift 1 byte, validate 1025 bytes string
Shift 2 bytes, Validate 1026 bytes string
...
Shift 16 bytes, validate 1040 bytes string
Repeat step3, test buffer starting from second character
Repeat step3, test buffer starting from third character
...
Negative cases
Prepare bad characters and bad strings
Bad character
Bad character cross 16 bytes boundary
Bad character cross last 16 bytes and remaining bytes boundary
Test long strings
Prepare correct long strings same as positive cases
Append bad characters
Shift one byte for each iteration
Validate each shift
Code breakdown
Below table shows how 16 bytes input are processed step by step. See range-neon.c for according code.
TopGit's last sync did not record any GitHub topics for protocolbuffers/utf8_range. GitHub topics appear in the right sidebar of a repository page; that's the authoritative place to check.
How active is development on protocolbuffers/utf8_range?
The most recent commit recorded on protocolbuffers/utf8_range was 3.0 years ago, based on the GitHub push timestamp. The repository has 18 forks — one of the better signals of community interest.
How many stars does protocolbuffers/utf8_range have?
protocolbuffers/utf8_range has 21 GitHub stars — refresh the page for the live number, or check github.com/protocolbuffers/utf8_range. TopGit mirrors GitHub's count but does not claim minute-by-minute accuracy.
What language is protocolbuffers/utf8_range written in?
protocolbuffers/utf8_range is written primarily in C. GitHub's language field is based on the largest share of bytes in the default branch.
Where do I read more about protocolbuffers/utf8_range?
This TopGit page is a snapshot — the READ ME tab shows the project's own README content (links stripped, images preserved). The GitHub repository at github.com/protocolbuffers/utf8_range is the definitive source.
Read full README in the tab above.
Curious whether utf8_range is right for you?
Let ChatGPT, Claude, or Perplexity look into it — click below and see what AI actually says about utf8_range.