Я работаю над программой, состоящей из двух параллельных потоков. Один (здесь «Clock») выполняет некоторые вычисления на регулярной основе (10 Гц) и довольно интенсивно использует память. Другой (здесь «огромный список») использует даже больше оперативной памяти, но не так критичен по времени, как первый. Поэтому я решил уменьшить его приоритет до THREAD_PRIORITY_LOWEST. Тем не менее, когда поток освобождает большую часть памяти, которую использовал, критический не удается сохранить время.
Мне удалось сжать проблему до этого фрагмента кода (убедитесь, что оптимизации отключены!):
в то время как Clock пытается поддерживать тактовую частоту 10 Гц, нить огромныйList-потока распределяет и освобождает все больше и больше памяти, не организованной в каких-либо порциях.
#include "stdafx.h"#include <stdio.h>
#include <forward_list>
#include <time.h>
#include <windows.h>
#include <vector>
void wait_ms(double _ms)
{
clock_t endwait;
endwait = clock () + _ms * CLOCKS_PER_SEC/1000;
while (clock () < endwait) {} // active wait
}
void hugeList(void)
{
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_LOWEST);
unsigned int loglimit = 3;
unsigned int limit = 1000;
while(true)
{
for(signed int cnt=loglimit; cnt>0; cnt--)
{
printf(" Countdown %d...\n", cnt);
wait_ms(1000.0);
}
printf(" Filling list...\n");
std::forward_list<double> list;
for(unsigned int cnt=0; cnt<limit; cnt++)
list.push_front(42.0);
loglimit++;
limit *= 10;
printf(" Clearing list...\n");
while(!list.empty())
list.pop_front();
}
}
void Clock()
{
clock_t start = clock()-CLOCKS_PER_SEC*100/1000;
while(true)
{
std::vector<double> dummyData(100000, 42.0); // just get some memory
printf("delta: %d ms\n", (clock()-start)*1000/CLOCKS_PER_SEC);
start = clock();
wait_ms(100.0);
}
}
int main()
{
DWORD dwThreadId;
if (CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&Clock, (LPVOID) NULL, 0, &dwThreadId) == NULL)
printf("Thread could not be created");
if (CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&hugeList, (LPVOID) NULL, 0, &dwThreadId) == NULL)
printf("Thread could not be created");
while(true) {;}
return 0;
}
Прежде всего я заметил, что выделение памяти для связанного списка происходит намного быстрее, чем его освобождение.
На моей машине (Windows7) примерно на 4-й итерации метода «огромный список» Clock-Thread значительно возмущается (до 200 мс). Эффект исчезает без dummyData-вектора, «запрашивающего» память в Clock-Thread.
Так,
Обратите внимание, что мой исходный код использует некоторую связь через общие переменные, которые потребуются для какого-то IPC, если я выберу второй вариант.
Обратите внимание, что мой исходный код застревает примерно на 1 секунду, когда эквивалент метода «огромный список» очищает boost :: unordered_map и вводит ntdll.dll! RtIInitializeCriticalSection много раз.
(наблюдается исследователем систинернала)
Обратите внимание, что наблюдаемые эффекты не из-за подкачки, я использую 1,4 ГБ из 16 ГБ (64-битная win7).
редактировать:
Я просто хотел сообщить вам, что до сих пор я не смог решить мою проблему. Разделение обеих частей кода на два процесса не представляется возможным, так как мое время довольно ограничено, и я до сих пор никогда не работал с процессами. Боюсь, я не смогу вовремя добраться до работающей версии.
Однако мне удалось уменьшить эффекты, уменьшив количество выделений памяти некритическим потоком. Это было достигнуто с помощью быстрого распределителя памяти пула (как тот, который предоставлен в библиотеке повышения).
Кажется, не существует возможности явного создания определенных объектов (например, огромного списка пересылки в моем примере) в какой-то куче потока, которая не требует синхронизации.
Для дальнейшего чтения:
http://bmagic.sourceforge.net/memalloc.html
Узкое место в распределении / выделении памяти?
http://software.intel.com/en-us/articles/avoiding-heap-contention-among-threads
http://www.boost.org/doc/libs/1_55_0/libs/pool/doc/html/boost_pool/pool/introduction.html
Заменив std :: forward_list на std :: list, я запускал ваш код на компьютере corei7 4GB, пока не израсходовал 2GB. Нарушений нет вообще. (В отладочной сборке)
P.S
Да. Сборка релиза воссоздает проблему. Я заменил передний список на массив
double* p = new double[limit];
for(unsigned int cnt=0; cnt<limit; cnt++)
p[cnt] = 42.0;
а также
for(unsigned int cnt=0; cnt<limit; cnt++)
p[cnt] = -1;
delete [] p;
Это не воссоздает тогда.
Кажется, что планировщик потоков требует много маленьких кусочков памяти.
Других решений пока нет …