Lapack: Eine mögliche Installation der Index-64-Bibliothek neben der Standard-Index-32-Bibliothek zulassen?

Erstellt am 1. Nov. 2020  ·  41Kommentare  ·  Quelle: Reference-LAPACK/lapack

Derzeit hat Debian (einige) separate Header für blas64 und cblas64 (allerdings nicht von der Referenzimplementierung).

Ich bin mir nicht sicher, ob sie in Bezug auf die Referenzindex64-API korrekt sind oder nicht (sie stammen aus der Blis-Bibliothek).

Wäre es möglich, cmake eine Option hinzuzufügen, etwa BUILD_INDEX64 , die standardmäßig auf OFF , aber wenn sie aktiviert ist, werden die Index-64-Bibliotheken erstellt?
Wenn ich eine PR für eine solche Option mache, würde sie als Möglichkeit in Betracht gezogen?

Einige Dinge, die ich mir gedacht habe, damit dies mit der Standardinstallation koexistieren kann - benennen Sie die Bibliotheken mit libblas64.so, libcblas64.so, liblapack64.so, liblapacke64.so , auf diese Weise gibt es keinen Konflikt zwischen den Bibliotheksnamen (obwohl Sie natürlich nicht verlinken können mit libblas und libblas64 gleichzeitig).
Außerdem müsste die Bibliothek zweimal kompiliert werden, einmal für index32 und einmal für index64 (aber das ist ein ganz normales Szenario und kein Dealbreaker).
Der einzige Konflikt, für den ich mir keine gute Lösung vorstellen kann, sind die Namen der Header-Datei.
Wenn Sie dem Debian-Stil folgen, ist es möglicherweise ratsam, auch die c-Header mit der Endung 64 aufzurufen.
(Ich betreue dies für Gentoo und möchte das Ökosystem sehr nah an Debian halten, damit wir minimale Probleme für Entwickler haben, die zwischen Systemen wechseln)

Ich bin für alle Vorschläge offen, bevor ich die PR mache :heart:

Vielen Dank,
Aisha

Build System Enhancement

Alle 41 Kommentare

Hi Aisha, das macht für mich total Sinn, aber lass uns sehen, ob wir Feedback von anderen haben. Warten wir also ein paar Tage. J

Das klingt auf jeden Fall nach einem guten Plan.

OMG @langou
du bist so schnell :herz:

Nur der Vollständigkeit halber schreibe ich Dinge auf, die wir noch erledigen müssen:

  • Finden Sie heraus, wie Sie Header benennen, damit die 32-Bit-API mit der 64-Bit-API koexistieren kann
  • Korrigieren Sie printf/fprintf-Anweisungen, sodass sie den richtigen Qualifizierer zum Drucken verwenden.

Vorschläge zur Lösung des ersten Punktes sind willkommen, eine "saubere" Lösung habe ich leider nicht.

Ein paar Fragen, die mir bei der Benennung der Dateien helfen werden

  • Es scheint eine Menge doppelter Definitionen zwischen cblas_f77.h und cblas_test.h . Brauchen wir das wirklich?
  • Muss cblas_test.h installiert werden? Angesichts seines Namens (und der Dateien, in denen es verwendet wird) gehe ich davon aus, dass es nur während der Testphase verwendet wird. Vielleicht sollten wir diese Datei nicht systemweit installieren?

Hallo @epsilon-0,

Einige Dinge, die ich mir gedacht habe, damit dies mit der Standardinstallation koexistieren kann - benennen Sie die Bibliotheken zu libblas64.so, libcblas64.so, liblapack64.so, liblapacke64.so, auf diese Weise gibt es keinen Konflikt zwischen den Bibliotheksnamen ( obwohl Sie natürlich nicht gleichzeitig mit libblas und libblas64 verlinken können).

Vielleicht suchen Sie nach PR #218. Der Autor dieser PR ist Björn Esser vom Fedora-Projekt.

Hallo @epsilon-0. Hat #462 dieses Problem gelöst?

@weslleyspereira nein, das ist noch nicht abgeschlossen.
Es müssen noch einige Header-Umbenennungen / -Behandlungen durchgeführt werden.
Ich bin in den nächsten Wochen beschäftigt und werde es so schnell nicht schaffen.
Grundriss

  • die Header-Dateien sollten cblas.h und cblas64.h heißen, ebenso für andere Header

    • Dies bedeutet, dass die *.c Dateien eine geringfügige Anpassung benötigen, um den richtigen Header einzuschließen, aber dies ist nur während der Build-Zeit der Fall, sodass sie ausgehackt werden kann.

  • die cmake-Dateien sollten unter lapack64 oder cblas64 installiert werden usw.

OK, ich verstehe. Danke für die schnelle Nachverfolgung!

Ich habe ähnliche Probleme beim Versuch, Dinge mit pkgsrc zu verpacken. Ich möchte eine komplette Installation der Referenz mit cblas und lapacke haben. Für unterschiedliche Implementierungen, die gleichzeitig installiert werden, habe ich mich für unterschiedliche Bibliotheksnamen und Unterverzeichnisse für die Header entschieden, also zum Beispiel

/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)

(und so weiter)

Wir berücksichtigen keine Laufzeitumschaltung wie bei den binären Distributionen, daher ist es in Ordnung, wenn jede cblas.h (und lapacke.h) spezifisch für ihre passende Bibliothek ist, wie mit zusätzlichen Namen für libopenblas. Die Auswahl der Build-Zeit erfolgt über

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

(usw.) Das ist es, was die .pc-Dateien sagen sollen, und es ist viel einfacher, als einen anderen Header-Dateinamen mitzuteilen. Darin sind sie noch nicht konsequent, aber ich korrigiere es. Es scheint, dass die Leute das nur in ihren Distributionen gehackt haben, wenn sie sich überhaupt mit allen Referenzbibliotheken beschäftigen.

Ich habe jedoch eine Frage zu diesen Headern.

