我可以使用什么语言来快速执行此数据库摘要任务?

时间:2009-09-23 18:45:08

标签: python sql lisp ocaml apache-pig

所以我编写了一个Python程序来处理一些数据处理 任务。

这是我想要的计算语言的一个非常简短的说明:

parse "%s %lf %s" aa bb cc | group_by aa | quickselect --key=bb 0:5 | \
    flatten | format "%s %lf %s" aa bb cc

也就是说,对于每一行,解析出一个单词,一个浮点数和另一个单词。将它们视为玩家ID,分数和日期。我想要每个球员的前五个得分和日期。数据大小并非微不足道,但并不大;大约630兆字节。

我想知道我应该用什么真正的可执行语言编写它 让它同样简短(如下面的Python)但速度要快得多。

#!/usr/bin/python
# -*- coding: utf-8; -*-
import sys

top_5 = {}

for line in sys.stdin:
    aa, bb, cc = line.split()

    # We want the top 5 for each distinct value of aa.  There are
    # hundreds of thousands of values of aa.
    bb = float(bb)
    if aa not in top_5: top_5[aa] = []
    current = top_5[aa]
    current.append((bb, cc))

    # Every once in a while, we drop the values that are not in
    # the top 5, to keep our memory footprint down, because some
    # values of aa have thousands of (bb, cc) pairs.
    if len(current) > 10:
        current.sort()
        current[:-5] = []

for aa in top_5:
    current = top_5[aa]
    current.sort()
    for bb, cc in current[-5:]:
        print aa, bb, cc

以下是一些示例输入数据:

3 1.5 a
3 1.6 b
3 0.8 c
3 0.9 d
4 1.2 q
3 1.5 e
3 1.8 f
3 1.9 g

这是我得到的输出:

3 1.5 a
3 1.5 e
3 1.6 b
3 1.8 f
3 1.9 g
4 1.2 q

3有七个值,因此我们删除cd值 因为他们的bb值会使他们超出前5名。因为4有 只有一个值,它的“前5”只包含那个值。

这比在MySQL中执行相同的查询运行得更快(至少,在 我们发现做查询的方式)但我很确定它的消费 大部分时间都在Python字节码解释器中。我认为 另一种语言,我可能会得到它来处理数百个 每秒数千行而不是每分钟。所以我想 用更快实现的语言编写它。

但我不确定选择哪种语言。

我无法弄清楚如何在SQL中将其表达为单个查询,并且 实际上,我对MySQL的能力甚至不感兴趣 select * from foo into outfile 'bar';输入数据。

C是一个明显的选择,但是line.split()之类的东西,对列表进行排序 2元组,并制作哈希表需要编写一些代码 不在标准库中,所以我最终会得到100行代码 或更多,而不是14。

C ++似乎可能是一个更好的选择(它有字符串,地图, 对和标准库中的向量)但它看起来像代码 STL会比较混乱。

OCaml没问题,但它有相当于line.split()的情况, 我会对地图的表现感到难过吗?

Common Lisp可能有用吗?

是否有类似于Matlab的数据库计算 这让我把循环推入快速代码?有人试过Pig吗?

(编辑:通过提供一些示例输入和输出数据来回复davethegr8的评论,并修复了Python程序中的错误!)

(补充编辑:哇,这个评论帖子到目前为止非常棒。谢谢,大家!)

编辑:

有一个eerily similar question asked on sbcl-devel in 2007(谢谢,Rainer!),这是来自Will Hartung的awk脚本,用于生成一些测试数据(尽管它没有Zipfian分布的真实数据) :

BEGIN {
 for (i = 0; i < 27000000; i++) {
  v = rand();
  k = int(rand() * 100);
  print k " " v " " i;
 }
 exit;
}

18 个答案:

答案 0 :(得分:9)

答案 1 :(得分:6)

您可以使用更智能的数据结构并仍然使用python。 我在我的机器上运行了你的参考实现和我的python实现,甚至比较了输出结果。

这是你的:

$ time python ./ref.py  < data-large.txt  > ref-large.txt

real 1m57.689s
user 1m56.104s
sys 0m0.573s

这是我的:

$ time python ./my.py  < data-large.txt  > my-large.txt

real 1m35.132s
user 1m34.649s
sys 0m0.261s
$ diff my-large.txt ref-large.txt 
$ echo $?
0

这是来源:

