LINUX.ORG.RU

Подскажите алгоритм/структуру данных

 , , ,


0

3

Имеется огромный проект на С++ (около 9681 файлов исходников). И есть утилитарный скрипт, в котором основная задача сказать, встречаются ли два отдельно взятых файла в какой-либо единице трансляции (название единицы трансляции не важно). Иными словами, если имееются foo.h и bar.h и нужно сказать включены ли одновременно оба эти файла в какою-либо единицу трансляции, например baz.cpp.

Подскажите пожалуйста алгоритм/структуру данных чтоб можно было быстро дать ответ на вопрос из предыдущего абзаца. Причем ответ надо давать довольно часто (порядка 500 раз за прогон скрипта) и для разных пар файлов.

Disjoint Set (aka Union Find) не подходит, т.к. один заголовочный файл может включатся в разные единицы трансляции. Причем сами единицы трансляции имеют разный набор включенных файлов. Например, если имеется:

bar.h  foo.h  baz.h
  |   /     \ |
  bar.cpp    baz.cpp
то, bar.h и baz.cpp не имеют соединения. И, что важно, bar.h и baz.h не встречаются вместе ни в одной единице трансляции.

★★★★★

Последнее исправление: KennyMinigun (всего исправлений: 1)
Ответ на: комментарий от Crocodoom

Думаю, никакой дополнительной к двудольному графу структуры тут найти не удастся. Разве что какой-нибудь «разреженный» двудольный граф, если такие существуют — как противоположность полного двудольного графа.

Crocodoom ★★★★★
()

В перле не силён, псевдокод:

// для каждого .cpp множество всех .h которые включаются в него
map<string, set<string>> cpp2h;

// для каждого .h множество всех .cpp в которые он включается
map<string, set<string>> h2cpp;

string hasSameTranslationUnit(string h1, string h2)
{
    for (cpp in h2cpp[h1])
    {
        if (cpp2h[cpp].contains(h2))
            return true;
    }

    return false;
}

При желании можно хранить не все вхождения, а только прямые, но при просмотре тогда придётся сделать рекурсивный проход.

anonymous
()
Ответ на: комментарий от anonymous

Можно ещё оптимизировать сравнив количество элементов в h2cpp[h1] и h2cpp[h2] и делать перебор по меньшему множеству.

anonymous
()
Ответ на: комментарий от anonymous

Раз имя единицы трансляции не важно и надо оптимизировать проверку, то мне кажется, что лучше так:

map<string, set<string>> hNeighbours;

void
init(std::vector<SourceFile> files)
{
    for (SourceFile source : files) {
        for (string hdrA : file.headers()) {
            for (string hdrB : file.headers()) {
                if (hdrA != hdrB) {
                    hNeighbours[hdrA].insert(hdrB);
                }
            }
        }
    }
}

bool
contains(string hdrA, string hdrB)
{
    return hNeighbours[hdrA].contains(hdrB)
        || hNeighbours[hdrB].contains(hdrA);
}
xaizek ★★★★★
()

около 9681 файлов исходников

как будто это много

1) предпроход: для каждого файла запоминаем модули, в которые он инклудится. отсортированный массив подойдет

files = {name: [] for name in dir()}
for module in dir() {
    for includes in module {
        files[includes] += module
    }
}

2) по запросу: для данных двух файлов просто сверяешь их массивы на предмет общего элемента

MyTrooName ★★★★★
()
Последнее исправление: MyTrooName (всего исправлений: 1)
Ответ на: комментарий от MyTrooName

2) по запросу: для данных двух файлов просто сверяешь их массивы на предмет общего элемента

Это надо делать рекурсивно. Каждый раз. Потому не эффективно.

К примеру ситуация:

bar.h # цикл 
  |
foo.h
  \
  bar.h  baz.h
    \     /
    baz.cpp

f(foo.h, baz.h) == true

Я сейчас пытаюсь выровнять граф до леса деревьев по 1 уровню:

bar.h foo.h baz.h    bar.h  foo.h
  \    |   /           |   /
   baz.cpp           foo.h

С такой структурой проверка f(x,y) будет О(1) (не считая поиска нужного дерева)

KennyMinigun ★★★★★
() автор топика
Последнее исправление: KennyMinigun (всего исправлений: 1)
Ответ на: комментарий от xaizek

