提高将numpy数组转换为MATLAB double的性能

13

在Python中调用MATLAB肯定会导致一些性能下降,我可以通过重写(很多)Python代码来避免这种情况。然而,这对我来说不是一个现实的选择,但令我烦恼的是,从numpy数组到MATLAB双精度数之间的简单转换就导致了巨大的效率损失。

我指的是从data1到data1m的以下转换:

data1 = np.random.uniform(low = 0.0, high = 30000.0, size = (1000000,))
data1m = matlab.double(list(data1))

这里的 matlab.double 来自于 Mathworks 自己的 MATLAB 包/引擎。在我的系统上,第二行代码需要 20 秒左右,这似乎对于一个只是让数字“可读”于 MATLAB 的转换来说太长了。

因此,基本上我正在寻找与此处所提供的相反技巧相反的技巧,用于将 MATLAB 输出转换回 Python。


我正在使用Matlab 2022a和Python 3.9,问题似乎不存在 - 即可以将Numpy数组转换为Matlab double而无需任何列表。 - Des
3个回答

10

高效传递numpy数组

查看文件夹PYTHONPATH\Lib\site-packages\matlab\_internal中的mlarray_sequence.py文件。在那里,您将找到MATLAB数组对象的构建。性能问题来自于在generic_flattening函数内部使用循环进行数据复制。

为了避免这种行为,我们将稍微编辑文件。此修复应该适用于复杂和非复杂数据类型。

  1. 在出现问题的情况下请备份原始文件。

  2. 在文件开头的其他导入语句中添加import numpy as np

  3. 在第38行,您应该找到:

    init_dims = _get_size(initializer)
    

    请将此替换为:

    try:
        init_dims=initializer.shape
    except:
        init_dims = _get_size(initializer)
    
  4. 在第48行,您应该找到:

    if is_complex:
        complex_array = flat(self, initializer,
                             init_dims, typecode)
        self._real = complex_array['real']
        self._imag = complex_array['imag']
    else:
        self._data = flat(self, initializer, init_dims, typecode)
    

    请用以下内容替换:

    if is_complex:
        try:
            self._real = array.array(typecode,np.ravel(initializer, order='F').real)
            self._imag = array.array(typecode,np.ravel(initializer, order='F').imag)
        except:
            complex_array = flat(self, initializer,init_dims, typecode)
            self._real = complex_array['real']
            self._imag = complex_array['imag']
    else:
        try:
            self._data = array.array(typecode,np.ravel(initializer, order='F'))
        except:
            self._data = flat(self, initializer, init_dims, typecode)
    
    现在你可以直接将NumPy数组传递给MATLAB数组创建方法。
    data1 = np.random.uniform(low = 0.0, high = 30000.0, size = (1000000,))
    #faster
    data1m = matlab.double(data1)
    #or slower method
    data1m = matlab.double(data1.tolist())
    
    data2 = np.random.uniform(low = 0.0, high = 30000.0, size = (1000000,)).astype(np.complex128)
    #faster
    data1m = matlab.double(data2,is_complex=True)
    #or slower method
    data1m = matlab.double(data2.tolist(),is_complex=True)
    

    现在MATLAB数组创建的性能提高了15倍,界面更易于使用。


