Haxe 地图 vs 动态对象 vs 固定对象性能 CPP

Ner*_*ian 0 dictionary haxe object

与动态对象相比,haxe 中的地图似乎非常慢

我会避开他们。

所以使用这段代码:

        var nd=()->{
        
         //var op:Dynamic  = {x:100,y:1000};
         //op.z = 22;

         var op = {x:100,y:1000,z:22}

        //var op = ['x'=>100,'y'=>1000];
        //op['z'] = 22;

        var i;
        for(i in 0...1000000)
        {
            /*
           op['x']++;
           op['y']--;
           op['z']++;
           */
           op.x++;
           op.y--;
           op.z++;
        }

        trace('Line');
        }

        var j;
        var q:Float = haxe.Timer.stamp();
        for(j in 0...100) nd();
        trace(haxe.Timer.stamp()-q);
Run Code Online (Sandbox Code Playgroud)

  • 地图:19秒
  • 动态物体:9秒
  • 物体:0.6秒

地图慢得惊人

Yel*_*ife 5

这并不是说映射很慢,而是您的测试没有考虑编译器优化。而且似乎是在调试模式下运行的?

让我们看一下稍微详细一点的测试(迭代次数减少 10 倍,顺序和平均值打乱):

import haxe.DynamicAccess;

class Main {
    static inline var times = 10000;
    static function testInline() {
        var o = { x: 100, y: 1000, z: 22 };
        for (_ in 0 ... times) {
            o.x++;
            o.y--;
            o.z++;
        }
    }
    
    static function getClass() {
        return new Vector(100, 1000, 22);
    }
    static function testClass() {
        var o = getClass();
        for (_ in 0 ... times) {
            o.x++;
            o.y--;
            o.z++;
        }
    }
    
    static function testClassDynamic() {
        var o:Dynamic = getClass();
        for (_ in 0 ... times) {
            o.x++;
            o.y--;
            o.z++;
        }
    }
    
    static function getObj() {
        return { x: 100, y: 1000, z: 22 };
    }
    static function testObj() {
        var o = getObj();
        for (_ in 0 ... times) {
            o.x++;
            o.y--;
            o.z++;
        }
    }
    static function testDynamic() {
        var o:Dynamic = { x: 100, y: 1000, z: 22 };
        for (_ in 0 ... times) {
            o.x++;
            o.y--;
            o.z++;
        }
    }
    
    static function testDynamicPlus() {
        var o:Dynamic = { };
        o.x = 100;
        o.y = 1000;
        o.z = 22;
        for (_ in 0 ... times) {
            o.x++;
            o.y--;
            o.z++;
        }
    }
    static function testDynamicAccess() {
        var o:DynamicAccess<Int> = getObj();
        for (_ in 0 ... times) {
            o["x"]++;
            o["y"]--;
            o["z"]++;
        }
    }
    static function testMapString() {
        var o = ["x" => 100, "y" => 1000, "z" => 22];
        for (_ in 0 ... times) {
            o["x"]++;
            o["y"]--;
            o["z"]++;
        }
    }
    static function testMapInt() {
        var o = [100 => 100, 200 => 1000, 300 => 22];
        for (_ in 0 ... times) {
            o[100]++;
            o[200]--;
            o[300]++;
        }
    }
    static function shuffleSorter(a, b) {
        return Math.random() > 0.5 ? 1 : -1;
    }
    static function main() {
        var tests = [
            new Test("inline", testInline),
            new Test("class", testClass),
            new Test("object", testObj),
            new Test("object:Dynamic", testDynamic),
            new Test("class:Dynamic", testClassDynamic),
            new Test("object:Dynamic+", testDynamicPlus),
            new Test("DynamicAccess", testDynamicAccess),
            new Test("Map<String, Int>", testMapString),
            new Test("Map<Int, Int>", testMapInt),
        ];
        
        var shuffle = tests.copy();
        var iterations = 0;
        
        while (true) {
            iterations += 1;
            Sys.println("Step " + iterations);
            
            for (i => v in shuffle) {
                var k = Std.random(shuffle.length);
                shuffle[i] = shuffle[k];
                shuffle[k] = v;
            }
            
            for (test in shuffle) {
                var t0 = haxe.Timer.stamp();
                var fn = test.func;
                for (_ in 0 ... 100) fn();
                var t1 = haxe.Timer.stamp();
                test.time += t1 - t0;
                Sys.sleep(0.001);
            }
            
            for (test in tests) {
                Sys.println('${test.name}: ${Math.ffloor(test.time / iterations * 10e6) / 1e3}ms avg');
            }
            
            Sys.sleep(1);
        }
    }
    
}

