Pull to refresh
0
Parallels
Мировой лидер на рынке межплатформенных решений

Одержимость производительностью или опыт профилирования в виртуальной среде

Reading time 9 min
Views 15K
Давайте будем откровенны: неэффективно работающее приложение у большинства разработчиков вызывает дискомфорт. Подчас погоня за производительностью имеет почти спортивную природу, не связанную с прямыми обязанностями. На хабре, как и в жизни многих из нас, найдется немало впечатляющих примеров побед над неэффективностью разного толка. В общем, хороший разработчик не понаслышке знаком с инструментами и техниками профилирования. В этой же статья я хотел бы рассмотреть процесс профилирования в виртуальной среде Parallels Desktop 9 для Mac и VMWare Fusion 5. Под катом ждут тесткейсы, разбор полетов и внутренности гипервизора в самом брутальном виде.

Кому понадобилось запускать профилировщик внутри виртуальной машины?


Это самый первый вопрос, который может возникнуть при прочтении этой статьи. Безусловно, при помощи нехитрых приспособлений можно буханку белого (или черного) хлеба превратить в троллейбус … но кому, в самом деле, может понадобиться запускать профайлинг внутри виртуальной машины? Самое время вспомнить о счастливых обладателях Mac'ов, которым по долгу службы приходится разрабатывать под Windows или Linux. Mac OS, безусловно, в таком случае не станет помехой разработчику: на машине стоит VMWareFusion, а внутри виртуальной машины с Windows – Visual Studio, так что в принципе разработка идет, и проблем нет. Ровно до тех пор, пока не встает вопрос профилировки приложений. Я думаю, вы знакомы с циклом статей от Intel об оптимизации на основе данных, собранных профилировщиком Intel VTune Amplifier (вот одна из них, например), а если нет, то сейчас самое время. Итак вооружившись VTune'ом, установленным внутри виртуальной машины, приступим к профилированию.

Профилирование в виртуальной машине


Итак, в виртуальной машине установлен VTune Amplifier, и решено проанализировать компонент, на предмет оптимальности работы с памятью. Запущен анализ, и вуаля:



Никаких результатов. Причиной этому является то, что профилировщик в своей работе опирается на набор аппаратных счетчиков производительности, также известный, как Performance Monitoring Unit. Каждый из счетчиков может быть настроен на некоторое событие процессора (например, тик процессора или промах кэша), при возникновении которого он увеличивается на единицу. Уникальной особенностью сбора показателей производительности при помощи счетчиков в сравнении с программным инструментированием является полнота информации. Вы находите не только часто исполняемый код, но и код, вызывающий, например, частые промахи кэша. Значение счетчиков можно узнавать непрерывным опросом, а можно настроить генерацию прерывания по превышению счетчиком некоторого порогового значения. Последнее более популярно среди профилировщиков и упрощенная схема работы зачастую выглядит так (на рисунке у нас работа на одноядерном процессоре, и на начальный момент времени исполняется профилировщик):



Получив прерывание на переполнении счетчика, мы будем знать, что с момента установки счетчика произошло 0xFF событий. После чего мы прикапываем Instruction Pointer профилируемого приложения, на котором произошло прерывание, и считаем, что инструкция, на которую указывает этот самый IP, вызвал 0xFF событий. Так мы и собираем статистику.

Обман! От взгляда внимательного читателя не ускользнул тот факт, что события вызваны далеко не только этой единственной инструкцией, а всем блоком, который был на исполнении между двумя прерываниями. Но тем не менее результаты профилирования не теряют свою актуальность по двум основным причинам: количество событий между двумя прерываниями выбирается достаточно малым, чтобы получать адекватную статистику, и результаты всегда можно улучшить, увеличив выборку (дольше профилируем – точнее результаты). Помимо того существует технология Precise Event Based Sampling, позволяющая собирать точную статистику по некоторым событиям.

Внутри же виртуальной машины к счетчикам обратиться не удается, так как, очевидно, доступ к ним должен быть эмулирован (если мы дадим доступ к ним напрямую, то гостевая и хостовая операционные системы буду перетирать данные друг друга, и профайлинга не выйдет). Получив ошибку, описанную выше, делаем вывод: PMU не виртуализирован в VMWare Fusion. На этом бы можно было и закончить статью, но…

Виртуализация PMU в VMWare Fusion 5


… если вы являетесь счастливым обладателем VMWareFusion 5, то в настройках CPU можно выставить галочку “Virtualize CPU Performance Counters”, после чего профилировщик сможет работать внутри виртуальной машины, опираясь на эмулированные счетчики производительности.

