RCa*_*107 5 python random performance list
从列表中多次(> 1M)提取随机值的最佳(最快)方法是什么?
我目前的情况是,我有一个表示为邻接列表的图,其内部列表的长度可以有很大不同(在范围 [2,可能是 100k] 内)。
我需要迭代这个列表来生成随机游走,所以我当前的解决方案是沿着
当图不太大时,这种方法就足够好了,但是现在我正在处理一个包含 >440k 节点的图,每个节点所具有的边数差异非常大。
我目前用来提取随机索引的函数是
node_neighbors[int(random.random() * number_neighbors_of_node)]
Run Code Online (Sandbox Code Playgroud)
与我之前的实现相比,这加快了计算速度,但对于我的目的来说,它仍然慢得令人无法接受。
一个节点的邻居数量可以从 2 个到数万个,我无法删除小节点,我必须在这个环境中生成数万个随机游走。
从分析代码来看,大部分生成时间都花在寻找这些索引上,因此我正在寻找一种可以减少这样做所需时间的方法。然而,如果可以通过修改算法来完全避开它,那就太好了。
谢谢!
编辑:出于好奇,我使用测试了同一代码的三个变体timeit,结果如下:
setup='''
import numpy as np
import random
# generate a random adjacency list, nodes have a number of neighbors between 2 and 10000
l = [list(range(random.randint(2, 10000))) for _ in range(10000)]
'''
for _ in range(1000):
v = l[random.randint(0, 10000-1)] # Get a random node adj list
vv = v[random.randint(0, len(v)-1)] # Find a random neighbor in v
0.29709450000001425
for _ in range(1000):
v = l[random.randint(0, 10000-1)]
vv = v[np.random.choice(v)]
26.760767499999986
for _ in range(1000):
v = l[random.randint(0, 10000-1)]
vv = v[int(random.random()*(len(v)))]
0.19086300000000733
for _ in range(1000):
v = l[random.randint(0, 10000-1)]
vv = v[int(random.choice(v))]
0.24351880000000392
Run Code Online (Sandbox Code Playgroud)
您的解决方案(sol3)已经是最快的,其速度比您的测试建议的要快。我调整了性能测量,以消除节点的任意选择,有利于更接近您既定目标的路径遍历。
以下是改进后的性能测试和结果。我添加了 sol5() 来看看预先计算随机值列表是否会产生影响(我希望 numpy 能够对其进行矢量化,但它并没有变得更快)。
设置
import numpy as np
import random
# generate a random adjacency list, nodes have a number of neighbors between 2 and 10000
nodes = [list(range(random.randint(2, 10000))) for _ in range(10000)]
pathLen = 1000
Run Code Online (Sandbox Code Playgroud)
解决方案
def sol1():
node = nodes[0]
for _ in range(pathLen):
node = nodes[random.randint(0, len(node)-1)] # move to a random neighbor
def sol2():
node = nodes[0]
for _ in range(pathLen):
node = nodes[np.random.choice(node)]
def sol3():
node = nodes[0]
for _ in range(pathLen):
node = nodes[int(random.random()*(len(node)))]
def sol4():
node = nodes[0]
for _ in range(pathLen):
node = nodes[int(random.choice(node))]
def sol5():
node = nodes[0]
for rng in np.random.random_sample(pathLen):
node = nodes[int(rng*len(node))]
Run Code Online (Sandbox Code Playgroud)
测量
from timeit import timeit
count = 100
print("sol1",timeit(sol1,number=count))
print("sol2",timeit(sol2,number=count))
print("sol3",timeit(sol3,number=count))
print("sol4",timeit(sol4,number=count))
print("sol5",timeit(sol5,number=count))
sol1 0.12516996199999975
sol2 30.445685411
sol3 0.03886452900000137
sol4 0.1244026900000037
sol5 0.05330073100000021
Run Code Online (Sandbox Code Playgroud)
numpy 不太擅长操作具有可变维度的矩阵(例如邻居列表),但也许加速该过程的一种方法是矢量化下一个节点选择。通过为 numpy 数组中的每个节点分配一个随机浮点数,您可以使用它在节点之间导航,直到您的路径返回到已访问过的节点。只有这样,您才需要为该节点生成一个新的随机值。据推测,根据路径长度,这些“碰撞”的数量相对较少。
使用相同的想法,并利用 numpy 的向量化,您可以通过创建节点标识符(列)矩阵来并行进行多次遍历,其中每行都是并行遍历。
为了说明这一点,这里有一个函数,它使多个“蚂蚁”在其各自的随机路径上通过节点前进:
import numpy as np
import random
nodes = [list(range(random.randint(2, 10000))) for _ in range(10000)]
nbLinks = np.array(list(map(len,nodes)),dtype=np.int) # number of neighbors per node
npNodes = np.array([nb+[-1]*(10000-len(nb)) for nb in nodes]) # fixed sized rows for numpy
def moveAnts(antCount=12,stepCount=8,antPos=None,allPaths=False):
if antPos is None:
antPos = np.random.choice(len(nodes),antCount)
paths = antPos[:,None]
for _ in range(stepCount):
nextIndex = np.random.random_sample(size=(antCount,))*nbLinks[antPos]
antPos = npNodes[antPos,nextIndex.astype(np.int)]
if allPaths:
paths = np.append(paths,antPos[:,None],axis=1)
return paths if allPaths else antPos
Run Code Online (Sandbox Code Playgroud)
示例:12 只蚂蚁从随机起始位置随机前进 8 步
print(moveAnts(12,8,allPaths=True))
"""
[[8840 1302 3438 4159 2983 2269 1284 5031 1760]
[4390 5710 4981 3251 3235 2533 2771 6294 2940]
[3610 2059 1118 4630 2333 552 1375 4656 6212]
[9238 1295 7053 542 6914 2348 2481 718 949]
[5308 2826 2622 17 78 976 13 1640 561]
[5763 6079 1867 7748 7098 4884 2061 432 1827]
[3196 3057 27 440 6545 3629 243 6319 427]
[7694 1260 1621 956 1491 2258 676 3902 582]
[1590 4720 772 1366 2112 3498 1279 5474 3474]
[2587 872 333 1984 7263 168 3782 823 9]
[8525 193 449 982 4521 449 3811 2891 3353]
[6824 9221 964 389 4454 720 1898 806 58]]
"""
Run Code Online (Sandbox Code Playgroud)
单个蚂蚁的性能并不更好,但同时每个蚂蚁的时间要好得多
from timeit import timeit
count = 100
antCount = 100
stepCount = 1000
ap = np.random.choice(len(nodes),antCount)
t = timeit(lambda:moveAnts(antCount,stepCount,ap),number=count)
print(t) # 0.9538277329999989 / 100 --> 0.009538277329999989 per ant
Run Code Online (Sandbox Code Playgroud)
[编辑]我为可变大小的行想到了一个更好的数组模型,并提出了一种不会在固定维度(大部分为空)矩阵中浪费内存的方法。该方法是使用一个一维数组连续保存所有节点的链接,并使用两个附加数组来保存起始位置和邻居数量。事实证明,这种数据结构的运行速度甚至比固定大小的二维矩阵还要快。
import numpy as np
import random
nodes = [list(range(random.randint(2, 10000))) for _ in range(10000)]
links = np.array(list(n for neighbors in nodes for n in neighbors))
linkCount = np.array(list(map(len,nodes)),dtype=np.int) # number of neighbors for each node
firstLink = np.insert(np.add.accumulate(linkCount),0,0) # index of first link for each node
def moveAnts(antCount=12,stepCount=8,antPos=None,allPaths=False):
if antPos is None:
antPos = np.random.choice(len(nodes),antCount)
paths = antPos[:,None]
for _ in range(stepCount):
nextIndex = np.random.random_sample(size=(antCount,))*linkCount[antPos]
antPos = links[firstLink[antPos]+nextIndex.astype(np.int)]
if allPaths:
paths = np.append(paths,antPos[:,None],axis=1)
return paths if allPaths else antPos
from timeit import timeit
count = 100
antCount = 100
stepCount = 1000
ap = np.random.choice(len(nodes),antCount)
t = timeit(lambda:moveAnts(antCount,stepCount,ap),number=count)
print(t) # 0.7157810379999994 / 100 --> 0.007157810379999994 per ant
Run Code Online (Sandbox Code Playgroud)
当您添加更多蚂蚁时,“每只蚂蚁”的性能会提高,达到一定程度(比 sol3 快大约 10 倍):
antCount = 1000
stepCount = 1000
ap = np.random.choice(len(nodes),antCount)
t = timeit(lambda:moveAnts(antCount,stepCount,ap),number=count)
print(t,t/antCount) #3.9749405650000007, 0.0039749405650000005 per ant
antCount = 10000
stepCount = 1000
ap = np.random.choice(len(nodes),antCount)
t = timeit(lambda:moveAnts(antCount,stepCount,ap),number=count)
print(t,t/antCount) #32.688697579, 0.0032688697579 per ant
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1623 次 |
| 最近记录: |