class Test {
    public var time:Float = 0;
    public var func:Void->Void;
    public var name:String;
    public function new(name:String, func:Void->Void) {
        this.name = name;
        this.func = func;
    }
    public function toString() return 'Test($name)';
}

class Vector {
    public var x:Int;
    public var y:Int;
    public var z:Int;
    public function new(x:Int, y:Int, z:Int) {
        this.x = x;
        this.y = y;
        this.z = z;
    }
}

Run Code Online (Sandbox Code Playgroud)

经过一百个左右“步骤”后的输出:

inline: 0.011ms avg
class: 15.737ms avg
object: 281.417ms avg
object:Dynamic: 275.509ms avg
class:Dynamic: 233.208ms avg
object:Dynamic+: 1208.83ms avg
DynamicAccess: 1021.248ms avg
Map<String, Int>: 1293.529ms avg
Map<Int, Int>: 916.552ms avg
Run Code Online (Sandbox Code Playgroud)

让我们看一下每个测试的编译结果。
Haxe 生成的 C++ 代码经过格式化以提高可读性

排队

这就是您正在测试的内容,尽管根据注释掉的行判断您显然怀疑某些内容。

如果它看起来快得可疑,那是因为它是 - Haxe 编译器注意到你的对象是本地的并完全内联它:

import haxe.DynamicAccess;

class Main {
    static inline var times = 10000;
    static function testInline() {
        var o = { x: 100, y: 1000, z: 22 };
        for (_ in 0 ... times) {
            o.x++;
            o.y--;
            o.z++;
        }
    }
    
    static function getClass() {
        return new Vector(100, 1000, 22);
    }
    static function testClass() {
        var o = getClass();
        for (_ in 0 ... times) {
            o.x++;
            o.y--;
            o.z++;
        }
    }
    
    static function testClassDynamic() {
        var o:Dynamic = getClass();
        for (_ in 0 ... times) {
            o.x++;
            o.y--;
            o.z++;
        }
    }
    
    static function getObj() {
        return { x: 100, y: 1000, z: 22 };
    }
    static function testObj() {
        var o = getObj();
        for (_ in 0 ... times) {
            o.x++;
            o.y--;
            o.z++;
        }
    }
    static function testDynamic() {
        var o:Dynamic = { x: 100, y: 1000, z: 22 };
        for (_ in 0 ... times) {
            o.x++;
            o.y--;
            o.z++;
        }
    }
    
