Утечка памяти при использовании цитонизированной библиотеки python в c ++ - PullRequest
0 голосов
/ 28 января 2020

У меня есть сценарий, написанный на python. Я его цитонизирую и подключаю как C ++ модуль. Исходя из этого, я создаю dll и подключаю его к проекту в c #, где вызов библиотеки должен go через несколько раз.

Проблема возникает именно при повторном запуске библиотеки, потому что при первой обработке скрипта ОЗУ не очищается, что не позволяет перезапустить его. Python состоит из модулей, которые занимают много памяти, поэтому одноразовое использование библиотеки занимает 160 МБ ОЗУ. Я пытался использовать Py_Finalize (), но, насколько я понимаю, он удалил для меня только динамические c разделы (~ 86 МБ), поэтому повторная инициализация оказалась ошибкой. Если вы не используете Py_Finalize (), то каждый перезапуск будет занимать + 80-90 МБ памяти, что после повторных запусков становится очень большой проблемой.

Библиотека 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, и основное приложение заканчивается.

Возможно, я забыл чтобы описать некоторые другие детали, поэтому исправьте меня в комментариях, если требуется дополнительная информация

1 Ответ

1 голос
/ 28 января 2020

Вы можете инициализировать / завершать интерпретатор Python несколько раз, но это приведет к утечкам памяти. На практике вы должны вызывать initialize / finalize только один раз за время жизни приложения. Из документации Python функции Py_FinalizeEx [1]:

Ошибки и предостережения: уничтожение модулей и объектов в модулях происходит в случайном порядке; это может привести к сбою деструкторов ( del ()), если они зависят от других объектов (даже функций) или модулей. Динамически загружаемые модули расширения, загруженные Python, не выгружаются. Небольшие объемы памяти, выделенные интерпретатором Python, могут быть не освобождены (если вы обнаружили утечку, сообщите об этом). Память, связанная круговыми ссылками между объектами, не освобождается. Некоторая память, выделенная модулями расширения, может быть не освобождена. Некоторые расширения могут работать некорректно, если их процедура инициализации вызывается более одного раза; это может произойти, если приложение вызывает Py_Initialize () и Py_FinalizeEx () более одного раза.

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

Подробнее на топи c:

  1. { ссылка }
  2. { ссылка } (Python модуль графа объектов для проверки ссылок : https://mg.pov.lt/objgraph/)

В системе отслеживания ошибок Python имеется несколько ошибок, связанных с этой проблемой. Они могут когда-нибудь исправить утечки памяти в самом интерпретаторе CPython, но утечки памяти, вызванные загруженными модулями / библиотеками / расширениями, никогда не будут исправлены. См. Например:

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

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

...