使用 Python 进行 NVMe 吞吐量测试

Posted

技术标签:

【中文标题】使用 Python 进行 NVMe 吞吐量测试【英文标题】:NVMe Throughput Testing with Python 【发布时间】:2018-06-26 12:58:49 【问题描述】:

目前我需要做一些吞吐量测试。我的硬件设置是三星 950 Pro 连接到 NVMe 控制器,该控制器通过 PCIe 端口连接到主板。我有一个 Linux nvme 设备,对应于我安装在文件系统某个位置的设备。

我希望使用 Python 来做到这一点。我计划在安装 SSD 的文件系统上打开一个文件,记录时间,将一些 n 长度的字节流写入文件,记录时间,然后使用 os 模块文件操作实用程序关闭文件。这是衡量写入吞吐量的函数。

def perform_timed_write(num_bytes, blocksize, fd):
    """
    This function writes to file and records the time

    The function has three steps. The first is to write, the second is to
    record time, and the third is to calculate the rate.

    Parameters
    ----------
    num_bytes: int
        blocksize that needs to be written to the file
    fd: string
        location on filesystem to write to

    Returns
    -------
    bytes_per_second: float
        rate of transfer
    """
    # generate random string
    random_byte_string = os.urandom(blocksize)

    # open the file
    write_file = os.open(fd, os.O_CREAT | os.O_WRONLY | os.O_NONBLOCK)        
    # set time, write, record time
    bytes_written = 0
    before_write = time.clock()
    while bytes_written < num_bytes:
        os.write(write_file, random_byte_string)
        bytes_written += blocksize
    after_write = time.clock()

    #close the file
    os.close(write_file)

    # calculate elapsed time
    elapsed_time = after_write - before_write

    # calculate bytes per second
    bytes_per_second = num_bytes / elapsed_time


    return bytes_per_second

我的另一种测试方法是使用 Linux fio 实用程序。 https://linux.die.net/man/1/fio

在 /fsmnt/fs1 安装 SSD 后,我使用此作业文件来测试吞吐量

;Write to 1 file on partition
[global]
ioengine=libaio
buffered=0
rw=write
bs=4k
size=1g
openfiles=1

[file1]
directory=/fsmnt/fs1

我注意到Python函数返回的写入速度明显高于fio。因为 Python 是如此高级,所以你放弃了很多控制权。我想知道 Python 是否正在做一些事情来欺骗它的速度。有谁知道为什么 Python 生成的写入速度会比 fio 生成的快很多吗?

【问题讨论】:

os.write() 返回写入的字节数,您应该在每次循环迭代时将其添加到bytes_written。可能有短写。 Python测试与fio测试的相对速度是多少? Python:fio 速度为 4:1。你的建议也被注意到了。我更新了软件来做到这一点,因为你没有保证写入将返回块大小。它可能会失败并返回 0。添加它后,虽然结果看起来相同 我也怀疑非阻塞 I/O 选项正在改变时序的含义。你为什么这样做?如果您对确定设备的实际 I/O 速度感兴趣,您真的希望仅在 I/O 真正完成后才返回 write 调用。就目前而言,您测量的可能更像是非阻塞写入系统调用所花费的时间,FIO 工具可能足够聪明以进行补偿。只是猜测。 也许,但结果或多或少与第一个使用带有 open() 文件描述符的经典实现相同。我这样做是因为我需要一次写入多个分区,我相信我是通过多线程完成的,尽管结果表明不是,但这本身就是另一个问题。 【参考方案1】:

你的 Python 程序比你的 fio 工作做得更好的原因是因为这不是一个公平的比较,而且他们正在测试不同的东西:

你禁止 fio 使用 Linux 的缓冲区缓存(通过使用 buffered=0,这与说 direct=1 相同)通过告诉它执行 O_DIRECT 操作。对于您指定的作业,fio 将必须发送一个 4k 写入,然后等待该写入完成在设备上(并且该确认必须一直返回到 fio)在它之前可以发下一个。

允许您的 Python 脚本在接触 SSD 之前发送可在多个级别缓冲的写入(例如,在 C 库的用户空间内,然后再次在内核的缓冲区缓存中)。这通常意味着写入将被累积并合并在一起,然后再发送到较低级别,从而导致更粗的 I/O 具有更少的开销。此外,由于理论上您没有进行任何显式刷新,因此在程序退出之前不必将 I/O 发送到磁盘(实际上这将取决于许多因素,例如您执行多少 I/O、数量RAM Linux 可以留出缓冲区、文件系统保存脏数据的最长时间、您执行 I/O 的时间等)!你的os.close(write_file) 将变成fclose() which says this in its Linux man page:

注意 fclose() 只刷新 C 库提供的用户空间缓冲区。为确保数据物理存储在磁盘上,内核缓冲区也必须刷新,例如使用 sync(2) 或 fsync(2)。

事实上,您在调用 os.close() 之前会花费最后时间,因此您甚至可能会忽略将最终“批次”数据仅发送到内核所花费的时间,更不用说 SSD 了!

你的 Python 脚本更接近这个 fio 工作:

[global]
ioengine=psync
rw=write
bs=4k
size=1g

[file1]
filename=/fsmnt/fio.tmp

即使使用这个 fio 仍然处于劣势,因为您的 Python 程序有用户空间缓冲(所以bs=8k 可能更接近)。

关键是你的 Python 程序并没有真正测试你指定块大小的 SSD 速度,而且你原来的 fio 工作有点奇怪,受到严格限制(libaio ioengine 是异步的,但深度为 1 你'在我们到达behaviour of Linux AIO when using filesystems) 并对您的 Python 程序做不同的事情之前,我们将无法从中受益。如果与最大缓冲区的大小相比,你没有做更多的缓冲 I/O(在 Linux 上,内核的缓冲区大小与 RAM 成比例)并且如果缓冲 I/O 很小,那么练习就变成了有效性的演示缓冲。

【讨论】:

【参考方案2】:

如果您需要 NVMe 设备的确切性能,fio 是最佳选择。 FIO 可以直接将测试数据写入设备,无需任何文件系统。这是一个例子:

[global]
ioengine=libaio
invalidate=1
iodepth=32
time_based
direct=1
filename=/dev/nvme0n1

[write-nvme]
stonewall
bs=128K
rw=write
numjobs=1
runtime=10000

SPDK 是另一种选择。 https://github.com/spdk/spdk/tree/master/examples/nvme/perf 有一个性能测试示例。

Pynvme,它基于 SPDK,是一个 Python 扩展。您可以使用其 ioworker() 编写性能测试。

【讨论】:

以上是关于使用 Python 进行 NVMe 吞吐量测试的主要内容,如果未能解决你的问题,请参考以下文章

为啥对 NVMe SSD 上的单个文件进行并发随机写入不会导致吞吐量增加?

使用iPerf进行网络吞吐量测试

使用iperf进行设备吞吐量测试

NVMe - NB的特性

evpp性能测试: 与muduo进行吞吐量测试

evpp性能测试: 与muduo进行吞吐量测试