Lapack: 允许在标准 index-32 库旁边安装 index-64 库吗?

创建于 2020-11-01  ·  41评论  ·  资料来源: Reference-LAPACK/lapack

目前 debian 有(一些)单独的 blas64 和 cblas64 头文件(虽然不是来自参考实现)。

关于参考 index64 API(它们来自 blis 库),我不确定它们是否正确。

是否可以向 cmake 添加一个选项,例如BUILD_INDEX64 ,默认为OFF ,但如果已打开,它将创建索引 64 库?
如果我为这样的选项做 PR,它会被视为一种可能性吗?

我考虑过允许它与标准安装共存的一些事情 - 将库命名为libblas64.so, libcblas64.so, liblapack64.so, liblapacke64.so ,这样库名之间就没有冲突(当然,你不能链接同时使用 libblas 和 libblas64)。
此外,该库需要编译两次,一次用于 index32,一次用于 index64(但这是完全正常的情况,而不是破坏交易)。
我想不出好的解决方法的唯一冲突是头文件名。
如果遵循 debians 风格,那么调用以 64 结尾的 c 头文件可能是明智的。
(我正在为 Gentoo 维护这一点,并希望使生态系统与 debian 非常接近,以便开发人员在系统之间切换时遇到的问题最少)

在我提出 PR 之前,我愿意接受任何建议 :heart:

谢谢,
爱莎

Build System Enhancement

所有41条评论

嗨,Aisha,这对我来说很有意义,但让我们看看是否有其他人的反馈。 所以让我们等几天。 J

最肯定的是,这听起来是一个不错的计划。

天啊@langou
你真快:heart:

为了完整起见,我正在写下我们仍然必须做的事情:

  • 弄清楚如何命名标头,以便 32 位 API 可以与 64 位 API 共存
  • 修复 printf/fprintf 语句,以便它们使用正确的限定符进行打印。

欢迎任何解决第一点的建议,不幸的是,我没有“干净”的解决方案。

几个问题,这将帮助我处理文件的命名

  • cblas_f77.hcblas_test.h之间似乎存在大量重复定义。 我们真的需要那个吗?
  • cblas_test.h需要安装吗? 鉴于其名称(以及使用它的文件),我认为它仅在测试阶段使用。 也许我们不应该在系统范围内安装这个文件?

嗨@epsilon-0,

为了允许它与标准安装共存,我想到了一些事情 - 将库命名为 libblas64.so、libcblas64.so、liblapack64.so、liblapacke64.so,这样库名称之间就没有冲突(当然,您不能同时链接 libblas 和 libblas64)。

您可能正在寻找 PR #218。 该 PR 的作者是来自 Fedora 项目的 Björn Esser。

嗨@epsilon-0。 #462 这个问题解决了吗?

@weslleyspereira不,这还没有完成。
需要完成更多的标头重命名/处理。
接下来的几周我很忙,所以我不能很快做到这一点。
基本概要

  • 头文件应该被称为cblas.hcblas64.h ,对于其他头文件也是如此

    • 这意味着*.c文件需要稍微调整以包含正确的标头,但这仅在构建期间进行,因此可以将其删除。

  • cmake 文件应安装在lapack64cblas64等下。

好的我明白了。 感谢您的快速跟进!

我在尝试用 pkgsrc 打包东西时遇到了类似的问题。 我想要一个完整的参考安装,包括 cblas 和 lapacke。 对于同时安装的不同实现,我选择了不同的库名称和标题的子目录,例如

/usr/lib/libopenblas.so
/usr/lib/libopenblas64.so
/usr/lib/libblas.so
/usr/lib/libcblas.so
/usr/lib/libblas64.so
/usr/lib/libcblas64.so
/usr/include/openblas/cblas.h
/usr/include/openblas64/cblas.h
/usr/include/netlib/cblas.h
/usr/include/netlib64/cblas.h
/usr/include/cblas.h -> netlib/cblas.h (for compatibility, having the default)

(等等)

我们不像二进制发行版那样考虑运行时切换,所以如果每个 cblas.h(和 lapacke.h)都特定于其匹配的库,就像 libopenblas 的额外名称一样。 构建时间选择通过

BLAS_INCLUDES=-I/prefix/include/netlib64
BLAS_LIBS=-lblas64
CBLAS_LIBS=-lcblas64

(等等)这就是 .pc 文件应该说的,这比传达不同的头文件名要容易得多。 他们在这方面还不一致,但我正在修复它。 似乎到目前为止人们只是在他们的发行版中破解了它,如果根本不关心所有参考库。

不过,我对这些标题有一个疑问。

