This content was uploaded by our users and we assume good faith they have the permission to share this book. If you own the copyright to this book and it is wrongfully on our website, we offer a simple DMCA procedure to remove your content from our site. Start by pressing the button below!
Предисловие 25 января 2009 г. Институту системного программирования РАН исполняется 15 лет. Все эти годы основными задачами Института являлись фундаментальные исследования в области системного программирования, прикладные исследования и разработки в интересах различных областей индустрии и образование. Во всех этих областях удалось добиться значительных успехов. Результаты исследований и разработок освещаются на страницах web-сайта Института, публикуются в регулярных и тематических выпусках Трудов Института системного программирования РАН, ведущем российском журнале «Программирование», авторитетных зарубежных изданиях, докладываются на авторитетных российских и международных конференциях. Проекты, выполняемые специалистами Института, поддерживаются грантами РФФИ, Министерства образования и науки, Президиума РАН и Отделения математики. Прикладные исследования и разработки выполняются на основе контрактов с российскими и зарубежными компаниями. Сотрудники Института активно занимаются преподавательской деятельностью на кафедрах системного программирования факультетов Вычислительной математики и кибернетики МГУ им. М.В. Ломоносова и Управления и прикладной математики Московского физико-технического института. Многие студенты этих кафедр одновременно с базовым обучением активно участвуют в исследовательской работе отделов Института, после получения дипломов поступают в аспирантуру своих университетов и остаются работать в ИСП РАН. В результате в ИСП РАН работает очень много талантливых молодых исследователей и разработчиков, квалификация которых позволяет им активно участвовать в проектах Института, а зачастую и руководить ими. Это является залогом того, что и в будущем ИСП РАН сможет плодотворно решать актуальные и сложные проблемы системного программирования. Настоящий юбилейный сборник Трудов института системного программирования РАН полностью состоит из статей, написанных молодыми сотрудниками ИСП РАН и посвященных результатам выполняемых ими исследовательских проектов. В статье А. Аветисяна, В. Бабковой и А. Монакова «Обеспечение высокопродуктивного программирования для современных параллельных платформ» описываются перспективные направления исследований по высокопродуктивному программированию для параллельных систем с распределенной памятью. Обсуждаются текущие исследования и направления будущих работ, связанных с эффективным программированием многоядерных и гетерогенных систем. 5
Статья А. Белеванцева, Д. Журихина и Д. Мельника «Компиляция программ для современных архитектур» содержит обзор работ по оптимизации программ для современных вычислительных архитектур, проводимых в отделе компиляторных технологий ИСП РАН. Работы включают в себя выявление параллелизма на уровне команд для архитектуры Intel Itanium, исследование и разработку энергосберегающих оптимизаций для архитектуры ARM, а также исследования по динамическим оптимизациям для языков общего назначения, выполняемым на машине пользователя. Большая часть этих работ выполнялось на основе компилятора GCC с открытыми исходными кодами, являющегося стандартным компилятором для Unix-подобных систем. В статье В. Падаряна, А. Гетьмана и М. Соловьева «Программная среда для динамического анализа бинарного кода» рассматривается среда TrEx, позволяющая выполнять динамический анализ защищенного бинарного кода. Преследуемой целью является получение описания интересующего алгоритма. В среде TrEx реализуется оригинальная методика анализа и обеспечивается развитый набор программных средств, объединенных в рамках единого графического интерфейса. Подробно рассматриваются некоторые особенности среды, такие как аритектурнонезависимое API для работы средств анализа, возможности свертки вызовов функций, расширение пользовательского интерфейса скриптовым языком. В статье В. Кулямина «Перспективы интеграции методов верификации программного обеспечения» предлагается подход к построению расширяемой среды верификации программных систем, которая, по мнению автора, поможет решить проблемы практической применимости современных строгих методов верификации к практически значимым программам, сложность которых все время растет. Она же может стать аналогом испытательного стенда для апробации и отладки большого числа новых предлагаемых методов формальных верификации и статического анализа на разнообразном производственном программном обеспечении. В статье В. Мутилина «Метод проверки линеаризуемости многопоточных Java программ» описывается новый метод Sapsan, предназначаемый для функционального тестирования Java-программ с программным интерфейсом (API), процедуры (операции) которого можно вызывать из нескольких потоков одновременно. Метод Sapsan позволяет проверять одно из распространенных требований к таким программам – требование линеаризуемости, заключающееся в том, что параллельное выполнение операций эквивалентно некоторому последовательному выполнению этих же операций, удовлетворяющему спецификации. Статья А. Камкина «Метод формальной спецификации аппаратуры с конвейерной организацией и его приложение к задачам функционального тестирования» посвящена обсуждению метода формальной спецификации аппаратуры с конвейерной организацией, основанного на пред- и постусловиях стадий выполнения операций. Данный метод может быть 6
использован для функционального тестирования моделей аппаратуры, поскольку на основе спецификаций предлагаемого вида можно решать основные задачи тестирования: проверку правильности поведения системы и генерацию тестовой последовательности. Метод был успешно применен для тестирования нескольких модулей промышленного микропроцессора. В результате тестирования были найдены критичные ошибки, не обнаруженные при использовании других подходов. В статье В. Рубанова «Современная инфраструктура для обеспечения совместимости Linux-платформ и приложений» описывается подход к построению инфраструктуры для эффективной разработки и использования спецификаций Linux-платформ. Подобные спецификации описывают программные интерфейсы (API) для обеспечения совместимости между различными реализациями таких платформ и различными приложениями для них. Задача рассматривается в условиях эволюционирующих версий спецификации платформы и наличия множественных платформенных реализаций и приложений, удовлетворяющих той или иной версии спецификации. Предлагаемый подход основан на использовании централизованной базы данных, содержащей структурированную информацию о различных версиях спецификации и различных реализациях платформ и приложений, а также средств автоматической верификации фактического соответствия реализаций платформ и приложений той или иной версии спецификации. Подход иллюстрируется на примере инфраструктуры для поддержки стандарта Linux Standard Base (LSB), основного промышленного стандарта на интерфейсы базовых библиотек операционной системы Linux. В статье М. Гринева и И. Щеклеина «Ориентированные на приложения методы хранения XML-данных» утверждается, что единственно возможным подходом, способным обеспечить высокую эффективность управления XMLданными на основе универсальной модели данных XQury, является выбор способов внутреннего представления и методов обработки данных под потребности конкретного приложения. Достаточной информацией для описания потребностей является схема XML-данных и рабочая нагрузка в виде возможных запросов и операций модификации данных. Предлагается выбирать структуры хранения данных, необходимые для эффективного выполнения запросов и модификаций для данного приложения. Такой подход позволит поддерживать модель данных XQuery на логическом уровне, но избежать излишних накладных расходов на физическом уровне хранения данных. Описываются первые результаты по разработке таких методов хранения и обработки XML-данных. Наконец, в статье М. Гриневой и М. Гринева «Анализ текстовых документов для извлечения тематически сгруппированных ключевых терминов» предлагается новый метод извлечения ключевых терминов из текстовых документов. В качестве важной особенности метода отмечается тот факт, что 7
результатом его метода являются группы ключевых терминов, и термины из каждой группы семантически связаны с одной из основных тем документа. Метод основан на комбинации следующих двух техник: мера семантической близости терминов, посчитанная с использованием Википедии; алгоритм для обнаружения сообществ в сетях. Одним из преимуществ метода является отсутствие необходимости в предварительном обучении, поскольку метод работает с базой знаний Википедии. Экспериментальная оценка метода показала, что метод позволяет извлекать ключевые термины с высокой точностью и полнотой. Академик РАН В.П. Иванников
8
Обеспечение высокопродуктивного программирования для современных параллельных платформ А.И. Аветисян, В.В. Бабкова, А.В. Монаков {arut, barbara, amonakov}@ispras.ru http://www.ispras.ru/groups/ctt/parjava.html Аннотация. В настоящей статье описываются некоторые перспективные направления исследований по высокопродуктивному программированию для параллельных систем с распределенной памятью, проводимые в отделе компиляторных технологий Института системного программирования РАН. Обсуждаются текущие исследования и направления будущих работ, связанных с высокопродуктивным программированием многоядерных и гетерогенных систем.
1. Введение Развитие компьютерных и сетевых технологий привело к тому, что одним из основных свойств современных вычислительных систем является параллелизм на всех уровнях. Происходит широкое внедрение кластерных систем (распределенная память) с тысячами процессоров. Началось широкое производство многоядерных процессоров общего назначения, Современные многоядерные процессоры имеют не более 16 ядер, однако производители уже серьезно говорят о нескольких сотнях и даже тысячах ядер [1]. Кроме того, выпускаются специализированные процессоры, содержащие сотни параллельно работающих ядер на одном чипе (графические акселераторы компаний AMD и nVidia). Высокая производительность, низкое энергопотребление и низкая стоимость специализированных многоядерных процессоров (как правило, это процессоры для компьютерных игр) способствовали стремлению использовать их не только по их прямому назначению. Начались исследования возможностей широкого применения гетерогенных архитектур, состоящих из процессора общего назначения и набора специализированных многоядерных процессоров (акселераторов) для решения вычислительных задач общего назначения. Акселератор имеет доступ как к своей собственной памяти, так и к общей памяти гетерогенной системы. Примерами таких архитектур являются: архитектура IBM Cell, архитектуры, использующие графические акселераторы компаний AMD и nVidia, многоядерный графический ускоритель Larrabee компании Intel. 9
Остро встал вопрос о языках параллельного программирования, которые могли бы обеспечить достаточно высокую производительность труда программистов, разрабатывающих параллельные приложения. Однако языки, разработанные в 90-е годы (HPF [2], UPC [3] и др.) не смогли решить эту проблему [4]. Это привело к тому, что промышленную разработку прикладных параллельных программ, обеспечивающих необходимое качество, приходится вести, на так называемом «ассемблерном» уровне, на последовательных языках программирования (C/C++, Fortran), разработанных в 60-70 гг., с явным использованием обращений к коммуникационной библиотеке MPI (для систем с распределенной памятью), явным указанием прагм OpenMP (для систем с общей памятью), с использованием технологии программирования CUDA [5] (расширение языка C для акселераторов Nvidia), которая точно отражает организацию оборудования, что позволяет создавать эффективные программы, но требует высокого уровня понимания архитектуры акселератора и др. Таким образом, в настоящее время параллельное программирование связано с ручной доводкой программ (распределение данных, шаблоны коммуникаций, либо синхронизации доступа к критическим данным и т.п.). Это связано со значительными затратами ресурсов и требует высокой квалификации прикладных программистов. Цена, которую нужно заплатить, чтобы добиться хорошей производительности и требуемой степени масштабируемости приложений, часто оказывается непомерно высокой. Поэтому целью современных исследований является фундаментальная проблема высокой продуктивности [6] разработки параллельных приложений, когда обеспечивается достаточный уровень производительности при приемлемом уровне затрат на разработку. Это особенно актуально в связи с тем, что параллельное программирование становиться массовым. Исследования ведутся по многим направлениям: изучаются свойства приложений, делаются попытки классификации приложений, в том числе для выявления в них общих ядер; исследуются свойства аппаратуры с целью максимального их использования и развития; ведутся исследования и разработки по целому спектру средств программирования. Одним из направлений исследований является разработка языков нового поколения (X10 [7], Chapel [8], Fortress [9], Cilk [10], Brook+ [11] и др.). Несмотря на то, что эти разработки опираются на опыт предыдущих лет, пока они не привели к успеху, прежде всего, из-за недостаточного уровня современных компиляторных технологий. Реализуются как промышленные, так и исследовательские, системы, поддерживающие доводку программ разрабатываемых на «ассемблерном» уровне. К настоящему времени известно несколько таких систем: отладчики DDT [12], TotalView [13], система TAU [14], разработанная в университете штата Орегон и др. 10
Одним из таких средств является интегрированная среда ParJava [15], разработанная в ИСП РАН, которая предоставляет прикладному программисту набор инструментальных средств, поддерживающих разработку параллельных программ для вычислительных систем с распределенной памятью (высокопроизводительных кластеров) на языке Java, расширенном стандартной библиотекой передачи сообщений MPI. В настоящее время среда Java представляет значительный интерес с точки зрения высокопроизводительных вычислений. Это связано как с положительными свойствами Java как среды разработки прикладных программ (переносимость, простота отладки и др.), так и с тем, что использование инфраструктуры Java существенно упрощает разработку инструментальных средств. Можно упомянуть такие системы как: ProActive Parallel Suite [16] (INRIA), MPJ Express [17] (University of Reading and University of Southampton), Distributed Parallel Programming Environment for Java [18] (IBM) и др. Кроме того, добавлена поддержка Java + MPI в известной среде разработки параллельных программ на языках C/C++ и Fortran 77/90 TAU. В проекте ParJava решались две задачи: обеспечить возможность эффективного выполнения параллельных программ на языке Java с явными обращениями к MPI на высокопроизводительных кластерных системах и разработать технологический процесс реализации параллельных программ, обеспечивающий возможность переноса как можно большей части разработки на инструментальный компьютер. В настоящей статье описываются некоторые перспективные направления исследований по высокопродуктивному программированию для параллельных систем с распределенной памятью, проводимые в отделе компиляторных технологий Института системного программирования РАН. Обсуждаются текущие исследования и направления будущих работ, связанных с высокопродуктивным программированием многоядерных и гетерогенных систем. Статья состоит из 4 разделов. В разделе 2 описывается среда ParJava, модель параллельной Java-программы и возможности ее интерпретации, технологический процесс разработки программ в среде ParJava, механизмы времени выполнения. В разделе 3 приводятся результаты применения среды при разработке программ моделирования интенсивных атмосферных вихрей (ИАВ) и моделирования теплового движения молекул воды и ионов в присутствии фрагмента ДНК. В разделе 4 обсуждаются направления дальнейших исследований.
2. Средства разработки параллельных приложений в среде Java Среда ParJava позволяет выполнять большую часть разработки параллельной Java-программы на инструментальном компьютере. Для этого используется модель параллельной Java-программы [19], интерпретируя которую на 11
инструментальном компьютере можно получить оценки времени выполнения программы на заданном кластере (кластер определяется числом узлов, параметрами платформы, используемой в качестве его узлов, и параметрами его коммуникационной сети), а также оценки других динамических атрибутов программы, построить модели ее профилей и трасс. Полученная информация о динамических свойствах параллельной программы позволяет оценить границы ее области масштабируемости, помогает прикладному программисту вручную оптимизировать программу, проверяя на интерпретаторе, как отразились произведенные изменения на ее масштабируемости. Возможность использования инструментального компьютера для оптимизации и доводки параллельной программы избавляет программиста от большей части отладочных запусков программы на целевой вычислительной системе, сокращая период отладки и доводки программы.
2.1. Модель параллельной интерпретация
Java-программы
и
ее
Модель SPMD-программы представляет собой совокупность моделей всех классов, входящих в состав моделируемой программы. Модель каждого класса – это множество моделей всех методов этого класса; кроме того, в модель класса включается модель еще одного дополнительного метода, описывающего поля класса, его статические переменные, а также инициализацию полей и статических переменных. Модель метода (функции) состоит из списка описаний локальных и глобальных переменных метода и модифицированного абстрактного синтаксического дерева метода: внутренние вершины модели соответствуют операторам языка Java, а листовые – базовым блокам. В качестве базовых блоков рассматриваются не только линейные последовательности вычислений (вычислительные базовые блоки), но также и вызовы библиотечных функций, вызовы пользовательских методов и функций и вызовы коммуникационных функций. Для обеспечения возможности интерпретации модели по частям введено понятие редуцированного блока, который представляет значения динамических атрибутов уже проинтерпретированных частей метода. Каждый MPI-процесс моделируемой программы представляется в ее модели с помощью логического процесса, который определен как последовательность действий (примеры действий: выполнение базового блока, выполнение операции обмена и т.п.). Каждое действие имеет определенную продолжительность. В логическом процессе определено понятие модельных часов. Начальное показание модельных часов каждого логического процесса равно нулю. После интерпретации очередного действия к модельным часам соответствующего логического процесса добавляется значение времени, затраченного на выполнение этого действия (продолжительности). Продолжительность каждого действия, а также значения исследуемых динамических параметров базовых блоков, измеряются заранее на целевой платформе. 12
Для идентификации логических процессов используются номера моделируемых процессов, использованные в моделируемой программе при описании коммуникатора MPI (будем называть их пользовательскими номерами процессов). Как известно, для удобства программирования приложений в стандарте MPI реализована возможность задавать коммуникаторы, позволяющие задавать виртуальные топологии сети процессоров, описывая группы процессов. В среде ParJava коммуникаторы, заданные программистом, отображаются на MPI_COMM_WORLD, получая, тем самым, наряду с пользовательскими номерами внутренние номера. Все инструменты среды ParJava работают с внутренними номерами процессов, но при выдаче сообщений пользователю эти номера переводятся в пользовательские. В дальнейшем, при упоминании номера логического процесса будет подразумеваться его внутренний (системный) номер. Внутренний номер используется для доступа к логическому процессу при моделировании коммуникационных функций. Для сокращения времени интерпретации и обеспечения возможности выполнения интерпретации на инструментальном компьютере в среде ParJava моделируются только потоки управления процессов моделируемой программы и операции обмена данными между процессами. Это допустимо, так как значения времени выполнения и других исследуемых динамических атрибутов базовых блоков определяются на целевой вычислительной системе до начала интерпретации модели. Интерпретация модели лишь распространяет значения указанных динамических атрибутов на остальные узлы модели. Такой подход позволяет исключить из моделей базовых блоков переменные, значения которых не влияют на поток управления моделируемой программы. В результате часть вычислений, в которых определяются и используются указанные переменные, становится «мертвым кодом» и тоже исключается, что ведет к сокращению, как объема обрабатываемых данных, так и общего объема вычислений во время интерпретации. В некоторых базовых блоках описанный процесс может привести к исключению всех вычислений, такие базовые блоки заменяются редуцированными блоками. Внутреннее представление модели SPMD-программы разрабатывалось таким образом, чтобы обеспечить возможность возложить как можно большую часть функций интерпретатора на JavaVM. Такое решение позволило существенно упростить реализацию интерпретатора и обеспечить достаточно высокую скорость интерпретации, однако для его реализации потребовалось внести некоторые структурные изменения в модель параллельной программы. Внутреннее представление модели базового блока B представляет собой пару 〈DescrB, BodyB〉, где DescrB – дескриптор блока B (т.е. семерка 〈id, τ, P, IC, OC, Time, A〉, где id – уникальный идентификатор базового блока, присваиваемый ему при построении модели, τ – тип базового блока, P – ссылка на модель его тела, IC – множество входных управляющих переменных, OC – множество выходных управляющих переменных, Time – время выполнения базового 13
блока, A – ссылка на список остальных его атрибутов), а BodyB – модель тела блока B (список выражений на байт-коде, вычисляемых в блоке B). Внутреннее представление модели метода определяется как тройка 〈дескриптор метода, модель потока управления, модель вычислений〉. Дескриптор метода содержит сигнатуру метода, список генерируемых методом исключений, список дескрипторов формальных параметров и ссылки на модель потока управления и модель вычислений. Модель потока управления – это модифицированное АСД, описанное в [19], в котором базовые блоки представлены своими дескрипторами. Модель вычислений – это преобразованный байт-код интерпретируемой Java-программы: все базовые блоки модели вычислений включены в состав переключателя, значение управляющей переменной которого определяет номер очередного интерпретируемого базового блока. Интерпретация модели состоит в выполнении Java-программы, определяющей модель вычислений на JavaVM: в интерпретаторе модели потока управления определяется очередное значение управляющей переменной переключателя модели вычислений, после чего интерпретируется модель соответствующего базового блока. Интерпретация модели базового блока определяется его типом. Для блоков типа «вычислительный блок» (время выполнения таких базовых блоков определяется заранее на целевой платформе) вносится соответствующее изменение во временной профиль метода, и управление возвращается в модель вычислений. Для блоков типа «вызов пользовательской функции», управление возвращается в модель вычислений, где вызывается модель вычислений этого метода. Во время выполнения вызванной модели вычислений в стек помещается текущее состояние, и подгружаются необходимые структуры данных. После интерпретации метода из стека извлекается состояние на момент вызова пользовательской функции и продолжается выполнение модели вычислений первой функции. Для блоков типа «вызов коммуникационной функции», управление возвращается в модель вычислений, где вызывается модель соответствующей коммуникационной функции, которая помимо выполнения передачи данных между логическими процессами обеспечивает вычисление оценки времени выполнения коммуникации и внесение соответствующих изменений во временной профиль. При интерпретации блоков типа «редуцированный блок» (динамические атрибуты таких блоков уже определены и они не интерпретируются), возврат в модель вычислений не происходит; вносятся изменения во временной профиль, и выполняется поиск следующего базового блока.
2.2. Технологический процесс программ в среде ParJava
разработки
параллельных
В рамках среды ParJava разработан и реализован ряд инструментальных средств, которые интегрированы с открытой средой разработки Java-программ Eclipse [20]. После подключения этих инструментальных средств к Eclipse, 14
получилась единая среда разработки SPMD-программ, включающая как инструменты среды ParJava, так и инструменты среды Eclipse: текстовый редактор, возможность создания и ведения проектов, возможность инкрементальной трансляции исходной программы во внутреннее представление. Интеграция в среду Eclipse осуществлена с помощью механизма «подключаемых модулей». При разработке параллельной программы по последовательной программе сначала оценивается доля последовательных вычислений, что позволяет (с помощью инструмента “Speed-up”) получить оценку максимального потенциально достижимого ускорения в предположении, что все циклы, отмеченные прикладным программистом, могут быть распараллелены. Затем с использованием инструмента “Loop Analyzer” среды ParJava циклы исследуются на возможность их распараллелить. Для распараллеленных циклов с помощью инструмента “DataDistr” подбирается оптимальное распределение данных по узлам вычислительной сети. В частности, для гнезд циклов, в которых все индексные выражения и все границы циклов заданы аффинными формами, инструмент “DataDistr” позволяет с помощью алгоритма [21] найти такое распределение итераций циклов по процессорам, при котором не требуется синхронизаций (обменов данными), если, конечно, требуемое распределение существует (см. ниже пример 1). В этом случае инструмент “DataDistr” выясняет, можно ли так распределить итерации цикла по узлам, чтобы любые два обращения к одному и тому же элементу массива попали на один и тот же процессор. Для этого методом ветвей и сечений находится решение задачи целочисленного линейного программирования, в которую входят ограничения на переменные циклов, связанные с необходимостью оставаться внутри границ циклов, и условия попадания на один и тот же процессор для всех пар обращений к одинаковым элементам массива. Задача решается относительно номеров процессоров, причем для удобства процессоры нумеруются с помощью аффинных форм (т.е. рассматривается многомерный массив процессоров). Если оказывается, что для обеспечения сформулированных условий все данные должны попасть на один процессор, это означает, что цикл не может выполняться параллельно без синхронизации. В последнем случае инструмент “DataDistr” может в диалоговом режиме найти распределение данных по узлам, требующее минимального числа синхронизаций при обменах данными. Для этого к условиям сформулированной задачи линейного программирования добавляются условия на время обращений к одним и тем же элементам массива: например, в случае прямой зависимости, требуется, чтобы обращение по записи выполнялось раньше, чем обращение по чтению. В частности, при решении дополнительных временных ограничений, может оказаться, что они могут быть выполнены, если обрабатываемые в программе массивы будут разбиты на блоки. При этом смежные блоки должны быть распределены по процессорам «с перекрытием», чтобы все необходимые 15
данные были на каждом из процессоров. При этом возникают так называемые теневые грани (т.е. части массива, используемые на данном процессоре, а вычисляемые на соседнем процессоре). Ширина теневых граней определяется алгоритмом решения задачи и определяет фактический объем передаваемых в сети сообщений. Количество теневых граней зависит выбора способа нумерации процессоров: априорно выгоднее всего, чтобы размерность массива процессоров совпадала с размерностью обрабатываемых массивов данных. Однако в некоторых случаях оказывается более выгодным, чтобы размерность массива процессоров была меньше, чем размерность обрабатываемых массивов данных. Пример 1. В качестве примера работы инструмента “DataDistr” рассмотрим цикл: for (i = 1; i <= 100; i++) for(j = 1; j <=100; j++){ X[i,j] = X[i,j] + Y[i – 1,j]; /*s1*/ Y[i,j] = Y[i,j] + X[i,j - 1]; /*s2*/ } Для приведенного примера инструмент “DataDistr” выдаст следующее распределение: X[1,100] = X[1,100] + Y[0,100]; /*s1*/ for (p = -99; p <= 98; p++){ if (p >= 0) Y[p+l,l] = X[p+l,0] + Y[p+l,l]; /*s2*/ for (i = max(l,p+2); i <= min(100,100+p); i++) { X[i,i-p-l] = X[i,i-p-l] + Y[i-l,i-p-l]; /*s1*/ Y[i,i-p] = X[i,i-p-l] + Y[i,i-p]; /*s2*/ } if (p <= -1) X[101+p,100]=X[101+p,100]+Y[101+p-l,100];/*s1*/ } Y[100,l] = X[100,0] + Y[100,l]; /*s2*/ где p – номер вычислителя, а цикл по p определяет распределение данных по вычислителям. Таким образом, исходный цикл расщепился на 200 цепочек вычислений, каждая из которых может выполняться независимо. После того, как данные распределены по вычислителям, прикладному программисту необходимо выбрать такие операции обмена данными и так трансформировать свою программу, чтобы добиться максимально возможного перекрытия вычислений и обменов данными. Среда ParJava позволяет решать этот вопрос в диалоговом режиме, используя интерпретатор параллельных программ (инструмент “Interpreter”). 16
В тривиальных случаях даже использование стандартных коммуникационных функций (MPI_send, MPI_receive) позволяет достичь достаточного уровня масштабируемости. Однако, в большинстве случаев это невозможно, так как приводит к большим накладным расходам на коммуникации, а это в соответствии с законом Амдаля существенно урезает масштабируемость. Достичь перекрытия вычислений и обменов для некоторых классов задач позволяет использование коммуникационных функций MPI_Isend, MPI_Ireceive совместно с функциями MPI_Wait и MPI_Test. Это поясняется примером 2. Пример 2. Как видно из схемы на рис. 1, необходимо добиться, чтобы во время передачи данных сетевым процессором (промежуток между s
(рис. 2а). Если в рассматриваемом «скелете» заменить блокирующие операции Send и Recv на неблокирующие и установить соответствующую операцию Wait после гнезда циклов получится «скелет», представленный на рис. 2б. На рис. 3 приведены графики ускорения «скелетов» программы представленного на рис. 2а и 2б. Как видно, такая замена существенно расширила область масштабируемости программы. При этом окончательная версия «скелета», удовлетворяющая требованиям прикладного программиста, используется для построения трансформированной исходной программы. //sending Send Recv //calculating for (i = beg_i; i < end_i;
s
моментами времени t1 и t 2 ) вычислитель был занят обработкой данных, а не простаивал в ожидании окончания пересылки. i++)
//sending ISend IRecv //calculating for (i = beg_i + 1; i < end_i - 1; i++)
for (j = 0; j < N; j++) B[i][j] = f(A[i][j]);
(а)
for (j = 0; j < N; j++) B[i][j] = f(A[i][j]); //waiting Wait(); //calculating last columns if (myid != 0) for (j = 0; j < N; j++) B[0][j] = f(tempL[j]); if (myid != proc_size - 1) for (j = 0; j < N; j++) B[N - 1][j] = f(tempR[j]); (б)
Рис. 2. Схематичное изображение алгоритмов с блокирующими и неблокирующими пересылками Рис. 1. Схема передачи данных MPI Подбор оптимальных коммуникационных функций требует многочисленных интерпретаций различных версий разрабатываемой программы. Для ускорения этого процесса строится «скелет» программы и все преобразования делаются над ним. После достижения необходимых параметров параллельной программы автоматически генерируется вариант программы в соответствии с полученным коммуникационным шаблоном. Проиллюстрировать важность оптимального выбора операций пересылок можно на следующем «скелете» реальной программы моделирования торнадо 17
К сожалению, в большинстве реальных программ такими простыми преобразованиями не удается достичь необходимого уровня перекрытия, либо такое преобразование невозможно. Использование предлагаемой технологии обеспечивает возможность применения различных преобразований программы, и достигать необходимых параметров программы в приемлемое время. Этот процесс отладки и оптимизации параллельной программы показывает, что задача сама по себе неформализована. На сегодняшний день не может 18
быть реализован компилятор, делающий оптимизацию автоматически, т.к. в некоторых точках процесса программист обязан принимать волевые решения.
Сравнение на модельном примере 30
ускорение
25 20 15 10
позволяет качественно изменить требования по памяти, в том числе существенно сократив время интерпретации. Таким образом, инструментальные средства среды ParJava позволяют реализовать технологический процесс, обеспечивающий итеративную разработку параллельной программы. Отметим, что итеративная разработка предполагает достаточно большое число итераций, что может привести к большим накладным расходам по времени разработки и ресурсам. Итеративная разработка реальных программ, предлагаемая в рамках ParJava, возможна благодаря тому, что большая часть процесса выполняется на инструментальном компьютере, в том числе за счет использования инструментов, базирующихся на интерпретаторе параллельных программ. Применение интерпретатора позволяет достаточно точно оценивать ускорение программы. Ошибка на реальных приложениях не превосходила 10%, и в среднем составила ~5% [36-44]. Окончательные значения параметров параллельной программы можно уточнить при помощи отладочных запусков на целевой аппаратуре.
5
2.3. Механизмы времени выполнения среды ParJava
0
Для обеспечения возможности использования среды Java на высокопроизводительных кластерных системах были реализованы стандартная библиотека MPI и механизм контрольных точек.
0
4
8
12
16
20
24
28
процессоры
2.3.1. Реализация стандартной окружения Java
блокирующий send неблоки рующи й send Амдалева кр ивая для этой прогр аммы
Рис. 3. Сравнение масштабируемости параллельных программ, использующих блокирующие и неблокирующие пересылки На следующем этапе, необходимо проинтерпретировать полученную программу на реальных данных, для того чтобы оценить, какое количество вычислителей будет оптимальным для счета. Для этого снова используется инструмент «Inerpreter». Поскольку интерпретатор использует смешанную технику, включающую в себя элементы прямого выполнения, довольно остро стоит проблема нехватки памяти на инструментальной машине. Моделирование некоторых серьёзных программ требует использования довольно больших массивов данных, которые не могут быть размещены в памяти одного вычислительного узла. Для решения этой проблемы проводится преобразование модели, представляющее собой удаление выражений программы, значение которых не влияет на поток управления. Это 19
библиотеки
MPI
для
Первой задачей, которую было необходимо решить при разработке среды ParJava, была эффективная реализация стандартной библиотеки MPI для окружения Java. В настоящее время известно несколько реализаций библиотеки MPI для окружения Java, но ни одна из этих реализаций не обеспечивает достаточно эффективных обменов данными. Кроме того, в них реализованы не все функции библиотеки MPI. Поэтому для среды ParJava была разработана оригинальная реализация библиотеки MPI для окружения Java – библиотека mpiJava. В настоящее время в библиотеке mpiJava поддерживаются все функции стандарта MPI 1.1, а также параллельные операции ввода-вывода из стандарта MPI 2. Библиотека mpiJava реализована путем «привязки» (binding) к существующим реализациям библиотеки MPI с помощью интерфейса JNI по аналогии с «привязкой» для C++, описанной в стандарте MPI 2. Начиная с версии 1.4, в Java поддерживаются прямые буферы, содержимое которых может находиться в памяти операционной системы (вне кучи Java). Использование прямых буферов при передаче данных позволяет избежать 20
лишних копирований данных. Это позволяет сократить накладные расходы на передачу данных.
2.3.2. Механизм контрольных точек В среде ParJava реализован инструмент “CheckPoints”, реализующий механизм контрольных точек, который позволяет существенно сократить объемы сохраняемых данных и время на их сохранение. Реализация механизма контрольных точек для параллельной программы является нетривиальной задачей. Параллелизм усложняет процесс установки точек останова, т. к. сообщения порождают связи между отдельными процессами, и приходится обеспечивать так называемые консистентные состояния. Состояние двух процессов называется неконсистентным, если при передаче сообщения одного процесса другому, может возникнуть состояние, когда первый процесс еще не послал сообщение, а во втором уже сработала функция получения сообщения. Если в таком состоянии поставить точку останова, то восстановив впоследствии контекст задачи, мы не получим ее корректной работы. Пользователь указывает в программе место сохранения данных с помощью директивы EXCLUDE_HERE. В контрольной точке 1 (рис. 4) не сохраняются данные, которые будут обновлены до своего использования («мертвые» переменные). В контрольной точке 2 не сохраняются данные, которые используются только для чтения до этой контрольной точки. Результатом будет значительное уменьшение размеров сохраняемых данных в контрольной точке и уменьшение накладных расходов на их сохранение.
Выполнение программы
Контрольная точка 1
Контрольная точка 2
Вначале граф потока управления G=(N, E) разбивается на подграфы G′. Корнем каждого подграфа G′ является директива EXCLUDE_HERE. Подграф включает все пути, достижимые из этой директивы, не проходящие через другую директиву EXCLUDE_HERE. Для каждого подграфа вычисляются 2 множества переменных: DE(G′) множество переменных, которые «мертвы» на каждой директиве EXCLUDE_HERE в G′ и RO(G′) - множество переменных, предназначенных только-для-чтения по всему подграфу G′. Для нахождения множеств DE(G′) и RO(G′) используется консервативный анализ потока данных. В каждом состоянии S в программе вычисляются два множества характеризующие доступ к памяти: use[S] – множество переменных, которые могут быть использованы вдоль некоторого пути в графе, и def[S] – множество переменных, которым присваиваются значения до их использования вдоль некоторого пути в графе, начинающегося в S, или множество определений переменных. Ячейка памяти l является «живой» в состоянии S, если существует путь из S в другое состояние S′ такой, что l ∈use[S′] и для каждого S′′ на этом пути l∉def[S′′]. Ячейка l является элементом DE(G′), если l «мертвая» во всех операторах сохранения контрольных точек в G′. Ячейка памяти l является ячейкой только-для-чтения в операторе S, если l ∉use[S]. Поэтому l∈ RO(G′) тогда и только тогда, когда l∉gen[S] для всех S в G′. Эти определения консервативны, поскольку они ищут все возможные пути через граф потока управления, тогда как некоторые из них могут никогда не достигнуть выполнения в программе. Анализ «живых» переменных обычно выражается в виде уравнений потока данных, по одному на каждое состояние в программе. Мы дадим уравнение потока данных, которое позволит нам определить DE(G′) и RO(G′) для каждого подграфа G′ в программе. Каждое из этих уравнений может быть решено обычным итеративным методом. Для достижения нашей цели уравнение потока данных характеризуем его функцией обновления. Такая функция ассоциируется с каждым состоянием S. Определим in[S] как множество мертвых переменных в точке непосредственно перед блоком S, а out[S] – такое же множество в точке, непосредственно следующей за блоком S. Вычисляем множество мертвых переменных в направлении обратном графу потока управления. Система уравнений выглядит следующим образом:
out[S] = ∩S′ in[S′] in[S] = FS,
Рис. 4. Контрольные точки
21
22
3. Приложения среды ParJava В этом разделе описываются результаты применения разработанной методологии поддержки разработки параллельных программ на примере создания программ моделирования интенсивных атмосферных вихрей (ИАВ) и моделирования теплового движения молекул воды и ионов в присутствии фрагмента ДНК.
3.1. Моделирование процесса зарождения торнадо В Институте физики Земли РАН была разработана математическая модель развития торнадо в трехмерной сжимаемой сухоадиабатической атмосфере. Большой объем вычислений для получения численного решения потребовал реализации программы на высокопроизводительных вычислительных кластерах. Рассматриваемая система уравнений является сильно нелинейной системой смешанного типа. Для решения системы использовалась явная разностная условно-устойчивая схема второго порядка точности по времени и пространству; критерии ее устойчивости оказались близкими к явной схеме Маккормака. Программа разработана в Институте системного программирования РАН в сотрудничестве с Институтом физики Земли РАН с использованием среды ParJava и предназначена для выполнения на кластерных вычислительных системах. Программу можно разделить на два блока: загрузка/инициализация данных и главный цикл. Сохранение результатов происходит во время выполнения главного цикла. Входные данные хранятся в файле, где перечислены физические параметры модели и вспомогательные данные для работы программы, например, количество выдач значимых результатов и пути. Для выявления возможностей распараллеливания циклы были исследованы при помощи Омега теста, реализованного в среде ParJava, который показал отсутствие зависимостей по данным между элементами массивов, обрабатываемых в циклах. Это позволило разделить массивы на блоки и распределить полученные блоки по процессорам кластера. Поскольку разностная схема является трехточечной, возникают теневые грани ширины в 23
один пространственный слой. На текущей итерации используются только данные, вычисленные на предыдущих итерациях. Это позволяет обновлять теневые грани один раз при вычислении каждого слоя, что снижает накладные расходы на пересылку. Исследования на интерпретаторе показали, что двумерное разбиение массивов наиболее эффективно, поэтому в программе использовалось двумерное разбиение. Вариант с трехмерным разбиением оказался не самым оптимальным из-за неоднородности вычислений по оси Z. Как показано в разд. 2, для данного класса задач необходимо использовать коммуникационный шаблон с использованием неблокирующих коммуникаций. Так как программа моделирования торнадо – это программа с большим временем счета: 300 секунд жизни торнадо на кластере МСЦ (64 вычислителя Power 2,2 GHz, 4 GB) рассчитывались около недели, − и большим объемом генерируемых данных, использовался механизм контрольных точек. На рис. 5 приводятся графики ускорения программы при блокирующих и неблокирующих обменах данными. При тестировании производительности использовались начальные данные рабочей задачи, но вычислялись только первая секунда жизни торнадо. 68 64 60 56 52 48 44
Блокирующ ие пересылки
40 ускорение
где out[S] = ∩S′ DEAD[S′] – это пересечение множества мертвых переменных для всех состояний S′, которые являются преемниками S в G, а FS – это функция обновления, которая в нашем случае равна FS = out[S] ∪ gen[S] – use[S] и свидетельствует о переменных, которые мертвы непосредственно перед S и тех переменных, которые стали мертвыми после S, плюс о тех, в которые производилась запись в состоянии S, минус любые ячейки, с которых происходило чтение в S. При следующем запуске эти массивы и параметры загружаются, и по ним полностью восстанавливается контекст прерванной задачи.
36 32
Оптимизированные неблокирующ ие пересылки
28
Амдалева кривая
24 20 16 12 8 4 0 0
4
8
12
16
20
24
28
32
36
40
44
48
52
56
количество процессоров
Рис. 5. График ускорения.
24
60
64
68
3.2. Моделирование теплового движения молекул воды и ионов в присутствии фрагмента ДНК Для моделирования теплового движения молекул воды и ионов в присутствии фрагмента ДНК методом Монте-Карло существовала параллельная программа, написанная на языке Fortran с использованием MPI. Биологические и математические аспекты задачи и программы описаны в работе [23]. В параллельной программе исходное пространство, заполненное молекулами, разделяется на равные параллелепипеды по числу доступных процессоров. Процессоры проводят испытания по методу Монте-Карло над каждой молекулой. Решение о принятии новой конфигурации принимается на основе вычисления изменения энергии. Для вычисления изменения энергии на каждом шаге производится выборка соседей, вклад которых учитывается при вычислении. После того как процессоры перебирают все молекулы, производится обмен данными между процессорами, моделирующими соседние области пространства. Исходная Fortran-программа требовала для моделирования реальных систем большого числа процессоров (от 512) и могла выполняться только на количестве процессоров, равному кубу натурального числа, то есть на 8, 27, 64 и т.д. процессорах. Это приводило к увеличению времени, необходимого для моделирования одной системы. Главная проблема заключалась в кубическом росте количества перебираемых молекул при выборе соседей, с увеличением количества молекул, моделируемых на одном процессоре. В среде ParJava была реализована аналогичная программа на языке Java с использованием MPI. Исследование производительности Java-программы позволило выявить причину неэффективной работы Fortran-программы. Для сокращения объема вычислений при построении списка соседей были предложены и реализованы две модификации. Основная модификация заключалась во введении дополнительного разделения молекул по пространству. Это позволило уменьшить количество перебираемых молекул и сократить объем вычислений при построении списка соседей. Для оценки эффективности произведенных модификаций сравнивались три программы. Исходная Fortran-программа с использованием MPI, сравнивалась с двумя Java-программами, также использующими MPI. Первая Javaпрограмма включает в себя только одну модификацию, заключающуюся в использовании одного набора соседей при вычислении энергии для текущего и измененного положения молекулы. Во второй программе, дополнительно 25
было реализовано разбиение на поддомены. Обе Java-программы можно запускать на произвольном числе процессоров, с одним ограничением: по каждому из направлений исходная ячейка должна быть разделена хотя бы на 2 домена. Для сравнения производительности была проведена серия тестов с разным числом вычислителей. Объем задачи увеличивался с ростом числа вычислителей таким образом, чтобы на каждом вычислителе был одинаковый объем данных. На 8 вычислителях моделировалась кубическая ячейка с ребром 100 ангстрем, а на 64 вычислителях ребро ячейки составляло 200 ангстрем, при этом каждый вычислитель моделировал 4166 молекул. В процессе моделирования над каждой из молекул проводилось по 1000 испытаний. Результаты измерения, приведенные на рис. 6, получены на кластере ИСП РАН, состоящем из 12 узлов (2 х Intel Xeon X5355, 4 ядра), объединенных сетью Myrinet. Число MPI-процессов соответствовало числу ядер, каждое из которых может рассматриваться как отдельный вычислитель. Для Fortranпрограммы измерения производились с использованием 8, 27 и 64 ядер. Для Java-программ измерения производились с использованием 8, 12, 16, 24, 27, 32, 36, 40, 48, 56, 64 ядер. 1800
1600
1400
1200
Время, с
Данные результаты вычислений использовались в исследовании процесса зарождения торнадо и они продемонстрировали адекватность используемой модели и возможность использования среды ParJava для разработки такого рода приложений. Более подробно результаты моделирования торнадо рассматриваются в работе [22].
1000
800
600
400
200
0 8
12
16
20
24
27
32
36
40
44
48
52
56
60
64
кол-во процессоров Исходная программа на FORTRAN Модифицированная программа на java2
Модифицированная программа на java1
Рис. 6. Сравнение исходной программы на языке FORTRAN77 c модифицированными программами на языке Java. Из графика видно, что при равных объемах данных первая модифицированная программа на Java работает в 1,5 раза быстрее, а вторая в 2-3 раза, при этом 26
удалось сохранить точность расчета. Программа на Java, в точности соответствующая исходной программе на языке Фортран, требовала от 10 до 30% больше времени и имела аналогичную форму графика производительности. Исследование и модификация программы в среде ParJava позволило увеличить объем решаемой задачи с полным сохранением свойств модели. Модифицированная программа позволила смоделировать на 128 вычислителях кластера МСЦ фрагмент В-ДНК, состоящий из 150 пар нуклеотидов (15 витков двойной спирали, 9555 атомов) и водную оболочку, содержащую ионы
Cl − и Na + . Ячейки, включавшая этот фрагмент, имела
o
размер в 220 Α , и он содержал ~300 тысяч молекул воды. Время работы программы 9 часов, за это время над ионами и молекулами воды было произведено по 10000 элементарных испытаний. Более подробно результаты моделирования теплового движения молекул воды и ионов в присутствии фрагмента ДНК приводятся в работе [24].
4. Направления дальнейших исследований В настоящем разделе рассматриваются дальнейшие работы по параллельному программированию. Ставится цель разработать методы и реализовать соответствующие инструментальные средства, позволяющие в автоматическом режиме выявлять потенциальный параллелизм, генерировать параллельный код и осуществлять доводку полученного кода с учетом особенностей выбранной аппаратуры (кластер, система с общей памятью, кластер с многоядерными узлами). Для анализа и трансформации гнезд циклов, в которых все индексные выражения и границы циклов задаются аффинными формами относительно индексов массивов, будет разработана инфраструктура, базирующаяся на декларативном представлении гнезд циклов в виде выпуклых многогранников в пространстве индексов. Такое представление существенно снижает накладные расходы на анализ и трансформацию гнезд циклов, так как позволяет выполнять их с помощью операций над матрицами. В составе инфраструктуры будет реализован набор методов (API), реализующих семь базовых трансформаций циклов (любая трансформация цикла является их суперпозицией), а также методы, позволяющие определять зависимости по данным, выявлять шаблоны доступа к памяти, вычислять границы циклов при изменении порядка циклов в гнезде и др. Кроме того, будет разработана и реализована подсистема преобразования императивного представления (байт код) программы в декларативное, а также подсистема преобразования декларативного представления в параллельные программы для систем с распределенной памятью (Java+MPI) или для систем с общей памятью (Javaтреды). Будет исследован круг вопросов связанных с генерацией эффективного кода в модели, когда каждый процесс MPI является 27
многотредовым. Будет проведен сравнительный анализ такой реализации с реализацией на Java+MPI на кластерах с многоядерными узлами. Будет разработан инструмент, позволяющий в автоматическом режиме подбирать коммуникационные примитивов с использованием методики, рассмотренной в разделе 2. В основе инструмента многократная интерпретация модели разрабатываемой параллельной программы. Для обеспечения многократной интерпретации в приемлемое время будет реализована возможность автоматической генерации «скелета» реального приложения. Будут исследованы произвольные (не аффинные) гнезда циклов и разработаны инструментальные средства, позволяющие распараллеливать их в диалоговом режиме: инструмент для выяснения наличия зависимостей по данным между итерациями цикла с помощью синтетического Омега-теста и инструменты для вычисления вектора направлений и вектора расстояний между, характеризующих зависимости между итерациями гнезда циклов. В последнее время получили распространение специализированные устройства, обеспечивающие высокую степень параллелизма. Одним из классов таких устройств являются графические акселераторы. При стоимости и энергопотреблении, сравнимыми с процессорами архитектуры x86-64, они превосходят их по пиковой производительности на операциях с плавающей точкой и пропускной способности памяти приблизительно на порядок. Большой интерес вызывают исследования возможности использования неоднородных вычислительных архитектур, включающих универсальные процессоры и акселераторы для решения задач, не связанных непосредственно с обработкой графики. Для разработки программ для таких гибридных систем в настоящее время используется модель программирования CUDA, первоначально разработанная для акселераторов Nvidia. Она точно отражает организацию оборудования, что позволяет создавать эффективные программы, но в то же время требует от разработчика хорошего понимания архитектуры акселератора, а перенос существующего кода для выполнения на акселераторе с помощью CUDA обычно требует значительных модификаций. Соответственно, актуальной является задача разработки технологий компиляции, позволяющих упростить написание эффективных программ и перенос существующего кода на графические акселераторы. Для этого предлагается определить набор прагм, позволяющих выделить участки кода, которые должны быть скомпилированы для выполнения на акселераторе. Чтобы быть разумной альтернативой более низкоуровневым средствам, такой набор расширений должен быть достаточно гибким, чтобы позволять улучшать производительность кода за счёт тонкой настройки конфигурации потоков выполнения и распределения данных в иерархии памяти акселератора. В то же время, реализованные средства должны позволять 28
последовательный перенос программного кода на акселератор с минимальными изменениями в исходных кодах и процессе компиляции. Реализацию предлагается осуществить в компиляторе GCC, который является де-факто стандартным компилятором для операционной системы Linux, поддерживает несколько входных языков (C, C++, Fortran, Java, Ada и другие) и позволяет генерировать код для множества архитектур. В GCC уже реализованы OpenMP 3.0 и система анализа зависимостей и трансформации циклов GRAPHITE, что является существенной частью необходимой для такого проекта инфраструктуры. Литература [1] 1. David A. Patterson et al. The Parallel Computing Laboratory at U.C. Berkeley: A Research Agenda Based on the Berkeley View. Technical Report No. UCB/EECS-2008-23 [2] http://www.eecs.berkeley.edu/Pubs/TechRpts/2008/EECS-2008-23.html. March 21, 2008. [3] 2. J.C. Adams, W.S. Brainard, J.T. Martin, B.T. Smith, J.L. Wagener. Fortran 95 Handbook. Complete ISO/ANSI Reference. Scientific and Engineering Computation Series. MIT Press, Cambridge, Massachusetts, 1997 [4] 3. W. Chen, C. Iancu, K. Yelick. Communication Optimizations for Fine-grained UPC Applications. //14th International Conference on Parallel Architectures and Compilation Techniques (PACT), 2005. [5] 4. K. Kennedy, C. Koelbel, H. Zima. The Rise and Fall of High Performance Fortran: An Historical Object Lesson // HOPL III: Proceedings of the third ACM SIGPLAN conference on History of programming,2007, San Diego, California, June 09 - 10, 2007, pp. 7-1 – 7-22 [6] 5. CUDA, среда для параллельного программирования на GPU. http://www.nvidia.com/object/cuda_home.html [7] 6. The DARPA High Productivity Computing Systems. http://www.highproductivity.org/ [8] 7. K. Ebcioglu, V. Saraswat, V. Sarkar. X10: an Experimental Language for High Productivity Programming of Scalable Systems // Proceedings of the Second Workshop on Productivity and Performance in High-End Computing (PPHEC-05) Feb 13, 2005, San Francisco, USA pp. 45-52 [9] 8. B.L. Chamberlain, D. Callahan, H.P. Zima. Parallel Programmability and the Chapel Language // International Journal of High Performance Computing Applications, August 2007, 21(3): 291-312. [10] 9. E. Allen, D. Chase, J. Hallett et al The Fortress Language Specification (Version 1.0) / cSun Microsystems, Inc., March 31, 2008 (262 pages) [11] 10. Cilk++ Solution Overview. http://www.cilk.com/multicore-products/cilk-solutionoverview/ [12] 11. Brook+ Streaming Compiler. http://ati.amd.com/technology/streamcomputing/sdkdwnld.html [13] 12. Parallel Debugger: DDT. http://www.nottingham.ac.uk/hpc/html/docs/numerical/parallel_ddt.php [14] 13. TotalView. http://www.totalviewtech.com/
29
[15] 14. Sameer S. Shende, Allen D. Malony. The TAU Parallel Performance System // The International Journal of High Performance Computing Applications,Volume 20, No. 2, Summer 2006, pp. 287–311 [16] 15. В.П. Иванников, А.И. Аветисян, С.С. Гайсарян, В.А. Падарян. Оценка динамических характеристик параллельной программы на модели. // «Программирование» 2006, №4 с. 21–37 [17] 16. Brian Amedro, Vladimir Bodnartchouk, Denis Caromel, Christian Delbé, Fabrice Huet, Guillermo L. Taboada. Current State of Java for HPC. Technical report N° 0353. August 2008. [18] 17. Mark Baker, Bryan Carpenter, and Aamir Shafi. MPJ Express: Towards Thread Safe Java HPC, Submitted to the IEEE International Conference on Cluster Computing (Cluster 2006), Barcelona, Spain, 25-28 September, 2006. [19] 18. Distributed Parallel Programming Environment for Java. http://www.alphaworks.ibm.com/tech/dppej [20] 19. В.П. Иванников, А.И. Аветисян, С.С. Гайсарян, В.А. Падарян. Прогнозирование производительности MPI-программ на основе моделей. // «Автоматика и телемеханика», 2007, №5, с. 8-17 [21] 20. Eclipse. http://www.eclipse.org/ [22] 21. A.W. Lim, M.S. Lam. Maximizing parallelism and minimizing synchronization with affine transforms. Proc. 24th ACM SIGPLAN-SIG-ACT Symposium on principles of programming languages. 1997, pp. 201-214. [23] 22. Аветисян А.И., Бабкова В., Гайсарян С.С., Губарь А.Ю. Рождение торнадо в теории мезомасштабной турбулентности по Николаевскому. Трехмерная численная модель в ParJava. // Журнал «Математическое моделирование», 2008, №8. [24] 23. Теплухин А. В. Многопроцессорное моделирование гидратации мезоскопических фрагментов ДНК. // Математическое моделирование, 2004г., том 16, номер 11, с.15-24. [25] 24. Аветисян А.И., Гайсарян С.С., Калугин М.Д., Теплухин А.В. «Разработка параллельного алгоритма компьютерного моделирования водно-ионной оболочки ДНК»// Труды XIII Байкальской Всероссийской конференции «Информационные и математические технологии в науке и управлении». Часть I. - Иркутск: ИСЭМ СО РАН, 2008, с. 195-206.
30
Компиляция программ для современных архитектур А. Белеванцев, Д. Журихин, Д. Мельник Аннотация. Настоящая статья посвящена обзору некоторых работ по оптимизации программ для современных вычислительных архитектур, проводимых в отделе компиляторных технологий Института системного программирования РАН. Работы включают в себя выявление параллелизма на уровне команд для архитектуры Intel Itanium, исследование и разработку энергосберегающих оптимизаций для архитектуры ARM, а также исследования по динамическим оптимизациям для языков общего назначения, выполняемым на машине пользователя. Большинство приведенных работ выполнялось в рамках компилятора GCC с открытыми исходными кодами, являющегося стандартным компилятором для Unix-подобных систем.
1. Введение Развитие вычислительной техники за последние годы приводит к появлению большого количества процессорных архитектур, для использования возможностей которых необходимы новые технологии компиляции. Например, архитектуры с явно выраженным параллелизмом команд требуют от компилятора наличия оптимизаций, направленных на выявление и использование такого параллелизма, а именно – агрессивного планирования команд и конвейеризации циклов. Популярность встраиваемых архитектур, повсеместно использующихся в мобильных устройствах самого разнообразного назначения, влечет необходимость разработки компиляторных технологий, обеспечивающих не только высокую производительность программ, но и небольшой размер исполняемых файлов, а также низкое энергопотребление системы. Многоядерные архитектуры и гетерогенные архитектуры с несколькими акселераторами, получившие широкое распространение, нуждаются в разработке новых методов компиляции, позволяющих программисту в полуавтоматическом режиме указать желаемое распределение вычислений и потоков данных по компонентам таких архитектур. Наконец, актуальной является задача об оптимизации программы для конкретной реализации некоторой архитектуры, а также для конкретных наборов входных данных пользователя. Эта задача частично решена для динамических языков типа Java, но не для языков общего назначения. 31
Настоящая статья посвящена обзору некоторых работ, проводимых по этим направлениям в отделе компиляторных технологий Института системного программирования РАН. Работы включают в себя выявление параллелизма на уровне команд для архитектуры Intel Itanium, исследование и разработку энергосберегающих оптимизаций для архитектуры ARM, а также исследования по динамическим оптимизациям для языков общего назначения, выполняемым на машине пользователя. Большинство приведенных работ выполнялось в рамках компилятора GCC [[10]] с открытыми исходными кодами, являющегося де-факто стандартом для UNIX-систем и поддерживающего широкий набор входных языков (Cи/Си++, Фортран, Java, Ада) и целевых архитектур (x86, PowerPC, SPARC, ARM, Itanium и множество других). Каждой из этих работ далее посвящена один раздел, включающий обзор существующих исследований по соответствующему направлению, описание работ, выполненных в ИСП РАН, полученные результаты и планы на ближайшие годы. Наконец, в заключение статьи приводятся и обсуждаются выводы из проведенных исследований.
2. Компиляция для архитектур с явно выраженным параллелизмом команд Современные процессорные архитектуры обладают большим количеством параллельно работающих конвейерных функциональных устройств. Для достижения высокой производительности на этих архитектурах требуется обеспечить непрерывную загрузку этих функциональных устройств, максимально используя параллелизм на уровне команд, имеющийся в программе. Основным способом в выявлении такого параллелизма является переупорядочивание команд, выполняемое при планировании команд либо конвейеризации циклов. Суперскалярные архитектуры (x86, PowerPC) планируют команды аппаратно во время выполнения программы, т.е. порядок выдачи команд на выполнение может отличаться от порядка, диктуемого программой. Архитектуры с явным параллелизмом команд (EPIC) требуют, чтобы окончательный порядок выполнения команд определялся при компиляции: сама архитектура точно следует заданному порядку, не выполняя никакого динамического переупорядочивания. Это позволяет отказаться от аппаратных устройств, реализующих это переупорядочивание, в пользу других свойств, предоставляющих компилятору больше возможностей по выявлению параллелизма на уровне команд. Рассмотрим кратко наиболее важные из этих свойств, реализованных в архитектуре Itanium.
32
2.1. Особенности архитектур параллелизмом
с
явно
выраженным
Опережающее выполнение команд прежде, чем становится известно, что их выполнение необходимо, принято называть спекулятивным выполнением (speculative execution). В суперскалярной архитектуре поддержка спекулятивного выполнения, необходимая для предсказания переходов, обеспечивается специальным буфером переупорядочивания, хранящим промежуточные результаты выполнившихся спекулятивно команд, а также раздельными механизмами фиксации и выдачи спекулятивных и обычных команд. В EPIC-архитектуре компилятор обязан выбрать команду для спекулятивного выполнения, переместить её в новое место и пометить, как спекулятивную. При этом для корректной обработки исключений архитектура обеспечивает их подавление при выполнении спекулятивной команды и выброс исключения на специальной команде проверки, также вставляемой компилятором. Пример спекулятивного выполнения команд на архитектуре Itanium показан на рис. 1. б) а) r2 = ld[r3] ;; p6 = cmp r2, 0 ;; (p6) jmp label mul r4, r4, r1 ;; add r5, r5, r4 ;; r6 = ld [r5]
Рассмотрим пример реализации условного выполнения в процессорах Itanium. Предикатные регистры в Itanium хранятся в 64-битном слове, при этом команды сравнения устанавливают пару соседних предикатных регистров в противоположные значения; таким образом, можно одновременно хранить результат 31 сравнения (значение нулевого предикатного регистра фиксировано и равно логической единице). В коде каждой команды есть 6битное поле, в котором записан номер предикатного регистра, контролирующего её выполнение (наличие всегда установленного в единицу предикатного регистра позволяет единообразно записывать условно и безусловно выполняющиеся команды). Условные переходы записываются как безусловные переходы, защищённые соответствующим предикатом. На рис. 2 показан пример вычисления минимума из двух целых чисел на ассемблере x86 (без использования команды условной пересылки) и на ассемблере Itanium с применением условного выполнения. а) б) в)
Другим примером особенности, направленной на выявление параллелизма на уровне команд, является поддержка условного выполнения через предикатные регистры. При условном выполнении практически любую команду можно аннотировать одним из предикатных регистров, при этом команда выполняется только в том случае, если значение предикатного регистра равно единице. Это позволяет выражать достаточно длинные ветвления без переходов, но лишь командами сравнения и командами с условным выполнением, что в свою очередь уменьшает количество зависимостей по управлению, мешающих выявлению параллелизма. 33
cmp4.gt p6,p7=r32,r33 (p6) movl r32=r33
Рис. 2. Вычисление минимума двух чисел на ассемблере x86 (а) и с помощью условного выполнения на процессоре Itanium (б).
Рис. 3. Исходный цикл (а) и ядро конвейеризованного цикла c использованием явных пересылок между регистрами (б) и вращающихся регистров (в). 34
Наконец, последним рассмотрим поддержку в EPIC-архитектуре вращающихся регистров. Важной оптимизацией для выявления параллелизма на уровне команд является программная конвейеризация циклов, целью которой является такое планирование команд тела цикла, что итерации цикла выстраиваются в «конвейер», образуя пролог цикла, ядро из команд с различных итераций, и эпилог. В том случае, когда в ядре перекрываются сразу несколько итераций, часто необходимо выполнять переименование регистров, чтобы устранить ложные зависимости по регистрам между итерациями. Такое переименование обычно требует дополнительных операций пересылок между регистрами (см. рисунок 3(б), где такие пересылки показаны курсивом), причем если результат, записанный на предыдущей итерации в копируемый регистр, еще не готов, то обращение к этому регистру вызовет останов конвейера до завершения вычисления этого результата. Избавиться от лишних пересылок регистров помогает механизм вращающихся регистров, представляющий из себя аппаратно поддерживаемое переименование регистров. Команды цикла используют виртуальные номера регистров, r32-r127, а команда br.ctop выполняет сдвиг окна отображения виртуальных регистров в физические таким образом, что происходит циклическое переименование: r[i]=r[i-1], i=1..N-1, r[0]=r[N-1], где N – размер вращающегося регистрового окна. Никаких физических пересылок значений между регистрами при этом не происходит. Более того, за счет использования вращающихся предикатных регистров автоматически генерируется пролог и эпилог цикла (см. рис. 3 (в)).
2.2. Алгоритм планирования циклов для Intel Itanium
команд
и
селективного планирования был разработан для архитектур с очень длинным командным словом и хорошо подходит для экспериментов по увеличению производительности для EPIC-архитектур. Он поддерживает ряд полезных преобразований команд, позволяющих избавляться от части зависимостей по данным (переименование регистров, подстановка через копии), а также делает простым добавление новых преобразований.
2.2.1. Базовый алгоритм селективного планирования Селективный планировщик является классическим итеративным планировщиком, обходящим регион планирования сверху вниз. Обрабатываются произвольные ациклические регионы графа потока управления программы, возможно, с несколькими входами. Поддерживается несколько точек планирования, к которым собираются доступные команды, называемых барьерами. Каждая итерация планировщика четко делится на этап сбора доступных команд, этап выбора лучшей команды для планирования и этап перемещения выбранной команды, при этом корректность программы обеспечивается этапом сбора и перемещения, а получаемая производительность полностью зависит от этапа выбора лучшей команды, который обычно является набором эвристик. После того, как на текущем цикле планирования для данного барьера невозможно выполнить больше команд, либо нет доступных для выполнения команд, обрабатывается следующий барьер. После обработки всех барьеров происходит передвижение барьеров через запланированные команды, и цикл планирования повторяется. Планировщик останавливается по достижении конца региона.
конвейеризации
В ИСП РАН было выполнено несколько работ по улучшению производительности компилятора GCC для платформы Intel Itanium, в ходе которых разрабатывалась и реализовывалась поддержка в GCC рассмотренных выше свойств этой архитектуры. Первыми были закончены работы по добавлению поддержки спекулятивного выполнения в планировщик команд компилятора GCC, описанные в [[5]]. По результатам тестирования реализации на пакете тестов SPEC CPU 2000 [[23]] было получено ускорение в 2.5%, а на отдельных тестах – до 20%. Это позволило включить реализованную поддержку в официальные релизы компилятора GCC, начиная с версии 4.2.0. Кроме этого, были выполнены работы по улучшению точности низкоуровневого анализа алиасов, используемого в компиляторе GCC, и использованию более точных данных при планировании команд. По результатам изначальных исследований по улучшению планирования команд было принято решение о разработке и реализации нового планировщика команд и конвейеризации циклов для EPIC-архитектур, основанного на подходе селективного планирования [[19]]. Алгоритм 35
Рис. 4. Конвейеризация циклов в селективном планировании: продвижение барьеров во внутреннем цикле (слева), образование регионов для всего гнезда циклов (справа). При сборе команд для планирования регион обходится в обратном топологическом порядке; при этом текущее множество собранных команд «протаскивается» через обрабатываемую команду на пути «наверх», и все команды, имеющие неустранимые зависимости по управлению либо по 36
данным с обрабатываемой, удаляются из множества. Все преобразования команд, ведущие к устранению зависимостей, могут быть реализованы на этом этапе. В точках разделения потока управления текущее множество доступных команд предварительно получается как объединение всех множеств, доступных на потомках обрабатываемой команды. Дополнительно в процессе сбора могут быть вычислены некоторые атрибуты команд (доступность вдоль разных путей, вероятность выполнения и т.п.), которые могут использоваться в дальнейшем при выборе лучшей команды для планирования. Промежуточные множества доступных команд сохраняются в начале каждого базового блока. На этапе перемещения выбранной команды регион обходится аналогичным образом сверху вниз в поиске команд, которые могли быть преобразованы в выбранную, при этом используются сохраненные множества доступных команд – если искомая команда не содержится в сохраненном множестве, то её нет смысла искать ниже текущего места региона. Если команда найдена, то она удаляется из региона, а на обратном пути вверх обновляются сохраненные множества доступных команд и в точках слияния потока управления на путях, не лежащих на текущем пути обхода, создаются компенсационные копии выбранной команды. После окончания перемещения выбранная команда в преобразованном виде добавляется в поток команд в точке планирования, а все промежуточные множества доступных команд оказываются верными, что значительно ускоряет этап сбора команд для следующей итерации планирования. Важным достоинством селективного планировщика является возможность конвейеризации циклов, вытекающая из поддержки перемещений команд с созданием компенсационных копий и из того, что перемещение команд через барьер запрещено. При планировании внутреннего цикла из гнезда циклов текущим регионом считается ациклический регион, получающийся из цикла разрывом тех дуг, на которых в данный момент стоят барьеры. В начале планирования разрывается обратная дуга цикла. На этапе сбора команд разрешается собирать уже запланированные команды – при планировании обычного региона такая ситуация запрещена. При перемещении уже запланированной команды вдоль обратной дуги на входе в цикл наблюдается слияние потока управления, и поэтому на дуге перед циклом будет создана компенсационная копия, образующая пролог конвейеризованного цикла (см. рис. 4). При планировании внешнего цикла пролог внутреннего цикла добавляется к региону планирования, а тело внутреннего цикла обрабатывается как «черный ящик», при этом перемещения команд через него запрещены.
2.2.2. Усовершенствования базового алгоритма После реализации вышеописанного базового алгоритма для компилятора GCC и первоначальных экспериментов нами был разработан и реализован ряд 37
усовершенствований, улучшивших как показатели производительности алгоритма, так и время его работы. Во-первых, нами были реализованы дополнительные преобразования команд: спекулятивное выполнение команд и условное выполнение команд. Для поддержки обоих преобразований необходимо модифицировать этап сбора доступных команд, а также поиск и перемещение выбранной команды наверх к точке планирования. Спекулятивные команды для Intel Itanium создаются при протаскивании команды загрузки наверх либо через условный или безусловный переход (спекулятивность по управлению), либо через возможно зависимую команду записи в память (спекулятивность по данным). Для спекулятивных команд отслеживается вероятность выполнения зависимостей (одной или нескольких), нарушенных при превращении команды в спекулятивную форму. При обнаружении команды загрузки, породившей спекулятивную форму, помимо ее удаления создается команда проверки результата спекулятивного выполнения и код восстановления, а при создании компенсационной копии такой команды эта копия обязательно преобразуется в спекулятивную форму. Более детально поддержка спекулятивного выполнения в нашем планировщике команд описана в работах [[4], [7], [8]]. Команды для условного выполнения создаются при слиянии промежуточных множеств доступных команд в точке разделения потока управления: в зависимости от направления, с которого поступила команда, если она еще не была аннотирована предикатным регистром, то она аннотируется либо регистром, контролирующим условный переход в точке разделения потока, либо его отрицанием. При поиске выбранной в условной форме команды необходимо преобразовать эту команду в обычную форму ровно на том условном переходе, на котором к команде был добавлен предикат при сборе команд. Остальные этапы алгоритма, в том числе создание компенсационных копий, при обработке команд в условной форме не меняются. Во-вторых, был выполнен ряд улучшений этапа выбора лучшей команды. В первую очередь, для выбора стал использоваться существующий механизм компилятора GCC, заключающийся в отслеживании конфликтов конвейера процессора через конечный автомат, описывающий функциональные устройства процессора [[15]]. Интерфейс автомата позволяет узнать необходимую задержку в тактах для выдачи данной команды в данном состоянии автомата. С помощью этого интерфейса в GCC реализован механизм локального перебора команд из множества готовых к выдаче на данном такте для поиска такой команды, выдача которой позволит выдать на данном такте наибольшее количество других готовых команд. Данный механизм был адаптирован нами для работы с вычисленным планировщиком множеством готовых команд. Далее, в ходе этапа сбора доступных команд также вычисляется полезность команды, отражающая вероятностей выполнения тех путей графа потока управления, вдоль которых доступна эта команда. Полезность команды в 38
промежуточном множестве доступных команд умножается на вероятность перехода по дуге при протаскивании команды вверх вдоль этой дуги, а при объединении множеств в точке слияния потока управления полезности одинаковых команд, пришедших в эту точку по разным путям, складываются. Полезность готовой команды используется при сортировке готовых команд для выделения более приоритетных. Другими эвристиками при этой сортировке являются длина критического пути, начинающегося от команды, ее спекулятивность, а также вероятность выполнения зависимостей, нарушенных ее перемещением, если она спекулятивна. Кроме того, незапланированные команды предпочитаются запланированным, чтобы гарантировать окончание алгоритма при конвейеризации циклов. Наконец, было выполнено большое количество исправлений реализации планировщика и кодогенератора GCC (более 30), которые явились результатом анализа производительности скомпилированных программ из пакета тестов SPEC CPU 2000. Приведем наиболее важные примеры. Переименование регистров применялось только к тем инструкциям, чья латентность превышает время выполнения инструкции копирования регистра в регистр. Это отсекает переименования, которые никогда не дадут выигрыша. Другим улучшением является запрет на применение преобразования переименования регистров (и спекулятивного выполнения команд по управлению) в тех случаях, когда результирующая инструкция будет запланирована на последнем такте цикла, и можно показать, что такое преобразование будет невыгодным. Далее, перепланирование конвейеризованного кода для достижения более плотного расписания в тех местах кода, из которых были перемещены инструкции, позволило нам улучшить ряд тестов SPEC на 0.5-1%. Этот дополнительный проход особенно полезен для маленьких циклов, в которых создаваемые конвейеризацией «дырки» имеют значение. В-третьих, алгоритм планирования был ускорен по сравнению с базовым. Из основных улучшений, приведших к уменьшению времени работы алгоритма, можно перечислить следующие: • кэширование результатов проноса команды через другую команду; • сохранение полной «истории» преобразований, которым подверглась команда при проносе наверх, для быстрого «отката» этих изменений; • применение переименования регистров только к самым приоритетным инструкциям; • ограничение количества обновлений множества доступных команд так, чтобы множества обновлялись только после планирования нескольких команд на данном барьере; • ограничение длины «окна» команд, которое просматривает планировщик в поисках кандидатов на выдачу, для прохода, на 39
котором выполняется перепланирование кода после конвейеризации. По результатам тестирования усовершенствованного алгоритма планирования на платформе Intel Itanium было получено среднее ускорение в 3-4% на пакете тестов SPEC CPU FP 2000 (для разного набора базовых опций получено разное ускорение), а на отдельных тестах – до 10%. Часть результатов представлена в таблице 1. Мелким шрифтом выделен тест, который работает некорректно с текущей реализацией поддержки условного выполнения.
Таблица 1. Результаты тестов SPEC FP для планировщика команд. Исходные коды реализованного алгоритма планирования команд и конвейеризации циклов был включен в специальную ветвь компилятора GCC, доступную с официального сайта разработчиков. Кроме того, по результатам настройки алгоритм планирования был включен в основную ветвь разработки компилятора GCC, как планировщик по умолчанию для платформы Itanium, и будет доступен в следующем релизе компилятора версии 4.4.0. Мы продолжаем работы над улучшением алгоритма, в первую очередь – над добавлением поддержки полного графа зависимостей по данным, что позволит как ускорить сам алгоритм, так и реализовать более эффективные эвристики для этапа выбора наилучшей команды. Также будут вестись работы над настройкой реализованного алгоритма на другие архитектуры, в частности, IBM Power6. 40
3. Оптимизации энергопотребления встраиваемых систем, управляемые компилятором Исследования по оптимизации энергопотребления встраиваемых систем активно ведутся в последнее десятилетие. Из наиболее популярных направлений можно отметить динамическое изменение напряжения на процессоре и его частоты; оптимизации доступа к памяти, в том числе отключение неактивных банков памяти; оптимизацию энергопотребления на стадии разработки новых чипов и т.д. (хорошие обзоры можно найти в работах [[9], [20]]). В данном разделе рассматриваются программные оптимизации энергопотребления, управляемые компилятором. Мы исследовали несколько направлений таких оптимизаций с использованием компилятора GCC для архитектуры ARM: динамическое изменение напряжения, основанное на данных профиля программы; влияние оптимизаций работы с памятью на энергопотребление; оптимизацию переключения битов на шине команд через модификации планировщика команд. Тестирование оптимизаций проводилось с помощью пакетов Aburto [[2]], MediaBench [[16]] и MiBench [[18]] на платах OMAP2430 [[21]] и MV320 [[20]], содержащие процессоры ARM 11-го поколения. Рассматриваемые тестовые пакеты состоят из небольших приложений, представляющих из себя обработку изображений и звука, а также другие вычисления, типичные для встраиваемых систем. В целом, проведенные исследования показали, что наиболее интересным подходом является динамическое изменение напряжения. В настоящее время мы развиваем прототипную реализацию этого метода в компиляторе GCC. Цикловые оптимизации, ускоряющие работу программы и снижающие энергопотребление, также являются многообещающими, однако в GCC мощная инфраструктура для таких оптимизаций появится лишь в версии 4.4.0, которая выходит в январе 2009 года.
3.1. Динамическое изменение напряжения Основной идеей динамического изменения напряжения на процессоре (далее ДИН) является такое изменение напряжение на элементе питания чипа в некоторых точках программы (называемых точками управления напряжением, ТУН), что энергопотребление системы сокращается, при этом сохраняя (либо незначительно снижая) производительность. Возможность такой оптимизации обеспечивается тем, что потребляемая энергия квадратично зависит от подаваемого напряжения, тогда как частота процессора (а, следовательно, и производительность) зависит от напряжения лишь линейно. Существует несколько классов алгоритмов ДИН, известные в литературе как статические (offline), динамические (online) и смешанные (mixed). Разница между этими классами заключается в моменте, в который принимается решение, во-первых, о местонахождении точек управления напряжением, и 41
во-вторых, о величине, на которую изменяется напряжение. Динамические алгоритмы ДИН принимают все эти решения во время работы программы (например, в планировщике ОС); статические алгоритмы определяют как точки, так и величины изменения напряжения во время компиляции (хотя непосредственно изменение напряжения также происходит во время работы программы); наконец, смешанные алгоритмы обычно вычисляют возможные точки изменения напряжения во время компиляции, а величина изменения определяется динамически. Нами была выполнена реализация статического алгоритма ДИН, основанная на [[13]]. Выбранный алгоритм вставляет точки изменения напряжения в тех местах программы, основное время выполнения которых тратится на работу с памятью. Если в такой области кода понизить напряжение на процессоре, то снижения производительности не произойдет, так как процессор все равно вынужден ждать данных из памяти. Необходимым условием для этого является раздельное питание процессора и памяти, что обычно и бывает в современных системах. Как точки изменения, так и величины изменения напряжения вычисляются алгоритмом статически на основании данных профиля программы, при этом учитывается время, затрачиваемое на смену напряжения. Мы рассматривали и другие статические алгоритмы ДИН в качестве кандидатов для исследований, но они либо тестировались только на симуляторах (а не на реальных встраиваемых системах либо ноутбуках), либо заключались в комбинировании классических цикловых оптимизаций с понижением напряжения, что может быть выполнено и независимо.
3.1.1. Реализованный алгоритм ДИН Алгоритм обрабатывает т.н. базовые и комбинированные регионы. Базовым регионом является либо базовый блок, либо гнездо циклов. Комбинированный регион – это объединение базовых регионов, имеющее один вход и один выход, при этом вход доминирует, а выход постдоминирует регион. Это определение предоставляет больше возможностей по созданию регионов, чем поиск по набору шаблонов графа потока управления, как предлагается в [[13]]. Тем не менее, существует ряд дополнительных ограничений на регионы. Вопервых, в первоначальной реализации не рассматривались регионы, содержащие вызовы функций, так как алгоритм был внутрипроцедурным (в текущей реализации это ограничение снято). Во-вторых, регионы с «нетипичным» потоком управления (например, несколько дуг пересекают границы цикла) не обрабатываются. В-третьих, небольшие регионы также исключаются из рассмотрения, так как затраты на переключение напряжения наверняка превысят возможный выигрыш на таком регионе. Алгоритм состоит из следующих основных шагов: • Построение базовых и комбинированных регионов для данной функции. 42
•
Профилирование времени выполнения, T(R,v), и количества раз, N(R), которое выполнился регион, для каждого базового региона на каждом доступном уровне напряжения. • Вычисление этих величин для комбинированных регионов. Время выполнения считается как сумма времен по всем базовым регионам, составляющим данный комбинированный регион; количество выполнений берется из базового региона, находящегося на входе в комбинированный. • Поиск такого региона, на котором понижение напряжения минимизирует энергопотребление системы во время выполнения программы, а сама программа замедляется не больше, чем на p%. Потребленная энергия оценивается по времени работы региона на данном уровне напряжения с учетом затрат на выполнение команд переключения напряжения. • Вставка команд изменения напряжения в начале и конце выбранного региона. Описанный алгоритм, как и многие другие алгоритмы ДИН, полагается на результаты профилирования программы. В нашей реализации для компилятора GCC используются уже имеющиеся в компиляторе механизмы, позволяющие профилировать количество выполнений базовых блоков и дуг графа потока управления. Дополнительно мы реализовали профилирование времен выполнения базовых блоков и циклов, входящих в комбинированные регионы (с помощью аппаратных счетчиков, если они есть в системе). Исходная реализация алгоритма рассматривает лишь регионы внутри одной функции и только для двух уровней напряжения, а также понижает напряжение только для одного региона из имеющихся, что существенно упрощает поиск необходимого минимума энергопотребления. Интерфейс переключения напряжения реализован через встроенные функции компилятора GCC (builtins) и системные вызовы ОС Linux. Тестирование реализации проводилось на пакете тестов Aburto и тестовой плате MV320. Из пакета предварительно было удалены тесты, калибрующиеся автоматически, так как они выполняют разный объем вычислений на разных частотах. В качестве базового использовался уровень оптимизации -O2. Из 196 функций, содержащихся в программах пакета Aburto, наша реализация алгоритма нашла 144 функции, которые подходят для динамического изменения напряжения. Для значения параметра p допустимого замедления программы от 10% до 40% было найдено от 3 до 14 подходящих регионов соответственно. При запуске оптимизированной версии время работы составило 8 минут, а потребленная энергия – 750 мВч. Неоптимизированные программы работали 7 минут 30 секунд, требуя 720 мВч. При этом потребление незагруженной системы составило 59 мВч за 45 секунд. Вычитая это потребление из обоих результатов, получаем, что при замедлении системы 43
на 6.6% сокращение потребления энергии только процессором составило 7%. Если же принять за ограничение времени работы системы 8 минут, то за это время неоптимизированные версии программ потребили бы 759.3 мВч, что соответствует сокращению потребления оптимизированной версией на 1.24%. В настоящий момент ведутся работы по реализации межпроцедурного алгоритма, в котором регионы могут содержать вызовы функций, а также вход и выход региона могут принадлежать разным функциям. Кроме того, разрабатывается эвристический алгоритм, понижающий напряжение на множестве регионов. По результатам предварительного тестирования, количество регионов, на которых происходит понижение напряжения, выросло в два раза, что позволяет предположить об увеличении эффективности алгоритма.
3.1.2. Оптимизация переключения битов (bit-switching) Переключение битов, происходящее на шинах команд и данных, ответственно за значительную долю потребляемой процессором энергии [[24]]. Переключение происходит тогда, когда процессором обрабатывается очередная команда. Если битовые кодирования последовательных команд отличаются в некоторых битах, то на переключение дорожек шины для этих битов тратится энергия. Оптимизация переключения битов заключается в такой организации команд и их кодировок, что переключения на шине случаются как можно реже. Мы исследовали вопрос о том, можно ли минимизировать переключения влиянием на порядок команд через планировщик команд компилятора. Вопервых, были выяснена верхняя оценка на количество энергии, которое можно сохранить через минимизацию переключения битов. Были подготовлены тесты, использующие команды с как можно более различающимся кодированием. Так, в битовой кодировке команд ands r6,r8,r0 и bicne r9,r7,#0x3FC только 3 из 32 битов одинаковы. Из двух тестовых программ, первая содержала цикл из 1000 команд: 500 команд первого типа, за которыми следовали 500 команд второго типа; вторая содержала цикл из 500 пар команд первого и второго типа. Оба цикла выполнялись достаточное количество раз для того, чтобы имелась возможность замерить энергопотребление. Эксперименты с выполнением этих двух тестах показали, что разница в энергопотреблении составляет 1-2% для одной тестовой платы и около 5% для второй платы. Учитывая, что энергопотребление процессора является лишь частью энергопотребления всей системы, можно было утверждать, что экономия в энергопотреблении процессора составила около 10%. Для минимизации переключения битов в компиляторе необходимо знать, как команда во внутреннем представлении компилятора будет закодирована в битовой форме. В случае компилятора GCC, результатом компиляции является ассемблерный листинг программы, а информации о кодировании команд нет, так как этим занимается ассемблер. Для преодоления этого 44
препятствия мы реализовали машинно-зависимую функцию (т.н. target hook), «предсказывающую» финальную кодировку команды во внутреннем представлении компилятора в той части, в которой это известно компилятору (то есть, за исключением вычисления адресов, неизвестных на этапе компиляции). При сравнении предсказанных кодировок с реально получившимися на ряде тестов обнаружилось практически полное совпадение, за исключением случаев, когда из данной команды во внутреннем представлении можно было сгенерировать несколько вариантов машинной команды, и в итоге был выбран менее вероятный вариант. С помощью полученной функции была реализована новая эвристика для планировщика команд GCC, которая дает предпочтение командам, образующим меньшее количество переключений битов с предыдущей запланированной командой. Эвристика использует параметр, изменяющийся от 0 до 32, который может рассматриваться как количество одинаковых битов на шине команд, которые увеличивают приоритет этой команды на 1. Так, если параметр установлен в 5, и планировщик выбирает между двумя командами с приоритетами 3 и 4, которые оцениваются как переключающие 7 и 22 бита на шине команд соответственно, то приоритет первой команды составит 3+(32-7)/5=8, а приоритет второй команды – 4+(32-22)/5=6, и будет выбрана первая команда вместо второй. При тестировании данной эвристики на пакете тестов Aburto максимальное сокращение переключений битов было зафиксировано на тесте sim и составило 7%, а в среднем – около 3%. К сожалению, этого недостаточно, чтобы значительно повлиять на энергопотребление. Возможно, одной из причин было то, что большое количество операций с плавающей точкой, реализованных через библиотечные вызовы, не позволяло достаточно точно предсказать кодирование этих операций. Аналогичные эксперименты с оптимизацией, комбинирующей несколько команд в одну, показали, что переключение битов меняется еще меньше, чем для планирования. Вообще говоря, видно, что для изменения энергопотребления на 1% необходимо изменить количество переключений битов как минимум на порядок больше, чего не получается достигнуть в рамках компилятора.
3.1.3. Оптимизация работы с памятью Подсистема работы с памятью является одной из самых потребляющих компонентов встраиваемых систем. Мы проанализировали ряд оптимизаций доступа к памяти, имеющихся в компиляторе GCC. Так, префетчинг данных поддерживается для некоторых реализаций процессора ARM через команду pld, и, в частности, поддерживается на тестовой плате OMAP2430. Тестирование реализации префетчинга массивов в циклах в компиляторе GCC версий 4.2 и 4.3 показало, что некоторые тесты ускоряются при использовании префетчинга, а некоторые замедляются – общая картина получается достаточно противоречивой, чтобы не рекомендовать использовать 45
префетчинг по умолчанию для компиляции программ для данной тестовой оптимизации, улучшающие платы. Другие машинно-независимые производительность и, как следствие, уменьшающие энергопотребление, не дают большого эффекта в текущих версиях GCC для архитектуры ARM (автоматическая векторизация, преобразования циклов). Мы предполагаем, что в будущем, с появлением в GCC инфраструктуры Graphite для оптимизации циклов [[11]], можно будет разрабатывать цикловые оптимизации, имеющие своей целью, в том числе, уменьшение энергопотребления. Кроме этого, известен ряд машинно-зависимых оптимизаций работы с памятью, направленных исключительно на энергопотребление. Например, скрэтч-память (scratch-pad memory) является по сути дополнительным кэшем, контролируемым компилятором. Использование такой памяти во встраиваемых системах позволяет экономить энергию, если память более эффективна, чем главная память, либо просто ускорять программу. К сожалению, в имеющихся у нас тестовых платах скрэтч-память присутствовала только в OMAP2430, и ее предназначение не позволяло использовать ее для этих целей. Оптимизация, отключающая неиспользуемые банки памяти, также возможна на тестовой плате OMAP2430, однако размер банка памяти в ней достаточно велик, и более разумным представляется распределять банки памяти по процессам в операционной системе вместо контроля распределения памяти компилятором.
4. Динамические оптимизации для языков общего назначения При компиляции программы необходимо учитывать конкретные наборы входных данных компилируемой программы и особенности аппаратуры, на которой она будет выполняться. Практика применения современных оптимизирующих компиляторов показывает, что это способно ускорить выполнение программы на десятки процентов. В современных компиляторах для языков общего назначения (Си/Си++) не существует приемлемого решения этих задач. Для учета наборов входных данных производится сбор профилей на заданном множестве наборов входных данных и учет полученной статистики. Отметим, что статистика на разных наборах данных может значительно отличаться, что в некоторых случаях приводит к замедлению программы. Такой подход связан со значительными накладными расходами на сбор профилей и подбор параметров компилятора. Параметры архитектуры целевой машины (размер кэша, соотношение между частотой памяти и процессора, наличие специальных векторных инструкций) влияют на оптимизации обращений к памяти (префетчинг, оптимизации локальности), векторизацию, встраивание функций, развертку циклов и др. В 46
настоящее время проблема учета деталей архитектуры решается только за счет генерации многочисленных версий кода программы (даже в рамках одной аппаратной платформы имеется десятки версий), что неудобно и приводит к дополнительным накладным расходам. Для оптимизации программы с учетом профиля пользователя планируется рассмотреть следующие подходы: 1. Динамическая оптимизация во время работы программы (JIT). Имеет то преимущество, что программа оптимизируется на конкретном наборе входных данных для данного конкретного запуска. Собранная статистика используется только для оптимизации данного запуска. Разные запуски программы могут приводить к различным оптимизациям. Необходим баланс между уровнями оптимизации «холодного» и «горячего» кода. JIT-оптимизации на языке Java, учитывающие профиль пользователя, подробно исследованы. Максимальный эффект в этом случае дают: оптимизация встраивания функций, развертка циклов, оптимизация обращений к памяти и распределение регистров. Эти оптимизации могут быть применены и в JIT-компиляторе для Си/Си++. Меньшая эффективность от этих оптимизаций из-за необходимости сложного анализа алиасов для Си/Си++ не уменьшает их актуальности. 2. Статическая оптимизация между запусками программы. Статистика накапливается между запусками, во время остановки программы выполняется оптимизация. Этот подход ближе к обычной оптимизации с учетом профиля программы, однако, не требует наличия JIT-компилятора. 3. Оптимизация выполняется динамически, однако данные статистики и принятые решения по оптимизации сохраняются между запусками. Позволяет уменьшить расходы на JIT-оптимизацию при условии того, что похожий набор данных уже встречался и был оптимизирован. Для оптимизации программы с учетом конкретной архитектуры пользователя будут рассмотрены следующие подходы: 1. Динамическая оптимизация во время работы программы, применяемая только к «горячим» участкам кода (аналогично пункту 1 для оптимизаций с учетом профиля). 2. Статическая оптимизация во время установки программы. Для этого требуется лишь распространение программы во внутреннем представлении, компилятор и компоновщик на стороне пользователя, а виртуальная машина и JIT-компилятор не требуются. Этот подход используется при развертывании .NET-программ (оптимизатор NGEN от Microsoft). В качестве основы для проведения работ мы выбрали систему LLVM (Low Level Virtual Machine) [[14]] с открытыми исходными кодами на языке Си++, 47
поддерживаемый компанией Apple. Все необходимые компоненты – внутреннее представление достаточно высокого уровня, компоновщик, виртуальная машина, JIT-компилятор – представлены или разрабатываются в рамках проекта LLVM. Из-за модульной организации и высокоуровневого языка реализации LLVM является популярным исследовательским компилятором. В LLVM была предложена концепция “lifelong optimization”, представляющая из себя компоненты для оптимизации программы на всем жизненном цикле ее существования, включая оптимизацию на машине пользователя. Кроме того, LLVM поддерживает межмодульные оптимизации и JIT-компиляцию, но не оптимизацию на стороне пользователя. В компании Apple реализован JIT-компилятор для OpenGL программ с помощью LLVM, позволивший отказаться от специализированного JIT-компилятора, использовавшегося до этого, и значительно улучшить производительность графических операций. Следовательно, ожидаемым результатом работ для нас является система на базе LLVM, функционирующая как на машине разработчика, так и на целевой машине, и использующая динамические оптимизации для учета конкретных входных данных пользователя и специализации под машину пользователя. Для выполнения этой цели по вышеперечисленным направлениям нами были выделены следующие работы: • исследование и разработка системы поддержки времени выполнения для LLVM, позволяющей осуществлять динамический мониторинг и профилирование работы программы – необходимо организовать интерпретацию программы во внутреннем представлении LLVM, динамическое малозатратное профилирование программы, сохранение результатов профилирования в промежуточных файлах; • исследование и разработка динамических оптимизаций, которые применимы к языкам общего назначения C/Си++, а также реализация выбранных оптимизаций c учетом профиля программы в JITкомпиляторе LLVM; • исследование и разработка подсистемы оптимизации программы во внутреннем представлении LLVM с учетом параметров целевой машины. • сравнительный анализ возможностей статического компилятора с возможностями JIT-компилятора LLVM с использованием пакета тестов SPEC CPU2006 и на реальных приложениях. Необходимо также отметить, что с развитием инфраструктуры для оптимизаций времени компоновки в компиляторе GCC часть разработанных технологий можно будет перенести в GCC – например, выполнять оптимизации на машине пользователя над внутренним представлением, сохраненным в объектных файлах. Выполнение этой работы позволит сделать доступным эти технологии для более широкого круга пользователей. 48
5. Заключение Мы выполнили краткий обзор части работ, которые проводятся по компиляторным технологиям для современных архитектур в Институте системного программирования РАН. Завершенные работы по оптимизациям для архитектуры Intel Itanium, проводившиеся в течение последних трех лет, привели к среднему ускорению тестов SPEC CPU FP 2000 около 10%. При этом большинство реализаций, в том числе новый планировщик команд и конвейеризатор циклов, были включены в официальную версию компилятора GCC. Наиболее важными для нас текущими работами являются разработка энергосберегающих оптимизаций для архитектуры ARM, выполняемая по контракту с компанией Samsung, и разработка методов динамической оптимизации для языков общего назначения. Первые результаты по энергосберегающим оптимизациям уже получены и позволяют утверждать, что динамическое изменение напряжения, управляемое компилятором, может быть полезным для встраиваемых систем на базе процессора ARM. В качестве основы для этих работ мы используем популярный компилятор GCC с открытыми исходными кодами, а также планируем использовать исследовательский компилятор LLVM. Литература [1] Arutyun Avetisyan, Andrey Belevantsev, and Dmitry Melnik. GCC instruction scheduler and software pipelining on the Itanium platform. 7th Workshop on Explicitly Parallel Instruction Computing Architectures and Compiler Technology (EPIC-7). Boston, MA, USA, April 2008. http://rogue.colorado.edu/EPIC7/avetisyan.pdf [2] Alfred Aburto's system benchmarks. ftp://gd.tuwien.ac.at/perf/benchmark/aburto [3] Andrey Belevantsev, Alexander Chernov, Maxim Kuvyrkov, Vladimir Makarov, Dmitry Melnik. Improving GCC instruction scheduling for Itanium. In Proceedings of GCC Developers' Summit 2005, Ottawa, Canada, June 2005, pp.1-14. [4] Andrey Belevantsev, Maxim Kuvyrkov, Vladimir Makarov, Dmitry Melnik, Dmitry Zhurikhin. An interblock VLIW-targeted instruction scheduler for GCC. In Proceedings of GCC Developers' Summit 2006, Ottawa, Canada, June 2006, pp.1-12. [5] А. Белеванцев, М. Кувырков, Д. Мельник. Использование параллелизма на уровне команд в компиляторе для Intel Itanium. Труды ИСП РАН, т.9, 2006, с.9-22. [6] Andrey Belevantsev, Maxim Kuvyrkov, Alexander Monakov, Dmitry Melnik, and Dmitry Zhurikhin. Implementing an instruction scheduler for GCC: progress, caveats, and evaluation. In Proceedings of GCC Developers’ Summit 2007, Ottawa, Canada, July 2007, pp. 7-21. [7] Andrey Belevantsev, Dmitry Melnik, and Arutyun Avetisyan. Improving a selective scheduling approach for GCC. GREPS: International Workshop on GCC for Research in Embedded and Parallel Systems, Brasov, Romania, September 2007. http://sysrun.haifa.il.ibm.com/hrl/greps2007/ [8] А.А.Белеванцев, С.С.Гайсарян, В.П.Иванников. Построение алгоритмов спекулятивных оптимизаций. Журнал Программирование, N3 2008, c. 21-42. [9] L. Benini and G. Micheli. System-level power optimization: Techniques and tools. ACM Transactions on Design Automation of Electronic Systems, 5:115–192, April 2000.
49
[10] GCC, GNU Compiler Collection. http://gcc.gnu.org [11] Graphite GCC framework. http://gcc.gnu.org/wiki/Graphite [12] K. Flautner, S. Reinhardt, T. Mudge. Automatic performance setting for dynamic voltage scaling. Proceedings of the 7th Annual international Conference on Mobile Computing and Networking, pp.260-271, 2001. [13] C. Hsu. Compiler-Directed Dynamic Voltage and Frequency Scaling for CPU Power and Energy Reduction. Doctoral Thesis, Rutgers University, 2003. [14] LLVM Compiler. http://llvm.net [15] Vladimir Makarov. The finite state automaton based pipeline hazard recognizer and instruction scheduler in GCC. In Proceedings of GCC Developers' Summit, Ottawa, Canada, June 2003. [16] MediaBench Test Suite. http://euler.slu.edu/~fritts/mediabench/ [17] Dmitry Melnik, Sergey Gaissaryan, Alexander Monakov, Dmitry Zhurikhin. An Approach for Data Propagation from Tree SSA to RTL. GREPS: International Workshop on GCC for Research in Embedded and Parallel Systems, Brasov, Romania, September 2007. [18] MiBench Test Suite. http://www.eecs.umich.edu/mibench/ [19] Soo-Mook Moon and Kemal Ebcioglu. Parallelizing Nonnumerical Code with Selective Scheduling and Software Pipelining. ACM TOPLAS, Vol 19, No. 6, pages 853-898, November 1997. [20] MV320 ARM Board. http://mvtool.co.kr/products/product.php?query=list&code=100101&lv=3&lang= [21] OMAP2430 Development Board. http://focus.ti.com/general/docs/wtbu/wtbugencontent.tsp?contentId=14645&navigation Id=12013&templateId=6123 [22] H. Saputra, M. Kandemir, N. Vijaykrishnan, M.J. Irwin, J. Hu, C.-H. Kremer. Energy conscious compilation based on voltage scaling. In ACM/SIGPLAN Joint Conference on Languages, Compilers, and Tools for Embedded Systems Software and Compilers for Embedded Systems, pp. 2-11, June 2002. [23] SPEC CPU 2000. http://spec.org/cpu2000/ [24] Ching-Long Su, Chi-Ying Tsui, and A.M. Despain. Low power architecture design and compilation techniques for high-performance processors. Compcon Spring ’94, Digest of Papers, pp.489-498, 1994. [25] V. Venkatachalam and M. Franz. Power reduction techniques for microprocessor systems. ACM Comput. Surv. 37, 3 (Sep. 2005), pp. 195-237.
50
Программная среда для динамического анализа бинарного кода В.А. Падарян, А.И. Гетьман, М.А. Соловьев {vartan, thorin, eyescream}@ispras.ru Аннотация. В данной работе рассматривается среда TrEx, позволяющая выполнять динамический анализ защищенного бинарного кода. Преследуемой целью является получение описания интересующего алгоритма. Среда реализует оригинальную методику анализа и предоставляет пользователю развитый набор программных средств, объединенных в рамках единого графического интерфейса. Подробно рассматриваются некоторые особенности среды, такие как аритектурнонезависимое API для работы средств анализа, возможности свертки вызовов функций, расширение пользовательского интерфейса скриптовым языком.
1. Введение В выполнении исследований программного обеспечения, оформленного в виде готового к работе бинарного кода, часто без исходных текстов, заинтересованы многие организации, решающие задачи сертификации ПО, а также проблемы отладки своих разработок или их совместимости с другими программами и системами. Всем им требуется проводить анализ бинарного кода различных программ, как по отдельности, так и в комплексе со всей средой исполнения компьютера, иногда вплоть до самого низкоуровневого кода операционной системы. Целью таких исследований является получение информации об особенностях реализации алгоритмов, восстановление реализации этих алгоритмов и их представление в понятном аналитику виде, восстановление протоколов обмена информацией и форматов данных, а также поиск «недокументированных» возможностей, ошибок и уязвимостей. В качестве примера рассмотрим ситуацию, когда требуется исследовать работу ПО, передающего данные по сети. Поскольку в работу вовлекается код не только самой программы, но и операционной системы и драйверов сетевых интерфейсов, недостаточно исследовать содержимое исполняемого файла программы. Требуется провести анализ всего стека сетевых протоколов, поскольку ошибки реализации и недокументированные возможности одних компонент могут влиять на работу и использоваться другими компонентами. Общий объём бинарного кода, который представляет для аналитика интерес, 51
может составлять десятки мегабайт. Решение этой задачи логично разбивается на решение ряда подзадач: 1. Поиск «точек зацепления», т.е. мест в системе, с которых целесообразно начинать исследование. 2. Раскрутка от точки зацепления назад (исследование кода, который привёл в точку) или вперёд (исследование кода, который работал после точки) с целью нахождения реализации алгоритма. 3. Поиск и анализ данных, влияющих на алгоритм по входу, и данных, образующихся по выходу алгоритма. 4. Восстановление алгоритмов, протоколов и форматов данных. 5. Выявление недекларированных возможностей, уязвимостей реализации. Важно отметить, что перечисленные подзадачи возникают при решении практически любых других задач, связанных с исследованием бинарного кода. Главным (и, возможно, единственным до сегодняшнего дня) эффективным методом решения таких задач является комбинация методов статического анализа (используется дизассемблер и декомпилятор) и «ручного» динамического анализа (используется отладчик и некоторые вспомогательные средства – дамперы, мониторы и т.д.). Статический анализ позволяет выполнять локальные исследования бинарного кода, к которому не применялись способы затруднения анализа. Из способов затруднения анализа отметим следующие: • использование свойств процессорной архитектуры фон Неймана, в которой исполняемый код и данные без наличия специальной информации не различимы (и, соответственно, задача их различения в таком случае является алгоритмически неразрешимой); • использование необходимости знания состояния и предыстории возникновения этого состояния вычислительной среды; • использование «размазывания» кода алгоритма по всему исполняемому модулю или даже нескольким модулям. • обычные методы затруднения статического анализа (например, переходы по вычисляемым адресам, смешанное кодирование инструкций, когда в одной инструкции может быть закодирована другая, которая может выполниться вместо основной при получении управления); • упаковка исполняемого кода; • обфускация (запутывающие преобразования); • виртуальные машины. Все перечисленные методы и особенно их комбинации при качественной реализации делают статический анализ совершенно неэффективным вследствие значительной трудоемкости, в результате чего аналитик вынужден применять динамические средства анализа (т.е. отладчик). Процесс отладки 52
более сложен и требует большей квалификации от аналитика, однако позволяет преодолевать некоторые проблемы, не решаемые в статике. Например, аналитик может посмотреть в отладчике, где в интересующий его момент функционирования системы находится код, а где данные, куда и в какой-то момент осуществляется переход по вычисляемому адресу, как выглядит распакованный код. Помимо того, аналитик может в некоторых случаях исследовать алгоритм распаковки, если этот алгоритм будет достаточно компактен и не защищен. Главной проблемой динамического анализа является то, что на данный момент нет доступных средств автоматизации труда, и практически все действия, выполняемые аналитиком, являются ручными операциями. Ситуацию усугубляет применение активных защит от динамического анализа, когда код содержит средства обнаружения работы под отладчиком и реагирования в виде неправильного функционирования, обфускация кода, использование виртуальных машин и многое другое. При правильной реализации такие методы защиты сводят к минимуму вероятность успеха исследования. Вычислительная мощь компьютеров растёт с каждым днём, и это позволяет реализовывать всё более сложные, комплексные и ресурсоёмкие алгоритмы усложнения и запутывания кода. Человеческих сил и возможностей уже недостаточно для анализа гигантских объёмов информации, содержащихся в исследуемых системах. Развитие средств запутывания алгоритмов, в том числе, и на аппаратном уровне, влечет невозможность решения задач восстановления алгоритмов существующими методами. Средства запутывания все более востребованы на рынке, и сейчас наблюдается активное их развитие и распространение. В данной работе описывается TrEx – программная среда динамического анализа бинарного кода. Возможности среды позволяют решать задачу восстановления алгоритма, преодолевая при этом комплекс средств защиты от анализа. Программные инструменты среды базируются анализе потоков данных в трассе выполнения программы и позволяют выполнять быстрое прототипирование специфических для каждого отдельного случая алгоритмов. Статья состоит из пяти разделов. Во втором разделе кратко описывается методика, на основе которой предлагается решать поставленную задачу. В третьем разделе описывается программная система, реализующая эту методику. В четвертом разделе рассматриваются дальнейшие пути развития программной системы. В последнем, пятом, разделе делаются итоговые заключения.
2. Методика анализа бинарного кода Среда TrEx реализует методику анализа, подробное рассмотрение которой можно найти в работе [1]. Здесь приводится краткое ее описание. 53
Исследуемая программа выполняется на симуляторе, обеспечивающем потактовую симуляцию инструкций, например, AMD SimNow [2], Virtutech Simics [3] и т.п. Аналитик добивается выполнения исследуемой функциональности на соответствующих начальных данных, и в это время осуществляется сохранение трассы. Трасса представляет собой непрерывную последовательность выполняемых на процессоре инструкций и снимки внутреннего состояния процессора при выполнении каждой инструкции. Таким образом, в трассе содержится значительный массив информации, описывающий все аспекты функционирования исследуемой программы (как заданных до начала трассировки, так и возникших в момент трассировки, например, пришедшие в систему сетевые пакеты). Трассировке практически не мешают никакие из существующих методов защиты исполняемого кода от анализа, потому что эти методы защищают только форму представления защищаемого кода, тогда как функциональность не может быть искажена. В частности, применение обфускации «диспетчер» [4] становится бесполезным, так как базовые блоки программы все равно должны выполняться в определенном порядке, и этот порядок непосредственно отражается в трассе. Поскольку трасса содержит все состояния процессора, некоторая часть записей в ней относится к выполнению других процессов и коду самой операционной системы. После того как из трассы выделены инструкции, относящиеся к исследуемой программе, ищется место ввода начальных данных соответствующего алгоритма или место вывода результата его работы. Фиксируются те ячейки памяти или регистры, в которых эти данные расположены. Далее из трассы выделяются только те инструкции, входных операндов которых достигли начальные данные (в случае фиксирования в трассе ввода) или работа которых повлияла на результирующие значения в выходе алгоритма (в случае, когда определялся выход алгоритма). Данный анализ не является статическим, он представляет собой post-mortem обработку отладочных данных, но преследуемые им цели (лучшее понимание программы) аналогичны целям программного слайсинга. В дальнейшем этот способ фильтрации шагов трассы будем называть слайсингом трассы. Полученный слайс программы содержит значительно меньшее количество инструкций и уже, как правило, является обозримым. Сокращение размеров трассы составляет, как правило, 3-4 порядка. Следующим этапом идет построение работоспособного ассемблерного листинга. Из элементов трассы извлекаются инструкции, упорядочиваются по их расположению в памяти, для константных переходов и адресов памяти генерируются метки, строятся пролог и эпилог, обеспечивающие размещение и выдачу начальных и результирующих данных. Следует отметить, что нерешенной на данный момент остается проблема построения листинга для самомодифицирующегося кода. 54
Ассемблерная программа рассматривается как самодостаточный контрольный пример, выполнение которого способно подтвердить корректность всех проведенных аналитиком операций, поскольку его выполнение должно выдавать те же результаты, что были получены при работе исходной программы. Перед передачей прикладному аналитику контрольный пример может быть подвергнут декомпиляции в язык высокого уровня.
3. Среда TrEx Общая схема устройства среды TrEx представлена на рис. 1. Пользователь взаимодействует со средой через графический интерфейс, предоставляя на входе трассу и получая на выходе ассемблерный листинг интересующего его алгоритма. Модули, принципиально обязанные учитывать специфику процессорной архитектуры, на которой выполняется программа, выделены жирным шрифтом. Эти компоненты позволяют располагать внутренним представлением, предоставляющим достаточное количество информации для работы средств анализа. При этом большинство из них не требуют специфических знаний об архитектуре. Даже если такие требования и возникают, например, в случае построителя листинга, происходит декомпозиция на базовый класс и набор производных классов, реализующих вспомогательные методы, где и расположено знание о семантике инструкций и выводных форматах ассемблера.
Рис. 1. Общая схема работы среды TrEx.
55
Далее будут рассмотрены некоторые особенности среды, такие как: аритектурнонезависимое API для работы средств анализа, возможности свертки вызовов функций, пользовательский интерфейс на основе скриптового языка.
3.1. Модель процессора общего назначения Одним из поставленных требований было не закладывать в основы среды привязки к какой-либо процессорной архитектуре. Добиться выполнения этого требования удалось благодаря разработке Common API – библиотеки, предоставляющий архитектурнонезависимый интерфейс к трассировочным данным. В нем можно выделить три раздела, обеспечивающих доступ к описанию ресурсов вычислительной платформы, данным, сохраненным в трассе, и результатам разбора машинных инструкций. Для описания регистрового файла, портов ввода-вывода и оперативной памяти машины в единообразном виде вводится модель адресных пространств. С точки зрения вводимой модели описываемая процессорная архитектура представляет собой набор адресных пространств, каждое из которых имеет свою разрядность адреса (рассматриваются разрядности в 8, 16, 32 и 64 бита). Элементами этих адресных пространств называются последовательные блоки, описываемые начальным адресом (с битовой частью) и длиной (также с битовой частью). Над элементами определены операции: • ElementsIntersect. Проверка, имеют ли два указанных элемента пересечение как соответствующие области своих адресных пространств. При этом предполагается, что элементы различных адресных пространств пересечений не имеют. • CompareElements. Сравнение элементов: для пары элементов e1; e2 указывается отношение между ними "<", ">" или "=". Конкретных требований к способу сравнения не предъявляется, однако данная операция обязана задавать полное отношение порядка на множестве элементов. Обе операции можно задать, ограничившись лишь знанием об адресах и размерах рассматриваемых элементов. Однако в общем случае из соображений производительности может потребоваться для некоторых часто используемых элементов (таких, как, например, регистры) использовать статически заданные или однократно заполняемые таблицы. С этой позиции оказывается удобным немного усложнить модель, введя понятие именованных элементов. Для каждого адресного пространства определяется множество (возможно, пустое) имен, с каждым из которых сопоставляется некоторый элемент этого адресного пространства. Элементы, имеющие имена, и будем называть именованными. Стоит отметить, что, несмотря на принципиальную возможность использования в рамках одного адресного пространства элементов с именами и без них, на практике такой необходимости не возникает. Причина заключается в том, что потребность в 56
присвоении имен обнаружилась только в отношении регистров, а для них на рассмотренных процессорных архитектурах не подразумевается произвольный доступ к части регистра. Для поддержки именованных элементов необходимо ввести операции их перечисления (EnumerateNamedElements) и получения описания элемента по его имени (GetNamedElementDescription). Предложенная модель позволяет единообразно описывать вершины графа зависимостей по данным, порождаемого выполнением инструкций трассы. В то же время сохраняется информация о виде элемента данных (регистр, порт ввода-вывода, память), которая может быть полезна при проведении анализа с более детальным учетом семантики инструкций трассы. Помимо того, предложенный подход позволяет описывать «вложенные» регистры, присутствующие в архитектуре INTEL IA-32. Модель является максимально простой. В частности, она в явном виде не предусматривает наличие нескольких наборов регистров (регистровые окна в SPARC или теневые наборы регистров в некоторых реализациях архитектуры MIPS) или нескольких параллельно существующих пространств виртуальной памяти. Однако поддержку подобных особенностей можно сделать на уровне декомпозиции инструкций на зависимости по данным. Так, для поддержки теневых наборов регистров достаточно указать в адресном пространстве регистров регистры из всех таких наборов, а при разборе инструкции отображать регистры в смысле MIPS на регистры модели из конкретного теневого набора. Описав архитектуру целевой машины в терминах адресных пространств, мы получаем возможность единообразно задавать всевозможные элементы данных, с которыми работает эта машина. Как было указано выше, применительно к системе TREX трасса состоит из шагов, для каждого из которых указана выполнявшаяся инструкция и значения некоторого подмножества регистров машины. Вместо того, чтобы предлагать единый формат трассы для всех архитектур, можно ограничиться парой операций, определенных над шагом трассы (их реализация уже будет зависеть от целевого процессора и выбранного формата хранения трассы): • GetInstruction. Сообщает, какая инструкция выполнялась на указанном шаге. Инструкция записана в бинарном виде (строка байтов) и требует декодирования. Кроме того, для процессорных архитектур с переменной дли-ной инструкции в силу особенностей проведения трассировки фактическая длина инструкции может также быть неизвестной до проведения декодирования. В последнем случае длина полагается равной максимально возможной для данного процессора (например, 15 для INTEL 64). • GetItem. По указанному элементу модели адресных пространств целевой архитектуры сообщает его значение на 57
данном шаге трассы перед выполнением соответствующей инструкции. Значение может оказаться неизвестным (например, значение запрошенного регистра вообще не трассировалось), в этом случае возвращается специальный признак «значение неизвестно». Легко видеть, что предлагаемый способ представления шагов трассы как интерфейс взаимодействия удовлетворяет требованиям: • адекватности и полноты – указанный способ представления шагов трассы позволяет получать всю необходимую для работы системы информацию; • минимальности – операции независимы и не выражаются друг через друга; • простоты – нельзя представить предложенные операции в виде более мелких. В качестве исходных данных для разбора инструкции выступают: 1. Сама эта инструкция в бинарном виде. Может быть получена из шага трассы (операция GetInstruction, описанная выше) или из отпечатка памяти. 2. Интерфейс для запроса значений регистров на момент выполнения инструкции. В случае работы с трассой этот интерфейс является прямым отображением на метод GetItem шага трассы, а в случае работы с памятью значения регистров полагаются неизвестными. 3. Информация о режиме работы процессора. Так как некоторые процессорные архитектуры (например, INTEL IA-32) способны функционировать в нескольких режимах, поведение инструкций в которых отличается (за счет различных механизмов отображения памяти, разрядности операндов и т. д.), необходимо явно указывать, в каком режиме работы выполнялась инструкция. Разметка шагов трассы по режимам сохраняется параллельно с трассой на этапе ее сбора. 4. Флаги декомпозиции на зависимости. На практике возникает необходимость исключать из рассмотрения при проведении слайсинга определенные категории зависимостей. Всего определено четыре таких категории. 4.1 ADDRESS. Зависимости по вычисленному адресу. Например, в инструкции INTEL IA-32 MOV EAX, [ECX+ESI*2-1] при включенном отслеживании адресных зависимостей необходимо во множество рассматриваемых элементов добавить регистры ECX, ESI и DS (сегментный регистр данных). 4.2 STACK. Зависимости по вычисленным адресам в стеке. При выключении этого флага адресные зависимости, соответствующие локальным переменным в стеке (в случае 58
INTEL IA-32 это те адреса, в которых фигурирует регистр SP/ESP) не будут генерироваться. 4.3 FLAG. Зависимости по флагам. Если эта опция выключена, флаги процессора при декомпозиции на зависимости не учитываются. 4.4 CF. Зависимости по управлению. Этот флаг определяет, следует ли учитывать при декомпозиции регистр или регистры, являющиеся счетчиком инструкций (для INTEL IA-32 это CS и IP/EIP). Флаги указываются аналитиком при проведении слайсинга. В совокупности указанные данные представляют собой задание на разбор, которое передается в компонент разборщика. Результатом являются блок информации об инструкции, в котором содержится в общем виде информация об инструкции и ее операндах; производится поверхностная классификация инструкций по признаку отношения к передаче управления и операндов по их типу: 1. Мнемоника инструкции. 2. Число операндов и информация о каждом из них в отдельности, включающая следующие пункты. 2.1 Классификация операнда в наиболее общем виде: константа; регистр или прямо указанный элемент памяти; элемент памяти, адресуемый косвенно. 2.2 Для операндов-констант предоставляется возможность запроса их значения. 2.3 Для операндов-регистров и ячеек памяти предоставляется возможность запроса соответствующих элементов в смысле модели адресных пространств. В случае косвенной адресации потребуется вычисление адреса, для чего используется переданная в задании ссылка на интерфейс запроса значений регистров. 3. Классификация инструкции по признаку отношения к передаче управления. Инструкции делятся на классы: инструкции вызова, инструкции возврата, инструкции безусловной передачи управления, инструкции условной передачи управления, остальные инструкции. Следует отметить, что знания мнемоники инструкции часто оказывается недостаточно для того, чтобы отнести ее к определенному классу. Так, например, инструкция безусловного перехода по адресу, записанному в регистре, JR в архитектуре MIPS должна быть отнесена в общем виде к инструкциям безусловной передачи управления. В то же время, ее форма JR $31, согласно принятым соглашениям, используется только для возврата из подпрограмм, так что в итоге принадлежность к определенному классу зависит от операнда инструкции. 4. Проверка, образуют ли две указанные инструкции пару «вызоввозврат». 59
Помимо того, требуется предоставлять информацию о зависимостях по данным, необходимую для работы алгоритма слайсинга. Декомпозиция инструкций производится на наборы троек 〈t, I, O〉, где t - тип зависимости, I множество входных элементов адресных пространств, а O - множество выходных элементов адресных пространств, фигурирующих в данной зависимости. Выделены следующие 5 типов зависимостей: 1. CHECK. Зависимость по управлению внутри инструкции (которая с точки зрения слайсинга все равно является зависимостью по данным). 2. GET. Зависимость по косвенному адресу (чтение из памяти). В качестве примера можно привести зависимость от регистров DS, ESI в инструкции LODSB. 3. KILL. Не являясь зависимостью как таковой, позволяет указать алгоритму слайсинга на тот факт, что во время выполнения инструкции некоторый элемент был уничтожен (т. е. его значение после выполнения инструкции непредсказуемо). Подобные ситуации характерны для флагов процессора INTEL IA-32. 4. SET. Зависимость по косвенному адресу (запись в память), например зависимость от ES, EDI в инструкции STOSB. 5. UPDATE. Все остальные зависимости по данным, не обладающие специальными свойствами. Для описания предлагаемым образом некоторых инструкций (например, инструкции обмена XCHG процессора INTEL IA-32) необходимо использовать дополнительные теневые регистры, для которых определяется специальное адресное пространство, автоматически добавляемое ко всем моделям адресных пространств конкретных процессорных архитектур. Данное адресное пространство включает в себя 8 теневых регистров (S0-S7), каждый из которых может использоваться как 128-, 64-, 32-, 16- или 8-битный. Кроме того, определен один теневой битовый регистр SU. После введения таких регистров инструкция XCHG распадается на три тройки: {〈U, {o0}, {S7}〉, 〈U, {o1}, {o0}〉, 〈U; {S7}, {o1}〉}, где o{0, 1} – операнды инструкции. Можно видеть, что такая запись удовлетворяет требованиям к исходным данным алгоритма слайсинга (т. к. является одной из возможных форм записи графа зависимостей по данным). Преимуществом перед графовым представлением является то, что можно работать с отдельными такими тройками, указывая, таким образом, какая именно из содержащихся в инструкции зависимостей по данным повлекла включение инструкции в слайс. В рамках описанного способа представления целевой процессорной архитектуры, для поддержки архитектур INTEL IA-32 и MIPS64 были 60
реализованы интерфейсы, соответствующие модели адресных пространств, подсистеме работы с трассой и разбору инструкций.
3.2. Свертка функций Вследствие большого количества инструкций в трассе, аналитик перед началом анализа некоторым образом разбивает трассу на блоки (примером такого блока может служить вызов функции), а затем анализирует их по очереди. Таким образом, после завершения анализа некоторого блока и его описания, аналитику, как правило, не требуется работать с инструкциями этого блока. Возникает задача скрыть от аналитика проанализированные и описанные блоки кода для упрощения анализа других блоков. Для этого добавлена возможность создания визуальных свёрток. Этот механизм аналогичен свёрткам в визуальных средах разработки высокоуровневых языков программирования, например в редакторе MS Visual Studio для каждой функции существует возможность скрыть её тело, если оно в данный момент пользователя не интересует. В трассах это позволяет пользователю скрывать проанализированные фрагменты кода. Свёртка представляет из себя именованную блок последовательных инструкций. При создании свёртки (может происходить вручную или автоматически, как результат работы алгоритмов анализа структуры трассы) указывается имя свёртки и её границы. Свёртка может находиться в 2х состояниях: • свёрнутом – при этом отображается значок «+» и имя свёртки, заданное при создании. • развёрнутом – при этом отображается значок «-» на начальной строке блока и все инструкции, входящие в свёртку. Свёртки могут быть вложенными, но не могут пересекаться. При переходе на заданный шаг в трассе, в случае если он находится внутри свёртки и скрыт от пользователя, происходит автоматическое разворачивание этой свёртки, а также всех других свёрток, в которые она вложена. Класс свёртки CVisualFurl представляет собой структуру из трёх полей: имя, начальный и конечный шаги свёртки и реализует методы для доступа к их значениям. Класс FurlManager инкапсулирует всю работу со свёртками, отвечает за их сохранение и загрузку из файла. Так как объём трасс достаточно велик, хранить текстовое представление трассы невыгодно, поэтому при необходимости отобразить некоторый шаг трассы компонент отображения должен заново получать его текстовое представление. Компонент отображения обращается с запросами к FurlManager, для того чтобы правильно сгенерировать очередную строку в трассе Опишем основные методы класса FurlManager, а ниже вкратце опишем его взаимодействие с компонентом отображения. Основные методы класса FurlManager: 61
•
bool isDirty() – возвращает состояние менеджера, изменялось состояние свёрток с последнего сохранения (требуется повторное сохранение) или нет; • unsigned __int64 getStepsCount() – возвращает количество видимых шагов в трассе; • bool addFurl(CVisualFurl furl) – добавить заданную свёртку; • void delFurl(TracePosition visualPos) – удалить свёртку с началом в заданном шаге • bool expandFurl(TracePosition visualPos) – свернуть/развернуть свёртку (изменить состояние) заданной начальной по-зицией; • bool getFurl(TracePosition visualPos, FURL_INFO* result) – получить(если есть) свёртку по указанной начальной позиции; • bool ensureVisible(TracePosition visualPos) – развернуть все свёртки в которых лежит данный шаг трассы; • void saveFurls(CString fileName) – сохранить свёртки в заданный файл; • void loadFurls(CString fileName) – загрузить свёртки из заданного файла. Чтобы отобразить очередной шаг трассы, нужно знать начинается ли на этом шаге свёртка, и если начинается, то в каком состоянии она находится – свёрнутом или развёрнутом. Для этого используется метод getFurl. В зависимости от результатов запросов очередная строка может быть отображена как: • просто текстовое представление текущей инструкции (свёртки на этом шаге нет) • текстовое представление текущей инструкции, со значком «-» (свёртка есть, и она находится в развёрнутом состоянии) • имя свёртки со значком «+»«-»(свёртка есть и она находится в свёрнутом состоянии) Кроме того, нужно знать точную длину видимой трассы, с учётом свёрнутых участков. Для этого используется метод getStepsCount. При переходе в заданный пользователем шаг нужно убедиться, что он в данный момент виден. Для этого используется метод ensureVisible. Если пользователь выполнил двойной клик по начальной инструкции свёртки нужно изменить её состояние. Для этого используется метод expandFurl. При загрузке трассы выполняется также и загрузка информации о свёртках – методом loadFurls.А после окончания работы с трассой, в слу-чае если состояние компонента FurlManager изменилось (появились новые свёртки или старые были удалены – метод isDirty возвращает истину) информация о свёртках сохраняется в файл с помощью метода saveFurls. В среде TrEx имеется возможность выполнять свертки не только сугубо в рамках графического интерфейса, но и на уровне шагов трассы, когда 62
последовательность инструкций заменяется т.н. псевдоинструкцией – шаги объединены в один элемент, но с сохранением информации о зависимостях по данным. Таким образом, в псевдоинструкции сохраняется, какие данные являются для этого блока входными (читаются в данном блоке и участвуют в преобразованиях), а какие являются выходными (их значения меняются в данном блоке) и как они между собой связаны. В частности это требуется для ускорения работы алгоритмов слайсинга, так как позволит, проанализировав зависимости некоторого блока один раз и сохранив их, при следующем проходе слайсинга пропускать блок, повторно используя уже построенные зависимости. При построении псевдоинструкции связи по данным внутри блока переводятся в представление графа зависимостей. Граф зависимостей представляет собой направленный двудольный граф, где одно множество вершин – это ячейки, содержащие значения, а второе множество – инструкции (шаги трассы), которые читают или пишут значения в эти ячейки. Направление ребра определяется тем, пишет инструкция в ячейку или читает из неё. Если ячейка читается – ребро направлено от ячейки к инструкции, если пишется, то ребро направлено от инструкции к ячейке. Задача построения свёртки функций по графу зависимостей состоит в трансформации этого графа в другой направленный двудольный граф с атрибутированными рёбрами, который и назовём свёрткой. Дадим его определение. Назовём свёрткой такой двудольный граф, в котором: • Одно множество состоит из входных ячеек (вершин) исходного графа зависимостей, то есть таких, для которых нет входящие рёбер в графе зависимостей. • Второе множество состоит из выходных ячеек (вершин) исходного графа зависимостей, то есть таких, для которых существуют входящие рёбра в графе зависимостей. • Рёбра направлены строго из первого множества во второе. • Атрибут на ребре обозначают тип зависимости между входной и выходной ячейкой, которые соединяет ребро. Выделим несколько видов зависимостей между ячейками. • Простая связь по данным – значение ячейки A во входной точке блока влияет на значение ячейки B в выходной точке блока. Таким зависимостям соответствуют любые инструкции перемещения значения из одной ячейки в другую (например MOVE) и преобразования ячейки (например ADD) • Косвенная связь по данным – адрес ячейки B в выходной точке блока зависит от значения ячейки A во входной точке блока; Таким зависимостям соответствуют операции с указателями, при которых адрес ячейки вычисляется на основании значения другой ячейки, например при работе с массивом: a[i] – адрес 63
искомой ячейки памяти зависит от значения индекса i. Рассмотрим подробнее преобразование «свёртка». Фактически требуется для всех входных и выходных ячеек, между которыми существует путь по рёбрам графа зависимостей добавить ребро в граф свёртки. Однако возникает проблема задания атрибута этого ребра. Таким образом, требуется: 1. Для каждой пары ячеек, одна из которых является входной, а другая выходной в графе зависимостей, найти все пути между ними. 2. Для каждого найденного пути на основе встречающихся в нём инструкций сгенерировать атрибут типа зависимости между входной и выходной ячейкой. 3. На основании же имеющегося атрибута, полученного при анализе прошлых путей и вновь созданного атрибута получить итоговый атрибут типа зависимости. Алгоритм поиска путей в графе является стандартным, поэтому остановимся подробнее на 2 и 3 пункте. Рассмотрим различные виды путей типы атрибутов, им соответствующие. • Путь из простых зависимостей. То есть входная переменная x участвовала в некоторых вычислениях, результатом которых явилось значение выходной переменной y. В этом случае атрибут, полученный на основании всего пути, соответствует простой зависимости y(x). • Путь из простых зависимостей, в котором присутствует одна или несколько косвенных зависимостей. В этом случае атрибут, полученный на основании всего пути, соответствует косвенной зависимости y(x). Из вышесказанного следует, что приоритет выставления атрибутов при анализе зависимостей следующий: • Косвенная зависимость. • Простая зависимость. Рассмотрим теперь процесс «слияния» атрибутов, полученных при анализе разных путей от входной ячейки к выходной. Вначале рассмотрим пример. Пусть a – текущая выходная ячейка блока, b – текущая входная ячейка блока, а c и d – некоторые другие выходные ячейки блока. Пусть между ячейками с и b есть простая зависимость, а между ячейками d и b – зависимость косвенная. Пусть есть инструкция, реализующая простую зависимость a(c,d). Требуется определить атрибут зависимости a(b). Видно, что существует 2 пути из b в a: b – c – a и b – d – a. Причём первому пути соответствует атрибут простой зависимости, а второму – атрибут косвенной зависимости. В этом случае итоговый атрибут будет соответствовать «объединению» этих двух типов зависимости. Очевидно, что в случае объединения атрибутов одного типа, итоговый атрибут будет равен каждому из исходных. Следовательно, атрибуты на рёбрах могут принимать три значения: 64
• атрибут простой зависимости • атрибут косвенной зависимости • атрибут объединения простой и косвенной зависимости. Предлагается хранить зависимости выходных ячеек от входных в виде битовых масок, где отдельные биты соответствуют типам зависимостей. А объединённым зависимостям соответствуют группы выставленных битов. Это позволит эффективно добавлять новые типы зависимостей, не меняя модели. В случае представления значения атрибута, как битовой маски, объединение атрибутов представляет собой просто выполнение побитового "или" над их масками. Алгоритм построения свёрток зависимостей по заданному блоку инструкций реализован в виде компонента FurlMaker. Промежуточные результаты алгоритма в процессе его работы хранятся в специальном компоненте, DependencyContainerTree оптимизированном по скорости доступа с учётом специфики алгоритма. Окончательные результаты хранятся в компоненте CDependencyFurl, который аналогичен компоненту визуальных свёрток, но дополнительно хранит отображение входных элементов на выходные и наоборот с учётом видов зависимостей. Основные методы алгоритма FurlMaker: • analyzeMinstr() – проанализировать текущую микроинструкцию; • removeLocals() – убрать из зависимостей временные ячейки (используются для промежуточного хранения значений); • fillFurl() – Заполнение свёртки найденными зависимостями; • addDependency() – Функция добавления зависимости; Для визуализации свёрток зависимостей была использована сторонняя библиотека Microsoft GLEE. Так как библиотека была написана на С#, то для её использования в C++ проекте был написан специальный переходник с использованием промежуточного языка Managed C++. Класс DependencyExtractor осуществляет извлечение требуемых зависимостей из компонента CDependencyFurl. Необходимость этого класса обусловлена тем, что для больших свёрток количество зависимостей может быть велико и для улучшения восприятия пользователь может выбрать, как ячейки, зависимости между которыми его интересуют, так и виды зависимостей.
3.3. Связывание интерфейсов среды TrEx со скриптовым языком В качестве инструментального языка, при разработке среды TrEx был использован язык С++, что позволило добиться в критических местах кода 65
должного уровня производительности. Однако с точки зрения пользователя среды она является «монолитом», обладающим фиксированным набором возможностей, и предоставляющим их средствами графического интерфейса. Разработка и встраивание собственных модулей-расширений способна решить эту проблему, однако такой подход сопряжен со значительными временными задержками, не приемлемыми для пользователя. Подходящим решением в данной ситуации является встраивание в среду интерпретатора скриптового языка, обеспеченного привязкой к методам и классам Common API. В качестве кандидата на встраивание рассматривались следующие языки: Lua, Ch, Java/Javascript, Perl, Ruby. Были сформулированы следующие обязательные требования, которым должен удовлетворять скриптовый язык и его окружение. • Возможность удобной работы с классами языка C++. • Поддержка шаблонов. • Перехват исключительных ситуаций. • Наличие средств автоматизации для построения привязки. В результате рассмотрения была выбрана связка Lua/SWIG. Ключевыми достоинствами выбранного решения является: открытый код, наличие сообщества, занимающегося развитием этих программных средств, малые накладные расходы на встраивание при использовании генератора кода привязки SWIG. Возможность выполнения Lua-скриптов обеспечивается скриптовой консолью, совмещенной с окном вывода информационных сообщений среды. Применяемый интерпретатор Lua, по сравнению со стандартным, обладает следующими отличиями. 1. Тип number, соответствующий в стандартном Lua C-типу double, в TrEx является 64-битным знаковым целым. 2. Формат ввода чисел соответствует стандартному: число «10» воспринимается как десятичное 10, число «0x10» как десятичное 16, число «010» как восьмеричное 8. Формат вывода чисел всегда шестнадцатеричный, без префикса «0x». 3. Операция возведения в степень в силу слабой применимости для целых чисел заменена на «исключающее или». 4. Набор стандартных библиотек сохранен без изменений, за исключением библиотеки math, которая отключена по причине переопределения типа number. При открытии трассы в ее контексте автоматически выполняется скрипт autoload.lua, расположенный в каталоге TrEx. Предоставляемый скрипт создает окружение, позволяющее получить доступ к элементам трассы в более простом виде, нежели при использовании C++-интерфейсов напрямую. В стандартном окружении определен набор таблиц, используемых для вводавывода и работы с моделью и трассой. 66
Таблица TrEx.Util содержит следующие два поля. • TrEx.Util.loggerInstance хранит ссылку на объект Logger, позволяющий выводить сообщения в консоль и файл журнала. • TrEx.Util.traceContext хранит ссылку на объект TraceContext открытой трассы. Таблица TrEx.Model используется для работы с регистровым файлом архитектуры, соответствующей открытой трассе. По ключу, соответствующему имени регистра (в нижнерегистровом или верхнерегистровом написании) или его числовому коду таблица позволяет получить описание регистра в виде структуры NamedElement. Таблица TrEx.Trace содержит механизмы для работы с шагами трассы. Помимо получения количества шагов в трассе по ключу n, она позволяет обратиться к отдельному шагу по ключу, соответствующему его порядковому номеру. Помимо того, через ключ TrEx.Trace.position доступен номер выбранного пользователем шага. Для доступа к декомпозиции на зависимости используется поле Dep объекта шага трассы, например TrEx.Trace[0].Dep. Данная таблица содержит количество зависимостей в ключе n, а также сами зависимости в виде объектов класса Dep по числовым ключам с 0 до n - 1. Разбор на зависимости управляется флагами, содержащимися в TrEx.Trace.depFlags. В начальном состоянии флаги устанавливаются в значение 15, соответствующее наиболее полному набору флагов. Рассмотрим пример использования TrEx.Trace.Dep: выводим все зависимости инструкции в шаге stepIndex (Рис. 2). Таблица TrEx.Notepad предоставляет расширенные возможности вывода для скриптов с использованием окон «блокнотов». В каждом из таких блокнотов может содержаться список шагов и сопоставленной им текстовой информации с возможностью перехода к соответствующему шагу в трассе по двойному клику. Для создания нового или доступа к уже существующему блокноту применяется индексированный доступ к таблице по имени блокнота (имя может являться произвольной строкой). Пример работы с блокнотом показан на Рис. 3. local dumpDeps = function(stepIndex) local i
print(i, TrEx.Trace[stepIndex].Dep[i]) end end Рис. 2. Работа с зависимостями в Lua-скрипте. for i = 0, TrEx.Trace.n - 1 do -- Проверяем eax. if 0 == TrEx.Trace[i].eax then -- Добавляем в блокнот "Zero EAX". TrEx.Notepad["Zero EAX"]:print(i, "На этом шаге eax == 0") end -- Проверяем ebx. if 0 == TrEx.Trace[i].ebx then -- Добавляем в блокнот "Zero EBX". TrEx.Notepad["Zero EBX"]:print(i, "На этом шаге ebx == 0") end -- Если на каком-то шаге eax и ebx равны нулю одновременно, добавляем текст в оба блокнота -- и выделяем его красным (0x0000FF) цветом. if (0 == TrEx.Trace[i].eax) and (0 == TrEx.Trace[i].ebx) then -- Добавляем в оба блокнота красный текст. TrEx.Notepad["Zero EAX"]:cprint(i, "Причем ebx тоже 0!", 0x0000FF) TrEx.Notepad["Zero EBX"]:cprint(i, "Причем eax тоже 0!", 0x0000FF) end end Рис. 3. Пример использования TrEx.Trace и TrEx.Notepad для поиска шагов, где eax == 0 или ebx == 0. В блокноте, таким образом, определены два метода: print(stepIndex, message) и cprint(stepIndex, message, color). В качестве color могут использоваться произвольные RGB-цвета.
4. Дальнейшие работы
-- Цикл по зависимостям. for i = 0, TrEx.Trace[stepIndex].Dep.n - 1 do -- Выводим номер зависимости и ее текстовый вид.
На текущий момент среда TrEx предоставляет аналитику набор средств, позволяющий восстанавливать алгоритм в виде ассемблерного листинга, 67
68
нуждающегося в дальнейшей доработке. Среда может получить развитие не только за счет улучшения качества работы уже существующих инструментов, но и решения других, смежных задач, о которых говорилось ранее. К числу таких задач следует отнести декомпиляцию в язык высокого уровня и поиск уязвимостей в бинарном коде. Задача декомпиляции является естественным продолжением в поднятии абстракции при работе с низкоуровневым представлением программы. Ассемблерный листинг является нижней границей среди возможных статических представлений исследуемой программы. Верхней границей является представление в виде математической модели соответствующей проблемной области. Однако, даже задачу декомпиляция в традиционный язык программирования, такой как C, нельзя считать решенной. Известные декомпиляторы (Boomerang [5], DCC [6], REC [7] и Hex-Rays [8]) не всегда корректно восстанавливают структурные конструкции, языковые типы, затрудняются в распознавании библиотечных функций (за исключением HexRays). Декомпиляция оптимизированной программы также нерешена – перечисленные декомпиляторы выдают результат, который либо не компилируется, либо работает некорректно, либо содержит ассемблерные вставки. Разрабатываемый в ИСП РАН декомпилятор [9] в язык C, обладает развитыми возможностями в восстановлении типов. В ближайшее время стоит задача адаптация этого декомпилятора к среде TrEx, когда восстановление происходит не из исполняемого файла, а из внутреннего представления среды. Важной особенностью является то, что рассматриваемая программа не всегда разрабатывалась на языке C, потенциально возможно появление в трассе кода, написанного на языке ассемблера. Помимо того, исследуемая программа может содержать в себе результаты различных преобразований как с целью оптимизации, так и обфускации. Еще одна смежная задача – выявление уязвимостей в бинарном коде. На протяжении ряда лет в ИСП РАН проводились работы, целью которых было выявление уязвимостей в исходном коде программ [10, 11]. Система выполняет поиск дефектов (ситуаций в исходном коде) в программах, написанных на Си и Си++, при помощи межпроцедурного анализа потока данных. Анализ выполняется по частям, что позволяет искать дефекты в программах неограниченного размера, а также в программах, полный набор исходного кода которых недоступен. Для обнаружения конкретных видов дефектов используются шаблоны ситуаций, в которых эти дефекты могут проявляться. Применение соответствующих методик позволит выявлять уязвимости в бинарном коде, т.е. в тех ситуациях, когда исходный код недоступен или в рассмотрение попадает код не только самой программы, но и операционной системы, в том числе и драйверов устройств. Основным источником данных для восстановления алгоритма является трасса, захватывающая все инструкции выполненные на процессоре. Трасса может 69
быть получена программной трассировкой, потактовой трассировкой на программном симуляторе, трассировкой на спец. оборудовании (например, для x86 это означает модификацию микрокодовой прошивки процессора), через гипервизор уровня аппаратной виртуализации. Последнее решение представляется наиболее перспективным, т.к. обладает рядом преимуществ перед остальными: (1) технология специально разрабатывалась для сокрытия (хоть и с другими целями) контролируемого выполнения и является стандартным решением для платформы x86, (2) потенциально низкие накладные расходы предположительно позволят не выходить за три порядка замедления, что в свою очередь даст возможность обойти распределенную антиотладочную защиту. Таким образом, трассировщик, построены на основе этой технологии будет обладать требуемым уровнем скрытности и в тоже время иметь качественно меньшие накладные расходы по сравнению с трассировщиком программного симулятора. Перечисленные выше три задачи составят набор самодостаточных работ, значительно расширяющих возможности системы. Помимо них будут расширяться уже существующие инструменты, меняться архитектура графической части среды для выхода на достаточный уровень модульности. Одним из направлений работ будет восстановление формата данных. Эта задача хоть и достаточно близка с некоторыми аспектами декомпиляции (восстановлением типов данных), уходит скорее в сторону восстановления протоколов, когда активно используется информация о содержимом буферов памяти, недоступная в случае статического анализа. Однако предлагаемый в данной работе подход к анализу программ не следует рассматривать как противовес статическому анализу. Напротив, восстановление алгоритма, осуществляемое в рамках среды TrEx, следует рассматривать как «мост» ведущий к статическому анализу. Ключевым моментом в используемой методике анализа является то, что исходные данные для анализа имеют динамическую природу, в силу принципиальных технических причин, описанных в начале статьи. Однако, статическое представление программы (ее код) обладает неоспоримыми преимуществами по сравнению с динамическим представлением (трасса). К числу таких преимуществ следует отнести обобщение свойств программы, тогда как трасса представляет только один конкретный сценарий, и то, что при разработке и анализе естественным для человека представлением является статическое. Таким образом, сформированное в рамках среды TrEx статическое представление программы должно передаваться далее соответствующей среде статического анализа. В случае, когда выходом TrEx является ассемблерный листинг, такой средой на безальтернативной основе становится уже упоминавшаяся среда Ida PRO. Работы по интеграции двух сред планируется начать уже в этом году. 70
Еще одним направлением развития среды TrEx будет пользовательский интерфейс, для которого ставятся задачи как количественные, так и качественные. В первом случае разработка сталкивается с трудностями, когда стандартные компоненты отображения не способны работать с тем объемом данных, что содержится в трассе. Эти задачи нельзя назвать принципиальными, но они также как и другие требуют ресурсов на свое решение. Второй тип задач требует принципиального решения как удобней и эффективней для аналитика представлять результаты работы того или иного программного инструмента.
5. Заключение Применение среды позволяет существенно сократить временные ресурсы, требуемые на получение описания алгоритма. Рассмотрим модельный пример, в рамках которого вычисляется следующая функция (Рис. 4).
Литература
Function test_func(ByVal n As Integer) As Integer If n = 0 Then test_func = 0 Else test_func = n + test_func(n - 1) End Function Рис. 4. Рекурсивное вычисление суммы арифметической прогрессии. В первом случае программа – vb6 – реализована на VisualBasic версии 6.0 и скомпилирована в виде p-кода (кода виртуальной машины). Вторая программа – vbnet – реализована на VisualBasic .NET и скомпилирована в виде Managedкода. Трасса снималась для n = 10. Ее размер составил порядка 700 МБ. В таблице 1 представлены результаты работы среды, позволившие уменьшить размер рассматриваемого кода на два-три порядка и довести его до приемлемого для аналитика значения (несколько сотен инструкций). Таким образом, можно заключить, что создана система анализа, реализующая базовый набор программных средств, необходимых аналитику. Система успешно используется для решения ряда практических задач. Использование системы существенно (на порядки) сокращает время исследования. Трасса, содержащая код работы анализируемой программы в пользовательском режиме.
vb6 vbnet
Размер, МБ
Число шагов
Инструкций в листинге
42 35
575 392 484 248
26 620 62 726
К числу основных возможностей среды TrEx следует отнести следующее. Разработана и реализована инфраструктура, позволяющая работать с трассой в рамках архитектурнонезависимых алгоритмов. Поддержаны архитектуры Intel64 и MIPS64. Разработаны и реализованы свертки блоков инструкций как исключительно в рамках графического интерфейса пользователя, так и с учетом зависимостей по данным. Разработан и реализован метод сигнатурного поиска библиотечных функций в трассах программ. Исследована возможность сжатия трассы программы, разработаны и реализованы соответствующие методы сжатия трассы. Определены направления ближайших исследований для дальнейшего развития методики анализа и реализации системы. В частности, в следующее три года будут начаты работы связанные с декомпиляцией в язык высокого уровня, выявлением ошибок и слабостей в бинарном коде, трассировка средствами аппаратной виртуализации платформы Intel 64.
Инструкций в слайсе.
[1] Тихонов А.Ю., Аветисян А.И., Падарян В.А. Извлечение алгоритма из бинарного кода на основе динамического анализа. // Труды XVII общероссийской научнотехнической конференции «Методы и технические средства обеспечения безопасности информации». Санкт-Петербург, 07-11 июля 2008 г. Стр. 109. [2] AMD SimNow Simullator. http://developer.amd.com/cpu/simnow/Pages/default.aspx [3] P. S. Magnusson, M. Christensson, J. Eskilson, D. Forsgren, G. Hallberg, J. Hogberg, F. Larsson, A. Moestedt, and B. Werner. Simics: A Full System Simulation Platform. // IEEE Computer, 35(2):50–58, Feb. 2002. [4] Wang C., Hill J., Knight J,. Davidson J. Software tamper resistance: obstructing static analysis of programs // Tech. Rep., N 12, Dep. of Comp. Sci., Univ. of Virginia, 2000. [5] Boomerang Decompiler Home Page. http://boomerang.sourceforge.net/ [6] DCC Decompiler Home Page. http://www.itee.uq.edu.au/~cristina/dcc.html [7] REC Decompiler Home Page. http://www.backerstreet.com/rec/ [8] Hex-Rays Decompiler SDK. http://www.hex-rays.com/ [9] K. Dolgova and A. Chernov. Automatic Type Reconstruction in Disassembled C Programs. // Proceedings of the 2008 15th Working Conference on Reverse Engineering. Pp. 202—206. [10] В.С.Несов, О.Р.Маликов. Автоматический поиск уязвимостей в больших программах. // Известия ТРТУ. Тематический выпуск «Информационная безопасность». №7 vol. 6. Таганрог: Изд-во ТРТУ 2006. Стр. 38—44. [11] Несов В.С., Гайсарян С.С. Автоматическое обнаружение дефектов в исходном коде программ. // Труды XVII общероссийской научно-технической конференции «Методы и технические средства обеспечения безопасности информации». СанктПетербург, 07-11 июля 2008. с. 107.
356 143
Таблица 1. Результаты работы среды TrEx. 71
72
Перспективы интеграции методов верификации программного обеспечения В. В. Кулямин [email protected] Аннотация. В статье предлагается подход к построению расширяемой среды верификации программных систем, которая, по мнению автора, поможет решить проблемы практической применимости современных строгих методов верификации к практически значимым программам, сложность которых все время растет. Она же может стать аналогом испытательного стенда для апробации и отладки большого числа новых предлагаемых техник формальных верификации и статического анализа на разнообразном промышленном программном обеспечении.
1. Введение Прогресс технологий разработки программного обеспечения (ПО) в последние десятилетия значительно увеличил производительность программистов в терминах количества кода, создаваемого ими в единицу времени. Это проявляется, в частности, в увеличении размеров наиболее сложных программных систем, разрабатываемых сейчас, до десятков миллионов строк кода [1,2]. Однако качество программ при этом заметным образом не изменилось — среднее количество ошибок на тысячу строк кода, еще не прошедшего тестирование, по-прежнему колеблется в пределах 10-50 [3]. Таким образом, совершенствование методов разработки ПО, давая возможность создавать все более сложные системы, необходимые современной экономике, науке и государственным организациям, парадоксальным образом лишь увеличивает количество дефектов в них и связанные с этим риски. Борьба с дефектами и ошибками в программном обеспечении ведется при помощи его верификации. В ходе ее выполнения проверяется взаимная согласованность всех артефактов разработки — проектной и пользовательской документации, исходного кода, конфигураций развертывания, — а также их соответствие требованиям к данной системе и нормам применимых к ней стандартов. Методы верификации ПО также активно развиваются, однако их прогресс менее заметен. Поэтому предельная сложность ПО, которое можно сделать надежно и корректно работающим, существенно меньше сложности систем, востребованных современным обществом. 73
Различные методы проведения верификации ПО можно (больше по историческим, чем содержательным причинам) разделить [4] на формальные методы, использующие строгий анализ математических моделей проверяемых артефактов и требуемых свойств; методы статического анализа, в ходе которых возможные ошибки ищутся без исполнения проверяемого ПО; методы динамического анализа, проводящие проверку реального поведения проверяемой системы в рамках некоторых сценариев ее работы; и экспертизу (review, inspection), выполняемую людьми на основе их знаний и опыта. Все эти методы имеют разные достоинства и недостатки, различные области применимости, и эффективность их применения сильно отличается в разных контекстах. Но полноценная верификация крупномасштабных сложных систем невозможна без совместного использования всех этих методов, поскольку только их сочетание позволяет преодолеть недостатки каждого. При этом на каждом уровне рассмотрения системы и для каждого вида компонентов хотелось бы выбирать самый эффективный метод, дающий наиболее достоверный вклад в оценку качества системы в целом и требующий минимальных затрат. К сожалению, пока не существует общего подхода, позволяющего сопоставлять и сравнивать различные методы верификации и их сочетания в различных контекстах при применении к реальным программным системам. Чтобы справиться со все возрастающей сложностью реальных систем, исследователями за последние 20-30 лет создано огромное количество разнообразных методов и техник верификации [4], особенно в рамках статического анализа и формальных методов. Но для их эффективного использования чаще всего нужно быть специалистом в соответствующей области. Многие из таких работ ограничиваются формулировкой идей и алгоритмов, несколько реже создаются прототипные реализации, цель которых — на двух-трех примерах продемонстрировать, что предложенная техника работает. Эти прототипы невозможно использовать для индустриальной разработки ПО, в рамках которой инструменты должны быть работоспособны и эффективны в очень широком контексте. У исследователей же почти никогда нет ресурсов и времени разрабатывать промышленно применимые инструменты. В тех очень редких случаях, когда удается все же сделать пригодный к использованию на практике инструмент, он объединяет десяток-два разнообразных техник и способен решать две-три задачи верификации. Однако в процессе промышленной разработки ПО таких задач несколько десятков, а большинству организаций удается успешно внедрить и начать активно использовать лишь два-три таких инструмента. Другой проблемой является растущая сложность создания и апробации новых техник верификации. Все необходимое для их работы окружение — инструменты анализа исходного кода, описания формальных моделей, 74
библиотеки для работы с внутренним представлением моделей и кода, инструменты, реализующие различные виды анализа кода и моделей, средства получения отчетов — невозможно разработать заново. Исследователю для проверки работоспособности его идеи приходится на скорую руку собирать это окружение из разнородных компонентов и библиотек, которые можно найти в свободном доступе. В лучшем случае удается создать прототип, который способен справиться с парой нужных примеров. Но таким способом невозможно создать среду, в рамках которой можно было бы проанализировать работоспособность и эффективность новой идеи в широком множестве разнообразных ситуаций, на разных видах приложений и требований к ним. Поэтому большинство новых идей применяются лишь в «тепличных условиях», а эффекты от их применения в широком контексте остаются неясными и непредсказуемыми. Решением для упомянутых проблем могла бы стать унифицированная расширяемая среда верификации программных систем, предоставляющая общее окружение для решения задач верификации и библиотеки готовых компонентов, реализующих типовые техники. Такая среда могла бы существенно упростить интеграцию модулей, реализующих различные техники верификации, за счет унифицированных интерфейсов ее расширения. Исследователи могли бы использовать ее для значительного снижения затрат на апробацию новых методов и анализ их работоспособности в разнообразных ситуациях. Промышленные разработчики — для интеграции нужного им набора техник в рамках единого инструмента и эффективного внедрения их в практическое использование. Подтверждением работоспособности и эффективности интеграции различных методов верификации ПО в разнообразных ситуациях являются многочисленные синтетические методы верификации.
2. Синтетические методы верификации ПО Синтетические методы верификации используют техники различных видов по приведенной выше классификации, а также комбинируют идеи различных подходов для получения большей эффективности верификации в терминах затрат на ее проведение и достоверности получаемых результатов. На данный момент такие методы относятся к одной из следующих групп. • Статический анализ предполагает построение некоторых моделей кода проверяемой системы, чаще всего, в виде размеченных графов потоков управления и данных, и анализ свойств этих моделей, например, поиск ошибок определенного рода по соответствующим им шаблонам в потоках данных. Сейчас все чаще используются специфические виды статического анализа, в рамках которых находят применения формальные модели и специализированные инструменты разрешения ограничений. 75
•
76
o
Расширенный статический анализ (extended static checking) [5,6] проверяет соответствие кода ПО требованиям, обычно записываемым тоже в коде в виде комментариев к его отдельным элементам (процедурам, типам данных и методам классов). При этом на основе результатов анализа кода автоматически строятся формальные модели его поведения, выполнение требований для которых проверяется чаще всего с помощью дедуктивного анализа и специализированных решателей (solvers).
o
Статический анализ на базе автоматической абстракции [7-10]. В рамках такого подхода на основе результатов статического анализа кода автоматически строятся более абстрактные, а потому более простые модели работы проверяемого ПО, которые затем подвергаются проверке на выполнение определенных свойств с помощью инструментов проверки моделей или решателей. Обычно проверяемые свойства фиксированы для данного инструмента или формулируются в конфигурационном файле. При нарушении требования в модели инструменты этого типа пытаются построить соответствующий сценарий работы кода. Если это не получается из-за упрощений, сделанных при построении модели, определяются элементы кода, препятствующие выполнению такого сценария, и в модель вносятся уточнения, более аккуратно описывающие работу именно этих элементов, после уточненная модель снова проверяется на выполнение заданного свойства. В итоге инструмент либо подтверждает выполнение требований, либо находит контрпример, либо завершает работу по истечении некоторого времени или из-за исчерпания ресурсов, не приходя к определенным выводам.
Синтетическое структурное тестирование [11-16] при котором после первого случайно выбранного теста остальные тесты генерируются автоматически так, чтобы обеспечить покрытие еще не покрытых ранее элементов кода. Для выбора подходящих тестовых данных используются решатели, учитывающие символическую информацию о ранее выполненных тестах (ограничения на данные, отделяющие прошедшие тесты от еще не покрытого кода), а для построения нужных последовательностей воздействий — случайная генерация, направляемая как этой же символической информацией, так и некоторыми эвристическими абстракциями, уменьшающими пространство состояний проверяемой системы.Тестирование на основе моделей (model based testing) [17-19] сочетает разработку формальных моделей требований к проверяемому ПО и построение
тестов на базе этих моделей. Структура модели при этом служит основой для критерия полноты тестирования, а ограничения модели на корректные результаты работы ПО используются в качестве тестовых оракулов, оценивающих правильность поведения ПО в ходе тестирования. В рамках последних двух подходов (или отдельно от них) применяются специфические техники построения тестов, сами по себе сочетающие разные методы верификации. o
o
•
Построение тестов с помощью разрешения ограничений [2022]. Часто при разработке тестов на основе критериев полноты тестирования формулируются так называемые цели тестирования (test objectives), представляющие собой специфические ситуации, в которых необходимо проверить поведение тестируемой системы для достижения необходимой уверенности в ее корректной работе. Цель тестирования формулируется как набор ограничений на проходимые во время теста состояния системы и данные выполняемых воздействий. Для построения теста, достигающего такую цель, можно использовать специализированные решатели (solvers). Такой решатель либо автоматически находит необходимые данные и последовательность вызовов операций как решение заданной системы ограничений, либо показывает, что эта система неразрешима, т.е. заданная цель тестирования недостижима и строить нацеленные на нее тесты не имеет смысла.
настоящее время достигнуты значительные успехи в разработке таких методов и внедрении их в практику промышленной разработки ПО, например, в следующих случаях. • Многочисленные проекты NASA по разработке ПО управления для космических спутников, челноков и специализированных исследовательских аппаратов, проводимые с использованием инструментов проверки моделей, генерации тестов на их основе и мониторинга [31-33]. Из используемых в этих проектах инструментах наиболее известны инструменты проверки моделей Spin [34,35], генератор тестов T-VEC [36,37] и инструмент символического выполнения Java PathFinder [38,39], используемый для проверки свойств Java программ, их мониторинга и тестирования.
Построение тестов как контрпримеров с помощью инструментов проверки моделей [23-26]. Другой способ построения тестов — сформулировать отрицание ограничений, задающих цель тестирования, как свойство, которое можно проверить или опровергнуть с помощью инструмента проверки моделей. Если это свойство подтверждается, значит, цель тестирования недостижима, если же оно опровергается, то инструмент строит контрпример, являющийся в данном случае необходимым тестом.
Мониторинг формальных свойств (runtime verification, passive testing) [27-30] тоже использует формальные модели требований для оценки правильности поведения проверяемой системы, но только в ходе ее обычной работы, без использования специально построенных тестов.
Как видно, все синтетические методы так или иначе пытаются соединить достоинства различных подходов к верификации, купируя их недостатки. В 77
78
•
Создание и использование в Microsoft инструмента Static Driver Verifier, использующего статический анализ с автоматической абстракций для проверки корректности работы драйверов Windows [40]. Сначала в проекте использовался инструмент проверки моделей SLAM, который затем был значительно доработан и дополнен возможностями анализа произвольного кода на языке C и автоматической абстракции, направляемой контрпримерами [41,42].
•
Внутренний проект Microsoft по проведению формальной спецификации и генерации тестовых наборов для разнообразных клиент-серверных протоколов, используемых в продуктах этой компании [43]. В рамках этого проекта используется, в основном, инструмент SpecExplorer [44], разработанный в Microsoft Research, а объем работ по анализу и формализации документации на протоколы оценивается в несколько десятков человеко-лет.
•
Проводившиеся и идущие в настоящее время в ИСП РАН проекты по созданию тестов на основе формальных моделей базовых библиотек операционных систем, телекоммуникационных протоколов семейства IPv6, оптимизирующих блоков компиляторов [45-47], использующие семейство инструментов тестирования на основе моделей UniTESK [48].
•
Использование формальных методов верификации и инструментов расширенного статического анализа при создании систем авионики в Airbus и Boeing [10,49,50]. В частности, в Airbus использовался инструмент статического анализа на основе формальных моделей ASTREE [10].
•
Использование формальных методов, тестирования на основе моделей и средств мониторинга при разработке ПО для смарткарт [51,52].
Все эти примеры подтверждают эффективность интеграции различных верификационных методов на практике. Тем не менее, несмотря на достигнутые успехи, каждый из имеющихся синтетических подходов использует лишь часть имеющегося потенциала и не предоставляет единой среды интеграции для всего многообразия различных техник верификации ПО.
3. Подход к построению верификации ПО
расширяемой
среды
Проблемы возрастающей сложности при создании и апробации новых методов верификации ПО и необходимость создания расширяемой среды, позволяющей интегрировать различные техники и инструменты, уже обсуждались различными авторами (см., например, [53]). Однако в доступной литературе пока не было представлено систематичного подхода к построению подобной среды. Чтобы стать реализуемым на практике, такой подход должен предлагать адекватные решения для нескольких методологических и организационных проблем, которые обсуждаются ниже.
3.1. Анализ требований Никакая верификация немыслима без предварительной четкой формулировки проверяемых требований, и на практике почти всегда любая деятельность по верификации предваряется анализом требований к проверяемой системе и (обычно, частичной) их формализацией. Однако методически единого подхода к вопросам анализа и представления требований не существует, и, скорее всего, не будет выработано в течение достаточно долгого времени. Как в этом случае можно надеяться построить единую среду верификации, интегрирующие разные подходы, в том числе и использующие различные методики анализа требований? Для этого предлагается оставить проблематику анализа требований за рамками обсуждаемой среды и определить четкий интерфейс между ней и деятельностью по выделению требований. Для обоснования адекватности проводимой верификации необходимо, чтобы каждая часть используемых моделей и каждый элемент отчетов о найденных проблемах могли быть соотнесены с каким-то элементом исходных требований. Поэтому, отвлекаясь от проблем формализации, установления взаимосвязей между требованиями, обеспечения их адекватности и полноты, можно считать требования лишь набором некоторых объектов с уникальными идентификаторами, позволяющими привязывать к ним элементы моделей, тесты, проводимые проверки и обнаруживаемые дефекты. Какова природа этих объектов — являются ли они текстами, формулами, изображениями, схемами и т.п. — при этом не важно. 79
Еще один важный аспект — место экспертизы среди поддерживаемых рассматриваемой средой подходов к верификации. Экспертиза применима к любым свойствам ПО и любым артефактам, хотя для разных целей используются разные ее виды. Она позволяет выявлять все виды ошибок, причем делать это на ранних этапах, тем самым минимизируя время существования дефекта в рамках жизненного цикла ПО и ресурсы, требующиеся на его устранение. Эмпирические исследования показывают, что эффективность экспертиз, измеряемая как отношение количества обнаруживаемых дефектов к затрачиваемым на это ресурсам, выше, чем для других методов верификации. Согласно различным отчетам от 50% до 90% всех зафиксированных в жизненном цикле ПО ошибок может быть обнаружено с помощью экспертиз [54-56]. В то же время проведение экспертизы не может быть автоматизировано и всегда требует привлечения людей, а ее эффективность существенно зависит от их опыта и мотивации, организации процесса разработки и профессионального взаимодействия между его участниками. Это накладывает серьезные ограничения на распределение ресурсов в проектах и может приводить к конфликтам, если мало внимания уделяется организационным аспектам проведения экспертиз. В рамках рассматриваемой среды предлагается по максимуму использовать формализованные представления для всех артефактов разработки, с тем чтобы к ним можно было применить автоматизированный анализ того или иного рода. Использовать экспертизу нужно в ходе анализа требований и их формализации, что позволит наиболее выгодным образом сочетать достоинства различных методов верификации. Экспертиза с ее включением людей и их возможности находить решения в неформализованных, неясных ситуациях, наиболее эффективна именно во время определения и уточнения требований, где не работают все остальные методы. При анализе же формализованных артефактов более эффективным представляется применять автоматизированный анализ.
3.2. Поддержка различных языков и нотаций Очень многие инструменты верификации ориентируются на определенные языки представления моделей и требований. При интеграции различных методов сразу же встанет вопрос о том, какие языки использовать вообще, и поддержку каких из них стоит реализовать в первую очередь. Опыт, полученный на основе большого числа проектов по верификации промышленного ПО [18,19,43,44,46,48], позволяет утверждать, что использование в рамках технологий и инструментов языков, которые как можно меньше отличаются от привычных обычным разработчикам, существенно облегчает их внедрение и использование в промышленности. Поэтому в рамках среды в первую очередь необходимо использовать такие формы представления моделей, которые были бы минимально необходимыми 80
расширениями широко распространенных языков программирования. В дальнейшем можно добавлять поддержку для наиболее известных языков формальных спецификаций. Поддержка различных языков и нотаций должна быть организована на уровне некоторого общего промежуточного представления языковых конструкций, используемого всеми инструментами анализа, но не пользователями непосредственно. Разработка такого промежуточного представления, применимого для многих разных языков, является нетривиальной задачей. Как показывает опыт, создать адекватное общее представление программ для сильно отличающихся языков (например, для Java и Пролога), практически невозможно. Поэтому построение интерфейса для используемого средой промежуточного представления будет постепенным, опирающимся на накапливаемый опыт работы с различными языками. Сам набор понятий, на базе которого можно создать такое представление, пока не выработан, и строить его придется уже в ходе создания описываемой среды верификации. При наличии такой возможности стоит использовать уже имеющиеся стандартные или широко используемые библиотеки для работы с промежуточным представлением для ряда языков. Например, для C и C++ стандартом де-факто постепенно становится промежуточное представление, используемое в рамках компилятора GCC [57]. Значительным преимуществом использования результатов подобных проектов является гарантированные их поддержка и развитие в будущем в течение обозримого времени.
будут добавляться другие компоненты, поддерживающие вспомогательные и менее значимые функции. Предлагается использовать в качестве основы для построения среды верификации архитектурный каркас инструментов тестирования на основе моделей. Это решение вызвано тем обстоятельством, что такое тестирование является одним из самых сложно организованных процессов верификации — в его ходе обычно необходимо сделать следующее. • Определить модель поведения тестируемой системы, формализующую требования к этому поведению. •
Проанализировать структуру модели для выбора критериев покрытия и отдельных целей тестирования, и определить эти критерии и цели.
•
Построить среду выполнения тестов, включающую средства мониторинга и тестовые оракулы — программные компоненты, определяющие соответствие или несоответствие наблюдаемого поведения системы и модели. Обычно такая среда состоит из библиотеки поддержки выполнения тестов, набора тестовых оракулов для всех проверяемых компонентов и набора адаптеров, связывающих эти компоненты с их оракулами. Оракулы в большинстве случаев генерируются автоматически из модели поведения системы.
•
Построить, автоматически или с привлечением человека, набор тестовых сценариев, определяющих последовательности вызова различных операций тестируемой системы или посылки ей сообщений или сигналов и данные, передаваемые в качестве параметров операций и сообщений.
•
Выполнить тестовые сценарии, протоколируя всю информацию, касающуюся соответствия наблюдаемого поведения системы и ее модели, а также покрытых во время тестирования ситуаций.
•
Провести анализ результатов тестов, в ходе которого выявляются и анализируются ошибки в системе или ее модели (проявляющиеся как несоответствия между ожидаемым и реальным поведением), а также анализируется достигнутое тестовое покрытие и принимается решение либо о создании дополнительных тестов, либо об окончании их разработки.
3.3. Архитектурная основа среды верификации Архитектура рассматриваемой среды верификации — набор ее основных компонентов, их внешних интерфейсов и правил взаимодействия, а также правил добавления новых компонентов — в значительной степени определяет одно из важнейших свойств этой среды — ее расширяемость. С другой стороны, решения, касающиеся базовых принципов построения среды могут влиять на возможности ее интеграции с другими инструментами разработки ПО. Прежде всего, для облегчения ее использования в промышленной разработке ПО, такая среда должна быть встроена в одну из широко используемых сред разработки, таких как Eclipse или Microsoft Visual Studio. Eclipse [58,59] является наиболее подходящей средой интеграции для первых версий, поскольку обладает огромным набором модулей расширения, в том числе являющихся инструментами верификации и модулями поддержки различных языков программирования. Кроме того, процессом создания таких модулей хорошо документирован. Кроме внешнего окружения, в рамках которой должна работать среда верификации, необходимо также определить ее каркас — некоторый базовый набор компонентов, реализующих основной набор функций и поддерживающие основные потоки данных внутри системы. К этому каркасу 81
Чтобы в архитектурный каркас тестирования на основе моделей вложить другие синтетические методы верификации, необходимо лишь добавить модули для анализа исходного кода проверяемых компонентов — все остальные необходимые модули в нем фактически уже имеются (см. также [60]).
82
Требования
Модели поведения
3.4. Организация разработки среды верификации
Исходный код
Для создания описанной среды верификации потребуется затратить значительные ресурсы, даже если удастся использовать имеющиеся компоненты, реализующие различные виды анализа, алгоритмы построения тестов или синтаксический разбор текстов на определенных языках программирования. Необходимые трудозатраты делают ее разработку силами небольшой группы практически нереальной. Поэтому разработка и развитие такой среды могут быть организованы как открытый проект в Интернет с возможностью включения в него любых участников, согласных следовать предложенным архитектурным решениям и другим правилам проекта. Для начала такого проекта важно подготовить общий каркас среды и реализацию некоторой значимой части ее функциональности. В качестве первого варианта можно рассматривать расширение системы модульного тестирования TestNG [61,62] с помощью средств построения тестов на основе моделей. TestNG — это популярная среда c открытым кодом, поддерживающая разработку модульных и интеграционных тестов для Java приложений и позволяющая гибко конфигурировать выполнение полученных тестовых наборов. Расширение ее возможностями тестирования на основе моделей и хотя бы одним-двумя видами анализа моделей и кода, позволяющими, например, реализовать синтетическое структурное тестирование в ряде ситуаций, позволит наглядно продемонстрировать интеграционные возможности предлагаемого подхода.
Среда разработки Промежуточное представление языка моделирования
База требований
Интерфейс к базе требований
Поддержка создания и редактирования моделей и адаптеров
Генерация оракулов для тестирования и мониторинга
Тестовые сценарии Проверяемая система
Поддержка выполнения тестов и мониторинга
Знаком отмечены те компоненты, которые должны иметь пользовательский интерфейс
Промежуточное представление языка программирования
Поддержка различных видов анализа моделей (решатели, инструменты проверки свойств моделей и пр.) Построение тестовых данных и сценариев
Построение отчетов
Отчеты о результатах верификации
4. Заключение Рис. 1. Предварительная архитектура расширяемой среды верификации ПО. Предварительный вариант архитектуры унифицированной расширяемой среды верификации ПО изображен на рис. 1. На нем присутствуют только наиболее крупные компоненты. При более детальной проработке архитектуры может потребоваться их разбиение на более мелкие и добавление других модулей, решающих вспомогательные задачи. В рамках такой архитектуры можно проводить как тестирование на основе моделей и верификационный мониторинг (без построения и использования тестов), так и расширенный статический анализ (используя только различные виды анализа исходного кода системы и моделей требований к ней) или синтетическое структурное тестирование (используя при построении тестов информацию о структуре исходного кода).
83
В данной статье предложен подход к интеграции различных методов верификации ПО. Целью его является существенное повышение сложности программных систем, для которых проведение верификации с помощью строгих методов, использующих формальные модели в явном или скрытом виде, сможет давать практически значимые результаты при приемлемых затратах. Предлагаемый подход основан на объединении нескольких успешно применяемых на практике синтетических методов верификации (расширенный статический анализ, синтетическое структурное тестирование, тестирования на основе моделей и мониторинг формальных свойств) в рамках единой расширяемой среды верификации ПО. В качестве базовой архитектуры для такой среды предложено использовать хорошо зарекомендовавшую себя архитектуру средств тестирования на основе моделей [48], расширенную дополнительными компонентами для анализа исходного кода проверяемых компонентов и для различных видов анализа моделей, в том числе разнообразными решателями. Тестирование на основе 84
моделей выбрано основой предложенной архитектуры, поскольку оно является самым сложным видом верификации из объединяемых методов. Представлен также ряд методических и технических решений, который, по мнению автора, позволит сделать создание описываемой среды верификации практически выполнимым, а кроме того, облегчит ее использование для решения практических задач верификации промышленного ПО. Еще одной сферой применения такой среды может стать апробация и отладка многочисленных новых техник верификации и анализа свойств ПО, нацеленного на его верификацию, на практически значимых системах разных классов.
[14] [15] [16] [17] [18]
Литература [1] V. Maraia. The Build Master: Microsoft's Software Configuration Management Best Practices. Addison-Wesley Professional, 2005. [2] G. Robles. Debian Counting. http://libresoft.dat.escet.urjc.es/debian-counting/. [3] C. Макконнелл. Совершенный код. М.: Русская редакция, 2005. [4] В. В. Кулямин. Методы верификации программного обеспечения. Всероссийский конкурс обзорно-аналитических статей по приоритетному направлению "Информационно-телекоммуникационные системы", 2008. http://window.edu.ru/window/library?p_rid=56168. [5] D. L. Detlefs, K. R. M. Leino, G. Nelson, J. B. Saxe. Extended static checking. Technical Report SRC-RR-159, Digital Equipment Corporation, Systems Research Center, 1998. [6] D. R. Cok, J. R. Kiniry. ESC/Java2: Uniting ESC/Java and JML. Proc. of International Workshop on the Construction and Analysis of Safe, Secure, and Interoperable Smart Devices (CASSIS'04), LNCS 3362:108-128, Springer-Verlag, January 2005. [7] P. Emanuelsson, U. Nilsson. A Comparative Study of Industrial Static Analysis Tools. Technical Report 2008:3, Linkoping University, 2008. http://www.ep.liu.se/ea/trcis/2008/003/trcis08003.pdf. [8] T. A. Henzinger, R. Jhala, R. Majumdar, G. Sutre. Software Verification with Blast. Proc. of 10-th SPIN Workshop on Model Checking Software (SPIN 2003), LNCS 2648:235-239, Springer-Verlag, 2003. [9] T. Ball, S. K. Rajamani. Automatically Validating Temporal Safety Properties of Interfaces. Proc. of Model Checking of Software, LNCS 2057:103-122, Springer, 2001. [10] B. Blanchet, P. Cousot, R. Cousot, J. Feret, L. Mauborgne, A. Mine, D. Monniaux, X. Rival. Design and implementation of a special-purpose static program analyzer for safety-critical real-time embedded software. T. Mogensen, D. A. Schmidt, I. H. Sudborough, eds. The Essence of Computation: Complexity, Analysis, Transformation. Essays Dedicated to Neil D. Jones. LNCS 2566:85-108, SpringerVerlag 2002. [11] Y. Smaragdakis, C. Csallner. Combining Static and Dynamic Reasoning for Bug Detection. Proc. of TAP 2007, LNCS 4454:1-16, Springer, 2007. [12] K. Sen, G. Agha. CUTE and jCUTE: Concolic unit testing and explicit path modelchecking tools. Proc. of Computer Aided Verification, pp.419-423, August 2006. [13] T. Xie, D. Marinov, W. Schulte, D. Notkin. Symstra: A Framework for Generating Object-Oriented Unit Tests using Symbolic Execution. Proc. of 11-th International
85
[19] [20] [21] [22] [23]
[24] [25] [26] [27] [28] [29] [30] [31] [32]
86
Conference on Tools and Algorithms for the Construction and Analysis of Systems (TACAS 2005), Edinburgh, UK, pp. 365-381, April 2005. N. Tillmann, W. Schulte. Parameterized Unit Tests with Unit Meister. ACM SIGSOFT Software Engineering Notes, 30(5):241-244, September 2005. C. Pacheco, S. K. Lahiri, M. D. Ernst, T. Ball. Feedback-Directed Random Test Generation. Proc. of International Conference on Software Engineering, pp. 75-84, 2007. P. Godefroid. Compositional dynamic test generation. Proc. of 34-th annual ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages (PLOP 2007), pp. 47-54, 2007. M. Broy, B. Jonsson, J.-P. Katoen, M. Leucker, A. Pretschner, eds. Model Based Testing of Reactive Systems. LNCS 3472, Springer, 2005. M. Utting, B. Legeard. Practical Model-Based Testing: A Tools Approach. MorganKaufmann, 2007. J. Jacky, M. Veanes, C. Campbell, W. Schulte. Model-Based Software Testing and Analysis with C#. Cambridge University Press, 2007. B. Korel. Automated Test Data Generation. IEEE Trans. on Software Engineering, 16(8):870-879, 1990. R. DeMillo, A. Offutt. Constraint-based automatic test data generation. IEEE Trans. on Software Engineering, 17(9):900-910, 1991. A. Gotlieb, B. Botella, M. Rueher. Automatic test data generation using constraint solving techniques. ACM SIGSOFT Software Engineering Notes, 23(2):53-62, 1998. A. Gargantini, C. Heitmeyer. Using Model Checking to Generate Tests from Requirements Specifications. Proc. of Joint 7-th European Software Engineering Conference and 7-th ACM SIGSOFT International Symposium on Foundations of Software Engineering (ESEC/FSE99), ACM Press, September 1999, pp. 146-162. H. S. Hong, I. Lee, O. Sokolsky, S. D. Cha. Automatic Test Generation from Statecharts Using Model Checking. Technical Report MS-CIS-01-07, Feb 2001. G. Hamon, L. de Moura, J. Rushby. Generating Efficient Test Sets with a Model Checker. Proc. of the 2-nd Software Engineering and Formal Methods International Conference, p. 261-270, 2004. D. Beyer, A. J. Chlipala, T. A. Henzinger, R. Jhala, R. Majumdar. Generating tests from counterexamples. Proc. of 26-th International Conference on Software Engineering (ICSE), p. 326-335, 2004. I. Lee, S. Kannan, M. Kim, O. Sokolsky, M. Viswanathan. Runtime Assurance Based On Formal Specifications. Proc. of International Conference on Parallel and Distributed Processing Techniques and Applications PDPTA’1999, pp. 279-287, 1999. Y. Cheon, G. T. Leavens. A runtime assertion checker for the Java Modeling Language (JML). Proc. of International Conference on Software Engineering Research and Practice (SERP’02), pp. 322-328, CSREA Press, June 2002. A. Cavalli, C. Gervy, S. Prokopenko. New approaches for passive testing using an Extended Finite State Machine Specification. Information and Software Technology, 45(12):837-852, Elsevier, September 2003. D. Drusinsky. Modeling and Verification Using UML Statecharts. Newnes, 2006. M. R. Blackburn, R. D. Busser, A. M. Nauman. Interface-Driven, Model-Based Test Automation. CrossTalk, The Journal of Defense Software Engineering, May 2003. C. Artho, H. Barringer, A. Goldberg, K. Havelund, S. Khurshid, M. Lowry, C. Pasareanu, G. Rosu, K. Sen, W. Visser, R. Washington. Combining test case
generation and runtime verification. Theoretical Computer Science, 336(2-3):209-234, May 2005. G. Brat, K. Havelund, S. Park, W. Visser. Model Checking Programs. Proc. of 15-th IEEE International Conference on Automated Software Engineering, Grenoble, France, pp. 3-11, September 2000. G. J. Holzmann. The SPIN Model Checker: Primer and Reference Manual. AddisonWesley Professional, 2003. http://spinroot.com/. M. Blackburn, R. D. Busser, J. S. Fontaine. Automatic generation of test vectors for SCR-style specifications. Proc. of 12-th Annual Conference on Computer Assurance, June 1997, pp. 54-67. http://www.t-vec.com/. G. Brat, W. Visser, K. Havelund, S. Park. Java PathFinder — second generation of a Java model checker. Proc. of Workshop on Advances in Verification, Chicago, Illinois, July 2000. http://javapathfinder.sourceforge.net/. http://www.microsoft.com/whdc/devtools/tools/SDV.mspx. T. Ball, S. K. Rajamani. Automatically Validating Temporal Safety Properties of Interfaces. Proc. of Model Checking of Software, LNCS 2057:103-122, Springer, 2001. T. Ball, E. Bounimova, B. Cook, V. Levin, J. Lichtenberg, C. McGarvey, B. Ondrusek, S. K. Rajamani, A. Ustuner. Thorough Static Analysis of Device Drivers. ACM SIGOPS Operating Systems Review 40(4):73-85, 2006. W. Grieskamp, N. Kicillof, D. MacDonald, A. Nandan, K. Stobie, F. L. Wurden. ModelBased Quality Assurance of Windows Protocol Documentation. Proc. of 1-st International Conference on Software Testing, Verification, and Validation, ICST 2008, Lillehammer, Norway, April 2008, pp. 502-506. M. Veanes, C. Campbell, W. Grieskamp, W. Schulte, N. Tillmann, L. Nachmanson. Model-Based Testing of Object-Oriented Reactive Systems with Spec Explorer. Formal Methods and Testing, LNCS 4949:39-76, Springer Verlag, 2008. V. Kuliamin, A. Petrenko, N. Pakoulin. Practical Approach to Specification and Conformance Testing of Distributed Network Applications. In M. Malek, E. Nett, N. Suri, eds. Service Availability. LNCS 3694, pp. 68-83, Springer-Verlag, 2005. A. Grinevich, A. Khoroshilov, V. Kuliamin, D. Markovtsev, A. Petrenko, V. Rubanov. Formal Methods in Industrial Software Standards Enforcement. Proc. of PSI’2006, Novosibirsk, Russia, June 2006, LNCS 4378:459-469, Springer-Verlag, 2006. С. В. Зеленов, С. А. Зеленова, А. С. Косачев, А. К. Петренко. Генерация тестов для компиляторов и других текстовых процессоров. Программирование, 29(2):59–69, 2003. В. В. Кулямин, А. К. Петренко, А. С. Косачев, И. Б. Бурдонов. Подход UniTesK к разработке тестов. Программирование, 29(6):25-43, 2003. J. Souyris, D. Delmas. Experimental Assessment of ASTRÉE on Safety-Critical Avionics Software. Proc. of Int. Conf. on Computer Safety, Reliability, and Security, SAFECOMP 2007, F. Saglietti, N. Oster, eds., Nuremberg, Germany, September 2007, LNCS 4680:479-490, Springer, 2007. P. Manolios, G. Subramanian, D. Vroon. Automating component-based system assembly. Proc. of ISSTA 2007, London, UK, 2007, pp. 61-72. E. Poll, J. van den Berg, B. Jacobs. Specification of the JavaCard API in JML. In Proc. of CARDIS’00. Kluwer Academic Publishers, 2000.
87
[52] F. Bouquet, B. Legeard. Reification of executable test scripts in formal specificationbased test generation: The Java card transaction mechanism case study. In Proc. of the International Symposium of Formal Methods Europe, pp. 778-795, Springer-Verlag, 2003. [53] A. R. Bradley, H. B. Sipma, S. Solter, Z. Manna. Integrating tools for practical software analysis. Proc. of 2004 CUE Workshop, Vienna, Austria, October 2004. [54] T. Gilb, D. Graham. Software Inspection. Addison-Wesley, 1993. [55] A. Porter, H. Siy, L. Votta. A Review of Software Inspections. University of Maryland at College Park, Technical Report CS-TR-3552, 1995. [56] O. Laitenberger. A Survey of Software Inspection Technologies. In Handbook on Software Engineering and Knowledge Engineering, v. 2, pp. 517-555. World Scientific Publishing, 2002. [57] GNU Compiler Collection Internals. http://gcc.gnu.org/onlinedocs/gccint/index.html. [58] B. Daum. Professional Eclipse 3 for Java Developers. Wrox, 2004. [59] http://www.eclipse.org/. [60] G. Yorsh, T. Ball, M. Sagiv. Testing, abstraction, theorem proving: better together! Proc. of ISSTA 2006, Partland, Maine, USA, 2006, pp. 145-156. [61] C. Beust, H. Suleiman. Next Generation Java Testing: TestNG and Advanced Concepts. Addison-Wesley Professional, 2007. [62] http://testng.org/doc/.
88
Метод проверки линеаризуемости многопоточных Java программ Мутилин В.С. [email protected] Аннотация. В статье описывается новый метод Sapsan. Он предназначен для функционального тестирования Java программ, предоставляющих программный интерфейс (API), процедуры (операции) которого можно вызывать из нескольких потоков одновременно. Метод Sapsan позволяет проверять одно из распространенных требований к таким программам – требование линеаризуемости, заключающееся в том, что параллельное выполнение операций эквивалентно некоторому последовательному выполнению этих же операций, удовлетворяющему спецификации.
1. Введение В последнее время многопоточное программирование получило широкое распространение. Разрабатываемые программы с целью ускорения работы все чаще состоят из нескольких потоков, которые выполняют работу параллельно. Но разработка многопоточных программ намного сложнее последовательных. Это связано с тем, что порядок, в котором будут выполнены инструкции разных потоков, заранее непредсказуем и разработчик должен предусмотреть корректную работу программы при всех возможных чередованиях инструкций. В данной работе мы рассматриваем Java программы, предоставляющие программный интерфейс, через который с ними взаимодействуют другие программы. Интерфейс состоит из операций (процедур), которые можно выполнять (вызывать) с различными значениями параметров. Выполнение операции завершается возвратом значения, называемого результатом. Кроме того, операции могут быть выполнены в различных потоках одновременно. Выполнение операций в нескольких потоках будем называть параллельным выполнением, а выполнение операций в одном потоке – последовательным. Параллельное выполнение линеаризуемо, если оно эквивалентно некоторому последовательному выполнению, удовлетворяющему спецификации. Формально понятие линеаризуемости будет определено в разделе 2. Можно видеть, что задача проверки линеаризуемости – это частный случай задачи функционального тестирования, в которой проверяется, удовлетворяет ли программа функциональным требованиям к ней, заданным в виде 89
спецификации. Но в отличие от общего случая, спецификация требуется только для последовательных выполнений. Свойство линеаризуемости во многом сходно с такими свойствами как сериализуемость [4,19], атомарность [11,23], последовательная согласованность (sequential consistency) [17]. В отличие от них, линеаризуемость предполагает наличие спецификации, тогда как эти свойства накладывают ограничения только на саму программу. Впервые понятие линеаризуемости встречается в работе [16] 1990 года, в которой был предложен способ ручного доказательства этого свойства. Свойство линеаризуемости получило признание в научных работах, но до сих пор не было предложено метода его автоматизированной проверки. Работа ученых в основном концентрировалась на свойствах независимых от спецификации. Так, для программ на языке Java, были разработаны инструменты для проверки атомарности. Выделим две группы инструментов. К первой группе относятся инструменты статического анализа программ [11,24]. Эти инструменты предполагают аннотирование кода программы или определенный метод программирования. Так, например инструмент [11], предполагает использование специальной системы типов. Основным недостатком этой группы инструментов является большое количество ложных срабатываний, возникающих из-за неверных предположений о динамическом поведении программы. Ко второй группе относятся инструменты, исследующие динамическое поведение программы. Одними из наиболее распространенных являются инструменты на основе методов проверки моделей (model checking) [7,12,15], осуществляющие поиск чередований инструкций в параллельных потоках. В последнее время инструменты, реализующие эти методы, сделали существенный шаг вперед. Стало возможным проверять свойства для программ, написанных на широко распространенных языках программирования, а не на простых модельных языках. Так инструмент Java PathFinder [22], способен проверять свойства программ на языке программирования Java, а инструмент VeriSoft [14] предназначен для проверки программ на языке C. Однако инструменты на их основе, предназначенные для проверки атомарности, сталкиваются с рядом сложностей. Во-первых, чтобы запустить эти инструменты требуется подготовить окружение, т.е. задать набор потоков, вызывающих операции интерфейса с некоторыми значениями параметров. Но так как требование атомарности в общем случае формулируется для неограниченного числа потоков, то проверив атомарность на конечных наборах, мы не можем быть уверенны в атомарности программы в целом. Во-вторых, поиск занимает сравнительно большое время. В зависимости от количества потоков требуется от нескольких минут до нескольких часов. Поэтому, даже ограничившись конечными наборами потоков и, многократными запусками поиска, мы столкнемся со значительными временными затратами. 90
Отметим также инструменты мониторинга Java программ [6,10,23], которые пытаются проверить атомарность на основе выполнений, возникающих в процессе работы программы. Основной недостаток этих инструментов в том, что они не гарантируют атомарности на всех возможных выполнениях. На практике линеаризуемые программы широко распространены. Отметим два распространенных класса: библиотеки, предназначенные для многопоточного использования и программы, предоставляющие интерфейс промежуточного уровня клиент-серверных приложений. В библиотеках, предназначенных для многопоточного использования, такое требование выдвигается по умолчанию. Функциональные требования описываются для каждой операции в отдельности, и требуется, чтобы операции было безопасно вызывать из нескольких потоков (thread-safe). В наших терминах это и есть требование линеаризуемости. Клиент-серверные приложения по своему назначению предоставляют клиентам сервисы. Клиенты могут использовать сервисы одновременно, при этом клиент не должен замечать присутствия других клиентов. Если два клиента совершают какие-то операции, то результат должен быть такой же, как если бы клиентов обслуживали последовательно. Данный класс систем чрезвычайно широк, так как практически все современные системы, предоставляющие сервисы, могут обслуживать несколько клиентов одновременно. Далее в разделе 2 рассмотрено формальное определение понятия линеаризуемости. В разделе 3 описана модель Java программы, в терминах которой в разделе 4 сформулированы достаточные условия, являющиеся основой для проверки линеаризуемости. В разделах 5 и 6 краткое описание метода проверки линеаризуемости Sapsan сопровождено иллюстрацией на простом примере. А в разделе 7 приведены результаты его применения.
История потока α (проекция, подистория) в истории H (обозначаем H | α ) – это последовательность всех событий в H, у которых имя процесса равно α . Две истории H, H’ эквивалентны (обозначаем H~H’), если для любого процесса α выполнено H | α = H ' | α . История правильная, если любая подистория H | α – последовательная. Все рассматриваемые в данной работе истории правильные. Операция в истории является незаконченной, если за началом нигде далее в последовательности не следует подходящий конец. complete(H) – максимальная подпоследовательность H, состоящая только из начал и подходящих концов (удалены незаконченные операции). Множество S замкнуто по префиксам (prefix-closed), если для любой истории H из S верно, что любой префикс H тоже в S. Последовательная спецификация программы – это замкнутое по префиксам множество последовательных трасс. История H соответствует спецификации, если H ∈ S .
2.2. Определение линеаризуемости История H индуцирует на операциях иррефлексивный частичный порядок < H такой, что e0 < H e1 , если end (e0 ) предшествует begin(e1 ) в H. Определение 2. История H линеаризуема, если она может быть расширена (добавлением нуль и более возвратов) до некоторой истории H’, для которой: 1. 2.
complete(H’) эквивалентна некоторой последовательной истории S, соответствующей спецификации; < H ⊆< S .
2. Понятие линеаризуемости
2.3. Самолинеаризуемость программы
2.1. Понятие истории
Для установления свойства линеаризуемости нам будет полезно понятие самолинеаризуемости, независящее от спецификации. Под достижимой историей программы будем понимать историю, которая может реально возникнуть в программе. В дальнейшем мы определим понятие достижимой истории на основе трассы выполнения. Определение 3. Программа самолинеаризуема, если для любой достижимой истории H существует достижимая последовательная история H’ такая, что H~H’.
Мы имеем набор операций op<имя>, каждая из которых имеет начало (вызов) op<имя>_begin<параметры> и конец (возврат) op<имя>_end<результат>. История – это конечная последовательность из событий α : op<имя>_begin<аргументы> и α : op<имя>_end<результат>, где α поток. Конец подходит (пара) началу, если совпадают потоки и имена операций. Определение 1. История последовательная, если: 1. 2.
Первое событие – начало операции. За каждым событием, кроме последнего, сразу же следует подходящий конец. 91
Если программа самолинеаризуема, то проверив, что все достижимые последовательные истории соответствуют спецификации, мы покажем линеаризуемость программы. 92
•
local (α , g ) – выдает локальное состояние потока α в состоянии g.
•
shared(g) – выдает разделяемое состояние s.
•
t (α ) – обозначает, что t выполняется в потоке α ∈ Ψ .
l 0 , L,ν , T , где l0 ∈ L – начальное
•
active(t (α ), g ) ≡ pre(t ) = local (α , g )
локальное состояние, L – локальные состояния (состояния управления), ν : T → ∑ – пометки, T ⊆ L × G × C × L' – переходы.
•
enabled (t (α ), g ) ≡ active(t (α ), g ) ∧ enabled (t , shared ( g )) –
3. Модель программы Программа (система, реализация) это тройка: s 0 , S , P , где s 0 ∈ S начальное состояние, S – множество разделяемых состояний (может быть бесконечно), P – конечный набор подпрограмм операций. Каждая подпрограмма P это четверка
∑ = {τ , op<имя>_begin<аргументы>, op<имя>_end<результат>}. Все переходы из начального состояния помечены op<имя>_begin<аргументы>, переход, помеченный промежуточные переходы τ , op<имя>_end<результат>, завершает подпрограмму. В переходах C : S → S – команда (command) изменения состояния G : S → {true, false} – (инструкция, последовательность инструкций), охранный предикат (guard). Множества локальных состояний разных подпрограмм не пересекаются. Подпрограмма операции, вообще говоря, может зацикливаться, но для дальнейших алгоритмов мы требуем ацикличность. Начальные и конечные переходы операций не меняют разделяемого состояния. Для того чтобы выполнить программу необходимо задать потоки пользователя Ψ = ϕ1 , ϕ 2 , K , ϕ n , которые будут выполняться. Поток
пользователя ϕ i задается как последовательность подпрограмм пользователя p 0 , p1 , K , p ni .
подпрограммы
Поток
начинает
выполнение
в
начальном
состоянии
p 0 , при завершении текущей подпрограммы происходит
переход из ее конечного состояния в начальное состояние следующей подпрограммы. Поток завершает выполнение после завершения подпрограммы pni . Для заданных пользовательских потоков Ψ определим состояние выполнения i
g = ( s, l 1 , l 2 , K , l n ) , s – разделяемое состояние, l – локальное состояние
ϕi .
потока
Начальное
состояние
выполнения
g 0 = ( s 0 , l 01 , l 02 , K , l 0n ) .
возможность выполнить переход t (α ) в состоянии g. переходы
существует,
если
sˆ = t.command ( s ) и lˆα = post (t ) .
Трасса
выполнения
программы
– t1 (α1 )
это
последовательность t 2 (α 2 )
t (α )
m m t1 (α 1 ), t 2 (α 2 ), K , t m (α m ) такая, что g 0 ⎯⎯⎯→ g1 ⎯⎯⎯→ L ⎯⎯ ⎯ ⎯ → gm . Трасса потока – проекция трассы выполнения программы на поток. Трасса операции в потоке – проекция трассы потока на операцию.
История выполнения для трассы σ = t1 (α 1 ), t 2 (α 2 ), K , t m (α m ) , обозначим H (σ ) , это последовательность меток ν (t i (α i )) , из которой удалены все метки τ . История H достижима, если существует трасса σ такая, что H = H (σ ) .
4. Достаточные условия самолинеаризуемости 4.1. Понятие независимости Мы будем использовать классическое определение понятия независимости [9,13,20] (Определение 4) и расширим его для произвольного набора пользовательских потоков Ψ (Определение 5). Определение 4. D(Ψ ) – рефлексивное, симметричное отношение зависимости для выполнения Ψ , в котором, если (t1 (α ), t 2 ( β )) ∉ D(Ψ ) (независимы), то для любого достижимого состояния g выполнено:
Множество всех состояний выполнения обозначим G. Введем следующие обозначения: • enabled (t , s ) ≡ t.guard ( s ) – проверка охранного предиката перехода t в s. •
(α ) g ⎯t⎯ ⎯→ g ' ,
g = ( s, l 1 , K , l α , K , l n ) . Переход enabled (t (α ), g ) =true и g ' = ( sˆ, l 1 , K , lˆα , K , l n ) , где
Определим
1. 2.
1 (α ) Из enabled (t1 (α ), g ) и g ⎯t⎯ ⎯→ g ' следует, что enabled (t 2 ( β ), g ' ) = enabled (t 2 ( β ), g ) ; Если enabled (t1 (α ), g ) и enabled (t 2 ( β ), g ) , то существует
t (α ),t ( β )
1 единственное состояние gˆ такое, что g ⎯⎯ ⎯2 ⎯→ gˆ и
pre(t ) – начальное состояние перехода t, post (t ) – конечное состояние.
2 ( β ),t1 (α ) g ⎯t⎯ ⎯⎯ ⎯→ gˆ .
93
94
Определение 5. D – рефлексивное, симметричное отношение зависимости для программы, в котором, если (t1 , t 2 ) ∉ D (независимы), то ∀Ψ; ∀α , β ; ∃D(Ψ ) : (t1 (α ), t 2 ( β )) ∉ D(Ψ ) . Замечание. Независимые переходы можно переставлять местами, история трассы и конечное состояние выполнения при этом не изменится [13].
4.2. Понятие цикла по зависимостям Пусть есть трасса программы σ = t1 (α 1 ), t 2 (α 2 ), K , t m (α m ) . Определим отношение следования. Определение 6. Отношение следования (без транзитивного замыкания) •
5. Метод Sapsan Метод многопоточного тетирования Sapsan состоит из предусловия применения и семи шагов. На вход методу подается программа, предоставляющая интерфейс из операций. Задача метода – проверить линеаризуемость программы. Демонстрировать метод Sapsan будем на примере Cell, представленном на рис. 1. В этом примере интерфейс программы состоит из двух операций: • op: op_begin(int), op_end(boolean) • op<delete>: op<delete>_begin(), op<delete>_end(boolean) 1 Integer x = null; 2 boolean b = false; 3 4 boolean delete() { 5 synchronized(this) { 6 if(x!=null && b) {//3_T 7 x = null; 8 b = false; 9 return true; 10 } else {//3_F1, 3_F2 11 return false; 12 } 13 } 14 }
t i (α i ) < t j (α j ) , если t i (α i ) в трассе лежит раньше t j (α j ) и
выполнено одно из условий: o t i , t j ≠ {op_begin, op_end}, (t1 , t 2 ) ∈ D и α i ≠ α j o •
t i = op_end, t j = op_begin и ti , t j не принадлежат
одной операции. t i (α i ) = t j (α j ) , если α i = α j и ti , t j принадлежат одной
операции. Утверждение 1. Пусть в трассе σ последовательно встречаются t i (α i ), t j (α j ) и t i (α i ) ≤/ t j (α j ) . Пусть σ ' это трасса, полученная из
σ
перестановкой t i (α i ), t j (α j ) в обратном порядке. Тогда история трассы эквивалентна истории трассы
boolean insert(int i) { boolean r = false; synchronized(this) { if(x==null) {//1_T x = i; r = true; }//1_F } synchronized(this) { if(r) {//2_T b = true; return true; } else {//2_F return false; } } }
( H (σ ' ) = H (σ ) ). Рис. 1. Код программы Cell
Следует из того, что t i (α i ), t j (α j ) независимы, так как t i (α i ) ≤/ t j (α j ) . Последовательность по отношению следования – это последовательно переходов связанных отношениями по определению 6: t i1 (α i1 ) ≤ t i2 (α i2 ) ≤ L ≤ t i p (α i p ) , где t i j (α i j ) – элементы трассы. Цикл – это последовательность t i1 (α i1 ) < t i2 (α i2 ) ≤ L < t i p (α i p ) , в которой t i1 (α i1 ) = t i p (α i p ) и t i1 (α i1 ) лежит раньше t i p −1 (α i p −1 ) .
Утверждение 2 рассмотрим в данной статье без доказательства. Утверждение 2. Если в достижимых трассах нет циклов, то программа самолинеаризуема.
Программа реализует ячейку, в которой хранится целочисленное значение. Операция insert записывает целочисленный элемент в ячейку и возвращает true, если ячейка свободна, иначе возвращает false. Операция delete удаляет элемент из ячейки и возвращает true, если ячейка не пуста, иначе возвращает false. В нашем простом примере операция insert специально немного усложнена, для того чтобы продемонстрировать трудности, встречающиеся в более сложных программах.
5.1. Предусловие применения Для применения метода Sapsan требуется, чтобы программа удовлетворяла дисциплине синхронизации доступа к разделяемым переменным. Суть этой дисциплины заключается в том, что доступ к любой переменной из разных потоков должен быть защищен хотя бы одним общим объектом-монитором. Это условие гарантирует отсутствие состояний гонок (race conditions) в 95
96
программе. Проверка следования данной дисциплине может осуществляться одним из известных алгоритмов [8,21]. Следование данной дисциплине позволяет рассматривать в качестве переходов программы не отдельные инструкции, а целые последовательности инструкций, называемые блоками, ограниченные входами и выходами из объектов-мониторов. В примере Cell используется две разделяемые переменные x и b. Каждое обращение к этим переменным защищено объектом-монитором this. Поэтому дисцилина синхронизации выполнена.
5.2. Шаг 1. Инструментация Инструмент Sapsan инструментирует программу, т.е. вставляет в скомпилированный код программы дополнительные инструкции. Вместе с основным кодом программы должны быть инструментированы все используемые им библиотеки, в том числе поставляемые с виртуальной машиной Java, на которой будет происходить выполнение. Инструмент вставляет перехваты начала и конца операций, входа и выхода из объектамонитора. Вставляет код для отслеживания результатов проверки условий в условных выражениях и записи в поля объектов. Инструментация необходима для сохранения трассы выполнения, анализа независимости блоков и управления переключением потоков.
5.3. Шаг 2. Прогон имеющихся тестов
Трассы операций;
3.
Тестовое покрытие.
•
2_F – блок от начала второго входа в монитор (стр. 23) до возврата из операции (стр. 28), выполнение оператора if (стр. 24) идет по ветке false.
•
3_T – блок от начала операции delete (стр. 4) до возврата из операции (стр. 9), выполнение оператора if (стр. 6) идет по ветке true.
•
3_F1 – блок от начала операции delete (стр. 4) до возврата из операции (стр. 11), выполнение оператора if (стр. 6) идет по ветке false, ложен первый операнд конъюнкции (x!=null).
•
3_F2 – блок от начала операции delete (стр. 4) до возврата из операции (стр. 11), выполнение оператора if (стр. 6) идет по ветке false, ложен второй операнд конъюнкции (b).
5.4. Шаг 3. Оценка покрытия Для того чтобы гарантировать линеаризуемость на данном шаге, мы должны убедиться, что выполнены два требования: 1. Множество трасс операций, выделенное при прогоне тестов, содержит все трассы операций, встречающиеся в трассах выполнений программы.
В примере Cell могут быть выделены блоки: • 1_T – блок от начала операции insert (стр. 15) до второго входа в монитор (стр. 23), выполнение оператора if (стр. 18) идет по ветке true. •
2_T – блок от начала второго входа в монитор (стр. 23) до возврата из операции (стр. 26), выполнение оператора if (стр. 24) идет по ветке true.
Заметим, что блок 3_F2 в последовательных тестах получить мы не можем, т.к. для его появления требуется, чтобы один из потоков выполнил запись в переменную x в операции insert, но не зафиксировал ее присваиванием переменной b значения true. Примеры трасс операций. Операция insert:
Шаги 2-4 выполняются только при наличии готового поставляемого с программой тестового набора. Причем метод не накладывает никаких ограничений на этот тестовый набор. Цель этих шагов – извлечь максимальную пользу из имеющихся тестов. В результате прогона тестов мы получаем: 1. Блоки инструкций; 2.
•
2.
1_F – блок от начала операции insert (стр. 15) до второго входа в монитор (стр. 23), выполнение оператора if (стр. 18) идет по ветке false.
Программа соответствует спецификации на последовательных выполнениях.
Данный шаг метода опирается на уже имеющиеся методы тестирования и оценки полноты тестов. Оценить, покрыли ли все трассы операций, помогает покрытие по путям в графе потока управления (path coverage) [1]. В примере 97
98
Cell оценка данного покрытия поможет выявить отсутствие трасс с блоком 3_F2. Для оценки соответствия спецификации на последовательных выполнениях разработано множество методов. Для примера Cell были разработаны тесты JUnit[26].
5.5. Шаг 4. Проверка достаточных условий На данном шаге запускается инструмент Sapsan, который проверяет, выполнены ли достаточные условия. Инструмент Sapsan выводит начальное отношение зависимости. Независимыми полагаются блоки, не изменяющие разделяемое состояние и блоки, обращающиеся к разделяемым переменным при непересекающихся множествах захваченных объектов мониторов. В примере Cell блоки 1_F, 2_F, 3_F1, 3_F2 независимы между собой, т.к. не изменяют разделяемое состояние. Остальные пары блоков потенциально зависимы. Инструмент ищет циклы. Если цикл найден, то достаточные условия не выполнены, иначе выполнены. В нашем примере циклы будут найдены, например α : 1 _ T < β : 3 _ T < α : 2 _ T = α : 1 _ T .
5.6. Шаг 5. Разработка многопоточных тестов Для использования продвинутых возможностей инструмента Sapsan необходимо разработать специальные тесты. К тестам предъявляются те же требования, что и на шаге 3. Кроме того, тесты обязаны предоставлять следующие возможности: 1. Создавать отдельный поток, в котором была достигнута заданная трасса операции. 2.
Переходить в состояние, в котором начинался тест.
3.
Вычислять конечное состояние.
4.
Проверять соответствие трассы спецификации.
На данный момент разработаны библиотеки, которые позволяют разрабатывать тесты с данными возможностями на основе JUnit и UniTESK[2,3]. Для примера Cell в качестве тестового набора использовались тестовые варианты JUnit. Каждый тестовый вариант testCase состоит из одного вызова операции с некоторыми параметрами. Для тестового варианта задан метод setUp – инициализации состояния и метод tearDown – приведения системы в начальное состояние. В процессе выполнения тестов сохраняется информация о том, каким тестовым вариантом была достигнута данная трасса. Возможность создания 99
отдельного потока обеспечивается библиотекой, которая по сохраненной информации находит тестовый вариант и создает выполняющий его поток. Переход в состояние начала теста обеспечивается методами setUp и tearDown. Для всех тестовых вариантов задан метод getState, вычисляющий текущее состояние системы. За счет этого обеспечивается возможность вычисления конечного состояния. В отличие от обычных тестов JUnit, проверяющих соответствие спецификации прямо в тестовых вариантах (assert…), в многопоточных тестах проверки должны быть вынесены в отдельный метод checkResult. Таким образом, обычный запуск тестового варианта состоит из последовательности: setUp, testCase, getState, checkResult, tearDown.
5.7. Шаг 6. Эвристический анализ программы Эвристический анализ программы включает действия шагов 2 и 4, т.е. тесты выполняются обычным образом, собираются блоки, трассы, покрытие, дальше происходит проверка достаточных условий линеаризуемости. Если они не выполнены, то начинается основная часть эвристического анализа. Как видно из достаточных условий, на их выполнимость оказывает влияние независимость блоков и появление трасс операций в трассах выполнений программы. Чем больше у нас знаний о независимости блоков и непоявлении трасс, тем больше шансов, что достаточные условия будут выполнены. На шаге 4 мы считали независимыми только те блоки, которые были гарантированно независимы. Считали, что любая комбинация из трасс операций составляет трассу выполнения. На этом же шаге инструмент Sapsan уточняет эту информацию эвристическими методами. Более подробно анализ описан в разделе 6. В результате эвристического анализа для примера Cell мы имеем: 1. Установлена независимость блоков (1_T, 1_T), (1_T, 2_T), (2_T, 2_T), (3_T, 3_T), (3_T, 3_F2), (1_T, 2_F), (2_T, 1_F), (2_T, 2_F), (2_T, 3_F1), (3_T, 2_F).
Выведено, что блоки 1_T, 2_T, 3_T, 3_F1 не встречаются между блоками 1_T, 2_T.
α :1_ T < β : 3 _ T < α : 2 _ T = α :1_ T , Циклы вида α :1_ T < β : 3 _ F 2 < α : 2 _ T = α :1_ T становятся недостижимыми, остальные невозможны по установленной независимости. Поэтому программа Cell линеаризуема.
Множество трасс операций
Таблица независимости и непоявления
5.8. Шаг 7. Анализ результатов Анализ достаточных условий самолинеаризуемости
Возможно три варианта ответов инструмента «Да», «Нет», «Не удалось установить». Если инструмент выдает ответ «Да», то программа линеаризуема. Если ответ «Нет», то программа не линеаризуема и инструмент выдает трасу выполнения, набор потоков и состояние, на которых нарушается свойство линеаризуемости. Возможен также и третий вариант, когда инструменту не удалось установить линеаризуема ли программа. На выходе в этом случае трассы операций, для которых не выполнены достаточные условия. Эти подозрительные трассы могут быть проанализированы вручную.
Трассы операций, для которых не выполнены достаточные условия
1) Поиск нелинеаризуемого выполнения
2) Установление независимости
6. Эвристический анализ программы Схема работы анализа показана на рис. 2. Для найденных трасс операций проверяются достаточные условия самолинеаризуемости. Если достаточные условия выполнены, то, следовательно, мы показали, что программа линеаризуема. Иначе возможно три варианта: 1. В программе присутствует трасса, на которой нарушается линеаризуемость; 2.
Наша информация о независимости блоков неполна;
3.
Невозможно установить линеаризуемость, используя достаточные условия.
В первом случае нам необходимо попытаться найти подходящую трассу выполнения. Во втором попытаться установить независимость блоков. Эвристический анализ в инструменте Sapsan основан на классических алгоритмах поиска. На входе у алгоритмов программа и набор пользовательских потоков Ψ (см. разд. 3). Имея Ψ , программу можно выполнить и получить какую-то трассу выполнения. В зависимости от чередования инструкций могут получаться разные трассы. Все множество трасс для заданного Ψ – это трассы, полученные при всех возможных чередованиях инструкций потоков. Трасс, как правило, очень много, но не все из них требуются для проверки требуемых свойств. В зависимости от проверяемого свойства выбирается условие эквивалентности трасс. Например, для установления линеаризуемости можно считать эквивалентными трассы с одинаковыми историями. Задача алгоритма поиска – построить полное множество трасс, т.е. множество, содержащее все неэквивалентные трассы. 101
Да
Нет
Не удалось установить
Рис. 2. Установление линеаризуемости
6.1. Алгоритмы поиска В данной работе мы используем алгоритмы, описанные в работе Годфруа [14]. Эти алгоритмы существенно используют понятие независимости для оптимизации поиска. Различают два класса алгоритмов поиска: 1. Алгоритмы с сохранением состояния; 2.
Алгоритмы без сохранения состояния.
Для того чтобы осуществлять поиск, необходимо иметь возможность возвращаться в предыдущее состояние. В существующих алгоритмах это достигается двумя способами. В первом способе, возврат осуществляется восстановлением ранее сохраненного состояния. Он используется в алгоритмах с сохранением состояния. Второй способ, использующийся в алгоритмах без сохранения состояния, заключается в сбросе программы в начальное состояние и перевыполнении до нужного состояния. Для этого требуется наличие соответствующего механизма сброса. Как правило, имея возможность хранить состояния, мы имеем возможность сравнивать их, что может быть использовано для сокращения перебора. Кроме того, сравнение состояний дает возможность обнаруживать циклы в графе 102
переходов. Несмотря на эти преимущества, в данной работе выбран второй способ, потому как хранение состояний, которое потребовалось бы в первом способе обладает рядом недостатков: 1. Требует значительных объемов памяти для хранения состояний 2.
Требует моделирования состояний недоступных для чтения.
Известные алгоритмы, использующие первый способ, реализованы в виде специальной виртуальной машины [5,18,22], которая хранит пройденные состояния и позволяет управлять последовательностью выполнения инструкций. Эти машины с легкостью справляются со всеми инструкциями Java кода. Однако программы на Java, кроме чистого кода содержат вызовы процедур, реализованных на других языках, называемых внутренними (native) методами. Для этих методов требуется написать модель, сохраняющую и восстанавливающую состояния. На практике программы с внутренними методами встречаются часто. Практически все стандартные библиотеки содержат внутренние методы. Например, библиотека работы с сетью. Кроме того, программы часто обращаются к системам, написанным на других языках, например, к базам данных.
6.2. Установление независимости В инструменте Sapsan в качестве основы используется алгоритм поиска достижимых выполнений без сохранения состояний [14], реализованный в инструменте VeriSoft. Этот алгоритм был оснащен возможностью поиска по шаблону, что позволило сократить пространство поиска. Шаблон задает порядок, в котором должны встретиться переходы в искомой трассе. Шаблоны строятся на основе пар путей, для которых не выполнено достаточное условие самолинеаризуемости (Утверждение 2). Поиск по шаблону нацеливается только на трассы, удовлетворяющие шаблону, т.е. трассы выполнения, не подходящие под шаблон, отбрасываются. В примере Cell с помощью поиска по шаблонам было установлено, что следующие пары блоков не появляются ни в одной из трасс: (1_F, 1_T), (1_T, 1_T), (1_T, 2_T), (2_T, 1_T), (1_T, 3_T), (1_T, 3_F1), (3_F2, 1_T), (2_T, 2_T), (3_T, 2_T), (2_T, 3_F1), (3_F1, 2_T), (2_T, 3_F2), (3_T, 1_F), (3_T, 3_T), (3_F1, 3_T), (3_T, 3_F2), (3_F2, 3_T). Из непоявления пар можно сделать вывод о зависимости блоков. Если пара (X,Y) появляется, а в том же состоянии пара (Y,X) – нет, то данная пара является гарантированно зависимой. Отсюда мы получили, что пары (1_T, 1_T), (1_T, 2_T), (2_T, 2_T), (3_T, 3_T), (3_T, 3_F2), (1_T, 2_F), (2_T, 1_F), (2_T, 2_F), (2_T, 3_F1), (3_T, 2_F) – зависимы. Если не появляются обе пары (X,Y) и (Y,X), то пара независима. Так мы получили независимость (1_T, 1_F), (1_T, 3_T), (1_T, 3_F1), (3_F2, 1_T), (3_T, 2_T). Остальные пары (2_T, 3_F2), (3_T, 1_F), (3_F1, 3_T) были признаны независимыми, так как перестановка их местами не меняет конечное состояние ни в одном из выполнений. 103
Метод установки независимости является эвристическим, потому что в общем случае ответить на вопрос, достижимо данное выполнение, невозможно. Поэтому для поиска выбирается ограниченный набор пользовательских потоков Ψ . Ограничения на количество потоков задает пользователь.
7. Результаты применения метода На данный момент метод был успешно применен на нескольких простых примерах, встречающихся в литературе. Были найдены известные ошибки в StringBuffer[11,15] и Vector[23]. Для примера MultiSet из [6] инструмент не смог установить линеаризуемость (MultiSet не самолинеаризуем), но было показано, что параллельное выполнение всех троек операций удовлетворяет спецификации. Кроме того, были написаны многопоточные тесты для реализации кэша Ehcache [25], который оптимизирует доступ к хранящимся в нем элементам, размещая часто используемые элементы в памяти и сохраняя остальные на диске. Реализация этого кэша составляет примерно 40 тысяч строк кода на Java. Было выявлено нарушение достаточных условий и найдено выполнение не соответствующее спецификации.
8. Заключение В работе описан новый метод Sapsan, поддержанный одноименным инструментом, который позволяет автоматизированно проверять свойство линеаризуемости программ. Во введении были рассмотрены различные подходы к проверке линеаризуемости. Было отмечено, что существующие инструменты нацелены на проверку независимых от спецификации свойств. В методе Sapsan, напротив, спецификация является важной составляющей. В эвристическом анализе спецификация позволяет утверждать, что ошибка реально существует, а не просто предупреждать о возможной ошибке. Кроме того, спецификация используется при установлении независимости блоков. По сравнению с инструментами проверки моделей (model checking) метод Sapsan позволяет делать заключение о линеаризуемости программы для произвольного набора пользовательских потоков. Кроме того, алгоритмы эвристического анализа в силу их узкоспециализированной направленности на установление независимости оказываются быстрее классических алгоритмов поиска. Экспериментальные результаты показывают, что метод применим для практически значимых приложений. Литература [1] Борис Бейзер. Тестирование черного ящика. Питер, 2004.
104
[2] Виктор В. Кулямин, Александр К. Петренко, Александр С. Косачев, Игорь Б. Бурдонов. Подход UniTESK к разработке тестов. Программирование, том 29, стр. 25-43, 2003. [3] Алексей В. Хорошилов. Спецификация и тестирование компонентов с асинхронным интерфейсом. Диссертация на соискание ученой степени кандидата физико-математических наук, 2006. [4] Rajeev Alur, Ken Mcmillan, Doron Peled. Model-checking of correctness conditions for concurrent objects. Proceedings of the 11th Annual IEEE Symposium on Logic in Computer Science, стр. 219-228, 1996. [5] Derek Bruening. Systematic testing of multithreaded Java programs. Master's thesis, MIT, 1999. [6] Tayfun Elmas, Serdar Tasiran, Shaz Qadeer. VYRD: verifYing concurrent programs by runtime refinement-violation detection. Proceedings of the ACM SIGPLAN Conference on Programming Language Design and Implementation, стр. 27-37, 2005. [7] Tayfun Elmas, Serdar Tasiran. VyrdMC: Driving Runtime Refinement Checking with Model Checkers. Proceedings of the Fifth Workshop on Runtime Verification, том 144(4), стр. 41-56, 2005. [8] Tayfun Elmas, Shaz Qadeer, Serdar Tasiran. Goldilocks: a race and transaction-aware Java runtime. Proceedings of the ACM SIGPLAN Conference on Programming Language Design and Implementation, стр. 245-255, 2007. [9] Cormac Flanagan, Patrice Godefroid. Dynamic partial-order reduction for model checking software. Proceedings of the 32nd ACM SIGPLAN-SIGACT Symposium on the Principles of Programming Languages, стр. 110-121, 2005. [10] Cormac Flanagan, Stephen N. Freund. Atomizer: A dynamic atomicity checker for multithreaded programs. Proceedings of the ACM Symposium on the Principles of Programming Languages, стр. 256-267, 2004. [11] Cormac Flanagan, Shaz Qadeer. A type and effect system for atomicity. Proceedings of the ACM Conference on Programming Language Design and Implementation, том 38(5), стр. 338-349, 2003. [12] Cormac Flanagan. Verifying commit-atomicity using model-checking. Proceedings of 11th International SPIN Workshop, том 2989, стр. 252-266, 2004. [13] Patrice Godefroid. Partial-order methods for the verification of concurrent systems: an approach to the state-explosion problem. Springer-Verlag, 1996. [14] Patrice Godefroid. Model checking for programming languages using Verisoft. Symposium on Principles of Programming Languages, стр. 174-186, 1997. [15] John Hatcliff, Robby, Matthew B. Dwyer. Verifying atomicity specifications for concurrent object-oriented software using model checking. Proceedings of the Fifth International Conference on Verification, Model Checking and Abstract Interpretation, 2004. [16] Maurice P. Herlihy, Jeannette M. Wing. Linearizability: a correctness condition for concurrent objects. ACM Transactions on Programming Languages and Systems, стр. 463-492, 1990. [17] Leslie Lamport. Specifying concurrent program modules. ACM Transactions on Programming Languages and Systems, том 5(2), стр. 190-222, 1983. [18] Vadim S. Mutilin. Concurrent testing of Java components using Java PathFinder. Second International Symposium on Leveraging Applications of Formal Methods, Verification and Validation, том 2, стр. 53-59, 2006. [19] Christos H. Papadimitriou. The serializability of concurrent database updates. Journal of the ACM, том 26(4), стр. 631-653, 1979.
105
[20] Doron Peled. Combining partial order reductions with on-the-fly model checking. Formal Methods in System Design, том 8, стр. 39-64, 1996. [21] Stefan Savage, Michael Burrows, Greg Nelson, Patrick Sobalvarro, Thomas Anderson. Eraser: A dynamic data race detector for multithreaded programs. ACM Transactions on Computer Systems, том 15(4), стр. 391-411, 1997. [22] Willem Visser, Klaus Havelund, Guillaume Brat, Seungjoon Park, Flavio Lerda. Model checking programs. Automated Software Engineering, том 10(2), стр. 203-232, 2003. [23] Liqiang Wang, Scott D. Stoller. Runtime analysis of atomicity for multithreaded programs. IEEE Transactions on Software Engineering, том 32(2), стр. 93-110, 2006. [24] Liqiang Wang, Scott D. Stoller. Static analysis of atomicity for programs with nonblocking synchronization. Proceedings of the Tenth ACM SIGPLAN Symposium on Principles and Practice of Parallel Programming, стр. 61-71, 2005. [25] http://ehcache.sourceforge.net [26] http://www.junit.org
106
Метод формальной спецификации аппаратуры с конвейерной организацией и его приложение к задачам функционального тестирования А.С. Камкин [email protected]
Аннотация. В работе рассматривается метод формальной спецификации аппаратуры с конвейерной организацией, который основан на пред- и постусловиях стадий выполнения операций. Данный метод может быть использован для функционального тестирования моделей аппаратуры, поскольку на основе спецификаций предлагаемого вида можно решать основные задачи тестирования: проверку правильности поведения системы и генерацию тестовой последовательности. Метод был успешно применен для тестирования нескольких модулей промышленного микропроцессора. В результате тестирования были найдены критичные ошибки, не обнаруженные при использовании других подходов.
1. Введение Современные электронные устройства является очень сложными системами, производство которых можно начинать только после тщательного проектирования. Проектирование электроники осуществляется с помощью специализированных языков описания аппаратуры (HDLs, Hardware Description Languages), например, Verilog или VHDL [1], позволяющих абстрагироваться от деталей расположения и соединения отдельных элементов электронной схемы. Проект устройства на таком языке является исполнимой программой, у которой имеются параметры, соответствующие входам устройства, и называется моделью1. После окончания проектирования
1
Такие модели также называют HDL-моделями или RTL-моделями, поскольку проектирование ведется на уровне регистровых передач (RTL, Register Transfer Level). 107
модель преобразуется в точное описание расположения и соединения друг с другом отдельных элементов электронной схемы устройства. Поскольку современное аппаратное обеспечение является очень сложным, при его проектировании часто делаются ошибки. Ошибка проектирования впоследствии может проявиться как некорректное, несоответствующее требованиям поведение устройства в определенной ситуации. Ошибки в аппаратуре способны приносить огромный ущерб, однако их исправление в уже изготовленном устройстве практически невозможно и требует колоссальных затрат [2]. Поэтому большую часть ошибок крайне желательно выявить и исправить еще в процессе проектирования, при разработке модели. Для проверки корректности моделей аппаратуры используют различные методы функциональной верификации [3, 4]. Наиболее широко используемым на практике методом верификации является имитационное тестирование (simulation-based verification)2. Этот метод состоит в программной имитации работы устройства, описываемого моделью, с помощью симулятора, в рамках ряда специально разработанных тестовых сценариев, которые представляют собой основные варианты использования функций этого устройства, возможные при его эксплуатации. Главной задачей тестирования является проверка соответствия поведения системы предъявляемым к ней требованиям. Для возможности автоматизации такой проверки требования к системе должны быть представлены в машиночитаемой форме, которую называют формальными спецификациями. Настоящая работа посвящена формальной спецификации аппаратуры с конвейерной организацией. В общих словах, конвейерная организация означает, что в один и тот же момент времени устройство может обрабатывать сразу несколько операций, однако поток операций, подаваемых на выполнение, является последовательным [5]. Конвейерная обработка является основным способом повышения производительности разного рода устройств; в частности, по такому принципу проектируются микропроцессоры, их модули и подсистемы. В статье рассматривается метод формальной спецификации конвейерной аппаратуры, основанный на пред- и постусловиях стадий выполнения операций, и описывается его приложение к задачам функционального тестирования. Несколько слов о том, как организована статья. Во втором разделе, который начинается сразу за этим абзаца, даются общие сведения об аппаратуре с конвейерной организацией. Третий раздел описывает предлагаемый метод формальной спецификации. В четвертом разделе описывается применение метода для решения задач тестирования: проверки правильности поведения и генерации тестовой последовательности. Пятый раздел содержит сравнение 2
В дальнейшем для краткости будем называть имитационное тестирование аппаратуры просто тестированием, хотя под тестированием обычно понимается проверка готовой микросхемы. 108
рассматриваемого метода с существующими подходами. В шестом разделе описывается практическая апробация подхода. Наконец, в последнем, седьмом разделе делается заключение и очерчиваются направления дальнейших исследований.
2. Аппаратура с конвейерной организацией Модели аппаратуры на HDL-языках представляют собой системы из нескольких взаимодействующих модулей. Как и в традиционных языках программирования, модули используются для декомпозиции сложной системы на множество независимых или слабо связанных подсистем. Каждый модуль имеет интерфейс – набор входов и выходов, через которые осуществляется соединение модуля с окружением, и реализацию, определяющую способ обработки модулем входных сигналов: вычисление значений выходных сигналов и изменение внутреннего состояния. Для удобства будем считать, что спецификация и тестирование осуществляются на уровне отдельных модулей. Как правило, модули цифровой аппаратуры работают синхронно под управлением тактового сигнала. Фронты тактового сигнала разбивают непрерывное время на дискретный набор интервалов, называемых тактами. Что делать модулю на текущем такте, определяется значениями входных сигналов и внутренним состоянием модуля. Часть входов модуля определяет операцию, которую модулю следует выполнить, – такие входы называются управляющими; другая часть определяет аргументы операции, – такие входы называются информационными. Среди реализуемых модулем операций обычно присутствует «пустая» операция NOP (No Operation), означающая бездействие, отсутствие операции. Состояние модуля также можно разбить на две составляющие: состояние управления и состояние данных. Первая из них является частью управляющей логики модуля и используется для управления процессами выполнения операций. Состояние данных, как видно из названия, представляет собой внутренние данные модуля. Рассмотрим, как осуществляется выполнение модулем однотактной операции (см. рис. 1). До начала очередного такта окружение устанавливает код операции и аргументы на соответствующих входах модуля (на рис. 1 – сигнал подачи операции). Операция запускается с началом такта. За этот такт модуль производит необходимые вычисления, изменяет внутреннее состояние и устанавливает значения выходных сигналов, которые окружение может использовать, начиная со следующего такта (результат операции).
Рис. 1. Временная диаграмма сигналов для однотактной операции. В отличие от однотактной операции, результат многотактной операции вычисляется постепенно такт за тактом. Пусть операция A выполняется модулем за L тактов. Тогда на каждом такте i ∈ {1, ..., L} модуль выполняет некоторую микрооперацию Ai, а окружение после окончания каждого такта получает некоторый частичный результат. Представление многотактной операции A в виде последовательности микроопераций (A1, ..., AL) называется декомпозицией операции A на стадии3. В этой статье акцент делается на конвейерные модули, то есть модули, в которых операции подаются на выполнение последовательно одна за другой, но процессы их выполнения могут пересекаться по времени (см. рис. 2). Такие модули широко распространены, особенно в практике проектирования микропроцессоров. Можно выделить два типа конвейеров: конвейеры без блокировок (pipelines without interlocked stages) и конвейеры с блокировками (pipelines with interlocked stages). В конвейерах первого типа выполнение последовательных стадий, относящихся к одной операции, осуществляется на последовательных тактах: если стадия Ai была выполнена на такте j, то стадия Ai+1 будет выполнена на такте j+1. В конвейерах второго типа это свойство, вообще говоря, не выполнено – между тактами, на которых выполняются последовательные стадии, могут быть задержки: если стадия Ai была выполнена на такте j, то стадия Ai+1 будет выполнена на такте j+1+δ, где δ ≥ 0 – величина задержки между стадиями, которая определяется на основе состояния управления модуля. 3
109
В данной статье термины «стадия» и «микрооперация» являются синонимами. 110
3.1. Вспомогательные понятия Введем основные понятия теории автоматов, на которых базируется контрактная спецификация конвейера. Определение. Автоматом называется четверка 〈S, X, Y, T〉, в которой: •
S – множество состояний;
•
X – множество стимулов;
•
Y – множество реакций;
•
T ⊆ S × X × Y × S – отношение переходов. Каждый переход t ∈ T имеет вид (st, xt, yt, s't), где:
Рис. 2. Выполнение двух операций на конвейере. Блокировки в конвейере предназначены для того, чтобы избежать конфликтов использования ресурсов между операциями и сохранить целостность потока данных. Например, в приведенной ниже ассемблерной программе инструкция add складывает содержимое регистра r2 с содержимым регистра r3 и сохраняет результат в регистре r1. Следом за ней идет инструкция sub, которая использует значение регистра r1. В этом случае доступ к регистру r1 из инструкции sub блокируется до тех пор, пока инструкция add не запишет в него результат – в противном случае инструкция sub считает некорректное значение. add r1, r2, r3 // запись в регистр r1 sub r4, r1, r5 // чтение из регистра r1 Конвейерная организация аппаратуры позволяет увеличить производительность, но одновременно привносит сложность в реализацию, что сказывается и на сложности тестирования. Во-первых, усложняется проверка правильности поведения системы, поскольку стадии разных операций могут перекрываться по времени и это необходимо учитывать. Вовторых, из-за наличия нетривиальной управляющей логики увеличивается пространство состояний, что усложняет построение тестовой последовательности.
3. Формальная спецификация конвейера В данном разделе рассматривается метод формальной спецификации аппаратуры с конвейерной организацией, основанный на пред- и постусловиях стадий выполнения операций. Формальные спецификации предлагаемого вида называются контрактными спецификациями конвейера.
111
•
st ∈ S – пресостояние;
•
xt ∈ X – стимул;
•
yt ∈ Y – реакция;
•
s't ∈ S – постсостояние.
Модель автомата, расширенная контекстными переменными, входными параметрами стимулов, выходными параметрами реакций, предикатами и функциями переходов, определенными над контекстными переменными и входными параметрами, называется расширенным автоматом. Расширенные автоматы широко используются в информатике для моделирования программных и аппаратных систем. Обычно считают, что автомат, лежащий в основе расширенного автомата, моделирует управляющую логику системы, а контекстные переменные, параметры и функции, определенные над ними, – потоки данных. Для удобства объединим контекстные переменные и параметры расширенного автомата общим термином переменные. В последующих определениях будем считать, что для каждой переменной v задано множество возможных значений Domv, называемое доменом переменной v. Определение. Пусть V – некоторое множество переменных. Функцией означивания переменных из V называется отображение ν, которое каждой переменной x ∈ V ставит в соответствие ее значение ν(x) ∈ Domx. Множество всех функций означивания переменных из V будем обозначать символом DomV и называть множеством значений переменных из V. Определение. Расширенным автоматом называется шестерка 〈S, V, I ∪ O, X, Y, T〉, в которой:
112
•
S – множество состояний;
•
V – множество контекстных переменных;
Функция означивания контекстных переменных называется контекстом расширенного автомата. •
I ∪ O ⊆ V – множество входных и выходных параметров; Заметим, что в данном определении множество входных и выходных параметров является подмножеством множества контекстных переменных.
•
X – множество стимулов; С каждым стимулом x ∈ X связано множество входных параметров Inx ⊆ I. Множество значений входных параметров стимула x будем обозначать Domx.
•
Y – множество реакций;
•
T – отношение переходов. С каждым переходом t ∈ T связаны два подмножества множества контекстных переменных: o
Uset ⊆ V \ O переменных;
o
Deft ⊆ V \ I – множество определяемых контекстных переменных.
–
множество
используемых
контекстных
Каждый переход t ∈ T имеет вид (st, xt, yt, γt, δt, s't), где: o
δt: Domx × DomUse → DomDef – функция обновления контекста;
o
s't ∈ S – постсостояние.
pres, x(p, ν) = ∨γ ∈ Сond(s, x) γ, где Cond(s, x) = {γt | st = s, xt = x}. Если Cond(s, x) = ∅, pres, x полагается равным false. Определение. Пара x[p], состоящая из стимула x и набора значений его входных параметров p, называется инициализированным стимулом. Множество всех инициализированных стимулов будем обозначать символом Param(X). Аналогичное определение и связанные с ними обозначения имеют место и для переходов расширенного автомата. Определение. Пара t[p], состоящая из перехода t и набора значений входных параметров p соответствующего стимула xt называется инициализированным переходом. Определение. Инициализированный переход t[p] называется допустимым в конфигурации (s, ν), если st = s, xt ∈ Init(s) и γt(p, ν) = true. Расширенный автомат функционирует следующим образом. Находясь в некоторой конфигурации, он получает инициализированный стимул и вычисляет множество допустимых инициализированных переходов. После этого он выполняет один из переходов, недетерминированно выбранный из вычисленного множества. В процессе выполнения расширенный автомат изменяет значения входных параметров, обновляет контекст и переходит из пресостояния в постсостояние.
3.2. Спецификация конвейера без блокировок Для наглядности сначала определим контрактную спецификацию для случая конвейера без блокировок. Определение. Контрактной спецификацией конвейера без блокировок длины L называется шестерка 〈V, ν0, I ∪ O, X ∪ {τ}, Z ∪ {ε}, ρ〉, в которой:
Определение. Конфигурацией расширенного автомата называется пара (s, ν) ∈ S × DomV, состоящая из состояния и контекста. В дальнейшем будем рассматривать расширенные автоматы c выделенной начальной конфигурацией (s0, ν0). Такие автоматы называются инициальными и описываются семерками 〈S, V, (s0, ν0), I ∪ O, X, Y, T〉. Будем использовать следующие обозначения: Tran(s) = {t ∈ T | st = s} Init(s) = {xt | t ∈ Tran(s)} Определение. Предусловием стимула x в состоянии s называется предикат pres, x: Domx × DomV → {true, false}, определяемый равенством 113
•
V – множество контекстных переменных;
•
ν0 ∈ DomV – начальный контекст;
•
I ∪ O ⊆ V – множество входных и выходных параметров;
•
X ∪ {τ} – множество стимулов; С каждым стимулом x ∈ X ∪ {τ} связано множество входных параметров Inx ⊆ I. Множество значений входных параметров стимула x будем обозначать Domx. Множество PL = (X ∪ {τ})L называется множеством состояний управления, а его элемент π0 = (τ, ..., τ) – начальным состоянием управления. Для каждого стимула x также заданы: •
Множество стадий включает выделенную стадию ε, называемую пустой стадией, для которой выполнены следующие свойства:
•
•
Useε = ∅;
•
Defε = ∅;
•
postε ≡ true.
•
Usey ∩ Defz ≠ ∅ – конфликт типа «чтение-запись»;
•
Defy ∩ Defz ≠ ∅ – конфликт типа «запись-запись».
Если ни для каких стадий y ≠ z ни одно из перечисленных условий не выполнено, множество стадий называется согласованным.
Для каждой стадии z заданы: –
предшествующие; оператор связывания стадий конвейера возвращает множество стадий, выполняемых в текущем состоянии управления. Определение. Множество стадий называется конфликтным, если для некоторых стадий y ≠ z из этого множества выполнено хотя бы одно из следующих условий:
ρ: X ∪ {τ} → (Z ∪ {ε})L – функция композиции операций.
Определение. Состояние управления π называется согласованным, если для всех 1 ≤ i ≠ j ≤ L, таких что ρi(πi) ≠ ε и ρj(πj) ≠ ε, выполняется ρi(πi) ≠ ρj(πj) и множество стадий θ(π) является согласованным. Определение. Контрактная спецификация конвейера называется согласованной, если для всех стимулов x и состояний управления π из того, что π согласованно и γx(π) = true, вытекает, что x ° π согласованно. В дальнейшем будем рассматривать только согласованные контрактные спецификации конвейера. Пусть Σ = 〈V, ν0, I ∪ O, X ∪ {τ}, Z ∪ {ε}, ρ〉 – контрактная спецификация конвейера без блокировок длины L. Ее можно интерпретировать как инициальный расширенный автомат Δ = 〈S, V, (π0, ν0), I ∪ O, X ∪ {τ}, Y ∪ {ξ}, T〉 с выделенными тактовым стимулом τ и пустой реакцией ξ, устроенный следующим образом:
Последовательность стадий ρ(x) называется операцией стимула x. Функция ρ обладает следующим свойством: ρ(τ) = (ε, ..., ε)4. Для интерпретации контрактной спецификации конвейера будем использовать оператор сдвига конвейера и оператор связывания стадий конвейера. Определение. Функция °: (X ∪ {τ}) × PL → PL, определяемая равенством x0 ° (x1, ..., xL) = (x0, ..., xL-1), называется оператором сдвига конвейера. Определение. Функция θ: PL → 2Z, определяемая равенством θ(π) = {ρ1(π1), ..., ρL(πL)} \ {ε}, называется оператором связывания стадий конвейера. Смысл этих операторов достаточно прост: оператор сдвига конвейера добавляет в начало конвейера новый стимул, сдвигая при этом
S = PL – состояниями расширенного автомата Δ являются состояния управления контрактной спецификации Σ;
•
π0 = (τ, ..., τ) – начальным состоянием является начальное состояние управления;
•
Y = 2Z – реакциями расширенного автомата Δ являются множества стадий контрактной спецификации Σ;
•
ξ = ∅ – пустой реакцией является пустое множество стадий;
•
отношение переходов T расширенного автомата Δ для всех π ∈ S и x ∈ X ∪ {τ} таких, что γx(π) = true, содержит все возможные переходы t = (π, x, y, γ, δ, π'), в которых: •
4
Стимул τ и соответствующая ему последовательность пустых стадий (ε, ..., ε) формализуют операцию NOP. 115
•
116
Uset = ∪z ∈ θ(π')Usez
множество используемых контекстных переменных получается объединением соответствующих множеств для стадий из θ(π'); •
Deft = ∪z ∈ θ(π')Defz множество определяемых контекстных переменных получается объединением соответствующих множеств для стадий из θ(π');
•
π' = x ° π
Определение. Состоянием управления называется множество состояний обработки стимулов. Пустое множество состояний обработки стимулов называется начальным состоянием управления. Множество всевозможных состояний управления для конвейера длины L будем обозначать символом PL. Определение. Контрактной спецификацией конвейера с блокировками длины L называется шестерка 〈V, ν0, I ∪ O, X ∪ {τ}, Z ∪ {ε}, ρ〉, в которой:
постсостояние получается сдвигом конвейера; •
y = θ(π') реакция определяется как результат связывания стадий, выполненного в постсостоянии перехода;
•
•
•
V – множество контекстных переменных;
•
ν0 ∈ DomV – начальный контекст;
•
I ∪ O ⊆ V – множество входных и выходных параметров;
•
X ∪ {τ} – множество стимулов;
γ(p, ν) = prex(p, ν)
Для каждого стимула x ∈ X ∪ {τ} заданы:
охранный предикат определяется как предусловие соответствующего стимула;
результирующий контекст ν' = δ(p, ν) для всех ν ∈ DomV, таких что prex(p, ν) = true, удовлетворяют предикату:
Φπ, x(p, ν, ν') = ∨z ∈ θ(π') postz(ν|Use, ν'|Def). Определение. Предикат Φπ, x(p, ν, ν') называется тестовым оракулом стимула x в состоянии управления π. Тестовому оракулу отводится основная роль в проверке правильности поведения тестируемого устройства (см. раздел «Проверка правильности поведения»).
•
Для каждой стадии z ∈ Z ∪ {ε} заданы:
Определение. Автомат, лежащий в основе расширенного автомата Δ(Σ), интерпретирующего контрактную спецификацию Σ, называется управляющим автоматом спецификации Σ и обозначается Ω(Σ). Управляющий автомат является формальной моделью управляющей логики устройства. На основе обхода графа состояний управляющего автомата осуществляется генерация тестовой последовательности (см. подраздел 4.2). •
3.3. Спецификация конвейера с блокировками Обобщим введенное ранее понятие контрактной спецификации на случай конвейера, в котором возможны блокировки. Дадим несколько вспомогательных определений. Определение. Состоянием обработки стимула или процессом называется пара (x, l), в которой: •
x ∈ X ∪ {τ} – обрабатываемый стимул;
•
l ∈ {1, ..., L} – номер стадии обработки стимула. 117
Z ∪ {ε} – множество стадий; •
Usez ⊆ V \ O переменных;
•
Defz ⊆ V \ I – множество определяемых контекстных переменных;
ρ: X ∪ {τ} → (Z ∪ {ε})L – функция композиции операций.
Заметим, что по сравнению с определением контрактной спецификации конвейера без блокировок в данном определении появились охранные предикаты стадий – именно они используются для описания блокировок конвейера. Определение. Процесс (x, l) называется активным в состоянии управления π, если γz(π) = true, где z = ρl(x). В противном случае процесс называется заблокированным. 118
Определение. Множество Enabled(π) = {(x, l) ∈ π | γz(π) = true, где z = ρl(x)} называется множеством активных процессов в состоянии управления π. Определение. Множество Locked(π) = π \ Enabled(π), являющееся дополнением множества Enabled(π), называется множеством заблокированных процессов в состоянии управления π. Отличие в интерпретации контрактной спецификации конвейера с блокировками по сравнению со случаем без блокировок заключается в измененной семантике операторов сдвига конвейера и связывания стадий. Определение. Функция °: (X ∪ {τ}) × PL → PL, которая для всех пар (x, π) принимает значение x ° π, равное объединению следующих множеств: •
Locked(π);
•
{(x, l+1) | (x, l) ∈ Enabled(π) ∧ l < L};
•
{(x, 1)}, если x ≠ τ; ∅, если x = τ.
называется оператором сдвига конвейера. Определение. Функция θ: PL → 2Z, определяемая равенством θ(π) = {ρl(x) | (x, l) ∈ Enabled(π)} \ {ε}, называется оператором связывания стадий конвейера.
4. Приложение к задачам тестирования Рассмотрим применение контрактных спецификаций конвейера для решения основных задач тестирования: проверки правильности поведения и генерации тестовой последовательности.
4.1. Проверка правильности поведения Определим отношение соответствия между контрактными спецификациями конвейера и инициальными расширенными автоматами5. Рассмотрим контрактную спецификацию Σ = 〈VS, ν0S, IS ∪ OS, XS ∪ {τS}, ZS ∪ {εS}, ρS〉 конвейера без блокировок длины L и инициальный расширенный автомат Δ = 〈SI, VI, (s0I, ν0I), II ∪ OI, XI ∪ {τI}, YI ∪ {ξI}, TI〉 с выделенными тактовым стимулом τI и пустой реакцией ξI. Введем несколько вспомогательных понятий. Определение. Сюрьективное отображение ϕS→: PLS → SI, обладающее свойством ϕS→(∅) = s0I, называется функцией соответствия состояний.
5
Для возможности формального определения отношения соответствия предполагается, что реализация описывается расширенным автоматом. 119
Определение. Отображение ϕX→: Param(XS ∪ {τS}) → Param(XI ∪ {τI}), → S обладающее тем свойством, что ϕX (x ) = τI тогда и только тогда, когда xS = τS называется функцией соответствия стимулов. Определение. Биективное отображение ϕY←: YI ∪ {ξI} → 2Z, где Z = ZS, обладающее тем свойством, что ϕY←(yI) = ∅ тогда и только тогда, когда yI = ξI называется функцией соответствия реакций. Определение. Отображение ϕV←: DomV(I) → DomV(S), где V(I) = VI и V(S) = VS, обладающее свойством ϕV←(ν0I) = ν0S, называется функцией соответствия контекстов. Определение. Будем говорить, что инициальный расширенный автомат соответствует контрактной спецификации для заданных функций соответствия 〈ϕS→, ϕX→, ϕY←, ϕV←〉, если для всех πS ∈ PLS, S S S S I S x [p ] ∈ Param(X ∪ {τ }) и ν ∈ DomV(I) из того, что γx(π ) = true, вытекает, что xI ∈ Init(sI), причем если prex(pS, νS) = true, то pres, x(pI, νI) = true, где sI = ϕS→(πS), xI[pI] = ϕX→(xS[pS]) и νS = ϕV←(νI), при этом для каждого выполнено инициализированного перехода tI[pI] ∈ Enabled(sI, xI, νI) S S → ← s't = ϕS (π' ), ϕY (yt) = θ(π' ) и Φπ, x(pS, νS, ν'S) = true, где π'S = xS ° πS и ν'S = ϕV←(ν't). Заметим, что реализация может содержать неспецифицированные переходы, то есть переходы, в которых для определенного набора значений входных параметров и контекста выполнен охранный предикат, но для всех соответствующих стимулов спецификации нарушается либо охранный предикат, либо предусловие. Таким образом, контрактная спецификация Σ для каждого инициального расширенного автомата Δ и заданного набора функций соответствия ϕ определяет подавтомат, состоящий только из специфицированных переходов, который будем называть спецификационным подавтоматом. Проверка правильности поведения осуществляется для заданных функций соответствия, которые выполняют функции преобразования данных из спецификационного представления в реализационное (ϕS→ и ϕX→) и наоборот из реализационного представления в спецификационное (ϕY← и ϕV←). Компоненты тестовой системы, которые реализуют такие функции, называются адаптерами. Схема проверки правильности поведения, приведенная ниже, основана на определении отношения соответствия: (πS, νS) ← (∅, ν0S); if(sI ≠ ϕS→(πS) ∨ νS ≠ ϕV←(νI)) { error(«Ошибка инициализации»); } // пока тест не завершен while(¬isTestComplete()) { // получить очередной стимул от тестовой системы xS[pS] ← getNextStimulus(); 120
// проверить выполнимость охранного предиката и предусловия стимула if((γx(πS) ∧ prex(pS, νS)) { // преобразовать стимул в реализационное представление xI[pI] ← ϕX→(xS[pS]); // подать стимул на реализацию applyStimulus(xI[pI]); // подождать один такт waitOneCycle(); // получить состояния, реакцию и контекст реализации sI ← getState(); yI ← getReaction(); νI ← getContext(); // преобразовать реакцию/контекст в реализационное представление yS ← ϕY←(yI); ν'S ← ϕV←(νI); // вычислить новое состояние управления πS ← xS ° πS; if(sI ≠ ϕS→(πS)) { error(«Ошибка несоответствия состояний»); } if(yS ≠ θ(πS)) { error(«Ошибка несоответствия реакций»); } if(¬Φπ, x(pS, νS, ν'S)) { error(«Ошибка несоответствия контекстов»); } } } В представленной выше схеме проверки используются следующие обобщенные функции тестовой системы: • isTestComplete – проверяет завершена ли генерации тестовой последовательности; •
getNextStimulus – получает очередной инициализированный стимул от генератора тестовой последовательности;
•
applyStimulus – подает инициализированный стимул на реализацию;
•
waitOneCycle – ожидает один такт;
•
getState – получает состояние управления реализации;
•
getReaction – получает реакцию реализации;
•
getContext – получает контекст реализации.
Часто при тестировании состояние управления реализации является скрытым (либо к нему отсутствует доступ, либо доступ есть, но от деталей реализации управляющей логики целесообразно абстрагироваться), поэтому функция 121
получения состояния управления getState и функция соответствия состояний ϕS→ неопределенны. В этом случае проверки вида sI ≠ ϕS→(πS) опускаются. Сравнение yS ≠ θ(πS) также не осуществляется, поскольку реакции устройства (внутренние действия по обработке стимулов), как правило, не наблюдаемы. Проверки, которые выполняются на практике, базируются на предикате Φπ, x(pS, νS, ν'S), то есть на наблюдении за изменениями контекста (состояния данных и выходов модуля). В схеме проверки правильности поведения заложена следующая классификация ошибок в модулях микропроцессоров: ошибки несоответствия состояний, ошибки несоответствия реакций и ошибки несоответствия контекстов. Ошибки несоответствия состояний являются формализацией ошибок в управляющей логике. Именно на обнаружения ошибок этого типа в первую очередь нацелен предлагаемый метод автоматизации тестирования. Поскольку проверки правильности поведения основаны на предикате Φπ, x, предполагается, что ошибки несоответствия состояний проявляются в некорректном изменении контекста, в частности, выходов модуля.
4.2. Генерация тестовой последовательности Основная особенность конвейерных модулей заключается в сложной организации управляющей логики. В данном разделе рассматривается метод генерации тестовой последовательности, нацеленный на создание различных ситуаций именно в управляющем компоненте модуля. Метод основан на контрактных спецификациях конвейера. Пусть задана спецификация Σ = 〈V, ν0, I ∪ O, X ∪ {τ}, Z ∪ {ε}, ρ〉. Определение. Тестовой последовательностью для контрактной спецификации Σ называется произвольная последовательность переходов расширенного автомата Δ(Σ), интерпретирующего спецификацию Σ, допустимая в начальной конфигурации (∅, ν0). Формализацией управляющей логики модуля, описываемого спецификацией Σ, является управляющий автомат Ω(Σ). С точки зрения спецификации цель тестирования задается как покрытие всех достижимых состояний управляющего автомата. Предлагаемый метод генерации тестовой последовательности основан на обходе графа состояний управляющего автомата. Для обхода мы используем неизбыточные алгоритмы, то есть алгоритмы, для работы которых достаточно информации о пройденном подграфе. Входной информацией неизбыточных алгоритмов обхода графов является неизбыточное описание графа [6] Определение. Неизбыточным описанием графа состояний называется тройка 〈V, X, enabled〉, где: • 122
V – множество вершин;
•
X – множество стимулов;
•
enabled: V → 2X – функция, которая для каждой вершины графа возвращает множество допустимых в ней стимулов.
В работах [6-9] исследованы неизбыточные алгоритмы обхода разных классов графов. В частности, в статье [8] рассмотрен неизбыточный алгоритм обхода αdfsm6 на классе детерминированных сильно-связных конечных графов, а в [6] – алгоритм αndfsm на классе графов, имеющих детерминированный сильносвязный полный остовный подграф. Следует отметить, что алгоритм αndfsm применяется и в тех случаях, когда граф заведомо детерминирован. Дело в том, что этот алгоритм относится к «жадным» алгоритмам и в ряде случаев позволяет значительно сократить длину тестовой последовательности. Заметим, что управляющий автомат является конечным (если множество стимулов конечно) и детерминированным. Несложно показать, что граф состояний управляющего автомата является сильно-связным, если в нем нет тупиковых состояний, то есть состояний π ≠ ∅, таких, что Enabled(π) = ∅ (на практике это условие тоже выполнено – наличие тупикового состояния сигнализирует об ошибке в спецификации). Таким образом, условия применения неизбыточных алгоритмов выполнены. Неизбыточное описание графа состояний управляющего автомата имеет вид 〈PL, X ∪ {τ}, enabled〉, где множество допустимых стимулов для каждого состояния π определяется следующим образом: enabled(π) = {x ∈ X ∪ {τ} | γx(π) = true}. При тестировании управляющей логики конкретные значения параметров стимулов не важны – можно использовать любые значения, удовлетворяющие соответствующему предусловию. В предложенном методе предполагается, что для любого стимула x и контекста ν существует набор параметров p ∈ Domx, который удовлетворяет условию prex(p, ν) = true. На практике это не всегда так, то есть возможны ситуации, когда для некоторого стимула x существует контекст ν, для которого не существует значений параметров, удовлетворяющих предусловию. Получается, что в одном и том же состоянии управляющего автомата в некоторых ситуациях предусловие стимула выполнено, а в некоторых нет. Эта проблема решается добавлением в состояние обходимого графа конвейера дополнительной информации, на основе которой определяется выполнимость предусловий.
5. Сравнение с существующими подходами В данном разделе приводится сравнение предлагаемого метода спецификации и тестирования аппаратуры с конвейерной организацией с существующими 6
В указанной работе этот алгоритм обозначается символом A2.
123
подходами. Сравнение разбито на две части: в первой из них сравниваются методы спецификации, во второй – методы генерации тестовой последовательности.
5.1. Методы спецификации аппаратуры В настоящее время для спецификации аппаратного обеспечения широко используются так называемые языки верификации аппаратуры (HVL, Hardware Verification Languages) [1]. К таким языкам относятся PSL, OpenVera, SystemVerilog и другие. HVL-языки включают в себя конструкции языков программирования, языков описания аппаратуры, а также специальные средства, ориентированные на верификацию. Средства спецификации, используемые в современных языках верификации аппаратуры, базируются на темпоральной логике линейного времени (LTL, Linear Temporal Logic) и темпоральной логике деревьев вычислений (CTL, Computation Tree Logic) [1]. Логика CTL используется преимущественно для формальной верификации систем. Для целей симуляции и тестирования больший интерес представляет логика линейного времени. Все языки, использующие LTL, имеют схожие средства спецификации. Они оперируют с ограниченными по времени последовательностями событий, для которых можно обращаться как к прошлому, так и к будущему. Из простых последовательностей можно строить более сложные с помощью логических связок, таких как И и ИЛИ, или используя регулярные выражения. На последовательностях событий можно с помощью темпоральных операторов задавать утверждения (assertions). В подходах на основе темпоральных логик, упор делается на временную декомпозицию операций. Для каждой операции сначала выделяется ее временная структура – допустимые последовательности событий и задержки между ними; затем определяются предикаты, описывающие отдельные события; после этого предикаты, относящиеся к одному моменту времени, могут быть некоторым образом сгруппированы. В подходе, предлагаемом нами, основной акцент ставится на функциональную декомпозицию операций. Первым делом выделяется функциональная структура операции в виде последовательности стадий; каждая стадия специфицируется; временная привязка спецификаций осуществляется в процессе тестирования с помощью вычисления на каждом такте условий блокировки стадий. Мы полагаем, что функциональная структура операции более устойчива по сравнению с временной. Тем самым, подходы, основанные на функциональной декомпозиции операций, позволяют разрабатывать спецификации более устойчивые к изменениям реализации по сравнению с подходами на основе темпоральных логик. К достоинствам предлагаемого метода можно отнести его наглядность. Контрактная спецификация конвейера базируется на известных разработчиками аппаратуры понятиях таких, как стадия операции, блокировка стадии и других. 124
5.2. Методы генерации тестовой последовательности Методы генерации тестов для аппаратуры с конвейерной организацией в основном исследуются в контексте тестирования микропроцессоров и их модулей. Многие исследователи сходятся во мнении, что удобным средством генерации тестовых последовательностей являются автоматные модели. Для построения тестов, покрывающих отдельные ситуации, часто используют методы проверки моделей [10-18]. Генерация тестов, полно покрывающих управляющую логику устройства, как правило, основана на обходе графа состояний автоматной модели [19, 20]. Автоматная модель либо извлекается автоматически на основе статического анализа кода реализации [19, 21], либо строится вручную [14, 20]. Автоматическое извлечение модели является сложной задачей, требующей либо наличия в коде аннотаций разработчиков [19], либо привлечения эвристик [10, 11]. Для сложного аппаратного обеспечения автоматическое извлечение автоматной модели управляющей логики практически неосуществимо. При построении модели вручную возникает другая проблема – построенную модель сложно отлаживать [20]. Поскольку на основе такой модели предполагается генерация тестов, важно, чтобы модель точно описывала тестируемый компонент, так как в противном случае цели тестирования не будут достигнуты. В предлагаемом методе автоматная модель управляющей логики извлекается из формальных спецификаций аппаратного обеспечения. Во-первых, это делает метод масштабируемым на достаточно сложные устройства (см. раздел 6). Во-вторых, это решает задачу отладки моделей, поскольку формальные спецификации, из которых извлекается автоматная модель, также используются для проверки правильности поведения.
6. Опыт практического применения метода Описанный в статье метод спецификации и тестирования аппаратуры с конвейерной организацией был применен для тестирования модулей промышленного микропроцессора с MIPS64-совместимой архитектурой [22]: буфера трансляции адресов (TLB, Translation Lookaside Buffer) и модуля кэшпамяти второго уровня (L2 cache). Для разработки тестов использовался инструмент CTESK из набора инструментов UniTESK [23], расширенный библиотекой PIPE, реализующей основные концепции предлагаемого подхода.
6.1. Тестирование буфера трансляции адресов Память тестируемого буфера трансляции адресов состоит из 64 ячеек, которые составляют объединенный TLB (JTLB, Joint TLB). Кроме того, для повышения производительности модуль содержит два дополнительных буфера из 4 ячеек каждый: TLB данных (DTLB, Data TLB) и TLB инструкций (ITLB, Instructions TLB). DTLB используется при трансляции адресов данных, ITLB – при трансляции адресов инструкций. Наличие двух дополнительных буферов 125
позволяет производить две операции трансляции адреса одновременно: одну для выборки инструкции (через ITLB), вторую для загрузки или сохранения данных (через DTLB). Модуль TLB реализует операции чтения, записи, проверки наличия ячейки в буфере, а также операции трансляции адресов данных и инструкций. Интерфейс модуля состоит из приблизительно 30 входов и стольких же выходов. Модуль реализован на языке Verilog, его описание (не считая библиотек) составляет около 3500 строк кода. Формальные спецификации и тесты были разработаны одним человеком приблизительно за 2.5 месяца, а их объем составил около 3500 строк кода. В результате тестирования было найдено 10 ошибок в реализации модуля, включая критичные. Найденные ошибки не были обнаружены при тестировании микропроцессора с помощью других методов7.
6.2. Тестирование модуля кэш-памяти второго уровня Тестируемый модуль кэш-памяти второго уровня имеет объем 256 Кбайт и состоит из 8192 строк. Каждая строка содержит данные (4 двойных слова по 64 бит), тэг (18 старших бит физического адреса) и биты служебной информации. Память модуля является смешанной – в ней могут храниться как данные, так и инструкции. Кэш-память адресуется физическим адресом путем прямого отображения. Модуль кэш-памяти реализует операции загрузки и сохранения данных (в разных режимах), выборки инструкций, а также управляющую операцию для изменения данных и управляющих битов. Интерфейс модуля содержит около 70 входов и 30 выходов. Реализация модуля на языке Verilog составляет около 3000 строк кода. Формальные спецификации и тесты были разработаны одним человеком приблизительно за 4 месяца, а их объем составил около 4000 строк кода. В результате тестирования были найдены 9 ошибок в реализации модуля.
7. Заключение В статье рассмотрен метод формальной спецификации аппаратуры с конвейерной организацией, основанный на пред- и постусловиях стадий выполнения операций. Метод позволяет в наглядной, интуитивно понятной форме описывать функциональность конвейерных устройств и может быть использован для функционального тестирования. Метод был успешно применен для проверки нескольких модулей промышленного микропроцессора. В результате тестирования были найдены серьезные ошибки, не обнаруженные при использовании других подходов. 7
Микропроцессор тестировался с помощью тестовых программ на языке ассемблера, разработанных вручную и полученных с помощью случайной генерации. 126
К настоящему моменту нами получен большой опыт использования технологии UniTESK и инструмента CTESK для спецификации и тестирования моделей аппаратуры [24]. Опыт показывает, что некоторые шаги разработки тестов могут быть полностью или частично автоматизированы [25]. Детальное исследование этого вопроса и разработка инструментальной поддержки для дальнейшей автоматизации разработки тестов является одним из приоритетных направлений дальнейшей работы. Еще одним направлением исследований является обобщение метода на более сложные типы конвейерных устройств. В работе был рассмотрен лишь простейший случай, когда последовательность стадий каждой операции фиксирована. На практике встречаются конвейеры с ветвлениями, в которых цепочки стадий вычисляются динамически на основе некоторых промежуточных условий, а также параллельные конвейеры, позволяющие запускать несколько операций параллельно. Литература [1] S.A. Edwards. Design and Verification Languages. Technical Report, Columbia University, New York, USA, November 2004. [2] B. Beizer. The Pentium Bug – An Industry Watershed. Testing Techniques Newsletter, TTN Online Edition, September 1995. [3] J. Bergeron. Writing Testbenches: Functional Verification of HDL Models. Kluwer Academic Publishers, 2000. [4] W. Lam. Hardware Design Verification: Simulation and Formal Method-Based Approaches. Prentice Hall, 2005. [5] D. Patterson and J. Henessy. Computer Organization and Design. 3rd Edition, Morgan Kaufmann, 2005. [6] А.В. Хорошилов. Спецификация и тестирование систем с асинхронным интерфейсом. Институт системного программирования РАН, Препринт 12, 2006. [7] И.Б. Бурдонов, А.С. Косачев, В.В. Кулямин. Использование конечных автоматов для тестирования программ. Программирование, 2000, №2. [8] И.Б. Бурдонов, А.С. Косачев, В.В. Кулямин. Неизбыточные алгоритмы обхода ориентированных графов. Детерминированный случай. Программирование, 2003, №5. [9] И.Б. Бурдонов, А.С. Косачев, В.В. Кулямин. Неизбыточные алгоритмы обхода ориентированных графов. Недетерминированный случай. Программирование, 2004, №1. [10] D. Moundanos, J. Abraham, and Y. Hoskote. A Unified Framework for Design Validation and Manufacturing Test. ITC'1996: International Test Conference, 1996. [11] D. Moundanos, J. Abraham, and Y. Hoskote. Abstraction Techniques for Validation Coverage Analysis and Test Generation. IEEE Transactions on Computers, Volume 47, 1998. [12] D. Geist, M. Farkas, A. Landver, Y. Lichtenstein, S. Ur, and Y. Wolfsthal. Coverage Directed Test Generation Using Symbolic Techniques. FMCAD'1996: Formal Methods in Computer Aided Design, 1996. [13] P. Mishra and N. Dutt. Automatic Functional Test Program Generation for Pipelined Processors using Model Checking. HLDVT'2002: High-Level Design Validation and Test Workshop, 2002.
127
[14] P. Mishra and N. Dutt. Architecture Description Language Driven Functional Test Program Generation for Microprocessors using SMV. CECS Technical Report 02-26, 2002. [15] P. Mishra and N. Dutt. Graph-Based Functional Test Program Generation for Pipelined Processors. DATE'2004: Design, Automation and Test in Europe Conference and Exhibition, 2004. [16] P. Mishra and N. Dutt. Functional Coverage Driven Test Generation for Validation of Pipelined Processors. DATE'2005: Design, Automation and Test in Europe Conference and Exhibition, 2005. [17] H.M. Koo and P. Mishra. Test Generation using SAT-based Bounded Model Checking for Validation of Pipelined Processors. ACM Great Lakes Symposium on VLSI, 2006. [18] H.M. Koo and P. Mishra. Functional Test Generation using Property Decomposition for Validation of Pipelined Processors. DATE'2006: Design, Automation and Test in Europe Conference and Exhibition, March 2006. [19] R. Ho, C. Yang, M. Horowitz, and D. Dill. Architecture Validation for Processors. ISCA'1995: International Symposium on Computer Architecture, 1995. [20] S. Ur and Y. Yadin. Micro Architecture Coverage Directed Generation of Test Programs. DAC'1999: Design and Automation Conference, 1999. [21] Y. Hoskote, D. Moundanos, and J. Abraham. Automatic Extraction of the Control Flow Machine and Application to Evaluating Coverage of Verification Vectors. ICCD'1995: International Conference of Computer Design, 1995. [22] MIPS64 Architecture For Programmers. Revision 2.0. MIPS Technologies Inc., 2003. [23] http://www.unitesk.com. [24] М. Chupilko, A. Kamkin, and D. Vorobyev. Methodology and Experience of Simulation-Based Verification of Microprocessor Units Based on Cycle-Accurate Contract Specifications. SYRCoSE'2008: The 2nd Spring Young Researchers Colloquium on Software Engineering, 2008. [25] В.П. Иванников, А.С. Камкин, В.В. Кулямин, А.К. Петренко. Применение технологии UniTESK для функционального тестирования моделей аппаратного обеспечения. Препринт 8. Институт системного программирования РАН, Москва, 2005.
128
Современная инфраструктура для обеспечения совместимости Linux-платформ и приложений В.В. Рубанов [email protected] Аннотация. В статье описывается подход к построению инфраструктуры для эффективной разработки и использования спецификаций Linux-платформ. Подобные спецификации описывают программные интерфейсы (API) для обеспечения совместимости между различными реализациями таких платформ и различными приложениями для них. Задача рассматривается в условиях эволюционирующих версий спецификации платформы и наличия множественных платформенных реализаций и приложений, удовлетворяющих той или иной версии спецификации. Предлагаемый подход основан на использовании централизованной базы данных, содержащей структурированную информацию о различных версиях спецификации и различных реализациях платформ и приложений, а также средств автоматической верификации фактического соответствия реализаций платформ и приложений той или иной версии спецификации. Подход иллюстрируется на примере инфраструктуры для поддержки стандарта Linux Standard Base (LSB), основного промышленного стандарта на интерфейсы базовых библиотек операционной системы Linux.
1. Введение Одной из четко выделенных современных тенденций в мире разработки новых системных платформ можно с уверенностью назвать появление множества решений на базе операционной системы Linux. Новые платформы на базе этой ОС активно появляются в совершенно разных сегментах - от корпоративных серверов и рабочих станций до мобильных и встраиваемых систем. К сожалению, в условиях такого разнообразия системных платформ возникает проблема переносимости приложений между их различными реализациями. И если переносимость между различными сегментами (например, перенос приложения для мобильного телефона на сервер) не всегда востребована, то обеспечение совместимости различных платформ и приложений в рамках одного сегмента существенно необходимо как с точки зрения производителей платформ, так и с точки зрения производителей приложений и, в конце концов, с точки зрения конечных пользователей. 129
Это обусловлено тем, что проблемы переносимости приложений между разными платформами ограничивают число доступных приложений для каждой конкретной платформы, тем самым оказывая негативное влияние на сферу их применимости и, в конечном счете, популярность. С точки зрения разработчика приложений, проблемы переносимости увеличивают стоимость разработки, так как зачастую необходимо поддерживать различные версии приложения для каждой конкретной целевой платформы, или приводят к тому, что распространение приложения ограничивается одной частной платформой. Наконец, возможность использовать полный набор необходимых приложений на конкретной платформе или возможность смены платформы для предотвращения зависимости от поставщика (vendor lock-in) являются значимыми факторами для конечных пользователей, но именно эти возможности ставятся под угрозу из-за проблем переносимости приложений. Лучшее решение, которое было найдено на пути обеспечения переносимости приложений - это выработка открытых спецификаций (в том числе стандартов) программных интерфейсов, которые предоставляются платформой и на которые могут надежно опираться приложения. Такие спецификации описывают сигнатуры интерфейсов (имя функции, параметры, возвращаемые значения) и их поведение и являются своего рода публичным контрактом между производителями платформ и приложений. Корректные реализации программных интерфейсов платформы становятся совместимыми с приложениями, если последние соответственно ограничиваются использованием только специфицированных интерфейсов. Однако наличие только неформального описания в виде текста спецификации платформенных интерфейсов является недостаточным для эффективного использования такого подхода на практике. Как создатели платформенных спецификаций, так и разработчики соответствующих реализаций платформ и различных приложений для них нуждаются в инструментальной и методологической поддержке для успешного решения своих задач в практической плоскости. В данной статье рассматривается подход к организации такой поддержки на примере инфраструктуры, связанной с открытым стандартом на интерфейсы базовых библиотек операционной системы Linux - Linux Standard Base (LSB) [1]. Эта инфраструктура была построена в рамках совместной научноисследовательской и производственной деятельности ИСП РАН и Linux Foundation. Аналогичные инструменты и методы вполне применимы и для поддержки других системных платформ, в первую очередь основанных на Linux (например, Moblin [2], LiMo [3], Android [4], Maemo [5]). В первом разделе статьи дается обзор одной из наиболее успешных спецификаций программных интерфейсов, а именно стандарта Linux Standard Base (LSB). Второй раздел содержит описание современных достижений в области построения инфраструктуры поддержки интерфейсных стандартов на примере LSB. Среди подразделов выделяется историческая справка о 130
разработке инфраструктуры, а также отдельные подразделы, описывающие базу данных LSB, инструменты работы с ней, веб-портал разработчиков, сертификационные автоматизированные тесты и систему онлайн сертификации. В заключении приводится сводка результатов, полученных в ходе разработки инфраструктуры LSB, и делаются выводы.
2. LSB – спецификация единой платформы Linux Одним из наиболее ярких примеров спецификации Linux-платформ является стандарт Linux Standard Base (LSB) [1], основная идея которого заключается в описании общепринятого подмножества программных интерфейсов различных библиотек, составляющих «единую платформу Linux» с точки зрения производителей приложений. Это подмножество полностью представлено во всех основных дистрибутивах Linux в сегменте серверов и рабочих станций. Кроме собственно сигнатуры и поведения функций, спецификация интерфейсов в LSB включает также информацию бинарного уровня (в основном имена ELF-символов), необходимую для обеспечения переносимости приложений без перекомпиляции. Для разработки стандарта LSB в 2000 году был создан консорциум Free Standards Group (в настоящее время Linux Foundation [6]), который поддерживается ведущими ИТкомпаниями, такими как IBM, Intel, HP, Novell, Oracle и др. Первая версия стандарта была опубликована в июне 2001 года и описывала около 3000 интерфейсов. В последующие годы стандарт развивался и приобретал зрелость - в каждой версии появлялось все больше и больше интерфейсов (некоторые устаревшие исключались). Текущая версия LSB 3.2 включает более 30000 интерфейсов из более чем 40 библиотек. Большинство основных дистрибутивов Linux сертифицированы или фактически соответствуют этому стандарту (разные поколения дистрибутивов соответствуют разным версиям LSB). Важной особенностью стандарта LSB является то, что его разработчики не пытаются диктовать что-то новое для производителей дистрибутивов Linux. В большинстве случаев LSB просто ссылается на устоявшиеся промышленные стандарты и документацию, которым основные дистрибутивы удовлетворяют де-факто. Среди спецификаций, на которые ссылается LSB, стоит отметить Single Unix Specification (POSIX), ISO C99, ISO C++ Language и различную документацию разработчиков отдельных компонентов (как правильно библиотек) Linux. Только в случае отсутствия устоявшейся документации на тот или иной интерфейс LSB описывает его независимым образом, как правило, на основе анализа реализации интерфейса в основных дистрибутивах Linux. При принятии решений о стандартизации новых интерфейсов LSB использует критерии «наилучшей практики». Это означает, что интерфейс становится кандидатом для добавления в очередную версию LSB, если он присутствует в реализациях всех основных дистрибутивов Linux и этот интерфейс активно используется в приложениях. Другими словами, интерфейсы для 131
стандартизации должны быть достаточно популярны как среди разработчиков дистрибутивов, так и среди разработчиков приложений. Также, должны быть соблюден ряд технических требований, таких как наличие достаточно стабильных реализации, документации и тестов интерфейса. Важно отметить, что роль независящего от конкретного поставщика международного консорциума позволяет Linux Foundation принимать непредубежденные решения при разработке стандарта. Современная версия LSB 3.2 включает 5 обязательных модулей: • LSB Core – низкоуровневые системные интерфейсы C (библиотеки libc, libcrypt, libdl, libm, libpthread, librt, libutil, libpam, libz and libncurses). • LSB C++ - библиотека поддержки времени выполнения для языка C++ (libstdcxx). • LSB Desktop – различные функции для работы с графическим интерфейсом пользователя и вспомогательные сервисы (в основном XML, X11, GTK и Qt). • LSB Interpreted Languages – параметры окружения и стандартные модуля для языков Perl и Python. • LSB Printing – в основном библиотека libcups. Первые три модуля включают как архитектурно независимые описания, так и элементы, специфические для конкретных аппаратных архитектур, которые можно грубо представить в виде иерархии «модуль -> библиотека -> группа -> интерфейс». LSB 3.2 поддерживает 7 архитектур - IA32 (x86), AMD64 (x86_64), IA64 (Itanium), Power PC 32, Power PC 64, IBM S390 и IBM S390X. Модули Interpreted Languages и Printing включают только архитектурно независимые описания. Важно отметить, что LSB не нацелен на все дистрибутивы и приложения Linux – он лишь для наиболее распространенных решений общего назначения в сегменте серверов и рабочих станций. Специализированные дистрибутивы и некоторые системные приложения могут быть не вполне совместимы с LSB. Тем временем, даже если приложение использует некоторые интерфейсы за рамками LSB, то LSB все же имеет значение для таких приложений, так как позволяет сократить издержки на обеспечение переносимости в области интерфейсов, пересекающихся с LSB, на которые приложение может надежно опираться. Для обеспечения совместимости для интерфейсов вне LSB можно использовать отдельные методы, такие как статическая линковка необходимых библиотек или разработка специальных библиотекпереходников, которые могут скрывать различия между различными дистрибутивами для заранее заданного набора необходимых интерфейсов. LSB позволяет сократить число интерфейсов, для которых нужно применять такие специальные (относительно дорогостоящие) меры.
132
3. Техническая инфраструктура поддержки LSB
3.2. База данных LSB
Важнейшим фактором для эффективной разработки и поддержки интерфейсных спецификаций (в том числе стандартов) для системных платформ является техническая инфраструктура, обеспечивающая автоматизацию ключевых процессов в работе над спецификацией и облегчающая ее использование разработчиками. На примере LSB в качестве основных компонентов инфраструктуры поддержки можно назвать генераторы текста стандарта на основе центральной базы данных, веб-портал для разработчиков, системы анализа и принятия решений по развитию стандарта, тесты реализаций платформ (дистрибутивов) и приложений, включая системы автоматизированного запуска тестов, анализа результатов и сертификации. В последующих разделах дается подробный обзор этих элементов инфраструктуры после небольшого исторического отступления, описывающего историю разработки инфраструктуры LSB.
Основой инфраструктуры поддержки стандарта LSB является централизованная база данных (используется СУБД MySQL), которая содержит интегрированную информацию о структуре стандартизованных элементов (от модулей до конкретных интерфейсов, их параметров, типов данных, виртуальных таблиц, и т.п.). Также эта база данных включает информацию о составе существующих дистрибутивов Linux и внешних зависимостях различных популярных приложений. Кроме того, в базе данных отражается различная оперативная информация (например, статус сертификации различных продуктов, в том числе промежуточный). Текущая база в общей сложности содержит 92 таблицы с более чем 65 миллионами записей, которые можно условно разделить на три части: 1. Часть поддержки стандартизации – включает информацию о стандартизованных элементах LSB и об элементах-кандидатах. 2. Экосистемная часть – содержит информацию о составе (предоставляемые библиотеки и интерфейсы) основных дистрибутивов и зависимостях (требуемые от дистрибутива библиотеки и интерфейсы) популярных приложений. 3. Сертификационная часть – содержит оперативную информацию о статусе сертификации различных продуктов (дистрибутивов и приложений), об операциях аудита, о платежах за процедуру официальной сертификации и т.п.
3.1. Проектная программа LSB Infrastructure Российские специалисты активно вовлечены в LSB сообщество. Первым крупным проектом в этом направлении был Open Linux VERification (OLVER) [7]. Проект выполнялся Центром верификации ОС Linux [8] при Институте системного программирования РАН [9] по заказу Федерального агентства по науке и инновациям (Роснаука). В проекте был проанализирован текст основной (core) части стандарта LSB для около 1500 системных функций Linux, были формализованы требования на поведение этих функций и построены тесты для автоматической проверки дистрибутивов Linux на соответствие этим требованиям [10]-[11]. Результаты проекта OLVER заинтересовали комитет по стандартизации LSB в тот момент консорциум Free Standards Group (FSG), который предложил ИСП РАН долгосрочное сотрудничество в области построения новой инфраструктуры использования и развития стандарта LSB, а также разработки технологий автоматизации тестирования и создания собственно новых тестов для Linux, что являлось основным больным местом, сдерживающим дальнейшее развитие и продвижение этого стандарта. Впоследствии, в результате слияния FSG с OSDL был создан консорциум Linux Foundation [6] и сотрудничество с ИСП РАН было расширено. Важно отметить, что все результаты работ Linux Foundation и ИСП РАН в рамках этого сотрудничества являются открытыми и свободными (opensource) для сообщества разработчиков Linux. Первые результаты по созданию новой инфраструктуры в рамках проектной программы LSB Infrastructure [12] были представлены в июне 2007 года на конференции Linux Foundation Collaboration Summit 2007 и с тех пор они постоянно развиваются с помощью системы выпуска регулярных версий. Далее рассматривается текущее (конец 2008 года) состояние инфраструктуры LSB. 133
Часть поддержки стандартизации описывает следующие элементы: • группирующие элементы: o модули (наборы библиотек и команд); o библиотеки (наборы интерфейсных групп и заголовочных файлов); o интерфейсные группы (наборы классов и интерфейсов); o заголовочные файлы (наборы интерфейсов, типов данных и констант); • основные элементы: o команды (например, ls, cat и т.п.); o классы C++; o интерфейсы (включая глобальные переменные); o константы и макросы; o типы данных. Все эти элементы связаны в граф зависимостей различных видов так, что имеющейся информации достаточно для полностью автоматической генерации заголовочных файлов, которые содержат декларации всех необходимых элементов, входящих в стандарт. Идея экосистемной части заключается в объединении в одном месте информации о составе (предоставляемые библиотеки и интерфейсы) основных дистрибутивов и зависимостях (требуемые от дистрибутива библиотеки и интерфейсы) популярных приложений. Эта информация постоянно 134
обновляется и позволяет анализировать текущее положение с точки зрения принятия решений о развитии стандарта. Например, вот основные вопросы, на которые становится возможным ответить: какими дистрибутивами предоставляется (или наоборот в каких отсутствует) тот или иной интерфейс или насколько этот интерфейс популярен среди производителей приложений? Сертификационная часть поддерживает процесс сертификации, в том числе содержит официальный реестр сертифицированных продуктов, удовлетворяющих той или иной версии LSB. Также данные в этой части используются для оперативной работы онлайн системы сертификации LSB Certification System.
• • • •
3.3. Инструменты работы с БД и веб-портал разработчиков База данных LSB используется различными инструментами, среди которых стоит отметить более 40 скриптов, которые генерируют различные части «технического пакета поддержки LSB», такие как части текста самой спецификации стандарта, заголовочные файлы, части реализации инструментов тестирования и разработки (Software Development Kit - SDK). Для эффективного использования данных из БД человеком был создан вебпортал LSB Navigator, который предоставляет графический интерфейс пользователя и позволяет эффективно работать с данными в базе данных, включая поиск, рубрикатор, кросс-ссылки, возможности обратной связи и т.п. Этот портал находится в публичном доступе (http://linuxfoundation.org/navigator/) и предназначен как для рабочей группы по стандартизации LSB, так и для разработчиков различных дистрибутивов и приложений Linux. Основные возможности LSB Navigator включают: • Навигацию по стандартизованным элементам LSB от модулей до конечных элементов в виде интерфейсов, типов данных, констант и т.п. • Глобальные фильтры по версии LSB и по аппаратной архитектуре. • Индивидуальные «домашние странички» для более миллиона интерфейсов Linux (доступных всего в 2 перехода с главной страницы с помощью специализированного поиска), которые в свою очередь содержат следующую информацию: o LSB статус интерфейса («включен в LSB», «никогда не был в LSB», «планируется для включения», «был исключен», «будет скоро исключен» - deprecated); o контекст интерфейса (модуль, библиотека, заголовочный файл, к которым относится интерфейс и т.п.); o сигнатура интерфейса (имя, параметры и возвращаемое значение); o прямая ссылка на описание (в первую очередь поведения) интерфейса; o список дистрибутивов, которые предоставляют этот интерфейс; 135
o список приложений, которые используют этот интерфейс; o список открытых тестов, которые проверяют этот интерфейс; o обсуждения сообщества, связанные с этим интерфейсом. Информацию о дистрибутивах (предоставляемые библиотеки и интерфейсы). Информацию о приложениях (внешние зависимости по библиотекам и интерфейсам). Статистику по LSB элементам (общее число интерфейсов, команд, классов и т.д. в каждой версии LSB). Статистику по использованию интерфейсов приложениями: o Какие интерфейсы и библиотеки наиболее часто используются приложениями; o «LSB рейтинг» приложений – список приложений с указанием числа LSB и не-LSB библиотек и интерфейсов, используемых каждым приложением.
LSB Navigator фактически представляет собой интерактивную версию стандарта LSB и базу знаний с различной дополнительной информацией (аналогично Microsoft MSDN). Другие системы могут легко ссылаться на странички внутри LSB Navigator, позволяя интегрировано предоставлять пользователям контекстную информацию.
3.4. Сертификационные тесты дистрибутивов Ian Murdock, бывший руководитель рабочей группы LSB и технический директор Free Standard Group, а в настоящее время директор по операционным системам в Sun Microsystems, говорил: “Интерфейсный стандарт хорош настолько, насколько хороши автоматизированные тесты, проверяющие соответствие стандарту». Тогда он имел в виду тесты, проверяющие корректность реализации интерфейса в платформе (дистрибутивах в случае Linux), однако стоит отметить, что важны также и тесты приложений, которые проверяют, что приложения используют только стандартизованные интерфейсы и делают это корректным образом. Тем не менее, далее речь будет идти в основном о тестах дистрибутивов. В конце 2006 года покрытие LSB интерфейсов тестами составляло около 15%, что означало, что 85% стандартизованных тестов вообще не имели тестов. Именно поэтому разработка новых автоматизированных тестов соответствия дистрибутивов требованиям стандарта является важнейшим приоритетом программы LSB Infrastructure. Однако проблема заключалась в том, что стандарт включает огромное количество интерфейсов и создать за разумное время необходимое количество по-настоящему мощных тестов не представлялось возможным. В то время как было неприемлемым и то, что некоторые интерфейсы вообще не имели каких-либо тестов. Для решения этих проблем в условиях ограничений на ресурсы специалистами ИСП РАН была 136
предложена стратегия, основанная на выделении трех уровней качества тестов. 1. Глубокие (Deep) – на этом уровне целевой интерфейс вызывается много раз (в среднем около 100) в различных ситуациях (с различными параметрами и в различных внутренних состояниях). 2. Средние (Normal) – это наиболее распространенный уровень качества тестирования, принятый в индустрии. Целевой интерфейс проверяется в нескольких (в среднем 5) основных ситуациях. 3. Поверхностные (Shallow) – простейшие тесты с единственной гарантированной целью – вызвать целевой интерфейс хотя бы один раз в какой-нибудь типичной ситуации и проверить, что не было аварийного сбоя (crash). Этот уровень близок к понятию тестов существования или санитарных тестов (“existence”, “smoke”, “sanity”). Согласно предложенной стратегии, все интерфейсы, не имеющие тестов, были проанализированы и разбиты на три группы по степени важности интерфейса. Соответственно для особо важных интерфейсов было предложено разрабатывать глубокие тесты, для основной массы библиотек - тесты среднего уровня, а для многочисленных, но не важных, интерфейсов разработать поверхностные тесты. Для автоматизации разработки тестов различных уровней было предложено соответственно три технологии (включая соответствующие инструменты поддержки). 1. UniTESK (см. [13]) – для разработки глубоких тестов. 2. T2C (см. [14]) – для разработки тестов среднего уровня. 3. AZOV (см. [15]) – для разработки поверхностных тестов. В таблице 1 приведены данные о разработанных сотрудниками ИСП РАН новых тестах различного уровня. Трудоемкость разработки дана интегрально для полного цикла от изучения целевой системы до разработки и отладки теста на многочисленных системах и аппаратных архитектурах с последующим анализом/исправлением находимых проблем. Уровень тестов Глубокие Средние Поверхностные
Кол-во тестируемых интерфейсов 1 500 3 700 21 800
Кол-во Среднее кол-во Трудоемкость тестовых испытаний на разработки (чел.испытаний 1 интерфейс дней/интерфейс) ~150 000 100 5 17 200 5 1 21 800 1 0,02
Таблица 1. Статистика по разработанным в ИСП РАН новым тестам для LSB.
137
Важно отметить, что поверхностные тесты отличаются от глубоких и средних не только малым количеством тестовых испытаний, но и почти полным отсутствием проверок корректности функциональности. Глубокие и средние тесты содержат систематически выделенные согласно разработанной методологии проверки каждого атомарного требования из текста стандарта. При этом, когда разработанные таким образом тесты обнаруживают ошибку, то выводится не только обычная диагностика о параметрах конкретного несоответствия (например, «ожидалось XX, но функция вернула YY»), но и сообщается ссылка на конкретное место в тексте стандарта, в котором описано нарушенное общее требование. Такой уровень диагностики существенно облегчает процесс реального тестирования и повышает комфорт пользователей с точки зрения тестирования соответствия стандарту. Согласно принятой стратегии полное покрытие всех LSB интерфейсов тестами ожидается закончить к концу 2009 года.
3.5. Средства автоматизации LSB-сертификации Для того чтобы тесты соответствия можно было эффективно использовать на практике, нужны не только сами тесты (каждый из которых грубо представляет собой отдельную программу), но и средства их автоматизированного запуска и анализа результатов. Кроме того, необходима информационная система, которая поддерживает рабочие процедуры (workflow) формальной сертификации на основе результатов тестирования. В рамках программы LSB Infrastructure в начале 2008 года была разработана онлайн система поддержки LSB сертификации (LSB Certification System), которая направляет пользователей через необходимые шаги для выполнения тестирования их продуктов, разрешения проблем и в конечном итоге получения формальной сертификации. Эта система включает 3 основные части: • Управление сертификацией (Certification Workflow Management) – предоставляет пошаговые инструкции с хранением текущего индивидуального статуса (шаги могут быть произвольно разнесены по времени) для проведения сертификации дистрибутивов и приложений на соответствие LSB. • Реестр сертифицированных продуктов (LSB Product Directory) – публичная часть сертификационной системы, которая хранит список сертифицированных LSB продуктов, обеспечивая различные интерактивные группировки и фильтры при просмотре. • Подсистема управления проблемами (Problem Reporting) – предназначена для онлайн поддержки в разрешении различных проблем, которые возникают в процессе сертификации, например, пользователи могут сталкиваться с ошибками в тестах, и необходимо исследовать такие случаи и игнорировать такие ошибки при рассмотрении результатов тестирования. Фактически эта подсистема представляет систему 138
управления публичной базой знаний, одновременно предоставляя возможность организованного индивидуального общения между пользователями и сотрудниками рабочей группы LSB. Соответственно, в системе присутствуют два режима – для пользователей и для сотрудников рабочей группы LSB (администраторов). Наконец, в основе технической части процесса сертификации лежит запуск автоматизированных тестов соответствия и анализ их результатов. Для этих целей были разработаны инструменты Distribution Testkit Manager и Linux Application Checker для тестирования соответственно дистрибутивов и приложений. Эти средства представляют собой программные комплексы автоматизации и управления тестированием, которые обеспечивают следующие основные возможности: • интегрированный интерфейс пользователя (графический и командной строки на выбор), одинаковый для всех тестовых наборов LSB (созданных по различным технологиям); • выбор, какие именно тесты запускать (все, заранее определенные поднаборы или вручную выбранное подмножество); • сохранение всех настроек конфигурации для обеспечения непрерывности процесса тестирования в различных сеансах работы; • автоматическая загрузка недостающих на локальном компьютере тестов с FTP-сайта Linux Foundation; • запуск сертификационных тестов «одной кнопкой»; • унифицированные интерактивные отчеты о результатах тестирования, интегрированные с базой знаний известных ошибок и дополнительных рекомендаций, а также с контекстными ссылками на странички в LSB Navigator. Это позволяет пользователям тестов эффективно анализировать результаты тестирования; • реестр истории запусков с возможностью регрессионного сравнения. Онлайн система LSB сертификации и инструменты автоматизации запуска тестов интегрированы друг с другом, обеспечивая прозрачные переходы между локальной и серверной функциональностью для облегчения процесса сертификации в целом от организационных аспектов до технических.
4. Заключение Проблемы переносимости приложений между различными реализациями системных платформ создают трудности, как разработчикам самих платформ, так и разработчикам приложений и конечным пользователям. Именно эти проблемы являются огромным негативным фактором для успеха той или иной платформы. В мире Linux, где число различных реализаций платформ исчисляется сотнями, вопросы совместимости приобретают критическое значение. 139
Основной инициативой по обеспечению переносимости приложений между различными Linux-платформами в сегменте серверов и рабочих станций является стандарт Linux Standard Base. Опираясь в частности на один из старейших интерфейсных стандартов POSIX, LSB является наиболее зрелым примером спецификации системной платформы и набора технических и организационных решений, составляющих инфраструктуру поддержки ее эффективной эволюционной разработки и использования. В данной статье были рассмотрены основные компоненты этой инфраструктуры, такие как: • централизованная база данных, содержащая структурированную информацию о стандартизируемых элементах и окружающей их экосистеме; • инструменты автоматической генерации отдельных элементов текста спецификации и реализаций инструментов и тестов для разработчиков приложений на основе базы данных; • веб-портал для эффективного представления информации в базе данных (интерактивная версия спецификации и база знаний); • тесты для проверки реализаций платформ на соответствие спецификации; • тесты для проверки приложений на предмет корректного использования специфицированных интерфейсов платформы; • инструменты автоматизации запуска тестов и анализа результатов тестирования; • интегрированная с вышеуказанными инструментами онлайн система сертификации продуктов (реализаций платформ и приложений). Эта инфраструктура была построена коллективом Центра верификации ОС Linux при Институте системного программирования РАН в рамках сотрудничества с международным консорциумом Linux Foundation. Разработанные средства успешно применяются на практике и получили массу положительных отзывов, как от разработчиков самого стандарта, так и от разработчиков различных дистрибутивов и от разработчиков Linuxприложений. Есть все основания, что построение аналогичной инфраструктуры будет чрезвычайно полезно и для других спецификаций системных платформ, в первую очередь основанных на Linux (например, Moblin, LiMo, Android или Maemo), особенно там, где существуют множественные реализации платформы, а сама спецификация платформы постоянно эволюционирует. Литература [1] [2] [3] [4] [5] [6]
140
Linux Standard Base Homepage. http://www.linuxfoundation.org/en/LSB/. Moblin Homepage. http://moblin.org/ LiMo Homepage. http://www.limofoundation.org/ Android Homepage. http://code.google.com/intl/ru/android/ Maemo Homepage. http://maemo.org/ Linux Foundation. http://linuxfoundation.org/
[7] [8] [9] [10] [11]
[12] [13] [14] [15]
Проект Open Linux VERification (OLVER). http://linuxtesting.org/project/olver Linux Verification Center. http://linuxtesting.org Institute for System Programming of the Russian Academy of Sciences. http://ispras.ru/ V. Kuliamin, A. Petrenko, V. Rubanov, A. Khoroshilov. Formalization of Interface Standards and Automatic Construction of Conformance Tests. Proceedings of SECR 2006 conference, Moscow. А. И. Гриневич, В. В. Кулямин, Д. А. Марковцев, А. К. Петренко, В. В. Рубанов, А. В. Хорошилов Использование формальных методов для обеспечения соблюдения программных стандартов. Труды ИСП РАН, том.10, , 2006. С.51-68. LSB Infrastructure Program. http://ispras.linuxfoundation.org http://www.unitesk.com В.В.Рубанов, А.В.Хорошилов, Е.А.Шатохин. T2C: технология автоматизированной разработки тестов базовой функциональности программных интерфейсов. Труды ИСП РАН: Том 14, часть 2, 2008. С.65-82. Р. С. Зыбин, В. В. Кулямин, А. В. Пономаренко, В. В. Рубанов, Е. С. Чернов. Технология Azov автоматизации массового создания тестов работоспособности. Труды ИСП РАН, том. 14, часть 2:83-108, 2008.
1. Введение Модель данных XQuery [1] является стандартной моделью данных для работы со слабоструктурированными данными, представленными в формате XML. Поддержка слабоструктурированных данных делает эту модель достаточно универсальной и пригодной для преставления данных различной степени структурированности от регулярных реляционных данных до текстовых документов с размытой структурой. Оборотной стороной такой универсальности является достаточно низкая эффективность существующих реализаций. На сегодняшний день уже сложился ряд подходов [2, 3, 4, 5] к реализации модели данных, но каждый из этих подходов обладает очевидными преимуществами и недостатками, что делает эти подходы применимыми только для достаточно узких классов приложений. Более того, модель данных XQuery поддерживает возможности, которые являются избыточными для каждого конкретного вида приложения. Например, предположим, что приложение использует XML для представления реляционных данных. Запросы к таким данным обычно не требуют поддержки таких возможностей модели данных как братские (sibling) и родительские (parent) оси или порядок узлов в документе (document order). Другой пример – это запросы к контент-ориентированным XML-данным, таким как энциклопедические статьи [7] или текстовый документ, представленный в формате Microsoft Word XML [6]. Зачастую такие запросы не адресуют XML-элементы, предназначенные для описания способов визуализации данных (примеры такие элементов: para, bold, emphasize и другие, которые составляют, как правило, большую часть элементов в документе), но адресуют семантически значимые элементы, такие как author, дата, библиография. Следовательно, элементы визуализации могут быть представлены на уровне хранения в сжатом незапрашиваемом виде для увеличения скорости операций модификации и сериализации XML данных (под сериализацией здесь и далее мы понимаем процесс трансляции 143
внутреннего представления данных в строковое представление, соответствующее формату XML). Приведенные выше рассуждения позволяют нам прийти к выводу, что эффективное внутреннее представление и обработка XML-данных не могут быть достигнуты с использованием какого-либо общего подхода. По нашему мнению, единственно возможным подходом, способным обеспечить высокую эффективность для такой универсальной модели данных, является выбор способов внутреннего представления и методов обработки данных под потребности конкретного приложения. При этом достаточной информацией для описания потребностей является схема XML-данных и рабочая нагрузка в виде возможных запросов и операций модификации данных. То есть мы предлагаем пойти дальше построения планов выполнения запросов при фиксированных структурах хранения данных, как это делается в большинстве современных систем управления XML-данными, и, кроме того, выбирать структуры хранения данных, необходимые для эффективного выполнения запросов и модификаций для данного приложения. Такой подход позволит поддерживать XQuery модель данных на логическом уровне, но избежать излишних накладных расходов на физическом уровне хранения данных. С использованием такого подхода можно добиться эффективности обработки регулярных реляционных данных в формате XML, сопоставимой с эффективностью, которая обеспечивается реляционными базами данных. При этом контент-ориентированные данные будут обрабатываться с эффективностью, сопоставимой с эффективностью систем хранения текстовых документов. В данной статье мы описываем наши первые результаты по разработки таких методов хранения и обработки XML-данных. Статья имеет следующую структуру. В следующем разделе мы рассматриваем примеры, демонстрирующие преимущества предлагаемого подхода. В разд. 3 дается обзорное описание подхода. В разд. 4 описывается физическое представление данных и иллюстрируется на примерах. Разд. 5 посвящен обзору близких работ и существующих подходов хранения XML-данных. В заключительном, шестом разделе мы намечаем пути дальнейших исследований.
2. Мотивирующие примеры Для демонстрации основных преимуществ и различных аспектов предлагаемого подхода мы выбрали упрощенную версию приложения, которое используется для создания электронной версии Большой Российской Энциклопедии (БРЭ) [7]. Иллюстрации 1 и 2 показывают фрагменты XMLдокумента, содержащего статью энциклопедии, и его описывающей схемы соответственно. По определению [9] описывающая схема содержит ровно 144
один путь для каждого пути в документе, и каждый путь в описывающей схеме является путем хотя бы в одном из документов. В этом примере документ представляет собой том энциклопедии, который содержит, по крайней мере, три статьи. Каждая статья состоит из заголовка, списка авторов и тела, которое содержит текст статьи. <article id="2"> Cyclotron resonanceCentury S.Edelman.I. Kaganov
Рис. 2. Описывающая схема Большой Российской Энциклопедии
the
Для обработки энциклопедии в приложении используются набор запросов, которые являются предопределенными и могут изменяться только при переходе к новой версии системы. Ниже приводится список основных запросов. (Q1) Получение списка названий статей declare ordering unordered; volume/article/title
(Q2) Получение статьи по идентификатору declare ordering unordered; volume/article[@id eq “...”]
Рис. 1. Фрагмент Большой Российской Энциклопедии.
(Q3) Получение статьи по названию declare ordering unordered; volume/article[title eq “...”]
145
146
(Q4) Перечислить названия статьей, на которые ссылается статья с идентификатором равным 1 declare ordering unordered; for $i in volume/article [@id eq “1”]//link return volume/article [@id eq $i/@idref]/title
(Q5) Перечислить звания статей, которые ссылаются на статью с названием «Атом» declare ordering unordered; let $j := volume/article [title eq “atom”]/@id for $i in volume/article where $i//link[@idref eq $j] return $i/title
Рассматривая этот пример, мы можем выделить несколько интересных моментов, которые являются общими для многих приложений. 1. Элементы визуализации. Контент-ориентированные XML документы часто содержат большое количество XML-элементов, которые обрабатываются исключительно front-end-приложениями (такими как браузер или текстовый процессор) при отображении документа. В приведенном выше примере к таким элементам относятся p, i, b. Такие элементы, как правило, не адресуются запросами. Однако при хранении XML-документов с использование любого общего подхода такие элементы будут представляться таким же образом, как и семантически значимые элементы. 2. Реляционные данные. Помимо элементов визуализации в приведенном примере можно выделить элементы и атрибуты с простыми значениями (например, атрибуты id, idef и title element), которые адресуются запросами. При этом значения этих элементов и атрибутов используются только как промежуточные данные при вычислении запросов в том смысле, что это эти элементы не извлекаются сами по себе, а только как часть другого элемента (например, атрибут id используется для нахождения статьи и извлекается из базы данных только как часть статьи). 3. Порядок узлов документа (document order). По умолчанию результат вычисления запроса неявно сортируется в порядке узлов в документе. Однако очень часто этот порядок не имеет никакого значения для приложения. Например, в рассматриваемом примере не имеет смысла взаимный порядок следования названия статьи и авторов. В приведенных запросах неявная сортировка выключается в прологе. 4. Известные наперед запросы (рабочая нагрузка). В приведенном приложении все запросы известны еще на этапе его создания, то есть система не поддерживает ad hoc запросов к данным. Это позволяет нам, в 147
частности, построить список путевых выражений, которые составляют основу всех этих запросов: volume/article, volume/article/link, volume/article/title. Далее в статье мы покажем, как приведенные выше соображения могут быть использованы для выбора структур хранения и планов выполнения запросов.
3. Описание подхода Для реализации предлагаемого подхода необходимо решить две основные задачи: разработать методы выбора структур хранения для наперед известной и неизменной рабочей нагрузки; разработать методы реорганизации структур хранения на случай изменения рабочей нагрузки. Как мы уже отмечали выше, предполагается, что для большинства приложений рабочая нагрузка (то есть запросы и операции модификации) известна заранее и не подвержена частым изменениям. В следующих подразделах мы рассматриваем каждый из этих методов.
Рис. 3. Различные способы представления данных
3.1. Выбор структур приложение
хранения
ориентированных
на
Имея заранее известную рабочую нагрузку, мы компилируем планы выполнения запросов и соответствующий план хранения данных для заданной нагрузки. В этих планах возможности языка XQuery, которые являются избыточными для поддержки требуемых запросов, не поддерживаются при выполнении. Для построения плана хранения мы используем следующие основные методы: 1) Комбинирование структурного и текстового представления данных. Как уже упоминалось выше, большинство элементов в контенториентированных XML документах никогда не адресуются запросами. Нами был разработан метод анализа запросов, позволяющий выявить узлы документов, которые необходимо хранить в структурном 148
представлении (т.е. поддерживая необходимые указатели для навигации между узлами документов) для возможности вычислить все запросы эффективным способом. Все остальные элементы (как правило, это элементы визуализации) сохраняются в текстовом представлении в виде текстовых узлов XML модели данных. Разработанный метод является достаточно гибким и не ограничивается сохранением всего XML поддерева в виде текста. Элементы со структурным представлением могут иметь в качестве детей элементы с текстовым представлением, которые в свою очередь содержат в качестве детей элементы со структурным представлением. Мы также расширили этот подход для хранения в текстовом представлении некоторых элементов, адресуемых запросом, но по которым не производится поиск. Например, рассмотрим запрос «найти имена все сотрудников старше 60 лет». В этом запросе поиск производится по элементу «возраст» и этот элемент имеет смысл хранить в структурном виде. В то же время элемент «имя» можно хранить в текстовом виде как часть элемента «сотрудник», поскольку предполагается, что в результате поиска обычно возвращается небольшое число элементов, и для них извлечение имени из текстового представления через разбор на лету не является дорогостоящей операцией для всех найденных сотрудников. Для эффективного разбора на лету предлагается использовать методы потоковой обработки XML [18]. 2) Комбинирование методов кластеризации узлов в блоках внешней памяти. Кластеризация по описывающей схеме, используемая в Sedna XML Database [9], может быть использована совместно с методом хранения рядом детей и родителей, который используется в системах Natix и DB2 [2, 3]. Выбор способа кластеризации для различных групп узлов производится на основе анализа запросов. Комбинирование этих двух методов должно дать существенный выигрыш в производительности. 3) Исключение избыточных структур и выравнивание вложенности. Анализируя запросы, можно установить, какие структуры являются избыточными для их выполнения. Главным образом, можно удалять излишние указатели и группирующие элементы. Например, реляционные XML-данные могут быть сохранены в виде компактных записей близко к тому, как они хранятся в реляционных системах. Такие записи не имеют такую же строгую структуру, как в реляционных системах, поскольку необходимо поддерживать возможность нерегулярности в данных, но группировка элементов в записи существенно повышает эффективность системы, так как сокращается количество блоков, которые необходимо прочитать. Подобное «уплощение» данных можно проводить и в ряде других случаев для исключения промежуточных элементов. Например, если элемент «сотрудник» содержит элемент «адрес», содержащий, в свою очередь, элементы «улицы» и «дом», то элемент адрес может быть исключен, если 149
на его уровне нет элементов с именем «улицы» и «дом», которые могут значить что-либо другое. Обратим внимание, что метод не приводит к потере или размножению данных, а только исключает излишние структурные элементы, однако он может быть естественным образом расширен использованием проекции данных выполняемой при загрузке или модификации данных или поддержкой материализованных представлений. Проекции могут быть построены по анализу путевых выражений или предикатов с константами. Кроме того, исключение избыточных структур (особенно указателей) имеет еще и потенциальное преимущество, связанное с тем, что узлы становятся менее связанными, что не только повышает скорость выполнения запросов и модификаций, но и открывает новые перспективы в улучшении гранулярности транзакционных блокировок и построении распределенных баз данных. Например, это создает предпосылки для реализации параллелизма по данным (data parallelism) на архитектуре shared-nothing.
3.2. Реорганизация приложения
структур
хранения
при
изменении
В случае изменения приложения (то есть изменения рабочей нагрузки) в общем случае необходимо полностью перестраивать хранимые данные с целью изменения структуры их хранения. При этом политика реорганизации может быть достаточно гибкой. Во-первых, частота, с которой производится реорганизация, может быть сделан зависимой от выбранного уровня оптимизации, который задается в приложении. Во-вторых, реорганизация будет требоваться не так часто, как это может показаться на первый взгляд. В самом деле, мало вероятно, что новые запросы, которые обращены к «реляционным» XML-данным начнут использовать ссылки на братьев (sibling pointers), которые были удалены на этапе компиляции плана хранения. Тем не менее, в общем случае реорганизация все равно может потребоваться. Реорганизация может проводиться следующим образом. Вся база данных целиком может быть перестроена с использование массивно-параллельных распределенных вычислений. На современном оборудовании такое перестроение базы данных небольшого и даже среднего объема может быть осуществлено за приемлемое время, равное одному прочтению базы данных с диска. Как отмечалось выше, «уплощение» структур хранения облегчает создание распределенных баз данных. Если база данных является распределенной, то такая перестройка может быть произведена еще быстрее. В простом случае (когда не были использованы методы оптимизации распределенных баз данных, например, collocated join [10]) перестройка может быть выполнена параллельно и независимо для каждого узла распределенной базы данных. В более сложном случае (данные распределены по узлам для возможности 150
использовать collocated join) могут быть применены методы, подобные mapreduce [11], для перераспределения данных. При реорганизации базы данных существует две основные альтернативы. Вопервых, простое решение состоит в остановке базы данных на время перестройки. Если предположить, что для малых и средних баз данных перестройка не займет много времени, то такое решение приемлемо для многих приложений и может осуществляться в ночное время. Более продвинутое решение состоит в использовании механизма теневых страниц (или snapshot isolation [12]) для реорганизации базы данных без ее остановки.
сэкономить место и увеличить скорость сериализации. Как упоминалось в разд. 3, этот подход не исключает полностью возможность запрашивать элементы из текстового представления, поскольку может быть использован механизм разбора текстового представления на лету. Тестовое представление может содержать заглушки (placeholders) для ссылок на узлы, сохраненные с использование двух приведенных выше методов.
4. Представление данных физического уровня В этом разделе мы даем обзор основных идей для создания перестраиваемого внутреннего представления XML-данных, оптимизированного под заданную рабочую нагрузку. Вернемся к примеру, приведенному в разд. 2. Описывающая схема используется для группировки XML-узлов по путям в документе. Для каждой группы узлов оптимальный способ хранения выбирается с учетом нагрузки (рис. 3, a-c): • Дискриптор узла (node descriptor). Каждый узел в группе может быть сохранен как дескриптор узла, который имеет прямые указатели на детей, родителей и братьев. Это дает возможность эффективной навигации для вычисления структурных путевых выражений [9]. Кроме того, в этом подходе может быть использована нумерующая схема [11, 9]. Каждый дескриптор узла содержит метку номерующей схемы (nid). Основное преимущество использования нумерующей схемы состоит в быстром определении связей предок-потомок между любой парой узлов. Нумерующая схема может быть также использована для определения связей, заданных порядком узлов в документе (document order relationship). • Значения, упакованные в дескриптор узла. Для некоторых узлов могут быть использованы структуры, схожие с записями, используемыми при хранении реляционных данных [20]. Запись упаковывается в дескриптор узла (как значения id и title, показанные на рисунке 3, b). Такое “уплощение” данных дает несколько преимуществ. Во-первых, мы получаем практически максимально компактное представление за счет избавления от лишних указателей. Во-вторых, ускоряется выполнение путевых выражений. Особенно тех, которые накладывают условия на упакованные узлы (например, //article[@id eq “1”]). В-третьих, существенно ускоряется скорость сериализации данных. • Узлы, упакованные в текст. Узлы, которые не адресуются запросами (например, элементы визуализации) могут храниться в сериализованном текстовом виде. Это позволяет существенно 151
Рис. 4: Пример внутреннего представления данных. На рис. 4 показан план хранения для примера, приведенного в разд. 2. В соответствии с этим планом получается следующее: •
• •
152
Узлы article и link представляются в структурном виде с использованием дескрипторов узлов. Это связано с тем, что эти узлы напрямую запрашиваются и сериализуются в запросах Q1-Q5. При этом метки описывающей схемы и указатели на братьев не хранятся, поскольку они не используются для выполнения запросов. Атрибуты id и idref упакованы в дескрипторы родительских узлов, поскольку по ним производится поиск для извлечения этих родительских узлов. Узел title запрашивается и сериализуется в путевых выражениях запросов Q1-Q5, поэтому он также «уплощается».
•
Узлы author и другие дети узла article упакованы в текстовые представления. При сериализации узла article, заглушки #id, #title и #link заменяются полями id, title и link соответственно.
5. Близкие работы Нам известно очень небольшое число работ посвященных методам хранения XML данных с возможность оптимизации под приложение. В системе OrientStore [13] был предложен подход, в котором комбинируются способы представления данных, предложенные в системах Natix [2] и Senda [4]. Однако подход системы OrientStore не предполагает анализ запросов и основан только на анализе схемы данных. Кроме того, внутреннее представление поддерживает все возможности модели данных XQuery, что зачастую является избыточным, как мы показали в этой статье. Подходы к хранению данных, предложенные в системах LegoDB [14, 15] или XCacheDB [21], очень близки к подходам, предложенным в этой статье. Тем не менее, эти системы полностью построены над реляционными системами, что накладывает свои ограничения и вызывает дополнительные накладные расходы. Предложенные в этой статье идеи не следует путать с так называемым подходом компонентных баз данных (component databases) [16, 17]. По нашему мнению, компонентные база данных являются общим подходом, который не допускает эффективной реализации на практике. В отличие от компонентных баз данных, мы не предлагаем общей системы, предназначенной для принципиально различных классов приложений (таких как OLTP, OLAP и другие) и принципиально различных аппаратных платформ (таких как PDA, персональные компьютеры, серверы и другие). Предложенные в этой статье идеи ограничиваются расширением оптимизации запросов на оптимизацию структур хранения с целью избавления от избыточных свойств модели данных XQuery.
6. Заключение и будущие работы В данной статье были предложены методы оптимизации структур физического хранения XML данных для эффективного выполнения запросов из предопределенной рабочей нагрузки. Нами были описаны только предварительные результаты, однако предварительные эксперименты подтверждают действенность предложенного подхода. Он позволяет существенно сократить размер внутреннего представления, а также увеличить скорость выполнения запросов. Основным результатом данной работы должно стать создание системы, реализующей модель данных XQuery достаточно эффективно для использования XQuery-систем на практике. В будущих работах мы планируем разработать формальные методы анализа запросов, позволяющие автоматически строить планы хранения данных. Кроме того, мы 153
планируем создать методы, позволяющие производить реорганизацию базы данных без необходимости ее остановки.
эффективную
Литература [1] XQuery 1.0: An XML Query Language. W3C Recommendation 23 January 2007, w3.org/TR/2007/REC-xquery-20070123 [2] T. Fiebig, S. Helmer et al. Anatomy of a Native XML Base Management System, The VLDB Journal 11/ 4, 2002 [3] M. Nicola, B. van der Linden. Native XML support in DB2 universal database. In Proceedings of the VLDB, Trondheim, Norway, 2005 [4] M. Grinev, A. Fomichev, S. Kuznetsov, K. Antipin, A. Boldakov, D. Lizorkin, L. Novak, M. Rekouts, P. Pleshachkov. Sedna: A Native XML DBMS, www.modis.ispras.ru/sedna [5] M. Haustein, T. Härder. An efficient infrastructure for native transactional XML processing. Data Knowledge Eng., June 2007 [6] E. Ehrli. Walkthrough: Word 2007 XML Format Microsoft Corporation, June 2006 [7] Издательство “Большая Российская Энциклопедия", http://www.greatbook.ru/ [8] S. Chandrasekaran, R. Bamford. Shared Cache - The Future of Parallel Databases. In Proceedings of the ICDE, 2003. [9] A. Fomichev, M. Grinev, S Kuznetsov. Descriptive Schema Driven XML Storage. Technical Report, MODIS, Institute for System Programming of the Russian Academy of Sciences, 2004 [10] Join methods in partitioned database environments, IBM DB2 Database Information Center, http://publib.boulder.ibm.com/infocenter/db2luw/v9r5/index.jsp [11] J. Dean, S. Ghemawat. MapReduce: Simplified Data Processing on Large Clusters. OSDI, December 2004 [12] H. Berenson, P. Bernstein, J. Gray, J. Melton, E. O'Neil, P. O'Neil. A Critique of ANSI SQL Isolation Levels. SIGMOD International Conference on Management of Data San Jose, May 1995 [13] X. Meng, D. Luo, M. Lee, J. An. OrientStore: A Schema Based Native XML Storage System. In Proceedings of the VLDB, 2003 [14] P. Bohannon, J. Freire, J. R. Haritsa, M. Ramanath, P. Roy, J. Siméon: Bridging the XML Relational Divide with LegoDB. In Proceedings of the ICDE, 2003. [15] M. Ramanath, J. Freire, J. Haritsa, and P. Roy. Searching for Efficient XML to Relational Mappings. Technical Report, DSL/SERC, Indian Institute of Science, 2003 [16] M. Seltzer. Beyond Relational Databases: There is More to Data Access than SQL, ACM Queue 3/3, April 2005. [17] S. Chaudhuri, G. Weikum. Rethinking Database System Architecture: Towards a SelfTuning RISC-Style Database System. The VLDB Journal, 2000 [18] D. Florescu et al. The BEA Streaming XQuery Processor. The VLDB Journal 13/3, September 2004 [19] Q. Li, B. Moon. Indexing and Querying XML Data for Regular Path Expressions. Proceedings of the VLDB Conference, Roma, Italy, 2001 [20] H. Garcia-Molina, J. Ullman, J. Widom. Database Systems: The Complete Book. Prentice Hall, October 2001
154
Анализ текстовых документов для извлечения тематически сгруппированных ключевых терминов Мария Гринева, Максим Гринев {upa|maxim}@grinev.net Аннотация. В статье предлагается новый метод извлечения ключевых терминов из текстовых документов. В качестве важной особенности метода мы отмечаем тот факт, что результатом его работы являются группы ключевых терминов; при этом термины из каждой группы семантически связаны одной из основных тем документа. Метод основан на комбинации следующих двух техник: мера семантической близости терминов, посчитанная с использованием Википедии; алгоритм для обнаружения сообществ в сетях. Одним из преимуществ нашего метода является отсутствие необходимости в предварительном обучении, поскольку метод работает с базой знаний Википедии. Экспериментальная оценка метода показала, что он извлекает ключевые термины с высокой точностью и полнотой.
1. Введение Ключевыми терминами (ключевыми словами или ключевыми фразами) являются важные термины в документе, которые могут дать высокоуровневое описание содержания документа для читателя. Извлечение ключевых терминов является базисным этапом для многих задач обработки естественного языка, таких как классификация документов, кластеризация документов, суммаризация текста и вывод общей темы документа [7]. В этой статье мы предлагаем метод для извлечения ключевых терминов документа, используя Википедию в качестве ресурса, насыщенного информацией о семантической близости терминов. Википедия (www.wikipedia.org) – свободно распространяемая энциклопедия, на сегодняшний день являющаяся самой большой энциклопедией в мире. Она содержит миллионы статей, доступных на нескольких языках. В сентябре 2008 года Википедия содержит более 2.5 миллионов статей (более 6 миллионов, если считать перенаправляющие страницы, представляющие синонимы заголовка основной статьи). Обладая огромной сетью ссылок между статьями, большим числом категорий, перенаправляющих страниц (redirect pages) и страниц для многозначных терминов (disambiguation pages), Википедия представляет собой исключительно мощный ресурс для нашей работы и для 155
многих других приложений обработки естественного языка и информационного поиска. В основе нашего метода лежит использование следующих двух техник: мера семантической близости, посчитанная по Википедии, и алгоритм анализа сетей, а именно, алгоритм Гирвана-Ньюмана для обнаружения сообществ в сетях. Ниже мы дадим краткое описание этих техник. Установление семантической близости концепций в Википедии является естественным шагом на пути к построению инструмента, полезного для задач обработки естественного языка и информационного поиска. За последние три года появилось порядочное количество работ по вычислению семантической близости между концепциями с использованием различных подходов [13,14,4,19,21]. Работа [14] дает развернутый обзор многих существующих методов подсчета семантической близости концепций с использованием Википедии. Хотя метод, описываемый в нашей работе, не устанавливает каких-либо требований к способу определения семантической близости, эффективность работы метода зависит от качества работы выбранного метода подсчета семантической близости. Для экспериментов, описанных в этой работе, мы использовали метод подсчета семантической близости, описанный в работе Д. Турдакова и П. Велихова [21]. Зная семантическую близость терминов, мы можем построить семантический граф для всех терминов обрабатываемого документа. Семантический граф представляет собой взвешенный граф, в котором узлами являются термины документа, наличие ребра между парой терминов означает, что эти два термина семантически близки, весом ребра является численное значение семантической близости этих двух терминов. Мы заметили, что граф, построенный таким образом, обладает важным свойством: семантически близкие термины «сбиваются» в плотные подграфы, в так называемые сообщества, наиболее массивные и сильно связанные подграфы, как правило, соотносятся с главными темами документа, и термины, входящие в такие подграфы, являются ключевыми для данного документа. Новшество нашего подхода состоит в применении алгоритма обнаружения сообществ в сетях, который позволяет нам выявить тематические группы терминов, и затем выбрать из них наиболее плотные. Такие наиболее плотные группы терминов являются результатом работы метода – тематически сгруппированными ключевыми терминами. Задача анализа структуры сетей и обнаружения сообществ в них на сегодняшний день хорошо изучена. Было предложено много алгоритмов, которые с успехом применялись для анализа социальных сетей [22], сетей цитирования научных статей [16, 3], сетей покупок товаров крупных Интернет-магазинов таких как Amazon [1], биохимических сетей [6] и многих других. В то же время авторам данной работы неизвестны примеры применения таких алгоритмов к сетям, построенным на основе Википедии. В нашем методе используется алгоритм, предложенный М. Ньюманом и 156
М. Гирваном [15]. Существуют работы, показывающие, что данный алгоритм является высокоэффективным при анализе как синтетических сетей, так и сетей реального мира.
2. Близкие работы В области статистической обработки естественного языка существуют классические подходы к извлечению ключевых терминов: tf.idf и анализ колокаций (collocation analysis) [7]. Tf.idf (term frequency-inverse document frequency) является популярной метрикой при решении задач информационного поиска и анализа текста [17]. Tf.idf представляет собой статистическую меру того, насколько термин важен в документе, который является частью коллекции документов. С использованием Tf.idf важность термина пропорциональна количеству встречаемости термина в документе и обратно пропорциональна количеству встречаемости термина во всей коллекции документов. В то время как tf.idf используется для извлечения ключевых терминов, состоящих из одного слова, анализ коллокаций используется для обнаружения фраз. Подход Tf.idf, дополненный анализом коллокаций, позволяет извлечь ключевые фразы. Оба подхода требуют наличия некоторой коллекции документов для сбора статистики; такую коллекцию документов называют обучающим множеством. Качества работы подходов зависит от того, насколько удачно подобрано обучающее множество. Преимуществом данных подходов является простота реализации и удовлетворительное качество работы, когда обучающее множество хорошо подобрано. Благодаря этим преимуществам данные подходы широко распространены на практике. Мы бы хотели отметить интересный факт: существуют работы [9,11,2,8], где Википедия использовалась в качестве обучающего множества, и было показано, что Википедия может служить хорошим обучающим множеством для многих практических приложений. Существует альтернативный класс подходов к решению задач обработки естественного языка (извлечение ключевых слов является одной из таких задач), и данная работа принадлежит к этому классу подходов. Подходы этого класса основаны на использовании знании о семантической близости терминов. Семантическая близость терминов может быть получена при помощи словаря или тезауруса (например, WordNet [12]), но нас интересуют работы, использующие семантическую близость терминов, полученную по Википедии. Посчитать семантическую близость терминов с использованием Википедии можно двумя способами: используя гипертекстовые ссылки между статьями Википедии, которые соответствуют данным терминам [13,14,21], или измеряя косинус угла между векторами, построенными по текстам соответствующих статей Википедии [4]. Существует множество работ, где семантическая 157
близость терминов, полученная по Википедии, используется для решения следующих задач обработки естественного языка и информационного поиска: разрешение лексической многозначности термина [10,18,8,21], выведение общей темы документа [20], категоризация [5], разрешение кореферентности (coreference resolution) [19]. Авторам данной статьи неизвестны работы, где семантическая близость терминов использовалась бы для извлечения ключевых терминов документа, однако, работа [5] является наиболее близкой к нашей. В работе [5] решается задача категоризации текста, при этом из терминов текста строится семантический граф, аналогично тому, как мы предлагаем в данной работе. Идея применения алгоритмов анализа графов в этой работе проявляется в простой форме: выбираются наиболее центральные термины в графе при помощи алгоритма оценки центральности (betweenness centrality), далее эти термины используются для категоризации документа. Мы выделяем следующие преимущества нашего метода:
Наш метод не требует обучения, в отличие от описанных традиционных подходов. Благодаря тому, что Википедия является крупномасштабной и постоянно обновляемой миллионами людей энциклопедией, она остается актуальной и покрывает много специфических областей знаний. Таким образом, практически любой документ, большая часть терминов которого описана в Википедии, может быть обработан нашим методом. Ключевые термины сгруппированы по темам, и метод извлекает столько различных тематических групп терминов, сколько различных тем покрывается в документе. Тематически сгруппированные ключевые термины могут значительно улучшить выведение общей темы документа (используя, например, применение метода «spreading activation» по графу категорий Википедии, как описано в [20]), и категоризацию документа [5]. Наш метод высокоэффективен с точки зрения качества извлеченных ключевых терминов. Экспериментальные оценки метода, обсуждаемые далее в этой статье, показали, что метод извлекает ключевые термины из документов с высокой точностью и полнотой.
3. Метод извлечения ключевых терминов Метод состоит из следующих пяти шагов: 1) извлечение терминовкандидатов; 2) разрешение лексической многозначности терминов; 3) построение семантического графа; 4) обнаружение сообществ в семантическом графе; 5) выбор подходящих сообществ.
158
3.1. Извлечение терминов-кандидатов Целью этого шага является извлечение всех терминов документа и подготовка для каждого термина набора статей Википедии, который потенциально могут описывать его значение. Мы разбираем исходный документ на лексемы, выделяя все возможные Nграммы. Для каждой N-граммы мы строим ее морфологические вариации. Далее для каждой из вариации производится поиск по всем заголовкам статей Википедии. Таким образом, для каждой N-граммы мы получаем набор статей Википедии, которые могут описывать ее значение. Построение различных морфологических форм слов позволяет нам расширить поиск по заголовкам статьей Википедии и, таким образом, находить соответствующие статьи для большей порции терминов. Например, слова drinks, drinking и drink могут быть связаны с двумя статьями Википедии: Drink и Drinking.
тезауруса позволяет нам избежать данной проблемы: все ключевые термины, полученные в результате работы нашего метода, являются осмысленными фразами. Результатом работы данного шага является список терминов, в котором каждый термин соотнесен с одной соответствующей статьей Википедии, описывающей его значение.
3.3. Построение семантического графа На данном шаге по списку терминов, полученном на предыдущем шаге, мы строим семантический граф. Семантический граф представляет собой взвешенный граф, вершинами которого являются термины документа, наличие ребра между двумя вершинами означает тот факт, что термины семантически связаны между собой, вес ребра является численным значением семантической близости двух терминов, которые соединяет данное ребро.
3.2. Разрешение лексической многозначности терминов На данном шаге для каждой N-граммы мы должны выбрать наиболее подходящую статью Википедии из набора статей, который был построен для нее на предыдущем шаге. Многозначность слов – распространенное явление естественного языка. Например, слово «платформа» может означать железнодорожную платформу, или платформу программного обеспечения, а также платформу, как часть обуви. Правильное значение многозначного слова может быть установлено при помощи контекста, в котором это слово упоминается. Задача разрешения лексической многозначности слова представляет собой автоматический выбор наиболее подходящего значения слова (в нашем случае – наиболее подходящей статьи Википедии) при упоминании его в некотором контексте. Существует ряд работ по разрешению лексической неоднозначности терминов с использованием Википедии [21,8,18,10,11]. Для экспериментов, обсуждаемых в данной работе, был реализован метод, предложенный Д. Турдаковым и П. Велиховым в работе [21]. В [21] авторы используют страницы для многозначных терминов и перенаправляющие страницы Википедии. С использованием таких страниц Википедии строится набор возможных значений термина. Далее наиболее подходящее значение выбирается при помощи знаний о семантической близости терминов: для каждого возможного значения термина вычисляется степень его семантической близости с контекстом. В итоге выбирается то значение термина, степень семантической близости с контекстом которого было наибольшим. Распространенной проблемой традиционных методов извлечения ключевых терминов является наличие абсурдных фраз в результате, таких как, например, "using", "electric cars are". Использование Википедии как контролирующего 159
Рис. 1. Пример семантического графа, построенного по новостной статье «Apple to Make ITunes More Accessible For the Blind» Рис. 1 демонстрирует пример семантического графа, построенного из новостной статьи «Apple to Make ITunes More Accessible For the Blind». В статье говорится о том, что главный прокурор штата Массачусетс и Национальная Федерация Слепых достигли соглашения с корпорацией Apple Inc., следуя которому Apple сделает доступным свой музыкальный Интернетсервис ITunes для слепых пользователей посредством технологии screenreading. На рис. 1 можно видеть, что термины, релевантные Apple Inc. и 160
Blindness, образуют два доминантных сообщества, а термины Student, Retailing и Year оказались на периферии и слабо связаны с остальным графом. Важно отметить тот факт, что термины – ошибки, возникшие при разрешении лексической многозначности терминов, проведенного на втором шаге, оказываются периферийными или даже изолированными вершинами графа, и не примыкают к доминантным сообществам.
3.4. Обнаружение сообществ в семантическом графе Целью данного шага является автоматическое обнаружение сообществ в построенном семантическом графе. Для решения этой задачи мы применяем алгоритм Гирвана-Ньюмана. В результате работы алгоритма исходный граф разбивается на подграфы, которые представляют собой тематические сообщества терминов. Для оценки качества разбиения некоторого графа на сообщества авторы [15] предложили использовать меру модулярности (modularity) графа. Модулярность графа является свойством графа и некоторого его разбиения на подграфы. Она является мерой того, насколько данное разбиение качественно в том смысле, что существует много ребер, лежащих внутри сообществ, и мало ребер, лежащих вне сообществ (соединяющих сообщества между собой). На практике значение модулярности, лежащее в диапазоне от 0.3 до 0.7, означает, что сеть имеет вполне различимую структуру с сообществами, и применение алгоритма обнаружения сообществ имеет смысл. Мы отметили, что семантические графы, построенные из текстовых документов (таких как, например, новостная статья, или научная статья), имеют значение модулярности от 0.3 до 0.5.
3.5. Выбор подходящих сообществ На данном шаге из всех сообществ необходимо выбрать те, которые содержат ключевые термины. Мы ранжируем все сообщества таким образом, чтобы сообщества с высокими рангами содержали важные термины (ключевые термины), а сообщества с низкими рангами – незначимые термины, а также ошибки разрешения лексической многозначности терминов, которые могут возникнуть на втором шаге работы нашего метода. Ранжирование основано на использовании плотности и информативности сообщества. Плотностью сообщества является сумма весов ребер, соединяющих вершины этого сообщества. Экспериментируя с традиционными подходами, мы обнаружили, что использование меры tf.idf терминов помогает улучшить ранжирование сообществ. Tf.idf дает большие коэффициенты терминам, соответствующим именованным сущностям (например, Apple Inc., Steve Jobs, Braille), а терминам, соответствующим общим понятиям (таким как, например, Consumer, Year, Student) дает низкие коэффициенты. Мы считаем tf.idf для терминов, используя Википедию так, как описано в работе [8]. Под 161
информативностью сообщества мы понимаем сумму tf.idf-терминов, входящих в это сообщество, деленную на количество терминов сообщества. В итоге, мы считаем ранг сообщества, как плотность сообщества, умноженная на его информативность, и сортирует сообщества по убыванию их рангов. Приложение, использующее наш метод для извлечения ключевых слов, может использовать любое количество сообществ с наивысшими рангами, однако, на практике имеет смысл использовать 1-3 сообщества с наивысшими рангами.
4. Экспериментальная оценка В этом разделе мы обсудим экспериментальные оценки предложенного метода. Поскольку не существуют стандартных бенчмарков для измерения качества извлеченных из текстов ключевых терминов, мы провели эксперименты с привлечением ручного труда, то есть полнота и точность извлеченных ключевых слов оценивались людьми – участниками эксперимента. Мы собрали 30 блог-постов из следующих блогов технической тематики: «Geeking with Greg», автор Грег Линден, DBMS2, автор Курт Монаш, Stanford Infoblog, авторы – члены группы Stanford Infolab. В эксперименте приняли участие пять человек из отдела информационных систем ИСП РАН. Каждый участник должен был прочитать каждый блог-пост и выбрать в нем от 5 до 10 ключевых терминов. Каждый ключевой термин должен присутствовать в блог-посте, и для него должно быть найдено соответствующая статья в Википедии. Участники также были проинструктированы выбирать ключевые слова так, чтобы они покрывали все основные темы блог-поста. В итоге для каждого блог-поста мы выбрали такие ключевые термины, которые были выделены, по крайней мере, двумя участниками эксперимента. Названия перенаправляющих статей Википедии и название статей, на которые идет перенаправление, по сути, представляют собой синонимы, и мы в нашем эксперименте считали их одним термином. Метод, представленный в данной статье, был реализован по следующим архитектурным принципам. Для достижения лучшей производительности мы не вычисляли семантическую близость всех пар терминов Википедии заранее. Данные, необходимые для подсчета семантической близости терминов на лету, а именно, заголовки статей Википедии, информация о ссылках между статьями, статистическая информация о терминах были загружены в оперативную память. В итоге полученная база знаний занимала в оперативной памяти 4.5 Гбайта. База знаний была установлена на выделенном компьютере с размером оперативной памяти, равным 8 Гбайт. Клиентское приложение работали с базой знаний посредством вызовов удаленных процедур.
162
4.1. Оценка полноты выделенных ключевых терминов Под полнотой мы понимаем долю ключевых слов, выделенных вручную, которые так же были выделены автоматически нашим методом:
где под {manually extracted} мы понимаем множество ключевых слов, извлеченных вручную участниками эксперимента для некоторого документа, под {automatically extracted} мы понимаем множество всех ключевых терминов, автоматически извлеченных нашим методом для того же документа. Знаком |S| мы обозначаем мощность множества S, то есть количество терминов в множестве S. Для 30 блог-постов мы имеем 180 ключевых терминов, выделенных участниками эксперимента вручную, 297 – выделенных автоматически, 127 вручную выделенных ключевых слов были также выделены автоматически. Таким образом, полнота равно 68%.
4.2. Оценка точности выделенных ключевых терминов Мы оцениваем точность, используя ту же методологию, которой пользовались для оценки полноты. Под точностью мы понимаем долю тех слов, автоматически выделенных нашим методом, которые также были выделены вручную участниками эксперимента:
Итого, следуя показателям нашей тестовой коллекции, точность равна 41%.
4.3. Пересмотр оценки полноты и точности С целью более качественной оценки работы метода мы также пересмотрели наши оценки полноты и точности. Важной особенностью нашего метода является факт, что он в среднем выделяет больше ключевых слов, чем человек. Более точно, наш метод обычно извлекает больше ключевых терминов, релевантных одной теме. Например, рассмотрим рис. 1. Для темы, относящейся к Apple Inc., наш метод выделяет термины: Internet, Information access, Music download, Apple Inc., ITunes, Apple Keyboard и Steve Jobs, в то время как человек обычно выделяет меньше терминов и склонен выделять имена и названия: Music download, Apple Inc., ITunes и Steve Jobs. Это означает, что, иногда метод извлекает ключевые термины с лучшим покрытием основных тем документа, чем это делает человек. Этот факт побудил нас пересмотреть оценку полноты и точности работы нашего метода. 163
Каждый участник эксперимента был проинструктирован пересмотреть ключевые термины, которые он сам выделил следующим образом. Для каждого блог-поста он должен был изучить ключевые термины, выделенные автоматически, и, по возможности, расширить свой набор ключевых слов, то есть дополнить его теми терминами, которые, по его мнению, относятся к главным темам документа, но не были выделены на первом этапе. После такого пересмотра, мы получили 213 ключевых слов, выделенных вручную (вместо 180), таким образом, участники эксперимента добавили 33 новых ключевых термина, что означает, что наше предположение имело смысл, и такой пересмотр важен для полноценной оценки работы метода. В итоге, полнота равна 73% и точность – 52%.
5. Заключение Мы предложили новый метод для извлечения ключевых терминов из текстовых документов. Одним из преимуществ нашего метода является отсутствие необходимости в предварительном обучении, поскольку метод работает над базой знаний, построенной из Википедии. Важной особенностью нашего метода является форма, в которой он выдает результат: ключевые термины, полученные из документа, сгруппированы по темам этого документа. Сгруппированные по темам ключевые термины могут значительно облегчить дальнейшую категоризацию данного документа и выведение его общей темы. Эксперименты, проведенные с использованием ручного труда, показали, что наш метод позволяет извлекать ключевые термины с точностью и полнотой, сравнимой с теми, что дают современные существующие методы. Мы отметили, что наш метод может быть с успехом применен для очистки сложных составных документов от неважной информации, и выделения главной темы в них. Это означает, что его интересно было бы применить для выделения ключевых терминов из Web-страниц, которые, как правило, загружены второстепенной информацией, например, меню, навигационные элементы, реклама. Это направление дальнейшей работы. Список литературы [1] Clauset, A.; Newman, M. E. J.; and Moore, C. 2004. Finding community structure in very large networks. Physical Review E 70:066111. [2] Dakka, W., and Ipeirotis, P. G. 2008. Automatic extraction of useful facet hierarchies from text databases. In ICDE, 466–475. IEEE. [3] de Solla Price, D. J. 1965. Networks of scientific papers. Science 169:510–515. [4] Gabrilovich, E., and Markovitch, S. 2007. Computing semantic relatedness using wikipedia-based explicit semantic analysis. In Proceedings of The Twentieth International Joint Conference for Artificial Intelligence, 1606–1611. [5] Janik, M., and Kochut, K. J. 2008. Wikipedia in action: Ontological knowledge in text categorization. International Conference on Semantic Computing, 268–275.
164
[6] Kauffman, S. A. 1969. Metabolic stability and epigenesist in randomly constructed genetic nets. J Theor Biol 22(3):437–467. [7] Manning, C. D., and Schtze, H. 1999. Foundations of Statistical Natural Language Processing. The MIT Press. [8] Medelyan, O.; Witten, I. H.; and Milne, D. 2008. Topic indexing with wikipedia. In Wikipedia and AI workshop at the AAAI-08 Conference (WikiAI08). [9] Mihalcea, R., and Csomai, A. 2007. Wikify!: linking documents to encyclopedic knowledge. In CIKM ’07: Proceedings of the sixteenth ACM conference on Conference on information and knowledge management, 233–242. New York, NY, USA: ACM. [10] Mihalcea, R. 2005. Unsupervised large-vocabulary word sense disambiguation with graph-based algorithms for sequence data labeling. In HLT ’05: Proceedings of the conference on Human Language Technology and Empirical Methods in Natural Language Processing, 411–418. Morristown, NJ, USA: Association for Computational Linguistics. [11] Mihalcea, R. 2007. Using wikipedia for automatic word sense disambiguation. [12] Miller, G. A.; Fellbaum, C.; Tengi, R.; Wakefield, P.; Langone, H.; and Haskell, B. R. Wordnet: a lexical database for the english language. http://wordnet.princeton.edu/. [13] Milne, D., and Witten, I. 2008. An effective, low-cost measure of semantic relatedness obtained from wikipedia links. In Wikipedia and AI workshop at the AAAI-08 Conference (WikiAI08). [14] Milne, D. 2007. Computing semantic relatedness using wikipedia link structure. In Proceedings of the New Zealand Computer Science Research Student Conference (NZCSRSC). [15] Newman, M. E. J., and Girvan, M. 2004. Finding and evaluating community structure in networks. Physical Review E 69:026113. [16] Redner, S. 1998. How popular is your paper? An empirical study of the citation distribution. The European Physical Journal B 4:131. [17] Salton, G., and Buckley, C. 1988. Term-weighting approaches in automatic text retrieval. Inf. Process. Manage. 24(5):513–523. [18] Sinha, R., and Mihalcea, R. 2007. Unsupervised graph-based word sense disambiguation using measures of word semantic similarity. In ICSC ’07: Proceedings of the International Conference on Semantic Computing, 363–369. Washington, DC, USA: IEEE Computer Society. [19] Strube, M., and Ponzetto, S. 2006. WikiRelate! Computing semantic relatedness using Wikipedia. In Proceedings of the 21st National Conference on Artificial Intelligence (AAAI-06), 1419–1424. [20] Syed, Z.; Finin, T.; and Joshi, A. 2008. Wikipedia as an Ontology for Describing Documents. In Proceedings of the Second International Conference on Weblogs and Social Media. AAAI Press. [21] Turdakov, D., and Velikhov, P. 2008. Semantic relatedness metric for wikipedia concepts based on link analysis and its application to word sense disambiguation. In Colloquium on Databases and Information Systems (SYRCoDIS). [22] Wasserman, S.; Faust, K.; and Iacobucci, D. 1994. Social Network Analysis: Methods and Applications (Structural Analysis in the Social Sciences). Cambridge University Press.