谢谢你的建议!这看起来非常有前途,因为转换时间从40秒缩短到了0.6秒。然而,当我将它们用作输入时,现在出现了“分段错误(核心已转储)”错误。通过步进调试可以发现,在调用函数(而不是转换)时,“future = pythonengine.evaluateFunction(...)” [matlabengine.py中的第77行]时,_MLArrayMetaClass的__init__再次被调用,现在它会在改变过的第38行出错:AtributeError: 'double'对象没有属性'shape'。也许它在这里尝试初始化函数的输出? - 5Ke
是的,现在它可以工作了!您的方法将总转换时间从40秒减少到不到0.5秒! :-) 它并没有减少脚本本身的计算时间 - 这完全有道理,但这又让我想知道为什么保存/加载到.mat文件会减少计算时间。 - 5Ke
2
是的,但显然更高效一些;)。还请注意:如果您正在保存和加载一个相对较小的mat文件,该mat文件将由您的工作系统在内存中缓存,因此磁盘I/O没有额外开销。如果文件变得更大,情况可能会有所不同。我无法查看编译接口代码,但如果Matworks在Python接口中做了类似的工作,保存和加载的效率更高也就不足为奇了。 - max9111
如果data1是一个二维数组,那么matlab.double(data1.tolist())是不可避免的。 - seralouk
实际上,在进行这些修改后,matlab.double 的速度确实更快了。然而,在我的情况下,使用引擎执行 matlab 函数的实际速度非常慢。我发现这个链接(https://dev59.com/LlcO5IYBdhLWcg3wvUQp#45284125)很有帮助。 - seralouk
显示剩余8条评论

4
在等待更好的建议时,我将发布迄今为止想出的最佳技巧。它的核心是使用 `scipy.io.savemat` 保存文件,然后在 MATLAB 中加载该文件。
这不是最美观的黑客技巧,并且需要注意确保依赖于同一脚本的不同进程不会写入和加载彼此的.mat文件,但对我来说性能提升是值得的。
作为测试案例,我编写了两个简单几乎相同的 MATLAB 函数,需要 2 个 numpy 数组(我测试了长度为 1000000)和一个 int 作为输入。
function d = test(x, y, fs_signal)
d = sum((x + y))./double(fs_signal);

function d = test2(path)
load(path)
d = sum((x + y))./double(fs_signal);

test函数需要进行转换,而test2函数需要保存。

测试test:在我的系统上,将两个NumPy数组进行转换大约需要40秒钟。准备和运行测试的总时间为170秒

测试test2:在我的系统上,将数组和整数保存大约需要0.35秒钟。令人惊讶的是,在MATLAB中加载.mat文件非常高效(或者更令人惊讶的是,它在处理其双精度数据时非常低效)... 准备和运行test2的总时间为0.38秒

这几乎是性能提升了450倍...


也许编写自己的C++代码会有所帮助。使用例如cython将数据从Python转换为C++应该非常容易,然后您可以使用MATLAB的mex API创建MATLAB变量,并分配与Python(现在是C++)数据相同的内存指针。这两种方法肯定非常快速(因为它只是创建对象和分配指针),并且应该比依赖IO更优雅的解决方案。 - Ander Biguri
也许这会有帮助:https://github.com/kmatzen/matlab-python 这是一个包装器,用于 matlab C 接口,应该能提供不错的速度。 - max9111
暂时转向C++有点太艰巨了,虽然Cython确实看起来很有趣。我猜这取决于实施这一切的回报。如果切换到mex API,matlab函数本身的性能是否也会有所提升? - 5Ke
@max9111:链接已失效。 - Eric
这是唯一一个真正降低了执行时间的答案。其他所有答案都是关于优化加载和传递数组,但关键是Matlab引擎本质上就很慢。这个方法很有效,谢谢。 - seralouk

3

我的情况有些不同(从Matlab调用Python脚本),但是将ndarray转换为array.array极大地加快了进程。基本上,它与Alexandre Chabot的解决方案非常相似,但不需要修改任何文件:

#untested i.e. only deducted from my "matlab calls python" situation
import numpy
import array

data1 = numpy.random.uniform(low = 0.0, high = 30000.0, size = (1000000,))
ar = array.array('d',data1.flatten('F').tolist())
p = matlab.double(ar)
C = matlab.reshape(p,data1.shape) #this part I am definitely not sure about if it will work like that

如果使用Matlab,"array.array"和"double"的组合是相对快速的。在Matlab 2016b + python 3.5.4 64位测试通过。


1
我可以确认,这种方法比从Python传输数据到MATLAB的方法double(py.array.array('d', py.numpy.nditer(data1)))快3到5倍。干得好!加一分。顺便问一下,您是否知道如何以及是否可能在不复制内存的情况下传输Python数组到MATLAB(就像传递指针一样)? - Dev-iL
@Dev-iL 这是你要找的吗?我仍在努力让管理员将MATLAB API安装到我的conda环境中,以便我可以真正加快速度,但现在这个方法可行:https://www.mathworks.com/matlabcentral/answers/216498-passing-numpy-ndarray-from-python-to-matlab#answer_487604 - brethvoice
1
@brethvoice 我主要使用MATLAB,并使用matpy类来传递数据。当然,如果你或其他人有提高其性能的想法,那将不胜感激 :) - Dev-iL
@Dev-iL 我假设将数据在从Python传回MATLAB之前转换为MATLAB期望的格式将加速处理速度。但这需要您在Python中安装和使用matlab包,这需要管理员权限,所以我还没有尝试过。虽然可能不奏效,但是此网页使它看起来似乎有可能:https://www.mathworks.com/help/compiler_sdk/python/matlab-arrays-as-python-variables.html - brethvoice
错误:ValueError: initializer必须是一个矩形嵌套序列 - ch271828n

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接