提高python中函数的性能

时间:2013-03-05 12:23:44

标签: python performance optimization

我有一个几GB的文本文件,格式为

0 274 593869.99 6734999.96 121.83 1,
0 273 593869.51 6734999.92 121.57 1,
0 273 593869.15 6734999.89 121.57 1,
0 273 593868.79 6734999.86 121.65 1,
0 273 593868.44 6734999.84 121.65 1,
0 273 593869.00 6734999.94 124.21 1,
0 273 593868.68 6734999.92 124.32 1,
0 273 593868.39 6734999.90 124.44 1,
0 273 593866.94 6734999.71 121.37 1,
0 273 593868.73 6734999.99 127.28 1,

我有一个简单的函数可以在Windows上的Python 2.7中进行过滤。该函数读取整个文件,选择具有相同idtile(第一列和第二列)的行并返回点列表(x,y,z和标签)和idtile

tiles_id = [j for j in np.ndindex(ny, nx)] #ny = number of row, nx= number of columns
idtile = tiles_id[0]

def file_filter(name,idtile):
        lst = []
        for line in file(name, mode="r"):
            element = line.split() # add value
            if (int(element[0]),int(element[1])) == idtile:
                lst.append(element[2:])
                dy, dx = int(element[0]),int(element[1])
        return(lst, dy, dx)

该文件超过32 GB,瓶颈是文件的读取。我正在寻找一些建议或示例,以加快我的功能(例如:并行计算或其他方法)。

我的解决方案是将文本文件拆分为图块(使用x和y位置)。解决方案并不优雅,我正在寻找一种有效的方法。

10 个答案:

答案 0 :(得分:1)

我建议您更改代码,以便一次读取大文件并为每个tile ID写入(临时)文件。类似的东西:

def create_files(name, idtiles=None):
    files = {}
    for line in open(name):
         elements = line.split()
         idtile = (int(elements[0]), int(elements[1]))
         if idtiles is not None and idtile not in idtiles:
             continue
         if idtile not in files:
             files[idtile] = open("tempfile_{}_{}".format(elements[0], elements[1]), "w")
         print >>files[idtile], line
    for f in files.itervalues():
        f.close()
    return files

create_files()将返回{(tilex, tiley): fileobject}字典。