我正在破解 cmake 构建以分别构建每个组件,并正在尝试其他一些修复(请参阅 https://github.com/Reference-LAPACK/lapack/pull/556)。 我得到的 libblas.so 和 libblas64.so 库构建得很好,我配置了头文件……但是安装的 cblas.h 和 lapacke.h 对于 32 位和 64 位索引版本是相同的。 这与 openblas 不一致:在那里,我在 netlib 构建中没有看到一个重要的区别:

diff -ruN /data/pkg/include/openblas/openblas_config.h /data/pkg/include/openblas64/openblas_config.h
--- /data/pkg/include/openblas/openblas_config.h    2021-06-03 19:03:53.000000000 +0200
+++ /data/pkg/include/openblas64/openblas_config.h  2021-06-03 19:13:36.000000000 +0200
@@ -44,6 +44,7 @@
 #define OPENBLAS_DLOCAL_BUFFER_SIZE 32768
 #define OPENBLAS_CLOCAL_BUFFER_SIZE 16384
 #define OPENBLAS_ZLOCAL_BUFFER_SIZE 12288
+#define OPENBLAS_USE64BITINT 
 #define OPENBLAS_GEMM_MULTITHREAD_THRESHOLD 4
 #define OPENBLAS_VERSION " OpenBLAS 0.3.15 "
 /*This is only for "make install" target.*/

对于参考库,来自 32 位和 64 位索引构建的所有头文件都是相同的,显然用户应该将
-DWeirdNEC在 cblas.h 和-DLAPACK_ILP64 -DHAVE_LAPACK_CONFIG_H的标志中(30 年前可能很有趣)。 由于人们在生产中使用优化的 BLAS 库,事实上的标准是不将其暴露给用户。 这些反馈对参考,恕我直言,以及从 ILP64 构建安装的标头不应需要时髦的标志,以避免在链接到 64 位库时使您的应用程序崩溃。

我们是否同意在构建时修改标头以定义正确的整数是正确的解决方案?

顺便说一句,安装的 cblas 配置文件也错过了对必要 defs 的任何引用,因此似乎在 64 位索引构建中被破坏了。 但实际上,我想根本不安装这些。 它们与 .pc 文件是多余的,并且可能更难说服使用 cmake 的依赖包通过BLAS_LIBS等方式接受打包者选择。

PS:使用 Intel MKL,需要设置一个中央开关-DMKL_ILP64 。 我想设置微不足道
include/intel-mkl64/cblas.h

#ifndef MKL_ILP64
#define MKL_ILP64
#endif
#include <mkl_cblas.h>

以适应一般方案。 我也可以将定义放入 BLAS_INCLUDES,对于奇怪的 netlib 定义也是如此。 什么是更好的? 我们是想像英特尔那样做,还是像 OpenBLAS 那样做?

我们是否同意在构建时修改标头以定义正确的整数是正确的解决方案?

是的。 我同意这一点,并且更喜欢不复制整个标题的解决方案。 我认为它更干净。

顺便说一句,安装的 cblas 配置文件也错过了对必要 defs 的任何引用,因此似乎在 64 位索引构建中被破坏了。

对。 我刚刚安装了 64 位库 (BUILD_INDEX64=ON) 并且看不到任何告诉我使用WeirdNECLAPACK_ILP64HAVE_LAPACK_CONFIG_H 。 感谢您注意到这一点!

是的。 我同意这一点,并且更喜欢不复制整个标题的解决方案。 我认为它更干净。

这对我来说是模棱两可的。 哪个是更清洁的解决方案? 我现在准备的是这样的:

#if defined(WeirdNEC) || @HAVE_ILP64@
   #define CBLAS_INDEX long
   #ifndef WeirdNEC
   #define WeirdNEC
   #endif
#else
   #define CBLAS_INDEX int
#endif

CMakeFile 应将 HAVE_ILP 替换为 1 或 0,为当前构建安装生成的头文件。

(顺便说一句:long 在 Windows 上不起作用。它很长很长……或者 int64_t 在所有带有 stdint 的平台上。)

对。 我刚刚安装了 64 位库 (BUILD_INDEX64=ON) 并且看不到任何告诉我使用WeirdNECLAPACK_ILP64HAVE_LAPACK_CONFIG_H 。 感谢您注意到这一点!

我在想象你所做的未来

cc -I/foo/include/netlib64 -o bar bar.c -L/foo/lib -lcblas64

事情在 foo/include/netlib64/cblas.h 中处理,否则由 foo/include/netlib/cblas.h(可能链接到 foo/include/cblas.h)处理。

我怀疑这_不是_你的意思,但我想说服它更好;-)

您可以尝试通过在 /foo/include/cblas.h 中放置 'the' 头文件来避免复制头文件,并让 /foo/include/netlib64/cblas.h 仅通过定义 WeirdNEC 来包含该头文件,但这意味着 64 bit 和 32 位包共享通用的头文件,这对于打包来说很麻烦。 如果每个人都将其文件放在不同的位置/名称中,那就更好了。 该名称需要保留 cblas.h,因为您不想到处替换#include <cblas.h>行。

编辑:另外,让 cbl​​as.h 包含 ../cblas.h 本身就很混乱。 我们还为 cmake 定义了 _one_ 头文件安装目录。 默认情况下是 /foo/include,而不是 /foo/netlib64/include。 我不会改变这个默认值。 打包者必须像这样指定子目录(BSD make in pkgsrc):

.if !empty(LAPACK_COMPONENT:M*64)
.  if empty(MACHINE_ARCH:M*64)
PKG_FAIL_REASON+=       "${LAPACK_COMPONENT} incompatible with non-64-bit platform"
.  endif
HEADERDIR=netlib64
.else
HEADERDIR=netlib
.endif

# Note: We patch the build to install both static and
# shared libraries.
CMAKE_ARGS=     -DBUILD_DEPRECATED=ON \
                -DBUILD_SHARED_LIBS=ON \
                -DBUILD_STATIC_LIBS=ON \
                -DCMAKE_INSTALL_INCLUDEDIR=${PREFIX}/include/${HEADERDIR} \
                ${LAPACK_COMPONENT_CMAKE_ARGS}

将 32 位 cblas.h 修改为通常的位置后,运送/安装 32 位 cblas.h 的一个美丽方面是原始机制仍然有效。 只有 64 位变体会强制执行 WeirdNEC。 您可以决定只将 64 位安装到前缀中,而保持生态系统的其他部分不变。

哦,拜托…… CBLAS/cmake/cblas-config-install.cmake.in 似乎忘记了 -DCMAKE_INSTALL_INCLUDEDIR,不是吗?

# Report lapacke header search locations.
set(CBLAS_INCLUDE_DIRS ${_CBLAS_PREFIX}/include)

(评论是糖在上面。)

我有一种感觉,CMake 构建在人们的想象中要成熟得多。 该项目是认真地将其作为主要构建还是这只是一种驱动性贡献? 我真的很想修复旧式的 Makefile,而不是到处大惊小怪。 但是我现在花了很多时间来修复 CMake 的东西,无论如何我都很讨厌。 所以我想结束它。

我现在必须放弃......我设法将 cblas.h 移动到 cblas.h.in 如上所述,并添加

configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cblas.h.in cblas.h @ONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cblas_f77.h.in cblas_f77.h @ONLY)

到 CBLAS/include/CMakeLists.txt,也在顶层 CMakeLists.txt 中将@HAVE_ILP64@定义${LAPACK_BINARY_DIR}/include的相同内容的奇怪副本(真的吗?A在源树中复制?)

