【问题标题】:Memory leak when using cythonized python in c ++ library在 c ++ 库中使用 cythonized python 时的内存泄漏
【发布时间】:2020-05-13 15:00:16
【问题描述】:

我有一个用 python 编写的脚本。我对它进行 cytonize 并将其作为 C++ 模块插入。由此,我创建了 dll 并将其连接到 c# 中的项目,其中库调用必须经过多次。

问题正是由于库的重复启动而出现的,因为第一次处理脚本时,RAM 没有被清除,这会阻止它重新启动。 Python 由占用大量内存的模块组成,因此单次使用该库需要 160MB 的 RAM。 我尝试使用 Py_Finalize(),但据我了解,他只为我删除了动态部分(~86MB),所以重新初始化结果是一个错误。如果不使用Py_Finalize(),那么每次重启都会占用+80-90MB的内存,重复启动后就会成为一个很大的问题。

C++库:运行python的方法

void MLWrapper::outputInfo(char * curDirPath) {
    auto err = PyImport_AppendInittab("runML", PyInit_runML);
    wchar_t* szName = GetWC(curDirPath);
    Py_SetPythonHome(szName);
    Py_Initialize();
    auto module = PyImport_ImportModule("runML");
    mlDataG.predictionResult = runTab(&mlDataG);
    Py_Finalize();}

C#:使用 dll 的类

public class ExternalHelpers : IDisposable
{
    private IntPtr _libraryHandle;
    private OutputInfoDelegate _outputInfo;

    private delegate void OutputInfoDelegate([MarshalAs(UnmanagedType.LPStr)]string dirPath);

    public ExternalHelpers()
    {
        _libraryHandle = UnsafeMethods.LoadLibrary("MLWrapper.dll");

        if (_libraryHandle == IntPtr.Zero)
        {
            Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
        }

        _outputInfo = LoadExternalFunction<OutputInfoDelegate>(@"?outputInfo@MLWrapper@@YAXPEAD@Z") as OutputInfoDelegate;
    }

    public void OutputInfo(string path)
    {
        _outputInfo(path);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    ~ExternalHelpers()
    {
        Dispose(false);
    }


    private Delegate LoadExternalFunction<Delegate>(string functionName)
        where Delegate : class
    {
        IntPtr functionPointer =
            UnsafeMethods.GetProcAddress(_libraryHandle, functionName);

        if (functionPointer == IntPtr.Zero)
            Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
        // Marshal to requested delegate
        return Marshal.GetDelegateForFunctionPointer(functionPointer, typeof(Delegate)) as Delegate;
    }

    private void Dispose(bool disposing)
    {
        if (disposing)
        {
            _outputInfo = null;
        }

        if (_libraryHandle != IntPtr.Zero)
        {
            while (UnsafeMethods.FreeLibrary(_libraryHandle) == true)
            {
                continue;
            }

            //if (!UnsafeMethods.FreeLibrary(_libraryHandle))
            //    Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());

            _libraryHandle = IntPtr.Zero;
        }
    }
}

C#:方法调用

using (ExternalHelpers e = new ExternalHelpers())
{
    ...                     
    e.OutputInfo(@"C:\Users\user\source\repos\Project\bin\x64\Debug");...}

我该如何解决这个问题?

我也有动态重新连接库的想法。所以我可以完全关闭库并释放内存,但是当你清除模块的内存时,库关闭并退出代码:1 并且主应用程序结束。

也许我忘了描述一些其他细节,如果需要更多信息,请在 cmets 中纠正我

【问题讨论】:

  • 您编写了一个在 c++ 中运行的 python 程序,您从 c# 调用?是否有机会将其完全转换为一种语言?
  • 为什么不仅在 Python 解释器尚未初始化时才初始化,即在使用 Py_IsInitialized() 查询状态并仅在程序关闭时才完成?一些扩展(我认为 numpy 是(或至少是)其中之一)在同一进程中多次初始化/完成时存在问题(不确定内存泄漏是可能发生的最坏情况)。

标签: c# python c++ memory-leaks cython


【解决方案1】:

您可以多次初始化/完成 Python 解释器,但这会导致内存泄漏。在实践中,您应该在应用程序生命周期内只调用一次初始化/完成。来自Py_FinalizeEx[1]函数的Python文档:

错误和警告:模块和模块中的对象的破坏是 以随机顺序完成;这可能会导致析构函数(del() 方法) 当它们依赖于其他对象(甚至函数)或模块时失败。 由 Python 加载的动态加载的扩展模块不是 卸载。 Python解释器分配的少量内存 可能无法释放(如果发现泄漏,请报告)。记忆捆绑 up 在对象之间的循环引用不会被释放。一些记忆 由扩展模块分配的可能不会被释放。一些扩展可能 如果他们的初始化例程被调用超过 一次;如果应用程序调用 Py_Initialize() 和 Py_FinalizeEx() 不止一次。

参考:[1]https://docs.python.org/3/c-api/init.html#c.Py_FinalizeEx

更多主题:

  1. https://stackoverflow.com/a/8799062/623622
  2. https://stackoverflow.com/a/48269611/623622(用于检查引用的 Python 对象图模块:https://mg.pov.lt/objgraph/

Python 错误跟踪器中报告了多个与此问题相关的错误。他们可能有一天会修复 CPython 解释器本身的内存泄漏,但由加载的模块/库/扩展引起的内存泄漏将永远无法修复。例如:

A. https://bugs.python.org/issue1445210

B. https://bugs.python.org/issue1635741

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-03-16
    • 2014-11-03
    • 1970-01-01
    • 2016-06-11
    • 2021-05-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多