为什么访问numpy数组中的一个元素会使我的程序减速这么快?

时间:2019-06-02 20:28:37

标签: python performance numpy

我正在编写一个仿真(2D Ising模型仿真)。它需要在2D数组中选取一个随机元素,并将该值乘以-1。 2D数组中的元素是自旋位点,其值为+1或-1。

我忘记添加一行代码,但实际上会更改旋转位置。添加此(一行!)代码行会大大降低程序速度。所有自旋位点均默认为-1。

我尝试编辑其他函数,这些函数执行一些基本数学运算(计算自旋位点的总能量,基本上只是乘法和加法)来偷偷将-1翻转,方法是更改​​一些减法来解决-1翻转,从而避免了必须访问该数组元素。仍然疯狂地放慢速度。

这是时间命令的结果,没有翻转随机数组元素。

真实0m1.425s 用户0m1.685s sys 0m1.078s

然后添加一行代码即可得到结果(如下所示) 真正的0m26.615s 用户0m27.019s sys 0m0.920s

import numpy as np
import matplotlib.pyplot as plt
# As seen in lecture:
# Ising model implementation with Metropolis algorithm
# Based on IsingViz.py from Landau, et al.

N     = (2000, 2000) # number of spin sites                                   
num_steps = 20*N[0]*N[1]  # number of iterations
B     = 0.05         # magnetic field                               
mu    = .33          # g mu (not needed if B=0)
J     = 1.           # exchange energy                              
k     = 1.
t     = 1.
np.random.seed() 
state0 = -1*np.ones(N) # Start with an arbitrary spin configuration
Evals = []
def energy(State, J, mu, B):
    # Energy will call row_energy on every row in State, and on 
    # every row of its transpose.
    total_energy = 0
    for row in State:
        total_energy += row_energy(row, J, mu, B)
    for col in State.transpose():
        total_energy += row_energy(col, J, mu, B)
    return total_energy
def row_energy(S, J, mu, B):
    first_set = np.concatenate([np.array([S[-1]]), S[:-1]])
    FirstTerm = np.sum(-J*first_set[:-1]*first_set[1:])
    SecondTerm = np.sum(-mu*S*B)
    return (FirstTerm + SecondTerm)

def energy_change(S, coor):
    """Determine the change in energy if spin `i` is flipped

    `(i+1) % len(S)` implements the periodic boundary condition.
    """
    x = coor[0]
    y = coor[1]
    # Multiply spin site by all adjacent elements
    S_left  = S[x-1,y]
    S_right = S[(x+1) % len(S),y]
    S_up    = S[x,(y+1) % len(S)]
    S_down  = S[x,y-1]
    return 2*J*S[x,y]*(S_left + S_right + S_up + S_down % len(S)) + 2*B*mu*S[x,y]

def TwoDIsing(state0, num_steps, J, mu, B, kT):
    ES = energy(state0, J, mu, B)
    energy_values = []
    energy_values.append(ES)
    # Contains a copy of the state configuration so we don't have to store
    # 2**N**2 * # of time step elements
    state_configs = np.array([state0, state0])
    deltas = [] # A lighter way of keeping track of how the state changes.
    rands = np.random.randint(2000, size=(num_steps,2))
    count = 1
    for x, y in rands:
        #test_state = state_configs[-1]
        # Trial step: flip spin at one random site
        #test_state[x,y] *= -1.
        state_configs[1][x][y] *= -1
        ET = ES + energy_change(state_configs[-1], (x,y))
        if np.exp((ES-ET)/(kT)) > np.random.random():
            #state_configs[-1] = test_state      # replace the state, or
            ES = ET
            deltas.append((x,y))
        else:
            # advance the previous state forward
            state_configs[-1]=state_configs[-2]
            deltas.append(())
        energy_values.append(ES)
        count += 1
        if count % 1000 == 0: print((count / num_steps)*100," %.....................")
    return state_configs, energy_values, deltas

1 个答案:

答案 0 :(得分:0)

使用state_configs存储状态的两个副本时出错。不这样做的速度要快得多。阅读评论以获取详细信息

def TwoDIsing(state0, num_steps, J, mu, B, kT):
    ES = energy(state0, J, mu, B)
    energy_values = []
    energy_values.append(ES)
    # Contains a copy of the state configuration so we don't have to store
    # 2**N**2 * # of time step elements
    #state_configs = np.array([state0, state0])
    deltas = [] # A lighter way of keeping track of how the state changes.
    rands = np.random.randint(2000, size=(num_steps,2))
    count = 1
    for x, y in rands:
        #test_state = state_configs[-1]
        # Trial step: flip spin at one random site
        #test_state[x,y] *= -1.
        state0[x][y] *= -1
        ET = ES + energy_change(state0, (x,y))
        if np.exp((ES-ET)/(kT)) > np.random.random():
            #state_configs[-1] = test_state      # replace the state, or
            ES = ET
            deltas.append((x,y))
        else:
            # advance the previous state forward
            state0[x,y] *= -1
            deltas.append(())
        energy_values.append(ES)
        count += 1
        if count % 1000 == 0: print((count / num_steps)*100," %.....................")
    return state_configs, energy_values, deltas