【问题标题】:Asynchronous programming for calculating hashes of files用于计算文件哈希的异步编程
【发布时间】:2019-06-20 12:25:45
【问题描述】:

我正在尝试计算文件的哈希值以检查是否进行了任何更改。 我有 Gui 和其他一些观察者在事件循环中运行。 所以,我决定异步计算文件的哈希值 [md5/Sha1 哪个更快]。

同步码:

import hashlib
import time


chunk_size = 4 * 1024

def getHash(filename):
    md5_hash = hashlib.md5()
    with open(filename, "rb") as f:
        for byte_block in iter(lambda: f.read(chunk_size), b""):
            md5_hash.update(byte_block)
        print("getHash : " + md5_hash.hexdigest())

start = time.time()
getHash("C:\\Users\\xxx\\video1.mkv")
getHash("C:\\Users\\xxx\\video2.mkv")
getHash("C:\\Users\\xxx\\video3.mkv")
end = time.time()

print(end - start)

同步代码输出:2.4000535011291504

异步代码:

import hashlib
import aiofiles
import asyncio
import time


chunk_size = 4 * 1024

async def get_hash_async(file_path: str):
    async with aiofiles.open(file_path, "rb") as fd:
        md5_hash = hashlib.md5()
        while True:
            chunk = await fd.read(chunk_size)
            if not chunk:
                break
            md5_hash.update(chunk)
        print("get_hash_async : " + md5_hash.hexdigest())

async def check():
    start = time.time()
    t1 = get_hash_async("C:\\Users\\xxx\\video1.mkv")
    t2 = get_hash_async("C:\\Users\\xxx\\video2.mkv")
    t3 = get_hash_async("C:\\Users\\xxx\\video3.mkv")
    await asyncio.gather(t1,t2,t3)
    end = time.time()
    print(end - start)

loop = asyncio.get_event_loop()
loop.run_until_complete(check())

异步代码输出:27.957366943359375

我做得对吗?或者,是否需要进行任何更改以提高代码的性能?

提前致谢。

【问题讨论】:

  • 根据物理设备,它们并行读取大文件可能比一个接一个地读取它们要慢得多,因为它需要查找时间。
  • @KlausD。是的,我玩过更改块大小的代码,并发现异步代码越大块大小越快,它与同步代码没有任何区别。
  • 更好地使用线程。现在您正在使用隐藏在async/await 后面的线程池。直接使用concurrent.futures.ThreadPoolExecutor
  • 使用time.process_time(),您可能会获得更有意义的时光。
  • @BlackJack 是的,使用线程不会中断事件循环。但我从少数消息来源听说,在异步编程方法中避免使用线程是最佳实践。由于异步编程的主要目的是避免为处理创建线程。

标签: python hash python-asyncio python-aiofiles


【解决方案1】:

在同步情况下,您按顺序读取文件。按块顺序读取文件速度更快。

在异步情况下,您的事件循环在计算哈希时会阻塞。这就是为什么只能同时计算一个哈希值的原因。 What do the terms “CPU bound” and “I/O bound” mean?

如果你想提高计算速度,你需要使用线程。线程可以在 CPU 上并行执行。增加 CHUNK_SIZE 也应该有所帮助。

import hashlib
import os
import time

from pathlib import Path
from multiprocessing.pool import ThreadPool


CHUNK_SIZE = 1024 * 1024


def get_hash(filename):
    md5_hash = hashlib.md5()
    with open(filename, "rb") as f:
        while True:
            chunk = f.read(CHUNK_SIZE)
            if not chunk:
                break
            md5_hash.update(chunk)
        return md5_hash


if __name__ == '__main__':
    directory = Path("your_dir")
    files = [path for path in directory.iterdir() if path.is_file()]

    number_of_workers = os.cpu_count()
    start = time.time()
    with ThreadPool(number_of_workers) as pool:
        files_hash = pool.map(get_hash, files)
    end = time.time()

    print(end - start)

如果只计算 1 个文件的哈希值:aiofiles 为每个文件使用一个线程。操作系统需要时间来创建线程。

【讨论】:

  • 我的问题不是并行计算多个文件的哈希值。在我的用例中,我通常会在某个时间实例中拥有一个文件,因此上述创建并行计算线程的方法对我没有多大帮助。
  • 如果你使用事件循环并想做一些CPU任务,最好的方法是创建一个线程来计算哈希。
  • 在 Web API 中,对于 CPU 密集型任务,通常的做法是使用单独的工作程序(如 Celery)。在您的情况下,您可以创建一个线程并使用同步库计算哈希。
猜你喜欢
  • 1970-01-01
  • 2016-06-23
  • 2015-12-18
  • 1970-01-01
  • 2011-11-05
  • 2021-06-19
  • 1970-01-01
  • 1970-01-01
  • 2016-12-30
相关资源
最近更新 更多