Share the 7 tips of "Python extreme optimization and speed-up" taught by "Grandma Wang" downstairs!

Python is a scripting language. Compared with C/C + +, it has some shortcomings in efficiency and performance. However, there are many times when the efficiency of Python is not as exaggerated as expected. Let's move on to the topic.

1. Code optimization principle

This article will introduce a lot of techniques to speed up the running of Python code. Before going into the details of code optimization, you need to understand some basic principles of code optimization.

The first basic principle is to weigh the cost of optimization. Optimization comes at a price, and it is almost impossible to solve all performance problems. The usual choice is time for space or space for time. In addition, development costs also need to be considered.

The second basic principle is not to optimize too early. Many people start writing code with the goal of performance optimization, "making the right program faster is much easier than making the fast program correct". Therefore, the premise of optimization is that the code can work normally. Premature optimization may ignore the grasp of the overall performance index. Don't reverse the primary and secondary before getting the global results.

The third principle is not to optimize those unimportant parts. If every part of the code is optimized, these modifications will make the code difficult to read and understand. If your code is running slowly, you should first find the place where the code is running slowly, usually an internal loop, and focus on optimizing where it is running slowly.

2. Avoid

2.1 avoid module and function attribute access

#Not recommended. Code time: 14.5 seconds
import math

def computeSqrt(size: int):
    result = []
    for i in range(size):
        result.append(math.sqrt(i))
    return result

def main():
    size = 10000
    for _ in range(size):
        result = computeSqrt(size)

main()

Every use (when an attribute accesses an operator) triggers specific methods, such as__ getattribute__ () and__ getattr__ (), these methods will perform dictionary operation, so they will bring additional time overhead. Attribute access can be eliminated through the from import statement.

#Optimize the writing method for the first time. Code time: 10.9 seconds
from math import sqrt

def computeSqrt(size: int):
    result = []
    for i in range(size):
        result.append(sqrt(i))  #Avoid math Use of sqrt
    return result

def main():
    size = 10000
    for _ in range(size):
        result = computeSqrt(size)

main()

In Section 1, we mentioned that local variables can be searched faster than global variables. Therefore, for frequently accessed variables sqrt, we can speed up the operation by changing them to local variables.

#The second optimization writing method. Code time: 9.9 seconds
import math

def computeSqrt(size: int):
    result = []
    sqrt = math.sqrt  #Assign to local variable
    for i in range(size):
        result.append(sqrt(i))  #Avoid math Use of sqrt
    return result

def main():
    size = 10000
    for _ in range(size):
        result = computeSqrt(size)

main()

Except math In addition to sqrt, there is also in the computeSqrt function The existence of is to call the append method of list. By assigning this method to a local variable, the for loop in the computeSqrt function can be completely eliminated use.

#Recommended writing. Code time: 7.9 seconds
import math

def computeSqrt(size: int):
    result = []
    append = result.append
    sqrt = math.sqrt    #Assign to local variable
    for i in range(size):
        append(sqrt(i))  #Avoid} result Append and math Use of sqrt +
    return result

def main():
    size = 10000
    for _ in range(size):
        result = computeSqrt(size)

main()

2.2 avoid intra class attribute access

#Not recommended. Code time: 10.4 seconds
import math
from typing import List

class DemoClass:
    def __init__(self, value: int):
        self._value = value
    
    def computeSqrt(self, size: int) -> List[float]:
        result = []
        append = result.append
        sqrt = math.sqrt
        for _ in range(size):
            append(sqrt(self._value))
        return result

def main():
    size = 10000
    for _ in range(size):
        demo_instance = DemoClass(size)
        result = demo_instance.computeSqrt(size)

main()

Avoid The principle of also applies to in class properties. Visit self_ Value is slower than accessing a local variable. By assigning an attribute in the class that needs to be accessed frequently to a local variable, the running speed of the code can be improved.

#Recommended writing. Code time: 8.0 seconds
import math
from typing import List

