减少Dart中的包装对象数量
我的项目是把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 个回答
以下是一个示例代码,展示了包装器的速度比正常情况慢了6.3倍:
- 构造函数只负责包装值。如果你需要转换,可以使用其他方法,比如其他额外的构造函数。
- 比较运算符被拆分,以减少不必要的类型检查。
- 算术运算符得到了改进,增加了类型检查。
- 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();
}
我还没试过这个,不过可以考虑不把对象封装起来,而是把你在Python中需要的每个方法都单独写成一个函数,并加上类型检查。这样的话,两个之间共有的部分就能以最快的速度运行。只在一个Python类中实现的方法会比较快,而你只需要对那些在Python中类型变化很大的东西进行很多类型检查。
或者直接用Dart写一个Python解释器。
即使在你给出的例子中,如果你使用常量对象,而不是每次都新分配一个PyNum,效果可能会更好。
我遇到了类似的情况,需要把一些额外的信息和基本数据类型(比如字符串、整数、浮点数等)联系起来。
我没有找到其他解决办法,只能把它们包装起来。
你可以尝试通过以下方式来优化这些包装类:
- 把它们设为常量(使用常量构造函数)
- 尽可能把字段设为最终的(final)
- 可能还有其他方法
编辑
- 你肯定想要去掉所有那些 switch 语句。
- 0.134285 秒:没有包装类
- 0.645971 秒:使用简化的构造函数和 <= 操作符(见下文)
使用常量构造函数没有显著的差别(可能在转换为 JavaScript 时更重要) - 1.449707 秒:使用简化的构造函数
- 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);
}
另请参见: