В каждой заметке произведено сравнение скорости работы различных алгоритмов расстановки графов, а также примерные значения требуемой памяти.
Все сравнения я буду производить на невзвешенном ориентированном графе, включающем около 5 тысяч вершин и 18 тысяч рёбер.
Тестовая машинка - 4GB RAM + i3 540 3.07 GHz (x4).
Мерить будем пиковое значение памяти и время работы, поскольку для меня именно эти метрики являются критичными.
Итак, NetworkX - библиотека для манипулирования графами под python.
Сами разработчики пишут, что визуализация не является приоритетной задачей данной библиотеки, однако делает она это неплохо, а в купе с удобными инструментами для манипулирования её применение видится весьма перспективным.
В последнее время её сильно пиарят и документация у неё очень хорошая, поэтому можно предположить стремительный рост популярности.
Для замера пикового потребления памяти была написана небольшая функция:
По хорошему, её надо заменить модулем resource, но (чёрт его знает почему) пиковую память он отдаёт ровно вдвое большую, хотя анализирует тот же /proc/self/status. В общем мистика, надо разобраться =)Copy Source | Copy HTML
- def memory_usage():
- """Memory usage of the current process in kilobytes."""
- status = None
- result = 0;
- try:
- status = open('/proc/self/status');
- for line in status:
- parts = line.split();
- if parts[0][:-1] == 'VmPeak':
- result = int(parts[1]);
- finally:
- if status is not None:
- status.close();
- return result;
Код для расстановки и рендеринга графа будет выглядеть примерно так.
В данном примере используется случайный алгоритм расстановки графа (равномерно распределяет узлы по квадрату).Copy Source | Copy HTML
- import matplotlib.pyplot as plt
- import networkx as nx
- plt.figure( figsize=(50,50) );
- t = time.time();
- nx.draw_random(
- G,
- node_size=1,
- width=0.1,
- font_size=6,
- alpha=0.5,);
- logging.debug("compute %.4f sec %.2f Mb" % ((time.time()-t), memory_usage()/1024) );
- plt.savefig('networkx');
Визуализации подлежит заданный ранее граф G. Процесс создания графов подробно описан в доке и учитываться при сравнении не будет (поверьте, этих милисекунд вы не заметите рядом с временем отрисовки =) ).
Вообще (если кто не знал), основное время отрисовки - это время рассчёта положения точек. Для этого придумано множество алгоритмов и большинство библиотек позволяют выбрать из нескольких доступных.
Основные же пики памяти нередко приходятся на рендеринг итоговой картинки, поэтому данный процесс не включен в таймер.
Результаты нескольких замеров работы данного кода дали следующее:
RAM 220.00 Mb
Time 2.4674 сек.
Для полноты картины попробуем другие алгоритмы.
Используемые функции:
- draw_graphviz() - обёртка над graphviz (). В данном случае использует neato и минимизирует целевую функцию аля spring.
- draw_spectral() - опирается на граф Лапласа.
- draw_spring() - алгоритм Fruchterman-Reingold.
- draw_shell() - стремится к концентрическим кругам.
- draw_circular() -упрощённый аналог предыдущего алгоритма.
Стоит заметить, что все рассмотренные функции работают на одном ядре процессора, что грустно, поскольку задачи на графы чудесно параллелятся.
Также напомню, что в памяти хранится сам граф, поэтому данная метрика годится только для оценки соотношения памяти, потребляемой алгоритмами.
Итоговые результаты
Алгоритм | Время работы, сек | Пик потребления памяти, Мб |
---|---|---|
random | 2.4674 | 220 |
graphviz | 140.5406 | 234 |
spectral | 117.5004 | 226 |
spring | 307.1961 | 225 |
shell | 2.5685 | 220 |
circular | 2.6359 | 220 |
Визуальные результаты
Полноразмерные результаты доступны по клику (осторожно, трафик).
Random |
Circular and shell |
Graphviz |
Spectral |
Spring |
Комментариев нет:
Отправить комментарий