Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Система VTK

Глава 24 из 1 тома книги "Архитектура приложений с открытым исходным кодом".

Оригинал: VTK, глава из книги "The Architecture of Open Source Applications" том 1.
Автор: Berk Geveci и Will Schroeder
Перевод: Н.Ромоданов

24.2. Архитектурные особенности

Перед тем, как погружаться в конкретных особенности архитектуры VTK, давайте взглянем на высокоуровневые концепции, которые существенно повлияли на разработку и использование системы. Одной их таких особенностей является возможность в VTK использовать гибридные обвертки. С помощью этой возможности в реализации С++ в VTK автоматически создаются привязки к языкам Python, Java и Tcl (можно добавить и были добавлены дополнительные языки). Наиболее опытные разработчики будут работать в языке C++. Пользователи и разработчики приложений могут использовать язык C++, но часто для них предпочтительнее интерпретируемые языки, упомянутые выше. Этот гибрид среды компиляции/интерпретации сочетает в себе лучшее из двух миров: высокую производительность ресурсоемких алгоритмов и гибкость при прототипировании или разработке приложений. На самом деле в сообществе научных вычислений многим нравится такой подход к многоязычным вычислениям и они часто пользуются системой VTK в качестве шаблона для разработки своего собственного программного обеспечения.

Если рассматривать процесс разработки программного обеспечения, то в системе VTK используется CMake для управления сборкой, CDash/CTest - для тестирования и CPack - для кросс-платформенного развертывания пакетов. Действительно, система VTK может быть откомпилирована практически на любом компьютере, в том числе и на суперкомпьютерах, которые зачастую обладают заведомо скромными средами разработки. Кроме того, есть средства генерации веб-страниц, вики, списков рассылок (для пользователей и для разработчиков) и документации (то есть, Doxygen), а инструментальные средства дополнены треккером ошибок (Mantis).

24.21. Основные особенности

Поскольку VTK является объектно-ориентированной системой, в VTK тщательно контролируется доступ к элементам класса и экземплярам данных . Обычно все элементы данных являются либо защищенными (т. е. protected), либо приватными (т. е. private). Доступ к ним осуществляется через методы Set и Get с особыми вариациями, относящимися к логическим данным, модальным данным, строкам и векторам. Многие из этих методов на самом деле создается с помощью макросов, вставленных в заголовочные файлы класса. Так, например:

vtkSetMacro(Tolerance,double);
vtkGetMacro(Tolerance,double);

после раскрытия будут выглядеть следующим образом:

virtual void SetTolerance(double);
virtual double GetTolerance();

Есть много причин, выходящих за рамки простого улучшения ясности кода, для использования этих макросов. В VTK существуют важные элементы данных, управляющие процессом отладки, обновляющие значения времени изменения объектов (MTime), а также надлежащим образом управляющие подсчетом ссылок на объекты. Эти макросы обрабатывают такие данные правильным образом и использование этих макросов настоятельно рекомендуется. Например, особенно пагубная ошибка в системе VTK происходит тогда, когда с помощью Mtime не удается правильно управлять объектами. В этом случае код может выполняться не тогда, когда это надо, или может выполнять слишком часто.

Одной из сильных сторон системы VTK является ее сравнительно простые средства представления и управления данными. Обычно для представления подряд идущих частей информации используются различные массивы данных определенного типа (например, vtkFloatArray). Например, список из трех точек XYZ может быть представлен в vtkFloatArray девятью записями (x,y,z, x,y,z и т. д.). Для таких массивов существует понятие кортежа, поэтому 3D-точка представляет собой кортеж из трех элементов, тогда как симметричная тензорная матрица размером 3×3 представляет собой кортеж, состоящий из 6 элементов (в некоторых из которых можно, благодаря наличию в них симметрии, сэкономить место). Такая архитектура была взята за основу специально, поскольку в научных вычислениях она обычно используется в качестве интерфейса с системами, обрабатывающими массивы (например, Fortran), и с ее помощью гораздо эффективнее выделять и освобождать память в больших фрагментах данных, следующих непрерывно друг за другом. Кроме того, взаимодействие с данными, сериализация и выполнение операций ввода-вывода, как правило, происходит гораздо эффективнее, когда данные непрерывно следуют друг за другом. В системе VTK с помощью этих основных массивов данных (различных типов) происходит представление многих данных и для них есть много разнообразных удобных методов, используемых для добавления информации и для доступа к ней, в том числе методы для быстрого доступа, и методы, которые при добавлении новых данных могут, по мере необходимости, автоматически выделять память. Массивы данных являются подклассами абстрактного класса vtkDataArray, что означает, что для упрощения кодирования могут быть использованы общие виртуальные методы. Однако, для более высокой производительности используются статические шаблонные функции, в которых в зависимости от типа данных происходит переключение с последующим прямым доступом к массивам с непрерывно следующими данными.

Обычно шаблоны C++ не видны в интерфейсе API с общедоступными классами, хотя они широко используются с целью повышения производительности. Это также касается STL: для того, чтобы скрыть сложность реализации шаблона от пользователей и разработчиков приложений, мы обычно используем шаблон разработки PIMPL [1]. Он нам особенно полезен в случае, когда дело доходит до обверток вокруг кода в интерпретируемом коде так, как это описано выше. Поскольку в общедоступном коде API удается избегать использовать сложные шаблоны, то это означает, что, с точки разработчиков приложений, реализация системы VTK такова, что ней не сложно выбирать типы данных. Конечно, если заглянуть глубже, то исполнение кода происходит с использованием тех типов данных, которые определяются на этапе выполнения программы, когда собственно и происходит к ним доступ.