#!/usr/bin/python
# -*- coding: utf-8; -*-
import sys
import heapq

top_5 = {}

for line in sys.stdin:
    aa, bb, cc = line.split()

    # We want the top 5 for each distinct value of aa.  There are
    # hundreds of thousands of values of aa.
    bb = float(bb)
    if aa not in top_5: top_5[aa] = []
    current = top_5[aa]
    if len(current) < 5:
        heapq.heappush(current, (bb, cc))
    else:
        if current[0] < (bb, cc):
            heapq.heapreplace(current, (bb, cc))

for aa in top_5:
    current = top_5[aa]
    while len(current) > 0:
        bb, cc = heapq.heappop(current)
        print aa, bb, cc

更新:了解您的限制。 我还计划了一个noop代码,知道最快的python解决方案,其代码类似于原始代码:

$ time python noop.py < data-large.txt  > noop-large.txt

real    1m20.143s
user    1m19.846s
sys 0m0.267s

noop.py本身:

#!/usr/bin/python
# -*- coding: utf-8; -*-
import sys
import heapq

top_5 = {}

for line in sys.stdin:
    aa, bb, cc = line.split()

    bb = float(bb)
    if aa not in top_5: top_5[aa] = []
    current = top_5[aa]
    if len(current) < 5:
        current.append((bb, cc))

for aa in top_5:
    current = top_5[aa]
    current.sort()
    for bb, cc in current[-5:]:
        print aa, bb, cc

答案 2 :(得分:3)

这是另一个OCaml版本 - 针对速度 - 使用Streams上的自定义解析器。太长了,但解析器的一部分是可重用的。感谢 peufeu 引发竞争:)

速度:

  • 简单的ocaml - 27秒
  • ocaml with Stream解析器 - 15秒
  • c使用手动解析器 - 5秒

编译:

ocamlopt -pp camlp4o code.ml -o caml

代码:

open Printf

let cmp x y = compare (fst x : float) (fst y)
let digit c = Char.code c - Char.code '0'

let rec parse f = parser
  | [< a=int; _=spaces; b=float; _=spaces; 
       c=rest (Buffer.create 100); t >] -> f a b c; parse f t
  | [< >] -> ()
