用大数据集Python优化循环

2024-05-20 22:33:19 发布

您现在位置:Python中文网/ 问答频道 /正文

这是我第一次用Python做这么大的事情,所以我需要一些帮助。在

我有一个mongodb(或python dict),其结构如下:

{
  "_id": { "$oid" : "521b1fabc36b440cbe3a6009" },
  "country": "Brazil",
  "id": "96371952",
  "latitude": -23.815124482000001649,
  "longitude": -45.532670811999999216,
  "name": "coffee",
  "users": [
    {
      "id": 277659258,
      "photos": [
        {
          "created_time": 1376857433,
          "photo_id": "525440696606428630_277659258",
        },
        {
          "created_time": 1377483144,
          "photo_id": "530689541585769912_10733844",
        }
      ],
      "username": "foo"
    },
    {
      "id": 232745390,
      "photos": [
        {
          "created_time": 1369422344,
          "photo_id": "463070647967686017_232745390",
        }
      ],
      "username": "bar"
    }
  ]
}

现在,我想创建两个文件,一个包含摘要,另一个包含每个连接的权重。适用于小型数据集的循环如下:

^{pr2}$

缩放问题:我有20000个位置,每个位置可能有多达2000个用户,每个用户可能有10张左右的照片。在

有没有更有效的方法来创建上述循环?可能是多线程,JIT,更多的索引? 因为如果我在一个线程中运行以上操作,最多可以得到20000^2*2000*10个结果。。。在

那么如何才能更有效地处理上述问题呢? 谢谢


Tags: 用户idtimemongodbusername事情结构country
3条回答

瓶颈是磁盘I/O

当您合并结果并使用一个或多个writerows调用而不是多个writerow时,它应该快得多。在

@YuchenXie和@PaulMcGuire建议的微优化可能不是你的主要问题,那就是你循环超过20000×20000=400000000对条目,然后有一个2000×2000用户对的内部循环。那会很慢的。在

幸运的是,通过预缓存i['users']中的用户id的set,并用一个简单的集合交集替换内部循环,可以更快地完成内部循环。这将Python解释器中的O(num_users^2)操作更改为C中的O(num_users)操作,这应该会有所帮助。(我只是用大小为2000的整数列表来计时;在我的电脑上,它从原来的156ms变为现在的41µs,速度提高了4000倍。)

您还可以通过注意到关系是对称的,来切断对位置的主循环的一半工作,因此没有必要同时执行i = a[1]q = a[2]和{},q = a[1]。在

考虑到这些和@PaulMcGuire的建议,再加上一些其他风格上的变化,您的代码将变成(注意:前面未测试的代码):

from itertools import combinations, izip

data = db.collection.find()
a = list(data)

user_ids = [{user['id'] for user in i['users']} if 'users' in i else set()
            for i in a]

with open("edges.csv", "wb") as f:
    edges = csv.writer(f)
    for (i, i_ids), (q, q_ids) in combinations(izip(a, user_ids), 2):
        weight = len(i_ids & q_ids)
        if weight > 0:
            edges.writerow([i['id'], q['id'], weight])
            edges.writerow([q['id'], i['id'], weight])

with open("nodes.csv", "wb") as f:
    nodes = csv.writer(f)
    for i in a:
        nodes.writerow([
            i['id'],
            i['name'],
            i['latitude'],
            i['longitude'],
            len(i['users']),
            sum(len(p['photos']) for p in i['users']), # total number of photos
        ])

希望这足够加速了。如果不是,那么@YuchenXie的建议可能会有帮助,尽管我对此表示怀疑,因为stdlib/OS在缓冲此类问题上相当出色。(您可以使用文件对象上的缓冲设置。)

否则,它可能会归结为尝试从Python(Cython或手写C)中获取核心循环,或者给PyPy一次机会。不过,我很怀疑这会给你带来巨大的加速。在

你也可以把计算重量的方法推到Mongo中,这可能更聪明;我从来没有真正使用过它,所以我不知道。在

折叠此循环:

photos_number =0
for p in i['users']:
    photos_number += len(p['photos'])

下至:

^{pr2}$

有什么帮助吗?在

你的体重计算:

        weight = 0
        for user_i in i['users']:
            for user_q in q['users']:
                if user_i['id'] == user_q['id']:
                    weight +=1

还应可折叠为:

        weight = sum(user_i['id'] == user_q['id'] 
                        for user_i,user_q in product([i['users'],q['users']))

因为True等于1,所以求和所有布尔条件等于计算所有为True的值。在

相关问题 更多 >