Ich hacke den cmake-Build, um jede Komponente separat zu erstellen, und versuche einige andere Korrekturen (siehe https://github.com/Reference-LAPACK/lapack/pull/556). Ich bekomme die Bibliotheken libblas.so und libblas64.so gut gebaut, ich bekomme die Header-Verzeichnisse konfiguriert … aber die installierten cblas.h und lapacke.h sind für die 32- und 64-Bit-Indexierungsversionen identisch. Das steht im Widerspruch zu openblas: Da habe ich einen entscheidenden Unterschied, den ich bei den Netlib-Builds nicht sehe:

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.*/

Für die Referenzbibliotheken sind alle Header aus den 32- und 64-Bit-Index-Builds identisch und anscheinend wird von den Benutzern erwartet, dass sie
-DWeirdNEC in ihren Flaggen (könnte vor 30 Jahren lustig gewesen sein) für cblas.h und -DLAPACK_ILP64 -DHAVE_LAPACK_CONFIG_H . Da die Leute die optimierten BLAS-Bibliotheken in der Produktion verwenden, besteht der De-facto-Standard nicht darin, dies den Benutzern offenzulegen. Diese Rückmeldungen über die Referenz, IMHO, und die Header, die von einem ILP64-Build installiert wurden, sollten keine funky Flags erfordern, um einen Absturz Ihrer App beim Verknüpfen mit der 64-Bit-Bibliothek zu vermeiden.

Stimmen wir zu, dass es die richtige Lösung ist, die Header zur Build-Zeit zu ändern, um die richtigen Ganzzahlen zu definieren?

Übrigens, in den installierten cblas-Konfigurationsdateien fehlt auch jeder Verweis auf die erforderlichen Defs, sind also für die 64-Bit-Index-Builds kaputt, wie es scheint. Aber eigentlich überlege ich, diese überhaupt nicht zu installieren. Sie sind mit den .pc-Dateien überflüssig und machen es möglicherweise schwieriger, cmake-verwendende abhängige Pakete davon zu überzeugen, eine Paketauswahl über BLAS_LIBS . a. zu akzeptieren.

PS: Bei Intel MKL muss ein zentraler Schalter -DMKL_ILP64 gesetzt werden. Ich stelle mir vor, trivial einzurichten
include/intel-mkl64/cblas.h mit

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

in das allgemeine Schema passen. Ich könnte die Definition auch in BLAS_INCLUDES einfügen, das gleiche gilt für die seltsamen Netlib-Definitionen. Was ist besser? Wollen wir es wie Intel oder wie OpenBLAS machen?

Stimmen wir zu, dass es die richtige Lösung ist, die Header zur Build-Zeit zu ändern, um die richtigen Ganzzahlen zu definieren?

Jawohl. Ich stimme dem zu und bevorzuge die Lösung, die nicht den gesamten Header repliziert. Ich finde es sauberer.

Übrigens, in den installierten cblas-Konfigurationsdateien fehlt auch jeder Verweis auf die erforderlichen Defs, sind also für die 64-Bit-Index-Builds kaputt, wie es scheint.

Rechts. Ich habe gerade die 64-Bit-Bibliotheken (BUILD_INDEX64=ON) installiert und konnte nichts sehen, was mir sagt, dass ich WeirdNEC , LAPACK_ILP64 oder HAVE_LAPACK_CONFIG_H . Danke, dass du das bemerkt hast!

Jawohl. Ich stimme dem zu und bevorzuge die Lösung, die nicht den gesamten Header repliziert. Ich finde es sauberer.

Das ist für mich zweideutig. Welche ist die sauberere Lösung? Was ich jetzt vorbereite ist folgendes:

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

Das CMakeFile soll HAVE_ILP durch 1 oder 0 ersetzen, wobei der resultierende Header für den aktuellen Build installiert wird.

(Übrigens: long würde unter Windows nicht funktionieren. Es ist long long dort … oder int64_t auf allen Plattformen mit stdint.)

Rechts. Ich habe gerade die 64-Bit-Bibliotheken (BUILD_INDEX64=ON) installiert und konnte nichts sehen, was mir sagt, dass ich WeirdNEC , LAPACK_ILP64 oder HAVE_LAPACK_CONFIG_H . Danke, dass du das bemerkt hast!

Ich stelle mir eine Zukunft vor, in der du es tust

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

Und die Dinge werden in foo/include/netlib64/cblas.h gehandhabt, ansonsten über foo/include/netlib/cblas.h (möglicherweise verlinkt auf foo/include/cblas.h).

Ich habe den Verdacht, dass du das _nicht_ meintest, aber ich möchte davon überzeugen, dass es besser ist ;-)

Sie könnten versuchen, den Header nicht zu duplizieren, indem Sie 'den' Header in /foo/include/cblas.h platzieren und /foo/include/netlib64/cblas.h diesen nur einschließen, indem Sie WeirdNEC definieren, aber das bedeutet, dass die 64 Bit- und 32-Bit-Pakete teilen sich diese gemeinsame Header-Datei, was beim Packen unübersichtlich ist. Es ist viel besser, wenn jeder seine Datei an verschiedenen Orten/Namen ablegt. Der Name muss cblas.h bleiben, weil Sie nicht herumlaufen wollen, um #include <cblas.h> Zeilen zu ersetzen.

Bearbeiten: Auch cblas.h enthalten ../cblas.h ist an sich chaotisch. Außerdem definieren wir das Installationsverzeichnis des Headers _one_ für cmake. Standardmäßig ist das /foo/include, nicht /foo/netlib64/include. Ich werde diese Vorgabe nicht ändern. Paketierer müssen das Unterverzeichnis wie folgt angeben (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}

Ein schöner Aspekt beim Versand/Einbau der 32-Bit-cblas.h mit dieser Modifikation an den üblichen Ort ist, dass die Originalmechanik noch funktioniert. Nur die 64-Bit-Variante wird WeirdNEC erzwingen. Sie könnten sich entscheiden, nur die 64-Bit-Version in einem Präfix zu installieren und die anderen Teile des Ökosystems unberührt zu lassen.

Ach, komm schon … die CBLAS/cmake/cblas-config-install.cmake.in scheint -DCMAKE_INSTALL_INCLUDEDIR zu vergessen, nicht wahr?

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

(Der Kommentar ist Zucker oben.)

Ich habe das Gefühl, dass der CMake-Build viel weniger ausgereift ist, wenn man denkt. Ist es dem Projekt ernst damit, dies als primären Build zu haben, oder ist dies nur ein Drive-by-Beitrag? Ich bin wirklich versucht, das Makefile im alten Stil zu reparieren, weniger Aufregung. Aber ich habe jetzt so viel Zeit damit verbracht, das CMake-Zeug zu reparieren, das ich sowieso verabscheue. Also ich möchte es gerne hinter mich bringen.

Ich muss jetzt aufgeben … Ich habe es geschafft, cblas.h wie oben beschrieben nach cblas.h.in zu verschieben, und fügte hinzu

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)