    static function testDynamicPlus() {
        var o:Dynamic = { };
        o.x = 100;
        o.y = 1000;
        o.z = 22;
        for (_ in 0 ... times) {
            o.x++;
            o.y--;
            o.z++;
        }
    }
    static function testDynamicAccess() {
        var o:DynamicAccess<Int> = getObj();
        for (_ in 0 ... times) {
            o["x"]++;
            o["y"]--;
            o["z"]++;
        }
    }
    static function testMapString() {
        var o = ["x" => 100, "y" => 1000, "z" => 22];
        for (_ in 0 ... times) {
            o["x"]++;
            o["y"]--;
            o["z"]++;
        }
    }
    static function testMapInt() {
        var o = [100 => 100, 200 => 1000, 300 => 22];
        for (_ in 0 ... times) {
            o[100]++;
            o[200]--;
            o[300]++;
        }
    }
    static function shuffleSorter(a, b) {
        return Math.random() > 0.5 ? 1 : -1;
    }
    static function main() {
        var tests = [
            new Test("inline", testInline),
            new Test("class", testClass),
            new Test("object", testObj),
            new Test("object:Dynamic", testDynamic),
            new Test("class:Dynamic", testClassDynamic),
            new Test("object:Dynamic+", testDynamicPlus),
            new Test("DynamicAccess", testDynamicAccess),
            new Test("Map<String, Int>", testMapString),
            new Test("Map<Int, Int>", testMapInt),
        ];
        
        var shuffle = tests.copy();
        var iterations = 0;
        
        while (true) {
            iterations += 1;
            Sys.println("Step " + iterations);
            
            for (i => v in shuffle) {
                var k = Std.random(shuffle.length);
                shuffle[i] = shuffle[k];
                shuffle[k] = v;
            }
            
            for (test in shuffle) {
                var t0 = haxe.Timer.stamp();
                var fn = test.func;
                for (_ in 0 ... 100) fn();
                var t1 = haxe.Timer.stamp();
                test.time += t1 - t0;
                Sys.sleep(0.001);
            }
            
            for (test in tests) {
                Sys.println('${test.name}: ${Math.ffloor(test.time / iterations * 10e6) / 1e3}ms avg');
            }
            
            Sys.sleep(1);
        }
    }
    
}

class Test {
    public var time:Float = 0;
    public var func:Void->Void;
    public var name:String;
    public function new(name:String, func:Void->Void) {
        this.name = name;
        this.func = func;
    }
    public function toString() return 'Test($name)';
}

class Vector {
    public var x:Int;
    public var y:Int;
    public var z:Int;
    public function new(x:Int, y:Int, z:Int) {
        this.x = x;
        this.y = y;
        this.z = z;
    }
}

Run Code Online (Sandbox Code Playgroud)

因此,C++ 编译器可能会发现您实际上并未此函数中执行任何操作,此时内容将被删除:

在此输入图像描述

