简体   繁体   English

加快python中的代码块

[英]Speeding up block of code in python

I have identified two potential reasons why the following code snippet is performing very poorly, given points is a list of 10000 size-2 lists. 我发现了两个潜在的原因,原因是给定的点是10000个size-2列表,因此以下代码段的性能非常差。

  1. "append" to add new values given a key “添加”以给定键添加新值
  2. The neighbour map dictionary. 邻居地图字典。

     def calculate_distance(point1, point2): a = (point1[0], point1[1]) b = (point2[0], point2[1]) return distance.euclidean(a, b) def get_eps_neighbours(points, eps): neighbours = {} index = 0 for p in points: for q in points: if(calculate_distance(p, q) <= eps): if index in neighbours: neighbours[index].append(q) else: neighbours[index] = q index = index + 1 return {'neighbours': neighbours} 

Any suggestions on how I can improve the code's efficiency? 关于如何提高代码效率的任何建议?

This is an example of trivially parallel problem. 这是一个平凡的并行问题的例子。

My recommendation: 我的建议:

  • use numpy 使用numpy
  • create 2 points^points matrices (2D-arrays), one for x another for y 创建2个点^点矩阵(2D数组),一个用于x另一个用于y
  • use numpy 's array arithmetic 使用numpy的数组算法

Example: 例:

In [52]: points = [(1,1), (2,2), (3,3), (4,4)]  # super-simple data

In [54]: Xb = numpy.repeat(numpy.array(points)[:,0], 4).reshape(4, 4)

In [60]: Xb
Out[60]: 
array([[1, 1, 1, 1],
       [2, 2, 2, 2],
       [3, 3, 3, 3],
       [4, 4, 4, 4]])

In [61]: Xa = numpy.tile(numpy.array(points)[:,0], 4).reshape(4, 4)

In [62]: Xa
Out[62]: 
array([[1, 2, 3, 4],
       [1, 2, 3, 4],
       [1, 2, 3, 4],
       [1, 2, 3, 4]])

# Yb = numpy.repeat(numpy.array(points)[:,1], 4).reshape(4, 4)
# Ya = numpy.tile(numpy.array(points)[:,1], 4).reshape(4, 4)

In [65]: D = ((Xa - Xb) ** 2 + (Ya - Yb) ** 2) ** 0.5

In [66]: D
Out[66]: 
array([[ 0.        ,  1.41421356,  2.82842712,  4.24264069],
       [ 1.41421356,  0.        ,  1.41421356,  2.82842712],
       [ 2.82842712,  1.41421356,  0.        ,  1.41421356],
       [ 4.24264069,  2.82842712,  1.41421356,  0.        ]])

In [71]: D < 2
Out[71]: 
array([[ True,  True, False, False],
       [ True,  True,  True, False],
       [False,  True,  True,  True],
       [False, False,  True,  True]], dtype=bool)

# Assuming you want only one copy from each pair (a,b), (b,a)
In [73]: triangle = numpy.tri(4, 4, -1, bool)

In [74]: triangle
Out[74]: 
array([[False, False, False, False],
       [ True, False, False, False],
       [ True,  True, False, False],
       [ True,  True,  True, False]], dtype=bool)

In [76]: neighbours = (D < 2) * triangle  # multiplication for "logical and"
Out[76]: 
array([[False, False, False, False],
       [ True, False, False, False],
       [False,  True, False, False],
       [False, False,  True, False]], dtype=bool)

# Neighbours' x and y coordinates are available so:
In [107]: numpy.compress(neighbours.flatten(), Xa.flatten())
Out[107]: array([1, 2, 3])

# Indices to elements in original `points` list like this:
Indexb = numpy.repeat(numpy.arange(4), 4).reshape(4, 4)
Indexa = numpy.tile(numpy.arange(4), 4).reshape(4, 4)
numpy.transpose([numpy.compress(neighbours.flatten(), Indexa.flatten()),
                 numpy.compress(neighbours.flatten(), Indexb.flatten())])
array([[0, 1],
       [1, 2],
       [2, 3]])

With general idea of your algorithm, I think you may reduce the list of points that undergo euclidean distance test by first removeing (or copying to another list) only elements which 2*abs(px - qx) <= eps (repeat for y), that would be much faster than calculating euclidean for all points. 有了算法的总体思路,我认为您可以通过先删除(或复制到另一个列表)仅2*abs(px - qx) <= eps (重复y)的元素来减少经过欧几里得距离测试的点的列表,这比计算所有点的欧几里得要快得多。 If eps is small that would work. 如果eps小,那将起作用。

