Программирование на Visual C++. Архив рассылки
Шрифт:
Сильно упрощая и обобщая результаты, можно сказать, что общие потери на переключение контекста процессора не будут превышать шестидесяти процентов на разумном количестве потоков (до 8192). Но даже такой упрощенный вывод необходимо уточнить:
1. Тестовая программа создает стрессовую ситуацию – каждый "живой объект" стремится подмять под себя все свободное процессорное время, но мы заставляем его быть менее прожорливым, вызывая принудительное переключение контекста вызовом Sleep(0). Реальные "живые объекты" требуют меньше процессорного времени, что выражается в вызове функции Sleep с параметром, отличным от нуля. Таким образом в реальной программе переключения контекста будут происходить реже, чем в нашем тесте, и, следовательно, потери времени будут меньше.
2. Наши
Для более наглядного представления данных упростим вышеприведенную таблицу, объединив данные для двух- и однопроцессорных систем и отбросив результаты, полученные под Windows 98SE:
Кол-во потоков | Двухпроцессорная система (мс) | Однопроцессорная система | Прирост производительности |
---|---|---|---|
8192 | 8367 | 16118 | 93% |
4096 | 8414 | 15067 | 79% |
2048 | 8070 | 14867 | 84% |
1024 | 7820 | 14671 | 88% |
512 | 7578 | 14421 | 90% |
256 | 7414 | 13851 | 87% |
128 | 7305 | 13780 | 89% |
64 | 6640 | 12133 | 83% |
32 | 6282 | 10771 | 71% |
16 | 5961 | 10670 | 79% |
8 | 5930 | 10660 | 80% |
4 | 5937 | 10645 | 79% |
2 | 5399 | 10581 | 96% |
1 | 10633 | 10455 | – 2% |
То есть прирост производительности всегда меньше, чем в два раза, независимо от количества потоков. В среднем прирост производительности составляет порядка 85 процентов. Возможно (и даже наверняка) эта цифра будет отличаться для других процессоров, в особенности для линейки Intel Xeon, которая славится улучшенной поддержкой многопоточности, а так же для систем с количеством процессоров больше двух.
Так как мы говорим о Win32, выбор операционных систем не очень велик – линейки Windows 9x/ME и Windows NT/2000/XP. Исходя из имеющихся данных, для Windows NT/2000/XP принципиальной
Результаты для Windows 98 SE говорят сами за себя. Ввиду того, что принципиальных изменений в ядро Windows 95 до сих пор внесено не было, можно смело утверждать, что эти результаты показательны для любой версии Windows 9x/ME.
Разумно предположить, что количество потоков в процессе ограничено ресурсами самого процесса, а так же ресурсами ядра операционной системы.
ПРИМЕЧАНИЕ
Все сказанное ниже справедливо для линейки Windows NT/2000/XP.
Один из основных ресурсов ядра операционной системы, потребляемый при создании потока, это невыгружаемая памяти (non-paged memory) ядра. Создание одного потока требует около 12 килобайт невыгружаемой памяти. Ограничения на размер пула невыгружаемой памяти устанавливается в следующем ключе системного реестра:
параметрами NonPagedPoolQuota и NonPagedPoolSize. Их значение по умолчанию равно нулю, что отдает управление этими значениями в руки операционной системы.
То есть при современных объемах памяти крайне сложно выбрать все ресурсы ядра операционной системы, создавая большое количество потоков. Остаются только ресурсы процесса, о чем мы и поговорим подробнее.
Как известно, каждому процессу выделяется адресное пространство в четыре гигабайта, но под свои нужды процесс может употребить только первые два гигабайта. Собственно из этих двух гигабайт и выделяется память под стек для вновь создаваемого потока. Размер стека определяется двумя факторами – параметром /STACK линковщика и параметром dwStackSize функции CreateThread.
Размер стека, заданный параметром dwStackSize, не может быть меньше, чем указано в параметре /STACK линковщика и по умолчанию равен ему. Размер стека, используемый линковщиком по умолчанию равен одному мегабайту. Таким образом максимальное количество потоков, которые можно создать при всех параметрах заданных по умолчанию, равняется примерно 2035. По достижении этого предела функция CreateThread начинает возвращать ошибку ERROR_NOT_ENOUGH_MEMORY, что является истинной правдой – если умножить количество потоков на размер стека по умолчанию, то как раз получается примерно два гигабайта – размер адресного пространства отданный процессу на карманные расходы.
Обойти это ограничение можно указав меньший размер стека параметром /STACK линковщика или в Project Settings (Link/Output/Stack Allocations/Reserve) в Microsoft Visual C++. Размер стека указывается в байтах. Меняя это значение надо быть осторожным ввиду того, что стек используется не только для хранения адресов возврата функций и передачи параметров, но и для хранения локальных переменных. Однако это тема отдельного разговора.
"Живые объекты" предоставляют очень интересные возможности для построения сложных систем. И проведенные тесты дают нам возможность трезво и со значительной степенью точности оценить влияние этой технологии на производительность конечной программы. Потому что лично меня, как программиста, очень нервирует манипулирование категориями "быстро/медленно" или "будет тормозить/не будет тормозить" ;)
Отдельное спасибо хочется сказать Дэну Парновскому, который сделал ряд ценных замечаний в процессе разработки тестовой программы, без которых результаты измерений были бы некорректны.
Так же хочу поблагодарить Константина Князева, чьи комментарии помогли более четко сформулировать некоторые ключевые моменты заметки.
ВОПРОС-ОТВЕТ
Как предоставить пользователю выбор источника данных для создания ADO Connection?
Автор: Марк Балонкин