Comments 73
банальная цитата о преждевременной оптимизации
+4
Если вы обнуляете данные гигабайтами, то вы что-то делаете не так :)
Мне очень трудно представить когда это является узким местом в программе.
Мне очень трудно представить когда это является узким местом в программе.
+13
UFO just landed and posted this here
практически все известные мне менеджеры памяти не обнуляют страницы, а помечают их как свободные.
-3
UFO just landed and posted this here
Да, вы правы, на нулевом приоритете крутится поток MmZeroPageThread, который билдид список MmZeroedPageListHead.
Но никак не гигабайтами, и только во время абсолютного простоя, ибо ниже приоритета 0 нету ничего :)
Так что это слабо опровергает мои слова о том, что необходимо заострять внимание на этом аспекте.
Например, в вышеупомянутой винде код очистки крайне прост.
Дефолтный код:
И второй вариант с XMMI (extended memory managment):
Но никак не гигабайтами, и только во время абсолютного простоя, ибо ниже приоритета 0 нету ничего :)
Так что это слабо опровергает мои слова о том, что необходимо заострять внимание на этом аспекте.
Например, в вышеупомянутой винде код очистки крайне прост.
Дефолтный код:
.text:00430364 __fastcall KiZeroPages(x, x) proc near ; CODE XREF: MiZeroWorkerPages(x,x)+10Dp
.text:00430364 ; KiXMMIZeroPages(x,x)+B5j ...
.text:00430364 push edi
.text:00430365 xor eax, eax
.text:00430367 mov edi, ecx
.text:00430369 mov ecx, edx
.text:0043036B shr ecx, 2
.text:0043036E rep stosd
.text:00430370 pop edi
.text:00430371 retn
.text:00430371 __fastcall KiZeroPages(x, x) endp
И второй вариант с XMMI (extended memory managment):
.text:00430288 __fastcall KiXMMIZeroPagesNoSave(x, x) proc near
.text:00430288 ; CODE XREF: KiXMMIZeroPages(x,x)+84p
.text:00430288 ; DATA XREF: KiInitMachineDependent()+1F0o
.text:00430288
.text:00430288 var_4 = dword ptr -4
.text:00430288
.text:00430288 xorps xmm0, xmm0
.text:0043028B shr edx, 6
.text:0043028E
.text:0043028E loc_43028E: ; CODE XREF: KiXMMIZeroPagesNoSave(x,x)+19j
.text:0043028E movntps xmmword ptr [ecx], xmm0
.text:00430291 movntps xmmword ptr [ecx+10h], xmm0
.text:00430295 movntps xmmword ptr [ecx+20h], xmm0
.text:00430299 movntps xmmword ptr [ecx+30h], xmm0
.text:0043029D add ecx, 40h
.text:004302A0 dec edx
.text:004302A1 jnz short loc_43028E
.text:004302A3 sfence
.text:004302A6 xchg edx, [esp+var_4]
.text:004302AA retn
.text:004302AA __fastcall KiXMMIZeroPagesNoSave(x, x) endp
+6
UFO just landed and posted this here
еще раз.
речь идёт о производительности обнуления памяти.
программа работает месяц и за это время очистила пару гигов памяти, нужно ли оптимизировать, раз очищаются такие здоровые объемы памяти? Или это тоже считается «узким местом»? По-моему ответ очевиден, и не стоит передергивать мои слова :)
А на первый вопрос ответ можно глянуть в открытых сорцах ядра винды:
речь идёт о производительности обнуления памяти.
программа работает месяц и за это время очистила пару гигов памяти, нужно ли оптимизировать, раз очищаются такие здоровые объемы памяти? Или это тоже считается «узким местом»? По-моему ответ очевиден, и не стоит передергивать мои слова :)
А на первый вопрос ответ можно глянуть в открытых сорцах ядра винды:
//
// The following code sets the current thread's base priority to zero
// and then sets its current priority to zero. This ensures that the
// thread always runs at a priority of zero.
//
KeSetPriorityZeroPageThread (0);
+2
UFO just landed and posted this here
> Я вот на это ответил
а вторую часть не такого уж длинного комментария не заметили, или это так и надо выдирать фразы из контекста? :)
> Покрутите немного колесиком мыши…
опять же, я не имею в виду конкретное приложение (у вас — виндовый MM), а отвечаю абстрактно на простой вопрос («Если обнуляет медленно и не постоянно, то не считается?»)
а вторую часть не такого уж длинного комментария не заметили, или это так и надо выдирать фразы из контекста? :)
> Покрутите немного колесиком мыши…
опять же, я не имею в виду конкретное приложение (у вас — виндовый MM), а отвечаю абстрактно на простой вопрос («Если обнуляет медленно и не постоянно, то не считается?»)
0
UFO just landed and posted this here
Допустим это приложение очень быстро чем нибудь управляет, в этом случае нужно оптимизировать обнуление этого несчастного килобайта, будь он неладен.
Если этот несчастный нулевой килобайт — итоговая информация, то оптимизировать надо протокол, а не код и не алгоритм :) Если же поверх этого нулевого килобайта перед выдачей пишется что-то осмысленное, то время, потраченное на вычисления будет несоизмеримо больше времени обнуления.
Если этот несчастный нулевой килобайт — итоговая информация, то оптимизировать надо протокол, а не код и не алгоритм :) Если же поверх этого нулевого килобайта перед выдачей пишется что-то осмысленное, то время, потраченное на вычисления будет несоизмеримо больше времени обнуления.
0
UFO just landed and posted this here
Множество ситуаций, где обнуление памяти — вполне осмысленная операция и множество ситуаций, где скорость этого обнуления критична по времени, не пересекаются.
0
UFO just landed and posted this here
Ну тогда приведите пример, где обнуление памяти является вполне осмысленной операцией и одновременно с этим заметно влияет на общую скорость.
0
UFO just landed and posted this here
И это обнуление внесёт заметный вклад в общее время операции?
0
UFO just landed and posted this here
Обнулять массивы можно. Речь о том, что это обнуление либо изначально не является критичным по времени, либо его можно сделать таковым, не прибегая к оптимизации самой реализации обнуления.
0
UFO just landed and posted this here
Плохо — не должно. Хорошо — тоже :) Потому что с практической точки зрения оно фиолетово даже если бы компилятор не сводил всё к memset'у. И дело не в быстрых процессорах, а в соотношении количества обнулений с количеством и стоимостью вычислений поверх обнулённого. Оптимизировать обнуление имеет смысл только с эстетической точки зрения.
0
Есть мнение что всё что можно оптимизировать можно оптимизировать.
0
Да пусть сколько влезет обнуляет, хоть петабайты. Его вообще можно убрать и ничего не поломается ;) разве что VirtualAlloc'и чуть дольше будут отрабатывать, что опять же является критичным местом только в очень неадекватном коде.
0
UFO just landed and posted this here
Вообще-то VirtualAlloc работает с виртуальными страницами (если только не указан MEM_PHYSICAL, что есть экзотика). А хотел я сказать то, что при MEM_COMMIT он by design возвращает обнулённый кусок памяти. Нафига — по идее ради того чтобы не нарваться на ошмётки данных/паролей из чужого отработавшего процесса. Обсуждаемый idle thread нужен только для того чтобы VirtaulAlloc в большинстве случаев получал уже обнулённый кусок, когда выделяемая страница попадает на физическую память.
0
UFO just landed and posted this here
Физические страницы, принадлежавшие ранее одному приложению, не могут быть прочитаны другим приложением, пока они не попадут в его адресное пространство. А попасть они туда могут только через VirtualAlloc.
Таким образом, если обсуждаемого idle thread'а не будет или он не получит процессорного времени между смертью первого приложения и запуском второго (точнее между VirtualFree первого и VirtualAlloc второго), обнулением придётся заниматься VirtualAlloc'у.
Обнуляющий поток нужен только для того, чтобы в большинстве случаев VirtualAlloc получал уже обнулённую физическую страницу и не тратил на это обнуление процессорное время выделяющего память приложения.
Таким образом, если обсуждаемого idle thread'а не будет или он не получит процессорного времени между смертью первого приложения и запуском второго (точнее между VirtualFree первого и VirtualAlloc второго), обнулением придётся заниматься VirtualAlloc'у.
Обнуляющий поток нужен только для того, чтобы в большинстве случаев VirtualAlloc получал уже обнулённую физическую страницу и не тратил на это обнуление процессорное время выделяющего память приложения.
0
Тут дело в том, что из-за нулевого приоритета этого обнуляющего потока возможны ситуации, когда VirtualAlloc нарвётся на необнулённую страницу — и её всё равно придётся обнулять, но уже не из этого потока, и из VirtualAlloc'а (его kernel-mode части) — т.е. эта логика в VirtualAlloc'е по-любому есть. Назначение idle thread'а — избавить большинство VirtualAlloc'ов от этого обнуляющего цикла, выполняя обнуление в фоне и помечая физические страницы как обнулённые. Если этот флаг стоит — VirtualAlloc ничего не делает. Если флага нет, обнулением занимается VirtualAlloc.
0
UFO just landed and posted this here
Ок, не только в VirtualAlloc, но и во всех остальных ситуациях, выделяющих физическую страницу под кусок адресного пространества, будет стоять проверка на обнулённость страницы и если флага обнулённости не окажется, будет выполняться обнуление на месте.
Суть моего повествования в том что idle thread может не успеть ничего обнулить — и, таким образом, может быть вообще исключен из системы, не влияя на её работоспособность. При этом приложения будут и дальше получать обнулённые физические страницы в результате VirtualAlloc'ов, page fault'ов и т.п. Нзначение этого idle thread'а — сугубо оптимизирующее, а не краеугольнее.
Суть моего повествования в том что idle thread может не успеть ничего обнулить — и, таким образом, может быть вообще исключен из системы, не влияя на её работоспособность. При этом приложения будут и дальше получать обнулённые физические страницы в результате VirtualAlloc'ов, page fault'ов и т.п. Нзначение этого idle thread'а — сугубо оптимизирующее, а не краеугольнее.
+1
Вася и Петя зателнетелись на один сервер. Вася отредактировал секретный файлик и отлогинился. Хитрый Петя, написал программу, затребовал много памяти, и нашел в памяти файлик отредактированный Васей… Ведь менеджеры памяти не обнуляют свободные страницы, как Вы думаете?
+6
Ну например, многодорожечный аудиомикшер, в программе есть несколько десятков звукогенераторов, которые в реальном времени обрабатывают, каждый, скажем, под 192 КГц / 2 канала = 384000 сэмплов * 4 байта (или даже 8, щас модно на double микширование делать) в секунду, и перед каждым заполнением им нужно занулять свои буферы, потом суммировать туда что-то (алгоритм не всегда позволяет просто перезаписывать буфер). Естественно, это всё равно будет не самое узкое место в программе, но пара % загрузки процессора улетит тупо на нагрев воздуха. И если «преждевременно» не думать о таких мелочах, то потом проект уходит на профилирование и оптимизацию на месяц.
+1
никогда этим не интересовался, но неужели эти 400к сэмплов нужны одновременно?
простой подсчет показывает что только под эти буферы нужно будет 200-300 метров памяти, неужели микшер столько кушает?
простой подсчет показывает что только под эти буферы нужно будет 200-300 метров памяти, неужели микшер столько кушает?
+1
Конечно нет, если микшер не на иммутабельных списках.
Каждый моно канал кушает размер asio буфера, ну или другой подсистемы; от 256 (асио и крутая звуковуха) до пары десятков тысяч (вин мм) отсчетов, даблов в данном случае. Чем меньше, чем лучше — меньше задержка, и тем меньше нужно памяти. Плюс буфер для суммы, который конечно не нужно обнулять — он перезаписывается блин.
Каждый моно канал кушает размер asio буфера, ну или другой подсистемы; от 256 (асио и крутая звуковуха) до пары десятков тысяч (вин мм) отсчетов, даблов в данном случае. Чем меньше, чем лучше — меньше задержка, и тем меньше нужно памяти. Плюс буфер для суммы, который конечно не нужно обнулять — он перезаписывается блин.
0
Интересно. Спасибо.
А попробуйте, пожалуйста, для расширения мистического опыта добавить в ключи компиляции "-march=native -mtune=native" — интересно, появятся ли дополнительные оптимизации вроде использования SSE. Только не забудьте написать, какая у вас машина.
А попробуйте, пожалуйста, для расширения мистического опыта добавить в ключи компиляции "-march=native -mtune=native" — интересно, появятся ли дополнительные оптимизации вроде использования SSE. Только не забудьте написать, какая у вас машина.
+1
Может я чего не понимаю, но не слишком ли брутально не освобождать гиг памяти?
+1
Так программа завершится, и вся выделенная память будет уничтожена (т.к. будет уничтожена куча приложения). К слову — уничтожение кучи происходит быстрее, чем отдельных её составляющих, а потом и самой кучи ;)
+4
точно, моя невнимательность:)
0
Только вот memory leak детекторы будут ругаться все равно. В данном конкреном случае это не так важно, потому что это тест, а вот в реальном приложении из-за таких ошибок можно не заметить реальный лик. К тому же разве имеет особое значение для вашего приложения сколько времени займет его завершение?
0
Это ясно, я и не призывал не удалять память :) А вот насчёт времени завершения — не соглашусь. Тратится процессорное время на то, что в общем-то не нужно. С точки зрения логики — зачем вызывать удаление памяти, если завершается приложение? А потом получаются Оперы и Фаерфоксы, котрые после закрытия окна ещё очищают память в течение значительного времени, тратя ресурсы. Но проблема далеко не тривиальная, поэтому её игнорируют.
0
«К тому же разве имеет особое значение для вашего приложения сколько времени займет его завершение?»
Ещё как имеет значение: система может ждать завершения приложения при загрузке, приложение может быть пере запущено для обновления, и т.д., и т.п.
ps: я не в коей мере не занудствую просто при написании больших программных комплексов (а по хорошему в любой программе) надо учитывать такие моменты.
Ещё как имеет значение: система может ждать завершения приложения при загрузке, приложение может быть пере запущено для обновления, и т.д., и т.п.
ps: я не в коей мере не занудствую просто при написании больших программных комплексов (а по хорошему в любой программе) надо учитывать такие моменты.
0
всё так, но только в куче максимальный размер куска — 512Kb, в x64 — 1 метр, для больших кусков (если размер кучи не лимитирован) используется прямое выделение памяти через VirtualAlloc, и ваше утверждение про скорость уничтожения становится неверным :)
+1
Вы думаете, что вызвать delete/free для каждого объекта, а затем удаление кучи будет не медленнее удаления сразу всей кучи? Да, если объекты большие и их немного, то разница незаметна. А вот если выделено много маленьких объектов, то удаление всей кучи происходит значительно быстрее.
0
В MSVS 2010 все наоборот:
т.е. через memset выполняется 3-я ветка, а четвертая через цикл.
// TEMPLATE FUNCTION fill
template< class _FwdIt, class _Ty >
inline void _Fill(_FwdIt _First, _FwdIt _Last, const _Ty& _Val)
{ // copy _Val through [_First, _Last)
for (; _First != _Last; ++_First)
*_First = _Val;
}
inline void _Fill(char *_First, char *_Last, int _Val)
{ // copy char _Val through [_First, _Last)
_CSTD memset(_First, _Val, _Last - _First);
}
inline void _Fill(signed char *_First, signed char *_Last, int _Val)
{ // copy signed char _Val through [_First, _Last)
_CSTD memset(_First, _Val, _Last - _First);
}
inline void _Fill(unsigned char *_First, unsigned char *_Last, int _Val)
{ // copy unsigned char _Val through [_First, _Last)
_CSTD memset(_First, _Val, _Last - _First);
}
т.е. через memset выполняется 3-я ветка, а четвертая через цикл.
+1
Это еще не все. Померил время выполнения в разных режимах компиляции (SSE on/off и т.п.) и очень удивился, когда время для всех режимов оказалось одинаковым. Изучение сгенерированного кода показало, что во всех случаях вызывается memset. Даже для того случая, когда в debug версии явно виден цикл (первый вариант функции в моем комментарии выше).
0
Или цикл как то странно приводится к memset или таки используется одна из специализаций:
Как видно, первая и третья ветка вообще используют один и тот же код, а четвертая — после странной проверки делает все тот же memset.
; 8 : if (mode == 1)
00031 83 ff 01 cmp edi, 1
00034 75 16 jne SHORT $LN5@main
$LN32@main:
; 9 : std::memset(buf, 0, n * sizeof(*buf));
00036 68 00 00 00 40 push 1073741824 ; 40000000H
; 15 : std::fill(buf, buf + n, '\0');
0003b 6a 00 push 0
0003d 56 push esi
0003e e8 00 00 00 00 call _memset
; 16 : return buf[0];
00043 0f be 06 movsx eax, BYTE PTR [esi]
00046 83 c4 0c add esp, 12 ; 0000000cH
00049 5f pop edi
0004a 5e pop esi
; 17 : }
0004b c3 ret 0
$LN5@main:
; 10 : // else if (mode == 2)
; 11 : // bzero(buf, n * sizeof(*buf));
; 12 : else if (mode == 3)
0004c 83 ff 03 cmp edi, 3
; 13 : std::fill(buf, buf + n, 0);
0004f 74 e5 je SHORT $LN32@main
; 14 : else if (mode == 4)
00051 83 ff 04 cmp edi, 4
00054 75 18 jne SHORT $LN26@main
; 15 : std::fill(buf, buf + n, '\0');
00056 8d 86 00 00 00
40 lea eax, DWORD PTR [esi+1073741824]
0005c 3b f0 cmp esi, eax
0005e 74 0e je SHORT $LN26@main
00060 2b c6 sub eax, esi
00062 50 push eax
00063 6a 00 push 0
00065 56 push esi
00066 e8 00 00 00 00 call _memset
0006b 83 c4 0c add esp, 12 ; 0000000cH
$LN26@main:
; 16 : return buf[0];
0006e 0f be 06 movsx eax, BYTE PTR [esi]
00071 5f pop edi
00072 5e pop esi
Как видно, первая и третья ветка вообще используют один и тот же код, а четвертая — после странной проверки делает все тот же memset.
0
Странная проверка делается чтобы не вызывать memset, если n == 0 (хотя, в данном случае ее можно вообще выкинуть, почему компилятор до этого не додумался непонятно). А цикл да, мистическим образом превращается в memset, более того, следующий код тоже превратится в memset (!):
for( auto p = buf; p != buf + n; ++p )
*p = '\0';
00DA107D lea eax,[esi+40000000h]
00DA1083 cmp esi,eax
00DA1085 je f+95h (0DA1095h)
00DA1087 sub eax,esi
00DA1089 push eax
00DA108A push 0
00DA108C push esi
00DA108D call memset (0DA1D64h)
00DA1092 add esp,0Ch
+1
Выходит в VS2010 оптимизатор более продвинутый.
+2
Во первых у вас погрешность измерения сопоставима с результатами измерения. Используйте rtdsc.
Во вторых мемсетить лучше double* через SSE и мимо кэша (movntpd).
В третьих поиграйтесь лучше с memcpy() — это более полезно…
Во вторых мемсетить лучше double* через SSE и мимо кэша (movntpd).
В третьих поиграйтесь лучше с memcpy() — это более полезно…
+1
Поговорку про преждевременную оптимизацию не слышали?
-1
Конечно нет, если микшер не на иммутабельных списках.
Каждый моно канал кушает размер asio буфера, ну или другой подсистемы; от 256 (асио и крутая звуковуха) до пары десятков тысяч (вин мм) отсчетов, даблов в данном случае. Чем меньше, чем лучше — меньше задержка, и тем меньше нужно памяти. Плюс буфер для суммы, который конечно не нужно обнулять — он перезаписывается блин.
Каждый моно канал кушает размер asio буфера, ну или другой подсистемы; от 256 (асио и крутая звуковуха) до пары десятков тысяч (вин мм) отсчетов, даблов в данном случае. Чем меньше, чем лучше — меньше задержка, и тем меньше нужно памяти. Плюс буфер для суммы, который конечно не нужно обнулять — он перезаписывается блин.
0
Sign up to leave a comment.
Кто быстрее: memset, bzero или std::fill