【问题标题】:Improve performance of converting numpy array to MATLAB double提高将 numpy 数组转换为 MATLAB double 的性能
【发布时间】:2021-12-16 13:46:00
【问题描述】:

从 Python 调用 MATLAB 势必会降低一些性能,而我可以通过在 Python 中重写(大量)代码来避免这种情况。然而,这对我来说不是一个现实的选择,但让我很恼火的是,从 numpy 数组到 MATLAB double 的简单转换导致效率的巨大损失。

我说的是下面从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 的数字“可食用”之外并没有真正做任何事情的转换来说似乎太多了。

所以基本上我正在寻找一种与给定here 相反的技巧,用于将 MATLAB 输出转换回 Python。

【问题讨论】:

    标签: python matlab numpy


    【解决方案1】:

    高效传递 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 秒,这似乎很有希望。但是,当我将它们用作输入时,我现在收到 Segmentation fault (core dumped) 错误。逐步查看显示,在调用函数(而不是转换)时,future = pythonengine.evaluateFunction(...) [matlabengine.py 中的第 77 行],_MLArrayMetaClass 的 init 再次被调用,现在它跳过了更改后的第 38 行:属性错误:“双”对象没有属性“形状”。也许它会尝试在这里初始化函数的输出?
    • 我现在更新了我的代码。至少像 b=engine.sqrt(data2m) 这样的东西现在可以工作了。
    • 是的,现在可以了!您的方法将总转换时间从 40 秒减少到不到 0.5 秒! :-) 它并没有减少脚本本身的计算时间——这完全有道理,但它再次让我想知道为什么保存/加载到 .mat 文件会这样做。
    • 如何测量脚本的计算时间?你是在matlab还是python端测量时间?请注意,仅改进了到 Matlab mlarray 对象的数组转换,而不是与 matlab 引擎本身之间的传输(如果他们在此处复制而不是传递指针)。
    • 是的,但显然效率更高;)。另请注意:如果您正在保存和加载一个相对较小的 mat 文件,matfile 由您的工作系统缓存在内存中,因此磁盘 I/O 没有开销。如果文件变大,这可能看起来有点不同。我无法查看编译后的界面代码,但如果 Matworks 做了与 python 界面类似的工作,我并不惊讶于保存和加载效率更高。
    【解决方案2】:

    在等待更好的建议的同时,我将发布迄今为止我想出的最佳技巧。归结为使用 `scipy.io.savemat´ 保存文件,然后在 MATLAB 中加载此文件。

    这不是最漂亮的 hack,它需要小心确保依赖同一脚本的不同进程不会最终写入和加载彼此的 .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 数组需要 cirka 40 秒。准备和运行测试的总时间降至 170 秒

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

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

    【讨论】:

    • 也许编写自己的 C++ 代码可能会有所帮助。将数据从 python 转换为 C++ 应该很容易,例如cython,然后您可以使用 MATLAB 的 mex API 创建一个 MATLAB 变量并分配与 python(现在是 C++)数据相同的内存指针。这两者当然都非常快(因为它只是创建对象和分配指针)并且应该是比在 IO 上中继更优雅的解决方案。
    • 也许这会有所帮助:github.com/kmatzen/matlab-python 它是 matlab C 接口的包装器,应该可以提供不错的速度。
    • 现在转到 C++ 有点太令人生畏了,尽管 Cython 看起来确实很有趣。我想这取决于实现这一点的努力的回报。切换到 mex API 时,matlab 函数本身是否有可能也显示出改进的性能?
    • @max9111:链接已失效。
    • 这是唯一真正使执行时间缩短的答案。所有其他的都是关于优化加载和传递数组,但关键是 matlab 引擎在定义上很慢。这成功了谢谢
    【解决方案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 64bit 测试。

    【讨论】:

    • 我可以确认这种方法比 double(py.array.array('d', py.numpy.nditer(data1))) 快 x3-x5 用于将数据从 python 传输到 MATLAB。好样的! +1。顺便说一句,您知道是否以及如何在不复制内存的情况下将 python 数组传输到 MATLAB(就像传递指针一样)?
    • @Dev-iL 这就是您要找的吗?我仍在尝试让管理员将 MATLAB API 安装到我的 conda 环境中,这样我就可以真正加快速度,但这目前有效:mathworks.com/matlabcentral/answers/…
    • @brethvoice 我主要在 MATLAB 中工作,并且正在使用 matpy 类的某个版本来回传递数据。当然,如果您或其他任何人对如何提高其性能有任何想法,将不胜感激:)
    • @Dev-iL 我假设将数据转换为 MATLAB 的预期形式将其从 Python 传递回 MATLAB 会加快速度。这需要您在 Python 中安装和使用 matlab 包;需要管理员权限,所以我还没有完成。它可能不起作用,但这个网页使它看起来像它可能:mathworks.com/help/compiler_sdk/python/…
    • 错误:ValueError: initializer must be a rectangular nested sequence
    猜你喜欢
    • 1970-01-01
    • 2016-11-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-30
    • 2018-01-08
    • 2021-04-30
    • 2012-06-15
    相关资源
    最近更新 更多