Intel Parallel Studio – инструмент для создания параллельных приложений
Компания Intel расширяет свой многолетний опыт создания программных инструментов в сторону клиентских приложений для персональных компьютеров. Практика переноса или «конверсии» технологий из области научных исследований и высокопроизводительных вычислений в масс-маркет всегда приносила хорошие результаты. И сейчас наступает время, когда разработчикам клиентских Windows-приложений понадобятся мощные и удобные инструменты для адаптации существующих или написания новых приложений, максимально использующих производительность пользовательских систем на базе мультиядерных процессоров. Мы решили назвать эту экосистему мэйнстримом (mainstream), где используются требовательные к производительности приложения для десктопов и мобильных систем, написанные под Windows на языке С или C++. Понятно, что часто графический интерфейс приложений разрабатывается с использованием Java или .Net и managed-языков. Однако мы полагаем, что в большинстве случаев те части приложений, которые требовательны к производительности (решатели, фильтры, кодеки и т.д.), реализованы именно на С/С++, и именно в них важно добиться задействования всех возможностей микропроцессора.
Очевидно, что фактически стандартный набор разработчика мэйнстрим-приложений – это Microsoft Visual Studio. Нисколько не умаляя качеств этого продукта, Intel предлагает расширить возможности Visual Studio для того, чтобы упростить и оптимизировать цикл разработки масштабируемых параллельных программ для Windows.
Intel Parallel Studio – это набор из нескольких инструментов, являющийся гармоничное продолжение или расширение Microsoft Visual Studio и позволяющий за счет удобства использования, понятного интерфейса и оригинальных технологий добиваться хорошей эффективности параллельных программ на мультиядерных системах. Несмотря на то что этот набор представляет собой plug-in к Visual Studio, он полностью покрывает все этапы разработки приложения программистом, от создания скелета будущей параллельной программы до оптимизации релизной версии проекта. В этот набор входят четыре отдельных продукта, каждый из которых используется в своем сегменте цикла разработки, и каждый может быть проинсталлирован и интегрирован в Visual Studio как по отдельности, так и в составе всего пакета сразу.
Intel Parallel Advisor: поможет найти возможности распараллеливания кода с самого начала разработки приложения.
Intel Parallel Composer: предназначен для генерирования параллельного кода, т.е. создания программ с помощью компилятора и широкого набора библиотек для многопоточных алгоритмов.
Intel Parallel Inspector: проверит параллельное приложение на корректность и найдет ошибки работы с памятью.
Intel Parallel Amplifier: обнаружит узкие места в программе, которые мешают масштабируемости и увеличению производительности на мультиядерных платформах.
Так как цикл разработки может быть непрерывным и повторяемым в смысле возврата к коррекции дизайна после любого этапа, вплоть до оптимизации производительности, важно, чтобы все эти инструменты слаженно работали в одной среде и могли взаимодействовать друг с другом. Именно поэтому они глубоко интегрированы в среду Microsoft Visual Studio (рис. 1), и разработчику уже не придется запускать отдельные профилировщики и проводить отдельные эксперименты, когда нужно найти потоковые ошибки или оптимизировать программу. Весь функционал доступен в текущем проекте, из панелей инструментов или меню Visual Studio, а результаты анализа размещаются там же, где и файлы проекта (хотя пользователь может и сам выбрать, где ему хранить файлы с результатами).
Intel Parallel Advisor
Parallel Advisor – это совершенно новая категория или новый класс инструментов, который несет в себе некую методологию создания параллельных программ «с нуля» с правильными подходами к их реализации, в том числе с использованием параллельных библиотек производительности. Понятно, что не каждая программа или алгоритм легко и просто параллелятся. Advisor найдет, из-за чего именно параллельная реализация может оказаться неэффективной, и попытается выдать нужные решения. Кроме того, все знания по применению параллельных библиотек будут собраны здесь в виде сэмплов и шаблонов, для того чтобы максимально облегчить начальный этап их использования.
Однако и в случае с распараллеливанием готовой последовательной программы Advisor предоставит «путеводную нить», или workflow, по распараллеливанию, проверке корректности и оптимизации приложения, если разработчик пока еще не в силах «уложить» в голове эту методологию. Впрочем, через какое-то время работы с проектом надобность в Advisor отпадает, так как становятся понятными принципы разработки параллельной программы, а элементы методологии рассматриваются как дополнительный ресурс Help Page.
В текущем пакете Advisor не присутствует, зато его первая реализация в виде утилиты Intel Parallel Advisor Light доступна среди других исследовательских проектов на сайте Intel. Это сделано для того, чтобы сообщество разработчиков могло поделиться своими идеями относительно методологий распараллеливания, которые, возможно, будут потом реализованы в новом продукте. Тот же путь прошли хорошо известные теперь параллельная библиотека TBB и инструмент Performance Tuning Utility, которые стали частью Intel Parallel Studio, возвращая разработчикам их идеи в виде open source-библиотеки для разработки на С++ и совершенно нового продукта для оптимизации производительности.
Intel Parallel Composer
Несмотря на некий маркетинговый акцент в названии продукта Composer – это не просто компилятор С++ от Intel. Он уже интегрирован в Visual Studio вместе с библиотекой производительности IPP и параллельной библиотекой TBB, что значительно облегчает процесс разработки параллельного кода для новичков, т.е. тех, кто еще не пользовался продуктами Intel (например, Compiler Pro) и только собирается попробовать улучшить производительность своих приложений с помощью технологий Intel.
Наличие в пакете сразу нескольких компонентов позволит сразу же начать оптимизировать программу с использованием параллельных технологий, которые содержит Composer:
- вычислительные примитивы, реализованные в виде функций в библиотеке IPP, гарантируют высокую производительность алгоритмов на платформах Intel;
- поддержка новой версии стандарта OpenMP 3.0 позволит применять multitasking, недоступный в предыдущих версиях, которые поддерживаются в том числе и компилятором Microsoft;
- новый тип данных Valarray немного упростит код, реализующий векторные операции, а компилятор сгенерирует эффективный бинарный код, задействующий SIMD-инструкции для увеличения производительности;
- поддержка компилятором элементов стандарта С++ 0х облегчит кодирование программистам.
И наконец, новая «фича», пока недоступная даже в Intel Compiler Pro – это встроенный параллельный отладчик PDE (Parallel Debugging Extention). Он является расширением к отладчику Microsoft и позволяет анализировать данные, разделяемые между потоками OpenMP, акцентируя внимание на конфликтах доступа и возможных проблемах с корректностью параллельных вычислений. PDE вводит новый тип точек останова по событиям, например, таким как конкурентный доступ потоков к переменным или вход потоками в одну и ту же функцию. Подобной информации может быть собрано очень много, поэтому богатый набор фильтров позволяет отсеять неинтересные для разработчика события и сконцентрироваться на проблемных местах.
Чтобы быстро определить, насколько корректны результаты вычислений, выполняемых в параллельном регионе, можно выполнить в отладчике любой регион последовательно и сравнить потом результаты. Причем перекомпилировать ни приложение, ни исследуемый модуль для этого не требуется.
Intel Parallel Inspector
Это, пожалуй, самый востребованный и ожидаемый инструмент на сегодняшний день, так как он помогает избавиться от ошибок в многопоточной программе на этапе верификации, повышая корректность и стабильность ее исполнения. Несмотря на свой характерный функционал, Inspector применяется не только командами тестировщиков (QA team). Нормальная инженерная практика предполагает проверку программы на наличие ошибок и самим разработчиком, хотя бы на уровне юнит-тестов (unit tests).
Давайте разберем, какие же ошибки помогает обнаружить Parallel Inspector. Инструмент адресует два класса ошибок: ошибки многопоточности и ошибки работы с памятью, причем анализ для каждого класса запускается отдельно. Последний класс ошибок хорошо известен программистам, которые до последнего времени использовали различные инструменты, чтобы найти утечки памяти, нарушение целостности стека или доступ по несуществующим адресам. Второй класс ошибок связан с многопоточной природой программ. Они неизбежно возникают при разработке параллельных приложений, и их чрезвычайно сложно отловить, особенно если они проявляются нерегулярно и только при совпадении определенных условий.
Ошибки работы с памятью
Механизм обнаружения ошибок памяти основан на анализе абсолютно всех инструкций чтения/записи и их адресов на уровне бинарного кода с помощью бинарной инструментации. В основе инструментации лежит утилита Pin – A Dynamic Binary Instrumentation Tool, которая внедряется в исследуемый процесс во время его запуска и позволяет отслеживать выполнение практически любых инструкций, предоставляет API доступа к содержимому регистров, контексту выполнения программы, символьной и отладочной информации. Понятно, что всесторонний анализ исполняемого кода не может быть проведен без существенных накладных расходов, поэтому он разделен на уровни, отражающие глубину и сложность анализа. Чемы выше уровень, тем больше потребуется времени для проверки приложения.
- Уровень mi1 – позволяет обнаруживать только утечки памяти, выделенной в куче (heap). Глубина стека функций равна 7, что даст достаточно информации для определения местонахождения ошибки и структуры вызовов функций, выделявших память.
- Уровень mi2 – позволяет обнаруживать все остальные ошибки работы с памятью в куче, которые мы рассмотрим ниже. Однако для снижения накладных расходов и ускорения анализа глубина стека выбрана равной единице. То есть на этом уровне можно найти ответ на вопрос, есть ли в принципе ошибки в программе, – а где находятся эти ошибки, поможет определить следующий уровень.
- Уровень mi3 – отличается от предыдущего тем, что глубина стека увеличена до 12 и добавлен функционал поиска утерянных указателей. На этом уровне происходит наиболее полный анализ корректности работы с памятью в куче, но за это приходится платить высокими накладными расходами, которые увеличат время выполнения программы от 20 до 80 раз по сравнению с оригиналом.
- Уровень mi4 – высший уровень, дополнен анализом ошибок доступа к памяти, выделенной на стеке, которые не обнаружены на стадии компиляции или с помощью опций run-time check компилятора. Уровень вложенности функций – 32. Как и все остальные уровни, 4-й является инклюзивным, т. е. включает в себя все виды анализа на предыдущих уровнях. Соответственно накладные расходы будут максимальными.
Глубина стека на каждом уровне выбрана эмпирически и является компромиссом между полнотой предоставляемой информации и величиной накладных расходов на анализ приложения. В данной версии продукта пользователь не может менять глубину стека.
В зависимости от выбранного уровня анализа Parallel Inspector способен обнаруживать следующие виды ошибок работы с памятью:
- Memory Leak – возникают при выделении программой памяти в куче и неосвобождении ее по окончании программы;
- Invalid Memory Access – возникают при чтении/записи по недействительным адресам памяти, в куче или в стеке;
- Invalid Partial Memory Access – возникают при чтении/записи по частично недействительным адресам памяти;
- Missing Allocation – возникают при попытке освободить память по несуществующему адресу;
- Mismatched Allocation/Deallocation – возникают при попытке освободить память с помошью функций, не соответствующих функции выделения памяти;
- Uninitialized Memory Access – возникают при попытке чтения неинициализированной памяти, в куче или в стеке;
- Uninitialized Partial Memory Access – возникают при попытке чтения частично неинициализированной памяти.
Приведем лишь несколько простых примеров для иллюстрации ошибок памяти (Листинг 1).
// Запись по недействительному адресу
// освобожденной памяти
char *pStr = (char*) malloc(20);
free(pStr);
strcpy(pStr, “my string”); // Ошибка!// Попытка чтения неинициализированной памяти
char* pStr = (char*) malloc(20);
char c = pStr[0]; // Ошибка!
// Функция освобождения памяти
// не соответствует функции выделения
char *s = (char*)malloc(5);
delete s;// Ошибка!
// Выход за пределы стека
void stackUnderrun()
{
char array[10];
strcpy(array, “my string”);
int len = strlen(array);
while (array[len] != ‘Z’) // Ошибка!
len—;
}
Листинг 1. Иллюстрация ошибок памяти
По окончании анализа приложения предоставляется лог событий или ошибок, которые были выявлены. Причем на всех уровнях, кроме mi1, можно начать анализировать логи еще до окончания анализа, так как результаты уже будут доступны.
Результаты структурированы таким образом, чтобы вначале дать обзор списка проблем (рис. 2), от которых потом можно перейти к деталям или в окно анализа исходного кода. В списке с найденными ошибками доступна вся информация относительно процесса, модуля и функции, в которой эта ошибка произошла. При длинном списке ошибок (в случае достаточно большого проекта) удобно воспользоваться функционалом фильтрации по типу ошибки, по имени исходника, по имени функции или модуля. Исходный код сопровождается стеком вызовов функций. Для редактирования программы достаточно двойного щелчка мышки на строке кода, чтобы попасть в редактор Visual Studio.
Ошибки многопоточности
Наиболее распространенные ошибки многопоточности – это «гонки» (Data Races), или конкурирующий доступ потоков к разделяемым данным, и взаимоблокировки (Deadlocks), когда, захватив неправильно расставленные объекты синхронизации, потоки самозаблокировались и не могут продолжить выполнение. Простейший пример ошибки, которая рано или поздно приведет к «зависанию» программы, представлен в листинге 2.
// Поток A захватывает критическую секцию L1,
// затем ожидает критическую секцию L2
DWORD WINAPI threadA(LPVOID arg)
{
EnterCriticalSection(&L1);
EnterCriticalSection(&L2);
processA(data1, data2);
LeaveCriticalSection(&L2);
LeaveCriticalSection(&L1);
return(0);
}
// Поток В захватывает критическую секцию L2,
// затем ожидает секцию L1
DWORD WINAPI threadB(LPVOID arg)
{
EnterCriticalSection(&L2);
EnterCriticalSection(&L1);
processB(data2, data1) ;
LeaveCriticalSection(&L1);
LeaveCriticalSection(&L2);
return(0);
}
Листинг 2. Пример блокирующей иерархии объектов синхронизации
Коварство ошибок многопоточности заключается в том, что при отладке или тестировании приложения они могут и не проявляться. Но стоит изменить некоторые временные параметры, например, запустить приложение на системе с другой тактовой частотой процессора, они тут же обнаруживаются. Особенно болезненна для производителя софта ситуация, когда оттестированное вдоль и поперек в своей лаборатории приложение иногда «падает» на системе заказчика, и отладить его удаленно нет никакой возможности.
Для Parallel Inspector не имеет никакого значения, произошла ошибка во время исполнения анализируемого приложения или нет. Он инструментирует все инструкции потоковых API, вызовов функций синхронизации и обращения к разделяемым между потоками объектам на уровне бинарного кода. Затем проводятся анализ исполняемого пути приложения и выявление даже гипотетической возможности одновременного доступа потоков к незащищенным данным. Вот почему важно, чтобы были пройдены все критические пути исполнения кода приложения, т. е. тест должен обладать свойством полноты.
Те, кто уже знаком с инструментами многопоточного анализа Intel, наверняка обнаружат сходство с Thread Cheker. Действительно, в чем-то интерфейс был позаимствован оттуда, однако он значительно расширен, а механизм обнаружения ошибок и инструментирования, который лежит в основе Inspector, уже совершенно другой. Тем не менее технология бинарного инструментирования влечет за собой значительные накладные расходы на выполнение приложения, поэтому оно будет исполняться дольше, чем оригинал. Это неизбежная реальность. В связи с этим пользователю предоставлена возможность управления интрузивностью анализа. Перед началом анализа он выбирает один из четырех уровней анализа, в зависимости от его глубины:
- уровень ti1 – позволяет обнаруживать взаимные блокировки потоков. Глубина стека функций равна единице;
- уровень ti2 – дополнительно позволяет обнаруживать конкуренцию доступа к незащищенным данным, «гонки»;
- уровень ti3 – отличается от предыдущего тем, что глубина стека увеличена до 12;
- уровень ti4 – высший уровень, позволяющий определять все типы ошибок многопоточности. Уровень вложенности функций – 32. Как и все остальные уровни, 4-й является инклюзивным, т. е. включает в себя все виды анализа на предыдущих уровнях. Соответственно накладные расходы будут максимальными.
В дополнение к ошибкам взаимной блокировки Inspector способен обнаруживать следующие ошибки многопоточности:
- Lock Hierarchy Violation – возникает при захвате нескольких объектов синхронизации, состоящих в иерархии или уже захваченных данным потоком. Являются подмножеством ошибки взаимной блокировки, Deadlock;
- Potential Privacy Infringement – возникает при попытке доступа к стековой памяти другого потока.
По окончании исполнения приложения Inspector выведет список ошибок и диагностических сообщений о событиях, связанных с существованием потоков в процессе выполнения. Каждому сообщению Inspector сопоставит строку исходного кода, в которой найдена причина того или иного события или ошибки, а также стек вызовов функций и адрес памяти. Так как потоковые ошибки сопряжены с выполнением программы несколькими потоками одновременно, а иногда доступ к незащищенным данным может происходить из разных функций, то для удобства представления и обнаружения причин ошибки Inspector отображает сразу два окна исходного кода с функциями, выполнявшимися разными потоками (рис. 3). Из любого окна очень легко перейти в редактор Visual Studio для редактирования программы. Для того чтобы проанализировать приложение, не требуется никаких специальных ключей компиляции, достаточно просто в режиме Debug запустить инструмент на исполнение.
Intel Parallel Amplifier
Профилировщик производительности предназначен для того, чтобы выяснить, насколько эффективно приложение использует мультипроцессорную платформу и где находятся те узкие места в программе, которые мешают платформе масштабироваться и увеличивать производительность с ростом количества вычислительных ядер в системе. Методология профилировки приложения предельна проста: необходимо ответить на три основных вопроса, каждый из которых соответствует своему типу анализа и отражает суть, место и причины проблем с производительностью.
Hotspot-анализ. Вопрос: «На что моя программа тратит вычислительное время процессора?» Необходимо знать те места в программе (Hotspot-функции), где тратится больше всего вычислительных ресурсов при исполнении, а также тот путь, по которому можно в эти места попасть, т. е. стек вызовов.
Concurrency-анализ. «Почему моя программа плохо параллелится?» Бывает, что независимо от того, насколько продвинута параллельная инфраструктура приложения, ожидаемый прирост производительности при переходе, например, от четырехъядерной системы к восьмиядерной, так и не достигается. Поэтому тут нужна оценка эффективности параллельного кода, которая дала бы представление о том, насколько полно используются ресурсы микропроцессора.
Lock & Wait-анализ. «Где моя программа простаивает в ожидании синхронизации или операции ввода-вывода?» Поняв, что программа плохо масштабируется, нужно найти, где и какие именно объекты синхронизации стали препятствием к хорошей параллельности. Не исключено, что следует пересмотреть реализацию алгоритмов, а может быть, и все параллельную инфраструктуру приложения.
Каждый из этих видов анализа запускается по отдельности и имеет собственное окно представления результатов. При этом встроенный Source View расширяет возможности обзора результатов относительно исходного кода программы, а Statistical Call Tree, или статистическое дерево вызовов, поможет получить «объемное» представление о путях вызовов Hotspot-функций. Наличие встроенного функционала сравнения результатов позволяет отслеживать влияние изменения кода программы на ее производительность.
Итак, результатом запуска приложения на Hotspot-анализ будет интегрированное в главное окно Visual Studio окно со списком «горячих» функций, напротив каждой из которых представлена ее временная характеристика как в числовом, так и в графическом представлении. По умолчанию результаты отсортированы так, что самая «горячая» функция окажется в списке первой. Кроме того, имя каждой функции может быть «раскрыто» для представления всех стеков вызовов этой функции. А в окне Stack View можно определить, какой именно путь внес наибольший вклад (в смысле влияния на производительность).
Необходимо отметить, что поиск Hotspot-функций не несет в себе существенных накладных расходов, т. е. анализ не влияет на время исполнения анализируемого приложения. Это достигается за счет технологии временного сэмплирования стеков (Stack Sampling), благодаря которой разработчик получает трассу с данными, содержащими статистически значимые временные показатели функций, их стеки, а также контекст исполнения. В постпроцессинге трасса раскрывается в имя функции, потока, модуля и процесса, и выстраиваются список Hotspot-функций и статистическое дерево вызовов.
Естественно, что определив имя интересующей нас Hotspot-функции, хотелось бы проанализировать ее исходный код, чтобы понять, какие конструкции чрезмерно потребляют время процессора. Двойной клик мышкой на имени функции открывает закладку Source View, где затраченное функцией время на исполнение распределено по строкам исходного кода, а самое «горячее» место перемещено в центр окна – это очень удобно, особенно когда листинг функции занимает несколько экранов (рис. 4).
Если переключиться на закладку статистического дерева вызовов, можно получить Top-Down представление для данной функции, которое помогает определить все пути ее вызовов и критический путь. Важными временными характеристиками функций здесь являются Self-Time (время, затраченное на выполнение самой функцией) и Total-Time (время, затраченное самой функцией и всеми вызванными из нее функциями, так называемыми Children Functions). Без такого представления, в частности, очень трудно определить, каким образом часто исполняемые функции (типа memcpy) влияют на производительность, если они попали в список самых «горячих» в Hotspot-листе.
В результатах Concurrency-анализа главная метрика – это, пожалуй, интегральная характеристика параллельности всего приложения, представленная в окне Summary, которая дает представление о том, насколько хорошо распараллелено приложение в целом. Эта характеристика представлена гистограммой распределения времени исполнения приложения по уровням параллельности (Concurrency Level). Под уровнем параллельности понимают долю времени, в течение которого программа выполнялась в N ядрах процессора. Уровень N = 1 означает, что программа выполнялась последовательно, N = 2 – в двух потоках и т. д. В идеальном случае график должен иметь пик на уровне N, равном числу вычислительных ядер в системе, и незначительные показатели на всех остальных уровнях. Другое важное свойство такого представления состоит в том, что по графику можно оценить потенциал роста производительности программы в случае разрешения проблем с масштабируемостью.
В главном окне выводится список функций, при выполнении которых программа недоиспользовала возможности микропроцессора и уровень параллельности был низкий. Чем больше времени выполнялась функция в неэффективном с точки зрения производительности режиме, тем выше она в списке. Подчеркнем, что это не список Hotspot-функций, хотя он может с ним пересекаться. Здесь напротив каждой функции представлена графическая характеристика параллельности программы в виде «градусника», где зеленый цвет обозначает порцию времени исполнения в идеальном для данной системы режиме, а красным – время, в течение которого ресурсы системы недоиспользовались. Если функция вызывалась из нескольких мест, то каждому ее стеку будет атрибутирована эта характеристика. Есть еще синий цвет, который обозначает время исполнения программы в потоках, количество которых превышает число ядер процессора, – переиспользование ресурсов. Если число потоков незначительно превышает идеальное их число, ничего страшного с производительностью обычно не происходит, так как влияние избыточного количества потоков в очереди системы становится заметным со значительным его увеличением (рис. 5).
Таким образом, в параллельной программе важно не только найти Hotspot-функции, но и понять, насколько хорошо они распараллелены и нужно ли над ними работать в плане более эффективной балансировки нагрузки или оптимизации объектов синхронизации. Если же Hotspot-функции распараллелены хорошо, то они не окажутся в списке с красной зоной «градусника», и тогда работа по увеличению производительности разделяется на две задачи: оптимизация Hotspot-функций на микроархитектурном уровне и улучшение параллельности тех функций, которые есть в списке и недоиспользуют ресурсы микропроцессора.
Профилировка приложения с помощью Locks & Waits-анализа поможет найти причину, по которой приложение не масштабируется, что мешает ему выполняться с большим количеством потоков. В суммарной характеристике появляется метрика заблокированных потоков, т. е. порция времени, когда потоки находились в заблокированном состоянии и не нагружали микропроцессор. Главное же окно отображает список функций, исполнение которых было заблокировано в ожидании каких-либо объектов синхронизации; при этом чем больше влияние такой функции на время исполнения программы, тем выше она в списке. Изменяя группировку объектов отображения в списке, можно получить список тех самых объектов синхроницации, которые и являются причиной блокировок. Будь то критическая секция, «накрывающая» большой и часто исполняемый в потоках участок кода, или Wait-функция, ожидающая события достаточно большое количество времени, с этим надо будет что-то делать уже самому программисту. Получив список «виновников» плохой масштабируемости, разработчик сам принимает решение, каким образом изменить реализацию данного алгоритма или всю потоковую инфраструктуру в целом. Важно, что он имеет объективную картину состояния с блокировками в программе и может оценить, сколько необходимо усилий для переписывания софта и что это в результате даст.
Заключение
Предлагая комплексное решение в виде нового набора инструментов для разработки параллельных програм, Intel ставит своей задачей облегчить старт новичкам и сделать работу более продуктивной для опытных разработчиков на С/С++ под Windows, использующих Microsoft Wisual Studio. Разрабатывая эффективные и масштабируемые программы, мы сохраняем инвестиции в будущем за счет автоматического увеличения производительности приложений на будущих многоядерных платформах.
Выпуская Parallel Studio, Intel провела обширную программу бета-тестирования, в ходе которой множество разработчиков пользовались продуктом и присылали свои отзывы и рекомендации по каналам обратной связи. Все эти мнения были учтены и рассмотрены архитекторами проекта; был также сделан вывод о чрезвычайной важности участия комьюнити в исследовательских проектах, разрабатываемых в Intel. Собственно, поэтому важные исследовательские проекты размещены на сайте исследовательских проектов Intel whatif. Кроме того, все замечания и пожелания по улучшению продукта можно разместить на ISN форумах, как англоязычном, так и русскоязычном, тем более что для данного продукта это будет основная модель поддержки клиентов.