Утечка памяти и значительное снижение производительности при создании экземпляра COM-объекта .NET без GuidAttribute. - PullRequest
0 голосов
/ 18 февраля 2012

Рассмотрим следующий пример простейшего COM-объекта, который мы можем определить в C # (созданного с использованием Visual Studio 2010 SP1 с .NET Framework 4.0):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;

namespace CcwTestLib
{
   [ComVisible(true)]   
   [Guid("8ABD40E2-05E2-4436-9EAD-073911357155")]  
   public class CcwTestObject
   {

   }
}

Мы компилируем эту сборку и регистрируем ее для COM-взаимодействия, используя regasm (или встроенную опцию в Visual Studio).

Теперь мы просто пишем неуправляемое консольное приложение Win32 на C ++, которое ничего не делает, кроме как создает экземпляр этого объекта и выпускает его 100 000 раз. Например, используя следующую программу:

#include "stdafx.h"

// {8ABD40E2-05E2-4436-9EAD-073911357155}
static const GUID CLSID_CcwTestObject = 
   { 0x8abd40e2, 0x5e2, 0x4436, { 0x9e, 0xad, 0x7, 0x39, 0x11, 0x35, 0x71, 0x55 } };

int _tmain(int argc, _TCHAR* argv[])
{
   CoInitializeEx(NULL,  COINIT_MULTITHREADED);

   IUnknown *pTestObject = NULL;

   const int iCount = 100000;

   wprintf(L"Allocating COM instance %i times...\n", iCount);

   for (int i = 0; i < iCount; i++)
   {
      HRESULT hr = CoCreateInstance(CLSID_CcwTestObject, 
                                    NULL, 
                                    CLSCTX_INPROC_SERVER, 
                                    IID_IUnknown, 
                                    (LPVOID*)&pTestObject);
      if (FAILED(hr))
      {
         wprintf(L"Error: %i", hr);
         return -1;
      }

      pTestObject->Release();
   }

   CoUninitialize();

   return 0;
}

При запуске этого приложения в нашей локальной системе оно выполняется примерно за 820 мс и потребляет около 32 МБ памяти. Увеличение iCount до 10 000 000 делает выполнение программы намного дольше (конечно), но, учитывая потребление памяти, оно увеличивается примерно до 92 МБ и остается там до конца выполнения программы. Пока ничего странного.

Теперь по интересной части, приводящей к моему вопросу. Давайте удалим атрибут Guid из класса .NET (и отключим автоматическую регистрацию COM, если он включен, чтобы предыдущая регистрация все еще оставалась нетронутой в реестре) и перестроим сборку.

Мы снова запускаем тестовую программу с iCount, установленным на 100 000. На этот раз программа завершается примерно за 90000 мс ! Это примерно в 100 раз медленнее , чем раньше!

Еще более интересно и неприятно, когда мы увеличиваем iCount до 10 000 000 и запускаем программу. Если мы отслеживаем потребление памяти с помощью Process Explorer, VMMap или аналогичной программы, мы видим, что он медленно увеличивается, но не останавливается на 92 МБ, как мы могли бы ожидать. Вместо этого, кажется, продолжается вечно. Предположительно, приложение будет аварийно завершать работу при исчерпании пространства виртуальной памяти размером около 2 ГБ (поскольку это процесс x86), но, поскольку оно движется так медленно, мы не ожидали, что это произойдет в этом тесте, а остановились на 1200 МБ. *

Следует отметить, что использование COM-объекта, вызов его методов и т. Д. (Если мы их определили) работает нормально, как и должно, поскольку вся необходимая информация для создания объекта хранится в реестре. Часть этого в нашей системе выглядит следующим образом:

[HKEY_CLASSES_ROOT\CLSID\{8ABD40E2-05E2-4436-9EAD-073911357155}\InprocServer32]
@="mscoree.dll"
"ThreadingModel"="Both"
"Class"="CcwTestLib.CcwTestObject"
"Assembly"="CcwTestLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
"RuntimeVersion"="v4.0.30319"
"CodeBase"=file:///D:/Coding/Projects/CcwTest/CcwTestLib/bin/Debug/CcwTestLib.dll

Где CLSID правильно указывает на сборку и ее кодовую базу, а также на явный тип в сборке.

Мы также обнаружили, что изменение Guid в атрибуте на что-либо кроме того, с которым он зарегистрирован, создает ту же проблему.

Так почему это происходит? Это ошибка в .NET? И есть ли решение этой проблемы?

Я был бы очень рад получить некоторое представление об этой проблеме, которая заняла у нас около недели, чтобы сузить этот значительно упрощенный сценарий от обнаруженной утечки памяти в нашем продукте.

1 Ответ

0 голосов
/ 05 мая 2013

Кажется, утечка памяти была ошибкой, которая была исправлена ​​в .NET 4.5.

Для получения дополнительной информации см. Проблему в Microsoft Connect .

Цитироватьиз их ответа:

Утечка памяти уже исправлена ​​(предварительная версия .NET Framework 4.5 для разработчиков была первой общедоступной сборкой с исправлением).Что касается медлительности, несоответствие Guid эффективно деактивирует наш внутренний кэш, поэтому мы выполняем кучу операций поиска в реестре и поиска по именам при каждой активации.Поскольку это не основной сценарий, и его следует ограничить случаями, когда класс был переназначен без обновления регистрации, сейчас мы удаляем эту проблему.Это все еще отслеживается для будущих выпусков.

...