宏 append_subdir_files 应该做什么? 它似乎将前缀的副本添加到标头路径中。 我没有足够或太多的源头文件路径。 我只想把头文件从这里安装到那里,该死的。

有知识的人可以帮忙吗? 我想我明天就能搞定,但我不确定这是否没有在现实世界中粉碎某些东西来缓解情绪。

是的。 我同意这一点,并且更喜欢不复制整个标题的解决方案。 我认为它更干净。

这对我来说是模棱两可的。 哪个是更清洁的解决方案? 我现在准备的是这样的:

#if defined(WeirdNEC) || @HAVE_ILP64@
   #define CBLAS_INDEX long
   #ifndef WeirdNEC
   #define WeirdNEC
   #endif
#else
   #define CBLAS_INDEX int
#endif

CMakeFile 应将 HAVE_ILP 替换为 1 或 0,为当前构建安装生成的头文件。

(顺便说一句:long 在 Windows 上不起作用。它很长很长……或者 int64_t 在所有带有 stdint 的平台上。)

对。 我刚刚安装了 64 位库 (BUILD_INDEX64=ON) 并且看不到任何告诉我使用WeirdNECLAPACK_ILP64HAVE_LAPACK_CONFIG_H 。 感谢您注意到这一点!

我在想象你所做的未来

cc -I/foo/include/netlib64 -o bar bar.c -L/foo/lib -lcblas64

事情在 foo/include/netlib64/cblas.h 中处理,否则由 foo/include/netlib/cblas.h(可能链接到 foo/include/cblas.h)处理。

我怀疑这_不是_你的意思,但我想说服它更好;-)

对不起,让我解释一下。 起初,我喜欢保留原始标题cblas.h并使用类似的东西创建include/netlib64/cblas.hinclude/netlib/cblas.h的想法

#if defined(WeirdNEC)
   #define WeirdNEC
#endif
#include <cblas.h>

您可以尝试通过在 /foo/include/cblas.h 中放置 'the' 头文件来避免复制头文件,并让 /foo/include/netlib64/cblas.h 仅通过定义 WeirdNEC 来包含该头文件,但这意味着 64 bit 和 32 位包共享通用的头文件,这对于打包来说很麻烦。 如果每个人都将其文件放在不同的位置/名称中,那就更好了。 该名称需要保留 cblas.h,因为您不想到处替换#include <cblas.h>行。

编辑:另外,让 cbl​​as.h 包含 ../cblas.h 本身就很混乱。 我们还为 cmake 定义了 _one_ 头文件安装目录。

但是是的,如果我们的一个标题包含另一个标题,我们将不得不使用include/netlib64include作为包含目录。

默认情况下是 /foo/include,而不是 /foo/netlib64/include。 我不会改变这个默认值。 打包者必须像这样指定子目录(BSD make in pkgsrc):

.if !empty(LAPACK_COMPONENT:M*64)
.  if empty(MACHINE_ARCH:M*64)
PKG_FAIL_REASON+=       "${LAPACK_COMPONENT} incompatible with non-64-bit platform"
.  endif
HEADERDIR=netlib64
.else
HEADERDIR=netlib
.endif

# Note: We patch the build to install both static and
# shared libraries.
CMAKE_ARGS=     -DBUILD_DEPRECATED=ON \
                -DBUILD_SHARED_LIBS=ON \
                -DBUILD_STATIC_LIBS=ON \
                -DCMAKE_INSTALL_INCLUDEDIR=${PREFIX}/include/${HEADERDIR} \
                ${LAPACK_COMPONENT_CMAKE_ARGS}

这对我来说似乎很好。 因此,您只需添加构建 LAPACK 的替代方法,而不必_猜测_编译器标志。 但目前的方式也行得通。

(顺便说一句:long 在 Windows 上不起作用。它很长很长……或者 int64_t 在所有带有 stdint 的平台上。)

很高兴知道。 BLAS++ 和 LAPACK++ 使用 int64_t 而不是 long long。

@weslleyspereira所以你一开始喜欢这个主意:

#if defined(WeirdNEC)
   #define WeirdNEC
#endif
#include "../cblas.h"

使用/prefix/include/cblas.h 和/prefix/include/netlib64/cblas.h,后者定位前者? 但是您现在确实同意,为 64 位构建安装看起来像这样的标头是一种更强大的解决方案吗?

#if defined(WeirdNEC) || @HAVE_ILP64@
   #define CBLAS_INDEX long
   #ifndef WeirdNEC
   #define WeirdNEC
   #endif
#else
   #define CBLAS_INDEX int
#endif

(long 与 int64 是另一回事,但我完全赞成进行这种更改,就像 BLAS++ 一样)

