FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

AVX2 Latin1 => UTF16 by Nick-Nuon · Pull Request #318 · simdutf/simdutf · GitHub

AVX2 Latin1 => UTF16 - #318

Merged
lemire merged 7 commits into
simdutf:masterfrom
Nick-Nuon:avx2_latin1_to_utf16
Sep 25, 2023
Merged

AVX2 Latin1 => UTF16#318
lemire merged 7 commits into
simdutf:masterfrom
Nick-Nuon:avx2_latin1_to_utf16

Conversation

Copy link
Copy Markdown
Collaborator

These are my benchmark on big1 and gcc 11.3.1:

convert_latin1_to_utf16+haswell, input size: 432305, iterations: 30000, dataset: /home/leorio/unicode_lipsum/wikipedia_mars/french.latin1.txt
   0.313 ins/byte,    0.243 cycle/byte,   13.170 GB/s (5.2 %),     3.200 GHz,    1.289 ins/cycle 
   0.313 ins/char,    0.243 cycle/char,   13.170 Gc/s (5.2 %)     1.00 byte/char 
convert_latin1_to_utf16+icelake, input size: 432305, iterations: 30000, dataset: /home/leorio/unicode_lipsum/wikipedia_mars/french.latin1.txt
   0.157 ins/byte,    0.215 cycle/byte,   14.432 GB/s (2.4 %),     3.103 GHz,    0.729 ins/cycle 
   0.157 ins/char,    0.215 cycle/char,   14.432 Gc/s (2.4 %)     1.00 byte/char 
convert_latin1_to_utf16+iconv, input size: 432305, iterations: 30000, dataset: /home/leorio/unicode_lipsum/wikipedia_mars/french.latin1.txt
  31.022 ins/byte,    7.012 cycle/byte,    0.455 GB/s (0.3 %),     3.193 GHz,    4.424 ins/cycle 
  31.022 ins/char,    7.012 cycle/char,    0.455 Gc/s (0.3 %)     1.00 byte/char 
convert_latin1_to_utf16+icu, input size: 432305, iterations: 30000, dataset: /home/leorio/unicode_lipsum/wikipedia_mars/french.latin1.txt
   2.505 ins/byte,    0.644 cycle/byte,    4.961 GB/s (0.7 %),     3.195 GHz,    3.890 ins/cycle 
   2.505 ins/char,    0.644 cycle/char,    4.961 Gc/s (0.7 %)     1.00 byte/char 
convert_latin1_to_utf16+westmere, input size: 432305, iterations: 30000, dataset: /home/leorio/unicode_lipsum/wikipedia_mars/french.latin1.txt
   0.563 ins/byte,    0.184 cycle/byte,   17.360 GB/s (2.6 %),     3.201 GHz,    3.053 ins/cycle 
   0.563 ins/char,    0.184 cycle/char,   17.360 Gc/s (2.6 %)     1.00 byte/char 

lemire commented Sep 21, 2023

Copy link
Copy Markdown
Member

Intriguing.

lemire commented Sep 21, 2023

Copy link
Copy Markdown
Member

@Nick-Nuon It is important to realize that if we are consuming latin 1 at 15 GB/s, we are also producing UTF-16 at 30 GB/s. I think that results are likely to differ from run to run because these functions are very bound to loads or stores, and it is an instance with things like memory alignment may matter a lot.

Annoying, the SSE kernel is faster than your hand-crafted AVX kernel, and the SSE kernel is still using scalar code. But the scalar code is automatically vectorized to something 'like' what we are doing. It is an instance where having wider registers does not help much.

$ ./build/benchmarks/benchmark -P convert_latin1_to_utf16 -F u
nicode_lipsum/wikipedia_mars/french.latin1.txt  -I 30000
We define the number of bytes to be the number of *input* bytes.
We define a 'char' to be a code point (between 1 and 4 bytes).
===========================
Using ICU version 67.1
testcases: 1
input detected as unknown
current system detected as icelake
===========================
convert_latin1_to_utf16+haswell, input size: 432305, iterations: 30000, dataset: unicode_lipsum/wikipedia_mars/french.latin1.txt
   0.282 ins/byte,    0.209 cycle/byte,   15.297 GB/s (3.6 %),     3.202 GHz,    1.347 ins/cycle 
   0.282 ins/char,    0.209 cycle/char,   15.297 Gc/s (3.6 %)     1.00 byte/char 
