Утечка памяти из критических разделов в Vista / Win2008?

Похоже, что частое использование критических секций в Vista / Windows Server 2008 приводит к тому, что ОС не полностью восстанавливает память. Мы обнаружили эту проблему в приложении Delphi, и она явно связана с использованием CS API. (см. этот SO вопрос)

Кто-нибудь еще видел это с приложениями, разработанными на других языках (C ++, ...)?

Пример кода просто инициализировал 10000000 CS, а затем удалял их. Это прекрасно работает в XP / Win2003, но не освобождает всю пиковую память в Vista / Win2008 до завершения работы приложения.
Чем больше вы используете CS, тем больше ваше приложение бесплатно сохраняет память.


person Francesca    schedule 30.04.2009    source источник
comment
Привет, Франсуа. У вас есть новости по этому поводу? Мне любопытно :)   -  person Alex    schedule 19.05.2009
comment
Смотрите мой собственный ответ. В категории «Это функция, а не ошибка» действительно произошли изменения ...   -  person Francesca    schedule 20.05.2009
comment
Привет, Франсуа. Спасибо, что поделился. Кстати, вы можете принять свой ответ;)   -  person Alex    schedule 22.05.2009


Ответы (3)


Microsoft действительно изменила способ InitializeCriticalSection работы в Vista, Windows Server 2008 и, вероятно, также в Windows 7.
Они добавили «функцию» для сохранения некоторой памяти, используемой для отладочной информации, когда вы выделяете кучу CS. Чем больше вы выделяете, тем больше памяти сохраняется. Он может быть асимптотическим и в конечном итоге выровняться (не полностью купленный для этого).
Чтобы избежать этой «функции», вы должны использовать новый API InitalizeCriticalSectionEx и передать флаг CRITICAL_SECTION_NO_DEBUG_INFO.
Преимущество этого заключается в том, что это может быть быстрее, поскольку очень часто только spincount будет использоваться без фактического ожидания.
Недостатки в том, что ваши старые приложения могут быть несовместимы, вам нужно изменить свой код, и теперь он зависит от платформы (вы должны проверить версию, чтобы определить, какую из них использовать). А также вы теряете возможность отлаживать, если вам это нужно.

Тестовый комплект для зависания Windows Server 2008:
- создайте этот пример C ++ как CSTest.exe

#include "stdafx.h" 
#include "windows.h" 
#include <iostream> 

using namespace std; 

void TestCriticalSections() 
{ 
  const unsigned int CS_MAX = 5000000; 
  CRITICAL_SECTION* csArray = new CRITICAL_SECTION[CS_MAX];  

  for (unsigned int i = 0; i < CS_MAX; ++i)  
    InitializeCriticalSection(&csArray[i]);  

  for (unsigned int i = 0; i < CS_MAX; ++i)  
    EnterCriticalSection(&csArray[i]);  

  for (unsigned int i = 0; i < CS_MAX; ++i)  
    LeaveCriticalSection(&csArray[i]);  

  for (unsigned int i = 0; i < CS_MAX; ++i)  
    DeleteCriticalSection(&csArray[i]); 

  delete [] csArray; 
} 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
  TestCriticalSections(); 

  cout << "just hanging around..."; 
  cin.get(); 

  return 0; 
}

-... Запустите этот командный файл (требуется sleep.exe из серверного SDK)

@rem you may adapt the sleep delay depending on speed and # of CPUs 
@rem sleep 2 on a duo-core 4GB. sleep 1 on a 4CPU 8GB. 

@for /L %%i in (1,1,300) do @echo %%i & @start /min CSTest.exe & @sleep 1 
@echo still alive? 
@pause 
@taskkill /im cstest.* /f

-... и вы увидите сервер Win2008 с 8 ГБ и четырехъядерным процессором, который зависнет до достижения 300 запущенных экземпляров.
-... повторите на сервере Windows 2003 и вы увидите, как он справляется с этим как с шармом.

person Francesca    schedule 20.05.2009
comment
Привет, Франсуа, спасибо за информацию. Не могли бы вы вставить свой код раздела инициализации Delphi для автоматического использования InitializeCriticalSectionEx при работе в Vista / Windows2008? - person carlmon; 13.02.2011

