- PVSM.RU - https://www.pvsm.ru -
Статья, первая часть которой здесь представлена, содержит различные соображения автора, накопившиеся в ходе длительной разработки специализированной системы поиска социальных связей, базирующейся на библиотеке Boost Graph Library (BGL). В этом (техническом) разделе суммируются впечатления автора от работы с этой библиотекой, поднимаются вопросы инструментовки при создании графовых приложений и затрагиваются некоторые практические проблемы метапрограммирования на C++.
Шаблонная библиотека BGL [1] наверняка известна любому разработчику, сталкивавшемуся с графовыми задачами. Появившись в составе Boost 1.18.1 в 2000 году, она сразу же заслужила одобрительные отзывы таких классиков жанра, как Александр Степанов. Руководство к библиотеке, составленное Джеререми Сиком, Лай-Кваном Ли и Эндрю Ламсдейном вышло в русском переводе в 2006 в издательстве «Питер» (оригинал – Jeremy G. Siek, Lie-Quan Lee и Andrew Lumsdaine, «The Boost Graph Library», 2001, Addison-Wesley). Библиотека интенсивно обновлялась и развивалась практически до конца 2013 года (Boost 1.55.0). В частности, в 2005 году появился анонс ее распределенной версии (PBGL), которая вошла в состав Boost с версии 1.40 в 2009 году и по сию пору остается чем-то вроде стандарта де-факто для графовых вычислений на высокопроизводительных кластерах, во всяком случае, в академическом мире. Насколько можно судить по истории коммитов, до 2005 года основным разработчиком библиотеки был Джерерми Сик, после 2005 – Дуглас Грегор (Douglas Gregor), а вообще в разное время над библиотекой работало немалое количество разнообразного люда. Посвященные ей публикации неоднократно появлялись и на habr.com: в первую очередь нужно отметить серию статей Вадима Андросова: [1 [2], 2 [3], 3 [4]]. Таким образом, библиотеке в принципе посвящена хорошая и разнообразная литература, но собственная ее документация, также, вообще говоря, довольно обширная, несколько страдает из-за того, что:
, спустя некоторое время с удивлением обнаружил реализованное еще в 2005 году представление compressed_sparse_row_graph (разряженную матрицу). Аналогичная история имела место с алгоритмом Брона-Кербоша. Не верьте оглавлениям, используйте прямой поиск по заголовочным файлам;The BGL currently provides two graph classes and an edge list adaptor:
adjacency_list
adjacency_matrix
edge_list
Количество категорий и разнообразных селекторов, в том числе схожих до степени смешения, настолько велико, что в них временами начинают путаться и сами авторы. Например, в конструкторах уже упоминавшегося выше compressed_sparse_row_graph в текущей версии присутствует систематическая ошибка, приводящая к вылетам при попытках копировать ненаправленный список смежности:
Здесь можно по случаю отметить, что полное тестирование столь гибкого механизма представляет собой отдельную проблему, так как сопровождается комбинаторным взрывом числа возможных подстановок.
Нужно с сожалением отметить, что в настоящее время основные разработчики, видимо, утратили интерес к дальнейшей работе над библиотекой и последние шесть лет она, отнюдь не исчерпав потенциала своего развития и даже не освободившись вполне от внутренних несогласованностей и прямых ошибок, находится в свободном полете. Озвучивавшиеся в районе 2011 года планы по существенному расширению набора методов и покрытию новых областей теории графов (в том числе за счет добавления внутренней поддержки graph partitioning к возможности читать формат METIS) остались нереализованными. Представляется также, что библиотека могла бы многое выиграть (как минимум, в отношении читаемости) от широкого использования новинок, вошедших в стандарт после 2011 года.
Таким образом, вопросы выбора опорной библиотеки для графовых приложений при взгляде из 2019 года выглядят не столь однозначно, как хотелось бы, и за последние 5 лет неопределенность скорее возросла, чем уменьшилась.
Ситуация эта вызывает некоторую печаль, т. к. создание универсального механизма, подобного BGL, само по себе является своего рода интеллектуальным подвигом, и как по мощности подхода, так и по богатству арсенала реализованных универсальных методов (добрых полторы сотни однопоточных и пара десятков распределенных) библиотека, насколько известно автору сих строк, по прежнему не имеет себе равных.
На данный момент только эта библиотека принципиально позволяет без потери производительности, навязывания жестких соглашений по представлению данных и потери контроля над внутренними механизмами самой библиотеки, полностью развести графовые алгоритмы и графовые представления, дополнительно сделав последние вполне независимыми от представления метаданных, ассоциированных с ребрами и вершинами (что в принципе и является, очевидно, наиболее правильным способом ведения дел).
Слово «принципиально» здесь использовано не без причины. Рассматривая конкретную ситуацию на примере уже упоминавшегося выше многострадального класса compressed_sparse_row_graph, можно отметить, например, следующие отступления от высоких стандартов:
Наконец, в шаблоне compressed_sparse_row_graph осталась нереализованной специализация для ненаправленного графа (boost::undirectedS).
В связи с этим при использовании свойства edge_index (порядкового номера ребра) дополнительные сложности возникают из-за того, что для списка смежности данное свойство должно явно задаваться как внутреннее и как таковое может изменяться по произволу, но для ненаправленного графа его значение не зависит от направления, в котором проходится ребро. Для разряженной же матрицы (всегда направленной) оно является встроенной константной property_map специального вида (вычисляющейся как индекс в массиве ребер). Соответственно, значения для встречных ребер (представляющих ненаправленный граф) не могут изменяться и всегда будут различными.
Все эти расхождения приводят к невозможности «простой замены представления графа на эквивалентное» при вызове алгоритмических функций, что существенно подрывает основное преимущество библиотеки. На практике в подобных случаях требуется либо избыточная специализация кода, либо его переработка для исключения элементов с различающимся поведением, либо такая подстройка шаблонов графов, чтобы они при различающихся определениях атрибутов «вели себя одинаково», либо, наконец, вынос из библиотеки отдельных файлов и создание «личной версии boost».
Дополнительно можно отметить следущие, не столь существенные, неудобства:
Некоторые представления (тот же compressed_sparse_row_graph) позволяют управлять этими размерностями. Другие же (adjacency_list) не имеют подобных параметров и всегда используют 64-разрядные целые (как правило, избыточные), заменить которые невозможно без модификации кода;
Реализация подобных функций, естественно, зависит от графового представления: в данном случае она может реализовываться тривиальным обменом элементов пары, более или менее эффективным поиском по контейнеру или вовсе отсутствовать. Конечному пользователю трудно разбираться во всем этом разнообразии вариантов, тем более, что согласно идеологии библиотеки, внутренние члены дескрипторов и не должны представлять для него интереса.
Где-то по соседству располагается тематика поддержки технически ненаправленных графов, имеющих на ребрах служебный маркер направления. Впрочем, повышенное внимание к такому представлению может быть связано с частной спецификой решаемых автором задач, и наличие широкого интереса к поддержке таких объектов неочевидно.
В самом деле, автоматические помощники:
Другой аспект той же ситуации можно проиллюстрировать при помощи первой строки кодового фрагмента, изображенного на предыдущем рисунке. Читателю предлагается выполнить запросы «boost current time» vs «CRT current time» и сравнить результаты. Да, boost::date_time (ныне частично переехавший в std) дает возможность правильно делать множество сложных вещей, в то время как CRT позволяет неправильно делать несколько тривиальных операций, но в повсеместно встречающихся простейших бытовых ситуациях CRT оказывается со всех точек зрения удобней, а многочленные конструкции вида posix_time::second_clock::local_time (пример щадящий) тяготеют к превращению в кочующие по программе иероглифы. Лишите разработчика доступа к личной библиотеке таких иероглифов и скорость разработки устремится к нулю [6].
Boost::string_algo дает возможность делать со строками что угодно, но, положа руку на сердце, каждая не вполне тривиальная операция сопровождается сеансом повторного чтения документации для освежения общей логики библиотеки, наименований предикатов, а также отдельным упражнением по выяснению совместимости параметров. Схожая ситуация имеет место и с операциями токенизации в boost::regexp, при безупречной внутренней логике последних.
Коли такая ситуация имеет место с самыми общеупотребительными библиотеками, неудивительно, что BGL, как библиотека более специализированная, в которой к тому же имеются, например, функции make_property_map_function и make_function_property_map, не имеющие отношения друг к другу, а также сакраментальная функция get, перезагруженная на любое число аргументов любого типа, порождает те же проблемы, но в гипертрофированном виде. Да-с, любая задача может быть решена цепочкой вызовов get, но, увы, не каждая цепочка get решает данную задачу.
Читать такой код бывает легко и приятно, он даже может выглядеть как конспект формально записанного алгоритма на естественном языке, но при его написании сказывается невозможность заменять слова синонимами и т. п. проявления жесткости, для настоящего «естественного языка» нехарактерной.
(С другой стороны, регулярно происходящие обновления boost и std приносят множество не вполне тривиальных и зачастую чрезвычайно полезных конструкций и неожиданных решений, действительно позволяющих с меньшими затратами писать более ясный и компактный код. Однако поток новинок настолько широк, неравноценен и слабо структурирован, что важнейшие дополнения к стандартной библиотеке, даже такие очевидные, как затронутые ниже variants/apply_visitor или any, если концептуальные преимущества их применения в контексте конкретного проекта не относятся к разряду самоочевидных, без помощи счастливого случая могут подолгу выпадать из фокуса внимания, если не тратить существенную долю рабочего времени непосредственно на вдумчивое отслеживание новинок, изучение нетривиальных примеров их использования и мысленные попытки приложить их к уже существующему коду. Видимо, при имеющейся интенсивности обновлений единственный способ управиться с этой проблематикой – держать на каждый пяток практикующих программистов C++ одного C++-теоретика, занятого только вопросами приоритетности новинок, их внедрения в проект и выборочного просвещения практиков. Вывод: не начинайте C++-проекты с меньшим числом разработчиков).
Таким образом, если бы данный алгоритм писался в традиционном стиле, в его теле появилось бы три селектора с минимум тремя ветками в каждом (и необходимостью корректировать тело при появлении новых представлений). Так как каждое ветвление в теле алгоритма, отрабатывающем при проходе по графу огромное число раз, выливается в заметные потери времени, стремление избежать этих потерь при сохранении кода все того же традиционного стиля может привести к появлению 27+ реализаций алгоритма для различных комбинаций представлений.
Метапрограммный стиль должен спасать от этих неприятностей, позволяя поддерживать одну описывающую алгоритм метафункцию, неявно генерирующую все нужные реализации (а также, возможно, некоторое, и возможно немалое, количество ненужных, если runtime-структуры кода де-факто не генерируют некоторых сочетаний типов, что при комбинаторном взрыве вариантов может стать проблемой [8]), не содержащие обременения в виде дополнительных внутренних ветвлений.
Работоспособность этой системы, как известно, сильнейшим образом зависит от способностей компилятора к глубокой подстановке inline-функций и упразднению избыточных переменных, которые в полной мере раскрываются только при компиляции с ключом –O2. Без использования полной оптимизации метакод сильнейшим образом проигрывает традиционному коду из-за драматического роста числа вызовов функций (характерное соотношение по скорости между оптимизированной и неоптимизированной сборками колеблется в таких случаях между 1:3 и 1:5, что зачастую приводит к необходимости отлаживаться непосредственно по оптимизированной сборке – а это удовольствие, понятное дело, на любителя [9]).
При выполнении алгоритма использование каждой из версий функторов имеет определенную цену, которая легко поддается замеру. При этом использование самой быстрой версии по существу должно быть эквивалентно прямому обращению к массиву. Предположим, мы сравниваем традиционную (жестко типизированную) реализацию алгоритма с метапрограммной реализацией, получающей на вход соответствующий набор «быстрых» и «медленных» версий функторов. Исходя из вышесказанного, время работы не должно испытать существенных изменений. На практике же система демонстрирует странную эластичность: если время работы «традиционной» реализации действительно хорошо оценивается сложением «стоимостей» использованных функторов, то время выполнения метакода изменяется незначительно при представлении одного или даже двух функторов из трех их «быстрыми» версиями, и только при полной замене всех составляющих время скачком изменяется и действительно оказывается сопоставимым с временем «традиционной» реализации.
Не лишним будет отметить, что профилятор в такой ситуации оказывается более чем бесполезен: он вводит в заблуждение, стимулируя на 100% бесполезную оптимизацию случайных пользовательских функций из числа часто вызываемых, хотя задержки явно происходят не в них, а в «невидимом» соединительном коде. (Решение же, исходя из изложенного выше, должно состоять скорее в какой-то специальной, в угоду компилятору, декомпозиции исходной шаблонной функции, что, вне зависимости от успеха подобных манипуляций, является дикостью).
Бесхитростно написанный код, соответствующий этому случаю, выглядит примерно следующим образом:
void type_selector_fun(type_a a, type_b b, ...)
{
if (condition_1(a, b, ...))
{
auto arg = get_type_1_obj(a, b, ...);
run_calc(arg, a, b, ...);
}
else if (condition_1(a, b, ...))
{
auto arg = get_type_2_obj(a, b, ...);
run_calc(arg, a, b, ...);
}
else ...
}
Его можно переписать несколько компактней с использованием variant<...> примерно в следующем виде:
void type_selector_fun(type_a a, type_b b, ...)
{
variant<type_1, type_2, ...> arg;
if (condition_1(a, b, ...))
{
arg = get_type_1_obj(a, b, ...);
}
else if ...
...
apply_visitor([&](auto arg_){run_calc(arg_, a, b, ...); }, arg);
}
Недостатком этой формы записи является необходимость явного перечисления типов type_1, type_2, … в декларации variant. Типы эти могут быть громоздкими, не менее громоздкой может быть и запись с использованием declval/result_of_t.
При использовании any нет необходимости перечислять типы, но нет и возможности получить аналог apply_visitor.
Напрашивается использование некой шаблонной функции make_variant, позволяющей писать код примерно следующего вида:
auto arg = make_variant
(
bind(condition_1, a, b, ...), bind(get_type_1_obj, a, b, ...),
bind(condition_2, a, b, ...), bind(get_type_2_obj, a, b, ...),
...
);
, но лекарство выглядит не лучше болезни.
В общем, налицо типичная для метапрограммирования на C++ ситуация, когда для выражения очень простой идеи приходится использовать целый арсенал вспомогательных средств с не очень удовлетворительным в смысле читаемости и легкости записи результатом. По существу здесь хотелось бы иметь возможность писать примерно следующее:
// Автоматический выбор variant<...> для возвращаемого значения в зависимости
// от типов, использованных при возвратах: type_1, type_2 etc.
variant<auto...> get_type_obj(typa_a a, type_b b, ...)
{
if (condition_1(a, b, ...))
{
return get_type_1_obj(a, b, ...);
}
else if (condition_2(a, b, ...))
{
return get_type_2_obj(a, b, ...);
}
else ...
}
или даже:
select_value_type(arg)
{
if (condition_1(a, b, ...))
{
arg = get_type_1_obj(a, b, ...);
}
else ...
...
}
run_calc(arg, a, b, …);
Последний вариант, хотя он вовсе выбивается из стиля C++, выглядит наиболее практичным, так как переменных arg, для которых подбирается тип, может быть больше одной, и предвосхищать логику их построения нет оснований [10].
Соответствующий код может иметь примерно следующий вид:
struct CacheHolder
{
boost::variant<
container<T1>,
container<T2>,
// ...
container<TN>> ct;
template<typename T>
struct result_type_selector
{
typedef typename if_c<is_compatible<T, T1>::value, T1,
if_c<is_compatible<T, T2>::value, T2,
// ...
if_c<is_compatible<T, TN>::value, TN,
std::decay_t<T>>>>::type type;
};
template<typename T>
auto get() const -> const container<typename result_type_selector<T>::type>&
{
return boost::get<container<typename result_type_selector<T>::type>>(ct);
}
};
Здесь, как и выше, длинные конструкции выражают простую идею обращения к представляющей кэш переменной по определенному имени, вне зависимости от размерности кэшируемого значения (прозрачно проходящего через вызывающий код).
Для краткости код приведен для случая, когда активным может быть только один тип, однако на практике чаще встречается ситуация, когда несколько контейнеров может существовать одновременно (она может быть легко реализована в том же стиле с использованием tuple и optional).
Реализация функции get<...> предполагает, что вызывающий код имеет некоторое представление о том, к какой разновидности кэшируемого значения он хочет обратиться (например, к целочисленному или с плавающей запятой).
Не менее распространенной является ситуация, когда точное значение типа вызывающей стороне безразлично. В этом случае воспроизводится сценарий select_value_type/apply_visitor из предыдущего пункта (с поправкой на возможную множественность значений, предполагающую просмотр типов по убыванию приоритета).
Поясним механику работы PBGL на характерном примере алгоритма дельта-шагания. В этой параллельной версии алгоритма Дейкстры очередь с приоритетами заменяется на массив «ведерок». Элементы, попавшие в одно «ведерко», обрабатываются параллельно. В своей оригинальной форме дельта-шагание является типичным алгоритмом для систем с общей памятью.
В распределенной же версии происходит следующее: в PBGL при загрузке граф разбрасывается между процессами, причем каждому процессу соответствует непрерывный диапазон номеров вершин. Таким образом, по глобальному номеру вершины легко узнать, какому процессу она принадлежит. Соответственно, каждый поцесс на каждом ходу алгоритма хранит часть «ведерка», содержащую принадлежащие этому процессу вершины. Все процессы одновременно выбирают и обрабатывают вершины из своих частей «ведерок» по одной, при этом рассылают сообщения о необходимости обновления следующих «ведерок» процессам, владеющим соседними вершинами. Легко видеть, что при прочих равных увеличение числа процессов ведет к увеличению количества рассылаемых ими сообщений. В результате время выполнения алгоритма может не только не уменьшаться, но даже возрастать. В частности, запуск нескольких MPI-процессов для решения этой задачи на одной физической машине с некоторой вероятностью приведет только к увеличению общей загрузки процессора без какого-либо выигрыша по времени.
Нужно отметить, что дельта-шагание является самым быстрым распределенным алгоритмом поиска (из трех, поддерживаемых библиотекой).
Таким образом, если граф предварительно не подготовлен, разбивать его следует на блоки максимального размера, по одному блоку на одну физическую машину. Под предварительной же подготовкой здесь подразумевается такая перенумерация вершин графа, чтобы непрерывные диапазоны номеров, которые использует PBGL, по возможности соответствовали слабо связанным подграфам. Для этих целей применяются такие пакеты, как METIS, paraMETIS и Zoltan. Работа с динамическими графами в таком режиме затруднена. [12]
В целом по результатам описанных опытов у автора сложилось впечатление, что нормальная работа кластера PBGL возможна только при наличии специального коммуникационного оборудования, а в качестве узлов такого кластера имеет смысл использовать машины с минимальным числом ядер и максимальной производительностью на нить. Авторы Trinity [13] в своей статье утверждают, что их распределенное хранилище работает гораздо эффективней – прокомментировать это утверждение автор затрудняется, но, с учетом изложенных обстоятельств, находит это вполне возможным: архитектура PBGL несет отчетливую печать времени, когда многоядерные машины еще не получили массового распространения.
Также PBGL разделяет проблемы однопоточной версии: некоторую рассинхронизированность кода, документации и примеров, усугубляющиеся в связи с большей сложностью системы и меньшим числом пользователей, готовых поделиться полезным опытом.
Принимая во внимание достаточно длинный список специфических жалоб, не будет неуместным поинтересоваться: может ли автор в 2019 году порекомендовать BGL для новых проектов. Ответ будет таким: автор полагает, что у библиотек такого стиля и приложений на их основе обязано быть будущее [14]. Что до выбора опорной библиотеки для конкретного проекта, то над инструментовкой стоит серьезно поразмыслить, не упуская из вида перечисленных выше проблем. Ответ, очевидно, зависит от многих обстоятельств, включающих, но не ограничивающихся перечисленным в следующих пунктах:
Вероятно, при прочих равных использование BGL может быть оправданным либо в случае совсем малого разового применения (вымучать или скопировать работающий фрагмент кода и забыть), либо для большой системы, для которой повышенная гибкость со временем окупит тяжелое вхождение и прочие издержки. В других случаях имеет смысл тщательно изучить иные варианты.
Что до возможных альтернатив, то их список включает в себя как минимум следующие пункты [15]:
Название | LEMON |
Тип библиотеки | C++, шаблонная заголовочная |
URL | lemon.cs.elte.hu [16] |
Распределенная | нет |
Многопоточная | нет |
ОС | any |
Последняя версия | 2014 Распространяется архивом |
Число упоминаний на stackoverflow [17] | ~100 (36 в разделе [lemon-graph-library]) |
Комментарий | По некоторым сообщениям, в однопоточном режиме существенно превосходит BGL по скорости [18]. Отношение авторов к многопоточности явствует из следующего диалога [19]. С учетом изложенного выше в разделе о PBGL данная позиция представляется сомнительной. |
Название | SNAP |
Тип библиотеки | C++ |
URL | github.com/snap-stanford/snap.git [20] |
Распределенная | нет |
Многопоточная | да (часть методов) |
ОС | Linux, Mac, Cygwin |
Последняя версия | 2018 Репозиторий активно обновляется |
Число упоминаний на stackoverflow | < 50 |
Комментарий | Одна из крупнейших (более 10 Мб кода) библиотек сетевого анализа (Network Ananlysis), активно развивающаяся уже многие годы. Странным образом сравнительно обойдена массовым вниманием. См. описание идеологии системы [21]. Отношение к реализации параллельных методов, высказанное на стр. 12, близко автору этой статьи. В условиях эксплуатации типичного современного машинного парка оно является наиболее естественным. Смена парадигмы пришлась на условный 2011 год, к которому относится приведенная выше декларация LEMON. |
Название | MTGL |
Тип библиотеки | C++, шаблонная заголовочная |
URL | software.sandia.gov/svn/public/mtgl/trunk [22] |
Распределенная | ? |
Многопоточная | да |
ОС | any [23] |
Последняя версия | ? |
Число упоминаний на stackoverflow | 3 |
Комментарий | Загадочный член собрания. Библиотека активно развивалась в промежутке между 2005 и 2012 годами. Исходники залиты в 2017 году. Статус неясен, упоминание о проекте с сайта Sandia удалено. Идеологически инспирировано той же BGL, но код полностью независим. Общий объем исходников (включая многочисленные тесты и примеры) достигает 17 Мб. Код выглядит хорошо проработанным. См. описание [24]. |
Название | igraph |
Тип библиотеки | C |
URL | github.com/igraph/igraph.git [25] |
Распределенная | нет |
Многопоточная | нет |
ОС | any? |
Последняя версия | 2014 Репозиторий активно обновлятся |
Число упоминаний на stackoverflow | Порядка 100 в разделах [igraph] [c++] и [igraph] [c], а всего более 500 (для всех языков) |
Комментарий | Еще одна библиотека сетевого анализа, по всей видимости, весьма популярная (в основном у питонистов и т.д.). Описание здесь [26]. |
Название | graph-tool |
Тип библиотеки | C++ python lib |
URL | git.skewed.de/count0/graph-tool.git [27] |
Распределенная | нет |
Многопоточная | да |
ОС | Судя по использованию autoconf — *nix only, но вероятна несложная адаптация под другие системы |
Последняя версия | 2019 |
Число упоминаний на stackoverflow | < 20 |
Комментарий | Еще одна активно развивающаяся библиотека сетевого анализа с длинной историей коммитов, непосредственно использующая BGL (в локальной исправленной версии). См. Таблицу со сравнением производительности. [28] |
Название | LEDA |
Тип библиотеки | C++ |
URL | www.algorithmic-solutions.com/index.php/products/leda-for-c [29] |
Распределенная | нет |
Многопоточная | ? |
ОС | any |
Последняя версия | ? |
Число упоминаний на stackoverflow | ~10 |
Комментарий | Коммерческая лицензия. Большая (и, можно сказать, старинная) библиотека для научных и технологических вычислений, включающая графовый раздел. По всей видимости, опирается на собственную инфраструктуру, а не на stl/boost, и в этом смысле является архаичной. |
Определенный общий интерес представляет также вопрос о классификации различных программных продуктов, ориентированных на работу с графами. Их разнообразие, не говоря о числе, весьма велики. Нимало не претендуя на завершенность (и даже формальную корректность) классификации, можно попытаться, тем не менее, выделить следующие важные направления в разработке графовых приложений:
Системы такого рода ориентированы на выполнение транзактных операций над большими (распределенными дисковыми) графами. Хотя API такой системы может быть в высшей степени развитым, скорость выполнения собственно графовых алгоритмов, насколько можно судить, не является первым приоритетом. Система может даже не пытаться загружать граф целиком в память. Для модификации и обхода графа поддерживаются декларативные языки (SPARQL, Cypher, Gremlin). Большое значение придается обеспечению преемственности с традиционными SQL-системами.
Хотя API таких систем может быть достаточно развитым (помимо прочего, GraphX поддерживает SPARQL и Cypher), основной упор при работе с ними приходится на решение инфраструктурных проблем. Для GraphX характерны иммутабельность данных и уклон в конвейеризацию всех операций. MS Trinity на данный момент не включает высокоуровневых методов и предоставляет только набор примитивов для работы с узлами и ребрами. Системы, работающие поверх Hadoop, в принципе малоприспособлены для решения произвольных графовых задач.
На их основе могут создаваться прикладные системы, адаптирующие графовые алгоритмы к конкретным предметным областям.
К этой же категории можно условно отнести множество независимых графовых приложений, детальный анализ которого потребовал бы слишком большого времени. Последнее содержит приложения всех мыслимых разновидностей: академические и коммерческие, однопользовательские и многопользовательские, недавно появившиеся и существующие уже более десятилетия и т. д.
Неясная, но значительная часть графовых приложений ориентированы на задачи Network Analysis и, уже, Social Network Analysis (Community Detection). Как ни странно, существенно меньше распространены системы Link Analysis (использующиеся, как правило, различными «борцами с преступностью»), имеющие определенное сходство с разрабатываемой нами системой. Во всех случаях без специальной проверки трудным оказывается выяснить характер используемых различными системами моделей данных и сопутствующие ограничения по производительности, поддерживаемым объемам, наборам операций и т.п.
Автор: basil_pivovarov
Источник [33]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-3/333726
Ссылки в тексте:
[1] Шаблонная библиотека BGL: #P1
[2] 1: https://habr.com/en/post/211558/
[3] 2: https://habr.com/en/post/212089/
[4] 3: https://habr.com/en/post/212871/
[5] типичное обсуждение: https://stackoverflow.com/questions/7584320/sorting-the-edgelist-in-boostgraph
[6] скорость разработки устремится к нулю: #P2
[7] возникающая при работе с шаблонным кодом BGL: #P3
[8] что при комбинаторном взрыве вариантов может стать проблемой: #P4
[9] а это удовольствие, понятное дело, на любителя: #P5
[10] предвосхищать логику их построения нет оснований: #P6
[11] поясняется здесь: #P6X1
[12] Работа с динамическими графами в таком режиме затруднена.: #P7
[13] Trinity: https://github.com/Microsoft/GraphEngine.git
[14] обязано быть будущее: #P8
[15] следующие пункты: #P9
[16] lemon.cs.elte.hu: https://lemon.cs.elte.hu
[17] Число упоминаний на stackoverflow: #P10
[18] в однопоточном режиме существенно превосходит BGL по скорости: https://blog.sommer-forst.de/2016/10/28/solving-the-shortest-path-problem-5-benchmarks/
[19] явствует из следующего диалога: http://lemon.cs.elte.hu/pipermail/lemon-user/2010-September/000266.html
[20] github.com/snap-stanford/snap.git: https://github.com/snap-stanford/snap.git
[21] описание идеологии системы: https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5361061/pdf/nihms823738.pdf
[22] software.sandia.gov/svn/public/mtgl/trunk: https://software.sandia.gov/svn/public/mtgl/trunk
[23] any: http://www.cs.sandia.gov/~jberry/
[24] описание: https://cfwebprod.sandia.gov/cfdocs/CompResearch/docs/MTGL.pdf
[25] github.com/igraph/igraph.git: https://github.com/igraph/igraph.git
[26] здесь: https://igraph.org/c/doc/igraph-docs.pdf
[27] git.skewed.de/count0/graph-tool.git: https://git.skewed.de/count0/graph-tool.git
[28] Таблицу со сравнением производительности.: https://graph-tool.skewed.de/performance
[29] www.algorithmic-solutions.com/index.php/products/leda-for-c: https://www.algorithmic-solutions.com/index.php/products/leda-for-c
[30] Google Pregel: https://kowshik.github.io/JPregel/pregel_paper.pdf
[31] подталкивает к охоте на (возможно отсутствующих) черных кошек в темных комнатах: http://boost.2283326.n4.nabble.com/Chrono-Is-there-any-functions-that-convert-Chrono-s-time-point-and-duration-to-their-Ptime-equivalen-td4311207.html
[32] звездой сияет подбор аргумента делением пополам: https://rextester.com/NQZ38860
[33] Источник: https://habr.com/ru/post/471652/?utm_source=habrahabr&utm_medium=rss&utm_campaign=471652
Нажмите здесь для печати.