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)
地图慢得惊人
这并不是说映射很慢,而是您的测试没有考虑编译器优化。而且似乎是在调试模式下运行的?
让我们看一下稍微详细一点的测试(迭代次数减少 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.cpp和Anon.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++ 编译器之间的优化差异。