I don't know if this will speed up your code but the pythonic way of counting loops is like this: 我不知道这是否可以加快您的代码的速度,但是计数循环的Python方法是这样的:

for i, p in enumerate(points):

Also - I'm not sure I understand the logic of searching through the whole dictionary (map) keys every time. 另外-我不确定我是否每次都能理解整个字典(map)键的逻辑。 This bit of code doesn't look like its doing something useful 这段代码看起来并不像在做有用的事情

neighBourMap[index] = q

This adds a key value pair of key: q, value: q to the dictionary. 这会将键值对q的键值对添加到字典中。 Have you tried just using a list instead ie 您是否尝试过仅使用列表,即

neighBourMap = []

All the other answers are correct, but they won't give you a huge speedup. 所有其他答案都是正确的,但是它们不会给您带来巨大的提速。 Using numpy arrays will give you a little bit of speedup, parallelizing will give you a speedup. 使用numpy数组可以使您加速,并行化可以使您加速。 But the speedup will be not enought, if you have 1 million points and you still use your current algorithms, which does n^2 distance calculations. 但是,如果您拥有一百万个点,并且仍然使用当前的算法(进行n ^ 2距离计算),那么加速将不够。 (1 million)^2 is way to many. (100万)^ 2是通往许多目标的方法。 If your using numpy or not? 如果您使用numpy?

You should switch your algorithm. 您应该切换算法。 You should store your points in a kd tree. 您应该将点存储在kd树中。 This way you can concentrate your search to a few neighbor candidates. 这样,您可以将搜索集中在几个邻近的候选对象上。 Instead of iterating over all points q , you can simply iterate over all points q with |qx - px| < eps and |qy - py| < eps 除了遍历所有点q ,您可以简单地使用|qx - px| < eps and |qy - py| < eps遍历所有点q |qx - px| < eps and |qy - py| < eps |qx - px| < eps and |qy - py| < eps |qx - px| < eps and |qy - py| < eps . |qx - px| < eps and |qy - py| < eps If your eps is small and there are only a few neighbors for each point, than this should speed you up quite a lot. 如果您的eps很小,并且每个点只有几个邻居,那么这将使您的速度大大提高。

Here's a pdf, that describes the algorithm how to find all points in a certain range: http://www.cse.unr.edu/~bebis/CS302/Handouts/kdtree.pdf 这是一份pdf文件,其中描述了该算法如何查找特定范围内的所有点: http : //www.cse.unr.edu/~bebis/CS302/Handouts/kdtree.pdf

You want all combinations of points with each other. 您希望所有点相互组合。 You can use itertools.combinations . 您可以使用itertools.combinations

Since we're making only the combinations we need, we don't need to keep looking up the dictionary index to append. 由于我们仅在进行所需的组合,因此无需继续查找要附加的字典索引。 We can keep the point and its list of neighbours together. 我们可以将点及其邻居列表放在一起。

Using a defaultdict with a list means we don't have to manually create the list the first time we look up a point. list使用defaultdict意味着我们不必在第一次查找点时手动创建list

Also, you don't actually want the value of the euclidian distance, you just want to know if it is less than some other value. 另外,您实际上并不需要欧几里得距离的值,只想知道它是否小于其他值。 So comparing the squares will give you the same result. 因此,比较平方将为您提供相同的结果。

To use a point as a key to a dictionary it needs to be immutable, so we convert it to a tuple: 要将point用作字典的键,它必须是不可变的,因此我们将其转换为元组:

def distance_squared(a, b):
    diff = complex(*a) - complex(*b)
    return diff.real ** 2 + diff.imag ** 2

from itertools import combinations
from collections import defaultdict

neighbours = defaultdict(list)
eps_squared = eps ** 2

point_neighbours = ((point, neighbours[tuple(point)]) for point in points)

for (p, p_neighbours), (q, _) in combinations(point_neighbours , r=2):
    if distance_squared(p, q) <= eps_squared:
        p_neighbours.append(q)

For one thing you can replace 一件事情你可以取代

index in neighBourMap.keys()):`

with just 只是

index in neighBourMap

which will run faster as a copy of the dictionary's key does not need to be created. 由于不需要创建字典键的副本,因此运行速度更快。

Better yet, use a defaultdict(list) which obviates the need for checking for a key before appending to the list value. 更好的是,使用defaultdict(list)可以避免在追加到列表值之前检查键的需求。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM