【问题标题】:Fastest way to perform operations on numpy array billions of times对 numpy 数组执行数十亿次操作的最快方法
【发布时间】:2022-01-07 10:45:21
【问题描述】:

我的代码有以下目标:

1.) 给定 2 个 numpy 数组 a1 和 a2(仅由 0,1 组成)在 a1 和 a2 中找到 0 的索引位置。

2.) 查找数组 a1 和 a2 之间的匹配索引位置(如果有)

3.) 计算指标

a1 数组形状 = 2161

a2 数组形状 = 2161

例如

a1 = [0,1,1,0,1,0,1]

a2 = [1,1,0,0,1,1,0]

a1 中 0 的索引 = idx_a1[0,3,5]

a2 中 0 的索引 = idx_a2[2,3,6]

常见的 0 索引 b/w a1 和 a2 = 3

function_1 执行 step1 和 step2 和 step3

function1_iterations 在随机打乱 a1 并重复度量计算 1000 次后重复 function_1。这是为了确定指标是否具有统计意义。

我在 1 亿个数组对上执行以下代码,在 256 个内核上进行多处理。 1 亿个数组对的最佳运行时间约为 40 分钟。有什么办法可以显着提高效率吗?我需要在数十亿个数组对上运行它。

我下面的代码是我能从之前的 codereview 中得到一些帮助的最快的代码:

def function_1(self,a1, a2):
        
       
    
        event_index1, = np.where(a1 == 0)
        event_index2, = np.where(a2 == 0)
        n1, = event_index1.shape
        n2, = event_index2.shape
    
        if n1 == 0 or n2 == 0:
            return 0, 0
    
        n_matches, = np.intersect1d(event_index1, event_index2, assume_unique=True,).shape
        
        c_ij = n_matches/2

    
 
        metric_1= (c_ij *2) / math.sqrt(n1 * n2)
   

        return metric_1

    
    
    def function1_iterations(self,a1,a2,repeat = 1000,original_metric1):
        
        list_metric1 = []
        a1_copy = copy.deepcopy(a1)

        
        for i in range(0,repeat):
            np.random.shuffle(a1_copy)    # shuffle bits in array and recalculate 1000 times  
            
            metric_1 = self.function_1(a1= a1_copy, a2 = a2) 
            list_metric1.append(metric_1)
        list_metric1= np.array(list_metric1)
        
      
        significance_val = len(np.where(list_metric1>= [original_metric1])[0])/repeat
        
        
        return significance_val 

【问题讨论】:

  • 您应该确定代码的哪一部分最慢并简化您的问题以尝试首先优化该部分
  • 等等,你在设置c_ij = c_ji = n_matches/2,然后只使用c_ijc_ji来计算c_ij + c_ji?为什么要这么做? (这不是瓶颈,但很奇怪。)
  • @mozway 我应该为此逐行分析代码吗?我用timeit来测试代码sn-p的性能
  • 如果您的目标是优化以在大型数据集上运行,那么可能

标签: python arrays numpy


【解决方案1】:

关于代码的第一部分,您查找公共索引的方法效率不高,而是使用布尔运算:

# setup
np.random.seed(0)
a1 = np.random.choice([0,1], 1000000)
a2 = np.random.choice([0,1], 1000000)


# your option
event_index1, = np.where(a1 == 0)
event_index2, = np.where(a2 == 0)
n_matches, = np.intersect1d(event_index1, event_index2, assume_unique=True,).shape
n_matches

# output: 250947
# 74.8 ms ± 2.99 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)


# boolean operations
n_matches = ((a1==0)&(a2==0)).sum()

# output: 250947
# 1.98 ms ± 23.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

【讨论】:

  • 谢谢,使用 timeit 与原始代码进行比较。在我的数据的 100,000 个数组对上重复。由于某种原因,性能是相同的。低于 timeit 运行时间(以秒为单位)。我为此使用了 timeit.repeat。 Orig = [1.8448893700000013,1.9183033499999993,1.9350099100000022,1.8952869600000013 ,1.881331590000002,1.8508602699999983,1.8069528700000035,1.9085916200000042,1.8852551699999993,1.8711837899999977] mozway_run = [1.9086207299999998,1.8873793100000007,1.9485812600000003,1.9337501099999996,1.8837477899999997,1.78254278,1.7270117499999997,1.6787742400000014,1.7728193299999986,2.0007923199999995]
  • 这可能意味着你有另一个瓶颈(并不是说一旦发现这个瓶颈就不会进一步改进你的代码)
  • 您可能应该就代码审查提出问题,但请确保提供格式更好的代码(以及生成大量输入的可重现方式)
【解决方案2】:

我认为您不需要执行索引比较。数组值与零的直接匹配应该提供更快的结果:

def function_1(a1,a2):
    z1 = a1.size - np.sum(a1) # number of zeros in a1
    z2 = a2.size - np.sum(a2) # number of zeros in a2
    cs = min(a1.size,a2.size) # common size
    return np.sum((a1[:cs]|a2[:cs])==0)/math.sqrt(z1*z2) # measure

请注意,这假设 a1 和 a2 可以具有不同的大小,并且每次都有不同数量的零。调用函数 (function_iterations) 不会产生这些条件。在这种情况下,从一次迭代到下一次迭代,a1 和 a2 的大小将始终相同,并且具有相同数量的零。这意味着可以通过只计算一次分母并将其作为参数提供(或与计数匹配分开执行除法)来简化计算。

function1_iterations() 函数几乎没有并行性,所以大部分时间都花在了那里。

为了从并行计算中受益,您需要高效地构建一个混洗位矩阵,并使用 numpy 操作(而不是具有数千次迭代的 Python 循环)将它们作为一个整体进行处理。

这是一个示例,说明如何生成具有更好并行性的新指标列表:

def computeMetrics(a1,a2,repeat = 1000):

    z1 = a1.size - np.sum(a1) # number of zeros in a1
    z2 = a2.size - np.sum(a2) # number of zeros in a2
    cs = min(a1.size,a2.size) # common size

    # shuffle matrix of a1 copies
    a1s = np.repeat(a1[None,:],repeat,axis=0)  
    a1s = np.apply_along_axis(np.random.choice,1,a1s,a1.size,replace=False)

    # compute metric for each shuffled row (with parallelism)
    return np.sum((a1s[:,:cs]|a2[:cs])==0,axis=1)/math.sqrt(z1*z2)

输出:

a1 = np.array([0,1,1,0,1,0,1])
a2 = np.array([1,1,0,0,1,1,0])
print(computeMetrics(a1,a2,10))

[0.33333333 0.66666667 0.33333333 0.33333333 0.33333333 0.66666667
 0.         0.66666667 0.33333333 0.33333333]

a1 = np.array([0,1,1,0,1,0,1,0,0,1,1,0,0])   
a2 = np.array([1,1,0,0,1,1,0])            
print(computeMetrics(a1,a2,10))

[0.21821789 0.21821789 0.21821789 0.43643578 0.43643578 0.43643578
 0.65465367 0.21821789 0.21821789 0.21821789]

对实际数据使用 100M 重复可能需要太多内存,但您可以将其分解为 1M 的块并连接结果。

computeMetrics(a1,a2,1000)      takes 0.044 sec on my laptop
computeMetrics(a1,a2,1_000_000) takes 48 seconds

【讨论】:

    猜你喜欢
    • 2012-11-07
    • 2019-04-11
    • 2021-10-18
    • 2023-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-19
    • 2015-07-27
    相关资源
    最近更新 更多