Рассмотрим компонент, который будет проанализирован при помощи профилировщика внутри ВМ. Он представляет собой следующее: структура данных типа tSharedData, над которой два потока совместно производят некоторые операции. Для синхронизации использованы два семафора, и потоки по очереди пишут в разделяемые данные. В то же время работает третий поток, который вычитывает значение Time Stamp Counter, и передает его в упомянутую выше структуру.

Код двух рабочих потоков (nThreadNum определяет номер потока):

DWORD WINAPI Thread(LPVOID lpParameter)
{
	tThreadParam* pParams = (tThreadParam*)lpParameter;
	tSharedData* pData = pParams->pSharedData;
	while( pData->nIsWorking )
	{
		if( pParams->nThreadNum == 1 )
		{
			// First Thread
			pData->nLastThread = nCurrent;
			ReleaseSemaphore( pData->hSecondThreadSemaphore,1,NULL );
			WaitForSingleObject( pData->hFirstThreadSemaphore, INFINITE );
		}else{
			//Second thread
			WaitForSingleObject( pData->hSecondThreadSemaphore, INFINITE );
			pData->nLastThread = nCurrent;
			ReleaseSemaphore( pData->hFirstThreadSemaphore,1,NULL );
		}
	}
	return 0;
}


Код третьего потока:

DWORD WINAPI TimeThread(LPVOID lpParameter)
{
	tSharedData* pData = (tSharedData*)lpParameter;
	while( pData->nIsWorkingCacheAligned ) // О да, мы знаем о кешах так что данные этого потока 
	{                                      // разместили на безопасном расстоянии от данных первых двух
		pData->nCurrentTime = __rdtsc();
	}
	return 0;
}


В общем, достаточно синтетический пример, но на нем можно достаточно наглядно увидеть эффекты, возникающие при профилировке в виртуализированной среде. Проводим анализ в течение 20 секунд, и получаем:



Второй столбец в этой таблице – количество тиков процессора, затраченных на исполнение функций из первого столбца. Но нам интереснее следующие два столбца: MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HIT(M) — количество выполненных(RETIRED) операций(OUPS) загрузки(LOAD) данных(MEM), которые оказались (HIT) в LLC (M) модифицированными (Далее под промахами кеша подразумеваются промахи первых двух уровней кешей). И мы видим следующий результат: оба потока примерно одинаково бодро вызывают процесс синхронизации кэшей разных ядер последнего уровня, тем самым вызывая проблемы с производительностью. В принципе, результат ожидаемый: все три потока постоянно обращаются к разделяемой памяти, так что синхронизация кэшей вещь неизбежная, если данные не были грамотно выровнены. Остается только, глубоко вдохнув, приступить к оптимизации. И все-таки мы проводили профилирование в виртуализированной среде, а я вполне могу проверить результаты, исследовав этот код уже на реальной машине, на которой работала VMware Workstation. И результаты…



… отличаются кардинально. Как видим, поток, который читал значения TSC, вызвал мизерное количество загрузок LLC данных (и модифицированных, и не модифицированных), по сравнению с первыми двумя потоками. Так что внутри виртуальной машины мы намерили сбивающие с толку данные. Встает закономерный вопрос:

Что не так с виртуализацией PMU?


Наберите побольше воздуха в грудь: чтобы ответить на этот вопрос нам придется немного окунуться в детали работы виртуальных машин, а конкретно – виртуальных машин, использующих средства аппаратной виртуализации на примере Intel Virtualization Technology или VT-x. Эта технология добавляет еще один режим работы процессора – non-root моде. При работе в этом режиме обращение к оборудованию/системным ресурсам (такими как таблица страничных преобразований или прерываний) вызывает переключение в специально установленный обработчик, исполняющийся в «обычном», root режиме на нулевом кольце. Компонент виртуалки, содержащий этот обработчик, обычно называется монитором виртуальной машины. Фишка в том, что технология VT-x позволяет конфигурировать, будет ли гостевой системе дан прямой доступ к ресурсу или же этот доступ будет эмулирован. Например, пусть гость читает настоящий time-stamp counter, но ни в коем случае не дадим ему копаться в таблице страничных преобразований, а заведем ему его собственную (shadow) – пусть в ней ковыряется.



Итак, виртуализация PMU – это в основном виртуализация внушительного числа счетчиков, которые мы либо эмулируем, либо отдаем в полное пользование гостю (аккуратно сохраняя контекст родной (хостовой) ОС). Если учесть, что первое — это много однотипного кода, помноженное на неэффективность частного переключения root/non-root (каждое обращение к счетчику будет вызывать вмешательство монитора ВМ), то выбор разработчиков виртуальных машин очевиден – пускай гость владеет счетчиками беспрепятственно. Так и сделали сначала в KVM, но профилирование гостевого TCP стека дало непредвиденно скудное в процентном соотношении время работы с оборудованием. Ибо работа шла в мониторе, вне профилируемого гостя, который не имел доступа к железу. И тогда их головы посетила светлая идея: а что если мы будем учитывать еще и события, произошедшие в мониторе? Сказано – сделано. Теперь операции по работе с оборудованием утяжелились.

