我需要编写一个程序来计算两个用户在同一组中的次数。用户按用户名和组ID分配。 例如,使用输入(存储在文本文件中):
john 32
john 21
jim 21
jim 32
bob 32
我想要结果:
john-jim 2
john-bob 1
jim-bob 1
这听起来微不足道。但问题是:我有1,800万组和30万用户。还有很多会员资格(我预计每个用户平均至少50个,可能更多)。这意味着需要大量的数据和处理。
我写了5个不同的程序,没有一个能够减少数据量:它像PostgreSQL查询一样慢。耗尽内存消耗在Java工作内存中的Map中运行(第一个堆空间,在优化之后我得到了罕见的“超出GC开销限制”)。从Java连续写入数据库太慢(即使使用批处理查询进行优化)。越来越绝望,我尝试了一些更奇特的东西,比如把所有的对都写成一个数组,然后对它们进行排序(O(n log(n)))然后将它们计算为peuàpeu。但是仍然有太多的数据存储在内存中。
有关算法的任何想法吗?或者这是不可能的?
答案 0 :(得分:7)
RDBMS专门用于排序等操作。在数据库外部执行此操作几乎不会在性能上接近。用SQL做吧!
这可以完成工作(在更新中简化):
SELECT t1.usr || '-' || t2.usr, count(*) AS ct
FROM usr_grp t1
JOIN usr_grp t2 USING (grp_id)
WHERE t2.usr > t1.usr -- prevent dupes and get sorted pair
GROUP BY t1.usr, t2.usr;
正如您所说,根据您拥有的重叠次数,这可能会产生大量的行。所以这永远不会很快。
提出问题:生成无人可以处理的数百万行的目的是什么?你确定,这个操作从一开始就有意义吗?
为了让它更快,你可以......
我们始终建议所有用户运行最新的可用次要内容 适用于任何主要版本的版本。
integer
作为用户的代理键,因此您只能在usr_grp
处理整数。使表和索引更小并且处理更快。如果n:m表(usr_grp
)的基数比表usr
大得多,那么它应该更快,即使它意味着额外的连接。
SELECT u1.usr || '-' || u2.usr, count(*) AS ct
FROM usr_grp t1
JOIN usr_grp t2 USING (grp_id)
JOIN usr u1 ON t1.usr_id = u1.usr_id
JOIN usr u2 ON t2.usr_id = u2.usr_id
WHERE t2.usr_id > t1.usr_id
GROUP BY u1.usr_id, u2.usr_id;
grp_id
必须先行。 Why does this matter?
CREATE INDEX usr_grp_gu_idx ON usr_grp(grp_id, usr_id);
work_mem
和shared_buffers
的设置。我将数字@OldCurmudgeon reported用于他的测试用例并在PostgreSQL中创建了一个类似的测试用例。
此公共测试数据库中〜 250 ms
结果未订购(无ORDER BY
),因为尚未指定
与 2.5分钟相比,reported below。因素600。
答案 1 :(得分:2)
如何让文件系统执行此操作。
对于每个条目 - 打开一个以组ID命名的文件并附加新用户的名称。每组最终会有一个文件。
你现在有 - 例如:
Group-21.txt
jim
john
Group-32.txt
bob
jim
john
现在遍历所有文件,在其中生成每个用户名对(我会对名称进行排序并对它们执行标准组合过程)。对于每对,请将“1”附加到具有特定名称的文件。
你现在有 - 例如:
User-jim-john.txt
11
User-bob-jim.txt
1
User-bob-john.txt
1
现在,文件名和计数对(在一元中,所以你真正需要的是文件大小,以字节为单位)。
几乎所有这些都可以并行完成,但第1阶段必须在第2阶段开始之前完成。为了提高速度 - 添加核心 - 购买更快的磁盘。没有内存限制,只有磁盘。
已添加:我刚刚使用一个线程对此算法运行了一些模拟测试
1800个组,300个用户和15000个成员资格都是随机生成的,大约需要2.5分钟。 900个团体,150个用户和7500个会员资格需要54秒。
答案 2 :(得分:1)
无论解决方案是什么,复杂性取决于生成的对的数量,而不一定取决于组或人的数量。对于不同的团体规模:
所以我的第一个建议是在数据集中清除非常大的组。如果您不能省略大型组,并发现它不适合内存或者需要花费很长时间才能通过单个线程进行操作,则可以使用Map-Reduce自动并行化计算,如下所示。如果您从组成员身份开始,例如:
32 -> john, jim, bob
21 -> john, jim
您可以使用地图步骤生成所有对:
john-jim -> 32, john-bob -> 32, jim-bob -> 32
john-jim -> 21
这些将通过名称对聚合。然后在reduce中,只计算每个键的出现次数。这假设您有足够的磁盘来存储所有对。