使用大数据集Python优化循环

时间:2013-08-26 13:00:03

标签: python mongodb loops optimization

这是我第一次使用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"
    }
  ]
}

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

#a is the dataset
data = db.collection.find()
a =[i for i in data]

#here go the connections between the locations
edges = csv.writer(open("edges.csv", "wb"))
#and here the location data
nodes = csv.writer(open("nodes.csv", "wb"))

for i in a:

    #find the users that match
    for q in a:
        if i['_id'] <> q['_id'] and q.get('users') :
            weight = 0
            for user_i in i['users']:
                for user_q in q['users']:
                    if user_i['id'] == user_q['id']:
                        weight +=1
            if weight>0:
                edges.writerow([ i['id'], q['id'], weight])


    #find the number of photos
    photos_number =0
    for p in i['users']:
        photos_number += len(p['photos'])


    nodes.writerow([ i['id'],
                    i['name'],
                    i['latitude'],
                    i['longitude'],
                    len(i['users']),
                    photos_number
                ])

缩放问题:我有20000个位置,每个位置最多可能有2000个用户,每个用户可能有大约10张照片。

有没有更有效的方法来创建上述循环?也许Multithreads,JIT,更多索引? 因为如果我在单个线程中运行以上可以达到20000 ^ 2 * 2000 * 10的结果......

那么我怎样才能更有效地处理上述问题呢? 感谢

3 个答案:

答案 0 :(得分:3)

@YuchenXie和@PaulMcGuire建议的微优化可能不是你的主要问题,即你循环超过20,000 x 20,000 = 400,000,000对条目,然后有一个2,000 x 2,000用户对的内循环。那会很慢。

幸运的是,通过在set中预先缓存用户ID的i['users'],并用简单的集交集替换内循环,可以使内循环更快。这会将Python解释器中发生的O(num_users^2)操作更改为在C中发生的O(num_users)操作,这应该会有所帮助。 (我只是用大小为2,000的整数列表计时;在我的计算机上,它从你使用它的方式的156ms到这种方式的41μs,加速为4,000倍。)

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

考虑到这些和@PaulMcGuire的建议以及其他一些风格变化,您的代码变为(警告:未经测试的代码):

q = a[1]

希望这应该足以加速。如果没有,@ YuchenXie的建议可能会有所帮助,虽然我很怀疑,因为stdlib / OS相当擅长缓冲这类事情。 (您可以使用文件对象上的缓冲设置。)

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

你也可以将硬重量计算推入Mongo,这可能会更加明智;我从来没有真正使用它,所以我不知道。

答案 1 :(得分:2)

瓶颈是磁盘I / O.

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

答案 2 :(得分:1)

是否会折叠此循环:

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

下至:

photos_number = sum(len(p['photos']) for p in i['users'])

有帮助吗?

你的体重计算:

        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值相同。