【问题标题】:读取大文件使用 100% 内存,我的整个电脑死机
【发布时间】:2022-01-19 12:52:47
【问题描述】:

我制作了一个简单的应用程序来加密和解密文件。但是当我加载像 2gb 这样的大文件时,我的程序会使用 100% 的内存。我使用多处理和多线程。

poolSize = min(cpu_count(), len(fileList))
process_pool = Pool(poolSize)
thread_pool = ThreadPool(len(fileList))

lock = Lock()
worker = partial(encfile, process_pool, lock)

thread_pool.map(worker, fileList)
def encfile(process_pool, lock, file):
    with open(file, 'rb') as original_file:
        original = original_file.read()

    encrypted = process_pool.apply(encryptfn, args=(key, original,))

    with open (file, 'wb') as encrypted_file:
        encrypted_file.write(encrypted)

【问题讨论】:

标签: python multithreading memory multiprocessing


【解决方案1】:

这是我的总体思路:

由于内存是个问题,您必须以较小的块读取文件,例如 64K 块,然后加密每个 64K 块并将其写出。当然,加密块的长度不是 64K,所以问题就变成了如何解密。因此,每个加密块必须以一个固定长度的标头作为前缀,该标头只不过是编码为 4 字节无符号整数的以下加密块的长度(这应该绰绰有余)。解密算法循环首先读取下一个 4 字节长度,然后从中知道接下来的加密块有多少字节。

顺便说一句,如果您不使用 encfile lock 来计算已处理的文件数,则无需传递给它。

from tempfile import mkstemp
from os import fdopen, replace


BLOCKSIZE = 64 * 1024
ENCRYPTED_HEADER_LENGTH = 4

def encfile(process_pool, lock, file):
    """
    Encrypt file in place.
    """

    fd, path = mkstemp() # make a temporary file

    with open(file, 'rb') as original_file, \
    fdopen (fd, 'wb') as encrypted_file:
        while True:
            original = original_file.read(BLOCKSIZE)
            if not original:
                break
            encrypted = process_pool.apply(encryptfn, args=(key, original))
            l = len(encrypted)
            l_bytes = l.to_bytes(ENCRYPTED_HEADER_LENGTH, 'big')
            encrypted_file.write(l_bytes)
            encrypted_file.write(encrypted)
    replace(path, file)


def decfile(file):
    """
    Decrypt files in place.
    """

    fd, path = mkstemp() # make a temporary file

    with open(file, 'rb') as encrypted_file, \
    fdopen (fd, 'wb') as original_file:
        while True:
            l_bytes = encrypted_file.read(ENCRYPTED_HEADER_LENGTH)
            if not l_bytes:
                break
            l = int.from_bytes(l_bytes, 'big')
            encrypted = encrypted_file.read(l)
            decrypted = decryptfn(key, encrypted)
            original_file.write(decrypted)
    replace(path, file)

说明

块大小越大,需要的内存就越多(您的原始程序读取整个文件;该程序一次只能读取 64K)。但是我假设块大小太小会导致对加密的调用过多,这是由多处理完成的,这将需要更多的 CPU 开销——所以这是一个权衡。 64K 是任意的。如果你有记忆,增加很多。您甚至可以尝试 1024 * 1024 (1M)。

我之前试图解释这一点,但让我详细说明一下:

假设当您加密一个 64K 块时,该特定 64K 块的加密大小最终为 67,986 字节长(不同的 64K 块加密通常具有不同的长度,除非其未加密的值恰好相同)。如果我只是写出没有其他信息的数据,我需要某种方法来知道要解密文件,首先需要读回 67,986 字节的数据并将其传递给解密方法(使用正确的密钥,当然)因为您必须解密加密内容的精确结果,不能少也不能更大的字节。换句话说,您不能只读取任意块中的加密文件并将这些块传递给解密方法。但那会是什么呢?因此,了解每个加密块有多大的唯一方法是在这些块前面加上一个标头,该标头给出下一个块的长度。

l_bytes = l.to_bytes(ENCRYPTED_HEADER_LENGTH, 'big') 获取存储在变量 l 中的整数长度,并将其编码为大小为 ENCRYPTED_HEADER_LENGTH 的字节数组,以“大端”顺序表示字节从高位字节到低位字节排列:

>>> ENCRYPTED_HEADER_LENGTH = 4
>>> l = 67986
>>> l_bytes = l.to_bytes(ENCRYPTED_HEADER_LENGTH, 'big')
>>> l_bytes
b'\x00\x01\t\x92'
>>> l_bytes = l.to_bytes(ENCRYPTED_HEADER_LENGTH, 'little')
>>> l_bytes
b'\x92\t\x01\x00'
>>>

\t 是制表符,其值为\x09,因此我们将写出 0010992,它是 67986 的 4 字节十六进制值

【讨论】:

  • 运行后文件大小为0字节。
  • 加密文件还是解密文件?您是否尝试过使用 print 语句对其进行调试以查看失败的地方?
  • 我看到了问题。我会在几分钟内修复它。
  • 查看更新的代码。
  • 我已经为答案添加了更新。
猜你喜欢
  • 1970-01-01
  • 2021-11-19
  • 2012-03-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-01
  • 2020-01-26
  • 1970-01-01
相关资源
最近更新 更多