【问题标题】:Mimicking Matlab Structures Using Java Objects使用 Java 对象模仿 Matlab 结构
【发布时间】:2013-03-20 17:00:48
【问题描述】:

这是我的问题(大图)。我有一个项目使用大型和复杂的(我的意思是包含多层嵌套结构)Matlab 结构。这是可以预见的缓慢(尤其是在尝试加载/保存时)。我试图通过将其中一些结构转换为 Java 对象来改进运行时。问题是这些 Matlab 结构中的数据在很多地方都可以访问,因此任何需要重写访问语法的事情都是禁止的。因此,我需要 Java 对象尽可能地模仿 Matlab 结构的行为,特别是在访问存储在其中的值时(这些值只设置在一个地方,因此 java 中缺少运算符重载来设置不是考虑因素)。

我遇到的问题(小图)在于从这些结构的数组中访问数据。例如,

person(1)
  .age = 20
  .name
    .first = 'John'
    .last = 'Smith

person(2)
  .age = 25
  .name
    .first = 'Jane'
    .last = 'Doe'

Matlab 将允许您执行以下操作,

>>age = [person(1:2).age]
age = 
    20    25

尝试用 Java 完成同样的任务,

>>jperson = javaArray('myMatlab.Person', 2);
>>jperson(1) = Person(20, Name('John', 'Smith'));
>>jperson(2) = Person(25, Name('Jane', 'Doe'));
>>age = [jperson(1:2).age]
??? No appropriate method or public field age for class myMatlab.Person[]

有什么方法可以让 Java 对象模仿这种行为? 我的第一个想法是简单地扩展Person[] class,但this doesn't appear to be possible because it is final. 我的第二种方法是创建一个包含 Person 的 ArrayList 的包装类,但是我不相信这会起作用,因为调用

wrappedPerson(1:2)

将被解释为对 WrappedPerson 类的构造函数调用或尝试访问不存在的 WrappedPerson 数组的元素(因为 java 不允许我覆盖“()”运算符)。任何见解将不胜感激。

我用于 java 类的代码是

public class Person {
  int _age;
  ArrayList<Name> _names;

  public Person(int age, Name name) {
    _age = age;
    _names.add(name);
  }

  public int age() {return _age;}
  public void age(int age) {_age = age;}
  public Name[] name() {return _names.toArray(new Name[0]);}
  public void name(Name name) { _names.add(name);}
}

public class Name {
  String _first;
  String _last;

  public Name(String first, String last) {
    _first = first;
    _last = last;
  }

  public int first() {return _first;}
  public void first(String firstName) {_first = firstName;}
  public int last() {return _last;}
  public void last(String lastName) {_last = lastName;}
}

【问题讨论】:

  • 考虑到:这不是 Java 运算符,您期望如何通过 1:2?
  • Matlab 自动处理该转换。如果您有一个大小为 4 的 Person 数组,person = new Person[4],您可以在 Matlab 中调用 person(1:2),它将数组的前两个元素作为大小为 2 的 Person 数组返回。
  • Waitaminnit... [foo(1:3).bar.baz] 语法是否适用于普通的 Matlab 结构,还是会引发 ()-indexing must appear last in an index expression 错误?
  • 取决于什么是“bar”。如果它是一个结构数组,那么您必须分两步完成,{a = [foo(1:3).bar]; a = [a(:).baz]}

标签: java matlab interop


【解决方案1】:

TL;DR:这是可能的,有一些花哨的 OOP M 代码技巧。改变(). 的行为可以通过在Java 包装类之上定义subsref 的Matlab 包装类来完成。但是由于固有的 Matlab 到 Java 的开销,它可能最终不会比普通的 Matlab 代码快,只是更加复杂和繁琐。除非您也将逻辑移到 Java 中,否则这种方法可能不会为您加快速度。

我提前为我啰嗦了。

在您全力以赴之前,您可以对从您的 Matlab 代码调用的 Java 结构的性能进行基准测试。虽然 Java 字段访问和方法调用本身比 Matlab 快得多,但从 M 代码调用它们会产生大量开销,因此除非您也将大量逻辑下推到 Java 中,否则您很可能最终会速度净损失。每次您将 M 代码跨到 Java 层时,您都需要付费。看看这个答案的基准:Is MATLAB OOP slow or am I doing something wrong? 以了解规模。 (完全公开:这是我的答案之一。)它不包括 Java 字段访问,但由于自动装箱开销,它可能在方法调用的顺序上。如果您像示例中那样使用 getter 和 setter 方法而不是公共字段(即“良好”Java 风格)编写 Java 类,那么每次访问都会产生 Java 方法调用的成本,并且与纯 Matlab 结构相比,它会糟糕

话虽如此,如果你想让x = [foo(1:2).bar] 语法在foo 是Java 数组的M 代码中工作,那基本上是可能的。 (). 在调用 Java 之前都在 Matlab 中进行了评估。您可以做的是在 Matlab OOP 中定义您自己的自定义 JavaArrayWrapper 类,对应于您的 Java 数组包装器类,并在其中包装您的(可能包装的)Java 数组。让它覆盖subsrefsubsasgn 来处理().。对于(),对数组进行常规子集化,将其返回包装在 JavaArrayWrapper 中。对于. 案例:

  • 如果包装对象是标量,则照常调用 Java 方法。
  • 如果包装的对象是一个数组,则循环遍历它,对每个元素调用 Java 方法,然后收集结果。如果结果是 Java 对象,则将它们包装在 JavaArrayWrapper 中返回。

但是。由于跨越 Matlab/Java 障碍的开销,这会很慢,可能比纯 Matlab 代码慢一个数量级。

