减少Dart中的包装对象数量

2 投票
3 回答
1128 浏览
提问于 2025-04-17 22:57

我的项目是把Python 2.7的代码转换成Dart代码。为了能完全模拟Python数据类型的所有特性,我在Dart中创建了一些包装类,这些类扩展了Dart基本数据类型的功能,使它们能匹配相应的Python类型。比如,数字用的是$PyNum,字符串用的是$PyString,等等。现在一切都很好,转换后的代码也能正常工作。代码示例是:

def fib(n):
    if n <= 2:
        return 1
    else:
        return fib (n - 1) + fib (n - 2)

print (fib(36))

生成的Dart代码是:

import 'lib/inbuilts.dart';
import 'dart:io';

fib(n) {
    if (n <= new $PyNum(2)) {
        return new $PyNum(1);
    } else {
        return (fib((n - new $PyNum(1))) + fib((n - new $PyNum(2))));
    }
}

main() {
    stdout.writeln(fib(new $PyNum(36)));
}

这段代码运行得很好,但在一些极端递归的代码中,每次函数调用都会创建大量的包装对象,这对代码的运行时间影响很大。例如,未包装的Dart代码:

import'dart:io';

fib(n) {
    if (n <= 2) {
        return 1;
    } else {
        return (fib((n - 1)) + fib((n - 2)));
    }
}

main() {
    stdout.writeln(fib(36));
}

这段代码的运行速度几乎比包装后的代码快15倍,原因很明显。所有涉及包装数据类型的计算都会返回这个类的新实例。对我来说,能通过Dart模拟Python数据类型提供的所有特性是非常重要的,而包装是我目前想到的唯一方法。我尝试使用单例类来创建一个公共对象进行计算,但在递归和多线程的情况下失败了

我的$PyNum包装类是这样的:

class $PyNum {
    num _value;

    $PyNum(value) {
        switch ($getType(value)) {
            case 6:
                _value = value;
                break;
            case 7:
                try {
                    _value = num.parse(value);
                } catch (ex) {
                    print("Invalid string literal for num parsing");
                    exit(1);
                }
                break;
            case 5:
                _value = value.value();
                break;
            default:
                throw "Invalid input for num conversion";
        }
    }

    value() => _value;
    toString() => _value.toString();

    operator +(other) => new $PyNum(_value + other.value());
    operator -(other) => new $PyNum(_value - other.value());
    operator *(other) => new $PyNum(_value * other.value());
    operator ~/(other) => new $PyNum(_value ~/ other.value());
    operator |(other) => new $PyNum(_value | other.value());
    operator &(other) => new $PyNum(_value & other.value());
    operator ^(other) => new $PyNum(_value ^ other.value());
    operator %(other) => new $PyNum(_value % other.value());
    operator <<(other) => new $PyNum(_value << other.value());
    operator >>(other) => new $PyNum(_value >> other.value());
    operator ==(other) {
        switch ($getType(other)) {
            case 6:
                return _value == other;
            case 5:
                return _value == other.value();
            default:
                return false;
        }
    }
    operator <(other) {
        switch ($getType(other)) {
            case 6:
                return _value < other;
            case 5:
                return _value < other.value();
            default:
                return true;
        }
    }
    operator >(other) => !(this < other) && (this != other);
    operator <=(other) => (this < other) || (this == other);
    operator >=(other) => (this > other) || (this == other);
}

$getType(variable) {
    if (variable is bool)
        return 0;
    else if (variable is $PyBool)
        return 1;
    else if (variable is $PyDict)
        return 2;
    else if (variable is $PyList)
        return 3;
    else if (variable is List)
        return 4;
    else if (variable is $PyNum)
        return 5;
    else if (variable is num)
        return 6;
    else if (variable is $PyString)
        return 7;
    else if (variable is $PyTuple)
        return 8;
    else
        return -1;
}

能从这个类创建常量对象吗?我不太确定该怎么做。

有没有其他更有效的方法来做到这一点,同时还能模拟Python的所有特性?非常感谢任何帮助!

3 个回答

1

以下是一个示例代码,展示了包装器的速度比正常情况慢了6.3倍:

  1. 构造函数只负责包装值。如果你需要转换,可以使用其他方法,比如其他额外的构造函数。
  2. 比较运算符被拆分,以减少不必要的类型检查。
  3. 算术运算符得到了改进,增加了类型检查。
  4. Python的类型被组合成一个叫做$PyType的组。这减少了类型检查的次数。

这个示例中去掉了不必要的代码。

import 'dart:io';

fib(n) {
  if (n <= 2) {
    return 1;
  } else {
    return (fib((n - 1)) + fib((n - 2)));
  }
}

fib2(n) {
  if (n <= new $PyNum(2)) {
    return new $PyNum(1);
  } else {
    return (fib2((n - new $PyNum(1))) + fib2((n - new $PyNum(2))));
  }
}

