3

这是我的问题(大图)。我有一个使用大型和复杂(我的意思是包含多层嵌套结构)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[] 类,但这似乎是不可能的,因为它是最终的。我的第二种方法是创建一个包含 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;}
}
4

1 回答 1

6

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

我提前为我的啰嗦而道歉。

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

综上所述,如果您想让该x = [foo(1:2).bar]语法在作为 Java 数组的 M 代码中工作foo,那基本上是可能的。和都在调用 Java 之前在 Matlab 中进行评估().您可以做的是在 Matlab OOP 中定义您自己的自定义 JavaArrayWrapper 类,对应于您的 Java 数组包装器类,并将您的(可能包装的)Java 数组包装在其中。让它覆盖subsrefandsubsasgn来处理()and .。对于(),对数组进行正常的子集化,将其返回包装在 JavaArrayWrapper 中。对于这种.情况:

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

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

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

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

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

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 代码对象。

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

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

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

于 2013-03-20T20:16:50.613 回答