逐行读取文件的最快方法是什么?

2024-06-26 13:37:44 发布

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

我已经用Python编写了一段代码来逐行读取文件,并执行一些平均和求和操作

我需要加快速度的建议

目前pressurefile中的行数为945670(将更高)

原始代码 这是我发布的原始版本。根据你的建议,我正在优化代码,最后发布了最新版本

    def time_average():
    try:
        filename = mem.pressurefile
        navg = mem.NFRAMES
        dz = mem.dz
        zlo = mem.zlo
        NZ = mem.NZ
        mass = mem.mass

        dens_fact = amu_to_kg / (mem.slab_V * ang3_to_m3)
        
        array_pxx = np.zeros([NZ,1])
        array_pyy = np.zeros([NZ,1])
        array_pzz = np.zeros([NZ,1])
        array_ndens = np.zeros([NZ,1])
        
        array_density = np.zeros([NZ,1])
        array_enthalpy = np.zeros([NZ,1])
        array_surf_tens = np.zeros([NZ,1])
        
        counter = 0
        with open(filename) as f:
            for line in f:
                line.strip("\n")
                #content = [_ for _ in line.split()]
                content = line.split()
                if len(content) == 7:
                    z = float(content[3]) - zlo
                    pxx = float(content[4])
                    pyy = float(content[5])
                    pzz = float(content[6])
                    
                    loc = math.floor(z/dz)
                    if loc >= NZ:
                        loc = loc - NZ
                    elif loc < 0:
                        loc = loc + NZ   
                    #print(z, loc, zlo)
                    
                    array_pxx[loc] += pxx
                    array_pyy[loc] += pyy
                    array_pzz[loc] += pzz
                    array_ndens[loc] += 1
                counter += 1
        for col in range(NZ):
            array_pxx[col] /= navg
            array_pyy[col] /= navg
            array_pzz[col] /= navg
            array_ndens[col] /= navg
            array_density[col] = mass * dens_fact * array_ndens[col]
            
        return (array_density, array_enthalpy, array_surf_tens)
    except IndexError as err:
        writelog (err)
        writelog(float(content[3]) , loc, zlo)

到目前为止,我已经尝试了以下选项:

剖析:

使用cprofile配置主代码,并确定上述辅助函数对于74.4MB文件需要10秒。对我来说,这10秒是很高的

选项1:cython3

使用cython编译,如下所示

    cython3 --embed -o ptythinfile.c ptythinfile.py

    gcc -Os -I /usr/include/python3.8 -o ptythinfile ptythinfile.c -lpython3.8 -lpthread -lm -lutil -ldl

这并没有带来任何性能改进

选项2:C/C++

将整个代码转换为C/C++并编译

事实上,我的第一个代码是C++,调试是个噩梦,切换到Python。所以,我不想走这条路

选项3:Pypy3

我尝试使用Py3,但遇到了兼容性问题。我有python3.8和3.9,但pypy3一直在寻找3.6,然后我放弃了

选项4:外部C库

我阅读了有关将helper函数编译为c代码并调用python的教程。这将是我的下一次尝试

在谷歌搜索中,我发现了许多选项,如shedskin等。您能否指出优化上述代码片段的最佳方法以及可能的替代解决方案,以加快速度

更新1:2021年10月21日 代码根据以下专家的评论进行更新。测试和工作良好。但是,平均代码执行时间从~10秒减少到~9.4秒

pressurefile的内容是LAMMPS软件的输出,其前几行如下所示:

    ITEM: TIMESTEP
    50100
    ITEM: NUMBER OF ATOMS
    2744
    ITEM: BOX BOUNDS pp pp pp
    -2.5000000000000000e+01 2.5000000000000000e+01
    -2.5000000000000000e+01 2.5000000000000000e+01
    -7.5000000000000000e+01 7.5000000000000000e+01
    ITEM: ATOMS id x y z c_1[1] c_1[2] c_1[3]
    2354 18.8358 -21.02 -70.5731 -21041.8 -3738.18 -2520.84
    1708 5.54312 -8.1526 -62.6984 4362.84 -30610.2 -4065.84

最后两行是我们需要处理的

