Drucken Sie eine __m128i-Variable

Lesezeit: 9 Minuten

Benutzer-Avatar
arunmoezhi

Ich versuche zu lernen, mit Intrinsic zu codieren, und unten ist ein Code, der Additionen durchführt

compiler used: icc

#include<stdio.h>
#include<emmintrin.h>
int main()
{
        __m128i a = _mm_set_epi32(1,2,3,4);
        __m128i b = _mm_set_epi32(1,2,3,4);
        __m128i c;
        c = _mm_add_epi32(a,b);
        printf("%d\n",c[2]);
        return 0;
}

Ich bekomme den folgenden Fehler:

test.c(9): error: expression must have pointer-to-object type
        printf("%d\n",c[2]);

Wie drucke ich die Werte in der Variablen c was vom Typ ist __m128i

  • Beachte das auch __m128i hat keine Informationen über den Typ, der gespeichert wird. Es können 8-Bit-Ints, 16-Bit-Ints, 32-Bit usw. sein. Einige Compiler unterstützen die .m128i_i32 Felderweiterungen. Aber es ist definitiv kein Standard und nicht in GCC.

    – Mystisch

    6. November 2012 um 18:59 Uhr

  • im Zusammenhang mit dem Titel: Wie drucke ich die __uint128_t-Nummer mit gcc?

    – jfs

    6. November 2012 um 19:00 Uhr

  • Beachten Sie, dass einige Compiler eine eingebaute printf-Unterstützung für SIMD-Typen haben, z. B. Apples Versionen von gcc, clang usw., die alle unterstützt werden %vld zum Drucken u __m128i als 4 x 32 bit ints.

    – PaulR

    6. November 2012 um 19:12 Uhr


  • Ich verwende den Intel-Compiler

    – arunmoezhi

    6. November 2012 um 19:15 Uhr

  • Gibt es eine Möglichkeit, eine maskierte Addition durchzuführen? Angenommen, ich möchte nur die alternativen Elemente speichern (ca[0],c[2])?

    – arunmoezhi

    6. November 2012 um 19:18 Uhr

Benutzer-Avatar
fragenmish

Verwenden Sie diese Funktion, um sie auszudrucken:

#include <stdint.h>
#include <string.h>

void print128_num(__m128i var)
{
    uint16_t val[8];
    memcpy(val, &var, sizeof(val));
    printf("Numerical: %i %i %i %i %i %i %i %i \n", 
           val[0], val[1], val[2], val[3], val[4], val[5], 
           val[6], val[7]);
}

Sie teilen 128 Bit in 16 Bit (oder 32 Bit) auf, bevor Sie sie drucken.

Dies ist eine Möglichkeit zum 64-Bit-Splitten und -Drucken, wenn Sie über 64-Bit-Unterstützung verfügen:

#include <inttypes.h>

void print128_num(__m128i var) 
{
    int64_t v64val[2];
    memcpy(v64val, &var, sizeof(v64val));
    printf("%.16llx %.16llx\n", v64val[1], v64val[0]);
}

Notiz: Gießen der &var direkt zu einem int* oder uint16_t* würde auch MSVC funktionieren, aber dies verstößt gegen striktes Aliasing und ist ein undefiniertes Verhalten. Verwenden memcpy ist der standardkonforme Weg, dasselbe zu tun, und mit minimaler Optimierung generiert der Compiler genau denselben Binärcode.

  • Ersetzen llx mit lld wenn du int willst

    – fragmisch

    6. November 2012 um 18:52 Uhr

  • Es klappt. Ich habe uint32_t verwendet, um die 32-Bit-Ganzzahlen zu drucken. Aber die Ausgabe ist umgekehrt. Anstatt von 2,4,6,8 Ich bekomme 8,6,4,2. Tut _mm_add_epi32 Werte in umgekehrter Reihenfolge speichern?

    – arunmoezhi

    6. November 2012 um 19:00 Uhr


  • @NateEldredge: Wahrscheinlich nicht. EIN _mm_extract_epi32, oder in einem lokalen Array speichern sind normaler. Sie könnten auch a zuweisen union von a __m128i und ein Array. Dies ist in Ordnung für Test-/Debug-Drucke wenn Es funktioniert, wenn Sie es versuchen. Ein Debugger zeigt Ihnen jedoch einfacher als Debug-Ausdrucke, was sich in Ihren Vektoren befindet.

    – Peter Cordes

    17. April 2016 um 2:17 Uhr


  • Auch : __m128i bp = _mm_set_epi32(0xFF, 0xfe,0xfa,0xfb); std::cout << std::setfill('0') << std::hex<<std::setw(16)<< bp.m128i_i64[1]<<std::setw(16)<< bp.m128i_i64[0];

    – Алексей Неудачин

    25. Oktober 2018 um 14:06 Uhr

  • Wie wäre es mit int *val = (int*)&var? Dann bräuchte man die nicht memcpy.

    – Nanashi No Gombe

    15. Juni 2020 um 21:49 Uhr