zu CBLAS/include/CMakeLists.txt, wobei auch @HAVE_ILP64@ auf 1 oder 0 in der CMakeLists.txt der obersten Ebene definiert wurde. Aber ich kann mein Leben lang nicht herausfinden, wie man das Installationszeug, das sich in einer übergeordneten CMakeLists.txt befindet, die generierten Header installiert, oder die seltsame Kopie desselben von ${LAPACK_BINARY_DIR}/include (wirklich? A innerhalb des Quellbaums kopieren?)

Was soll das Makro append_subdir_files tun? Es scheint eine Kopie des Präfixes den Headerpfaden voranzustellen. Ich habe nicht genug oder zu viel Pfad zu den Quellheaderdateien. Ich möchte nur die Header-Dateien von HIER nach DORT installieren, verdammt.

Kann hier jemand sachkundig weiterhelfen? Ich denke, ich könnte es morgen herausfinden, aber ich bin mir nicht sicher, ob das ohne etwas in der realen Welt zur emotionalen Erleichterung zu zerschlagen ist.

Jawohl. Ich stimme dem zu und bevorzuge die Lösung, die nicht den gesamten Header repliziert. Ich finde es sauberer.

Das ist für mich zweideutig. Welche ist die sauberere Lösung? Was ich jetzt vorbereite ist folgendes:

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

Das CMakeFile soll HAVE_ILP durch 1 oder 0 ersetzen, wobei der resultierende Header für den aktuellen Build installiert wird.

(Übrigens: long würde unter Windows nicht funktionieren. Es ist long long dort … oder int64_t auf allen Plattformen mit stdint.)

Rechts. Ich habe gerade die 64-Bit-Bibliotheken (BUILD_INDEX64=ON) installiert und konnte nichts sehen, was mir sagt, dass ich WeirdNEC , LAPACK_ILP64 oder HAVE_LAPACK_CONFIG_H . Danke, dass du das bemerkt hast!

Ich stelle mir eine Zukunft vor, in der du es tust

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

Und die Dinge werden in foo/include/netlib64/cblas.h gehandhabt, ansonsten über foo/include/netlib/cblas.h (möglicherweise verlinkt auf foo/include/cblas.h).

Ich habe den Verdacht, dass du das _nicht_ meintest, aber ich möchte davon überzeugen, dass es besser ist ;-)

Entschuldigung, lass es mich erklären. Zuerst mochte ich die Idee, den ursprünglichen Header cblas.h beizubehalten und include/netlib64/cblas.h und include/netlib/cblas.h mit so etwas wie . zu erstellen

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

Sie könnten versuchen, den Header nicht zu duplizieren, indem Sie 'den' Header in /foo/include/cblas.h platzieren und /foo/include/netlib64/cblas.h diesen nur einschließen, indem Sie WeirdNEC definieren, aber das bedeutet, dass die 64 Bit- und 32-Bit-Pakete teilen sich diese gemeinsame Header-Datei, was beim Packen unübersichtlich ist. Es ist viel besser, wenn jeder seine Datei an verschiedenen Orten/Namen ablegt. Der Name muss cblas.h bleiben, weil Sie nicht herumlaufen wollen, um #include <cblas.h> Zeilen zu ersetzen.

Bearbeiten: Auch cblas.h enthalten ../cblas.h ist an sich chaotisch. Außerdem definieren wir das Installationsverzeichnis des Headers _one_ für cmake.

aber ja, wir müssten include/netlib64 und include als include-Verzeichnisse verwenden, wenn ein Header den anderen einschließt.

Standardmäßig ist das /foo/include, nicht /foo/netlib64/include. Ich werde diese Vorgabe nicht ändern. Paketierer müssen das Unterverzeichnis wie folgt angeben (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}

Das scheint mir gut zu sein. Sie würden also nur eine Alternative zum Erstellen von LAPACK hinzufügen, ohne die Compiler-Flags _erraten_ zu müssen. Aber der jetzige Weg würde auch funktionieren.

(Übrigens: long würde unter Windows nicht funktionieren. Es ist long long dort … oder int64_t auf allen Plattformen mit stdint.)

Gut zu wissen. BLAS++ und LAPACK++ verwenden int64_t anstelle von long long.

@weslleyspereira Also dir hat diese Idee zuerst gefallen:

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

mit /prefix/include/cblas.h und /prefix/include/netlib64/cblas.h, wobei letzteres ersteres findet? Aber Sie stimmen jetzt zu, dass es eine robustere Lösung ist, einen Header zu installieren, der für einen 64-Bit-Build so aussieht?

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

(long vs. int64 ist eine andere Sache, aber ich bin für diese Änderung, genau wie BLAS++)