class DemoClass:
    def __init__(self, value: int):
        self._value = value
    
    def computeSqrt(self, size: int) -> List[float]:
        result = []
        append = result.append
        sqrt = math.sqrt
        value = self._value
        for _ in range(size):
            append(sqrt(value))  #Avoid self_ Use of value
        return result

def main():
    size = 10000
    for _ in range(size):
        demo_instance = DemoClass(size)
        demo_instance.computeSqrt(size)

main()

3. Avoid global variables

#Not recommended. Code time: 26.8 seconds
import math

size = 10000
for x in range(size):
    for y in range(size):
        z = math.sqrt(x) + math.sqrt(y)

At the beginning, many programmers will write some simple scripts in Python. When writing scripts, they are usually used to writing them directly as global variables, such as the above code. Because the definition of global variables and the implementation of local variables are different, they will run slower than the definition of global variables in the code. By putting script statements into functions, you can usually achieve a speed increase of 15% - 30%.

#Recommended writing. Code time: 20.6 seconds
import math

def main():  #Define into functions to reduce the use of all variables
    size = 10000
    for x in range(size):
        for y in range(size):
            z = math.sqrt(x) + math.sqrt(y)

main()

4. Avoid unnecessary abstraction

#Not recommended, code time: 0.55 seconds
class DemoClass:
    def __init__(self, value: int):
        self.value = value

    @property
    def value(self) -> int:
        return self._value

    @value.setter
    def value(self, x: int):
        self._value = x

def main():
    size = 1000000
    for i in range(size):
        demo_instance = DemoClass(size)
        value = demo_instance.value
        demo_instance.value = i

main()

Any time you use additional processing layers (such as decorators, property access, descriptors) to wrap code, it slows down the code. In most cases, it is necessary to re-examine the definition of using attribute accessors. Using getter/setter functions to access attributes is usually a code style left over by C/C + + programmers. If it's not really necessary, use simple attributes.

#Recommended writing method, code time: 0.33 seconds
class DemoClass:
    def __init__(self, value: int):
        self.value = value  #Avoid unnecessary property accessors

def main():
    size = 1000000
    for i in range(size):
        demo_instance = DemoClass(size)
        value = demo_instance.value
        demo_instance.value = i

main()

5. Use the short-circuit characteristics of if conditions

#Not recommended, code time: 0.05 seconds
from typing import List

def concatString(string_list: List[str]) -> str:
    abbreviations = {'cf.', 'e.g.', 'ex.', 'etc.', 'flg.', 'i.e.', 'Mr.', 'vs.'}
    abbr_count = 0
    result = ''
    for str_i in string_list:
        if str_i in abbreviations:
            result += str_i
    return result

def main():
    for _ in range(10000):
        string_list = ['Mr.', 'Hat', 'is', 'Chasing', 'the', 'black', 'cat', '.']
        result = concatString(string_list)

main()

The short-circuit characteristic of the if # condition refers to the statement such as if a and b, which will be returned directly when a is False and b will not be calculated; For statements such as if a or b, when a is True, it will be returned directly and b will not be evaluated. Therefore, in order to save running time, for or statements, variables with high probability of True value should be written in front of or, and should be pushed back.

#Recommended writing method, code time: 0.03 seconds
from typing import List

def concatString(string_list: List[str]) -> str:
    abbreviations = {'cf.', 'e.g.', 'ex.', 'etc.', 'flg.', 'i.e.', 'Mr.', 'vs.'}
    abbr_count = 0
    result = ''
    for str_i in string_list:
        if str_i[-1] == '.' and str_i in abbreviations:  #Using the short-circuit characteristics of ^ if ^ condition
            result += str_i
    return result

def main():
    for _ in range(10000):
        string_list = ['Mr.', 'Hat', 'is', 'Chasing', 'the', 'black', 'cat', '.']
        result = concatString(string_list)

main()

6. Avoid data replication

6.1 avoid meaningless data replication

#Not recommended, code time: 6.5 seconds
def main():
    size = 10000
    for _ in range(size):
        value = range(size)
        value_list = [x for x in value]
        square_list = [x * x for x in value_list]

main()

Value in the above code_ List is completely unnecessary, which creates unnecessary data structures or copies.