Ваш тест, скорее всего, не отражает проблемы. Критические разделы считаются «облегченными мьютексами», потому что настоящий мьютекс ядра не создается при инициализации критического раздела. Это означает, что ваши 10 миллионов критических разделов - это просто структуры с несколькими простыми членами. Однако, когда два потока обращаются к CS одновременно, для их синхронизации действительно создается мьютекс - и это отдельная история.

Я предполагаю, что в вашем реальном приложении потоки сталкиваются, в отличие от вашего тестового приложения. Теперь, если вы действительно рассматриваете критические разделы как легкие мьютексы и создаете их много, ваше приложение может выделять большое количество реальных мьютексов ядра, которые намного тяжелее, чем объект легкого критического раздела. А поскольку мьютексы являются объектом ядра, их чрезмерное количество может серьезно повредить ОС.

Если это действительно так, вам следует уменьшить использование критических секций, где вы ожидаете много столкновений. Это не имеет ничего общего с версией для Windows, поэтому мое предположение может быть неверным, но все же это следует учитывать. Попробуйте отслеживать количество дескрипторов ОС и посмотрите, как работает ваше приложение.

person eran    schedule 30.04.2009
comment
Главное, что один и тот же код (просто вызов API) хорошо работает в XP / 2003, но не в Vista / 2008. - person Francesca; 30.04.2009
comment
Я понятия не имею, как работает ваш код, но, по крайней мере, для тестового приложения, которое многократно выделяет и освобождает память, возможно, что диспетчер памяти пытается кэшировать свободную память, а не возвращать ее в ОС, предполагая, что это потребуется в ближайшее время . Vista и XP, вероятно, имеют разные менеджеры памяти, отсюда и разница. Происходит ли то же самое при размещении какой-либо другой произвольной структуры того же размера вместо реальной CS? Вы действительно видите, что создается много дескрипторов? - person eran; 30.04.2009

Вы видите что-то еще.

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

int _tmain(int argc, _TCHAR* argv[])
{
    while (true)
    {
        CRITICAL_SECTION* cs = new CRITICAL_SECTION[1000000];
        for (int i = 0; i < 1000000; i++) InitializeCriticalSection(&cs[i]);
        for (int i = 0; i < 1000000; i++) DeleteCriticalSection(&cs[i]);
        delete [] cs;
    }

    return 0;
}
person Michael    schedule 30.04.2009
comment
спасибо Майкл. Вы контролировали память для своего приложения, когда оно запущено и бездействует, во время выполнения теста CS, и после того, как оно его выполнило, когда оно простаивает и все еще не завершено. Память всегда полностью восстанавливается после завершения работы приложения, проблема в том, что она все еще активна после того, как много использовала CS. (надеюсь, что это ясно) - person Francesca; 30.04.2009
comment
Обратите внимание на бесконечный цикл (while (true)). Я следил за ним, пока приложение активно работало, создавая и удаляя критические разделы. Как и ожидалось, использование памяти было постоянным. - person Michael; 30.04.2009
comment
Я ожидал, что он будет подниматься и опускаться, когда вы создаете и удаляете свои критические разделы, показывающие красивые зубья пилы в Process Explorer (частные байты). Кстати, я сделал это с 10 000 000, если это имеет значение. - person Francesca; 30.04.2009
comment
Что, если вы вставите readln из ввода std после вашего / delete [] cs; /, чтобы приостанавливать его между каждым циклом? Объем памяти должен снизиться до начального значения. - person Francesca; 30.04.2009
comment
Я не уверен, что вы хотите, чтобы я доказал. . . выделение и освобождение миллиардов критических разделов в этом тестовом приложении позволяет поддерживать постоянное использование памяти с течением времени. Если бы произошла утечка при создании / удалении критических разделов в системе, даже размером всего один байт на критическую секцию, мы бы увидели, что использование ресурсов резко возрастет. - person Michael; 30.04.2009
comment
Вы измеряли начальный объем памяти до while (true) и непосредственно перед возвратом? - person user81126; 30.04.2009