Verdammt, ich bin mir nicht einmal sicher, ob ich mit Sicherheit davon ausgehen kann, dass `#include ".../cblas.h" nur den anderen eingerückten Header findet. Der C-Standard scheint zu sagen, dass die Suchreihenfolge implementierungsdefiniert ist, nicht unbedingt relativ zum aktuellen Header. Mein Hauptproblem als Paketierer ist, dass ich ein separates Paket für diesen gemeinsamen Header benötige oder das 64-Bit-Paket nur dafür vom 32-Bit-Paket abhängen muss. Das wäre scheiße.

Ich würde jetzt wirklich gerne mit einer solchen Änderung für pkgsrc vorankommen, um später eine Änderung für den Upstream-Code zu begleichen. Wir könnten ein neues Symbol diskutieren, um 32-Bit-Indizes oder 64-Bit-Indizes explizit mit einem der Header ( -DNETLIB_INDEX_BITS=64 ?)

Kann ich eine Einigung darüber erzielen, dass unsere beabsichtigte Lösung dies ist?

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

und

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

Jeder Build von LAPACK-Code führt zu Headern, die zumindest standardmäßig mit den installierten Bibliotheken übereinstimmen, ohne dass der Benutzer etwas definiert. IN ORDNUNG?

Ich könnte dies dann vor der bevorstehenden Veröffentlichung von pkgsrc (die Deadline nähert sich) einbringen und wir können die Details dieser Implementierung weiter besprechen, damit ich die Patches nach dem Zusammenführen hier mit einer neuen LAPACK-Version löschen kann. Mit dieser Änderung muss auch der einfache Makefile-Build repariert werden, aber ich brauche das noch nicht für _meine_ Patches, wenn ich nur den CMake-Build verwende.

(Ich muss nur irgendwie mein Temperament überprüfen, wenn ich versuche, diesen seltsamen CMake-Build zur Einreichung zu bringen, bei dem Header-Kopien in den Build-Verzeichnissen gemischt werden und sie dann nicht für die Installation gefunden werden können. Oder entscheiden Sie, ob diese kaputten .cmake-Dateien für uns von Nutzen sind, vielleicht lösche sie einfach aus install … wir haben pkg-config!)

Irgendetwas? Ich muss zugeben, dass ich in der Praxis keine Chance für eine andere Lösung sehe, da dies das Beispiel von openblas ist, der Hauptimplementierung, die wir verwenden. Ich kann mir vorstellen, Intel davon zu überzeugen, auch ein Unterverzeichnis für 64-Bit/32-Bit-Indexheader zu haben, das über ihre mkl_cblas.h und mkl_lapacke.h wickelt. Ansonsten baue ich ein einfaches Paket, das nur diese bereitstellt.

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

Momentan habe ich pkgsrc Maschinen hinzugefügt, um Builds mit der lustigen -DWeirdNEC -DHAVE_LAPACK_CONFIG_H -DLAPACK_ILP64 Zeile bereitzustellen, wobei sowohl cblas als auch cblas64 identische Header installieren. Es könnte so bleiben, aber ich denke immer noch, dass es sinnvoll ist, den Header so einzurichten, dass er dem Build-ABI entspricht.

@weslleyspereira Also dir hat diese Idee zuerst gefallen:

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

mit /prefix/include/cblas.h und /prefix/include/netlib64/cblas.h, wobei letzteres ersteres findet? Aber Sie stimmen jetzt zu, dass es eine robustere Lösung ist, einen Header zu installieren, der für einen 64-Bit-Build so aussieht?

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

Ja das ist es. Ich stimme Ihrer Lösung zu, Unterordner für die 32- und 64-Bit-Header zu haben. Ich habe das mit @langou besprochen , und er war auch überzeugt, dass dies eine gute Lösung wäre.

(long vs. int64 ist eine andere Sache, aber ich bin für diese Änderung, genau wie BLAS++)

Rechts. Dies sollte in einem anderen Thema behandelt werden.

Ich würde jetzt wirklich gerne mit einer solchen Änderung für pkgsrc vorankommen, um später eine Änderung für den Upstream-Code zu begleichen. Wir könnten ein neues Symbol diskutieren, um 32-Bit-Indizes oder 64-Bit-Indizes explizit mit einem der Header ( -DNETLIB_INDEX_BITS=64 ?)

Kann ich eine Einigung darüber erzielen, dass unsere beabsichtigte Lösung dies ist?

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

und

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

Jawohl. Ich denke, Sie können in Zukunft eine PR vorschlagen, danke! Ich persönlich finde ein neues Symbol wie NETLIB_INDEX_BITS total sinnvoll. Ich würde nur sicherstellen, dass der Standardwert 32 bleibt und dass -DWeirdNEC -DNETLIB_INDEX_BITS=64 impliziert.

Jeder Build von LAPACK-Code führt zu Headern, die zumindest standardmäßig mit den installierten Bibliotheken übereinstimmen, ohne dass der Benutzer etwas definiert. IN ORDNUNG?

Klingt gut für mich.

Ich könnte dies dann vor der bevorstehenden Veröffentlichung von pkgsrc (die Deadline nähert sich) einbringen und wir können die Details dieser Implementierung weiter besprechen, damit ich die Patches nach dem Zusammenführen hier mit einer neuen LAPACK-Version löschen kann. Mit dieser Änderung muss auch der einfache Makefile-Build repariert werden, aber ich brauche das noch nicht für _meine_ Patches, wenn ich nur den CMake-Build verwende.

Okay! Wir werden wahrscheinlich im zweiten Semester 2021 ein LAPACK-Release haben. Und ja, das Makefile sollte entsprechend angepasst werden, und ich bin bereit, dabei zu helfen.

Dies hängt etwas zusammen. Wir sollten nicht vergessen, dass die Header für netlib CBLAS nicht nur von netlib bereitgestellt werden … NumPy verwendet immer einen eigenen Header:

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

Und in diesem Header setzt es CBLAS_INDEX=size_t , anders als der Integer-Typ, der bei der Angabe von Indizes verwendet wird. Es wird ausschließlich für die Rückgabewerte einiger Funktionen verwendet:

$ 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);

Der Unterschied:

$ 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);

Ich frage mich, ob das möglicherweise Probleme verursacht. Für Netlib gibt es nur einen Indextyp, während andere Implementierungen einen anderen Rückgabewerttyp für die Indexfunktionen verwenden. OpenBLAS gibt das Beispiel. Sie sagen, isamax gibt unsigned size_t zurück, aber der C-Wrapper ruft tatsächlich eine Fortran-Funktion auf, die eine vorzeichenbehaftete Ganzzahl zurückgibt (Bearbeiten: Eine Subroutine, die einen 32- oder 64-Bit-Ganzzahlwert mit Vorzeichen in eine übergebene Referenz auf eine vorzeichenlose 64-Bit-Variable auf 64 . schreibt Bitsysteme).

Hat die Referenzimplementierung eine Meinung dazu? Ich _schätze_, dass es keine wirklichen Probleme gibt, da ein size_t-Wert immer jede nicht-negative Rückgabe von isamax() aufnehmen kann. Aber es riecht fragwürdig. (Bearbeiten: Sie könnten mit 64-Bit-Indizes auf einem 32-Bit-System bauen, bei dem size_t 32 Bit ist, oder? Dann haben Sie einen Überlauf. Zusätzlich zu der Unbequemlichkeit, size_t * in int * umzuwandeln.)

Da sich dort optimierte Implementierungen für size_t entschieden haben, sollte die Referenz diese Tatsache akzeptieren und befolgen?

Und wie gefährlich ist es eigentlich, Numpy mit Referenz-Cblas zu verknüpfen?

OpenBLAS gibt das Beispiel. (...)
Da sich dort optimierte Implementierungen für size_t entschieden haben, sollte die Referenz diese Tatsache akzeptieren und befolgen?

Ich kann sicherlich nicht für numpy (oder mkl usw.) sprechen, aber ich würde zögern zu behaupten, dass OpenBLAS in irgendeiner Form normativ ist, am wenigsten relativ zu dem, was (ich glaube) allgemein als __die__ Referenzimplementierung angesehen wird. .

Sicher. Es ist nur so, dass OpenBLAS oder MKL das sind, was die Leute in der Praxis verwenden, und beide scheinen sich darauf festgelegt zu haben

#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);

oder ähnlich

#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);

gegenüber der Referenz

#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);

Wie kommt es, dass sie hier von der Referenz abweichen? Wurde darüber kommuniziert? Außerdem … sehe ich, dass MKL und OpenBLAS eine Vielzahl von Funktionen definieren, die nicht einmal Teil von Referenz-CBLAS sind:

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);

Die Erweiterung des Standards ist also eine Sache, aber size_t vs int scheint auf 64-Bit-Systemen ein ernstes Problem zu sein. Das sollte irgendwie geregelt werden. Mir scheint, dass der Netlib-Weg sinnvoll ist: Derselbe Typ, der für Indizes verwendet wird. Da alle Fortran-Routinen am Ende so nennen

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

… eine Adresse von size_t für iamax abzugeben, das scheint einfach falsch zu sein. Ich habe keine andere Implementierung als diese Referenz in den OpenBLAS-Quellen gefunden. Sind sie nur dumm, den externen Typ so zu ändern, oder übersehe ich etwas sehr Grundlegendes? Benutzt wirklich jemand diese Funktionen?

Hallo zusammen, Referenz BLAS, Referenz CBLAS, Referenz LAPACK, zwei der Hauptschwerpunkte dieser Projekte sind (1) numerische Algorithmen und (2) die Definition gemeinsamer Schnittstellen, eine Referenzimplementierung und eine Testsuite, die diesem entspricht. Ich denke, jeder, der an diesen Projekten beteiligt ist, freut sich, von anderen Projekten (OpenBLAS, MKL usw.) über Software-Engineering, Best Practices für die Bereitstellung der Software usw. zu lernen und von ihnen zu lernen. Wir können von diesen Projekten viel lernen. (Und wir lernen auch viel von anderen numerischen linearen Algebra-Projekten!) Wie auch immer: Referenz BLAS, CBLAS, LAPACK können einige Verbesserungen in ihrer CMake-Verpackung, Schnittstellen gebrauchen, und wenn OpenBLAS (zB) einen besseren Prozess hat, ist der gut geeignet für wir, nun, ich bin alle dafür, dass wir uns auf dieses Modell zubewegen.

Um etwas Kontext hinzuzufügen, wurde das CBLAS aus einem Komitee (dem Basic Linear Algebra Subprograms Technical Forum) geboren, das von 1996 bis 2000 an der Überarbeitung des BLAS arbeitete und als Teil dessen eine C-Schnittstelle für das BLAS definierte. Sehen:
http://www.netlib.org/blas/blast-forum/
Siehe insbesondere:
http://www.netlib.org/blas/blast-forum/cinterface.pdf
Ich glaube, dass das von LAPACK angebotene CBLAS eine Implementierung der Schnittstelle ist, wie sie vor 25 Jahren vom Basic Linear Algebra Subprograms Technical Forum definiert wurde.

Wenn es Vorschläge zur Verbesserung von CBLAS gibt, senden Sie diese mit. Ich kann versuchen, dies an die verschiedenen Stakeholder weiterzugeben.

Danke für den Hinweis. Der relevante Teil scheint also B.2.2 in dieser Spezifikation zu sein, der besagt, dass BLAS_INDEX normalerweise size_t , aber auch so gewählt werden kann, dass er mit dem (vorzeichenbehafteten) Fortran-Integer-Typ identisch ist, der für verwendet wird Indizierung. Es liegt an der Umsetzung.

Es scheint also, dass beliebte optimierte Implementierungen size_t und die Netlib-Referenz dieselbe Ganzzahl gewählt hat, die sie für Fortran verwendet. Ich sehe überall Kopien von cblas.h in verschiedenen Projekten, die die lib verwenden (wie numpy, das einen Header für eine externe lib liefert), mit dieser Zeile

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

In https://github.com/LuaDist/gsl/blob/master/cblas/gsl_cblas.h wird dies begleitet von

/* 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.
 */

Das hört sich so an, als ob es in der Referenzimplementierung entstanden ist, hat sich aber seitdem geändert? Wenn ich mir 41779680d1f233928b67f5f66c0b239aecb42774 anschaue … sehe ich, dass der CBLAS_INDEX Schalter mit WeirdNEC schon vor dem 64-Bit-Build vorhanden war. Wow, ist dieser Commit neu. Jetzt sehe ich, dass size_t bis 2015 in der Referenz cblas.h war, 83fc0b48afd1f9a6d6f8dddb16e69ed7ed0e7242 es geändert und die WeirdNEC-Definition eingeführt hat. Ich hätte nicht gedacht, dass das so neu ist! Wild verwirrend.

Ich sehe auch, dass die frühere Version von cblas.h int an den Fortran-Aufruf übergeben hat, jetzt CBLAS_INDEX . Dies scheint jetzt richtig zu sein, mit der konsequenten Verwendung von CBLAS_INDEX als Integer-Typ und dem Schalter für 32 oder 64 Bit im Fortran-Teil.

Aber könnte es sein, dass die optimierten Bibliotheken, die eine alte Version von cblas.h mit size_t geerbt haben, aber Quellen mit aktuellem CBLAS-Code aus der Referenz synchronisieren, einen netten Fehler haben? Machen sie nicht so etwas für den 32-Bit-Fall auf einem 64-Bit-System?

#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;
}

Das führt zu

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

Es hilft, den Wert size_t auf Null zu initialisieren, aber wahrscheinlich nur im Little-Endian-Fall. Bekommt das niemand Ärger? Mir muss etwas fehlen.

Schlussfolgern:

  1. Referenz CBLAS hatte zuerst size_t als Rückgabewert.
  2. Im eigentlichen Anruf nach Fortran wurde jedoch int verwendet.
  3. Downstream (optimierte BLAS, CBLAS-Benutzer) laufen mit der alten Version des Headers.
  4. Referenz CBLAS führt den WeirdNEC-Hack für ein bestimmtes System ein und ersetzt size_t durch int oder long (entspricht der Fortran-Seite?!)
  5. Die 64-Bit-Referenz-CBLAS-Schnittstelle wird darauf aufgebaut und verwendet überall CBLAS_INDEX für die Fortran-Standard-Ganzzahl.
  6. Downstreams machten ihr eigenes Ding mit 64-Bit-Unterstützung, aber trennten sie von CBLAS_INDEX , was immer size_t ist.
  7. Downstreams erben die CBLAS-Wrapper, die CBLAS_INDEX , um Fortran aufzurufen, das eine Standard-Ganzzahl erwartet.

Das klingt im Ergebnis nach einem wunderbaren Bruch. Header und Code divergierten. Wie kommt es, dass noch niemand Probleme bemerkt hat? Oder habe ich den Teil übersehen, in dem der Referenz-CBLAS-Wrapper-Code für isamax und Freunde nicht verwendet wird?

OpenBLAS verwendet zumindest nicht den CBLAS-Wrapper-Code von Reference-LAPACK (und hat es nie getan, der Quellcode ist da, wird aber nicht gebaut)

@martin-frbg Gut zu wissen. Können Sie einen Codepfad für beispielsweise x86-64 angeben, der zeigt, wie size_t an die eigentliche Berechnung für cblas_isamax() weitergegeben wird? Ich habe einige spezifische Kernel-Implementierungen gefunden, bin mir aber im allgemeinen Fall nicht sicher.

Es wäre gut zu wissen, dass niemand tatsächlich ein (size_t*) an die Fortran-Schnittstelle weitergibt.

Es ist sicher nicht gut, dass Projekte einfach davon ausgehen

size_t cblas_isamax(…)

wenn die eigentliche Bibliothek ein int oder long (oder int64_t) als Rückgabewert anbieten könnte. Könnte die meiste Zeit mit Werten in 64-Bit-Registern funktionieren, aber es ist nicht schön. Können wir dies in den Implementierungen korrigieren? Die Leute haben in den letzten 5 Jahren das Beispiel von Netlib über die konsequente Verwendung von CBLAS_INDEX aufgegriffen.

relevanter Code befindet sich in OpenBLAS/interface, zB wird interface/imax.c zu cblas_isamax() kompiliert, wenn CBLAS definiert wird, kein Fortran-Code in seinen Aufrufgraphen eingebunden.

Ah gut. Der einzige wirklich problematische Fall besteht darin, dass abhängige Projekte eine Kopie von cblas.h verwenden, die nicht in die Bibliothek passt.

Ich finde keine tatsächliche Verwendung von cblas_isamax() und Freunden in NumPy (und SciPy), daher könnte dies nur ein theoretisches Problem sein. Es sollte trotzdem behoben werden. So:

  1. Andere folgen dem Netlib-Beispiel für die Verwendung von int32_t/int64_t (seien wir dabei explizit;-) BLAS_INDEX sowohl für Größenrückgaben als auch für Indexargumente.
  2. Netlib gibt nach und kehrt für diese Rückgaben wie andere zu size_t zurück.

Ist dies ein separates Thema zu diskutieren? Es hängt jedoch von der Wahl der 32- oder 64-Bit-Bibliothek ab.

PS: Ich bin mir immer noch nicht sicher, ob Enumerationen in der API eine gute Idee sind (als tatsächlicher Datentyp für Funktionsargumente und Strukturmember), da es Compileroptionen gibt, um zu ändern, welche Ganzzahl darunter verwendet wird. In der Praxis nicht so relevant, macht mich aber trotzdem unruhig.

Je mehr ich darüber nachdenke, desto eher tendiere ich zu Option 2: Wir hatten size_t schon sehr lange in der API. Dann hat Netlib dieses size_t in int oder long geändert. Unabhängig davon, was besser zum Fortran-Code passt oder konsistenter sein könnte, wurde die size_t-API etabliert und die Netlib-Referenz hat das gebrochen.

Soll ich eine PR über das Ändern von Dingen eröffnen für

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);

wieder? An dieser Position sollte kein Makro mehr sein, um zu betonen, dass es immer size_t ist, überall, Vergangenheit und Zukunft.

In https://github.com/numpy/numpy/issues/19243 sind wir nun im Wesentlichen bei der Sache angekommen: „Screw Netlib, size_t funktioniert für alle anderen“.

Es gibt drei Gründe, size_t :

  1. Alle C- und C++-Standardbibliotheksfunktionen akzeptieren und geben diesen Wert zurück, zB void* malloc(size_t) , size_t strlen() oder std::size_t std::vector<T>::size() (C++). Durch die Verwendung von size_t das Abschneiden von Werten und vorzeichenbehaftete/vorzeichenlose Konvertierungen vermieden.
  2. size_t wird oft verwendet, um Mengen auszudrücken, die nicht negativ sein können, zB Matrixdimensionen.
  3. Die C- und C++-Standards garantieren, dass Sie die Größe eines beliebigen Arrays in einem size_t speichern und alle Elemente mit size_t indizieren können, vgl. cppference.com: size_t .

Bearbeiten: Sie könnten mit 64-Bit-Indizes auf einem 32-Bit-System erstellen, bei dem size_t 32 Bit beträgt, oder? Dann hast du einen Überlauf.

Nein, denn ein 32-Bit-System kann mehr als 4 GB virtuellen Speicher haben (Linux unterstützt dies), aber ein einzelner 32-Bit-Prozess kann nie auf mehr als 4 GB zugreifen. Das heißt, die oberen 32-Bit der 64-Bit-Indizes werden nie verwendet.

_Speicherlimit für einen 32-Bit-Prozess, der auf einem 64-Bit-Linux-Betriebssystem ausgeführt wird_

Ich denke auch, dass es richtig ist, size_t beizubehalten, denn der Wechsel davon war der ABI-Bruch und brachte Netlib aus der Synchronisation mit dem Rest der Welt.

Aber ich fühle mich gezwungen, an deinen Argumenten herumzuhacken ;-)

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

Als ich dies recherchierte, stolperte ich über das Eingeständnis, dass es ein historischer Fehler war, einen vorzeichenlosen Typ für C++-Container-Indizes und wahrscheinlich sogar den Rückgabetyp der size()-Methode zu verwenden, da Sie am Ende schnell vorzeichenbehaftete und vorzeichenlose Zahlen mischen irgendwie. Der aktuelle Zustand von Netlib wäre mit sich selbst konsistent und würde immer vorzeichenbehaftete Typen für Größe und Indizes verwenden, aber natürlich inkonsistent mit malloc() , das eine Größe ohne Vorzeichen erfordert, um tatsächlich den gesamten Speicher adressieren zu können, der hineinpasst 32 Bit (oder theoretisch 64 Bit).

Ich frage mich richtig darüber in Code, den ich geschrieben habe, wo ich schließlich einen Index als Offset an einen Funktionsaufruf übergeben habe. Der Index ist ohne Vorzeichen, der Offset mit Vorzeichen. Abgesehen davon, dass Compiler (MSVC) durch -unsigned_value verwirrt werden, würde dies bedeuten, dass ich mich immer um einen möglichen Überlauf bei der Konvertierung kümmern muss.

Aber wie auch immer, wenn es nur darum geht, Speichergrößen zu berechnen, die malloc() und Freunden übergeben werden können, ist size_t die natürliche Sache, und es gab es schon früher in CBLAS.

Bei möglichen Problemen mit dem aktuellen Status des Codes: Nichtübereinstimmung mit cblas.h Anbietern in Builds:

Nein, denn ein 32-Bit-System kann mehr als 4 GB virtuellen Speicher haben (Linux unterstützt dies), aber ein einzelner 32-Bit-Prozess kann nie auf mehr als 4 GB zugreifen. Das heißt, die oberen 32-Bit der 64-Bit-Indizes werden nie verwendet.

Richtig, size_t bleibt 32 Bit. Wenn Sie (so albern es auch sein mag) cblas_isamax() , um eine 64-Bit-Ganzzahl zurückzugeben, nachdem Sie den Build gehackt haben, um nicht long , sondern int64_t , was wird natürlich? passiert wirklich bei einer solchen Verwendung?

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

Die x86-Aufrufkonvention könnte den 64-Bit-Wert in EAX und EDX einfügen. Oder es könnte mit einer Zeigerrückgabe und einem Puffer funktionieren. Aber was würden andere Architekturen tun? Sie erhalten also möglicherweise keine Korruption, aber mit Sicherheit einen falschen Wert. Im besten Fall werden die höheren 32 Bit ignoriert.

Stellen Sie sich nun ein 32-Bit-Big-Endian-System (eine Form von ARM) vor … sicher, dass Sie sogar die gewünschte Hälfte des Wertes zurückbekommen?

Sie könnten nicht wirklich mit nicht-sparse-Daten arbeiten, die 64-Bit-Indizes im 32-Bit-Programm benötigen. Aber nur in der Lage zu sein, einen nicht übereinstimmenden Funktionsaufruf durchzuführen, der _am_least_ falsche Ergebnisse liefert, scheint ungesund.

Ich habe ein paar schnelle Tests durchgeführt … unter x86-Linux ( gcc -m32 auf einem x86-64-System) lassen Sie einfach die oberen 32 Bits weg.

Der interessantere Fall … 64 bit size_t:

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

Auch auf x86-64 macht die eigentümliche Beziehung zwischen 64-Bit-RAX und 32-Bit-EAX die Dinge etwas klar definiert, um auch die oberen 32 Bits stillschweigend auf Null zu setzen, sobald Sie eine 32-Bit-Operation auf dem gemeinsam genutzten Register durchführen. Aber es macht Spaß mit einer etwas seltsamen Funktionsdefinition:

$ 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

Sie könnten argumentieren, ob es für den Compiler klug ist, mit dem vollen 64-Bit-Register zu arbeiten und die oberen 32 Bit für eine Funktion leer zu lassen, von der erwartet wird, dass sie einen 32-Bit-Wert zurückgibt, aber es ist völlig legal, wenn Sie sich nur auf den Aufrufer verlassen Ich denke, mit den unteren 32 Bit.

$ 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

Spaß. Ein 32-Bit-Rückgabewert, der mehr liefert, als in 32 Bit möglich ist. Dies kann (im Prinzip) passieren, wenn der aktuelle Zustand von Netlib CBLAS mit Code verknüpft ist, der size_t erwartet. Ich vermute jedoch, dass die oberen 32 Bits von RAX im tatsächlichen Code in der Praxis Null sein werden. Aber wer weiß … der Compiler erwartet, dass der Aufrufer auf keiner Plattform mehr als die unteren 32 Bit verwendet … könnte genauso gut Müll dort speichern.

Also … sind wir uns einig, Netlib zurück zu size_t als Rückgabewert zu verschieben?

Danke für all diese wertvollen Kommentare!

Ich habe dieses Thema ein wenig mit @langou diskutiert. Basierend auf der Diskussion hier lautet mein Vorschlag:

In einer separaten PR:

  1. Wir gehen zurück zu einem cblas.h , das zwei Integer-Definitionen verwendet, sagen wir CBLAS_INDEX und CBLAS_INT. Das passiert in MKL (CBLAS_INDEX und MKL_INT) und OpenBLAS (CBLAS_INDEX und blasint). CBLAS_INDEX wird nur bei der Rückgabe von i*amax . Damit stellen wir ein ABI wieder her, das mit anderen BLAS kompatibel ist.
  2. Außerdem wählen wir als Standardwert für CBLAS_INDEX size_t und sammeln Meinungen aus der Community.

Ich denke, dies ist eine ausgerichtete (oder vielleicht die gleiche) Idee hinter den jüngsten Diskussionen in diesem Thread.
Wie @dhrpc betonte,
https://github.com/Reference-LAPACK/lapack/commit/83fc0b48afd1f9a6d6f8dddb16e69ed7ed0e7242 hat den Standardwert von CBLAS_INDEX geändert und
https://github.com/Reference-LAPACK/lapack/commit/41779680d1f233928b67f5f66c0b239aecb42774 hat die Verwendung von CBLAS_INDEX geändert.

Nur zur Verstärkung:

  • OpenBLAS, MKL, GNU Scientific Library und Numpy verwenden standardmäßig size_t .
  • Die C-Schnittstelle für BLAS (https://www.netlib.org/blas/blast-forum/cinterface.pdf) gibt an, dass normalerweise CBLAS_INDEX = size_t .

Sind Sie einverstanden? Wenn Sie das tun, kann ich die PR öffnen. Oder vielleicht möchte @dhrpc das tun.

Ich stimme zu. Und bitte mach einfach weiter mit der PR.

@mgates3 erwähnte mir die Diskussion in der Slate Google Group:
https://groups.google.com/a/icl.utk.edu/g/slate-user/c/f5y6gt0aoLs/m/oQyyhikwCgAJ
In der Diskussion geht es nicht darum, was "CBLAS_INDEX" sein soll, sondern mehr, was "CBLAS_INT" sein soll. Sollte CBLAS_INT size_t oder vorzeichenbehaftete Ganzzahl oder etc. sein? Ich denke, die Teilnehmer machen gute Punkte, also gebe ich weiter.

Siehe #588.

Richtig, size_t bleibt 32 Bit. Wenn Sie (so dumm es auch sein mag) cblas_isamax() , um eine 64-Bit-Ganzzahl zurückzugeben, nachdem Sie den Build gehackt haben, um nicht long , sondern int64_t , natürlich, was wird? passiert wirklich bei einer solchen Verwendung?

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

Die x86-Aufrufkonvention könnte den 64-Bit-Wert in EAX und EDX einfügen. Oder es könnte mit einer Zeigerrückgabe und einem Puffer funktionieren. Aber was würden andere Architekturen tun? Sie erhalten also möglicherweise keine Korruption, aber mit Sicherheit einen falschen Wert. Im besten Fall werden die höheren 32 Bit ignoriert.

Stellen Sie sich nun ein 32-Bit-Big-Endian-System (eine Form von ARM) vor … sicher, dass Sie sogar die gewünschte Hälfte des Wertes zurückbekommen?

Dies ist das Spiel vorbei. Auf 32-Bit-Arm-CPUs können vier 32-Bit-Werte in Registern übergeben und zurückgegeben werden, 64-Bit-Werte belegen zwei aufeinanderfolgende Register, siehe Abschnitt 6.1.1.1 in _Procedure Call Standard for the Arm Architecture_ . Anstatt in ein Register zu schreiben, wird der Angerufene zwei Register mit seinen 64-Bit-Ganzzahlen überladen; das ist offensichtlich ein problem. Sobald dem Aufrufer die Register für Parameter ausgehen, wird der Stack verwendet. Die Stapelausrichtung ist 32 Bit, aber anstatt 32 Bit zu lesen oder zu schreiben, schreibt der Angerufene 64 Bit; Auch hier ist das Spiel vorbei und dieses Problem (Nichtübereinstimmung der Stack-Lese-/Schreibgrößen) sollte irgendwann bei allen Befehlssatzarchitekturen zu Problemen führen.

Ich frage mich richtig darüber in Code, den ich geschrieben habe, wo ich schließlich einen Index als Offset an einen Funktionsaufruf übergeben habe. Der Index ist ohne Vorzeichen, der Offset mit Vorzeichen. Abgesehen davon, dass Compiler (MSVC) durch -unsigned_value verwirrt werden, würde dies bedeuten, dass ich mir immer Sorgen um einen möglichen Überlauf bei der Konvertierung machen muss.

Nein, die Standardkomitees hinter C und C++ sorgen dafür, dass sich Ihr Code in diesem Fall so verhält, wie es offensichtlich ist: Wenn u ein Wert ohne Vorzeichen und s ein Wert mit Vorzeichen ist, wobei u hat mindestens so viele Bits wie s , dann liefert u + s das mathematisch korrekte Ergebnis, es sei denn, u + s über- oder unterschreitet. Wenn es unter-/überläuft, wird das Ergebnis umgebrochen, dh (u + s) mod 2^b , wobei b die Anzahl der Bits in u und s . Wenn der vorzeichenbehaftete Typ hingegen alle Werte des vorzeichenlosen Typs darstellen kann, wird der vorzeichenlose Wert in den vorzeichenlosen Typ umgewandelt.

Die relevanten Abschnitte im C11-Standardentwurf sind die folgenden:

  • 6.2.5.9: Binäre Operationen mit nur vorzeichenlosen Operanden können nicht überlaufen; das Ergebnis wird modulo MAX + 1 , wobei MAX der größte darstellbare Wert ist.
  • 6.3.1.3: Bei einem Wert s Vorzeichen s wenn s >= 0 , andernfalls wird er in s + MAX + 1 .
  • 6.3.1.8: Vorzeichenbehaftete und vorzeichenlose Operanden [gleicher Größe] werden in vorzeichenlose umgewandelt; ein vorzeichenloser Operand wird in einen vorzeichenbehafteten Typ umgewandelt, wenn der vorzeichenbehaftete Typ alle Werte des vorzeichenlosen Typs darstellen kann

Daher wird u + s (C-Syntax) ausgewertet zu

  • (u + s) mod (M + 1) wenn s >= 0 ,
  • (u + s + M + 1) mod (M + 1) sonst.

Ohne Über- oder Unterlauf wird dieser Ausdruck zu u + s ausgewertet, was das intuitiv gewünschte Ergebnis ist.

Als ich dies recherchierte, stolperte ich über das Eingeständnis, dass es ein historischer Fehler war, einen vorzeichenlosen Typ für C++-Container-Indizes und wahrscheinlich sogar den Rückgabetyp der size()-Methode zu verwenden, da Sie am Ende schnell vorzeichenbehaftete und vorzeichenlose Zahlen mischen irgendwie.

Es gibt einige C++-Programmierer (einschließlich des Erfinders von C++), die vorschlagen, überall vorzeichenbehaftete Ganzzahlen zu verwenden, siehe die C++-Kernrichtlinien, aber ich würde dies nicht als Eingeständnis bezeichnen. Das Problem mit der Richtlinie "Ganzzahlen mit Vorzeichen überall" sind

  • Prüfung auf Minimalwerte: Bei einer Ganzzahl ohne Vorzeichen ist es in vielen Fällen überflüssig, den Minimalwert zu prüfen, bei einer Ganzzahl mit Vorzeichen ist sie obligatorisch; dies ist fehleranfällig und kann zu Sicherheitsproblemen führen, siehe zum Beispiel CWE-839 _Numeric Range Comparison Without Minimum Check_ .
  • overflows: ein vorzeichenloser Überlauf hat ein wohldefiniertes Ergebnis, während ein vorzeichenbehafteter Integer-Überlauf ein undefiniertes Verhalten darstellt.

Sie können versuchen, mit dem Ausdruck a + b < a nach einem signierten Überlauf zu suchen, aber der Compiler könnte ihn ohne Vorwarnung GCC-Bug 30475 _assert(int+100 > int) optimiert weg_ von 2007. Dies würde funktionieren mit vorzeichenlosen Ganzzahlen ( a unsigned, b möglicherweise vorzeichenbehaftet und b mit höchstens so vielen Bits wie a ). Betrachtet man den Artikel

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

JHenneberg picture JHenneberg  ·  10Kommentare

Peter9606 picture Peter9606  ·  7Kommentare

Dichloromethane picture Dichloromethane  ·  11Kommentare

weslleyspereira picture weslleyspereira  ·  5Kommentare

pablosanjose picture pablosanjose  ·  41Kommentare