Benutzer-Avatar
Peter Kordes

  • Über gcc/clang/ICC/MSVC, C und C++ portierbar.
  • völlig sicher bei allen Optimierungsstufen: keine strikte Aliasing-Verletzung UB
  • print in hex als u8-, u16-, u32- oder u64-Elemente (basierend auf der Antwort von @ AG1)
  • Druckt in Speicherreihenfolge (das niederwertigste Element zuerst, z _mm_setr_epiX). Kehren Sie die Array-Indizes um, wenn Sie es vorziehen, in der gleichen Reihenfolge zu drucken, die Intels Handbücher verwenden, wobei sich das wichtigste Element auf der linken Seite befindet (wie z _mm_set_epiX). Verwandte: Konvention zum Anzeigen von Vektorregistern

Verwendung einer __m128i* aus einem Array von zu laden int ist sicher, weil die __m128 Typen sind so definiert, dass sie Aliasing erlauben, genau wie ISO C unsigned char*. (z. B. in den Kopfzeilen von gcc enthält die Definition __attribute__((may_alias)).)

Die Umkehrung ist nicht sicher (zeigt auf eine int* auf einen Teil von a __m128i Objekt). MSVC garantiert, dass dies sicher ist, aber GCC/clang nicht. (-fstrict-aliasing ist standardmäßig aktiviert). Es funktioniert manchmal mit GCC/clang, aber warum es riskieren? Es stört manchmal sogar die Optimierung; siehe diese Fragen und Antworten. Siehe auch Ist `reinterpret_cast`ing zwischen Hardware-SIMD-Vektorzeiger und dem entsprechenden Typ ein undefiniertes Verhalten?

Siehe GCC AVX _m256i Umwandlung in int Array führt zu falschen Werten für ein reales Beispiel für GCC-Breaking-Code, der auf zeigt int* an einer __m256i.


(uint32_t*) &my_vector verstößt gegen die C- und C++-Aliasing-Regeln und funktioniert nicht garantiert so, wie Sie es erwarten. Das Speichern in einem lokalen Array und der anschließende Zugriff darauf ist garantiert sicher. Es optimiert sogar mit den meisten Compilern, so dass Sie es bekommen movq / pextrq direkt von xmm zu Integer-Registern statt an tatsächlich z.B. speichern/neu laden.

Source + asm-Ausgabe im Godbolt-Compiler-Explorer: Beweis, dass es mit MSVC kompiliert und so weiter.

#include <immintrin.h>
#include <stdint.h>
#include <stdio.h>

#ifndef __cplusplus
#include <stdalign.h>   // C11 defines _Alignas().  This header defines alignas()
#endif

void p128_hex_u8(__m128i in) {
    alignas(16) uint8_t v[16];
    _mm_store_si128((__m128i*)v, in);
    printf("v16_u8: %x %x %x %x | %x %x %x %x | %x %x %x %x | %x %x %x %x\n",
           v[0], v[1],  v[2],  v[3],  v[4],  v[5],  v[6],  v[7],
           v[8], v[9], v[10], v[11], v[12], v[13], v[14], v[15]);
}

void p128_hex_u16(__m128i in) {
    alignas(16) uint16_t v[8];
    _mm_store_si128((__m128i*)v, in);
    printf("v8_u16: %x %x %x %x,  %x %x %x %x\n", v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7]);
}

void p128_hex_u32(__m128i in) {
    alignas(16) uint32_t v[4];
    _mm_store_si128((__m128i*)v, in);
    printf("v4_u32: %x %x %x %x\n", v[0], v[1], v[2], v[3]);
}

void p128_hex_u64(__m128i in) {
    alignas(16) unsigned long long v[2];  // uint64_t might give format-string warnings with %llx; it's just long in some ABIs
    _mm_store_si128((__m128i*)v, in);
    printf("v2_u64: %llx %llx\n", v[0], v[1]);
}

Wenn Sie Portabilität auf C99 oder C++03 oder früher (dh ohne C11 / C++11) benötigen, entfernen Sie die alignas() und verwenden storeu Anstatt von store. Oder verwenden __attribute__((aligned(16))) oder __declspec( align(16) ) stattdessen.

(Wenn Sie Code mit Intrinsic schreiben, sollten Sie eine neuere Compiler-Version verwenden. Neuere Compiler machen normalerweise besser asm als ältere Compiler, einschließlich für SSE/AVX-Intrinsic. Aber vielleicht möchten Sie gcc-6.3 mit verwenden -std=gnu++03 C++03-Modus für eine Codebasis, die nicht für C++11 oder so bereit ist.)


Beispielausgabe vom Aufrufen aller 4 Funktionen

// source used:
__m128i vec = _mm_setr_epi8(1, 2, 3, 4, 5, 6, 7,
                            8, 9, 10, 11, 12, 13, 14, 15, 16);

// output:

