高级 C++ 快速入门指南#
nvCOMP 提供了一个 C++ 接口,通过在 nvcompManager 对象内部抛出异常并管理状态和临时内存分配,简化了库的使用。
- 高级接口提供以下功能
压缩设置存储在 nvcompManager 对象中
用户可以解压缩 nvCOMP 压缩的缓冲区,而无需知道缓冲区是如何压缩的
nvcompManager 可以自动将单个未压缩的连续缓冲区拆分为块,以允许算法利用可用的并行性
用户可以选择存储和验证未压缩和压缩缓冲区的校验和
要使用 nvCOMP 的 C++ 接口,您需要包含 nvcomp.hpp
以及您将要使用的特定压缩器的头文件。例如,对于 high_level_quickstart_example.cpp
中使用的 LZ4 压缩方案,我们需要包含
#include "nvcomp/lz4.hpp"
#include "nvcomp.hpp"
所有 nvCOMP API 都在 nvcomp
命名空间中声明。为了便于使用,我们建议在适当的范围内指定以下内容
using namespace nvcomp;
下面我们介绍该接口,并总结 nvcompManager 类层次结构的相关成员函数的声明。有关相同功能的完整示例,请查看 high_level_quickstart_example.cpp
。
管理器构造#
用户有两种构造 nvcompManager 的选项。在任何一种情况下,用户都可以指定一个 CUDA 流用于所有 nvcompManager GPU 操作。如果未指定流,则将使用默认流。在以下章节中,假定默认 bitstream_kind = BitstreamKind::NVCOMP_NATIVE
。您可以在 BitstreamKind
中阅读有关其他选项的信息。
1) 从 nvcomp 压缩缓冲区构造#
用户可以使用压缩缓冲区构造管理器。这是推荐的用于解压缩的管理器构造方式,因为它不易出错。
为了使用 create_manager
工厂,用户必须包含 nvcomp/nvcompManagerFactory.hpp
cudaStream_t stream;
CUDA_CHECK(cudaStreamCreate(&stream));
std::shared_ptr<nvcompManagerBase> decomp_nvcomp_manager = create_manager(comp_buffer, stream);
使用此方法的完整示例在 high_level_quickstart_example.cpp
中的 decomp_compressed_with_manager_factory_example
中提供。
2) 直接构造#
在直接构造中,用户必须指定他们希望用于压缩或解压缩的特定压缩器的参数。如果手动指定用于解压缩的管理器,则必须注意确保管理器的配置与用于压缩缓冲区的配置相匹配。
块大小是决定内部分块大小的常用参数。如果给定更大的块大小,某些压缩器可能会提供更高的压缩率。例如,在 LZ4 中,块大小越大,算法可以用来查找匹配项的回溯窗口就越大。
校验和支持#
在管理器构造时,用户还可以指定是否存储和/或验证未压缩和压缩缓冲区的校验和。HLIF 校验和是在 GPU 上使用修改后的 CRC32 算法计算的。应该注意的是,这些校验和旨在用于错误检测,而不是安全性。此外,启用校验和可能会导致相当大的性能损失,具体取决于压缩算法。
完整的示例 high_level_quickstart_example.cpp
中的 comp_decomp_with_single_manager_with_checksums
和 decomp_compressed_with_manager_factory_with_checksums
演示了如何使用 HLIF 校验和。
cudaStream_t stream;
CUDA_CHECK(cudaStreamCreate(&stream));
const int chunk_size = 1 << 16;
nvcompType_t data_type = NVCOMP_TYPE_CHAR;
LZ4Manager nvcomp_manager{chunk_size, data_type, stream};
压缩#
压缩包括两个步骤:配置
,然后是 压缩
。
步骤 1 配置#
配置阶段提供压缩缓冲区的最大大小。它还执行压缩的内部设置。
/**
* @brief Configure the compression of a single buffer.
*
* This routine computes the size of the required result buffer. The result config also
* contains the nvcompStatus* that allows error checking.
*
* @param uncomp_buffer_size The uncompressed input data size (in bytes).
*
* \return CompressionConfig for the size provided.
*/
virtual CompressionConfig configure_compression(
const size_t uncomp_buffer_size) = 0;
步骤 2 压缩#
压缩采用 configure_compression 的结果、一个 const 输入缓冲区和一个结果缓冲区。结果缓冲区应根据 configure_compression 的结果进行分配,其中包括最大可能的压缩大小。
/**
* @brief Perform compression asynchronously for a single buffer.
*
* @param uncomp_buffer The uncompressed input data.
* (a pointer to device continuous memory).
*
* @param comp_buffer The location to output the compressed data to.
* (a pointer to device continuous memory)
* Size requirement is provided in CompressionConfig.
*
* @param comp_config Generated for the current uncomp_buffer with configure_compression.
*
* @param comp_size The location to output size in bytes after compression.
* (a pointer to a single size_t variable on device)
* Optional when bitstream kind is NVCOMP_NATIVE.
*/
virtual void compress(
const uint8_t* uncomp_buffer,
uint8_t* comp_buffer,
const CompressionConfig& comp_config,
size_t* comp_size = nullptr) = 0;
解压缩#
解压缩包括两个步骤:配置,然后是解压缩。
步骤 1 配置#
要配置解压缩,用户有两种选择。
A) 使用压缩缓冲区配置#
如果在解压缩压缩缓冲区时用户没有用于压缩缓冲区的 CompressionConfig,则用户必须使用 configure API。此 API 同步管理器构造时提供的流,因为解压缩需要可能只能在 GPU 上访问的信息。
/**
* @brief Configure the decompression for a single buffer using a compressed buffer.
*
* Synchronizes the user stream.
* - If bitstream kind is NVCOMP_NATIVE, it will parse the header in comp_buffer.
* - If bitstream kind is RAW, it may be required (e.g for LZ4) to parse the whole comp_buffer,
* which could be significantly slower that other options.
* - If bitstream kind is WITH_UNCOMPRESSED_SIZE, it will read the size from the beginning of the comp_buffer.
*
* @param comp_buffer The compressed input data.
* (a pointer to device continuous memory)
*
* @param comp_size Size of the compressed input data. This is required only for RAW format.
* (a pointer to device variable with compressed size)
*
* \return DecompressionConfig for the comp_buffer provided.
*/
virtual DecompressionConfig configure_decompression(
const uint8_t* comp_buffer, const size_t* comp_size=nullptr) = 0;
B) 使用压缩配置配置#
有时,用户将保留用于压缩缓冲区的 CompressionConfig 对象。在这种情况下,DecompressionConfig 可以从 CompressionConfig 构造。由于 CompressionConfig 驻留在主机内存中,因此此配置可以在不同步流的情况下发生。
/**
* @brief Configure the decompression for a single buffer using a CompressionConfig object.
*
* Does not synchronize the user stream.
*
* @param comp_config The config used to compress a buffer.
*
* \return DecompressionConfig based on compression config provided.
*/
virtual DecompressionConfig configure_decompression(
const CompressionConfig& comp_config) = 0;
步骤 2 解压缩#
解压缩利用用户应提供的结果 decomp_buffer
。解压缩缓冲区的大小由之前的配置步骤提供。
/**
* @brief Perform decompression asynchronously of a single buffer.
*
* @param decomp_buffer The location to output the decompressed data to.
* (a pointer to device continuous memory)
* Size requirement is provided in DecompressionConfig.
*
* @param comp_buffer The compressed input data.
* (a pointer to device continuous memory)
*
* @param decomp_config Resulted from configure_decompression given this comp_buffer.
* Contains nvcompStatus* in host/device-accessible memory to allow error checking.
*
* @param comp_size The size of compressed input data passed.
* (a pointer to a single size_t variable on device)
* Optional when bitstream kind is NVCOMP_NATIVE.
*/
virtual void decompress(
uint8_t* decomp_buffer,
const uint8_t* comp_buffer,
const DecompressionConfig& decomp_config,
size_t* comp_size = nullptr) = 0;
批量 API#
管理器还支持多个缓冲区的批量压缩和解压缩。根据函数,您需要传递 std:vector
或 c 样式数组。如果函数接受 c 样式数组,您还需要传递批处理大小。
virtual std::vector<CompressionConfig> configure_compression(
const std::vector<size_t>& uncomp_buffer_sizes) = 0;
virtual std::vector<DecompressionConfig> configure_decompression(
const uint8_t* const * comp_buffers, size_t batch_size, const size_t* comp_sizes = nullptr) = 0;
批量压缩和解压缩的完整示例可以在 high_level_quickstart_example.cpp
中的 multi_comp_decomp_batched
中找到。
比特流类型#
BitstreamKind::RAW
#
如果您想使用类似底层 C 的 API,但又不想管理临时缓冲区,则可以将 BitstreamKind::RAW
传递给管理器构造函数。在这种情况下,chunk_size
和 checksum_policy
参数将被忽略。管理器不会将输入数据拆分为块,您需要自己执行此操作以保持良好的性能,并且如果块大小太大,某些算法可能会失败。不会添加 nvCOMP 标头,该功能可与底层 C API 互操作。由于没有 nvCOMP 标头,您需要将 comp_sizes
传递给大多数管理器函数,以存储和读取压缩块的大小。
使用此方法的完整示例在 high_level_quickstart_example.cpp
中的 multi_comp_decomp_raw
中提供。
BitstreamKind::WITH_UNCOMPRESSED_SIZE
#
某些算法(如 LZ4)不会在其附加到压缩数据的标头内存储输入数据的大小。此值是执行解压缩所必需的,因此为了获得它,我们可能需要执行虚拟解压缩,这将影响性能。在这种情况下,我们建议使用默认的 (BitstreamKind::NVCOMP_NATIVE
) 比特流类型管理器。
但是,如果您想使用类似底层 C 的 API,则可以将 BitstreamKind::WITH_UNCOMPRESSED_SIZE
传递给管理器构造函数。此管理器将类似于使用 BitstreamKind::RAW
创建的管理器,但会添加一个小的标头,其中仅包含压缩缓冲区的原始大小,这可以加快解压缩速度。但是,由于添加了非标准标头,因此此管理器不再与底层 C API 互操作。
HLIF 压缩/解压缩示例 - LZ4#
high_level_quickstart_example.cpp
提供了以下工作示例从参数构造管理器
从压缩缓冲区构造管理器
多个缓冲区的流式压缩和解压缩