Python: оптимизация или, по крайней мере, получение свежих идей для генератора деревьев - PullRequest
1 голос
/ 24 января 2010

Я написал программу, которая генерирует случайные выражения, а затем использует генетические методы для выбора для пригодности.

Следующая часть программы генерирует случайное выражение и сохраняет его в древовидной структуре.

Поскольку это может вызываться миллиарды раз во время прогона, я подумал, что его следует оптимизировать по времени.

Я новичок в программировании, и я работаю (играю) сам, так же, как я ищу в инерне

идей, я хотел бы внести свой вклад, поскольку я чувствую, что делаю это в изоляции.

Узкими местами являются узлы. init (), (22% от общего времени) и random.choice (), (14% от общего времени)

import random

def printTreeIndented(data, level=0):
    '''utility to view the tree
    '''
    if data == None:
         return
    printTreeIndented(data.right, level+1)
    print '  '*level + '  '+ str(data.cargo)#+ '  '+ str(data.seq)+ '  '+ str(data.branch)
    printTreeIndented(data.left, level+1)

#These are the global constants used in the Tree.build_nodes() method.
Depth = 5
Ratio = .6 #probability of terminating the current branch.
Atoms = ['1.0','2.0','3.0','4.0','5.0','6.0','7.0','8.0','9.0','x','x','x','x']
#dict of operators. the structure is: operator: number of arguements
Operators = {'+': 2, '-': 2, '*': 2, '/': 2, '**': 2}


class KeySeq:
    '''Iterator to produce sequential 
    integers for keys in Tree.thedict
    '''
    def __init__(self, data = 0):
        self.data = data
    def __iter__(self):
        return self
    def next(self):
        self.data = self.data + 1
        return self.data
KS = KeySeq()

class Node(object):
    '''
    '''
    def __init__(self, cargo, left=None, right=None):
        object.__init__(self)
        self.isRoot = False
        self.cargo = cargo
        self.left  = left
        self.right = right
        self.parent = None
        self.branch = None
        self.seq = 0


class Tree(object):
    def __init__(self):
        self.thedict = {}     #provides access to the nodes for further mutation and
        # crossbreeding.
        #When the Tree is instantiated, it comes filled with data.
        self.data = self.build_nodes()

# Uncomment the following lines to see the data and a crude graphic of the tree.
#        print 'data: '
#        for v in  self.thedict.itervalues():
#            print v.cargo,
#        print
#        print
#        printTreeIndented(self.data)

    def build_nodes (self,  depth = Depth, entry = 1,  pparent = None,
     bbranch = None):
        '''
        '''
        r = float()
        r = random.random()

        #If r > Ratio, it forces a terminal node regardless of
        #the value of depth.
        #If entry = 1, then it's the root node and we don't want
        # a tree with just a value in the root node.

        if (depth <= 0) or ((r > Ratio) and (not (entry))):
            '''
            Add a terminal node.
            '''
            this_atom = (random.choice(Atoms))
            this_atom = str(this_atom)
            this_node = Node(this_atom)
            this_node.parent = pparent
            this_node.branch = bbranch
            this_node.seq = KS.next()
            self.thedict[this_node.seq] = this_node
            return this_node

        else:
            '''
            Add a node that has branches.
            '''
            this_operator = (random.choice(Operators.keys()))

            this_node = Node(this_operator)
            if entry:
                this_node.isRoot = True
            this_node.parent = pparent
            this_node.branch = bbranch
            this_node.seq = KS.next()
            self.thedict[this_node.seq] = this_node
            #branch as many times as 'number of arguements'
            # it's only set up for 2 arguements now.
            for i in range(Operators[this_operator]):
                depth =(depth - 1)
                if i == 0:
                    this_node.left = (self.build_nodes(entry = 0, depth =(depth),
                     pparent = this_node, bbranch = 'left'))
                else:
                    this_node.right = (self.build_nodes(entry = 0, depth =(depth),
                     pparent = this_node, bbranch = 'right'))
            return this_node


def Main():
    for i in range(100000):
        t = Tree()
    return t

if __name__ == '__main__':
    rresult = Main()

Ответы [ 3 ]

4 голосов
/ 25 января 2010

Ниже я суммировал некоторые из наиболее очевидных усилий по оптимизации, не особо затрагивая алгоритм. Все настройки выполняются на Python 2.6.4 в системе Linux x86-64.

Начальное время: 8,3 с

Низко висящие фрукты

желе уже указали на некоторые. Простое исправление уже немного улучшает время выполнения. Замена повторных вызовов на Operators.keys() путем повторного использования одного и того же списка также экономит некоторое время.

Время: 6,6 с

Использование itertools.count

Указано Дейвом Кирби , простое использование itertools.count также экономит ваше время:

from itertools import count
KS = count()

Время: 6,2 с

Улучшение конструктора

Поскольку вы не устанавливаете все атрибуты Node в ctor, вы можете просто переместить объявления атрибутов в тело класса:

class Node(object):
    isRoot = False
    left  = None
    right = None
    parent = None
    branch = None
    seq = 0

    def __init__(self, cargo):
        self.cargo = cargo

Это не меняет семантику класса, насколько вам известно, поскольку все значения, используемые в теле класса, являются неизменяемыми (False, None, 0), если вам нужны другие значения, сначала прочитайте этот ответ по атрибутам класса .

Время: 5.2с

Использование namedtuple

В вашем коде вы больше не изменяете дерево выражений, поэтому вы также можете использовать неизменяемый объект. Node также не имеет никакого поведения, поэтому использование namedtuple является хорошим вариантом. Это, однако, имеет значение, поскольку элемент parent должен был быть отброшен на данный момент. Судя по тому факту, что вы могли бы вводить операторы с более чем двумя аргументами, вам в любом случае придется заменить влево / вправо список дочерних элементов, который снова будет изменяемым и позволит создать родительский узел перед всеми дочерними элементами.

from collections import namedtuple
Node = namedtuple("Node", ["cargo", "left", "right", "branch", "seq", "isRoot"])
# ...
    def build_nodes (self,  depth = Depth, entry = 1,  pparent = None,
         bbranch = None):
        r = random.random()

        if (depth <= 0) or ((r > Ratio) and (not (entry))):
            this_node = Node(
                random.choice(Atoms), None, None, bbranch, KS.next(), False)
            self.thedict[this_node.seq] = this_node
            return this_node

        else:
            this_operator = random.choice(OpKeys)

            this_node = Node(
              this_operator,
              self.build_nodes(entry = 0, depth = depth - 1,
                               pparent = None, bbranch = 'left'),
              self.build_nodes(entry = 0, depth = depth - 2,
                               pparent = None, bbranch = 'right'),
              bbranch, 
              KS.next(), 
              bool(entry))

            self.thedict[this_node.seq] = this_node    
            return this_node

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

Конечное время: 4,1 с

Куда пойти отсюда

Если вы хотите иметь поддержку более двух операторов и / или поддержку родительского атрибута, используйте что-то вроде следующего кода:

from collections import namedtuple
Node = namedtuple("Node", ["cargo", "args", "parent", "branch", "seq", "isRoot"])

    def build_nodes (self,  depth = Depth, entry = 1,  pparent = None,
         bbranch = None):
        r = random.random()

        if (depth <= 0) or ((r > Ratio) and (not (entry))):
            this_node = Node(
                random.choice(Atoms), None, pparent, bbranch, KS.next(), False)
            self.thedict[this_node.seq] = this_node
            return this_node

        else:
            this_operator = random.choice(OpKeys)

            this_node = Node(
              this_operator, [], pparent, bbranch,
              KS.next(), bool(entry))
            this_node.args.extend(
              self.build_nodes(entry = 0, depth = depth - (i + 1),
                               pparent = this_node, bbranch = i)
              for i in range(Operators[this_operator]))

            self.thedict[this_node.seq] = this_node    
            return this_node

Этот код также уменьшает глубину в зависимости от положения оператора.

2 голосов
/ 24 января 2010

Вы можете заменить генератор KeySeq на itertools.count, который делает то же самое, но реализован в C.

Я не вижу способа ускорить конструктор Node. Вызов random.choice можно оптимизировать, вставив код - вырезайте и вставляйте его из исходного кода для случайного модуля. Это исключит вызов функции, который в Python относительно дорогой.

Вы можете ускорить его, запустив psyco , который является своего рода оптимизатором JIT. Однако это работает только для 32-битных сборок Intel Python. В качестве альтернативы вы можете использовать cython - это преобразует код python (ish) в C, который может быть скомпилирован в модуль Python C. Я говорю Pythonish, так как есть некоторые вещи, которые не могут быть преобразованы, и вы можете добавить аннотации типов данных C, чтобы сделать сгенерированный код более эффективным.

2 голосов
/ 24 января 2010

Вы можете опустить множество скобок в своем коде, это одно из преимуществ Python. Например. при установке скобок вокруг условий, таких как

if (depth <= 0) or ((r > Ratio) and (not (entry))):

просто напишите

if depth <= 0 or (r > Ratio and not entry):

И я думаю, что есть пара избыточных вызовов, например,

this_atom = str(this_atom)

(this_atom уже будет строкой, а сборка строк всегда дорогая, поэтому просто пропустите эту строку)

или вызов object конструктора

object.__init__(self)

что тоже не нужно.

Что касается метода Node.__init__, являющегося «узким местом»: я полагаю, что нельзя тратить большую часть своего времени там, так как при построении деревьев, подобных этому, вы ничего не будете делать, кроме создания новых узлов. *

...