在写入每一行后关闭文件的变体,以解决“打开太多文件”错误。此变体返回{(tilex, tiley: filename}字典。可能会慢一些。

def create_files(name, idtiles=None):
    files = {}
    for line in open(name):
         elements = line.split()
         idtile = (int(elements[0]), int(elements[1]))
         if idtiles is not None and idtile not in idtiles:
             continue
         filename = "tempfile_{}_{}".format(elements[0], elements[1])
         files[idtile] = filename
         with open(filename, "a") as f:
             print >>f, line
    return files

答案 1 :(得分:1)

你的'idtile似乎按照一定的顺序。也就是说,样本数据表明,一旦你遍历某个“偶像”并击中下一个,就不会再出现具有“idtile”的行。如果是这种情况,一旦你完成了你想要的'idtile'并打了另一个就可以打破for循环。在我的头顶:

loopkiller = false
for line in file(name, mode="r"):
    element = line.split()
    if (int(element[0]),int(element[1])) == idtile:
        lst.append(element[2:])
        dy, dx = int(element[0]),int(element[1])
        loopkiller = true
    elif loopkiller:
        break;

这样,一旦你完成了某种“偶像”,你就会停下来;而在你的例子中,你继续阅读直到文件的结尾。

如果您的偶像以随机顺序出现,也许您可​​以先尝试编写文件的有序版本。

此外,单独评估您的idtile数字可能会帮助您更快地遍历文件。假设你的idtile是一个两位元组的一位数和三位整数,可能是这样的:

for line in file(name, mode="r"):
    element = line.split()
    if int(element[0][0]) == idtile[0]:
        if element[1][0] == str(idtile[1])[0]:
            if element[1][1] == str(idtile[1])[1]:
                if element[1][2] == str(idtile[1])[2]:
                    dy, dx = int(element[0]),int(element[1])
                else go_forward(walk)
            else go_forward(run)
         else go_forward(sprint)
     else go_forward(warp)

答案 2 :(得分:1)

速度的主要规则:少做。

  • 您可以创建huge.txt的排序版本并跟踪idtitle的排名。因此,如果您搜索(223872,23239),您已经知道文件中的哪个位置给定的信息,并且可以跳过之前的所有内容(file.seek)。有人可能会说这个信息有点等于'INDEX'
  • 您可以使用mmap将文件映射到ram。
  • 您可以开始编写“工人”以处理不同的文件/职位
  • 您可以将给定文件转换为类似SQL服务器的内容,并使用标准SQL来检索数据。是的,将32gig数据传输到数据库需要时间,但请将其视为一种预处理。之后,数据库将使用任何方式比您的方法更快地访问数据。

小思想:

  • 您可以使用切片而不是line.split()来避免大量微小的内存分配。

如何使用数据库的概述:

假设您有类似PostgreSQL的内容:

CREATE TABLE tiles
(
  tile_x integer,
  tile_y integer,
  x double precision,
  y double precision,
  z double precision,
  flag integer
);

然后,您可以将输入文件中的所有空格替换为|,将所有,替换为(以创建漂亮而闪亮的.csv)并直接输入进入数据库:

 COPY "tiles" from "\full\path\to\huge.txt" WITH DELIMITER "|";

然后你可以做这样的花哨的东西:

SELECT * FROM "tiles";

tile_x | tile_y |     x     |     y      |   z    | flag
-------+--------+-----------+------------+--------+-----
     0 |    274 | 593869.99 | 6734999.96 | 121.83 |    1
     0 |    273 | 593869.51 | 6734999.92 | 121.57 |    1
     0 |    273 | 593869.15 | 6734999.89 | 121.57 |    1
     0 |    273 | 593868.79 | 6734999.86 | 121.65 |    1
     0 |    273 | 593868.44 | 6734999.84 | 121.65 |    1
     0 |    273 |    593869 | 6734999.94 | 124.21 |    1
     0 |    273 | 593868.68 | 6734999.92 | 124.32 |    1
     0 |    273 | 593868.39 |  6734999.9 | 124.44 |    1
     0 |    273 | 593866.94 | 6734999.71 | 121.37 |    1
     0 |    273 | 593868.73 | 6734999.99 | 127.28 |    1 

或类似的东西:

SELECT * FROM "tiles" WHERE tile_y > 273;

tile_x | tile_y |     x     |     y      |   z    | flag
-------+--------+-----------+------------+--------+-----
     0 |    274 | 593869.99 | 6734999.96 | 121.83 |    1

答案 3 :(得分:1)

您可以将过滤器转换为生成器函数:

def file_filter(name):
        lst = []
        idtile = None
        for line in file(name, mode="r"):
            element = line.split() # add value
            if idtile is None:
               idtile = (int(element[0]), int(element[1]))
            if (int(element[0]), int(element[1])) == idtile:
                lst.append(element[2:])
                dy, dx = int(element[0]),int(element[1])
            else:
                yield lst, dx, dy
                lst = []
                idtile = None

此函数应为每个idtile返回一个list_of_data, dx, dy元组,前提是该文件按idtile排序

新的你可以像这样使用它:

for lst, dx, dy in file_filter('you_name_it'):
    process_tile_data(lst, dx, dy)

答案 4 :(得分:1)

我建议比较你的完整阅读程序所用的时间,以及只读行和不做任何事情的时间。如果那些时间接近,你唯一能做的就是改变方法(拆分文件等),你可以优化的是数据处理时间,而不是文件读取时间。

我还在代码中看到两个值得修复的时刻:

with open(name) as f:
    for line in f:
        pass #Here goes the loop body
  1. 使用with显式关闭您的文件。您的解决方案应该在CPython中运行,但这取决于实现,并且可能不会始终有效。

  2. 您将字符串转换为int两次。这是一个相对缓慢的操作。通过重复使用结果删除第二个。

  3. P.S。它看起来像地球表面上一组点的深度或高度值数组,并且表面在切片中分割。 : - )

答案 5 :(得分:1)

我的解决方案是将大文本文件拆分为每个idtile的许多小二进制文件。要更快地阅读文本文件,您可以使用pandas:

import pandas as pd
import numpy as np
n = 400000 # read n rows as one block
for df in pd.read_table(large_text_file, sep=" ", comment=",", header=None, chunksize=n):
    for key, g in df.groupby([0, 1]):
        fn = "%d_%d.tmp" % key
            with open(fn, "ab") as f:
                data = g.ix[:, 2:5].values
                data.tofile(f)

