Wie kann festgestellt werden, ob der Speicher ausgerichtet ist?
Lesezeit: 9 Minuten
Benutzer229898
Ich bin neu in der Optimierung von Code mit SSE/SSE2-Anweisungen und bis jetzt bin ich nicht sehr weit gekommen. Meines Wissens würde eine übliche SSE-optimierte Funktion so aussehen:
void sse_func(const float* const ptr, int len){
if( ptr is aligned )
{
for( ... ){
// unroll loop by 4 or 2 elements
}
for( ....){
// handle the rest
// (non-optimized code)
}
} else {
for( ....){
// regular C code to handle non-aligned memory
}
}
}
Wie kann ich jedoch richtig feststellen, ob der Speicher ptr zeigt auf ist ausgerichtet auf zB 16 Bytes? Ich denke, ich muss den regulären C-Codepfad für nicht ausgerichteten Speicher einschließen, da ich nicht sicherstellen kann, dass jeder Speicher, der an diese Funktion übergeben wird, ausgerichtet wird. Und die Verwendung der Intrinsics zum Laden von Daten aus dem nicht ausgerichteten Speicher in die SSE-Register scheint schrecklich langsam zu sein (sogar langsamer als normaler C-Code).
Danke im Voraus…
random-name, nicht sicher, aber ich denke, es könnte effizienter sein, die ersten paar “nicht ausgerichteten” Elemente einfach separat zu behandeln, wie Sie es mit den letzten paar tun. Dann können Sie immer noch SSE für die “mittleren” verwenden …
– Rehno-Lindeque
21. Dezember 2009 um 12:27 Uhr
Hm, das ist ein guter Punkt. Ich werde es versuchen. Vielen Dank!
– Benutzer229898
22. Dezember 2009 um 16:15 Uhr
Besser: Verwenden Sie einen skalaren Prolog, um die falsch ausgerichteten Elemente bis zur ersten Ausrichtungsgrenze zu behandeln. (gcc tut dies bei der automatischen Vektorisierung mit einem Zeiger unbekannter Ausrichtung.) Oder wenn Ihr Algorithmus idempotent ist (wie a[i] = foo(b[i])), führen Sie einen möglicherweise nicht ausgerichteten ersten Vektor aus, dann die Hauptschleife beginnend an der ersten Ausrichtungsgrenze nach dem ersten Vektor, dann einen letzten Vektor, der am letzten Element endet. Wenn das Array tatsächlich falsch ausgerichtet war und / oder die Zählung kein Vielfaches der Vektorbreite war, überlappen sich einige dieser Vektoren, aber das schlägt immer noch Skalar.
– Peter Cordes
23. August 2017 um 13:50 Uhr
Am besten: Stellen Sie eine Zuweisung bereit, die 16-Byte-ausgerichteten Speicher bereitstellt. Arbeiten Sie dann mit dem ausgerichteten 16-Byte-Puffer, ohne dass führende oder abschließende Elemente korrigiert werden müssen. Das machen Bibliotheken wie Botan und Crypto++ für Algorithmen, die SSE, Altivec und Co. verwenden.
Die Besetzung zu void * (oder Äquivalent, char *) ist notwendig, da der Standard nur eine invertierbare Konvertierung zu garantiert uintptr_t zum void *.
Wenn Sie Typsicherheit wünschen, sollten Sie eine Inline-Funktion verwenden:
und auf Compiler-Optimierungen hoffen, wenn byte_count ist eine Kompilierzeitkonstante.
Warum müssen wir umwandeln?void *?
Die C-Sprache erlaubt verschiedene Darstellungen für verschiedene Zeigertypen, zB könnten Sie einen 64-Bit haben void * Typ (der gesamte Adressraum) und ein 32-Bit foo * Typ (ein Segment).
Die Umwandlung foo * -> void * kann eine tatsächliche Berechnung beinhalten, z. B. das Hinzufügen eines Offsets. Der Standard überlässt es auch der Implementierung, was passiert, wenn (beliebige) Zeiger in Ganzzahlen umgewandelt werden, aber ich vermute, dass es oft als Noop implementiert wird.
Für eine solche Implementierung foo * -> uintptr_t -> foo * würde funktionieren, aber foo * -> uintptr_t -> void * und void * -> uintptr_t -> foo * würde nicht. Die Alignment-Berechnung würde auch nicht zuverlässig funktionieren, weil Sie nur die Alignment relativ zum Segment-Offset prüfen, was Ihren Wünschen entsprechen kann oder auch nicht.
Fazit: Immer verwenden void * um implementierungsunabhängiges Verhalten zu erhalten.
Dieses Makro sieht wirklich fies und raffiniert zugleich aus. Ich werde es auf jeden Fall testen.
– Benutzer229898
14. Dezember 2009 um 17:06 Uhr
Bitte geben Sie alle Ihnen bekannten Beispiele für Plattformen an, in denen non-void * erzeugt keinen ganzzahligen Wert im Bereich von uintptr_t. Und/oder wissen Sie, warum der Standard so formuliert ist?
– Craig McQueen
25. November 2010 um 23:07 Uhr
Warum einschränken?, sieht so aus, als würde es nichts tun, wenn es nur einen Zeiger gibt?
– Michail
23. September 2015 um 6:45 Uhr
@Mikhail: die Kombination von const * mit restrict ist eine stärkere Garantie als einfach const *: ohne restrictes ist legal, die wegzuwerfen const und den Speicher modifizieren; mit restrict vorhanden, ist es nicht; leider habe ich gelernt, dass dies in der Praxis nicht sinnvoll ist, da es nur dann zum Tragen kommt, wenn der Zeiger tatsächlich verwendet wird, was der Aufrufer nicht generell annehmen kann (dh der Nutzen liegt ausschließlich auf Seiten des Angerufenen); In diesem speziellen Fall ist es sowieso überflüssig, da wir es mit einer Inline-Funktion zu tun haben, sodass der Compiler ihren Körper sehen und selbst schlussfolgern kann, dass kein Speicher geändert wird
– Christoph
23. September 2015 um 16:52 Uhr
Wenn ein float * kann (theoretisch) eine andere Darstellung haben als a void *bedeutet das, dass die Ausrichtungsprüfung mit einem anderen als dem beabsichtigten Wert durchgeführt werden könnte?
– mwfearnley
13. März 2019 um 21:07 Uhr
Pascal Cuoq
EDIT: Gießen zu long ist eine billige Möglichkeit, sich gegen die wahrscheinlichste Möglichkeit zu schützen, dass int und Zeiger heutzutage unterschiedliche Größen haben.
Wie in den Kommentaren unten erwähnt, gibt es bessere Lösungen, wenn Sie bereit sind, einen Header einzufügen …
Ein Zeiger p ist an einer 16-Byte-Grenze iff ausgerichtet ((unsigned long)p & 15) == 0.
Sie könnten stattdessen verwenden uintptr_t – Es hat garantiert die richtige Größe, um einen Zeiger zu halten. Vorausgesetzt natürlich, Ihr Compiler definiert es.
– Anonym.
13. Dezember 2009 um 23:26 Uhr
Es spielt keine Rolle, ob die Zeiger- und Integer-Größen nicht übereinstimmen. Sie kümmern sich nur um die unteren paar Bits.
– Richard Pennington
13. Dezember 2009 um 23:29 Uhr
Ich würde normalerweise verwenden p % 16 == 0da Compiler die Potenzen von 2 normalerweise genauso gut kennen wie ich, und ich das besser lesbar finde
– Hasturkun
13. Dezember 2009 um 23:30 Uhr
@Hasturkun Division/Modulo über vorzeichenbehaftete Ganzzahlen werden in C99 nicht in bitweisen Tricks kompiliert (einige dumme Sachen zum Runden gegen Null), und es ist in der Tat ein intelligenter Compiler, der erkennt, dass das Ergebnis des Modulo mit Null verglichen wird (in dem falls das bitweise Zeug wieder funktioniert). Nicht unmöglich, aber nicht trivial. Im Allgemeinen ist es besser, in eine Ganzzahl ohne Vorzeichen umzuwandeln, wenn Sie % verwenden und den Compiler & kompilieren lassen möchten.
– Pascal Cuoq
13. Dezember 2009 um 23:34 Uhr
@Pascal Cuoq, gcc bemerkt dies und gibt genau den gleichen Code für aus (p & 15) == 0 und (p % 16) == 0 mit dem -O Flagge gesetzt. Ich habe eine Reihe anderer Compiler gesehen, die ganzzahlige Division/Modulo/Multiplikation mit einer Potenz von 2 erkennen und das Schlaue daran tun. (Ich stimme jedoch dem Casting auf unsigned zu)
– Hasturkun
13. Dezember 2009 um 23:43 Uhr
Andere Antworten schlagen eine UND-Operation vor, bei der niedrige Bits gesetzt sind und mit Null verglichen werden.
Ein einfacherer Test wäre jedoch, eine MOD mit dem gewünschten Ausrichtungswert durchzuführen und mit Null zu vergleichen.
#define ALIGNMENT_VALUE 16u
if (((uintptr_t)ptr % ALIGNMENT_VALUE) == 0)
{
// ptr is aligned
}
Ich habe dich positiv bewertet, aber nur, weil du Ganzzahlen ohne Vorzeichen verwendest 🙂
– Pascal Cuoq
13. Dezember 2009 um 23:36 Uhr
Ich glaube, das schlägt mit fehl uint8_t Typen, die manchmal Ausrichtungsanforderungen von 1 haben.
– jww
24. August 2018 um 14:07 Uhr
@jww Ich bin mir nicht sicher, ob ich verstehe, was du meinst. Eine Ausrichtungsanforderung von 1 würde im Wesentlichen keine Ausrichtungsanforderung bedeuten. Sie müssen sich keine Gedanken über die Ausrichtung machen uint8_t. Aber bitte klären, wenn ich falsch verstehe.
– Craig McQueen
29. August 2018 um 12:13 Uhr
Das u Suffix auf der Ganzzahl macht sie vorzeichenlos. Es ist gut, das Mischen von vorzeichenbehafteten und vorzeichenlosen Ausdrücken in Ausdrücken zu vermeiden, um mögliche Fallstricke zu vermeiden, die bei der Arithmetik mit gemischten Vorzeichen auftreten können. Siehe GCC-Warnung „Vergleich zwischen vorzeichenbehafteten und vorzeichenlosen Ganzzahlausdrücken“. In diesem Fall spielt es wahrscheinlich keine Rolle, aber es ist gut, sich gute Gewohnheiten anzueignen. (Ich vermute die 0 sollte sein 0u zu)
– Craig McQueen
8. August 2019 um 5:10 Uhr
Beachten Sie, dass Sie keine echte MOD-Operation verwenden sollten, da dies eine ziemlich teure Operation ist und so weit wie möglich vermieden werden sollte. Sie sollten immer die Operation and verwenden. Aber ich glaube, wenn Sie einen ausreichend ausgefeilten Compiler mit allen aktivierten Optimierungsoptionen haben, wird er Ihre MOD-Operation automatisch in einen einzigen Opcode konvertieren. (Linux-Kernel verwendet und funktioniert zu fyi)
Sie könnten die Ausrichtung zur Laufzeit überprüfen, indem Sie so etwas wie aufrufen
struct foo_type{ int bar; }foo;
assert(is_aligned(&foo)); // passes
Um zu überprüfen, ob schlechte Ausrichtungen fehlschlagen, könnten Sie dies tun
// would almost certainly fail
assert(is_aligned((foo_type*)(1 + (uintptr_t)(&foo)));
Dies ist im Grunde das, was ich benutze. Indem ich die Ganzzahl zu einer Vorlage mache, stelle ich sicher, dass die Kompilierzeit verlängert wird, sodass ich bei allem, was ich tue, nicht mit einer langsamen Modulo-Operation enden werde.
Ich überprüfe immer gerne meine Eingaben, daher die Behauptung zur Kompilierzeit. Wenn Ihr Ausrichtungswert falsch ist, wird es nicht kompiliert …
template <unsigned int alignment>
struct IsAligned
{
static_assert((alignment & (alignment - 1)) == 0, "Alignment must be a power of 2");
static inline bool Value(const void * ptr)
{
return (((uintptr_t)ptr) & (alignment - 1)) == 0;
}
};
Um zu sehen, was los ist, können Sie Folgendes verwenden:
// 1 of them is aligned...
int* ptr = new int[8];
for (int i = 0; i < 8; ++i)
std::cout << IsAligned<32>::Value(ptr + i) << std::endl;
// Should give '1'
int* ptr2 = (int*)_aligned_malloc(32, 32);
std::cout << IsAligned<32>::Value(ptr2) << std::endl;
char D[1];
assert( boost::alignment::is_aligned(&D[0], alignof(double)) ); // might fail, sometimes
Paul Tomblin
Können Sie den ptr einfach mit 0x03 (ausgerichtet auf 4s), 0x07 (ausgerichtet auf 8s) oder 0x0f (ausgerichtet auf 16s) ‘und’, um zu sehen, ob eines der niedrigsten Bits gesetzt ist?
Nein, das kannst du nicht. Ein Zeiger ist kein gültiges Argument für den &-Operator.
– Steve Jessop
13. Dezember 2009 um 23:34 Uhr
@SteveJessop, zu dem du casten könntest uintptr_t.
– Benutzer6754053
20. Dezember 2016 um 23:10 Uhr
@MarkYisri: Ja, ich erwarte, dass in der Praxis jede Implementierung, die SSE2-Anweisungen unterstützt, eine implementierungsspezifische Garantie bietet, die funktioniert 🙂
– Steve Jessop
10. Januar 2017 um 11:42 Uhr
14091500cookie-checkWie kann festgestellt werden, ob der Speicher ausgerichtet ist?yes
random-name, nicht sicher, aber ich denke, es könnte effizienter sein, die ersten paar “nicht ausgerichteten” Elemente einfach separat zu behandeln, wie Sie es mit den letzten paar tun. Dann können Sie immer noch SSE für die “mittleren” verwenden …
– Rehno-Lindeque
21. Dezember 2009 um 12:27 Uhr
Hm, das ist ein guter Punkt. Ich werde es versuchen. Vielen Dank!
– Benutzer229898
22. Dezember 2009 um 16:15 Uhr
Besser: Verwenden Sie einen skalaren Prolog, um die falsch ausgerichteten Elemente bis zur ersten Ausrichtungsgrenze zu behandeln. (gcc tut dies bei der automatischen Vektorisierung mit einem Zeiger unbekannter Ausrichtung.) Oder wenn Ihr Algorithmus idempotent ist (wie
a[i] = foo(b[i])
), führen Sie einen möglicherweise nicht ausgerichteten ersten Vektor aus, dann die Hauptschleife beginnend an der ersten Ausrichtungsgrenze nach dem ersten Vektor, dann einen letzten Vektor, der am letzten Element endet. Wenn das Array tatsächlich falsch ausgerichtet war und / oder die Zählung kein Vielfaches der Vektorbreite war, überlappen sich einige dieser Vektoren, aber das schlägt immer noch Skalar.– Peter Cordes
23. August 2017 um 13:50 Uhr
Am besten: Stellen Sie eine Zuweisung bereit, die 16-Byte-ausgerichteten Speicher bereitstellt. Arbeiten Sie dann mit dem ausgerichteten 16-Byte-Puffer, ohne dass führende oder abschließende Elemente korrigiert werden müssen. Das machen Bibliotheken wie Botan und Crypto++ für Algorithmen, die SSE, Altivec und Co. verwenden.
– jww
24. August 2018 um 14:10 Uhr