Как оптимизировать операции с большими (75 000 элементов) наборами логических значений в Python? - PullRequest
9 голосов
/ 19 октября 2010

Этот скрипт называется svnmerge.py , который я пытаюсь немного подправить и оптимизировать.Я полностью новичок в Python, так что это нелегко.

Текущая проблема, кажется, связана с классом RevisionSet в скрипте.По сути, он создает большую хеш-таблицу (?) Логических значений с целочисленными ключами.В худшем случае - по одной на каждую ревизию в нашем репозитории SVN, которая сейчас составляет около 75 000.

После этого он выполняет операции над множествами таких огромных массивов - сложение, вычитание, пересечение и так далее.Реализация - это самая простая реализация O (n), которая, естественно, довольно медленно работает с такими большими наборами.Вся структура данных может быть оптимизирована, поскольку существуют длинные промежутки непрерывных значений.Например, все ключи от 1 до 74 000 могут содержать true.Кроме того, сценарий написан для Python 2.2, который является довольно старой версией, и мы в любом случае используем 2.6, так что здесь тоже можно кое-что получить.

Я мог бы попытаться собрать это вместе, но этобудет сложно и займет много времени - не говоря уже о том, что это может быть уже где-то реализовано.Хотя я хотел бы получить опыт обучения, результат сейчас важнее.Что бы вы посоветовали мне сделать?

Ответы [ 5 ]

7 голосов
/ 19 октября 2010

Вы можете попробовать сделать это с NumPy вместо простого Python.Я обнаружил, что для таких операций очень быстро.

Например:

# Create 1000000 numbers between 0 and 1000, takes 21ms
x = numpy.random.randint(0, 1000, 1000000)

# Get all items that are larger than 500, takes 2.58ms
y = x > 500

# Add 10 to those items, takes 26.1ms
x[y] += 10

Так как здесь гораздо больше строк, я думаю, что 75000 не должнобыть проблемой либо:)

1 голос
/ 19 октября 2010

Вот быстрая замена RevisionSet, которая превращает его в набор. Это должно быть намного быстрее. Я не полностью протестировал это, но это работало со всеми тестами, которые я сделал. Несомненно, есть и другие способы ускорить процесс, но я думаю, что это действительно поможет, потому что он на самом деле использует быструю реализацию множеств, а не выполнение циклов в Python, которые исходный код выполнял в таких функциях, как __sub__ и __and__. Единственная проблема в том, что итератор не отсортирован. Возможно, вам придется немного изменить код, чтобы учесть это. Я уверен, что есть и другие способы улучшить это, но, надеюсь, это даст вам хорошее начало.

class RevisionSet(set):
    """
    A set of revisions, held in dictionary form for easy manipulation. If we
    were to rewrite this script for Python 2.3+, we would subclass this from
    set (or UserSet).  As this class does not include branch
    information, it's assumed that one instance will be used per
    branch.
    """
    def __init__(self, parm):
        """Constructs a RevisionSet from a string in property form, or from
        a dictionary whose keys are the revisions. Raises ValueError if the
        input string is invalid."""


        revision_range_split_re = re.compile('[-:]')

        if isinstance(parm, set):
            print "1"
            self.update(parm.copy())
        elif isinstance(parm, list):
            self.update(set(parm))
        else:
            parm = parm.strip()
            if parm:
                for R in parm.split(","):
                    rev_or_revs = re.split(revision_range_split_re, R)
                    if len(rev_or_revs) == 1:
                        self.add(int(rev_or_revs[0]))
                    elif len(rev_or_revs) == 2:
                        self.update(set(range(int(rev_or_revs[0]),
                                         int(rev_or_revs[1])+1)))
                    else:
                        raise ValueError, 'Ill formatted revision range: ' + R

    def sorted(self):
        return sorted(self)

    def normalized(self):
        """Returns a normalized version of the revision set, which is an
        ordered list of couples (start,end), with the minimum number of
        intervals."""
        revnums = sorted(self)
        revnums.reverse()
        ret = []
        while revnums:
            s = e = revnums.pop()
            while revnums and revnums[-1] in (e, e+1):
                e = revnums.pop()
            ret.append((s, e))
        return ret

    def __str__(self):
        """Convert the revision set to a string, using its normalized form."""
        L = []
        for s,e in self.normalized():
            if s == e:
                L.append(str(s))
            else:
                L.append(str(s) + "-" + str(e))
        return ",".join(L)

Дополнительно: Между прочим, я сравнил выполнение объединений, пересечений и вычитаний исходного RevisionSet и моего RevisionSet выше, и приведенный выше код в 3–7 раз быстрее для этих операций при работе с двумя RevisionSet, которые имеют 75000 элементов. Я знаю, что другие люди говорят, что NumPy - это путь, но если вы не очень разбираетесь в Python, как указывает ваш комментарий, то вы, возможно, не захотите идти по этому пути, потому что это повлечет за собой гораздо больше изменений. Я бы порекомендовал попробовать мой код, посмотреть, работает ли он, и если да, то посмотреть, достаточно ли он быстр для вас. Если это не так, то я бы попробовал профилирование, чтобы увидеть, что нужно улучшить. Только тогда я мог бы рассмотреть возможность использования numpy (это отличный пакет, который я использую довольно часто).

0 голосов
/ 19 октября 2010

Просто мысль. Раньше я делал подобные вещи, используя кодирование в двоичной обработке изображений. То есть хранить каждый набор как последовательность чисел: количество выключенных битов, количество включенных битов, количество выключенных битов и т. Д.

Затем вы можете выполнять всевозможные логические операции над ними в качестве декораций для простого алгоритма слияния.

0 голосов
/ 19 октября 2010

Вы должны переписать RevisionSet, чтобы получить набор ревизий.Я думаю, что внутреннее представление для ревизии должно быть целым числом, и диапазоны ревизий должны создаваться по мере необходимости.

Нет веских причин для использования кода, поддерживающего python 2.3 и более ранние версии.

0 голосов
/ 19 октября 2010

Например, все ключи от 1 до 74 000 содержат true

Почему бы не работать на подмножестве? Всего 74001 до конца.

Сокращение 74/75-й ваших данных намного проще, чем попытка написать алгоритм, более умный, чем O (n).

...