convert_latin1_to_utf16+icelake, input size: 432305, iterations: 30000, dataset: unicode_lipsum/wikipedia_mars/french.latin1.txt
   0.157 ins/byte,    0.197 cycle/byte,   15.748 GB/s (2.5 %),     3.101 GHz,    0.796 ins/cycle 
   0.157 ins/char,    0.197 cycle/char,   15.748 Gc/s (2.5 %)     1.00 byte/char 
convert_latin1_to_utf16+iconv, input size: 432305, iterations: 30000, dataset: unicode_lipsum/wikipedia_mars/french.latin1.txt
  31.022 ins/byte,    7.012 cycle/byte,    0.455 GB/s (0.3 %),     3.193 GHz,    4.424 ins/cycle 
  31.022 ins/char,    7.012 cycle/char,    0.455 Gc/s (0.3 %)     1.00 byte/char 
convert_latin1_to_utf16+icu, input size: 432305, iterations: 30000, dataset: unicode_lipsum/wikipedia_mars/french.latin1.txt
   2.505 ins/byte,    0.640 cycle/byte,    4.992 GB/s (0.8 %),     3.196 GHz,    3.914 ins/cycle 
   2.505 ins/char,    0.640 cycle/char,    4.992 Gc/s (0.8 %)     1.00 byte/char 
convert_latin1_to_utf16+westmere, input size: 432305, iterations: 30000, dataset: unicode_lipsum/wikipedia_mars/french.latin1.txt
   0.563 ins/byte,    0.193 cycle/byte,   16.590 GB/s (2.3 %),     3.201 GHz,    2.918 ins/cycle 
   0.563 ins/char,    0.193 cycle/char,   16.590 Gc/s (2.3 %)     1.00 byte/char 

Overall, I think your PR looks fine.