v2_u64: 0x807060504030201 0x100f0e0d0c0b0a09
v4_u32: 0x4030201 0x8070605 0xc0b0a09 0x100f0e0d
v8_u16: 0x201 0x403 0x605 0x807  | 0xa09 0xc0b 0xe0d 0x100f
v16_u8: 0x1 0x2 0x3 0x4 | 0x5 0x6 0x7 0x8 | 0x9 0xa 0xb 0xc | 0xd 0xe 0xf 0x10

Passen Sie die Formatzeichenfolgen an, wenn Sie für eine konsistente Ausgabebreite mit führenden Nullen auffüllen möchten. Sehen printf(3).

Benutzer-Avatar
Antonio

Ich weiß, dass diese Frage mit C gekennzeichnet ist, aber es war das beste Suchergebnis, auch wenn nach einer C++-Lösung für dasselbe Problem gesucht wurde.

Dies könnte also eine C++-Implementierung sein:

#include <string>
#include <cstring>
#include <sstream>

#if defined(__SSE2__)
template <typename T>
std::string __m128i_toString(const __m128i var) {
    std::stringstream sstr;
    T values[16/sizeof(T)];
    std::memcpy(values,&var,sizeof(values)); //See discussion below
    if (sizeof(T) == 1) {
        for (unsigned int i = 0; i < sizeof(__m128i); i++) { //C++11: Range for also possible
            sstr << (int) values[i] << " ";
        }
    } else {
        for (unsigned int i = 0; i < sizeof(__m128i) / sizeof(T); i++) { //C++11: Range for also possible
            sstr << values[i] << " ";
        }
    }
    return sstr.str();
}
#endif

Verwendungszweck:

#include <iostream>
[..]
__m128i x
[..]
std::cout << __m128i_toString<uint8_t>(x) << std::endl;
std::cout << __m128i_toString<uint16_t>(x) << std::endl;
std::cout << __m128i_toString<uint32_t>(x) << std::endl;
std::cout << __m128i_toString<uint64_t>(x) << std::endl;

Ergebnis:

141 114 0 0 0 0 0 0 151 104 0 0 0 0 0 0
29325 0 0 0 26775 0 0 0
29325 0 26775 0
29325 26775

Hinweis: Es gibt eine einfache Möglichkeit, dies zu vermeiden if (size(T)==1)siehe https://stackoverflow.com/a/28414758/2436175

  • Du solltest benutzen alignas(16) T values[16/sizeof(T)]; und _mm_storeu_si128( (__m128i*)values, var); Der Rest des Codes funktioniert dann einwandfrei. Und vereinfacht, weil man ein Range-for-like verwenden kann for(T v : values)Ich finde.

    – Peter Cordes

    15. Oktober 2017 um 7:13 Uhr


  • @PeterCordes Ich verstehe deinen Punkt. Ich frage mich, ob man stattdessen einfach ein memcpy verwenden könnte, das würde die Notwendigkeit ersparen, einen ausgerichteten Puffer zu benötigen.

    – Antonio

    15. Oktober 2017 um 22:19 Uhr

  • Siehe meine Antwort. Verwenden storeu Anstatt von store wenn Sie kein C++11 für haben alignas, oder Compiler-spezifische Anweisungen. Es wird wahrscheinlich noch wegoptimiert. (Und übrigens, modernes Windows / Linux richtet den Stapel bereits um 16B aus, sodass es den Compiler nichts kostet, den Puffer auszurichten, wenn er tatsächlich speichert / neu lädt.)

    – Peter Cordes

    15. Oktober 2017 um 22:24 Uhr

  • @PeterCordes Doch ist memcpy keine gültige Alternative?

    – Antonio

    16. Oktober 2017 um 2:02 Uhr

  • Ja, es ist nur ein Leistungsproblem, wenn Sie es mit einer Nicht-Potenz-von-2-Klasse verwenden, nicht uint*_t. Es ist sinnvoll, es aus Gründen der Lesbarkeit unverändert zu lassen. (Vor allem, da die Verwendung von std::string und einen String-Stream zum Drucken eines Vektors.) Wenn Sie dies in eine Bibliothek stellen würden, damit die Leute es verwenden können, ohne es anzusehen, anstatt einer SO-Antwort, würden Sie andere Entscheidungen treffen.

    – Peter Cordes

    17. Oktober 2017 um 8:00 Uhr


Benutzer-Avatar
Lucien

#include<stdio.h>
#include<emmintrin.h>
int main()
{
    __m128i a = _mm_set_epi32(1,2,3,4);
    __m128i b = _mm_set_epi32(1,2,3,4);
    __m128i c;

    const int32_t* q; 
    //add a pointer 
    c = _mm_add_epi32(a,b);

    q = (const int32_t*) &c;
    printf("%d\n",q[2]);
    //printf("%d\n",c[2]);
    return 0;
}

Versuchen Sie diesen Code.

1371660cookie-checkDrucken Sie eine __m128i-Variable

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy