Vramfs:基于 Vram 的 Linux 文件系统
Overv/vramfs
未使用的 RAM 是浪费的 RAM,所以为什么不让你的显卡中的一些 VRAM 工作起来呢?
vramfs 是一个实用程序,它使用 FUSE library 在 VRAM 中创建一个文件系统。这个想法与 ramdisk 非常相似,只不过它使用独立显卡的显存来存储文件。它不用于严肃用途,但它实际上工作得相当好,特别是现在有 4GB 或更多 VRAM 的消费级 GPU。
在开发者的系统上,连续读取性能约为 2.4 GB/s,写入性能为 2.0 GB/s,约为使用 ramdisk 可实现性能的 1/3。对于一个并非为大量数据传输到主机而设计的设备来说,这已经足够好了,但未来的开发应该旨在更接近 PCI-e 带宽限制。有关更多信息,请参阅“benchmarks”部分。
要求
- Linux 内核 2.6+
- FUSE 开发文件
- 支持 OpenCL 1.2 的显卡
构建
首先,安装显卡的 OpenCL 驱动程序,并通过运行 clinfo 验证它是否被识别为 OpenCL 设备。然后安装 libfuse3-dev 包或从源代码构建它。您还需要 pkg-config 和 OpenCL 开发文件(opencl-dev,opencl-clhpp-headers 包或等效项),且 OpenCL 标头至少为 1.2 版本。
只需运行 make 即可构建 vramfs。
如果您想使用 valgrind 进行调试,则应使用最小的伪 OpenCL 实现进行编译,以避免屏幕上出现由 OpenCL 驱动程序引起的警告:
- valgrind:
make DEBUG=1
挂载
通过运行 bin/vramfs <mountdir> <size> 挂载磁盘。mountdir 可以是任何空目录。size 是磁盘大小,以字节为单位。有关更多信息,请运行不带参数的 bin/vramfs。
建议的 vramdisk 最大大小是 VRAM 的 50%。如果超过这个值,您的驱动程序或系统可能会变得不稳定,因为它必须开始交换。例如,Chrome 中的网页将停止正常渲染。
如果磁盘已经有一段时间没有活动,显卡可能会降低其内存时钟,这意味着它需要一秒钟才能再次加速。
实现
FUSE library 用于将 vramfs 实现为用户空间文件系统。这简化了开发,并使使用诸如 OpenCL 之类的 API 变得简单。
基本架构
程序启动时,它会检查是否具有支持 OpenCL 的 GPU,并尝试分配指定的内存量。分配内存后,将创建根条目对象,并存储对其的全局引用。
然后,FUSE 将诸如 stat、readdir 和 write 之类的调用转发到文件系统函数。然后,这些函数将使用指定的路径通过根条目找到该条目。然后将在条目对象上执行所需的操作。如果该条目是文件对象,则该操作可能会导致 OpenCL cvEnqueueReadBuffer 或 cvEnqueueWriteBuffer 调用来操作数据。
创建或打开文件时,将创建一个 file_session 对象,以存储对文件对象的引用以及在 fopen 和 fclose 调用之间持久存在的任何其他数据。
VRAM 块分配
OpenCL 用于通过创建缓冲区对象来分配显卡上的内存块。挂载新磁盘时,将创建一个 disk size / block size 缓冲区池并将其初始化为零。这不仅是一种好的做法,而且对于某些 OpenCL 驱动程序来说,还需要检查块所需的 VRAM 实际上是否可用。不幸的是,Nvidia 显卡不支持 OpenCL 1.2,这意味着必须通过从预先分配的填充零的缓冲区进行复制来模拟 cvEnqueueFillBuffer 调用。有点有趣的是,在支持两者的显卡上,它似乎并没有对性能产生影响。
对块的写入通常是异步的,而读取是同步的。幸运的是,OpenCL 默认保证命令的按顺序执行,这意味着块的读取将等待写入完成。OpenCL 1.1 是完全线程安全的,因此在发送命令时不需要特别注意。
块对象使用 shared_ptr 进行管理,以便它们可以在解构时自动将自身重新插入到池中。
文件系统
文件系统是由 entry_t 对象组成的树,这些对象具有诸如父目录、模式和访问时间之类的属性的成员。每种类型的条目都有其自己的派生自它的子类:file_t、dir_t 和 symlink_t。实现所有 FUSE 回调的主文件具有对根目录条目的永久引用。
file_t 类包含额外的 write、read 和 size 方法,并管理用于存储文件数据的块。
dir_t 类具有额外的 unordered_map,它将名称映射到 entry_t 引用,以便使用其成员函数 find 进行快速子查找。
最后,symlink_t 类具有一个额外的 target 字符串成员,用于存储符号链接的指针。
所有条目对象也使用 shared_ptr 进行管理,以便在取消链接对象且没有进程再持有对其的文件句柄时,自动释放对象及其数据(例如,文件块)。这也可用于以后轻松实现硬链接。
这些类使用 getter/setter 函数来在适当的时候自动更新访问、修改和更改时间。例如,调用 dir_t 的 children 成员函数会更改目录的访问时间和更改时间。
线程安全
不幸的是,大多数操作都不是线程安全的,因此所有 FUSE 回调都共享一个互斥锁,以确保一次只有一个线程在改变文件系统。例外的是 read 和 write,它们会在等待读取或写入完成时暂时释放锁。
基准测试
用于测试的系统具有以下规格:
- OS: Ubuntu 14.04.01 LTS (64 位)
- CPU: Intel Core i5-2500K @ 4.0 Ghz
- RAM: 8GB DDR3-1600
- GPU: AMD R9 290 4GB (Sapphire Tri-X)
通过为每个新大小创建一个新的 2GiB 磁盘并读取/写入一个 2GiB 文件,测量了连续读取、写入和写入+同步的性能(针对不同的块分配大小)。
使用以下命令创建磁盘:
bin/vramfs /tmp/vram 2G
并使用 dd 命令写入和读取文件:
# write
dd if=/dev/zero of=/tmp/vram/test bs=128K count=16000
# write+sync
dd if=/dev/zero of=/tmp/vram/test bs=128K count=16000 conv=fdatasync
# read
dd if=/tmp/vram/test of=/dev/null bs=128K count=16000
对于每个块大小,重复这些命令 5 次,然后进行平均,以生成图中所示的结果。由于驱动程序无法分配那么多 OpenCL 缓冲区,因此无法测试低于 32KiB 的块大小。将来可能会通过使用子缓冲区来解决此问题。
虽然 128KiB 块提供了最高的性能,但由于空间开销较低,因此 64KiB 可能是更可取的。
未来的想法
- 为 SLI/Crossfire 设置实现 RAID-0
许可证
The MIT License (MIT)
Copyright (c) 2014 Alexander Overvoorde
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.