最初的 cuSpatial C++ API (libcuspatial) 设计上依赖于 RAPIDS libcudf 并使用其核心数据类型,特别是 cudf::column
。对于不使用 libcudf 或其他 RAPIDS API 的用户来说,依赖 libcudf 可能是采用 libcuspatial 的一大障碍。libcudf 是一个非常大的库,构建它需要大量时间。
因此,cuSpatial 的核心现在实现为一个不依赖 libcudf 的独立 C++ API。这是一个基于迭代器和范围的头文件模板 API。这有许多优点。
这种 API 的主要缺点是
好消息是,通过将现有的基于 libcudf 的 C++ API 作为头文件 libcuspatial API 之上的一个层来维护,可以避免列式 API 用户的第 1 个问题和第 2 个问题。
以下是 cuspatial::haversine_distance
的基于迭代器的 API 示例。(关于 API 文档的讨论请参见下文。)
有几个关键点需要注意。
std::transform
等 STL 算法非常相似。cuspatial::vec_2d
类型(include/cuspatial/vec_2d.hpp)作为结构体数组传递。这通过函数体中的 static_assert
来强制执行(稍后讨论)。Location
类型是一个模板,默认等于输入迭代器的 value_type
。T
),默认等于 Location
的 value_type
。a_lonlat_first
和 a_lonlat_last
)。这模仿了 STL API。std::transform
的启发,尽管与 transform
一样,haversine_distance
的许多用途不需要此返回的迭代器。以下是上述 cuspatial::haversine_distance
的 (Doxygen) 文档。
/** * @brief Compute haversine distances between points in set A to the corresponding points in set B. * * Computes N haversine distances, where N is `std::distance(a_lonlat_first, a_lonlat_last)`. * The distance for each `a_lonlat[i]` and `b_lonlat[i]` point pair is assigned to * `distance_first[i]`. `distance_first` must be an iterator to output storage allocated for N * distances. * * Computed distances will have the same units as `radius`. * * https://en.wikipedia.org/wiki/Haversine_formula * * @param[in] a_lonlat_first: beginning of range of (longitude, latitude) locations in set A * @param[in] a_lonlat_last: end of range of (longitude, latitude) locations in set A * @param[in] b_lonlat_first: beginning of range of (longitude, latitude) locations in set B * @param[out] distance_first: beginning of output range of haversine distances * @param[in] radius: radius of the sphere on which the points reside. default: 6371.0 * (approximate radius of Earth in km) * @param[in] stream: The CUDA stream on which to perform computations and allocate memory. * * @tparam LonLatItA Iterator to input location set A. Must meet the requirements of * [LegacyRandomAccessIterator][LinkLRAI] and be device-accessible. * @tparam LonLatItB Iterator to input location set B. Must meet the requirements of * [LegacyRandomAccessIterator][LinkLRAI] and be device-accessible. * @tparam OutputIt Output iterator. Must meet the requirements of * [LegacyRandomAccessIterator][LinkLRAI] and be device-accessible. * @tparam Location The `value_type` of `LonLatItA` and `LonLatItB`. Must be `cuspatial::vec_2d<T>`. * @tparam T The underlying coordinate type. Must be a floating-point type. * * @pre `a_lonlat_first` may equal `distance_first`, but the range `[a_lonlat_first, a_lonlat_last)` * shall not overlap the range `[distance_first, distance_first + (a_lonlat_last - a_lonlat_last)) * otherwise. * @pre `b_lonlat_first` may equal `distance_first`, but the range `[b_lonlat_first, b_lonlat_last)` * shall not overlap the range `[distance_first, distance_first + (b_lonlat_last - b_lonlat_last)) * otherwise. * @pre All iterators must have the same `Location` type, with the same underlying floating-point * coordinate type (e.g. `cuspatial::vec_2d<float>`). * * @return Output iterator to the element past the last distance computed. * * [LinkLRAI]: https://cppreference.cn/w/cpp/named_req/RandomAccessIterator * "LegacyRandomAccessIterator" */
关键点
@pre
将要求记录为前置条件。这是现有的 API,重构后未改变。以下是现有的 cuspatial::haversine_distance
关键点
cudf::column_view
。这是一个类型擦除容器,因此必须在运行时确定数据类型。unique_ptr<cudf::column>
。detail
版本 API。这遵循 libcudf 的做法,将来可能会改变。libcuspatial API 应定义在 cpp/include/cuspatial/
目录中的头文件中。API 头文件应以 API 命名。示例中,haversine.hpp
定义了 cuspatial::haversine_distance
API。
实现也必须在头文件中,但应位于 cuspatial/detail
目录中。实现应从 API 定义文件中包含,放在文件末尾。示例:
公共 API 在 cuspatial
命名空间中。请注意,头文件 API 和基于 libcudf 的 API 可以存在于同一个命名空间中,因为它们没有歧义(参数非常不同)。
头文件 API 的实现应在 cuspatial::detail
命名空间中。
主要实现应在 detail 头文件中。
由于它是静态类型 API,头文件实现可以比基于 libcudf 的 API 简单得多,后者需要运行时类型分派。对于 haversine_distance
,只需几个静态断言和动态期望检查,然后调用带自定义转换 functor 的 thrust::transform
。
注意,我们使用 static_assert
断言迭代器输入的类型与文档中期望的相匹配。我们还进行运行时检查,确保半径为正。最后,我们只需调用 thrust::transform
,并将 haversine_distance_functor
的实例传递给它,这是一个接受两个 vec_2d<T>
输入并实现 Haversine 距离公式的函数。
重构的实质是将基于 libcudf 的 API 作为头文件 API 的包装器。这主要包括将类型分派 functor 中的业务逻辑实现替换为对头文件 API 的调用。我们还需要将分散的纬度和经度输入转换为 vec_2d<T>
结构体。这可以使用 type_utils.hpp
中提供的 cuspatial::make_vec_2d_iterator
实用程序轻松完成。
因此,要重构基于 libcudf 的 API,我们删除以下代码。
并替换为以下代码。
现有的基于 libcudf 的 API 测试基本可以保持不变。应该添加新的测试以单独测试头文件 API,以防基于 libcudf 的 API 被移除。
请注意,测试与头文件 API 一样,不应依赖 libcudf 或 libcudf_test。基于 cuDF 的 API 犯了依赖 libcudf_test 的错误,这导致当 libcudf_test 改变时 cuSpatial 有时会中断。