К сожалению, такой алгоритм не охватывает проблему цепочки #include (а еще есть циклы). Надо еще добавлять рекурсию или трансформировать граф.

KennyMinigun ★★★★★
() автор топика

Мне кажется надо использовать матрицу смежности. Каждый файл (заголовочный и исходный) - это вершина неориентированного графа. Помещаем 1 в матрицу смежности если h включен в cpp. Матрица смежности неор. графа - симметрична, поэтому достаточно хранить половину данных. По скольку у вас очень разряженная матрица получится (количество включений сравнимо с количеством файлов-вершин), то можно матрица смежности хранить списком строк матрицы с массивов смежных вершин. Если файл включается большего одного раза, то в матрице смежности будет число связей больше 1. Чтобы чуть ускорить поиск в имени файла в матрице можно построить доп. индекс (бинарное дерево) для быстрого перехода на нужную строку матрицу смежности.

gvtlor
()
Ответ на: комментарий от MyTrooName

просто кешируй результат рекурсии

Так практически и делаю.

Только если кешировать по ключу {file1,file2} — то не сильно помогает (cache miss около 90%). В процессе рекурсии я «сворачиваю» всю часть графа, которую видел при проходе (как в комментариях раньше написал).

KennyMinigun ★★★★★
() автор топика
Ответ на: комментарий от KennyMinigun

И в чём проблема? Если найти все хидеры для каждого сорс файла, то потом легко сделать то что было предложено.

anonymous
()
Ответ на: комментарий от KennyMinigun

По первому сообщению думал, что оно уже в подходящем виде. Список заголовков для cpp-файла это же множество узлов, достижимых из его узла в оригинальном или инвертированном ориентированном графе. И, наверное, для каждого промежуточного узла можно этот список кешировать и таким образом сократить время построения списков для исходников.

xaizek ★★★★★
()
Последнее исправление: xaizek (всего исправлений: 1)
Ответ на: комментарий от anonymous

contains

к ситингу?

Очевидно, что это было продолжение псевдокода.

xaizek ★★★★★
()

А в чем проблема собрать мапу где ключем будет имя инклудника, а значением список CPP файлов при компиляции которых используется этот инклудник ?

std::map< std::string, std::list<std::string> >
10K файлов это в приципе не очень много + можно сделать компресию через словарь. Тоесть в начале составляем словарь файлов:
std::map<std::string, unsigned>
Который будет мапить имя файла в уникальное 32 битное число, а дальше наша мапа будет иметь вид:
std::map< unsigned, std::vector<unsigned> >

zaz ★★★★
()

Еще момент, если этот проект собирается через чтото основаное на «make» (autotools, cmake) - то можно использовать его дерево зависимостей (там уже все просщитано какие CPP нужно пересобрать при изменении данного хедера).

zaz ★★★★
()
Ответ на: комментарий от rumgot

Не спора ради, а просто любопытно: а зачем это нужно?

Есть специфичный для проекта механизм из defensive programming. Точнее переключалки старый/новый код. Когда удаляется одна из таких переключалок, иногда остается мусор: например неиспользуемые обьявления (из-за обычной людской невнимательности). Скрипт должен помогать находить такой мусор.

KennyMinigun ★★★★★
() автор топика
Ответ на: комментарий от zaz

А в чем проблема собрать мапу где ключем будет имя инклудника,

Если простую мапу: без разрешения цепочки инклудов и циклов, то довольно просто. Проблема появляется когда таки надо ходить по цепочкам инклюдов.

KennyMinigun ★★★★★
() автор топика
Ответ на: комментарий от zaz

чтото основаное на «make» (autotools, cmake) - то можно использовать его дерево зависимостей

Интересная идея. Надо будет глянуть.

KennyMinigun ★★★★★
() автор топика

Если у тебя там только инклюдгарды (без прагма уанс), то: а) распарси гарды всех заголовочников, б) пройдись препроцессором по спп, в) грепни результат препроцессора по нужным гардам. Быстро, грязно, топорно, рабоче.

Stil ★★★★★
()
Ответ на: комментарий от Stil

Там какой-то граф уже есть. А если заново парсить, то проще пропустить cpp через препроцессор и глянуть на #line директивы, там будут пути к заголовкам и не надо на гарды смотреть.

xaizek ★★★★★
()
Ответ на: комментарий от Stil

Если у тебя там только инклюдгарды

Мешанина там

KennyMinigun ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.