koddla

Yazılımcıları bilgi ile güçlendirir.

Python’da büyük bir dosyanın satır sayısı en ucuz nasıl elde edilir?

Python’daki büyük bir dosyanın (yüz binlerce satır) satır sayısını almamız gerekirse ne yapabiliriz? Hem hafıza hem de zaman açısından en verimli yol nedir?

İlk akla gelen yol aşağıdaki gibi olabilir:

def file_len(fname):
    with open(fname) as f:
        for i, l in enumerate(f):
            pass
    return i + 1

Peki daha iyisini yapmak mümkün mü?


Bundan daha iyisini yapmak biraz zor.

Sonuçta, herhangi bir çözümün tüm dosyayı okuması, kaç tane \n karakteri olduğunu anlaması ve bu sonucu döndürmesi gerekecek.

Tüm dosyayı okumadan bunu yapmanın daha iyi bir yolu muhtemelen yok. Dolayısıyla en iyi çözüm her zaman I /O-hızına bağlı olacak. Yapabileceğiniz en iyi şey gereksiz bellek kullanmadığınızdan emin olmak olacak. Yukarıdaki kod bunu düşünüyor gibi görünüyor.

Yine de farklı yöntemlere de göz atalım.

Öncelikle tek satırda bu işlemi yapabilirdik, bu kod yazımı açısından oldukça ferah olurdu:

num_lines = sum(1 for line in open('myfile.txt'))

Peki performans açısından neler yapabiliriz:

Perfplot analizine göre, arabelleğe alınmış okuma çözümü en hızlısı.

def buf_count_newlines_gen(fname):
    def _make_gen(reader):
        while True:
            b = reader(2 ** 16)
            if not b: break
            yield b

    with open(fname, "rb") as f:
        count = sum(buf.count(b"\n") for buf in _make_gen(f.raw.read))
    return count

Hızlı ve hafıza tasarruflu. Diğer çözümler ise yaklaşık 20 kat daha yavaş.

Grafiği oluşturmak için aşağıdaki kodu kullanabilirsiniz:

import mmap
import subprocess
from functools import partial

import perfplot


def setup(n):
    fname = "t.txt"
    with open(fname, "w") as f:
        for i in range(n):
            f.write(str(i) + "\n")
    return fname


def for_enumerate(fname):
    i = 0
    with open(fname) as f:
        for i, _ in enumerate(f):
            pass
    return i + 1


def sum1(fname):
    return sum(1 for _ in open(fname))


def mmap_count(fname):
    with open(fname, "r+") as f:
        buf = mmap.mmap(f.fileno(), 0)

    lines = 0
    while buf.readline():
        lines += 1
    return lines


def for_open(fname):
    lines = 0
    for _ in open(fname):
        lines += 1
    return lines


def buf_count_newlines(fname):
    lines = 0
    buf_size = 2 ** 16
    with open(fname) as f:
        buf = f.read(buf_size)
        while buf:
            lines += buf.count("\n")
            buf = f.read(buf_size)
    return lines


def buf_count_newlines_gen(fname):
    def _make_gen(reader):
        b = reader(2 ** 16)
        while b:
            yield b
            b = reader(2 ** 16)

    with open(fname, "rb") as f:
        count = sum(buf.count(b"\n") for buf in _make_gen(f.raw.read))
    return count


def wc_l(fname):
    return int(subprocess.check_output(["wc", "-l", fname]).split()[0])


def sum_partial(fname):
    with open(fname) as f:
        count = sum(x.count("\n") for x in iter(partial(f.read, 2 ** 16), ""))
    return count


def read_count(fname):
    return open(fname).read().count("\n")


b = perfplot.bench(
    setup=setup,
    kernels=[
        for_enumerate,
        sum1,
        mmap_count,
        for_open,
        wc_l,
        buf_count_newlines,
        buf_count_newlines_gen,
        sum_partial,
        read_count,
    ],
    n_range=[2 ** k for k in range(27)],
    xlabel="num lines",
)
b.save("out.png")
b.show()

Bir yanıt yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Back to top