HaxeFoundation / haxe

Haxe - The Cross-Platform Toolkit
https://haxe.org
6.15k stars 656 forks source link

String performance is low and crash #8780

Open sonygod opened 5 years ago

sonygod commented 5 years ago

hashlink :crash neko :crash node:17ms hxcpp:crash php:crash

package;

/**
 * ...
 * @author sonygod
 */
class StringTest 
{

    static function main() 
    {

        var s = "1234567890abcdefghijklmnopqrstuvwxyz";
        var t = Date.now().getTime();
        var str = '';

        for (i in 0...100000){
            str += s;
        }

        trace(Date.now().getTime()-t);

    }
}
sonygod commented 5 years ago

image

Simn commented 5 years ago

String concatenation is quite bad for our targets. You get much better results by using StringBuf. The question is if the compiler could detect patterns like this and rewrite them to StringBuf automatically.

By the way, HL and hxcpp don't crash for me, they just take a really long time. I'm not seeing much of a memory increase in the process, so I don't know why you would experience crashes.

sonygod commented 5 years ago

they just take a really long time.yes ,very long time =crash.

image

I've waited for long time to see the last result, but unfortunately, it's cost lots of CPU.

sonygod commented 5 years ago

after use StringBuf, wow, it's only used 0ms.

   package;
import StringBuf;
import sys.io.File;
/**
 * ...
 * @author sonygod
 */
class StringBufTest 
{

     static function main()
    {
        var s = "1234567890abcdefghijklmnopqrstuvwxyz";
        var t = Date.now().getTime();
        var str = new StringBuf();

        for (i in 0...100000){
            str.add(s);
        }

        trace(Date.now().getTime() - t);
        trace(str.length);
        File.saveContent("c:/test.txt", str.toString());

    }

}
sonygod commented 5 years ago
package;
import StringBuf;

/**
 * ...
 * @author sonygod
 */
class StringBufTest 
{

     static function main()
    {
        var s = "1234567890abcdefghijklmnopqrstuvwxyz";
        var t = Date.now().getTime();
        var str = new StringBuf();

        for (i in 0...100000){
            var x=s.split('').join(',');//after add this ,it's slow down again.
            str.add(x);
        }

        trace(Date.now().getTime() - t);
        trace(str.length);

    }

}
Simn commented 5 years ago

@hughsando Could you check the most recent example with -D HXCPP_GC_GENERATIONAL? It hangs on a clean compilation, and segfaults after compiling without the flag once.

hughsando commented 5 years ago

This memory access pattern is pretty bad for hxcpp, with the split creating holes in memory that can't be filled by the retained join results. This confused hxcpp into thinking it did not need more memory, when it actually did. HXCPP_GC_MOVING is designed to help here, but it should be fixed either way now.

matthewjumpsoffbuildings commented 4 years ago

@sonygod im having the same issue, and as soon as i add the split() back in, it gets ridiculously slow

matthewjumpsoffbuildings commented 4 years ago

@hughsando is this supposed to be resolved? cause im using haxe 4 release, hl 1.10, and its still terrible

hughsando commented 4 years ago

The performance here looks good. Even with 1,000,000 iterations, hxcpp=1.6 seconds, hl.exe = 23.6s You might try using the git version of hxcpp and see if this makes a difference.

Simn commented 4 years ago

To clear up some potential confusion:

@ncannasse = HL target = slow here @hughsando = C++ target = not slow here

Also note that @Aurel300 will be working on GC improvements for HL which we plan to land in Haxe 4.1.

matthewjumpsoffbuildings commented 4 years ago

@hughsando - for 1,000,000 iterations 23.6 seconds is good?

On my ancient Macbook node does a million iterations in 480 milliseconds. You need to set your sights a bit higher mate

matthewjumpsoffbuildings commented 4 years ago

I was interested in Haxe because with HL/Neko it seems to straddle a middle ground between compile time and performance.

What I assumed was node was on one end, and would have the worst performance but no compile time. On the other end is a native C++ binary, which has the best performance, but longest compile time.

I assumed Haxe HL/Neko targets would sit in the middle of the two extremes, some compile time, better performance than node.

Turns out everything I thought about HL/Neko was wrong. For my purposes, (large loops with lots of string manipulation) Node is almost as fast as a C++ binary created by haxe, and makes HL/Neko look like an absolute joke.

Tbh Im a little disappointed cause I love Haxe in theory/principle.

Simn commented 4 years ago

For your use-case I'd focus on node. It's very well optimized for string manipulation and it will take a long time before HL can hope to compete with that.

sonygod commented 4 years ago

@matthewjumpsoffbuildings

current version 4.0 benchmark of performance of hl or hxcpp is slower than nodejs ,it's fact!

Maybe we can give time and give confidence to Haxe team .

hughsando commented 4 years ago

Yes, what this is really measuring here is GC performance. And node has many many man years of effort put into its GC. Hxcpp has quite a bit and HL does not have that much so there is nothing too surprising here.

For reference, on my older laptop, with 1000000 iterations: hxcpp = 1.36s and cppia = 1.39s so cppia gives a good compile time/performance tradoff. Again, there is not much drop between cppia and hxcpp because they are using the same garbage collection code, which is doing most of the work. Cppia is quite similar to HL, but without the hype.

One thing about GC limited string manipulation code is that it usually be significantly optimized with some effort, but this depends on the application.

ncannasse commented 4 years ago

@matthewjumpsoffbuildings please does not deduce the speed of one target for a single benchmark. There are always some specific cases were some targets will perform less good than others. That will not give a good estimate of the overall target performances at all