Некоторые пользователи задаются вопросом, почему в системе VTK для управления памятью используется подсчет ссылок на данные, а не используется более удобный для пользователей подход, например, сборка мусора. Основной ответ состоит в том, что поскольку размеры данных могут быть огромными, в VTK нужен полный контроль над тем, что происходит при удалении данных. Например, объем матрицы данных размером 1000×1000×1000, размер каждого элемента в которой равен одному байту, будет составлять гигабайт данных. Достаточно неразумно ждать того момента, пока сборщик мусора решит, нужно или не нужно освобождать память от этой матрицы. В большинстве классов (подклассов класса vtkObject) в системе VTK имеется встроенная возможность подсчета ссылок. В каждом объекте есть счетчик ссылок, который инициализируется единицей, когда создается экземпляр объекта. Каждый раз, когда объект регистрируется, значение счетчика увеличивается на единицу. Аналогичным образом, когда выполняется операция, обратная регистрации (или, что эквивалентно тому, что объект удаляется), счетчик ссылок уменьшается на единицу. В конце концов счетчик ссылок на объект становится равным нулю и в этот момент происходит самоуничтожение объекта. Типичный пример выглядит следующим образом:

vtkCamera *camera = vtkCamera::New();   // счетчик ссылок равен 1
camera->Register(this);                 // счетчик ссылок равен 2
camera->Unregister(this);               // счетчик ссылок равен 1
renderer->SetActiveCamera(camera);      // счетчик ссылок равен 2
renderer->Delete();                     // счетчик ссылок равен s 1 когда renderer удаляетсяis deleted
camera->Delete();                       // camera самоуничтожается

Есть еще одна важная причина, почему в системе VTK важно подсчитывать число ссылок: предоставляется возможность эффективного копирования данных. Например, представьте себе объект данных D1, в котором находится несколько массивов данных: точки, полигоны, цвета, скаляры и текстурные координаты. Теперь представьте обработку этих данных, когда создается новый объект данных D2, который является таким же, как первый, плюс добавляются векторные данные (расположенные в точках). Расточительным подходом является полное (со всеми элементами) копирование объекта D1 в создаваемый объект D2, а затем добавление к D2 нового массива векторов данных. Либо мы создаем пустой объект D2, а затем передаем массивы из D1 в D2 (поверхностное копирование), используя подсчет ссылок для отслеживания владельца данных, и, наконец, добавляем к D2 новый массив векторов. (Прим.пер.: здесь, скорее всего идет речь о том, что создаются ссылки на массивы, а не копируются сами массивы). Последний подход позволяет избежать копирования данных, что, как мы показали ранее, имеет важное значение для хорошей системы визуализации. Как мы увидим далее в этой главе, конвейер обработки данных постоянно выполняет операции такого рода, т. е. копирует данные из входных данных алгоритма в выходные данные и, следовательно, выполняет подсчет ссылок на данные, что необходимо в системе VTK.

Конечно, есть несколько проблем, связанных с подсчетом ссылок. Иногда могут существовать циклические ссылки, когда объекты во взаимодополняющей конфигурации ссылаются друг на друга в цикле. В этом случае требуется интеллектуальное вмешательство, или, как в случае VTK, используется специальный механизм, реализованный в vtkGarbageCollector, с помощью которого происходит управление объектами, задействованных в циклах. Когда обнаруживается такой класс (предполагается, что это происходит во время разработки), класс самостоятельно регистрируется в сборщике мусора и выполняет перезагрузку своих собственных методов Register и UnRegister. Затем, при последующем удалении объекта (или выполнении операции, обратной регистрации) метод выполняет топологический анализ в сетке подсчета ссылок и ищет автономные островки из объектов, ссылающихся друг на друга. Затем эти объекты удаляются сборщиком мусора.

Большинство экземпляров в системе VTK строится через фабрику объектов, реализованную как статический элемент класса. Синтаксис типичного примера выглядит следующим образом:

vtkLight *a = vtkLight::New();

Здесь важно признать, что, в действительности, экземпляр может создаваться не классом vtkLight, он может создаваться подклассом класса vtkLight (например, vtkOpenGLLight). Есть целый ряд факторов, влияющих на фабрику объектов, наиболее важными из которых является переносимость приложений и независимость от устройств. Например, в приведенном выше мы создаем свет в сцене, для которой выполняется операция рендеринга. В конкретном приложении на конкретной платформе vtkLight::New может в результате создать свет с помощью библиотеки OpenGL, однако на других платформах потенциально возможно, что для создания света в графической системе используются другие библиотеки рендеринга. Именно поэтому производный класс, используемый для создания экземпляра объекта, является функцией, зависящей от системной информации времени выполнения. Сначала в системе VTK было множество вариантов, в том числе gl, PHIGS, Starbase, XGL и OpenGL. Хотя большинство из этих теперь исчезли, но появились новые подходы в том числе DirectX и подходы, базирующиеся на использовании графических процессоров. По прошествии времени приложения, написанные с использованием VTK, не должны были измениться поскольку разработчики определили подклассы новых конкретных устройств для класса vtkLight и других классов, используемых для рендеринга, которые поддерживают развивающуюся технологию. Другим важным использованием фабрики объектов является возможность на этапе выполнения приложения выполнять замены, повышающие производительность. Например, класс vtkImageFFT может быть заменен классом, который получает доступ к аппаратным устройствам специального назначения или к библиотеке численной обработки данных.


Продолжение статьи: Представление данных