【问题标题】:Voronoi diagram with Jump Flooding algorithm: performance issue使用 Jump Flooding 算法的 Voronoi 图:性能问题
【发布时间】:2021-08-04 19:22:39
【问题描述】:

我正在尝试实现 Jump Flooding 算法以从点数组中绘制 Voronoi 图。下面的代码可以正常工作,但对于大 N 来说非常慢。它慢的原因是有一个循环将新信息添加到正在迭代的数组中。该数组列出了相邻点。如果我不包括这个附加,Voronoi 图是无意义的,算法没有达到大多数点。我不明白为什么在原始邻居搜索中识别的邻居的简单扫描并没有击中网格上的所有点。你能建议我的代码有什么问题吗?如果有一个高性能的替代方法可以将所有找到的邻居附加到一个巨大的数组中?或者这可能只是一个 python 问题,用 C/C++ 等重写将解决性能问题?

import matplotlib.pyplot as plt
import numpy as np

from time import time

# array = np.zeros((5, 5))
# originalSeeds = {1: [2, 2], 2: [4, 1], 3: [0, 4]}

array = np.zeros((10, 10))
originalSeeds = {1: [2, 2], 2: [4, 1], 3: [0, 4]}
# array = np.zeros((200, 200))
# originalSeeds = {1: [20, 20], 2: [40, 10], 3: [0, 40]}
for key, value in originalSeeds.items():
    array[value[0], value[1]] = key

seeds = {1: [], 2: [], 3: []}
# this is constantly updated list of neighbors

colors = [1, 2, 3]
colorvalues = {1: 1, 2: 2, 3: 3}

# plt.imshow(array)
# plt.show()

start = time()

step = 2**(int(np.log2(array.shape[0])))


def distance(crd1, crd2):
    return np.linalg.norm(crd1 - crd2)


while step >= 1:
    for color in colors:
        originalSeedCoords = originalSeeds[color]
        # neighbor displacments
        disp = step * np.array([[1, 1], [0, 1], [-1, 1], [1, -1], [1, 0], [-1, -1], [0, -1], [-1, 0]])
        for element in disp:
            center = originalSeeds[color]
            idx = center + np.array(element)

            if idx[0] > array.shape[0] - 1 or idx[1] > array.shape[1] - 1 or idx[0] < 0 or idx[1] < 0:
                continue
            else:

                if array[idx[0], idx[1]] == color:
                    continue
                elif array[idx[0], idx[1]] == 0:
                    array[idx[0], idx[1]] = colorvalues[color]
                    seeds[color].append(list(idx))
                else:
                    currColor = array[idx[0], idx[1]]

                    if distance(idx, originalSeeds[color]) < distance(idx, originalSeeds[currColor]):
                        array[idx[0], idx[1]] = colorvalues[color]
                        seeds[color].append(list(idx))
                    else:
                        array[idx[0], idx[1]] = colorvalues[currColor]
                        seeds[currColor].append(list(idx))

    for color in colors:
        for seed in seeds[color]:  # this list may grov during iteration, filling in uncharted points
            for element in disp:
                center = seed

                idx = center + np.array(element)

                if idx[0] > array.shape[0] - 1 or idx[1] > array.shape[1] - 1 or idx[0] < 0 or idx[1] < 0:
                    continue
                else:
                    if array[idx[0], idx[1]] == color:
                        continue
                    elif array[idx[0], idx[1]] == 0:
                        array[idx[0], idx[1]] = colorvalues[color]
                        if list(idx) not in seeds[color]:
                            seeds[color].append(list(idx))
                    else:
                        currColor = array[idx[0], idx[1]]

                        if distance(idx, originalSeeds[color]) < distance(idx, originalSeeds[currColor]):
                            array[idx[0], idx[1]] = colorvalues[color]
                            if list(idx) not in seeds[color]:
                                seeds[color].append(list(idx))
                        else:
                            array[idx[0], idx[1]] = colorvalues[currColor]
                            if list(idx) not in seeds[currColor]:
                                seeds[currColor].append(list(idx))

    step = step // 2
    # step -= 1

【问题讨论】:

  • 用 C++ 转换这段代码会有很大帮助。这应该至少快 1 个数量级。使用更好的数据结构也应该有所帮助。事实上,dicts/hash-maps 很慢。如果键始终是大于或等于 0 的小整数,则可以使用快速纯数组。除此之外,算法可能也可以改进。事实上,not in 是在O(n) 时间执行的。 seeds[...] 应转换为集合而不是列表,以便在恒定时间内执行此操作(需要进行小修复以避免在迭代集合时出现散列问题和更新)。其他改进可能是可能的。
  • 我不能在这里使用集合,因为数组需要在迭代过程中改变长度(python 集合不能这样做)。有没有一种方法可以在循环遍历它们时重写它而不需要附加到“seeds [color]”数组?

