Increasing speed of python code

前端 未结 8 1815
野性不改
野性不改 2021-02-05 12:37

I have some python code that has many classes. I used cProfile to find that the total time to run the program is 68 seconds. I found that the following function in

相关标签:
8条回答
  • 2021-02-05 13:25

    You can eliminate some lookups by using local function aliases:

    def qtyDemanded(self, timePd, priceVector):
        '''Returns quantity demanded in period timePd. In addition,
        also updates the list of customers and non-customers.
    
        Inputs: timePd and priceVector
        Output: count of people for whom priceVector[-1] < utility
        '''
        price = priceVector[-1]
        self.customers = []
        self.nonCustomers = []
    
        # local function aliases
        addCust = self.customers.append
        addNonCust = self.nonCustomers.append
    
        for person in self.people:
            if person.utility >= price:             
                person.customer = 1
                addCust(person)
            else:
                person.customer = 0
                addNonCust(person)
    
        return len(self.customers)
    
    0 讨论(0)
  • 2021-02-05 13:27

    Please consider trimming down your f_wo_append function:

    def f_wo_append():
        '''Function without append'''
        P = 75
        numcustomers = 0
        for person in popn.people:
            person.customer = iscust = person.utility >= P
            numcustomers += iscust
        return numcustomers
    

    Edit in response to OP's comment """This made it a lot worse! The trimmed version takes 4 times more time than the version I have posted above. """

    There is no way that that could take "4 times more" (5 times?) ... here is my code, which demonstrates a significant reduction in the "without append" case, as I suggested, and also introduces a significant improvement in the "with append" case.

    import random # instead of numpy
    import time
    timer_func = time.clock # better on Windows, use time.time on *x platform
    
    class Person(object):
        def __init__(self, util):
            self.utility = util
            self.customer = 0
    
    class Population(object):
        def __init__(self, numpeople):
            random.seed(1)
            self.people = [Person(random.uniform(0, 300)) for i in xrange(numpeople)]
            self.cus = []
            self.noncus = []        
    
    def f_w_append(popn):
        '''Function with append'''
        P = 75
        cus = []
        noncus = []
        for per in popn.people:
            if  per.utility >= P:
                per.customer = 1
                cus.append(per)
            else:
                per.customer = 0
                noncus.append(per)
        popn.cus = cus # omitted from OP's code
        popn.noncus = noncus # omitted from OP's code
        return len(cus)
    
    def f_w_append2(popn):
        '''Function with append'''
        P = 75
        popn.cus = []
        popn.noncus = []
        cusapp = popn.cus.append
        noncusapp = popn.noncus.append
        for per in popn.people:
            if  per.utility >= P:
                per.customer = 1
                cusapp(per)
            else:
                per.customer = 0
                noncusapp(per)
        return len(popn.cus)    
    
    def f_wo_append(popn):
        '''Function without append'''
        P = 75
        for per in popn.people:
            if  per.utility >= P:
                per.customer = 1
            else:
                per.customer = 0
    
        numcustomers = 0
        for per in popn.people:
            if per.customer == 1:
                numcustomers += 1                
        return numcustomers
    
    def f_wo_append2(popn):
        '''Function without append'''
        P = 75
        numcustomers = 0
        for person in popn.people:
            person.customer = iscust = person.utility >= P
            numcustomers += iscust
        return numcustomers    
    
    if __name__ == "__main__":
        import sys
        popsize, which, niter = map(int, sys.argv[1:4])
        pop = Population(popsize)
        func = (f_w_append, f_w_append2, f_wo_append, f_wo_append2)[which]
        t0 = timer_func()
        for _unused in xrange(niter):
            nc = func(pop)
        t1 = timer_func()
        print "popsize=%d func=%s niter=%d nc=%d seconds=%.2f" % (
            popsize, func.__name__, niter, nc, t1 - t0)
    

    and here are the results of running it (Python 2.7.1, Windows 7 Pro, "Intel Core i3 CPU 540 @ 3.07 GHz"):

    C:\junk>\python27\python ncust.py 300 0 80000
    popsize=300 func=f_w_append niter=80000 nc=218 seconds=5.48
    
    C:\junk>\python27\python ncust.py 300 1 80000
    popsize=300 func=f_w_append2 niter=80000 nc=218 seconds=4.62
    
    C:\junk>\python27\python ncust.py 300 2 80000
    popsize=300 func=f_wo_append niter=80000 nc=218 seconds=5.55
    
    C:\junk>\python27\python ncust.py 300 3 80000
    popsize=300 func=f_wo_append2 niter=80000 nc=218 seconds=4.29
    

    Edit 3 Why numpy takes longer:

    >>> import numpy
    >>> utils = numpy.random.uniform(0, 300, 10)
    >>> print repr(utils[0])
    42.777972538362874
    >>> type(utils[0])
    <type 'numpy.float64'>
    

    and here's why my f_wo_append2 function took 4 times longer:

    >>> x = utils[0]
    >>> type(x)
    <type 'numpy.float64'>
    >>> type(x >= 75) 
    <type 'numpy.bool_'> # iscust refers to a numpy.bool_
    >>> type(0 + (x >= 75)) 
    <type 'numpy.int32'> # numcustomers ends up referring to a numpy.int32
    >>>
    

    The empirical evidence is that these custom types aren't so fast when used as scalars ... perhaps because they need to reset the floating-point hardware each time they are used. OK for big arrays, not for scalars.

    Are you using any other numpy functionality? If not, just use the random module. If you have other uses for numpy, you may wish to coerce the numpy.float64 to float during the population setup.

    0 讨论(0)
提交回复
热议问题