TL;DR: 使用一些高级的OOP M代码技巧,可以改变
()
和
.
的行为,通过定义一个在Java包装器类之上定义
subsref
的Matlab包装器类来实现。但是由于固有的Matlab-to-Java开销,它可能不会比普通Matlab代码更快,只是更复杂和繁琐。除非您也将逻辑移入Java中,否则此方法可能不会加速您的代码。
在开始之前,请先对从您的Matlab代码调用的Java结构的性能进行基准测试。虽然Java字段访问和方法调用本身要比Matlab快得多,但从M代码调用它们存在相当大的开销,因此,除非您将大部分逻辑下推到Java中,否则您可能最终会损失速度。每次穿过M代码到Java层,都需要付出代价。请参考这个答案中的基准测试:Is MATLAB OOP slow or am I doing something wrong? 让您有一个大致的了解。(完全披露:那是我的答案。)它不包括Java字段访问,但由于自动装箱开销,它可能与方法调用的数量相当。如果您像示例中那样编写Java类,使用getter和setter方法而不是公共字段(也就是说,在“良好”的Java风格中),则每次访问都会产生Java方法调用的成本,与纯Matlab结构相比,它将是糟糕的。
尽管如此,如果您想要在 x = [foo(1:2).bar]
语法内部使其能够在M代码中正常工作,其中 foo
是一个Java数组,基本上是可以实现的。在Matlab OOP中定义一个自定义的JavaArrayWrapper类对应于您的Java数组包装器类,并将您的(可能已包装的)Java数组包装在其中。覆盖 subsref
和 subsasgn
以处理 ()
和 .
。对于 ()
,对数组进行正常的子集处理,并将其包装在JavaArrayWrapper中返回。对于 .
情况:
- 如果包装的对象是标量,则像平常一样调用Java方法。
- 如果包装的对象是数组,则循环遍历它,在每个元素上调用Java方法,并收集结果。如果结果是Java对象,则将它们包装在JavaArrayWrapper中返回。
但是。由于穿过Matlab / Java界限的开销,这将很慢,可能比纯Matlab代码慢一个数量级。
为了让它快速运行,您可以提供一个相应的自定义Java类,包装Java数组并使用Java Reflection API从每个选择的数组成员对象中提取属性并将其收集到数组中。关键是,在Matlab中进行“链式”引用时,如
x = foo(1:3).a.b.c
,并且
foo
是一个对象时,它不会逐步评估其中的每一步,即先评估
foo(1:3)
,然后在结果上调用
.a
,等等。实际上,它会解析整个
(1:3).a.b.c
引用,将其转换为结构化参数,并将整个引用传递给
foo
的
subsref
方法,该方法负责解释整个链。隐式调用看起来像这样。
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) {
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();
}
}
然后,您的 M 代码包装器类将检查结果并决定它是否应作为 Matlab 数组或逗号分隔列表(即多个 argouts,使用 [...]
收集)返回为基本类型,或者应包装在另一个 JavaArrayWrapper M 代码对象中。
这个 M 代码包装器类看起来会像这样。
classdef MyMJavaArrayWrapper < handle
properties
jWrappedArray
end
methods
function varargout = subsref(obj, s)
if isequal(s(1).type, '()')
indices = s(1).subs;
s(1) = [];
else
indices = [];
end
fieldNameChain = parseFieldNamesFromArgs(s);
out = getNestedFields( jWrappedArray, indices, fieldNameChain );
varargout = unpackResultsAndConvertIfNeeded(out);
end
end
end
执行subsasgn调用时涉及到的结构体编组和解组成本可能会压倒Java代码的速度优势。您可以通过用C中的MEX实现替换M代码实现subsasgn来消除这种开销,使用JNI生成Java对象,调用getNestedFields并将结果转换为Matlab结构。这远远超出了我能给出示例的范围。如果您觉得这有点可怕,我完全同意。您在这里碰到了语言的边缘,并试图从用户空间扩展语言(尤其是提供新的句法行为)真的很难。我不认真地在生产代码中执行此类操作;只是试图概述您正在查找的问题领域。您是否正在处理这些深度嵌套结构的同质数组?也许可以将它们转换为“平面组织”的结构,其中一次结构标量,而不是具有标量字段的结构数组。然后,您可以在纯M代码中对它们进行矢量化操作。这将使事情变得更快,特别是使用save和load时,其中开销按照mxarray每个缩放。
[foo(1:3).bar.baz]
的语法在普通的Matlab结构体中是否有效,还是会抛出一个()-indexing must appear last in an index expression
的错误? - Andrew Janke