// Zero extend each set of 8 Latin1 characters to 16 16-bit integers
__m256i out = _mm256_cvtepu8_epi16(in);
if (big_endian) {
out = _mm256_shuffle_epi8(out, byteflip);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Should this be a shift instead of a shuffle ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

A shift might be faster but note that the benchmark results above are for the case where big_endian is false. That is, this 'compile time branch' is not taken in the code that we care about (because big endian is uncommon).

Nick-Nuon commented Sep 23, 2023
edited
Loading

Copy link
Copy Markdown
Collaborator Author

@Nick-Nuon It is important to realize that if we are consuming latin 1 at 15 GB/s, we are also producing UTF-16 at 30 GB/s. I think that results are likely to differ from run to run because these functions are very bound to loads or stores, and it is an instance with things like memory alignment may matter a lot.

Annoying, the SSE kernel is faster than your hand-crafted AVX kernel, and the SSE kernel is still using scalar code. But the scalar code is automatically vectorized to something 'like' what we are doing. It is an instance where having wider registers does not help much.

$ ./build/benchmarks/benchmark -P convert_latin1_to_utf16 -F u
nicode_lipsum/wikipedia_mars/french.latin1.txt  -I 30000
We define the number of bytes to be the number of *input* bytes.
We define a 'char' to be a code point (between 1 and 4 bytes).
===========================
Using ICU version 67.1
testcases: 1
input detected as unknown
current system detected as icelake
===========================
convert_latin1_to_utf16+haswell, input size: 432305, iterations: 30000, dataset: unicode_lipsum/wikipedia_mars/french.latin1.txt
   0.282 ins/byte,    0.209 cycle/byte,   15.297 GB/s (3.6 %),     3.202 GHz,    1.347 ins/cycle 
   0.282 ins/char,    0.209 cycle/char,   15.297 Gc/s (3.6 %)     1.00 byte/char 
convert_latin1_to_utf16+icelake, input size: 432305, iterations: 30000, dataset: unicode_lipsum/wikipedia_mars/french.latin1.txt
   0.157 ins/byte,    0.197 cycle/byte,   15.748 GB/s (2.5 %),     3.101 GHz,    0.796 ins/cycle 
   0.157 ins/char,    0.197 cycle/char,   15.748 Gc/s (2.5 %)     1.00 byte/char 
convert_latin1_to_utf16+iconv, input size: 432305, iterations: 30000, dataset: unicode_lipsum/wikipedia_mars/french.latin1.txt
  31.022 ins/byte,    7.012 cycle/byte,    0.455 GB/s (0.3 %),     3.193 GHz,    4.424 ins/cycle 
  31.022 ins/char,    7.012 cycle/char,    0.455 Gc/s (0.3 %)     1.00 byte/char 
convert_latin1_to_utf16+icu, input size: 432305, iterations: 30000, dataset: unicode_lipsum/wikipedia_mars/french.latin1.txt
   2.505 ins/byte,    0.640 cycle/byte,    4.992 GB/s (0.8 %),     3.196 GHz,    3.914 ins/cycle 
   2.505 ins/char,    0.640 cycle/char,    4.992 Gc/s (0.8 %)     1.00 byte/char 
convert_latin1_to_utf16+westmere, input size: 432305, iterations: 30000, dataset: unicode_lipsum/wikipedia_mars/french.latin1.txt
   0.563 ins/byte,    0.193 cycle/byte,   16.590 GB/s (2.3 %),     3.201 GHz,    2.918 ins/cycle 
   0.563 ins/char,    0.193 cycle/char,   16.590 Gc/s (2.3 %)     1.00 byte/char 

Overall, I think your PR looks fine.

Not sure how much more time I should spend on it, but
out of sheer curiosity, I continued a bit.
There seems to be only so many ways to skin a cat here so

so I checked what godbolt had to say about it : https://godbolt.org/z/ba9rfqGoe

LHS is AVX2, RHS is scalar.
I asked uica next:

This is the uica output for the avx2 function:

This is the uica output for the scalar

I know that the second output is not using all instructions all the time so that 60 cycle per iteration figure is off but, at a glance, it still seems a lot more instructions than my function. Guess we can't judge a book (or a function) by its cover so gonna take a bit more look at it.

Nick-Nuon commented Sep 24, 2023
edited
Loading

Copy link
Copy Markdown
Collaborator Author
convert_latin1_to_utf16+haswell, input size: 432305, iterations: 30000, dataset: /home/leorio/unicode_lipsum/wikipedia_mars/french.latin1.txt
   0.563 ins/byte,    0.182 cycle/byte,   17.608 GB/s (2.3 %),     3.203 GHz,    3.096 ins/cycle 
   0.563 ins/char,    0.182 cycle/char,   17.608 Gc/s (2.3 %)     1.00 byte/char 
convert_latin1_to_utf16+icelake, input size: 432305, iterations: 30000, dataset: /home/leorio/unicode_lipsum/wikipedia_mars/french.latin1.txt
   0.157 ins/byte,    0.216 cycle/byte,   14.347 GB/s (2.1 %),     3.100 GHz,    0.726 ins/cycle 
   0.157 ins/char,    0.216 cycle/char,   14.347 Gc/s (2.1 %)     1.00 byte/char 
convert_latin1_to_utf16+iconv, input size: 432305, iterations: 30000, dataset: /home/leorio/unicode_lipsum/wikipedia_mars/french.latin1.txt
  31.022 ins/byte,    7.011 cycle/byte,    0.455 GB/s (0.3 %),     3.193 GHz,    4.424 ins/cycle 
  31.022 ins/char,    7.011 cycle/char,    0.455 Gc/s (0.3 %)     1.00 byte/char 
convert_latin1_to_utf16+icu, input size: 432305, iterations: 30000, dataset: /home/leorio/unicode_lipsum/wikipedia_mars/french.latin1.txt
   2.505 ins/byte,    0.640 cycle/byte,    4.993 GB/s (0.8 %),     3.195 GHz,    3.915 ins/cycle 
   2.505 ins/char,    0.640 cycle/char,    4.993 Gc/s (0.8 %)     1.00 byte/char 
convert_latin1_to_utf16+westmere, input size: 432305, iterations: 30000, dataset: /home/leorio/unicode_lipsum/wikipedia_mars/french.latin1.txt
   0.563 ins/byte,    0.182 cycle/byte,   17.567 GB/s (2.6 %),     3.202 GHz,    3.090 ins/cycle 
   0.563 ins/char,    0.182 cycle/char,   17.567 Gc/s (2.6 %)     1.00 byte/char 

I just wanted to reach par and that did it. It doesn't use AVX2 instructions in the C++ code but gcc uses AVX2 instructions in the underlying assembly.

Not sure if I spent too much time on it so I will move on to another ticket for now.

lemire commented Sep 24, 2023

Copy link
Copy Markdown
Member

C++ code but gcc uses AVX2 instructions in the underlying assembly.

Very sensible. If you think that this is ready, just mark it as 'ready for review'.

Nick-Nuon marked this pull request as ready for review September 24, 2023 22:28
lemire linked an issue Sep 25, 2023 that may be closed by this pull request
lemire merged commit 52ec2b3 into simdutf:master Sep 25, 2023
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Support Latin 1 => UTF 16 (AVX)

3 participants


Back | FazBrowse Home | New Git URL