要使其快速工作,您可以提供一个相应的自定义 Java 类,该类包装 Java 数组并使用 Java 反射 API 提取每个选定数组成员对象的属性并将它们收集到一个数组中。关键是,当您在 Matlab 中执行“链式”引用时,例如 x = foo(1:3).a.b.cfoo 是一个对象,它不会在评估 foo(1:3) 时进行逐步评估,然后在结果上调用 .a , 等等。它实际上解析整个(1:3).a.b.c 引用,将其转换为结构化参数,并将整个内容传递给foosubsref 方法,该方法负责解释整个链。隐式调用看起来像这样。

x = subsref(foo, [ struct('type','()','subs',{{[1 2 3]}}), ...
                   struct('type','.', 'subs','a'), ...
                   struct('type','.', 'subs','b'), ... 
                   struct('type','.', 'subs','c') ] )

因此,鉴于您可以预先访问整个引用“链”,如果 foo 是定义 subsasgn 的 M 代码包装类,您可以将整个引用转换为 Java 参数并传递它在对 Java 包装器类的单个方法调用中,然后使用 Java 反射动态地遍历包装的数组,选择引用元素,并执行链式引用,所有这些都在 Java 层内。例如。它会在这样的 Java 类中调用 getNestedFields()

public class DynamicFieldAccessArrayWrapper {
    private ArrayList _wrappedArray;

    public Object getNestedFields(int[] selectedIndexes, String[] fieldPath) {
        // Pseudo-code:
        ArrayList result = new ArrayList();
        if (selectedIndexes == null) {
            selectedIndexes = 1:_wrappedArray.length();
        }
        for (ix in selectedIndexes) {
            Object obj = _wrappedArray.get(ix-1);
            Object val = obj;
            for (fieldName in fieldPath) {
                java.lang.reflect.Field field = val.getClass().getField(fieldName);
                val = field.getValue(val);
            }
            result.add(val);
        }
        return result.toArray(); // Return as array so Matlab can auto-unbox it; will need more type detection to get array type right
    }
}

然后您的 M 代码包装器类将检查结果并确定它是否是原始的并且应该作为 Matlab 数组或逗号分隔列表返回(即多个 argouts,使用 [...] 收集),或者应该包装在另一个 JavaArrayWrapper M-code 对象中。

M 代码包装类看起来像这样。

classdef MyMJavaArrayWrapper < handle
    % Inherit from handle because Java objects are reference-y
    properties
        jWrappedArray  % holds a DynamicFieldAccessArrayWrapper
    end
    methods
        function varargout = subsref(obj, s)
            if isequal(s(1).type, '()')
                indices = s(1).subs;
                s(1) = [];
            else
                indices = [];
            end
            % TODO: check for unsupported indexing types in remaining s
            fieldNameChain = parseFieldNamesFromArgs(s);
            out = getNestedFields( jWrappedArray, indices, fieldNameChain );
            varargout = unpackResultsAndConvertIfNeeded(out);
        end
    end
end

编组和解组 subsasgn 调用的值所涉及的开销可能会超过 Java 位的任何速度增益。

您可以通过将 subsasgn 的 M 代码实现替换为在 C 中执行结构编组和解组的 MEX 实现来消除这种开销,使用 JNI 构建 Java 对象,调用 getNestedFields,并将结果转换为Matlab 结构。这远远超出了我可以举的例子。

如果你觉得这有点吓人,我完全同意。您在这里遇到了语言的边缘,并且尝试从用户空间扩展语言(尤其是提供新的句法行为)真的很难。我不会认真地在生产代码中做这样的事情;只是想勾勒出你正在寻找的问题所在的区域。

您是否正在处理这些深度嵌套结构的同构数组?也许可以将它们转换为“平面组织”结构,而不是具有标量字段的结构数组,而是具有数组字段的标量结构。然后,您可以在纯 M 代码中对它们进行矢量化操作。这将使事情变得更快,尤其是使用 saveload,其中每个 mxarray 的开销都会增加。

【讨论】:

  • 首先,非常感谢您的回复。那里有很多重要的信息,我将进行处理。要回答您的几个问题,1)在我的情况下,几乎可以肯定跨越 Matlab / Java 边界,因为我可以在 Java 端进行大量并行处理,而我没有工具箱可做所以在Matlab方面。最初的基准测试表明节省了大量资金,这就是我进行到这一点的原因。您提出了一个关于字段访问与方法访问的有趣问题。我会检查一下。
  • 2) 不幸的是,我被“原样”的结构组织所困。一个快速的后续问题,将 M 代码层实现为 MCOS 还是 UDD 会更好吗?
  • 1) 是的,如果您在 Java 端做一些重要的工作,而不是仅仅将其用作“哑”数据容器,您会看到很多改进。只需尝试定义您的 Java 接口,使其接受较大的参数,最大限度地提高每次方法调用传递的数据比率,以分摊跨越边界的成本。
  • 2) 很难说。如果你痴迷于速度,UDD 在几个版本之前更快,但我最近没有对它进行基准测试。试试你的版本。 (请参阅我的链接答案。)但 MCOS 支持 handle 语义,这在某些情况下更适合 Java 对象行为。如果您需要支持obj.method(arg1, arg2) 语法,那么您将无法使用 MCOS。但是method(obj, arg1, arg2) 语法更快,并且允许您在 MCOS 和 UDD 实现之间切换,因此如果您现在可以选择,您应该更喜欢语法。另外,现在 Mathworks 支持 MCOS,这可能胜过一切。
  • 嗯...看来字段访问可能比方法访问更快。我在 java 中编写了一个带有公共双精度的测试类和一个返回该双精度的方法。然后我使用这两种访问方法从 Matlab 调用它 10,000 次。结果是字段访问耗时 0.3297 秒,方法访问耗时 0.6328 秒。
猜你喜欢
  • 2010-10-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-04
  • 2013-08-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多