高级 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_checksumsdecomp_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_sizechecksum_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 提供了以下工作示例
  • 从参数构造管理器

  • 从压缩缓冲区构造管理器

  • 多个缓冲区的流式压缩和解压缩