and int = parser
  | [< ''0'..'9' as c; t >] -> int_ (digit c) t
  | [< ''-'; ''0'..'9' as c; t >] -> - (int_ (digit c) t)
and int_ n = parser
  | [< ''0'..'9' as c; t >] -> int_ (n * 10 + digit c) t
  | [< >] -> n
and float = parser
  | [< n=int; t=frem; e=fexp >] -> (float_of_int n +. t) *. (10. ** e)
and frem = parser
  | [< ''.'; r=frem_ 0.0 10. >] -> r
  | [< >] -> 0.0
and frem_ f base = parser
  | [< ''0'..'9' as c; t >] -> 
      frem_ (float_of_int (digit c) /. base +. f) (base *. 10.) t
  | [< >] -> f
and fexp = parser
  | [< ''e'; e=int >] -> float_of_int e
  | [< >] -> 0.0
and spaces = parser
  | [< '' '; t >] -> spaces t
  | [< ''\t'; t >] -> spaces t
  | [< >] -> ()
and crlf = parser
  | [< ''\r'; t >] -> crlf t
  | [< ''\n'; t >] -> crlf t
  | [< >] -> ()
and rest b = parser
  | [< ''\r'; _=crlf >] -> Buffer.contents b
  | [< ''\n'; _=crlf >] -> Buffer.contents b
  | [< 'c; t >] -> Buffer.add_char b c; rest b t
  | [< >] -> Buffer.contents b

let () =
  let all = Array.make 200 [] in
  let each a b c =
    assert (a >= 0 && a < 200);
    match all.(a) with
    | [] -> all.(a) <- [b,c]
    | (bmin,_) as prev::tl -> if b > bmin then
      begin
        let m = List.sort cmp ((b,c)::tl) in
        all.(a) <- if List.length tl < 4 then prev::m else m
      end
  in
  parse each (Stream.of_channel stdin);
  Array.iteri 
    (fun a -> List.iter (fun (b,c) -> printf "%i %f %s\n" a b c))
    all

答案 3 :(得分:3)

这是Common Lisp中的草图

请注意,对于长文件,使用READ-LINE会有一个惩罚,因为它会为每一行提供一个新字符串。然后使用READ-LINE的衍生物之一,它们使用行缓冲器浮动。您也可以检查是否希望哈希表区分大小写。

第二版

不再需要拆分字符串,因为我们在此处执行此操作。它是低级代码,希望可以获得一些速度提升。它会检查一个或多个空格作为字段分隔符以及标签。

(defun read-a-line (stream)
  (let ((line (read-line stream nil nil)))
    (flet ((delimiter-p (c)
             (or (char= c #\space) (char= c #\tab))))
      (when line
        (let* ((s0 (position-if     #'delimiter-p line))
               (s1 (position-if-not #'delimiter-p line :start s0))
               (s2 (position-if     #'delimiter-p line :start (1+ s1)))
               (s3 (position-if     #'delimiter-p line :from-end t)))
          (values (subseq line 0 s0)
                  (list (read-from-string line nil nil :start s1 :end s2)
                        (subseq line (1+ s3)))))))))

上面的函数返回两个值:键和其余的列表。

(defun dbscan (top-5-table stream)
   "get triples from each line and put them in the hash table"
  (loop with aa = nil and bbcc = nil do
    (multiple-value-setq (aa bbcc) (read-a-line stream))
    while aa do
    (setf (gethash aa top-5-table)
          (let ((l (merge 'list (gethash aa top-5-table) (list bbcc)
                          #'> :key #'first)))
             (or (and (nth 5 l) (subseq l 0 5)) l)))))


(defun dbprint (table output)
  "print the hashtable contents"
  (maphash (lambda (aa value)
              (loop for (bb cc) in value
                    do (format output "~a ~a ~a~%" aa bb cc)))
           table))

(defun dbsum (input &optional (output *standard-output*))
  "scan and sum from a stream"
  (let ((top-5-table (make-hash-table :test #'equal)))
    (dbscan top-5-table input)
    (dbprint top-5-table output)))


(defun fsum (infile outfile)
   "scan and sum a file"
   (with-open-file (input infile :direction :input)
     (with-open-file (output outfile
                      :direction :output :if-exists :supersede)
       (dbsum input output))))

一些测试数据

(defun create-test-data (&key (file "/tmp/test.data") (n-lines 100000))
  (with-open-file (stream file :direction :output :if-exists :supersede)
    (loop repeat n-lines
          do (format stream "~a ~a ~a~%"
                     (random 1000) (random 100.0) (random 10000)))))

(创建测试数据)

(defun test ()
  (time (fsum "/tmp/test.data" "/tmp/result.data")))

第三版,LispWorks

使用一些SPLIT-STRING和PARSE-FLOAT函数,否则使用通用CL。

(defun fsum (infile outfile)
  (let ((top-5-table (make-hash-table :size 50000000 :test #'equal)))
    (with-open-file (input infile :direction :input)
      (loop for line = (read-line input nil nil)
            while line do
            (destructuring-bind (aa bb cc) (split-string '(#\space #\tab) line)
              (setf bb (parse-float bb))
              (let ((v (gethash aa top-5-table)))
                (unless v
                  (setf (gethash aa top-5-table)
                        (setf v (make-array 6 :fill-pointer 0))))
                (vector-push (cons bb cc) v)
                (when (> (length v) 5)
                  (setf (fill-pointer (sort v #'> :key #'car)) 5))))))
    (with-open-file (output outfile :direction :output :if-exists :supersede)
      (maphash (lambda (aa value)
                 (loop for (bb . cc) across value do
                       (format output "~a ~f ~a~%" aa bb cc)))
               top-5-table))))    

答案 4 :(得分:3)

我的机器上有45.7秒,有27M行数据,如下所示:

42 0.49357 0
96 0.48075 1
27 0.640761 2
8 0.389128 3
75 0.395476 4
24 0.212069 5
80 0.121367 6
81 0.271959 7
91 0.18581 8
69 0.258922 9

你的脚本对这个数据花了1m42,c ++例子也是1m46(g ++ t.cpp -o t来编译它,我对c ++一无所知)。

Java 6,并不重要。输出并不完美,但很容易修复。

package top5;

import java.io.BufferedReader;
import java.io.FileReader;
import java.util.Arrays;
import java.util.Map;
import java.util.TreeMap;

public class Main {

    public static void main(String[] args) throws Exception {
        long start  = System.currentTimeMillis();
        Map<String, Pair[]> top5map = new TreeMap<String, Pair[]>();
        BufferedReader br = new BufferedReader(new FileReader("/tmp/file.dat"));

        String line = br.readLine();
        while(line != null) {
            String parts[] = line.split(" ");

            String key = parts[0];
            double score = Double.valueOf(parts[1]);
            String value = parts[2];
            Pair[] pairs = top5map.get(key);

            boolean insert = false;
            Pair p = null;
            if (pairs != null) {
                insert = (score > pairs[pairs.length - 1].score) || pairs.length < 5;
            } else {
                insert = true;
            }
            if (insert) {
                p = new Pair(score, value);
                if (pairs == null) {
                    pairs = new Pair[1];
                    pairs[0] = new Pair(score, value);
                } else {
                    if (pairs.length < 5) {
                        Pair[] newpairs = new Pair[pairs.length + 1];
                        System.arraycopy(pairs, 0, newpairs, 0, pairs.length);
                        pairs = newpairs;
                    }
                    int k = 0;
                    for(int i = pairs.length - 2; i >= 0; i--) {
                        if (pairs[i].score <= p.score) {
                            pairs[i + 1] = pairs[i];
                        } else {
                            k = i + 1;
                            break;
                        }
                    }
                    pairs[k] = p;
                }
                top5map.put(key, pairs);
            }
            line = br.readLine();
        }
        for(Map.Entry<String, Pair[]> e : top5map.entrySet()) {
            System.out.print(e.getKey());
            System.out.print(" ");
            System.out.println(Arrays.toString(e.getValue()));
        }
        System.out.println(System.currentTimeMillis() - start);
    }

    static class Pair {
        double score;
        String value;

        public Pair(double score, String value) {
            this.score = score;
            this.value = value;
        }

        public int compareTo(Object o) {
            Pair p = (Pair) o;
            return (int)Math.signum(score - p.score);
        }

        public String toString() {
            return String.valueOf(score) + ", " + value;
        }
    }
}

伪造数据的AWK脚本:

BEGIN {
 for (i = 0; i < 27000000; i++) {
  v = rand();
  k = int(rand() * 100);
  print k " " v " " i;
 }
 exit;
}

答案 5 :(得分:2)

答案 6 :(得分:2)

相当简单的Caml(27 * 10 ^ 6行 - 27秒,C ++ by hrnt - 29秒)

open Printf
open ExtLib

let (>>) x f = f x
let cmp x y = compare (fst x : float) (fst y)
let wsp = Str.regexp "[ \t]+"

let () =
  let all = Hashtbl.create 1024 in
  Std.input_lines stdin >> Enum.iter (fun line ->
    let [a;b;c] = Str.split wsp line in
    let b = float_of_string b in
    try
      match Hashtbl.find all a with
      | [] -> assert false
      | (bmin,_) as prev::tl -> if b > bmin then
        begin
          let m = List.sort ~cmp ((b,c)::tl) in
          Hashtbl.replace all a (if List.length tl < 4 then prev::m else m)
        end
    with Not_found -> Hashtbl.add all a [b,c]
  );
  all >> Hashtbl.iter (fun a -> List.iter (fun (b,c) -> printf "%s %f %s\n" a b c))

答案 7 :(得分:1)

说到计算时间的下限:

让我们分析一下上面的算法:

for each row (key,score,id) :
    create or fetch a list of top scores for the row's key
    if len( this list ) < N
        append current
    else if current score > minimum score in list
        replace minimum of list with current row
        update minimum of all lists if needed

设N是前N的N. 设R是数据集中的行数 设K是不同键的数量

我们可以做出哪些假设?

R * sizeof(行)&gt; RAM或者至少它足够大,我们不想加载它,使用哈希按键分组,并对每个bin进行排序。出于同样的原因,我们不会对整个事情进行排序。

Kragen喜欢哈希表,因此K * sizeof(每个键状态)&lt;&lt; RAM,很可能它适合L2 / 3缓存

Kragen没有排序,所以K * N&lt;&lt; R即每个键具有多于N个条目

(注意:A&lt;&lt; B表示A相对于B较小)

如果数据具有随机分布,那么

在少量行之后,大多数行将被每个键的最小条件拒绝,每行的成本为1比较。

所以每行的成本是1个哈希查找+ 1个比较+ epsilon *(列表插入+(N + 1)比较最小)

如果分数具有随机分布(比如介于0和1之间)且上述条件成立,则两个epsilons都将非常小。

实验证明:

上面的2700万行数据集在前N个列表中产生5933个插入。通过简单的键查找和比较拒绝所有其他行。 epsilon = 0.0001

粗略地说,成本是每行1次查找+比较,这需要几纳秒。

在目前的硬件上,对于IO成本,尤其是解析成本,这是不可忽视的。

答案 8 :(得分:1)

这是一个C ++解决方案。但是,我没有很多数据来测试它,所以我不知道它实际上有多快。

[edit]感谢这个帖子中awk脚本提供的测试数据,我 设法清理并加快了代码的速度。我并不想找出最快的版本 - 意图是提供一个相当快的版本,这个版本并不像人们认为STL解决方案那样难看。

这个版本应该是第一个版本的两倍(在大约35秒内通过2700万行)。 Gcc用户,记得 用-O2编译它。

#include <map>
#include <iostream>
#include <functional>
#include <utility>
#include <string>
int main() {
  using namespace std;
  typedef std::map<string, std::multimap<double, string> > Map;
  Map m;
  string aa, cc;
  double bb;
  std::cin.sync_with_stdio(false); // Dunno if this has any effect, but anyways.

  while (std::cin >> aa >> bb >> cc)
    {
      if (m[aa].size() == 5)
        {
          Map::mapped_type::iterator iter = m[aa].begin();
          if (bb < iter->first)
            continue;
          m[aa].erase(iter);
        }
      m[aa].insert(make_pair(bb, cc));
    }
  for (Map::const_iterator iter = m.begin(); iter != m.end(); ++iter)
    for (Map::mapped_type::const_iterator iter2 = iter->second.begin();
         iter2 != iter->second.end();
         ++iter2)
      std::cout << iter->first << " " << iter2->first << " " << iter2->second <<
 std::endl;

}

答案 9 :(得分:1)

有趣的是,最初的Python解决方案是迄今为止最干净的(尽管C ++示例很接近)。

如何在原始代码上使用Pyrex或Psyco?

答案 10 :(得分:1)

有人试过用awk来解决这个问题。特别是'mawk'?根据这篇博文:http://anyall.org/blog/2009/09/dont-mawk-awk-the-fastest-and-most-elegant-big-data-munging-language/

,它应该比Java和C ++更快

编辑:只是想澄清一下,在博客文章中提出的唯一声明是,对于特定适合awk风格处理的某类问题,mawk虚拟机可以击败Java中的'vanilla'实现。 C ++。

答案 11 :(得分:1)

既然你问过Matlab,我就是这样做的,就像你要求的那样。我尝试没有任何for循环,但我确实有一个,因为我不想花很长时间。如果您担心内存,那么您可以使用fscanf从块中提取数据而不是读取整个缓冲区。

fid = fopen('fakedata.txt','r');
tic
A=fscanf(fid,'%d %d %d\n');
A=reshape(A,3,length(A)/3)';  %Matlab reads the data into one long column'
Names = unique(A(:,1));
for i=1:length(Names)
    indices = find(A(:,1)==Names(i));   %Grab all instances of key i
    [Y,I] = sort(A(indices,2),1,'descend'); %sort in descending order of 2nd record
    A(indices(I(1:min([5,length(indices(I))]))),:) %Print the top five
end
toc
fclose(fid)

答案 12 :(得分:0)

Pig版本会像这样(未经测试):

 Data = LOAD '/my/data' using PigStorage() as (aa:int, bb:float, cc:chararray);
 grp = GROUP Data by aa;
 topK = FOREACH grp (
     sorted = ORDER Data by bb DESC;
     lim = LIMIT sorted 5;
     GENERATE group as aa, lim;
)
STORE topK INTO '/my/output' using PigStorage();

猪没有针对性能进行优化;它的目标是使用并行执行框架来处理多TB数据集。它确实有一个本地模式,所以你可以试试,但我怀疑它会击败你的剧本。

答案 13 :(得分:0)

我喜欢午休时间的挑战。这是一个1小时的实施。

好的,当你不想做一些极其奇特的废话之类的添加时,没有什么可以阻止你使用自定义的基础10浮点格式,其中唯一实现的运算符是比较,对吧?洛尔。

我在之前的项目中有一些快速的代码,所以我只是导入了它。

http://www.copypastecode.com/11541/

此C源代码大约需要6.6秒来解析580MB的输入文本(2700万行),其中一半时间是fgets,lol。然后计算top-n大约需要0.05秒,但我不确定,因为top-n所需的时间小于定时器噪声。

你将是那个通过XDDDDDDDDDD

测试它的正确性的人 有趣的是吗?

答案 14 :(得分:0)

好吧,请抓一杯咖啡,阅读strtod的源代码 - 这是令人难以置信的,但如果你想漂浮,则需要 - &gt;文字 - &gt;漂浮以回馈你开始时的同一个浮动......真的......

解析整数要快得多(但在python中并不是那么多,但在C中,是的)。

无论如何,将数据放在Postgres表中:

SELECT count( key ) FROM the dataset in the above program

=&GT; 7秒(所以读取27M记录需要7秒)

CREATE INDEX topn_key_value ON topn( key, value );

191 s

CREATE TEMPORARY TABLE topkeys AS SELECT key FROM topn GROUP BY key;

12 s

(您可以使用索引更快地获取'key'的不同值,但它需要一些轻量级的plpgsql黑客攻击)

CREATE TEMPORARY TABLE top AS SELECT (r).* FROM (SELECT (SELECT b AS r FROM topn b WHERE b.key=a.key ORDER BY value DESC LIMIT 1) AS r FROM topkeys a) foo;

时间:15,310毫秒

INSERT INTO top SELECT (r).* FROM (SELECT (SELECT b AS r FROM topn b WHERE b.key=a.key ORDER BY value DESC LIMIT 1 OFFSET 1) AS r FROM topkeys a) foo;

时间:17,853毫秒

INSERT INTO top SELECT (r).* FROM (SELECT (SELECT b AS r FROM topn b WHERE b.key=a.key ORDER BY value DESC LIMIT 1 OFFSET 2) AS r FROM topkeys a) foo;

时间:13,983毫秒

INSERT INTO top SELECT (r).* FROM (SELECT (SELECT b AS r FROM topn b WHERE b.key=a.key ORDER BY value DESC LIMIT 1 OFFSET 3) AS r FROM topkeys a) foo;

时间:16,860毫秒

INSERT INTO top SELECT (r).* FROM (SELECT (SELECT b AS r FROM topn b WHERE b.key=a.key ORDER BY value DESC LIMIT 1 OFFSET 4) AS r FROM topkeys a) foo;

时间:17,651毫秒

INSERT INTO top SELECT (r).* FROM (SELECT (SELECT b AS r FROM topn b WHERE b.key=a.key ORDER BY value DESC LIMIT 1 OFFSET 5) AS r FROM topkeys a) foo;

时间:19,216 ms

SELECT * FROM top ORDER BY key,value;

正如你所看到的,计算top-n非常快(假设n很小)但是创建(强制)索引非常慢,因为它涉及完整的排序。

您最好的选择是使用快速解析的格式(二进制,或为您的数据库编写自定义C聚合,这将是恕我直言的最佳选择)。如果python可以在1秒内完成,那么C程序中的运行时不应超过1秒。

答案 15 :(得分:0)

这是一个很好的午休挑战,他,他。

Top-N是众所周知的数据库杀手。如上面的帖子所示,没有办法在常见的SQL中有效地表达它。

至于各种实现,你必须记住,这里的缓慢部分不是排序或前N,它是文本的解析。你最近看过gl​​ibc的strtod()的源代码吗?

例如,我得到了,使用Python:

Read data : 80.5  s
My TopN   : 34.41 s
HeapTopN  : 30.34 s

你很可能永远不会得到非常快的时间,无论你使用什么语言,除非你的数据采用某种格式,解析比文本快得多。例如,将测试数据加载到postgres需要70秒,其中大部分也是文本解析。

如果你的topN中的N很小,比如5,我的算法的C实现可能是最快的。如果N可以更大,那么堆是更好的选择。

所以,既然你的数据可能在数据库中,而你的问题是数据而不是实际的处理,如果你真的需要一个超快的TopN引擎,你应该做的就是写一个C您选择的数据库模块。由于postgres对于任何事情都更快,我建议使用postgres,而且为它编写C模块并不困难。

这是我的Python代码:

import random, sys, time, heapq

ROWS = 27000000

def make_data( fname ):
    f = open( fname, "w" )
    r = random.Random()
    for i in xrange( 0, ROWS, 10000 ):
        for j in xrange( i,i+10000 ):
            f.write( "%d    %f    %d\n" % (r.randint(0,100), r.uniform(0,1000), j))
        print ("write: %d\r" % i),
        sys.stdout.flush()
    print

def read_data( fname ):
    for n, line in enumerate( open( fname ) ):
        r = line.strip().split()
        yield int(r[0]),float(r[1]),r[2]
        if not (n % 10000 ):
            print ("read: %d\r" % n),
            sys.stdout.flush()
    print

def topn( ntop, data ):
    ntop -= 1
    assert ntop > 0
    min_by_key = {}
    top_by_key = {}
    for key,value,label in data:
        tup = (value,label)
        if key not in top_by_key:
            # initialize
            top_by_key[key] = [ tup ]
        else:
            top = top_by_key[ key ]
            l    = len( top )
            if l > ntop:
                # replace minimum value in top if it is lower than current value
                idx = min_by_key[ key ]
                if top[idx] < tup:
                    top[idx] = tup
                    min_by_key[ key ] = top.index( min( top ) )
            elif l < ntop:
                # fill until we have ntop entries
                top.append( tup )
            else:
                # we have ntop entries in list, we'll have ntop+1
                top.append( tup )
                # initialize minimum to keep
                min_by_key[ key ] = top.index( min( top ) )

    # finalize:
    return dict( (key, sorted( values, reverse=True )) for key,values in top_by_key.iteritems() )

def grouptopn( ntop, data ):
    top_by_key = {}
    for key,value,label in data:
        if key in top_by_key:
            top_by_key[ key ].append( (value,label) )
        else:
            top_by_key[ key ] = [ (value,label) ]

    return dict( (key, sorted( values, reverse=True )[:ntop]) for key,values in top_by_key.iteritems() )

def heaptopn( ntop, data ):
    top_by_key = {}
    for key,value,label in data:
        tup = (value,label)
        if key not in top_by_key:
            top_by_key[ key ] = [ tup ]
        else:
            top = top_by_key[ key ]
            if len(top) < ntop:
                heapq.heappush(top, tup)
            else:
                if top[0] < tup:
                    heapq.heapreplace(top, tup)

    return dict( (key, sorted( values, reverse=True )) for key,values in top_by_key.iteritems() )

def dummy( data ):
    for row in data:
        pass

make_data( "data.txt" )

t = time.clock()
dummy( read_data( "data.txt" ) )
t_read = time.clock() - t

t = time.clock()
top_result = topn( 5, read_data( "data.txt" ) )
t_topn = time.clock() - t

t = time.clock()
htop_result = heaptopn( 5, read_data( "data.txt" ) )
t_htopn = time.clock() - t

# correctness checking :
for key in top_result:
    print key, " : ", "        ".join (("%f:%s"%(value,label)) for (value,label) in    top_result[key])
    print key, " : ", "        ".join (("%f:%s"%(value,label)) for (value,label) in htop_result[key])

print
print "Read data :", t_read
print "TopN :     ", t_topn - t_read
print "HeapTopN : ", t_htopn - t_read

for key in top_result:
    assert top_result[key] == htop_result[key]

答案 16 :(得分:0)

挑选“前5名”看起来像这样。请注意,没有排序。 top_5词典中的任何列表都不会超过5个元素。

from collections import defaultdict
import sys

def keep_5( aList, aPair ):
    minbb= min( bb for bb,cc in aList )
    bb, cc = aPair
    if bb < minbb: return aList
    aList.append( aPair )
    min_i= 0
    for i in xrange(1,6):
        if aList[i][0] < aList[min_i][0]
            min_i= i
    aList.pop(min_i)
    return aList


top_5= defaultdict(list)
for row in sys.stdin:
    aa, bb, cc = row.split()
    bb = float(bb)
    if len(top_5[aa]) < 5:
        top_5[aa].append( (bb,cc) )
    else:
        top_5[aa]= keep_5( top_5[aa], (bb,cc) )

答案 17 :(得分:0)

这不像

那么简单
 SELECT DISTINCT aa, bb, cc FROM tablename ORDER BY bb DESC LIMIT 5

当然,如果不对数据进行测试,很难说出最快的速度。如果这是你需要快速运行的东西,那么优化数据库以使查询更快,而不是优化查询可能是有意义的。

当然,如果你还需要平面文件,你也可以使用它。