哎呀,我什至不确定假设`#include ".../cblas.h" 只会找到另一个缩进的标题是否安全。 C 标准似乎说搜索顺序是实现定义的,不一定相对于当前标头。 作为打包者,我的主要问题是我需要一个单独的包来处理通用头文件,或者让 64 位包依赖于 32 位包。 这会很糟糕。

我真的很想继续对 pkgsrc 进行这样的更改,以便稍后解决上游代码的更改。 我们可以讨论一个新符号,以使用任何标头( -DNETLIB_INDEX_BITS=64 ?)显式地强制使用 32 位索引或 64 位索引,只是默认为构建库的内容。

我可以就我们的预期解决方案达成一致吗?

lib/libcblas64.so
include/optional_subdir64/cblas.h

lib/libcblas.so
include/optional_subdir/cblas.h

LAPACK 代码的每个构建都会生成头文件,至少在默认情况下,这些头文件与已安装的库相匹配,而无需用户定义任何内容。 好?

然后,我可以在即将发布的 pkgsrc(截止日期临近)之前将其插入,我们可以进一步讨论该实现的细节,以便我可以在将此处的某些内容与新的 LAPACK 版本合并后删除补丁。 有了这个改变,普通的 Makefile 构建也需要修复,但是当我只使用 CMake 构建时,我还不需要修复 _my_ 补丁。

(只需要在尝试击败那个奇怪的 CMake 构建提交时以某种方式检查我的脾气,它在构建目录周围打乱标题副本,然后找不到它们进行安装。或者决定那些损坏的 .cmake 文件对我们有任何用处,也许只是从安装中删除它们……我们得到了 pkg-config!)

任何事物? 我必须承认,我在实践中看不到有太多机会使用不同的解决方案,因为这是我们使用的主要实现 openblas 设置的示例。 我可以想象说服英特尔也有 64 位/32 位索引标头的子目录,包装他们的 mkl_cblas.h 和 mkl_lapacke.h。 否则我会构建一个只提供这些的简单包。

include/mkl-blas/cblas.h
include/mkl-blas64/cblas.h

目前,我向 pkgsrc 添加了机器以提供带有有趣的-DWeirdNEC -DHAVE_LAPACK_CONFIG_H -DLAPACK_ILP64行的构建,cblas 和 cblas64 都安装了相同的标头。 它可以保持这种状态,但我仍然认为设置标头以匹配构建 ABI 是有意义的。

@weslleyspereira所以你一开始喜欢这个主意:

#if defined(WeirdNEC)
   #define WeirdNEC
#endif
#include "../cblas.h"

使用/prefix/include/cblas.h 和/prefix/include/netlib64/cblas.h,后者定位前者? 但是您现在确实同意,为 64 位构建安装看起来像这样的标头是一种更强大的解决方案吗?

#if defined(WeirdNEC) || @HAVE_ILP64@
   #define CBLAS_INDEX long
   #ifndef WeirdNEC
   #define WeirdNEC
   #endif
#else
   #define CBLAS_INDEX int
#endif

对,就是那样。 我同意您为 32 位和 64 位标头设置子文件夹的解决方案。 我与@langou讨论了这个

(long 与 int64 是另一回事,但我完全赞成进行这种更改,就像 BLAS++ 一样)

对。 这应该在另一个问题中解决。

我真的很想继续对 pkgsrc 进行这样的更改,以便稍后解决上游代码的更改。 我们可以讨论一个新符号,以使用任何标头( -DNETLIB_INDEX_BITS=64 ?)显式地强制使用 32 位索引或 64 位索引,只是默认为构建库的内容。

我可以就我们的预期解决方案达成一致吗?

lib/libcblas64.so
include/optional_subdir64/cblas.h

lib/libcblas.so
include/optional_subdir/cblas.h

是的。 我想你以后可以继续提出 PR,谢谢! 我个人认为像NETLIB_INDEX_BITS这样的新符号完全有意义。 我只想确保默认值保持为 32,而-DWeirdNEC意味着-DNETLIB_INDEX_BITS=64

LAPACK 代码的每个构建都会生成头文件,至少在默认情况下,这些头文件与已安装的库相匹配,而无需用户定义任何内容。 好?

听起来不错。

然后,我可以在即将发布的 pkgsrc(截止日期临近)之前将其插入,我们可以进一步讨论该实现的细节,以便我可以在将此处的某些内容与新的 LAPACK 版本合并后删除补丁。 有了这个改变,普通的 Makefile 构建也需要修复,但是当我只使用 CMake 构建时,我还不需要修复 _my_ 补丁。

好的! 我们可能会在 2021 年第二学期发布 LAPACK。是的,Makefile 应该相应调整,我愿意提供帮助。

这有点关系。 我们不应忘记 netlib CBLAS 的头文件不仅由 netlib 提供……NumPy 始终使用自己的头文件:

https://github.com/numpy/numpy/blob/main/numpy/core/src/common/npy_cblas.h

在这个头文件中,它设置了CBLAS_INDEX=size_t ,不同于指定索引时使用的整数类型。 它仅用于某些函数的返回值:

$ grep CBLAS_INDEX ./numpy/core/src/common/npy_cblas_base.h                                                                                                                                  
CBLAS_INDEX BLASNAME(cblas_isamax)(const BLASINT N, const float  *X, const BLASINT incX);
CBLAS_INDEX BLASNAME(cblas_idamax)(const BLASINT N, const double *X, const BLASINT incX);
CBLAS_INDEX BLASNAME(cblas_icamax)(const BLASINT N, const void   *X, const BLASINT incX);
CBLAS_INDEX BLASNAME(cblas_izamax)(const BLASINT N, const void   *X, const BLASINT incX);

区别:

$ grep cblas_isamax ./numpy/core/src/common/npy_cblas_base.h  /data/pkg/include/cblas.h                                                                                                      
./numpy/core/src/common/npy_cblas_base.h:CBLAS_INDEX BLASNAME(cblas_isamax)(const BLASINT N, const float  *X, const BLASINT incX);
/data/pkg/include/cblas.h:CBLAS_INDEX cblas_isamax(const CBLAS_INDEX N, const float  *X, const CBLAS_INDEX incX);

我想知道这是否可能造成麻烦。 对于 Netlib,只有一种类型的索引,而其他实现对索引函数使用不同的返回值类型。 OpenBLAS 树立了榜样。 他们说 isamax 返回无符号 size_t,但 C 包装器实际上调用了一个 Fortran 函数,该函数返回一个有符号整数(编辑:一个将有符号 32 或 64 位整数值写入到 64位系统)。

参考实现对此有意见吗? 我 _guess_ 没有真正的麻烦,因为 size_t 值将始终能够保持来自 isamax() 的任何非负回报。 但闻起来很臭。 (编辑:您可以在 size_t 为 32 位的 32 位系统上使用 64 位索引构建,对吗?然后您会溢出。除了将size_t *int *的不安之外。)

由于优化的实现似乎已经决定了 size_t ,参考应该接受这个事实并遵循吗?

实际上,将 numpy 与参考 cblas 链接起来有多危险?

OpenBLAS 树立了榜样。 (……)
由于优化的实现似乎已经决定了 size_t ,参考应该接受这个事实并遵循吗?

我当然不能代表 numpy(或 mkl 等),但我会犹豫是否声称 OpenBLAS 是任何形式的规范,尤其是相对于(我相信)通常被视为 __the__ 参考实现的内容。 .

当然。 只是 OpenBLAS 或 MKL 是人们在实践中使用的,而且两者似乎都已经解决了

#define CBLAS_INDEX size_t  /* this may vary between platforms */
#ifdef MKL_ILP64
#define MKL_INT MKL_INT64
#else
#define MKL_INT int
#endif
CBLAS_INDEX cblas_isamax(const MKL_INT N, const float  *X, const MKL_INT incX);

或类似地

#ifdef OPENBLAS_USE64BITINT
typedef BLASLONG blasint;
#else
typedef int blasint;
#endif
#define CBLAS_INDEX size_t
CBLAS_INDEX cblas_isamax(OPENBLAS_CONST blasint n, OPENBLAS_CONST float  *x, OPENBLAS_CONST blasint incx);

与参考

#ifdef WeirdNEC
   #define CBLAS_INDEX long
#else
   #define CBLAS_INDEX int
#endif
CBLAS_INDEX cblas_isamax(const CBLAS_INDEX N, const float  *X, const CBLAS_INDEX incX);

为什么他们与这里的参考不同? 有没有就此进行沟通? 另外……我看到 MKL 和 OpenBLAS 定义了许多甚至不属于参考 CBLAS 的函数:

CBLAS_INDEX cblas_isamin(const MKL_INT N, const float  *X, const MKL_INT incX);
CBLAS_INDEX cblas_idamin(const MKL_INT N, const double *X, const MKL_INT incX);
CBLAS_INDEX cblas_icamin(const MKL_INT N, const void   *X, const MKL_INT incX);
CBLAS_INDEX cblas_izamin(const MKL_INT N, const void   *X, const MKL_INT incX);

CBLAS_INDEX cblas_isamin(OPENBLAS_CONST blasint n, OPENBLAS_CONST float  *x, OPENBLAS_CONST blasint incx);
CBLAS_INDEX cblas_idamin(OPENBLAS_CONST blasint n, OPENBLAS_CONST double *x, OPENBLAS_CONST blasint incx);
CBLAS_INDEX cblas_icamin(OPENBLAS_CONST blasint n, OPENBLAS_CONST void  *x, OPENBLAS_CONST blasint incx);
CBLAS_INDEX cblas_izamin(OPENBLAS_CONST blasint n, OPENBLAS_CONST void *x, OPENBLAS_CONST blasint incx);

CBLAS_INDEX cblas_ismax(OPENBLAS_CONST blasint n, OPENBLAS_CONST float  *x, OPENBLAS_CONST blasint incx);
CBLAS_INDEX cblas_idmax(OPENBLAS_CONST blasint n, OPENBLAS_CONST double *x, OPENBLAS_CONST blasint incx);
CBLAS_INDEX cblas_icmax(OPENBLAS_CONST blasint n, OPENBLAS_CONST void  *x, OPENBLAS_CONST blasint incx);
CBLAS_INDEX cblas_izmax(OPENBLAS_CONST blasint n, OPENBLAS_CONST void *x, OPENBLAS_CONST blasint incx);

CBLAS_INDEX cblas_ismin(OPENBLAS_CONST blasint n, OPENBLAS_CONST float  *x, OPENBLAS_CONST blasint incx);
CBLAS_INDEX cblas_idmin(OPENBLAS_CONST blasint n, OPENBLAS_CONST double *x, OPENBLAS_CONST blasint incx);
CBLAS_INDEX cblas_icmin(OPENBLAS_CONST blasint n, OPENBLAS_CONST void  *x, OPENBLAS_CONST blasint incx);
CBLAS_INDEX cblas_izmin(OPENBLAS_CONST blasint n, OPENBLAS_CONST void *x, OPENBLAS_CONST blasint incx);

因此,扩展标准是一回事,但size_tint对 64 位系统似乎是一个严重的问题。 这应该以某种方式解决。 在我看来,Netlib 方式是明智的:与用于索引的类型相同。 因为最后都是这样调用 Fortran 例程的

c     isamaxsub.f
c
c     The program is a fortran wrapper for isamax.
c     Witten by Keita Teranishi.  2/11/1998
c
      subroutine isamaxsub(n,x,incx,iamax)
c
      external isamax
      integer  isamax,iamax
      integer n,incx
      real x(*)
c
      iamax=isamax(n,x,incx)
      return
      end

... 为 iamax 提交 size_t 的地址,这似乎是错误的。 在 OpenBLAS 源代码中,我没有找到除此参考之外的其他实现。 他们只是愚蠢地改变外部类型,还是我忽略了一些非常基本的东西? 真的有人在使用这些功能吗?

大家好,参考 BLAS、参考 CBLAS、参考 LAPACK,这些项目的两个主要重点是 (1) 数值算法和 (2) 定义通用接口、参考实现和测试套件。 我认为参与这些项目的每个人都乐于从其他项目(OpenBLAS、MKL 等)中学习软件工程、部署软件的最佳实践等。我们可以从这些项目中学到很多东西。 (而且我们也从其他数值线性代数项目中学到了很多!)总之:参考 BLAS、CBLAS、LAPACK 可以在其 CMake 封装、接口上使用一些改进,如果 OpenBLAS(例如)有更好的流程,那非常适合我们,嗯,我都赞成转向这种模式。

为了添加一些上下文,CBLAS 诞生于一个委员会(基本线性代数子程序技术论坛),该委员会从 1996 年到 2000 年致力于重新审视 BLAS,作为其中的一部分,他们为 BLAS 定义了一个 C 接口。 看:
http://www.netlib.org/blas/blast-forum/
具体见:
http://www.netlib.org/blas/blast-forum/cinterface.pdf
我相信 LAPACK 提供的 CBLAS 是 25 年前基本线性代数子程序技术论坛定义的接口的实现。

如果有改进 CBLAS 的建议,请将其发送。 我可以尝试将其传递给各个利益相关者。

谢谢指点。 所以相关部分似乎是该规范中的 B.2.2,它说BLAS_INDEX通常是size_t ,但也可能被选择为与用于的(有符号)Fortran 整数类型相同索引。 这取决于实施。

因此,流行的优化实现似乎选择了size_t ,而 Netlib 参考选择了它用于 Fortran 的相同整数。 我在使用 lib 的各种项目中看到了 cblas.h 的副本(如 numpy,为外部 lib 发送标头),带有该行

#define CBLAS_INDEX size_t  /* this may vary between platforms */

https://github.com/LuaDist/gsl/blob/master/cblas/gsl_cblas.h 中,这伴随着

/* This is a copy of the CBLAS standard header.
 * We carry this around so we do not have to
 * break our model for flexible BLAS functionality.
 */

这听起来像是起源于参考实现,但后来发生了变化? 看看 41779680d1f233928b67f5f66c0b239aecb42774 ......我看到CBLAS_INDEX与 WeirdNEC 的开关在 64 位构建之前就已经存在了。 哇,这是最近提交的。 现在我看到size_t一直在引用 cblas.h 中直到 2015 年,83fc0b48afd1f9a6d6f8dddb16e69ed7ed0e7242 已经改变了它并引入了 WeirdNEC 定义。 没想到这是最近的事! 乱七八糟的。

我还看到早期版本的 cblas.h 将int交给了 fortran 调用,现在是CBLAS_INDEX 。 现在这似乎是正确的,一致使用CBLAS_INDEX作为整数类型和 Fortran 部分中的 32 位或 64 位开关。

但是,继承旧版本的 cblas.h 和size_t但同步源与参考中的当前 CBLAS 代码的优化库是否有一个很好的错误? 他们不是在 64 位系统上为 32 位情况做这样的事情吗?

#include <stdio.h>
#include <stdlib.h>


void ia_max(int a, void *b)
{
    int *ib = (int*)b;
    *ib = a*2;
}


int main(int argc, char **argv)
{
    int i = atoi(argv[1]);
    size_t maxi;
    ia_max(i, &maxi);
    printf("in %d out %ld\n", i, (long)maxi);
    return 0;
}

这导致

$ gcc -O -o t t.c
$ ./t 42
in 42 out 140724603453524

size_t值初始化为零有帮助,但可能仅在小端的情况下。 没有人为此惹上麻烦吗? 我必须错过一些东西。

总结:

  1. 参考 CBLAS 首先将size_t作为返回值。
  2. 不过,它在对 Fortran 的实际调用中使用了 int。
  3. 下游(优化的 BLAS、CBLAS 用户)使用旧版本的标头运行。
  4. 参考 CBLAS 为特定系统引入了 WeirdNEC hack,将 size_t 替换为 int 或 long(匹配 Fortran 方面?!)
  5. 64 位参考 CBLAS 接口在此基础上构建,在任何地方使用CBLAS_INDEX作为 Fortran 默认整数。
  6. 下游在 64 位支持下做了自己的事情,但将其与CBLAS_INDEX分开,后者始终是 size_t。
  7. 下游继承 CBLAS 包装器,使用CBLAS_INDEX来调用需要默认整数的 Fortran。

结果,这听起来像是一个美妙的破损。 标头和代码出现分歧。 怎么还没人注意到问题? 或者我是否错过了实际上没有使用 isamax 和朋友的参考 CBLAS 包装器代码的部分?

OpenBLAS 至少不使用来自 Reference-LAPACK 的 CBLAS 包装器代码(从来没有,源代码在那里但没有被构建)

@martin-frbg 很高兴知道。 你能指出 x86-64 的代码路径,显示 size_t 是如何传递给 cblas_isamax() 的实际计算的吗? 我找到了一些特定的内核实现,但不确定一般情况。

很高兴知道实际上没有人将(size_t*)传递给 Fortran 接口。

当然,项目只是假设不好

size_t cblas_isamax(…)

当实际库可能提供 int 或 long(或 int64_t)作为返回值时。 大多数情况下可能会使用 64 位寄存器中的值,但这并不好。 我们可以在实现中纠正这个问题吗? 在过去的 5 年里,人们并没有注意到 Netlib 的例子,即一贯使用CBLAS_INDEX

相关代码在 OpenBLAS/interface 中,例如 interface/imax.c 在定义 CBLAS 时被编译为 cblas_isamax(),其调用图中不涉及 Fortran 代码。

啊好。 因此,实际存在问题的一种情况是依赖于使用不适合库的 cblas.h 副本的项目。

我没有在 NumPy(和 SciPy)中找到cblas_isamax()和朋友的实际用法,所以这可能只是一个理论问题。 尽管如此,它应该被修复。 所以:

  1. 其他人遵循使用 int32_t/int64_t 的 Netlib 示例(让我们明确一点;-) BLAS_INDEX 用于大小返回和索引参数。
  2. Netlib 陷入困境并像其他人一样返回size_t

这是要单独讨论的问题吗? 不过,它确实与 32 位或 64 位库的选择有关。

PS:我仍然不确定 API 中的枚举是否是一个好主意(作为函数参数和结构成员的实际数据类型),因为有编译器选项可以更改它们下面使用的整数。 在实践中没有那么重要,但仍然让我感到不安。

我想得越多,我就越倾向于选项 2:我们在 API 中使用 size_t 已经很长时间了。 然后 Netlib 将该 size_t 更改为 int 或 long。 不管什么更匹配 Fortran 代码或更一致,size_t 是建立的 API 和 Netlib 参考打破了这一点。

我应该打开一个关于改变事物的 PR

size_t cblas_isamax(const CBLAS_INDEX N, const float  *X, const CBLAS_INDEX incX);
size_t cblas_idamax(const CBLAS_INDEX N, const double *X, const CBLAS_INDEX incX);
size_t cblas_icamax(const CBLAS_INDEX N, const void   *X, const CBLAS_INDEX incX);
size_t cblas_izamax(const CBLAS_INDEX N, const void   *X, const CBLAS_INDEX incX);

再次? 这个位置应该没有宏来强调它总是 size_t,无处不在,过去和未来。

https://github.com/numpy/numpy/issues/19243 中,我们现在基本上归结为:“Screw Netlib,size_t 对其他人有效”。

使用size_t三个原因:

  1. 所有 C 和 C++ 标准库函数都接受并返回此值,例如void* malloc(size_t)size_t strlen()std::size_t std::vector<T>::size() (C++)。 使用size_t可避免截断值和有符号/无符号转换。
  2. size_t通常用于表示不能为负的数量,例如矩阵维度。
  3. C 和 C++ 标准保证您可以将任何数组的大小存储在size_t ,并且您可以使用size_t索引所有元素,参见。 cppference.com: size_t

编辑:您可以在 size_t 为 32 位的 32 位系统上使用 64 位索引构建,对吗? 然后你就溢出了。

不能,因为 32 位系统可能有超过 4 GB 的虚拟内存(Linux 支持这一点),但单个 32 位进程永远不能访问超过 4 GB。 也就是说,从不使用 64 位索引的高 32 位。

_在 64 位 Linux 操作系统上运行的 32 位进程的内存限制_

我还认为保持 size_t 是正确的做法,因为从它改变是 ABI 中断并使 Netlib 与世界其他地方不同步。

但我觉得有必要对你的论点吹毛求疵;-)

1. All of the C and C++ standard library functions accept and return this value

当我对此进行研究时,我偶然发现,对 C++ 容器索引使用无符号类型是一个历史性错误,甚至可能使用 size() 方法的返回类型,因为您很快就会在其中混合有符号和无符号数字某种方式。 Netlib 的当前状态将与其自身一致,始终使用有符号类型作为大小和索引,但当然与malloc()不一致,后者要求无符号大小实际上能够寻址适合的所有内存32 位(或 64 位,理论上)。

我在我写的代码中想知道自己是否正确,我最终将索引作为函数调用的偏移量。 索引是无符号的,偏移量是有符号的。 除了编译器 (MSVC) 被-unsigned_value弄糊涂之外,这意味着我总是不得不担心转换中可能出现的溢出。

但无论如何,如果只是计算要交给 malloc() 和朋友的内存大小,那么 size_t 是很自然的事情,而且它之前在 CBLAS 中已经存在。

关于代码当前状态的可能问题,与构建中的 vendored cblas.h不匹配:

不能,因为 32 位系统可能有超过 4 GB 的虚拟内存(Linux 支持这一点),但单个 32 位进程永远不能访问超过 4 GB。 也就是说,从不使用 64 位索引的高 32 位。

是的, size_t保持 32 位。 当您(可能很傻)构建cblas_isamax()以返回 64 位整数时,在破解构建后不使用long ,而是使用int64_t ,当然,什么会真的会发生在这样的用法中吗?

size_t cblas_isamax(); // really int64_t cblas_isamax()!
size_t value = cblas_isamax(…);

x86 调用约定可能会将 64 位值放入 EAX 和 EDX。 或者它可能与指针返回和一些缓冲区一起工作。 但是其他架构会做什么呢? 所以你可能不会腐败,但肯定是错误的价值。 最好的情况是高 32 位被忽略。

现在想象一个大端 32 位系统(某种形式的 ARM)……确定你甚至会得到所需的一半返回值?

当然,您无法真正处理在 32 位程序中需要 64 位索引的非稀疏数据。 但是仅仅能够进行_at_least_给出错误结果的非匹配函数调用似乎是不健康的。

我做了一些快速测试……在 x86 Linux 上(在 x86-64 系统上为gcc -m32 ),你只需要删除高 32 位。

更有趣的情况…… 64 位 size_t:

size_t cblas_isamax(); // really int32_t cblas_isamax()!
size_t value = cblas_isamax(…);

同样,在 x86-64 上,64 位 RAX 和 32 位 EAX 之间的特殊关系使事情有些明确定义,一旦您在共享寄存器上执行 32 位操作,也只是默默地将高 32 位清零。 但是有一个有点奇怪的函数定义很有趣:

$ cat ret32.c 
#include <stdint.h>

int32_t ret64(int64_t a)
{
    a += 1LL<<32;
    return a;
}
$ gcc -m64  -g -c -o ret32.o ret32.c 
$ LANG=C objdump -S ret32.o 
[…]
   8:   48 89 7d f8             mov    %rdi,-0x8(%rbp)
    a += 1LL<<32;
   c:   48 b8 00 00 00 00 01    movabs $0x100000000,%rax
  13:   00 00 00 
  16:   48 01 45 f8             add    %rax,-0x8(%rbp)
    return a;
  1a:   48 8b 45 f8             mov    -0x8(%rbp),%rax

您可能会争辩说,编译器在完整的 64 位寄存器上工作并让高 32 位未清除对于预期返回 32 位值的函数是否明智,但如果您只依赖调用者,这是完全合法的我猜是使用低 32 位。

$ cat call.c 
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

INDEX ret64(int64_t);

int main(int argc, char **argv)
{
    if(argc < 2)
        return 1;
    int64_t a = (int64_t)strtoll(argv[1], NULL, 10);
    INDEX  s = ret64(a);
    printf("%lld\n", (long long)s);
    return 0;
}
$ gcc -m64 -g -DINDEX=int32_t -c -o call32_64.o call.c
$ gcc -m64 -g -DINDEX=size_t -c -o call64_64.o call.c
$ ./call32_64 1
1
$ ./call64_64 1
4294967297

乐趣。 一个 32 位的返回值,它提供了比 32 位更多的可能。 这就是(原则上)当 Netlib CBLAS 的当前状态与期望 size_t 的代码链接时会发生的情况。 我猜虽然在实践中的实际代码中 RAX 的高 32 位将为零。 但是谁知道……编译器希望调用者在任何平台上都不要使用超过低 32 位的位……不妨在那里存储垃圾。

那么……我们是否同意将 Netlib 移回 size_t 作为返回值?

感谢所有这些宝贵的意见!

我和@langou 讨论了这个话题。 基于这里的讨论,我的建议是:

在单独的 PR 中:

  1. 我们回到使用两个整数定义的cblas.h ,比如说 CBLAS_INDEX 和 CBLAS_INT。 这就是 MKL(CBLAS_INDEX 和 MKL_INT)和 OpenBLAS(CBLAS_INDEX 和 blasint)中发生的情况。 CBLAS_INDEX 将仅用于i*amax的返回。 这样,我们恢复了与其他 BLAS 兼容的 ABI。
  2. 此外,我们选择 CBLAS_INDEX 的默认值为size_t并收集社区的意见。

我认为这与此线程中最近的讨论背后的想法一致(或可能相同)。
正如@drhpc指出的那样,
https://github.com/Reference-LAPACK/lapack/commit/83fc0b48afd1f9a6d6f8dddb16e69ed7ed0e7242更改了 CBLAS_INDEX 的默认值,并且
https://github.com/Reference-LAPACK/lapack/commit/41779680d1f233928b67f5f66c0b239aecb42774更改了 CBLAS_INDEX 的使用。

只是为了加强:

  • 默认情况下,OpenBLAS、MKL、GNU 科学图书馆和 Numpy 都使用size_t
  • BLAS 的 C 接口 (https://www.netlib.org/blas/blast-forum/cinterface.pdf) 表示通常CBLAS_INDEX = size_t

你同意? 如果你这样做,我可以打开PR。 或者@drhpc可能想这样做。

我同意。 请继续进行 PR。

@mgates3向我提到了 Slate Google Group 上的讨论:
https://groups.google.com/a/icl.utk.edu/g/slate-user/c/f5y6gt0aoLs/m/oQyyhikwCgAJ
讨论的不是“CBLAS_INDEX”应该是什么,而是“CBLAS_INT”应该是什么。 CBLAS_INT 应该是 size_t 还是有符号整数等? 我认为参与者提出了很好的观点,所以我要传递下去。

请参阅#588。

是的, size_t保持 32 位。 当您(可能很傻)构建cblas_isamax()以返回 64 位整数时,在破解构建后不使用long ,而是使用int64_t ,当然,什么会真的会发生在这样的用法中吗?

size_t cblas_isamax(); // really int64_t cblas_isamax()!
size_t value = cblas_isamax(…);

x86 调用约定可能会将 64 位值放入 EAX 和 EDX。 或者它可能与指针返回和一些缓冲区一起工作。 但是其他架构会做什么呢? 所以你可能不会腐败,但肯定是错误的价值。 最好的情况是高 32 位被忽略。

现在想象一个大端 32 位系统(某种形式的 ARM)……确定你甚至会得到所需的一半返回值?

这游戏结束了。 在 32 位 Arm CPU 上,可以在寄存器中传递和返回四个 32 位值,64 位值占用两个连续的寄存器,参见_Arm 架构的过程调用标准_中的第 6.1.1.1 节。 被调用者不会写入一个寄存器,而是用他的 64 位整数将两个寄存器混为一谈; 这显然是个问题。 一旦调用者用完参数寄存器,就会使用堆栈。 堆栈对齐是 32 位,但不是读取或写入 32 位,而是被调用者写入 64 位; 再次,这是游戏结束,这个问题(堆栈读/写大小不匹配)应该会在某个时候导致所有指令集架构出现问题。

我在我写的代码中想知道自己是否正确,我最终将索引作为函数调用的偏移量。 索引是无符号的,偏移量是有符号的。 除了编译器 (MSVC) 被 -unsigned_value 混淆之外,这意味着我总是不得不担心转换中可能出现的溢出。

不,C 和 C++ 背后的标准委员会使您的代码在这种情况下表现得显而易见:如果u是无符号值,而s是有符号值,其中u至少有与s一样多的位,那么u + s将产生数学上正确的结果,除非u + s溢或下溢。 如果它下溢/溢出,结果将环绕,即(u + s) mod 2^b ,其中bus的位数。 另一方面,如果有符号类型可以表示无符号类型的所有值,那么无符号值将被转换为无符号类型。

C11标准草案中的相关条款如下:

  • 6.2.5.9:只有无符号操作数的二元运算不能溢出; 结果取模MAX + 1 ,其中MAX是最大的可表示值。
  • 6.3.1.3:给定一个有符号值s ,如果s >= 0 ,则转换为无符号值s s >= 0 ,否则转换为s + MAX + 1
  • 6.3.1.8:有符号和无符号操作数[大小相同]被转换为无符号; 如果有符号类型可以表示无符号类型的所有值,则无符号操作数将转换为有符号类型

因此, u + s (C 语法)将被评估为

  • (u + s) mod (M + 1)如果s >= 0 ,
  • (u + s + M + 1) mod (M + 1)否则。

在没有上溢或下溢的情况下,此表达式的计算结果为u + s ,这是直观期望的结果。

当我对此进行研究时,我偶然发现,对 C++ 容器索引使用无符号类型是一个历史性错误,甚至可能使用 size() 方法的返回类型,因为您很快就会在其中混合有符号和无符号数字某种方式。

有一些 C++ 程序员(包括 C++ 的发明者)提议在任何地方使用有符号整数,请参阅C++ 核心指南,但我不会将其称为承认。 “无处不在的有符号整数”政策的问题是

  • 检查最小值:对于无符号整数,在许多情况下检查最小值是多余的,对于有符号整数,它是强制性的; 这很容易出错并且可能导致安全问题,例如参见CWE-839 _Numeric Range比较无最小检查_
  • 溢出:无符号溢出具有明确定义的结果,而有符号整数溢出构成未定义行为。

您可以尝试使用表达式a + b < a检查有符号溢出,但编译器可能会在没有警告的情况下将其优化掉,例如参见GCC 错误 30475 _assert(int+100 > int) optimization away_ from 2007。这会起作用使用无符号整数( a无符号, b可能有符号, b最多与a )。 看到 2020 年的_OptOut – 编译器未定义行为优化_一文,GCC 行为显然没有改变。

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

christoph-conrads picture christoph-conrads  ·  26评论

hokb picture hokb  ·  16评论

h-vetinari picture h-vetinari  ·  8评论

Dichloromethane picture Dichloromethane  ·  11评论

miroi picture miroi  ·  10评论