Почему эта реализация двоичной кучи медленнее, чем у Python stdlib?

Я реализовал свой собственный модуль кучи, чтобы помочь мне понять структуру данных кучи. Я понимаю, как они работают и ими управляют, но моя реализация значительно медленнее, чем стандартный модуль python heapq при предварительной сортировке кучи (для списка размером 100000 heapq занимает 0,6 секунды, а мой код занимает 2 секунды (изначально был 2,6 секунды, сократите его до 2 с, беря операторы len () из percDown и передавая длину, чтобы не приходилось вычислять len каждый раз, когда метод вызывает сам себя). Вот моя реализация:

def percDown(lst, start, end, node):
    #Moves given node down the heap, starting at index start, until the heap property is
    #satisfied (all children must be larger than their parent)
    iChild = 2 * start + 1
    i = start
    # if the node has reached the end of the heap (i.e. no children left),
    # return its index (we are done)
    if iChild > end - 1:
        return start
    #if the second child exists and is smaller than the first child, use that child index
    #for comparing later
    if iChild + 1 < end and lst[iChild + 1] < lst[iChild]:
        iChild += 1
    #if the smallest child is less than the node, it is the new parent
    if lst[iChild] < node:
        #move the child to the parent position
        lst[start] = lst[iChild]
        #continue recursively going through the child nodes of the
        # new parent node to find where node is meant to go
        i = percDown(lst, iChild, end, node)
    return i

popMin: извлекает минимальное значение (lst [0]) и меняет порядок кучи

def popMin(lst):
    length = len(lst)
    if (length > 1):
        min = lst[0]
        ele = lst.pop()
        i = percDown(lst, 0, length - 1, ele)
        lst[i] = ele
        return min
    else:
        return lst.pop()

heapify: превращает список в кучу на месте

def heapify(lst):
    iLastParent = math.floor((len(lst) - 1) / 2)
    length = len(lst)
    while iLastParent >= 0:
        ele = lst[iLastParent]
        i = percDown(lst, iLastParent, length, lst[iLastParent])
        lst[i] = ele
        iLastParent -= 1

sort: сортирует данный список в кучу, используя методы выше (не на месте)

def sort(lst):
    result = []
    heap.heapify(lst)
    length = len(lst)
    for z in range(0, length):
        result.append(heap.popMin(lst))
    return result

Я по ошибке добавил сложность к созданию алгоритма / кучи, или это просто модуль python heapq, который сильно оптимизирован? У меня такое чувство, что это первое, так как 0,6 с против 2 - огромная разница.


person chillNZ    schedule 13.10.2014    source источник
comment
Голосование за закрытие вопроса о том, почему этот код не работает.   -  person Ciro Santilli 新疆再教育营六四事件ۍ    schedule 20.06.2015
comment
См. Также: stackoverflow.com/questions/2501457/   -  person Ciro Santilli 新疆再教育营六四事件ۍ    schedule 20.06.2015


Ответы (2)


0,6 с против 2,6 с - разница чуть меньше, чем в 4 раза. Это "слишком велико"?

Этой информации недостаточно для ответа. Разница в 4 раза, безусловно, могла быть вызвана неправильным алгоритмом ... но на самом деле нет никакого способа сказать это без тестирования на разных размерах.

Если вы получите, скажем, разницу только в 1,2 раза при 1000, разницу в 4 раза при 100000 и разницу в 12 раз при 1000000, то ваша алгоритмическая сложность, скорее всего, будет хуже, что означает, что вы, вероятно, получили что-то неправильно, и это то, что вам нужно исправить.

С другой стороны, если разница примерно в 4 раза для всех трех размеров, то в ваших накладных расходах просто больший постоянный множитель. Скорее всего, потому что у вас есть внутренний цикл, работающий в Python, а версия stdlib (CPython) использует модуль ускорителя _heapq, который выполняет тот же цикл в C, как описано в Ответ Мартин Питерс. Итак, вы не ошиблись. Вы, вероятно, можете немного оптимизировать его, но в конечном итоге вам придется либо переписать ядро ​​вашего кода на C, либо запустить его в интерпретаторе, оптимизированном для JIT, чтобы приблизиться к такому же качеству, как stdlib. И действительно, если вы пишете это просто для понимания алгоритма, вам не нужно этого делать.

В качестве примечания, вы можете попробовать запустить сравнение в PyPy. Большая часть его stdlib написана на чистом Python без специальной оптимизации, но оптимизированный JIT-компилятор делает его почти таким же быстрым, как собственный код C в CPython. И тот же самый JIT будет применен к вашему коду, а это означает, что ваш неоптимизированный код часто почти такой же, как собственный код C в CPython. Конечно, на это нет никакой гарантии, и это не меняет того факта, что вам всегда нужно тестировать с разными размерами, если вы пытаетесь проверить алгоритмическую сложность.

person abarnert    schedule 13.10.2014
comment
Хорошая идея протестировать на разных размерах, чтобы сравнить сложность, я должен был подумать об этом. Я просто попробовал его на разных размерах, и мой код постоянно примерно в 4 раза медленнее, чем модуль heapq. Спасибо! Меня не беспокоило то, что мой код не был оптимизирован до крайности, больше беспокоило, что я упустил логику и сложность алгоритма, поэтому меня полностью устраивает, что мой код в 4 раза медленнее - я бы никогда не использовал свой код повторно. в любом случае heapq! - person chillNZ; 14.10.2014
comment
@chillNZ: Что ж, однажды вам понадобится maxheap вместо minheap или 1-heap вместо 0-heap, и тогда будет интересно сравнить maxheap на чистом Python с minheap C, завернутым в DSU, который инвертирует все сравнения, чтобы увидеть, какой из них медленнее. :) (У меня была именно эта проблема несколько лет назад, и они оба отстой ... но это была моя первая настоящая программа, в которой PyPy надрал задницу CPython по скорости, так что это было моим решением.) - person abarnert; 14.10.2014
comment
Хм, да, я на самом деле использую свой код поверх модуля python, чтобы сделать теперь на месте maxheap heapsort, так как мне нужно добавить функциональность, которой нет у heapq (перемещение элемента max в конец списка вместо того, чтобы просто выскакивать это, а также создание максимальной кучи в первую очередь). Я не думаю, что буду на том уровне, чтобы беспокоиться о методах на месте и других методах повышения эффективности еще какое-то время, и как только я там окажусь, я, надеюсь, буду намного увереннее использовать свой собственный код вместо кода stdlib. ! (Я бы не стал использовать код, с которым сейчас играю, что имел в виду в своем первом комментарии.) - person chillNZ; 14.10.2014

Модуль Python heapq использует расширение C. Вы не можете превзойти код C.

Из heapq исходного кода модуля:

# If available, use C implementation
try:
    from _heapq import *
except ImportError:
    pass

Также см. _heapqmodule.c исходный код C.

person Martijn Pieters    schedule 13.10.2014
comment
Ах, это имеет смысл, я забыл, что некоторые модули Python используют C для ускорения обработки по сравнению с использованием самого Python. Это объясняет, в чем заключается основная разница, а все остальное - мой код далек от совершенства. Спасибо! - person chillNZ; 14.10.2014