(而如果你这样做return o.z,内容将相当于相反return 10022

班级

让我们讨论一下在良好的案例场景中您应该做的事情。

类实例上的已知字段访问非常快,因为它被编译为具有直接字段访问的 C++ 类:

inline: 0.011ms avg
class: 15.737ms avg
object: 281.417ms avg
object:Dynamic: 275.509ms avg
class:Dynamic: 233.208ms avg
object:Dynamic+: 1208.83ms avg
DynamicAccess: 1021.248ms avg
Map<String, Int>: 1293.529ms avg
Map<Int, Int>: 916.552ms avg
Run Code Online (Sandbox Code Playgroud)

需要从函数调用中获取类,以防止 Haxe 编译器内联它;C++ 编译器仍可能导致 for 循环崩溃。

目的

让我们通过从函数返回匿名对象来防止编译器内联该匿名对象。

但它仍然比地图快。

由于动态对象经常使用(JSON 和所有),因此使用了一些技巧 - 例如,如果您要创建具有一组预定义字段的匿名对象,将为这些对象执行额外的工作,以便可以更快地访问它们(此处视为Create(n)后续调用链setFixed):

void Main_obj::testInline()
{
    HX_STACKFRAME(&_hx_pos_e47a9afac0942eb9_5_testInline)
    int o_x = 100;
    int o_y = 1000;
    int o_z = 22;
    {
        int _g = 0;
        while ((_g < 10000))
        {
            _g = (_g + 1);
            int _ = (_g - 1);
            o_x = (o_x + 1);
            o_y = (o_y - 1);
            o_z = (o_z + 1);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以在Anon.cppAnon.h中看到其中一些技巧。

动态的

与上面相同,但将变量键入为 Dynamic,并且无需额外的函数调用。就我个人而言,我不会依赖这种行为。

类别:动态

尽管代码实际上与上面相同,

::Vector Main_obj::getClass()
{
    HX_GC_STACKFRAME(&_hx_pos_e47a9afac0942eb9_15_getClass)
    return ::Vector_obj::__alloc(HX_CTX, 100, 1000, 22);
}

void Main_obj::testClass()
{
    HX_STACKFRAME(&_hx_pos_e47a9afac0942eb9_17_testClass)
    ::Vector o = ::Main_obj::getClass();
    {
        int _g = 0;
        while ((_g < 10000))
        {
            _g = (_g + 1);
            int _ = (_g - 1);
            o->x++;
            o->y--;
            o->z++;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这运行得更快一点。这是通过预先生成反射函数来完成的,该函数将首先检查变量是否恰好是预定义的变量之一:

::Dynamic Main_obj::getObj()
{
    HX_STACKFRAME(&_hx_pos_e47a9afac0942eb9_36_getObj)
    return ::Dynamic(::hx::Anon_obj::Create(3)
                         ->setFixed(0, HX_("x", 78, 00, 00, 00), 100)
                         ->setFixed(1, HX_("y", 79, 00, 00, 00), 1000)
                         ->setFixed(2, HX_("z", 7a, 00, 00, 00), 22));
}

void Main_obj::testObj()
{
    HX_STACKFRAME(&_hx_pos_e47a9afac0942eb9_38_testObj)
    ::Dynamic o = ::Main_obj::getObj();
    {
        int _g = 0;
        while ((_g < 10000))
        {
            _g = (_g + 1);
            int _ = (_g - 1);
            ::hx::FieldRef((o).mPtr, HX_("x", 78, 00, 00, 00))++;
            ::hx::FieldRef((o).mPtr, HX_("y", 79, 00, 00, 00))--;
            ::hx::FieldRef((o).mPtr, HX_("z", 7a, 00, 00, 00))++;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

动态访问

与 Dynamic 相同,但我们还强制运行时使用 Reflect 函数跳过一些(不必要的)环节。

void Main_obj::testClassDynamic()
{
    HX_STACKFRAME(&_hx_pos_e47a9afac0942eb9_26_testClassDynamic)
    ::Dynamic o = ::Main_obj::getClass();
    {
        int _g = 0;
        while ((_g < 10000))
        {
            _g = (_g + 1);
            int _ = (_g - 1);
            ::hx::FieldRef((o).mPtr, HX_("x", 78, 00, 00, 00))++;
            ::hx::FieldRef((o).mPtr, HX_("y", 79, 00, 00, 00))--;
            ::hx::FieldRef((o).mPtr, HX_("z", 7a, 00, 00, 00))++;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

对象:动态+

我们可以通过创建一个空的 Dynamic 对象然后用字段填充它来忽略前面提到的预定义字段优化。这使我们非常接近 Map 的性能。

::hx::Val Vector_obj::__Field(const ::String &inName,::hx::PropertyAccess inCallProp)
{
    switch(inName.length) {
    case 1:
        if (HX_FIELD_EQ(inName,"x") ) { return ::hx::Val( x ); }
        if (HX_FIELD_EQ(inName,"y") ) { return ::hx::Val( y ); }
        if (HX_FIELD_EQ(inName,"z") ) { return ::hx::Val( z ); }
    }
    return super::__Field(inName,inCallProp);
}
Run Code Online (Sandbox Code Playgroud)

地图<字符串,整数>

鉴于 Map 无法从上述大多数上下文优化中受益(事实上,许多优化对于正常用例来说没有意义),它的性能应该不会特别令人惊讶。

void Main_obj::testDynamicAccess()
{
    HX_STACKFRAME(&_hx_pos_e47a9afac0942eb9_66_testDynamicAccess)
    ::Dynamic o = ::Main_obj::getObj();
    {
        int _g = 0;
        while ((_g < 10000))
        {
            _g = (_g + 1);
            int _ = (_g - 1);
            {
                ::String tmp = HX_("x", 78, 00, 00, 00);
                {
                    int value = ((int)((::Reflect_obj::field(o, tmp) + 1)));
                    ::Reflect_obj::setField(o, tmp, value);
                }
            }
            // ...
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

地图<字符串,整数>

额外的好处:毫无疑问,计算整数的哈希值比计算字符串的哈希值要便宜。

结论

不要急于根据微基准建议的方式以一种或其他方式编写代码。例如,虽然这看起来像是一个深入的细分,但它没有考虑垃圾收集或各种 C++ 编译器之间的优化差异。