#Recommended writing method, code time: 4.8 seconds
def main():
    size = 10000
    for _ in range(size):
        value = range(size)
        square_list = [x * x for x in value]  #Avoid meaningless duplication

main()

Another situation is that they are too paranoid about Python's data sharing mechanism, do not well understand or trust Python's memory model, and abuse Python copy Functions such as deepcopy(). Generally, the copy operation can be removed from these codes.

6.2 intermediate variables are not used when exchanging values

#Not recommended, code time: 0.07 seconds
def main():
    size = 1000000
    for _ in range(size):
        a = 3
        b = 5
        temp = a
        a = b
        b = temp

main()

The above code creates a temporary variable temp when exchanging values. Without the help of intermediate variables, the code is simpler and runs faster.

#Recommended writing method, code time: 0.06 seconds
def main():
    size = 1000000
    for _ in range(size):
        a = 3
        b = 5
        a, b = b, a  #Without the aid of intermediate variables

main()

6.3 string splicing uses join instead of+

#Not recommended, code time: 2.6 seconds
import string
from typing import List

def concatString(string_list: List[str]) -> str:
    result = ''
    for str_i in string_list:
        result += str_i
    return result

def main():
    string_list = list(string.ascii_letters * 100)
    for _ in range(10000):
        result = concatString(string_list)

main()

When using a + b to splice strings, because the string in Python is an immutable object, it will apply for a piece of memory space and copy a and b into the newly applied memory space respectively. Therefore, if n# strings are to be spliced, n-1# intermediate results will be generated. Each intermediate result needs to apply for and copy memory, which seriously affects the operation efficiency. When using join() to splice strings, you will first calculate the total memory space to be applied for, then apply for the required memory at one time, and copy each string element into the memory.

#Recommended writing method, code time: 0.3 seconds
import string
from typing import List

def concatString(string_list: List[str]) -> str:
    return ''.join(string_list)  #Use , join , instead of ,+

def main():
    string_list = list(string.ascii_letters * 100)
    for _ in range(10000):
        result = concatString(string_list)

main()

7. Cycle optimization

7.1 replace while loop with for loop

#Not recommended. Code time: 6.7 seconds
def computeSum(size: int) -> int:
    sum_ = 0
    i = 0
    while i < size:
        sum_ += i
        i += 1
    return sum_

def main():
    size = 10000
    for _ in range(size):
        sum_ = computeSum(size)

main()

Python's for loop is much faster than the while loop.

#Recommended writing. Code time: 4.3 seconds
def computeSum(size: int) -> int:
    sum_ = 0
    for i in range(size):  #The for loop replaces the while loop
        sum_ += i
    return sum_

def main():
    size = 10000
    for _ in range(size):
        sum_ = computeSum(size)

main()

7.2 use implicit for loop instead of explicit for loop

For the above example, you can further replace the explicit for loop with an implicit for loop

#Recommended writing. Code time: 1.7 seconds
def computeSum(size: int) -> int:
    return sum(range(size))  #Implicit for loop replaces explicit for loop

def main():
    size = 10000
    for _ in range(size):
        sum = computeSum(size)

main()

7.3 reduce the calculation of inner for loop

#Not recommended. Code time: 12.8 seconds
import math

def main():
    size = 10000
    sqrt = math.sqrt
    for x in range(size):
        for y in range(size):
            z = sqrt(x) + sqrt(y)

main() 

In the above code, sqrt(x) is located in the inner for loop, which will be recalculated during each training, which increases the time overhead.

#Recommended writing. Code time: 7.0 seconds
import math

def main():
    size = 10000
    sqrt = math.sqrt
    for x in range(size):
        sqrt_x = sqrt(x)  #Reduce the calculation of inner {for} loop
        for y in range(size):
            z = sqrt_x + sqrt(y)

main() 

-Copyright notice: all articles on this blog except for special noticesIn addition, the copyright belongs to the author. Reprint please indicate the source!  

 

Keywords: Python IntelliJ IDEA Pycharm IDE

Added by ronniebrown on Thu, 03 Feb 2022 01:33:00 +0200