Especially in cases were a single benchmark has magnitude difference, it's not in general because the target is magnitude slower but because the specific implementation of the feature has a different algorithm that either has room for improvements or makes different trade-off that gives some bad results in some cases.

In case of HL, which I can speak, Strings are not implemented as linked lists (ropes) like in JS so they form a single compact block of memory. This makes String concatenation expensive : a + b will allocate a new String and copy both a and b Strings into it. Doing that many times is O(n2).

You might conclude that HL is less fast for quick String manipulation than JS, which is true, but that does not generalize to other things you might want to do with the target.

PS : reproducible cases were one target is slower than others are always welcome, they help us improve the platforms in the area where a lot of gain can be made.

matthewjumpsoffbuildings commented 4 years ago

Thanks for the info @ncannasse - I appreciate your point, however I was hoping to use Haxe for a command line tool that is almost entirely focused on large amounts of string manipulation.

I still love the idea of Haxe in theory, just a bit bummed about this particular thing... that and I wish the docs were better, but thats another story :P

RealyUniqueName commented 4 years ago

Good thing about Haxe is that you can compile to any target (e.g. to nodejs) and choose the one, which serves your current task best.

sonygod commented 4 years ago

@hughsando your test is difference between my test.

hxcpp still very slowly ,I can't reproduce your 1.6s

sys:ubuntu 18.04 cpu :16 cores mem:16G haxe:4.01 hxcpp:git version

haxe -cp ./ -main StringTest -cpp cpp -D HXCPP_GC_MOVING

image

root@ubuntu:~/test/cpp# haxelib git hxcpp https://github.com/HaxeFoundation/hxcpp.git
You already have hxcpp version git installed.
Updating hxcpp version git ...
hxcpp was updated
Library hxcpp current version is now git
root@ubuntu:~/test/cpp# haxe -cp ./ -main StringTest -cpp cpp
Type not found : StringTest
root@ubuntu:~/test/cpp# cd ../
root@ubuntu:~/test# haxe -cp ./ -main StringTest -cpp cpp
haxelib run hxcpp Build.xml haxe -Dhaxe="4.1.0-rc.1" -Dhaxe3="1" -Dhaxe4="1" -Dhaxe_ver="4.100" -Dhxcpp_api_level="400" -Dhxcpp_smart_strings="1" -Dsource-header="Generated by Haxe 4.1.0-rc.1" -Dstatic="1" -Dtarget.name="cpp" -Dtarget.static="true" -Dtarget.sys="true" -Dtarget.threaded="true" -Dtarget.unicode="true" -Dtarget.utf16="true" -Dutf16="1" -I"./" -I"" -I"/usr/local/lib/haxe/extraLibs/" -I"/usr/local/share/haxe/extraLibs/" -I"/usr/local/bin/extraLibs/" -I"/usr/local/lib/haxe/std/cpp/_std/" -I"/usr/local/share/haxe/std/cpp/_std/" -I"/usr/local/bin/std/cpp/_std/" -I"/usr/local/lib/haxe/std/" -I"/usr/local/share/haxe/std/" -I"/usr/local/bin/std/"
root@ubuntu:~/test# cd cpp
root@ubuntu:~/test/cpp# ./StringTest
StringTest.hx:23: 60236.6118164062
root@ubuntu:~/test/cpp# cd ../
root@ubuntu:~/test# haxe -cp ./ -main StringTest -cpp cpp  -D  HXCPP_GC_MOVING
haxelib run hxcpp Build.xml haxe -DHXCPP_GC_MOVING="1" -Dhaxe="4.1.0-rc.1" -Dhaxe3="1" -Dhaxe4="1" -Dhaxe_ver="4.100" -Dhxcpp_api_level="400" -Dhxcpp_smart_strings="1" -Dsource-header="Generated by Haxe 4.1.0-rc.1" -Dstatic="1" -Dtarget.name="cpp" -Dtarget.static="true" -Dtarget.sys="true" -Dtarget.threaded="true" -Dtarget.unicode="true" -Dtarget.utf16="true" -Dutf16="1" -I"./" -I"" -I"/usr/local/lib/haxe/extraLibs/" -I"/usr/local/share/haxe/extraLibs/" -I"/usr/local/bin/extraLibs/" -I"/usr/local/lib/haxe/std/cpp/_std/" -I"/usr/local/share/haxe/std/cpp/_std/" -I"/usr/local/bin/std/cpp/_std/" -I"/usr/local/lib/haxe/std/" -I"/usr/local/share/haxe/std/" -I"/usr/local/bin/std/"
root@ubuntu:~/test# cd cpp
root@ubuntu:~/test/cpp# ./StringTest
StringTest.hx:23: 59342.0219726562

image

Link: StringBufTest
root@ubuntu:~/test# cd cpp
root@ubuntu:~/test/cpp# ls
Build.xml  HxcppConfig.h  include  obj  Options.txt  src  StringBufTest  StringBufTest.hash  StringTest  StringTest.hash
root@ubuntu:~/test/cpp# ./StringBufTest
StringBufTest.hx:22: 159.102294921875
StringBufTest.hx:23: 7100000
root@ubuntu:~/test/cpp# ./StringBufTest
StringBufTest.hx:22: 169.566162109375
StringBufTest.hx:23: 7100000
hughsando commented 4 years ago

Yes, I am not looking at "StringTest", but only "StringBufTest". If I understand your output here, it is showing 0.169 seconds which is around to what I get. The StringTest usage will not be optimised any more, since the real answer here is to use a StringBuf - this is basically why this class exits. You might be able to do something fancy with an abstract and "operator +=" to give almost the same syntax, and I guess node is doing something similar to this under the hood.