然后您可以通过以下方式获取一个二进制文件的内容:

np.fromfile("0_273.tmp").reshape(-1, 4)

答案 6 :(得分:1)

您可以通过进行字符串比较来避免在每一行上执行split()int()

def file_filter(name,idtile):
    lst = []
    id_str = "%d %d " % idtile
    with open(name) as f:
        for line in f:
            if line.startswith(id_str):
                element = line.split() # add value
                lst.append(element[2:])
                dy, dx = int(element[0]),int(element[1])
    return(lst, dy, dx)

答案 7 :(得分:1)

以下是一些统计数据。随着更多解决方案的出现,我将更新它。以下程序使用一个文件,该文件包含问题中重复的行。

import sys

def f0(name, idtile):
    lst = []
    dx, dy = None, None
    with open(name) as f:
        for line in f:
            pass


"""def f0(name, idtile):
    lst = []
    dx, dy = None, None
    with open(name) as f:
        for line in f:
            line.split()"""


def f1(name, idtile):
    lst = []
    dx, dy = None, None
    with open(name) as f:
        for line in f:
            element = line.split() # add value
            if (int(element[0]),int(element[1])) == idtile:
                lst.append(element[2:])
                dy, dx = int(element[0]),int(element[1])
    return(lst, dy, dx)


def f2(name, idtile):
    lst = []
    dx, dy = None, None
    with open(name) as f:
        for line in f:
            element = line.split() # add value
            pdx, pdy = int(element[0]),int(element[1])
            if (pdx, pdy) == idtile:
                lst.append(element[2:])
                dy, dx = pdx, pdy
    return(lst, dy, dx)

def f2(name, idtile):
    lst = []
    dx, dy = None, None
    with open(name) as f:
        for line in f:
            element = line.split() # add value
            pdx, pdy = int(element[0]),int(element[1])
            if (pdx, pdy) == idtile:
                lst.append(element[2:])
                dy, dx = pdx, pdy
    return(lst, dy, dx)


def f3(name,idtile):
    lst = []
    id_str = "%d %d " % idtile
    with open(name) as f:
        for line in f:
            if line.startswith(id_str):
                element = line.split() # add value
                lst.append(element[2:])
                dy, dx = int(element[0]),int(element[1])
    return(lst, dy, dx)

functions = [f0, f1, f2, f3]

functions[int(sys.argv[1])]("./srcdata.txt",(0, 273))

用于计时的shell脚本很简单:

#!/bin/sh
time python ./timing.py 0
time python ./timing.py 1
time python ./timing.py 2

我更喜欢以这种方式运行它以避免以前运行的函数影响其他函数的时间。

结果是:

0.02user 0.01system 0:00.03elapsed
0.42user 0.01system 0:00.44elapsed
0.32user 0.02system 0:00.34elapsed
0.33user 0.01system 0:00.35elapsed

好消息:阅读文件不是瓶颈删除额外转移到int是有效的

坏消息:我现在还不知道如何对其进行大幅优化。

答案 8 :(得分:1)

也许最好和禁食是解决你的问题是在(大规模)并行系统上使用map reduce算法。

答案 9 :(得分:0)

好。如果你需要多次这样做,你显然需要创建某种索引。但如果这不是一个频繁的活动,那么最好的选择就是像这样多线程。

NUMWORKERS = 8
workerlist = []
workQ = Queue.Queue()

def file_filter(name,idtile, numworkers):
    for i in range(NUMWORKERS):
        worker = threading.Thread(target=workerThread, args=(lst,))
    lst = []
    for line in file(name, mode="r"):
        workQ.put(line)                
    for i in range(NUMWORKERS):
        workQ.put(None)
    workQ.join()
    return lst , idtile[0], idtile[1]

def workerThread(lst):
    line = workQ.get()
    if not line:
        return
    element = line.split() # add value
    if (int(element[0]),int(element[1])) == idtile:
        lst.append(element[2:])

如果这是每个偶像发生的非常频繁的活动,那么解决方案将完全不同。为一些偶像一起做这件事会给你最好的摊销表现。因为已知的任何数量的idtile都可以在文件上的单个循环中处理。