标签: python performance voronoi


【解决方案1】:

Jump flooding 算法是为 GPU 设计的,该算法在所有像素上并行执行,并且使用 ping-pong 缓冲区来存储最后一次传递的结果。它不应该以顺序方式使用,更不用说在Python中实现。

【讨论】:

    【解决方案2】:

    您可以执行多个微优化,例如避免重新计算表达式(例如数组索引),避免对非常小的数组进行 Numpy 调用(因为它们比执行实际数组的成本更高)由于许多检查和分配而导致的计算)等。此外,您可以使用 set 来加速 not in 运算符,甚至可以删除它们。请注意,您不能将新值添加到迭代集中。解决此问题的一种解决方案是将值添加到新集合中并稍后迭代新集合,依此类推,直到新集合为空。这是生成的代码:

    import matplotlib.pyplot as plt
    import numpy as np
    import math
    
    
    # array = np.zeros((5, 5))
    # originalSeeds = {1: [2, 2], 2: [4, 1], 3: [0, 4]}
    
    array = np.zeros((100, 100))
    originalSeeds = {1: [2, 2], 2: [4, 1], 3: [0, 4]}
    # array = np.zeros((200, 200))
    # originalSeeds = {1: [20, 20], 2: [40, 10], 3: [0, 40]}
    for key, value in originalSeeds.items():
        array[value[0], value[1]] = key
    
    seeds = {1: set(), 2: set(), 3: set()}
    # this is constantly updated list of neighbors
    
    colors = [1, 2, 3]
    colorvalues = {1: 1, 2: 2, 3: 3}
    
    # plt.imshow(array)
    # plt.show()
    
    
    step = 2**(int(np.log2(array.shape[0])))
    
    
    def distance(crd1, crd2):
        return math.sqrt((crd1[0] - crd2[0])**2 + (crd1[1] - crd2[1])**2)
    
    
    n, m = array.shape
    while step >= 1:
        disp = [tuple(e) for e in step * np.array([[1, 1], [0, 1], [-1, 1], [1, -1], [1, 0], [-1, -1], [0, -1], [-1, 0]])]
        for color in colors:
            originalSeedCoords = originalSeeds[color]
            # neighbor displacments
            for element in disp:
                center = originalSeeds[color]
    
                x = int(center[0] + element[0])
                y = int(center[1] + element[1])
                idx = (x, y)
    
                if x > n - 1 or y > m - 1 or x < 0 or y < 0:
                    continue
                else:
                    if array[x, y] == color:
                        continue
                    elif array[x, y] == 0:
                        array[x, y] = colorvalues[color]
                        seeds[color].add(idx)
                    else:
                        currColor = array[x, y]
    
                        if distance(idx, originalSeeds[color]) < distance(idx, originalSeeds[currColor]):
                            array[x, y] = colorvalues[color]
                            seeds[color].add(idx)
                        else:
                            array[x, y] = colorvalues[currColor]
                            seeds[currColor].add(idx)
    
        for color in colors:
            curSeeds = seeds[color]
            while len(curSeeds) > 0:
                newSeeds = set()
                for seed in curSeeds:  # this list may grow during iteration, filling in uncharted points
                    for element in disp:
                        center = seed
    
                        x = int(center[0] + element[0])
                        y = int(center[1] + element[1])
                        idx = (x, y)
    
                        if x > n - 1 or y > m - 1 or x < 0 or y < 0:
                            continue
                        else:
                            if array[x, y] == color:
                                continue
                            elif array[x, y] == 0:
                                array[x, y] = colorvalues[color]
                                newSeeds.add(idx)
                            else:
                                currColor = array[x, y]
    
                                if distance(idx, originalSeeds[color]) < distance(idx, originalSeeds[currColor]):
                                    array[x, y] = colorvalues[color]
                                    newSeeds.add(idx)
                                else:
                                    array[x, y] = colorvalues[currColor]
                                    if currColor != color:
                                        seeds[currColor].add(idx)
                                    else:
                                        newSeeds.add(idx)
                seeds[color].update(newSeeds)
                curSeeds = newSeeds
    
        step = step // 2
        # step -= 1
    

    这个未选中代码在我的机器上大约快了 12 倍

    使用带有循环和由 CPython 解释器 执行的基本操作的纯 Python 代码在性能方面并不是很好。 Numba 的 JITCython 可用于加速很多代码。用 C++ 重写算法也很有帮助。我认为还可以通过使用比集合更好的数据结构来改进算法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-11-29
      • 2018-01-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多