main() {
  measure("fib", () => stdout.writeln(fib(42)));
  measure("fib2", () => stdout.writeln(fib2(new $PyNum(42))));
}

void measure(String msg, f()) {
  var sw = new Stopwatch();
  sw.start();
  f();
  sw.stop();
  print("$msg: ${sw.elapsedMilliseconds}");
}

class $PyTypes {
  static const $PyTypes NUM = const $PyTypes("NUM");

  final String name;

  const $PyTypes(this.name);
}

abstract class $PyType {
  $PyTypes get pyType;
}

class $PyNum extends $PyType {
  final int value;

  $PyTypes get pyType => $PyTypes.NUM;

  $PyNum(this.value);

  operator +(other) {
    if (other is $PyType) {
      switch (other.pyType) {
        case $PyTypes.NUM:
          $PyNum pyNum = other;
          return new $PyNum(value + pyNum.value);
      }
    } else if (other is int) {
      return new $PyNum(value + other);
    }

    throw new ArgumentError("other: $other");
  }

  operator -(other) {
    if (other is $PyType) {
      switch (other.pyType) {
        case $PyTypes.NUM:
          $PyNum pyNum = other;
          return new $PyNum(value - pyNum.value);
      }
    } else if (other is int) {
      return new $PyNum(value - other);
    }

    throw new ArgumentError("other: $other");
  }

  operator ==(other) {
    if (other is $PyType) {
      switch (other.pyType) {
        case $PyTypes.NUM:
          $PyNum pyNum = other;
          return value == pyNum.value;
      }
    } else if (other is int) {
      return value == other;
    }

    throw new ArgumentError("other: $other");
  }

  operator <(other) {
    if (other is $PyType) {
      switch (other.pyType) {
        case $PyTypes.NUM:
          $PyNum pyNum = other;
          return value < pyNum.value;
      }
    } else if (other is int) {
      return value < other;
    }

    throw new ArgumentError("other: $other");
  }

  operator <=(other) {
    if (other is $PyType) {
      switch (other.pyType) {
        case $PyTypes.NUM:
          $PyNum pyNum = other;
          return value <= pyNum.value;
      }
    } else if (other is int) {
      return value <= other;
    }

    throw new ArgumentError("other: $other");
  }

  String toString() => value.toString();
}
2

我还没试过这个,不过可以考虑不把对象封装起来,而是把你在Python中需要的每个方法都单独写成一个函数,并加上类型检查。这样的话,两个之间共有的部分就能以最快的速度运行。只在一个Python类中实现的方法会比较快,而你只需要对那些在Python中类型变化很大的东西进行很多类型检查。

或者直接用Dart写一个Python解释器。

即使在你给出的例子中,如果你使用常量对象,而不是每次都新分配一个PyNum,效果可能会更好。

4

我遇到了类似的情况,需要把一些额外的信息和基本数据类型(比如字符串、整数、浮点数等)联系起来。
我没有找到其他解决办法,只能把它们包装起来。

你可以尝试通过以下方式来优化这些包装类:

  • 把它们设为常量(使用常量构造函数)
  • 尽可能把字段设为最终的(final)
  • 可能还有其他方法

编辑
- 你肯定想要去掉所有那些 switch 语句。

  1. 0.134285 秒:没有包装类
  2. 0.645971 秒:使用简化的构造函数和 <= 操作符(见下文)
    使用常量构造函数没有显著的差别(可能在转换为 JavaScript 时更重要)
  3. 1.449707 秒:使用简化的构造函数
  4. 3.792590 秒:你的代码
class $PyNum2 {
  final num _value;

  const $PyNum2(this._value);

  factory $PyNum2.from(value) {
    switch ($getType2(value)) {
      case 6:
        return new $PyNum2(value);
        break;
      case 7:
        try {
          return new $PyNum2(num.parse(value));
        } catch (ex) {
          print("Invalid string literal for num parsing");
          exit(1);
        }
        break;
      case 5:
        return new $PyNum2(value.value());
        break;
      default:
        throw "Invalid input for num conversion";
    }
  }

  value() => _value;
  toString() => _value.toString();

  operator +(other) => new $PyNum2(_value + other.value());
  operator -(other) => new $PyNum2(_value - other.value());
  operator *(other) => new $PyNum2(_value * other.value());
  operator ~/(other) => new $PyNum2(_value ~/ other.value());
  operator %(other) => new $PyNum2(_value % other.value());
  operator ==(other) {
    switch ($getType2(other)) {
      case 6:
        return _value == other;
      case 5:
        return _value == other.value();
      default:
        return false;
    }
  }
  operator <(other) {
    switch ($getType2(other)) {
      case 6:
        return _value < other;
      case 5:
        return _value < other.value();
      default:
        return true;
    }
  }
  operator >(other) => !(this < other) && (this != other);
  operator <=(other) => this.value() <= other.value(); //(this < other) || (this == other);
  operator >=(other) => (this > other) || (this == other);
}

另请参见:

撰写回答