НО!

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

Подобные вопросы касательно виртуализации PMU терзали и разработчиков VMWare Fusion 5. Они слегка изменили концепцию виртуализации: все события были разделены на «неспекулятивные» (такие как количество исполненных инструкций, количество ветвлений и другие метрики, имеющие «детерминистическую» природу: сколько в коде инструкций или условных переходов — столько их и будет насчитано), и «спекулятивные» (такие, как промахи кешей и неправильно предсказанные ветвления). При этом «спекулятивные» события подсчитывались с учетом нагрузки гипервизора, а «неспекулятивные» — без (более полное описание концепции здесь). Понять такую логику нетрудно: когда в коде функции только 20 инструкций, а замеры показывают, что 220 (из-за того что учтена еще работа монитора виртуальных машин), то, как не сложно догадаться, такие результаты могут сбить с толку. Но при этом, измеряя «спекулятивные» метрики, мы все еще можем встретить результаты замеров, на которые не стоит опираться при оптимизации, как мы увидели выше.

Теперь мы можем ответить на вопрос, откуда такая разница в результатах. Функция по вычитыванию времени вызывает инструкцию RDTSC, которая эмулируется, и имеет непростую логику работы (timekeeping в виртуальных машинах вообще больная тема для разработчиков гипервизоров). Отсюда и масса промахов кэша: на вызове RDTSC происходит куда больше работы, чем ожидается.

Parallels Desktop 9


Безусловно, наше погружение в мир гипервизоров было бы неполным, если за рамками рассмотрения останется виртуальная машина Parallels Desktop 9. В этой виртуальной машине также была добавлена возможность доступа к PMU из гостевой системы, но подход к виртуализации был выбран несколько другой: события подсчитываются только в контексте гостевой системы- все влияние монитора на результаты замеров сведено к минимуму. Попробуем провести профилирование внутри этой виртуальной машины и сравним результаты.



Как видим, результаты сильнее коррелируют с результатами на реальном оборудовании, чем в VMWareFusion. (Поток, вычитывающий время, вызывает мизерное количество промахов кеша). Характер соотношение между количеством событий, произошедших в каждом из потоков, сохраняется. Может смутить нулевое количество пойманных промахов кеша в TimeThread в виртуальной машине, но это означает лишь то, что промахов кэша произошло порядка 2000 – именно таким выставлено пороговое значение для срабатывания счетчика промахов кеша. Но в целом характерные эффекты мы наблюдаем и в дальнейшем можем проводить оптимизацию, опираясь на найденные значения.

Тем не менее остается важный вопрос: так как быть с тем, что часть нагрузки, связанной с виртуализированными операциями, выпадает из статистики? Безусловно, это неприятный эффект, но стоит заметить следующее: 99% времени виртуальная машина работает без вмешательства монитора, то есть потеря части статистики часто для нас будет почти незаметна. Но главное, что такой подход более предсказуем, чем в случае с Fuison: если мы нашли ряд эффектов в нашем коде, то они проявятся и на реальном железе, в отличие от виртуальной машины VMWare, в которой можно обнаружить эффекты, которые не смогут быть воспроизведены на реальном оборудовании.

Резюме


Тот, кто дочитал статью до этого момента, воистину силен духом. За рамками обсуждения остались многие другие эффекты, возникающие при попытке профилирования в виртуальном окружении, но целью этой статьи является описания ключевых различий в профилировании в разных средах, зная о которых можно избежать некоторых проблем с интерпретацией результатов замеров производительности. Подытожим:

— Закон дырявых абстракций все так же актуален, и стоит быть вдвойне сосредоточенным, работая в виртуальном окружении.
— Если профилирование в виртуализированном окружении единственный возможный подход к анализу производительности, то следует быть начеку — операции по работе со временем и оборудованием могут выдавать серьезные артефакты в результатах при замерах влияния аппаратных эффектов.
— В целом же, в виртуальной машине можно собрать показательную статистику, опираясь на которую можно оптимизировать приложения.

Всем спасибо за внимание!
Tags:
Hubs:
+16
Comments 6
Comments Comments 6

Articles

Information

Website
www.parallels.com
Registered
Founded
Employees
201–500 employees
Location
США