最新代码

    def time_average():
    try:
        filename = mem.pressurefile
        navg = mem.NFRAMES
        dz = mem.dz
        zlo = mem.zlo
        NZ = mem.NZ
        mass = mem.mass

        dens_fact = amu_to_kg / (mem.slab_V * ang3_to_m3)
        
        array_pxx = np.zeros([NZ,1])
        array_pyy = np.zeros([NZ,1])
        array_pzz = np.zeros([NZ,1])
        array_ndens = np.zeros([NZ,1])
        
        #array_density = np.zeros([NZ,1])
        array_enthalpy = np.zeros([NZ,1])
        array_surf_tens = np.zeros([NZ,1])
        
        counter = 0
        locList = []
        pxxList = []
        pyyList = []
        pzzList = []
        with open(filename) as f:
            for line in f:
                #line.strip("\n")
                #content = [_ for _ in line.split()]
                content = line.split()
                if len(content) == 7:
                    z = float(content[3]) - zlo
                    pxx = float(content[4])
                    pyy = float(content[5])
                    pzz = float(content[6])
                    
                    #loc = math.floor(z/dz)
                    loc = int(z // dz)
                    
                    if loc >= NZ:
                        loc = loc - NZ
                    elif loc < 0:
                        loc = loc + NZ   
                    #print(z, loc, zlo)
                    
                    # Not great but much faster than using Numpy functions
                    locList.append(loc)
                    pxxList.append(pxx)
                    pyyList.append(pyy)
                    pzzList.append(pzz)
                counter += 1

        # Very fast list-to-Numpy-array conversion
        locList = np.array(locList, dtype=np.int32)
        pxxList = np.array(pxxList, dtype=np.float64)
        pyyList = np.array(pyyList, dtype=np.float64)
        pzzList = np.array(pzzList, dtype=np.float64)

        # Fast accumulate
        np.add.at(array_pxx[:,0], locList, pxxList)
        np.add.at(array_pyy[:,0], locList, pyyList)
        np.add.at(array_pzz[:,0], locList, pzzList)
        np.add.at(array_ndens[:,0], locList, 1)

        array_pxx /= navg
        array_pyy /= navg
        array_pzz /= navg
        array_ndens /= navg
        array_density = mass * dens_fact * array_ndens

        return (array_density, array_enthalpy, array_surf_tens)
    except IndexError as err:
        writelog (err)
        print(loc)
        writelog(float(content[3]) , loc, zlo)

测试计算机规格:
英特尔至强(R)W-2255CPU@3.70GHz×20
内存:16 GB
英伟达公司GP107GL[Quadro P620]
64位Ubuntu 20.04.3 LTS

当前平均代码执行时间约为2.6s(3x比原始代码快) 用户@JeromeRichard的信用


Tags: 代码npzeroscontentfloatarraymemloc
2条回答

读取文件的第一步可以通过genfromtxt轻松完成。这会逐行读取文件,将其拆分(就像您所做的那样),将结果收集到列表列表中,并在最后生成数组pandas.read_csv更快,至少在使用c模式时是这样,对于大文件,可能值得一试

生成保留第一列整数性质的结构化数组。通过字段名访问“columns”(如数据类型中所指定):

In [30]: data = np.genfromtxt('stack69665939.py',skip_header=9, dtype=None)
In [31]: data
Out[31]: 
array([(2354, 18.8358 , -21.02  , -70.5731, -21041.8 ,  -3738.18, -2520.84),
       (1708,  5.54312,  -8.1526, -62.6984,   4362.84, -30610.2 , -4065.84)],
      dtype=[('f0', '<i8'), ('f1', '<f8'), ('f2', '<f8'), ('f3', '<f8'), ('f4', '<f8'), ('f5', '<f8'), ('f6', '<f8')])

或以浮点形式加载所有值,生成(N,7)2d数组:

In [32]: data = np.genfromtxt('stack69665939.py',skip_header=9)
In [33]: data
Out[33]: 
array([[ 2.35400e+03,  1.88358e+01, -2.10200e+01, -7.05731e+01,
        -2.10418e+04, -3.73818e+03, -2.52084e+03],
       [ 1.70800e+03,  5.54312e+00, -8.15260e+00, -6.26984e+01,
         4.36284e+03, -3.06102e+04, -4.06584e+03]])

usecols指定为[3,4,5,6]可能会节省一些时间。您似乎只是对以下数据感兴趣:

In [35]: z = data[:,3]
In [36]: pxyz = data[:,[4,5,6]]
In [37]: z
Out[37]: array([-70.5731, -62.6984])
In [38]: pxyz
Out[38]: 
array([[-21041.8 ,  -3738.18,  -2520.84],
       [  4362.84, -30610.2 ,  -4065.84]])

然后,您似乎对z执行了一些操作来派生一个loc,并使用它来组合“pxyz”数组的“行”。我不想再重复了

无论如何,通常在处理大型csv文件时,我们一步读取,然后稍后处理生成的数组或数据帧。阅读时进行处理是可能的,但通常不值得付出努力

首先,Python显然不是高效进行此类计算的最佳工具。代码是顺序的,大部分时间花在CPython解释器操作或Numpy内部函数上

Option 1: cython3
This did not yield any performance improvements.

这部分是因为未启用优化。您需要使用标志-O2甚至-O3。不过,Cython可能帮不了什么忙,因为大部分时间都花在CPython上,在这个特定代码中进行Numpy调用

Option 2: C/C++ Converting entire code to C/C++ and compile it. In fact, my first code was in C++ and debugging was a nightmare and switched to python. So, I don't want to follow this route.

您不需要移植所有代码。您只能重写像这样的性能关键型函数,并将它们放在专用的CPython模块中(即编写C/C++扩展)。但是,此解决方案需要处理低级别的CPython内部构件。Cython有助于处理:AfAIK,可以使用Cython调用Cython函数中的C++函数,Cython帮助轻松地执行Cpython和C++函数之间的接口。简单的函数界面应该有助于使代码更易于阅读和维护。不过,我同意这不是很好,但是C++代码可以比cPython更快地计算这个数量。p>

Searching into the google I found many options like shedskin etc.

谢德斯金不再积极发展。我怀疑这样一个项目在您的情况下能有所帮助,因为代码非常复杂,并且使用Numpy

Numba理论上对这种情况有很大帮助。但是,字符串还没有得到很好的支持(即解析)

Could you point out the best way to optimize the above code snippet and possible alternative solutions to speed it up?

array_pxx[loc] += pxx这样的行非常慢,因为解释器需要在内部调用C Numpy函数,该函数执行许多不必要的操作:绑定/类型检查、类型转换、分配/释放、引用计数等。这样的操作非常慢(比C++慢1000倍以上)。避免这种情况的一个解决方案是在纯Python循环中使用纯Python列表(至少在代码无法有效矢量化时)。您可以高效地将列表转换为Numpy数组,并使用np.add.at执行累加。下面是一个改进的实现:

def time_average():
    try:
        filename = mem.pressurefile
        navg = mem.NFRAMES
        dz = mem.dz
        zlo = mem.zlo
        NZ = mem.NZ
        mass = mem.mass

        dens_fact = amu_to_kg / (mem.slab_V * ang3_to_m3)
        
        array_pxx = np.zeros([NZ,1])
        array_pyy = np.zeros([NZ,1])
        array_pzz = np.zeros([NZ,1])
        array_ndens = np.zeros([NZ,1])
        
        #array_density = np.zeros([NZ,1])
        array_enthalpy = np.zeros([NZ,1])
        array_surf_tens = np.zeros([NZ,1])
        
        counter = 0
        locList = []
        pxxList = []
        pyyList = []
        pzzList = []
        with open(filename) as f:
            for line in f:
                #line.strip("\n")
                #content = [_ for _ in line.split()]
                content = line.split()
                if len(content) == 7:
                    z = float(content[3]) - zlo
                    pxx = float(content[4])
                    pyy = float(content[5])
                    pzz = float(content[6])
                    
                    #loc = math.floor(z/dz)
                    loc = int(z // dz)
                    
                    if loc >= NZ:
                        loc = loc - NZ
                    elif loc < 0:
                        loc = loc + NZ   
                    #print(z, loc, zlo)
                    
                    # Not great but much faster than using Numpy functions
                    locList.append(loc)
                    pxxList.append(pxx)
                    pyyList.append(pyy)
                    pzzList.append(pzz)
                counter += 1

        # Very fast list-to-Numpy-array conversion
        locList = np.array(locList, dtype=np.int32)
        pxxList = np.array(pxxList, dtype=np.float64)
        pyyList = np.array(pyyList, dtype=np.float64)
        pzzList = np.array(pzzList, dtype=np.float64)

        # Fast accumulate
        np.add.at(array_pxx[:,0], locList, pxxList)
        np.add.at(array_pyy[:,0], locList, pyyList)
        np.add.at(array_pzz[:,0], locList, pzzList)
        np.add.at(array_ndens[:,0], locList, 1)

        array_pxx /= navg
        array_pyy /= navg
        array_pzz /= navg
        array_ndens /= navg
        array_density = mass * dens_fact * array_ndens

        return (array_density, array_enthalpy, array_surf_tens)
    except IndexError as err:
        writelog (err)
        print(loc)
        writelog(float(content[3]) , loc, zlo)

在我的机器上,此代码的速度大约快3倍。但是请注意,它应该占用更多内存(由于列表)

剩下的大部分时间都花在字符串转换(25%)、字符串拆分(20-25%)、列表追加(17%)和CPython解释器本身上,就像导入模块一样(20%)。I/O操作只占总时间的一小部分(在SSD上或文件被操作系统缓存时)。只要使用纯Python代码(与CPython一起使用),优化这一点就具有挑战性

相关问题 更多 >