Re: Encode UTF-8 optimizations
[email protected] (Karl Williamson) Mon, 22 Aug 2016 13:43:59 -0600
| Newsgroups | perl.unicode |
|---|---|
| Message-ID | <[email protected]> |
On 08/22/2016 07:05 AM, [email protected] wrote: > On Sunday 21 August 2016 08:49:08 Karl Williamson wrote: >> On 08/21/2016 02:34 AM, [email protected] wrote: >>> On Sunday 21 August 2016 03:10:40 Karl Williamson wrote: >>>> Top posting. >>>> >>>> Attached is my alternative patch. It effectively uses a different >>>> algorithm to avoid decoding the input into code points, and to copy >>>> all spans of valid input at once, instead of character at a time. >>>> >>>> And it uses only currently available functions. >>> >>> And that's the problem. As already wrote in previous email, calling >>> function from shared library cannot be heavy optimized as inlined >>> function and cause slow down. You are calling is_utf8_string_loc for >>> non-strict mode which is not inlined and so encode/decode of non-stri= ct >>> mode will be slower... >>> >>> And also in is_strict_utf8_string_loc you are calling isUTF8_CHAR whi= ch >>> is calling _is_utf8_char_slow and which is calling utf8n_to_uvchr whi= ch >>> cannot be inlined too... >>> >>> Therefore I think this is not good approach... >>> >> >> Then you should run your benchmarks to find out the performance. > > You are right, benchmarks are needed to show final results. > >> On valid input, is_utf8_string_loc() is called once per string. The >> function call overhead and non-inlining should be not noticeable. > > Ah right, I misread it as it is called per one valid sequence, not for > whole string. You are right. It is called once per valid sequence. See below. > >> On valid input, is_utf8_char_slow() is never called. The used-parts c= an be >> inlined. > > Yes, but this function is there to be called primary on unknown input > which does not have to be valid. If I know that input is valid then > utf8::encode/decode is enough :-) What process_utf8() does is to copy the alleged UTF-8 input to the=20 output, verifying along the way that it actually is legal UTF-8 (with 2=20 levels of strictness, depending on the input parameter), and taking some=20 actions (exactly what depends on other input parameters) if and when it=20 finds invalid UTF-8. The way it works after my patch is like an instruction pipeline. You=20 start it up, and it stays in the pipeline as long as the next character=20 in the input is legal or it reaches the end. When it finds illegal=20 input, it drops out of the pipeline, handles that, and starts up the=20 pipeline to process any remaining input. If the entire input string is=20 valid, a single instance of the pipeline is all that gets invoked. The use-case I envision is that the input is supposed to be valid UTF-8,=20 and the purpose of process_utf8() is to verify that that is in fact=20 true, and to take specified actions when it isn't. Under that use-case,=20 taking longer to deal with invalid input is not a problem. If that is=20 not your use-case, please explain what yours is. And I think you misunderstand when is_utf8_char_slow() is called. It is=20 called only when the next byte in the input indicates that the only=20 legal UTF-8 that might follow would be for a code point that is at least=20 U+200000, almost twice as high as the highest legal Unicode code point.=20 It is a Perl extension to handle such code points, unlike other=20 languages. But the Perl core is not optimized for them, nor will it be.=20 My point is that is_utf8_char_slow() will only be called in very=20 specialized cases, and we need not make those cases have as good a=20 performance as normal ones. >> On invalid input, performance should be a minor consideration. > > See below... See above. :) > >> The inner loop is much tighter in both functions; likely it can be hel= d in >> the cache. The algorithm avoids a bunch of work compared to the previ= ous >> one. > > Right, for valid input algorithm is really faster. If it is because of > less case misses... maybe... I can play with perf or another tool to > look what is bottle neck now. > >> I doubt that it will be slower than that. The only way to know in any >> performance situation is to actually test. And know that things will = be >> different depending on the underlying hardware, so only large differen= ces >> are really significant. > > So, here are my test results. You can say that they are subjective, but > I would be happy if somebody provide better input for performance tests= . > > Abbreviations: > strict =3D Encode::encode/decode "UTF-8" > lax =3D Encode::encode/decode "utf8" > int =3D utf8::encode/decode > orig =3D commit 92d73bfab7792718f9ad5c5dc54013176ed9c76b > your =3D orig + 0001-Speed-up-Encode-UTF-8-validation-checking.patch > my =3D orig + revert commit c8247c27c13d1cf152398e453793a91916d2185d > > Test cases: > all =3D join "", map { chr } 0 .. 0x10FFFF > short =3D "=C5=BElu=C5=A5ou=C4=8Dk=C3=BD k=C5=AF=C5=88 p=C4=9Bl =C4=8F=C3= =A1belsk=C3=A9 =C3=B3dy " x 45 > long =3D $short x 1000 > invalid-short =3D "\xA0" x 1000 > invalid-long =3D "\xA0" x 1000000 > > Encoding was called on string with Encode::_utf8_on() flag. > > > Rates: > > encode: > all short long invalid-short invalid-long > orig - strict 41/s 124533/s 132/s 115197/s 172/s > your - strict 176/s 411523/s 427/s 54813/s 66/s > my - strict 80/s 172712/s 186/s 113787/s 138/s > > orig - lax 1010/s 3225806/s 6250/s 546800/s 5151/s > your - lax 952/s 3225806/s 5882/s 519325/s 4919/s > my - lax 1060/s 3125000/s 6250/s 645119/s 5009/s > > orig - int 8154604/s 10000000/s infty 9787566/s 9748151/s > your - int 9135243/s 11111111/s infty 8922821/s 9737657/s > my - int 9779395/s 10000000/s infty 9822046/s 8949861/s > > > decode: > all short long invalid-short invalid-long > orig - strict 39/s 119048/s 131/s 108574/s 171/s > your - strict 173/s 353357/s 442/s 42440/s 55/s > my - strict 69/s 166667/s 182/s 117291/s 135/s > > orig - lax 39/s 123609/s 137/s 127302/s 172/s > your - lax 230/s 393701/s 495/s 37346/s 65/s > my - lax 79/s 158983/s 180/s 121456/s 138/s > > orig - int 274/s 546448/s 565/s 8219513/s 12357/s > your - int 273/s 540541/s 562/s 7226066/s 12948/s > my - int 274/s 543478/s 562/s 8502902/s 12421/s > > > int is there just for verifications of tests as utf8::encode/decode > functions was not changed. > > Results are: your patch is faster for valid sequences (as you wrote > above), but slower for invalid (in some cases radically). > > So I would propose two optimizations: > > 1) Change macro isUTF8_CHAR in is_strict_utf8_string_loc() with some ne= w > which does not call utf8n_to_uvchr. That call is not needed as in > that case sequence is already invalid. And an optimizing compiler should figure that out and omit the call, so=20 we shouldn't have to manually. In the next few months I will be working=20 on some fixes to the :utf8 layer. That could lead to a core function=20 similar to this, but without relying on the compiler to figure things out= . > > 2) Try to make inline version of function is_utf8_string_loc(). Maybe > merge with is_strict_utf8_string_loc()? That should boost non strict > decoder for invalid sequences (now it is slower then strict one). I'll look at which functions in utf8.c should be inlined. This is a=20 candidate. But I doubt that that is why this is slower in this case.=20 Read blead's perlhack.pod for performance testing tool hints. Your comments caused me to realize that the call to utf8n_to_uvchr()=20 when the input is syntactically valid, but is an illegal code point=20 (like a surrogate, etc) could be replaced by the faster=20 valid_utf8_to_uvchr(), which has not been in the public API. I have=20 pushed to http://perl5.git.perl.org/perl.git/shortlog/refs/heads/smoke-me/khw-encod= e a version of blead which has this function made public, and inlined, in=20 case you want to test with it. There are some things in the error handling code that could be improved=20 upon. For example, memcpy() of a single character is slower than just=20 copying a byte at a time, as the function call overhead dwarfs the saving= s. > > And maybe it could make sense make all needed functions as part of > public API. Yes > > Are you going to prepare pull request for Encode module? I will, after everything is settled. This may include changes to=20 Devel::PPPort to make sure this works on all perls that Encode supports. However, I think it would be good to get the extra tests of malformed=20 inputs into the distribution. (I haven't looked at your PR yet for=20 flaws. I'll do that, and hopefully will find none, so will recommend=20 your PR be pulled.) > > > Anyway, how it behave on EBCDIC platforms? And maybe another question > what should Encode::encode('UTF-8', $str) do on EBCDIC? Encode $str to > UTF-8 or to UTF-EBCDIC? It works fine on EBCDIC platforms. There are other bugs in Encode on=20 EBCDIC that I plan on investigating as time permits. Doing this has=20 fixed some of these for free. The uvuni() functions should in almost=20 all instances be uvchr(), and my patch does that. On EBCDIC platforms, UTF-8 is defined to be UTF-EBCDIC (or vice versa if=20 you prefer), so $str will effectively be in the version of UTF-EBCDIC=20 valid for the platform it is running on (there are differences depending=20 on the platform's underlying code page). >