【问题标题】:How to make python for loops faster如何使python for循环更快
【发布时间】:2022-10-15 23:35:19
【问题描述】:

我有一个字典列表,如下所示:

[{'user': '123456', 'db': 'db1', 'size': '8628'}
{'user': '123456', 'db': 'db1', 'size': '7168'}
{'user': '123456', 'db': 'db1', 'size': '38160'}
{'user': '222345', 'db': 'db3', 'size': '8628'}
{'user': '222345', 'db': 'db3', 'size': '8628'}
{'user': '222345', 'db': 'db5', 'size': '840'}
{'user': '34521', 'db': 'db6', 'size': '12288'}
{'user': '34521', 'db': 'db6', 'size': '476'}
{'user': '2345156', 'db': 'db7', 'size': '5120'}.....]

此列表包含数百万个条目。每个用户可以在多个数据库中找到,每个用户可以在同一个数据库中拥有多个整体。我想总结一下每个用户每个 db 占用的大小。我不想使用熊猫。 目前我这样做:

  • 我创建了 2 个唯一用户列表和唯一数据库
  • 使用这些列表遍历大列表并总结 user 和 db 相同的地方
result = []
for user in unique_users:
    for db in unique_dbs:
        total_size = 0
        for i in big_list:
            if (i['user'] == user and i['db'] == db):
                total_size += float(i['size'])
        if(total_size) > 0:
            row = {}
            row['user'] = user
            row['db'] = db
            row['size'] = total_size
            result.append(row)

问题是这个三重 for 循环发展成非常大的东西(数千亿次迭代),需要永远总结结果。 如果 big_list 很小,则效果很好。

我应该如何处理它以保持快速和简单? 非常感谢!

【问题讨论】:

  • 为什么不只创建一个用户映射到数据库列表及其相应大小?
  • 我该怎么做?
  • 你为什么不想使用熊猫?它会让它变得微不足道......
  • 数据是按用户和数据库排序的吗?
  • “我想总结一下每个用户每个 db 占用的大小。”我不太了解。对于此处显示的输入,输出应该是什么?

标签: python arrays performance for-loop bigdata


【解决方案1】:

目前的方法有两个主要问题:低效算法和低效的数据结构.

第一个是使用的算法显然效率低下因为它在大列表上迭代了很多次。不需要遍历整个列表来过滤唯一用户和数据库。您可以遍历大列表一次,然后使用字典聚合数据.目标字典的键只是一个(user, db) 元组。字典的值为total_size。这是一个未经测试的示例:

# Aggregation part
# Note: a default dict can be used instead to make the code possibly simpler
aggregate_dict = dict()
for i in big_list:
    key = (i['user'], i['db'])
    value = float(i['size'])
    if key in aggregate_dict:
        aggregate_dict[key] += value
    else:
        aggregate_dict[key] = value

# Fast creation of `result`
result = []
for user in unique_users:
    for db in unique_dbs:
        total_size = aggregate_dict.get((user, key))
        if total_size is not None and total_size > 0:
            result.append({'user': user, 'db': db, 'size': total_size})

另一个问题是低效的数据结构:对于每一行,复制键,而可以使用元组。事实上,一个更好的数据结构是存储(column, items) 键值的字典,其中items 是目标列的项目列表。这种存储数据的方式称为数据帧。这大致是 Pandas 内部使用的(除了它是一个更好的 Numpy 数组,因为它比大多数操作的列表更紧凑且通常更高效)。对输入和输出都使用这种数据结构应该会产生显着的加速(如果与 Numpy 结合使用)和更低的内存占用.

【讨论】:

  • 请注意,将最终结果创建为列表理解大约快 8%,因此 [{'user': user, 'db': db, 'size': total_size} for user in unique_users for db in unique_dbs if (total_size := aggregate_dict.get((user, key)))]。请注意,假设大小为正,我相信 if total_size is not None and total_size > 0 可以简化为 if total_size,因为总大小 None 和零都将评估为 false。我将包括下面的测试代码供您参考。时间有点不稳定,但列表理解总是更快。
