Поделиться через


Покрытие синхронизации

Покрытие кода для параллелизма

Dern ресурсу и Tan Петр Roy

Загрузить образец кода

Мы очень в другой crossroads в отрасли как более laden transistor процессоров требование многопоточный код реализуйте их полный потенциал. Хотя машин с рабочих станций netbooks sport меньше двухъядерными процессорами изнутри, throngs голодные транзисторов сидеть простоя--crying devour многопоточных приложений. Чтобы устранить oncoming звукозаписи параллельных приложений, компаний Intel и Microsoft racing на рынок со профилировщики, структурой, отладчики и библиотеками. Как proliferate многопоточных приложений, поэтому слишком обсуждения взаимоблокировок, динамических блокировок и состояния гонки данных станет чаще общих через отрасль программного обеспечения. Специалистов в области разработки программного обеспечения необходимо адаптировать новых средств, методов и метрик, могут работать с многопоточных программ.

Код приложения и блокировки

Метрики покрытия традиционного кода, такие как инструкции, блок и ветви, не представленные параллелизма вопросы пригодность адрес теста. В качестве примера рассмотрим инструкцию покрытия. Хотя неидеальная метрики популярен покрытия инструкции и полезно до некоторой степени узнать как тщательно протестированы коде. Как обновитель инструкция покрытия измеряет сколько операторов кода приложения были выполнены. Это простой метрику, используется много тестирование средств и многих разработчиков программного обеспечения включают покрытия высокой оператор как панель качество кода. К сожалению покрытие инструкции не дает любой видимость для объем параллелизма в коде тестируется.

Например, рис. 1 приведен простой реализации типа очереди поточно ориентированными, написанные на языке C# (везде в этой статье мы использовать C# и .NET для нашего примера, но идея покрытия синхронизации применим также машинного кода).

В нашем примере имеет только два метода: Помещение Dequeue и переносится в очередь .NET < T >по вокруг каждого помещение и извлечения операции блокировки. Как мы может проверить эта реализация очереди? В следующем примере кода показан простой модульного теста для нашего очереди поточно:

void SimpleQueueTest() {
ThreadSafeQueue<int> q = new ThreadSafeQueue<int>();
q.Enqueue(10);
Assert.AreEqual(10, q.Dequeue());
}

Заметьте, что этот простой тест дает покрытия инструкции 100 %, поскольку это один тест выполнят каждой инструкции в нашей реализации очереди.

Но подождите, мы возникают--предполагается, что очередь быть поточно ориентированными, но до сих мы проверены с помощью только один поток! Мы не тестирование критических поведение которых была причина, почему мы в первую очередь реализован этот тип ThreadSafeQueue. Инструкция покрытия говорит нам мы имеют покрытие 100 % при мы один поток для типа предназначен для осуществляться одновременно. Очевидно, инструкция покрытия является несоответствие требованиям сообщить нам объем характеристики параллелизма проверены кода. Что мы отсутствуют?

Рисунок 1 реализации C# поточно ориентированных очереди

public class ThreadSafeQueue<T> {
object m_lock = new object();
Queue<T> m_queue = new Queue<T>();
public void Enqueue(T value) {
lock (m_lock) {
m_queue.Enqueue(value);
}
}
public T Dequeue() {
lock (m_lock) {
return m_queue.Dequeue();
}
}
}

Что мы отсутствуют является ветвью, скрытые внутри оператора lock. Представьте, что мы заменить блокировки пользовательского блокировку примитива, например упрощенная реализация метода счетчик блокировок, следующим:

void Acquire() {
while (!TryAcquire ()) {
// idle for a bit
Spin();
}
}

TryAcquire вызывает метод ENTER; если не удается получить блокировку (например, возвращающий значение false TryAcquire), вращается немного и повторяет попытку. Если мы сочетается этот пользовательский код блокировки с наш метод помещение бы, влияние инструкции покрытия? Следующий код демонстрирует, как мы переписать помещение с пользовательской блокировки:

