【问题标题】:Reduce number of Wrapper Objects in Dart减少 Dart 中包装器对象的数量
【发布时间】:2014-04-25 15:18:37
【问题描述】:

我的项目涉及将 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;
}

可以从这个类中取出 const 对象吗?我不太清楚该怎么做。

有没有其他方法可以有效地做到这一点并且仍然能够模拟 Python 的所有功能?非常感谢任何帮助!

【问题讨论】:

    标签: python oop python-2.7 dart wrapper


    【解决方案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】:

      我有类似的情况,我需要将附加信息与基本数据类型(如 String、int、double、...
      除了包装它们,我没有找到解决方案。

      你可以尝试通过

      来优化这些包装类
      • 使它们成为 const(使用 const 构造函数
      • 尽可能使字段成为最终字段
      • 其他的可能是可能的

      编辑
      - 你肯定想摆脱所有这些 switch 语句。

      1. 0.134285 秒:无包装
      2. 0.645971 秒:使用简化的构造函数,运算符 使用 const 构造函数并没有显着差异(转换为 JS 时可能更重要)
      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);
      }
      

      另见:

      【讨论】:

      • 我已经用我写的 Wrapper 类更新了这个问题。这可以转换为 const 构造函数吗?
      • 我对此表示怀疑,但您可以轻松创建一个单独的 const 构造函数,例如fromInt(this._value)
      • 但是我必须使 _value 成为最终的,对吗?它的值在任何数学运算中都会被修改,如果我把它做成最终的我猜是不可能的?
      • 我只是看了一眼,但我的印象是你为结果创建了一个新对象。有时间我会仔细看看。
      • @Gunther,代码运算符 (this this.value()
      【解决方案3】:

      我还没有尝试过,但是永远不要包装对象,并且对于您在 Python 中需要的任何方法,将其实现为带有类型案例的独立函数。因此,两者之间的任何共同点都会全速运行。只在一个 Python 类中实现的方法会非常快,而且您只需对 Python 中的超态事物进行大量类型测试。

      或者只是在 Dart 中编写一个 Python 解释器。

      即使在您提供的示例中,如果您使用 const 对象而不是每次都分配一个新的 PyNum,您可能会做得更好。

      【讨论】:

      • 我已经用我写的 Wrapper 类更新了这个问题。这可以转换为 const 构造函数吗?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-02-12
      • 2020-12-30
      • 2019-03-26
      • 1970-01-01
      • 2018-09-01
      • 1970-01-01
      相关资源
      最近更新 更多