【解决方案2】:

尝试将用户映射到 db 到字典中的总大小。它将需要额外的内存,但访问速度应该更快,并且只需要一次通过数据:

user_to_db_to_size = {}
for entry in unique_users:
    user = entry['user']
    db = entry['db']
    size = int(entry['size'])
    if user not in user_to_db_to_size:
        user_to_db_to_size[user] = {}
    if db not in user_to_db_to_size[user]:
        user_to_db_to_size[user][db] = 0
    user_to_db_to_size[user][db] += size

print(user_to_db_to_size)

对于您的示例数据,它会产生:

{'123456': {'db1': 53956}, '222345': {'db3': 17256, 'db5': 840}, '34521': {'db6': 12764}, '2345156': {'db7': 5120}}

现在您可以通过以下方式访问每个用户/数据库的总大小:

print(user_to_db_to_size['123456']['db1'])  # 53956

【讨论】:

    【解决方案3】:

    如果您使用 Counter 并将值对 (user, db) 的元组作为键,则:

    from collections import Counter
    
    data = [{'user': '123456', 'db': 'db1', 'size': '8628'},
            {'user': '123456', 'db': 'db1', 'size': '7168'},
            {'user': '123456', 'db': 'db1', 'size': '38160'},
            {'user': '222345', 'db': 'db3', 'size': '8628'},
            {'user': '222345', 'db': 'db3', 'size': '8628'},
            {'user': '222345', 'db': 'db5', 'size': '840'},
            {'user': '34521', 'db': 'db6', 'size': '12288'},
            {'user': '34521', 'db': 'db6', 'size': '476'},
            {'user': '2345156', 'db': 'db7', 'size': '5120'}]
    
    print(sum((Counter({(d['user'], d['db']): int(d['size'])}) for d in data), start=Counter()))
    
    Counter({('123456', 'db1'): 53956, ('222345', 'db3'): 17256, ('34521', 'db6'): 12764, ('2345156', 'db7'): 5120, ('222345', 'db5'): 840})
    

    【讨论】:

      【解决方案4】:

      基于 Jérôme 的回答,将最终列表构造转换为列表理解的速度大约快 8%:

      import random
      import timeit
      
      random.seed(1)
      
      test_iterations = 10000
      big_list = [{'user': random.randint(0, 100), 'db': f'db{random.randint(1, 10)}', 'size': f'{random.randint(100, 90000)}' } for i in range(10000)]
      unique_users = { i['user'] for i in big_list }
      unique_dbs = { i['db'] for i in big_list }
      
      aggregate_dict = dict()
      for i in big_list:
          key = (i['user'], i['db'])
          value = float(i['size'])
          if key in aggregate_dict:
              aggregate_dict[key] += value
          else:
              aggregate_dict[key] = value
      
      def method1():
          result = []
          for user in unique_users:
              for db in unique_dbs:
                  total_size = aggregate_dict.get((user, key))
                  if total_size is not None and total_size > 0:
                      result.append({'user': user, 'db': db, 'size': total_size})
          return result
      
      def method2():
          return [{'user': user, 'db': db, 'size': total_size} for user in unique_users for db in unique_dbs if (total_size := aggregate_dict.get((user, key)))]
      
      assert method1() == method2()
      
      for i in range(5):
          print(f'Method1: {timeit.timeit(method1, number=test_iterations)}')
          print(f'Method2: {timeit.timeit(method2, number=test_iterations)}')
      

      返回:

      Method1: 2.8487615999765694
      Method2: 2.58896119997371
      Method1: 2.6110187000012957
      Method2: 2.3781125000095926
      Method1: 2.6778881999780424
      Method2: 2.5283793000271544
      Method1: 2.810435099992901
      Method2: 2.432665000029374
      Method1: 2.647385900025256
      Method2: 2.573091500031296
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-05-30
        • 1970-01-01
        • 2019-07-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-12-25
        • 1970-01-01
        相关资源
        最近更新 更多