Оптимизация производительности надстроек MS Project

Microsoft Project, как и другие продукты пакета MS Office, можно с уверенностью назвать быстрой программой (если не брать в расчет взаимодействие с Project Server). Она оперативно выполняет даже сложные действия над проектами, запрашиваемые пользователем. При создании наших дополнений к Project мы обращаем пристальное внимание на скорость их работы. Во-первых, надстройка не должна замедлять работу базовых функций Project в пользу дополнительных возможностей. Во-вторых, функции надстройки тоже должны выполняться быстро: это требование особенно усугубляется из-за того факта, что объектная модель приложений Office не является потокобезопасной, и в них нельзя создавать надежные индикаторы прогресса (progress bar).

В этой заметке мы опишем один из способов ускорения взаимодействия между надстройкой и MS Project, и проведем тест этого приема оптимизации.

Ускоряем работу с коллекциями

Оптимизируя производительность надстроек, важно понимать специфику обмена данными между MS Project и сторонними приложениями. Project предоставляет доступ к программным объектам через технологию COM. Основным достоинством этой технологии, является независимость от языка программирования и исполняющей среды. Однако, в процессе взаимодействия СOM-сервера и клиента, выполняются различные служебные операции, которые требуют дополнительных накладных расходов.

Для доступа к объектной модели MS Project из .NET-приложений, используется библиотека Project Interoperability, являющаяся по сути COM-клиентом. Она сильно упрощает для разработчика доступ к программному интерфейсу Project. Однако, являясь посредником между надстройкой и MS Project, она также является причиной возникновения некоторых задержек при получении данных.

Задержки при работе с API MS Project наиболее ощутимы при большом количестве вызовов API, например, при всевозможных выборках данных. MS Project предоставляет разработчику собственные коллекции данных, основные из которых — Tasks, Resources, Assignments. Указанные коллекции можно перебирать различными способами: через цикл For Each, поскольку все они реализуют интерфейс IEnumerable, или, приведя к IEnumerable<T>, запрашивать данные посредством Linq.

Основным способом снижения накладных расходов, возникающих при получении данных, является их локальное хранение, то есть кэширование. Попробуем оценить, насколько велики задержки при работе с коллекциями MS Project, относительно работы с локальными коллекциями. Важно также понять, в каких случаях стоит применять кэширование, а в каких нет.

Методика тестирования

Проведём тесты производительности на примере кода, который получает список с названиями всех задач проекта (тестовый проект содержит 1000 задач). Выполним тестовую функцию для 3-х вариантов перебора: перебор COM-коллекции Tasks напрямую, перебор массива и перебор универсального списка. Массив и список получим предварительно, поместив туда задачи из коллекции Tasks.

Для полноты картины выполним замеры некоторое количество раз, например 100.

        private void Test(MSP.Tasks comCollection, TestType type, string fileName) 
        {
            using (StreamWriter sw = new StreamWriter(fileName)) {
                IEnumerable<MSP.Task> collection = null;
                switch (type) {
                    case TestType.Com:
                        collection = comCollection.Cast<MSP.Task>();
                        break;
                    case TestType.Array:
                        collection = comCollection.Cast<MSP.Task>().ToArray<MSP.Task>();
                        break;
                    case TestType.List:
                        collection = comCollection.Cast<MSP.Task>().ToList<MSP.Task>();
                        break;
                }

                for (int i = 1; i <= 100; i++) {
                    Stopwatch watch = new Stopwatch();
                    watch.Start();

                    List<string> taskNames = new List<string>();
                    foreach (MSP.Task task in collection) {
                        taskNames.Add(task.Name);
                    }

                    watch.Stop();
                    sw.WriteLine(watch.ElapsedMilliseconds.ToString() + ";");
                    watch.Reset();
                }
            }
        }

Полученные результаты

Длительность перебора задач проекта

Длительность перебора задач проекта

На рисунке представлен график времени выполнения операции (миллисекунд) для каждого шага тестирования. Из графика мы видим, что время выполнения операции при переборе коллекции превышает оное при переборе массива или списка более чем в 10 раз. Такие длительные задержки имеют особенно серьезное значение при переборе больших коллекций данных.

Поскольку задержки вызваны, в первую очередь, самой технологией взаимодействия клиентских приложений с API Project, похожие результаты мы получим и для других коллекций Project, таких как Resources или Assignments.

Выводы

Как видно из графика, перебор массива или списка в примерно в 10 раз быстрее, чем перебор COM-коллекции. При этом большой разницы, кэшировать ли данные в универсальный список или массив, нет.

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

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>