public void Enqueue(T value) {
while (!TryAcquire ()) {
//idle for a bit
Spin();
}
m_queue.Enqueue(value);
Release();
}

Теперь если выполняется наших тестовых мы внезапно увидеть отсутствует инструкция покрытия. Любой один тест цепочки пропустите инструкцию "Счетчик", поскольку TryAcquire только вернет значение false, если другой поток, удерживающий блокировку в уже. Единственным способом, метод будет счетчик является, если другой поток вошел в критический раздел. Можно только рассмотрены инструкцию счетчика при наличии конфликтов. Это неявное ветви внутри оператора lock является источником наши отверстия покрытия.

Покрытие синхронизации

Поскольку всем Замените операторы блокировки собственные примитивы взаимоисключение don’t ожидать (ни нам advocate, таким образом), нам нужно найти способ предоставления этой скрытой ветви мы Измерьте количество конфликтов, произошедших во время тестирования. Исследователи на IBM прилагается модель покрытия кода вызывается выполнения синхронизации, можно выполнить только это.

Читать документ, указанный в разделе Дополнительная информация ниже, но основная идея является простой. Во-первых мы занять примитив синхронизации – например типа .NET монитора (который используется ключевое слово lock в C# и ключевое слово SyncLock в Visual Basic). Затем в коде, где выполняется блокировка полученные записи ли он либо заблокирован (в случае использования, скажем, Monitor.Enter) и в противном случае — каждой точке смог получить блокировку (например с помощью Monitor.TryEnter). Либо результат означает, что произошла конкуренции. Таким образом сказать, что нас покрытия через блокировку, это не достаточно для выполнения инструкции lock;Мы также должны выполнить инструкция lock во время другой поток удерживает блокировку.

Хотя эта методика применяется к машинных и управляемых приложений, оставшейся части статьи будут рассмотрены наши управляемого решение – средство покрытия синхронизации прототип для .NET вызывается обложки синхронизации.

Давайте Отвлекитесь для уточнения понятиями, используемыми наши средством покрытия. Точка синхронизации — это узел конкретных лексической вызова, который вызывает метод синхронизации. Перевод быстрого обратно в очередь поточно-наш пример (рис. 1), с нашей шляпа компилятора C#, мы видим двух таких местах, скрыты в инструкции lock в помещение и Dequeue. На одно выполнение мы заинтересованы в два покрытия: Выполнение инструкции, где возникают любые конфликты; методи конфликты приложения, где была принудительно метод для блокирования или подождите, пока другой поток для его освобождения.
В оставшейся части этой статьи мы будет уделено специально System.Threading.Monitor конь работы стабильной синхронизации .NET. Однако полезно Обратите внимание, этот подход работает одинаково хорошо с других примитивов как System.Threading.Interlocked.CompareExchange.

Круглый обработки с IL

Наша цель для этого инструмента покрытия синхронизации — прозрачно инструментировать существующих сборок .NET, можно перехватывать каждый вызов Monitor.ENTER в этой сборке и определить, синхронизировать ли точка обнаружил конкуренции. Как и большинство решений покрытия кода в настоящее время наш средство применяется приемы, перепишите IL (.NET промежуточный язык) для создания инструментированного версий сборок целевой. Хотя мы тратить большинство наших времени в C# по работе с IL, теоретически, это средство будет работать для всех языков .NET, которые компилируются IL.

Есть несколько возможных технологий возможность перезаписи кода, включая вновь выпущенные общей инфраструктуры компилятора из Microsoft Research (MSR), для простоты мы выбрали к небезопасной надежных компилятором IL списком декомпилятор ILDASM и ILASM. Работа с IL в текстовых файлах доказало быть огромным отдушина во время обнаружения, проектирования и разработки этапы проекта, включение нам для просмотра вариантов разработки в "зарплаты для воспроизведения"подход с нашего времени инвестиции. Мы доказало жизнеспособности и практичность наше решение на сначала через полностью вручную процесс состоит из пакетных файлов и Блокнот. Хотя это не может быть наилучшим средством промышленного качества, этот подход рекомендуется для подобных прототипа, переписывание приложения.

С этой общей решение завершения давайте краткий обзор быстрого цепочки наши средства как показано на рис. 2.

С помощью ILDASM, мы технологию целевой сборки в текстовый файл с помощью инструкций IL. Затем мы загрузить исходный код в память за фасадный, объектно ориентированного. Мы затем применить требуемые изменения и injections кода кода, создающие измененного исходного обратно на диск. — Только простой вопрос повторной компиляции охватываемые сборки с помощью ILASM и проверка с команда PeVerify для перехвата любого глупо недопустимые преобразования мы могут применены. После у нас есть наших сборок покрытия, мы выполнять наши тестовый проход в обычном режиме и собрать файл покрытия среды выполнения для последующего анализа.

Наш набор средств состоит из двух основных частей: Обложки синхронизации, который автоматизирует процесс инструментирования и предоставляет IL круглые платформы поездки, которого мы создаем наш инструментарий;и просмотра обложки, приложение Windows Presentation Foundation (WPF), предоставляющее знакомый и понятный способ просмотра файлов покрытия.

Конфликт записи

Теперь вы должны вопрос как можно наблюдать и записи, когда точку синхронизации возникают конфликты. Просмотрев "скрытые ветвь"мысленным модели как может быть реализован Monitor.Enter предоставляет нам имела, если мы применить его буквально почти в нашей преобразования. Мы, содержатся в предыдущем упрощенное реализации счетчик блокировки кода метод сбора перехватывает это точные данные в возвращаемом значении. Все мы нужно сделать – это его видеть.

Как мы хотели переписать IL другими пользователями, вставка произвольный ветвей обещает откройте в окне есть Pandora мы бы вместо избежать проблем. Отзыв вторая проблема записи и связывание данных точка синхронизации предлагает мы включить инструментов наши программистов и достижения некоторых косвенного обращения.

Мы решения обоих проблем путем введения класс-оболочка--фасадный покрытия поддержкой синхронизации через монитор, методы которого принимают аргумент syncId. Это syncId является созданный уникальное целое число, однозначно идентифицирует точку синхронизации--, мы дать каждой точки синхронизации уникальный идентификатор и мы помеченным точку синхронизации мы передачи, код наш класс-оболочка. Наши реализации покрытия начинается с вызывающего Monitor.TryEnter записи результатов и затем при необходимости делегирование исходного Monitor.Enter блокировки. Точка синхронизации состояния управляются классом простая база данных в памяти, мы вызываем CoverageTracker. Поместить все части вместе нашей версии покрытия Monitor.Enter выглядит следующим образом, как показано на рис. 3.

Рис. 3 Версия покрытия Monitor.Enter

namespace System.Threading.SyncCover {
public static class CoverMonitor {
public static void Enter(object obj, int syncId) {
if (Monitor.TryEnter(obj)) {
coverageTracker.MarkPoint(syncId, false); // No Contention.
} else {
Monitor.Enter(obj);
coverageTracker.MarkPoint(syncId, true); // Contention
}
}
}
}

Примечание в коде на рис. 3 предназначено для поддержки версии .NET 3.5 Monitor.ENTER. Предстоящие тип монитора 4 .NET включает изменения, устойчивых к асинхронным исключениям--увидеть blogs.msdn.com/ericlippert/archive/2009/03/06/locks-and-exceptions-do-not-mix.aspx. Добавление поддержки 4 .NET перегрузки является просто перегрузка оболочки в аналогичные fashion.Once у нас есть наш метод в наличии, довольно прямой вперед становится процесса инструментирования. Давайте взглянем на IL, созданные компилятором .NET 3.5 C# для инструкции lock следующим:

IL_0001: ldfld object class ThreadSafeQueue'1<!T>::m_lock
IL_0002: call void [mscorlib]System.Threading.Monitor::Enter(object)

Нужно просто искать вызовы Monitor.Enter и заменить их вызовы наши CoverMonitor.Enter при не forgetting для вставки дополнительных syncId аргумент до вызова метода. Следующий код иллюстрирует это преобразование:

IL_0001: ldfld object class ThreadSafeQueue'1<!T>::m_lock
I_ADD01: ldc.i4 1 // sync id
IL_0002: call void System.Threading.Coverage.Monitor::Enter(object, int32)

Как окончательный часть этого процесса мы должны Обсудите немного отчетности и какой тип сведений, полезна. Итак мы знаем о синхронизации точки, которые идентифицируются syncId вставляется непосредственно в исходный код целевой. Было бы гораздо полезнее, если нам удалось получить исходных файлов и номера строк каждой точки синхронизации. Мы обнаружили, что ILDASM с параметром /LINENUM сведения нам нужно путем извлечения исходного расположения файлов и номера строки из базы данных программы (PDB).

Когда мы сталкиваемся новая точка синхронизации во время процесса инструментирования мы создания следующего syncId и этот контекстных сведений карты. Как файл в конце инструментирование затем выдается данное сопоставление показано в следующем коде:

T|ThreadSafeQueue.exe
SP|
0|D:\ThreadSafeQueue\ThreadSafeQueue.cs|9|ThreadSafeQueue`1<T>|Enqueue
1|D:\ThreadSafeQueue\ThreadSafeQueue.cs|15|ThreadSafeQueue`1<T>|Dequeue

Рис. 4 Пример приложения выполнить

~|B3|~
A|ThreadSafeQueue.exe
C|ThreadSafeQueue.exe
D|20090610'091754
T|demo
M|DWEEZIL
O|Microsoft Windows NT 6.1.7100.0
P|4
SP|
0|1|1
1|1|0
~|E|~

Показать Me данные!

Пришло время показа. После у нас есть наш охватываемые двоичных файлов очень простой вопрос выполнение программы столько раз, сколько нужно. Это создает файл среды выполнения, содержащим показатели покрытия, собранные во время выполнения, как показано на рис. 4. Вдохновения ведения от других средств покрытия на рынке, мы предоставляем простого просмотра основе WPF для визуализации результатов покрытия.

Но давайте шаг для некоторое время. Мы знаем, что наши однопоточной тестовый случай даст нам нуля процентов синхронизации покрытия. Как мы устранить теста, мы может вызвать конфликты? На рис. 5 показано немного улучшенные тест, должны вызывать конфликты в методе помещение. После выполнения теста, можно увидеть результаты, как показано на рис. 6.

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

Рис. 5 Тест, вызывает конфликт на помещение

void ThreadedQueueTest()
{
ThreadSafeQueue<int> q = new ThreadSafeQueue<int>();
Thread t1 = new Thread(
() =>
{
for (int i = 0; i < 10000; i++)
{
q.Enqueue(i);
}
});
Thread t2 = new Thread(
() =>
{
for (int i = 0; i < 10000; i++)
{
q.Enqueue(i);
}
});
t1.Start();
t2.Start();
t1.Join();
t2.Join();
Assert.AreEqual(0, q.Dequeue());
}

С помощью выполнения синхронизации

Так при должны мы использовать покрытия синхронизации? Как и любой метрики покрытия кода покрытия синхронизации пытается измерить, насколько протестированы коде. Любого рода блокировки в приложении означает, что код был предназначен для осуществляться одновременно и серьезно следует хотите ли набор тестов фактически выполнят те блокировки. Вашей группы должны оценивается покрытия синхронизации 100 процентов, особенно если приложение ожидает параллельного выполнения в большой.

Попытка упражнения мы preach, мы использовали это средство покрытия синхронизации в ходе выполнения тестирования параллельных расширений для .NET Framework. Помогли нам найти тестирования отверстия и ошибки во время этого цикла разработки, и мы ожидаем продолжить использование будущее метрики. Два сценария, где покрытия синхронизации помогли нам особенно интересны:

Многопоточные тесты не одновременных

Покрытие синхронизации обнаружил, что некоторые тесты, которые мы думали, были запущены одновременно фактически не были. На рис. 7 — это простой многопоточных тест, аналогичны рис. 5, но на этот раз каждый поток enqueues только 10 элементов в очередь.

Рис. 7 A одновременных тестов, может не быть одновременных после всех

void ThreadedQueueTest() {
ThreadSafeQueue<int> q = new ThreadSafeQueue<int>();
Thread t1 = new Thread(
() => {
for (int i = 0; i < 10; i++) {
q.Enqueue(10);
}
});
Thread t2 = new Thread(
() => {
for (int i = 0; i < 10; i++) {
q.Enqueue(12);
}
});
t1.Start();
t2.Start();
t1.Join();
t2.Join();
}

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

Ненужных блокировок

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

lock(a) {
lock(b) {
//... stuff ...
}
}

Удивительно покрытие синхронизации фактически помогли нам найти ненужных блокировок подобные. Оказывается, что иногда, защищенном блокировку ресурса может больше не нужно быть. Например поток, который пытаются через ресурса не может быть требуется больше и удаляется код, но блокировку оставшиеся поток не был удален. При безвредным в поведении смысле дополнительные блокировки он по-прежнему может повредить производительности. (Обратите внимание, хотя это просто, поскольку блокировка никогда не contended для не значит это не нужно;такие сведения просто предоставляет начальную точку исследование, чтобы определить ли он действительно необходимы.)

Ограничения и будущей работы

Как и любой метрики покрытия кода покрытия синхронизации не идеально--имеет его ограничения. Главный ограничение, общих покрытия синхронизации с других показателей покрытия, его нельзя измерить что не существует. Покрытия синхронизации не может сообщить вам, необходимо заблокировать через некоторые ресурсы только, ресурсы, уже заблокировал был contended по. Таким образом, даже покрытия 100 процентов синхронизации не означает что тестирования усилий выполняется, только добиться некоторого уровня thoroughness в тестировании.

Другой проблемой является что инструментирование кода для покрытия синхронизации может изменить планировании потоков в тестах, например, может получить покрытия в инструментированном тестового запуска, но не в uninstrumented выполнения (хотя в наш опыт мы нашел, что эта проблема обычно не). Опять же средство вроде CHESS может помочь. Наши средства прототипа позволяет нам измерения конкуренции монитор и Interlocked операциями. В будущем мы планируем Добавление функций позволит нам измерения другие примитивы синхронизации, такие как семафор и ManualResetEvent. Мы считаем, метрики покрытия кода, вроде покрытия синхронизации может быть полезно и широко для параллельных приложений, как инструкция покрытия для однопоточного приложения.

Измерения с обязательными

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

Дополнительные сведения

  • Параллельных вычислений в корпорации Майкрософт:
    MSDN.Microsoft.com/Concurrency
  • Документ покрытия синхронизации:
    Bron Y. Y. Nir, а Magid Farchi, д. и принтер, s. 2005. Приложения покрытия синхронизации. В событий десятый симпозиума SIGPLAN ACM принципы и учебные параллельного программирования (Чикаго, Ill., США, 15-17 июня 2005 г.). PPoPP 05 ". ACM, New York, N.Y., 206-212.
  • Средство CHESS:
    research.microsoft.com/en-us/projects/chess/default.aspx
    Musuvathi M. и Qadeer, Южная 2007. Итеративный контекст ограничивающего систематическую тестирования многопоточных программ. В событий 2007 конференции SIGPLAN ACM на программирования языка проектирование и реализация (Сан-Диего, Calif., США, 10-13 июня 2007 г.). PLDI 07 ". ACM, New York, N.Y., 446-455.
  • Общие компилятора инфраструктуры (CCI):
    ccimetadata.CodePlex.com

Коричневая Roy является инженером разработки программного обеспечения в тесте с группой платформы параллельных вычислений в корпорации Майкрософт. Он получил степень его в компьютер наук по Технический Вирджиния в 2007.
Крис Dern является инженером разработки программного обеспечения параллелизма в тесте группе платформы параллельных вычислений в корпорации Майкрософт--где единственное, что лучше, чем написание программного обеспечения параллелизма является тестирования.