C# 使用SIMD向量类型加速浮点数组求和运算(4):用引用代替指针, 摆脱unsafe关键字,兼谈Unsafe类的使用

这篇具有很好参考价值的文章主要介绍了C# 使用SIMD向量类型加速浮点数组求和运算(4):用引用代替指针, 摆脱unsafe关键字,兼谈Unsafe类的使用。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

作者: zyl910

目录
  • 一、引言
  • 二、办法说明
    • 2.1 历史
    • 2.2 局部引用变量与引用所指的值(类似指针的 地址运算符&、间接运算符*
    • 2.3 重新分配局部引用变量(类似指针直接赋值)
    • 2.4 引用地址调整(类似指针加减法)
    • 2.5 引用地址比较(类似指针比较)
    • 2.6 重新解释(类似C++的 reinterpret_cast)
    • 2.7 引用取消只读(类似C++的 const_cast)
  • 三、将指针代码改写为引用代码
    • 3.1 代码编写
    • 3.2 测试结果
  • 四、小结
  • 参考文献

一、引言

C#没有直接提供对数据进行重新解释(C++的 reinterpret_cast)的功能,而在使用向量类型时,经常需要做这种操作。例如 第2篇文章,用了3种办法——

  1. 事先将基元类型数组转为了向量类型数组(SumVectorAvx).
  2. 使用Span改进数据加载(SumVectorAvxSpan).
  3. 使用指针改进数据加载(SumVectorAvxPtr).

第1种办法其实是将全部数据搬运了一遍,开销大。不适合生产使用,仅能用于教学演示。
剩下2种办法虽然能用,但还是存在一些缺点的——

  • Span:Span的长度(Length)是32位有符号整数(Int32),且索引一般不能为负数,导致它的地址范围只有31位,难以处理超过2GB的数据。而现在64位平台已经普及了,有时存在“处理超过2GB的数据”的需求。官方说了“我们决定将其保留为 int”(We decided to keep it as an int),Span的位数升级无望。
  • 指针:虽然在C#中能够使用指针语法,但是只能在用unsafe关键字申明的“非安全代码”里使用,且项目配置里需启用“允许非安全代码”(Allow unsafe code),使用比较繁琐。而且对于开发类库等严格审查的场合,有时会规定不启用“允许非安全代码”。其次C#的指针还存在 “不支持泛型”、“有时需要fixed”等缺点。

有没有更好的办法呢?

二、办法说明

最开始毫无头绪,直到我分析了Span的源码后,才发现C#如今能用引用来做重新解释,从而解决上述难题。
而且如今引用的功能非常强大,能完全代替指针操作,且能摆脱unsafe关键字。适合不启用“允许非安全代码”等严格的场合。

2.1 历史

C# 1.0 就支持了“ref”关键字,但仅能用于参数列表,用来表示“引用方式传参”。
C# 7.0 新增了“局部引用和引用返回”(Ref locals and returns)特性。自此C#的引用(ref)与C++的引用(&),功能很接近了.
C# 7.3 新增了“重新分配局部引用变量”(Reassign ref local variables)特性。C#的引用(ref)与C++的引用(&),几乎功能一致了.
再加上Unsafe类提供了 引用地址调整、引用地址比较、重新解释(C++的 reinterpret_cast)、引用取消只读(类似C++的 const_cast) 等功能. 使得C# 引用(ref)非常强大,能够完全代替指针(*)操作,彻底摆脱unsafe关键字。
引用比指针还多了这些优点——

  • 指针不支持泛型,而引用是支持泛型的。
  • 指针有时需要fixed,而引用无需使用fixed。

可以简单理解为这样——如今C#里的引用,比指针的功能更强大。
于是在一些官方文档中,将引用(ref)称呼为“托管指针”(Managed pointer);并将传统的指针(*),称呼为“非托管指针”(Unmanaged pointer)。

2.2 局部引用变量与引用所指的值(类似指针的 地址运算符&、间接运算符*)

摘自官方文档。

https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/statements/declarations#reference-variables
Reference 变量
声明局部变量并在变量类型之前添加 ref 关键字时,声明 reference 变量,或 ref 局部变量:
ref int alias = ref variable;

reference 变量是引用另一个变量(称为引用)的变量。 也就是说,reference 变量是其引用的别名。 向 reference 变量赋值时,该值将分配给引用。 读取 reference 变量的值时,将返回引用的值。 以下示例演示了该行为:
int a = 1;
ref int alias = ref a;
Console.WriteLine($"(a, alias) is ({a}, {alias})");  // output: (a, alias) is (1, 1)
a = 2;
Console.WriteLine($"(a, alias) is ({a}, {alias})");  // output: (a, alias) is (2, 2)
alias = 3;
Console.WriteLine($"(a, alias) is ({a}, {alias})");  // output: (a, alias) is (3, 3)

上述代码对应的C语言版代码如下。

int a = 1;
int* alias = &a;
printf("(a, alias) is (%d, %d)", a, *alias);	// output: (a, alias) is (1, 1)
a = 2;
printf("(a, alias) is (%d, %d)", a, *alias);	// output: (a, alias) is (2, 2)
*alias = 3;
printf("(a, alias) is (%d, %d)", a, *alias);	// output: (a, alias) is (3, 3)

主要是以下几点的写法不同:

  1. ref int alias与C语言的int* alias: 声明变量时,类型的前面可加上“ref”关键字,表示它是引用类型。类似C语言的变量类型加“*”,表示它是指针类型。
  2. alias = ref a与C语言的alias = &a: 将引用指向某个变量时,需要给右式中变量的前面加上“ref”关键字。类似指针的 地址运算符&
  3. 赋值时的alias与C语言的*alias: 引用在直接赋值时,都是读取或设置其指向的值(并不是更改引用的指向)。而指针须使用 间接运算符*

引用可以直接用“.”运算符来访问成员,不像指针那样还需使用“指针成员访问运算符->”。

2.3 重新分配局部引用变量(类似指针直接赋值)

上面提到引用在直接赋值时,都是读取或设置其指向的值。那么怎样才能更改引用的指向呢?
C# 7.3 新增了“重新分配局部引用变量”(Reassign ref local variables)特性,能够实现该功能。
摘自官方文档:

使用 ref 赋值运算符= ref 更改 reference 变量的引用,如以下示例所示:

void Display(int[] s) => Console.WriteLine(string.Join(" ", s));

int[] xs = { 0, 0, 0 };
Display(xs);

ref int element = ref xs[0];
element = 1;
Display(xs);

element = ref xs[^1];
element = 3;
Display(xs);
// Output:
// 0 0 0
// 1 0 0
// 1 0 3

在前面的示例中,element reference 变量初始化为第一个数组元素的别名。 然后,ref 将被重新分配,以引用最后一个数组元素。

上述代码对应的C语言版代码如下。

int[] xs = { 0, 0, 0 };
Display(xs);

int* element = &xs[0];
*element = 1;
Display(xs);

element = &xs[(sizeof(xs)/sizeof(xs[0]))-1];
*element = 3;
Display(xs);

规律是——

  • =的右边若有“ref”关键字:更改引用的指向。类似指针直接赋值。
  • =的右边若没有“ref”关键字:设置其指向的值。类似指针的 间接运算符*

上述示例中,演示了引用对数组的访问。C#中的指针若想访问数组,一般得使用fixed关键字,而引用不用。
注:上述示例还用到了C# 8.0 的一个特性—— 从末尾开始索引运算符^^1 就是指末尾的最后1个元素,于是在C语言中是 (sizeof(xs)/sizeof(xs[0]))-1 .

2.4 引用地址调整(类似指针加减法)

指针能通过加减法来调整地址,从而对一片连续的内存进行操作。
C#的引用也能够调整地址,故也能对一片连续的内存进行操作。Unsafe类提供了的Add等方法来调整引用的地址,例如以下方法。

Add<T>(T, Int32)	将偏移量添加到给定的托管指针。
Add<T>(T, IntPtr)	将元素偏移量添加到给定的托管指针。
AddByteOffset<T>(T, IntPtr)	将字节偏移量添加到给定的托管指针。
Subtract<T>(T, Int32)	从给定的托管指针中减去偏移量。
Subtract<T>(T, IntPtr)	从给定的托管指针中减去元素偏移量。
SubtractByteOffset<T>(T, IntPtr)	从给定的托管指针中减去字节偏移量。

带“ByteOffset”后缀的方法,是以字节为单位的。而没有该后缀的方法,是以元素大小为单位的,类似指针加减法。

2.5 引用地址比较(类似指针比较)

指针能够进行比较。
引用也能够进行比较。Unsafe类提供了的AreSame等方法来获得比较结果,例如以下方法。

AreSame<T>(T, T)	确定指定的托管指针是否指向同一位置。
IsAddressGreaterThan<T>(T, T)	返回一个值,该值指示指定的托管指针是否大于另一个指定的托管指针。
IsAddressLessThan<T>(T, T)	返回一个值,该值指示指定的托管指针是否小于另一个指定的托管指针。

2.6 重新解释(类似C++的 reinterpret_cast)

C++的 reinterpret_cast运算符能够对数据进行重新解释。且在C语言(或C#的非安全代码)中,可以通过对指针进行强制类型转换,从而对数据进行重新解释。
对于C#的引用,可以用Unsafe类的As方法对数据进行重新解释。
摘自官方文档。

As<TFrom,TTo>(TFrom)
将给定的托管指针重新解释为指向 TTo类型的新托管指针。

public static ref TTo As<TFrom,TTo> (ref TFrom source);

类型参数
TFrom	要重新解释的托管指针的类型。
TTo	托管指针的所需类型。

参数
source	TFrom	用于重新解释的托管指针。

返回
TTo	指向 TTo类型的新托管指针。

注解
此 API 在概念上类似于 C++ 的 `reinterpret_cast<>`。 调用方有责任确保强制转换是合法的。 不会执行运行时检查。
仅重新解释托管指针。 引用的值本身将保持不变。 请考虑以下示例。


int[] intArray = new int[] { 0x1234_5678 }; // a 1-element array
ref int refToInt32 = ref intArray[0]; // managed pointer to first Int32 in array
ref short refToInt16 = ref Unsafe.As<int, short>(ref refToInt32); // reinterpret as managed pointer to Int16
Console.WriteLine($"0x{refToInt16:x4}");

此程序的输出取决于当前计算机的端序。 在 big-endian 体系结构中,此代码输出 0x1234。 在 little-endian 体系结构上,此代码输出 0x5678。

将托管指针从较窄的类型转换为较宽的类型时,调用方必须确保取消引用指针不会产生超出边界的访问。 调用方还负责确保生成的指针正确对齐所引用的类型。
当将托管指针从较窄类型强制转换为较宽类型时,调用方必须确保对指针的解引用不会导致越界访问。调用者还负责确保结果指针为引用类型正确对齐。
 有关对齐假设的详细信息,请参阅 ECMA-335,第 I.12.6.2 (“对齐”) 。

2.7 引用取消只读(类似C++的 const_cast)

在C#中可以用“ref readonly”定义只读引用。摘自官方文档。

可以定义 ref readonly 局部变量。 不能为 ref readonly 变量赋值。 但是,可以 ref 重新分配这样的 reference 变量,如以下示例所示:

int[] xs = { 1, 2, 3 };

ref readonly int element = ref xs[0];
// element = 100;  error CS0131: The left-hand side of an assignment must be a variable, property or indexer
Console.WriteLine(element);  // output: 1

element = ref xs[^1];
Console.WriteLine(element);  // output: 3

可见,在使用“= ref”做引用赋值时,能方便的将“可变引用(ref)”赋值给“只读引用(ref readonly)”。因为将“能读写变量”限制为“只读变量”,是安全的。
但反向该如何做呢,即怎样将“只读引用(ref readonly)”转为“可变引用(ref)”?
虽然这个操作是不安全的,但是有些时候必须使用。类似C++提供const_cast运算法,能够去掉“const”。
Unsafe类的许多方法是仅支持“可变引用(ref)”的,例如“Add”等。当想对“只读引用(ref readonly)”使用Unsafe类的方法前,需要将“只读引用(ref readonly)”转为“可变引用(ref)”。

解决办法就是使用 Unsafe的AsRef方法。摘自官方文档。

AsRef<T>(T)
将给定的只读引用重新解释为可变引用。
public static ref T AsRef<T> (scoped in T source);

类型参数
T	引用的基础类型。

参数
source	T	要重新解释的只读引用。

返回
T	对类型的 T值的可变引用。

注解
此 API 在概念上类似于 C++的`const_cast<>`。调用方负责确保不会将数据写入引用的位置。 运行时包含基于只读引用真正不可变的假设的内部逻辑,违反此固定项的调用方可能会在运行时内触发未定义的行为。

AsRef 通常用于将只读引用传递给方法,例如 Add,接受可变托管指针作为参数。 请看下面的示例。

int ComputeSumOfElements(ref int refToFirstElement, nint numElements)
{
  int sum = 0;
  for (nint i = 0; i < numElements; i++)
  {
    sum += Unsafe.Add(ref refToFirstElement, i);
  }
}

如果输入参数不是 `ref int refToFirstElement`, 而是 `ref readonly int refToFirstElement`,则上一个示例不会编译,因为不能将只读引用用作Add的参数。相反,AsRef 可用于删除不可变性约束并允许编译成功,如以下示例所示。

int ComputeSumOfElements(ref readonly int refToFirstElement, nint numElements)
{
  int sum = 0;
  for (nint i = 0; i < numElements; i++)
  {
    sum += Unsafe.Add(ref Unsafe.AsRef(ref refToFirstElement), i);
  }
}

三、将指针代码改写为引用代码

3.1 代码编写

我们先来回顾一下指针版的代码。

private static float SumVectorAvxPtr(float[] src, int count, int loops) {
#if Allow_Intrinsics && UNSAFE
    unsafe {
        float rt = 0; // Result.
        int VectorWidth = Vector256<float>.Count; // Block width.
        int nBlockWidth = VectorWidth; // Block width.
        int cntBlock = count / nBlockWidth; // Block count.
        int cntRem = count % nBlockWidth; // Remainder count.
        Vector256<float> vrt = Vector256<float>.Zero; // Vector result.
        Vector256<float> vload;
        float* p; // Pointer for src data.
        int i;
        // Body.
        fixed(float* p0 = &src[0]) {
            for (int j = 0; j < loops; ++j) {
                p = p0;
                // Vector processs.
                for (i = 0; i < cntBlock; ++i) {
                    vload = Avx.LoadVector256(p);    // Load. vload = *(*__m256)p;
                    vrt = Avx.Add(vrt, vload);    // Add. vrt += vsrc[i];
                    p += nBlockWidth;
                }
                // Remainder processs.
                for (i = 0; i < cntRem; ++i) {
                    rt += p[i];
                }
            }
        }
        // Reduce.
        for (i = 0; i < VectorWidth; ++i) {
            rt += vrt.GetElement(i);
        }
        return rt;
    }
#else
    throw new NotSupportedException();
#endif
}

利用本文上面的知识,可以去掉unsafe关键字,将指针改造为引用。代码如下。

private static float SumVectorAvxRef(float[] src, int count, int loops) {
#if Allow_Intrinsics
    float rt = 0; // Result.
    int VectorWidth = Vector256<float>.Count; // Block width.
    int nBlockWidth = VectorWidth; // Block width.
    int cntBlock = count / nBlockWidth; // Block count.
    int cntRem = count % nBlockWidth; // Remainder count.
    Vector256<float> vrt = Vector256<float>.Zero; // Vector result.
    int i;
    // Body.
    for (int j = 0; j < loops; ++j) {
        ref Vector256<float> p0 = ref Unsafe.As<float, Vector256<float>> (ref src[0]); // Pointer for src data.
        // Vector processs.
        for (i = 0; i < cntBlock; ++i) {
            vrt = Avx.Add(vrt, p0);    // Add. vrt += vsrc[i];
            p0 = ref Unsafe.Add(ref p0, 1);
        }
        // Remainder processs.
        ref float p = ref Unsafe.As<Vector256<float>, float>(ref p0);
        for (i = 0; i < cntRem; ++i) {
            rt += Unsafe.Add(ref p, i);
        }
    }
    // Reduce.
    for (i = 0; i < VectorWidth; ++i) {
        rt += vrt.GetElement(i);
    }
    return rt;
#else
    throw new NotSupportedException();
#endif
}

在向量处理(Vector processs)阶段,需要向量类型的引用,于是定义了 ref Vector256<float> p0。因为C#里访问的引用,会相当于访问所指向的值,类似自动使用了指针的“间接运算符*”。即给Avx.Add传递的p0,是符合参数要求的值。
在余数处理(Remainder processs)阶段,需要基元类型的引用,于是定义了 ref float p。由于向量处理的循环结束时,p0正好移动到首个余数的位置,所以可以用 Unsafe.As 进行重新解释,为p赋值(设置引用的指向)。

3.2 测试结果

在我的电脑(lntel(R) Core(TM) i5-8250U CPU @ 1.60GHz、Windows 10)上运行时,x64、Release版程序的输出信息为:

BenchmarkVectorCore30

IsRelease:      True
EnvironmentVariable(PROCESSOR_IDENTIFIER):      Intel64 Family 6 Model 142 Stepping 10, GenuineIntel
Environment.ProcessorCount:     8
Environment.Is64BitOperatingSystem:     True
Environment.Is64BitProcess:     True
Environment.OSVersion:  Microsoft Windows NT 6.2.9200.0
Environment.Version:    3.0.3
RuntimeEnvironment.GetRuntimeDirectory: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.0.3\
RuntimeInformation.FrameworkDescription:        .NET Core 3.0.3
BitConverter.IsLittleEndian:    True
IntPtr.Size:    8
Vector.IsHardwareAccelerated:   True
Vector<byte>.Count:     32      # 256bit
Vector<float>.Count:    8       # 256bit
Vector<double>.Count:   4       # 256bit
Vector4.Assembly.CodeBase:      file:///C:/Program Files/dotnet/shared/Microsoft.NETCore.App/3.0.3/System.Numerics.Vectors.dll
Vector<T>.Assembly.CodeBase:    file:///C:/Program Files/dotnet/shared/Microsoft.NETCore.App/3.0.3/System.Private.CoreLib.dll

Benchmark:      count=4096, loops=1000000, countMFlops=4096
SumBase:        6.871948E+10    # msUsed=4891, MFLOPS/s=837.4565528521774
SumBaseU4:      2.748779E+11    # msUsed=1844, MFLOPS/s=2221.2581344902387, scale=2.65238611713666
SumVector4:     2.748779E+11    # msUsed=1219, MFLOPS/s=3360.1312551271535, scale=4.012305168170632
SumVector4U4:   1.0995116E+12   # msUsed=515, MFLOPS/s=7953.398058252427, scale=9.497087378640778
SumVectorT:     5.497558E+11    # msUsed=610, MFLOPS/s=6714.754098360656, scale=8.018032786885247
SumVectorTU4:   2.1990233E+12   # msUsed=187, MFLOPS/s=21903.74331550802, scale=26.155080213903744
SumVectorAvx:   5.497558E+11    # msUsed=609, MFLOPS/s=6725.7799671592775, scale=8.0311986863711
SumVectorAvxSpan:       5.497558E+11    # msUsed=610, MFLOPS/s=6714.754098360656, scale=8.018032786885247
SumVectorAvxRef:        5.497558E+11    # msUsed=609, MFLOPS/s=6725.7799671592775, scale=8.0311986863711
SumVectorAvxPtr:        5.497558E+11    # msUsed=610, MFLOPS/s=6714.754098360656, scale=8.018032786885247
SumVectorAvxU4: 2.1990233E+12   # msUsed=328, MFLOPS/s=12487.80487804878, scale=14.911585365853659
SumVectorAvxSpanU4:     2.1990233E+12   # msUsed=312, MFLOPS/s=13128.205128205129, scale=15.676282051282053
SumVectorAvxPtrU4:      2.1990233E+12   # msUsed=172, MFLOPS/s=23813.95348837209, scale=28.436046511627907
SumVectorAvxPtrU16:     8.386202E+12    # msUsed=188, MFLOPS/s=21787.23404255319, scale=26.01595744680851
SumVectorAvxPtrU16A:    8.3862026E+12   # msUsed=250, MFLOPS/s=16384, scale=19.564
SumVectorAvxPtrUX[4]:   2.1990233E+12   # msUsed=531, MFLOPS/s=7713.747645951035, scale=9.210922787193974
SumVectorAvxPtrUX[8]:   4.3980465E+12   # msUsed=500, MFLOPS/s=8192, scale=9.782
SumVectorAvxPtrUX[16]:  8.3862026E+12   # msUsed=484, MFLOPS/s=8462.809917355371, scale=10.105371900826446

可以看出 SumVectorAvxRef与SumVectorAvxPtr的性能几乎一致。引用版算法有时更快,可能是因为避免了fixed。
而且从“标准库中的Span是靠引用实现的”来看,.NET运行时在执行引用相关算法时,肯定是做了充足的编译优化的。使其能达到与指针版算法相当的性能。

四、小结

如今引用的功能非常强大,能完全代替指针操作,且能摆脱unsafe关键字。适合不启用“允许非安全代码”等严格的场合。

但需注意,引用有这些缺点——

  • 有些场合的编码比指针繁琐。例如用 Unsafe.As 做重新解释时,还需要写上源数据的类型。且不支持指针元素访问运算符 [],只能用Unsafe.Add等方法对地址进行调整。
  • Unsafe的方法一般是没有安全检查的,这样避免了性能损耗,但会留下一些安全隐患需开发者处理。若开发者处理不慎,会像开发指针代码那样易出现 地址越界、悬空指针 等Bug。故需要开发者有指针开发经验,细心避免bug。

源码地址——
https://github.com/zyl910/BenchmarkVector/tree/main/BenchmarkVector文章来源地址https://www.toymoban.com/news/detail-615037.html

参考文献

  • Span<T> needs to be support long length and indices #896》. https://github.com/dotnet/corefxlab/issues/896
  • Microsoft《unsafe(C# 参考)》. https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/unsafe
  • Microsoft《C# 发展历史》. https://learn.microsoft.com/zh-cn/dotnet/csharp/whats-new/csharp-version-history
  • Microsoft《Unsafe 类》. https://learn.microsoft.com/zh-cn/dotnet/api/system.runtime.compilerservices.unsafe?view=netcore-3.0
  • Microsoft《Span.cs》. https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/shared/System/Span.cs
  • zyl910《C# 使用SIMD向量类型加速浮点数组求和运算(1):使用Vector4、Vector<T>》. https://www.cnblogs.com/zyl910/p/dotnet_simd_BenchmarkVector1.html
  • zyl910《C# 使用SIMD向量类型加速浮点数组求和运算(2):C#通过Intrinsic直接使用AVX指令集操作 Vector256<T>,及C++程序对比》. https://www.cnblogs.com/zyl910/p/dotnet_simd_BenchmarkVector2.html
  • zyl910《C# 使用SIMD向量类型加速浮点数组求和运算(3):循环展开》. https://www.cnblogs.com/zyl910/p/dotnet_simd_BenchmarkVector3.html

到了这里,关于C# 使用SIMD向量类型加速浮点数组求和运算(4):用引用代替指针, 摆脱unsafe关键字,兼谈Unsafe类的使用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • x86平台SIMD编程入门(3):浮点指令

    算术类型 函数示例 备注 加 _mm_add_sd 、 _mm256_add_ps 减 _mm_sub_sd 、 _mm256_sub_ps 乘 _mm_mul_sd 、 _mm256_mul_ps 除 _mm_div_sd 、 _mm256_div_ps 平方根 _mm_sqrt_sd 、 _mm256_sqrt_ps 倒数 _mm_rcp_ss 、 _mm_rcp_ps 、 _mm256_rcp_ps 快速计算32位浮点数的近似倒数(1/x),最大相对误差小于 (1.5times 2^{-12}) 。 倒数

    2024年02月06日
    浏览(40)
  • 【ARMv8 SIMD和浮点指令编程】NEON 乘法指令——乘法知多少?

    NEON 乘法指令包括向量乘法、向量乘加和向量乘减,还有和饱和相关的指令。总之,乘法指令是必修课,在我们的实际开发中会经常遇到。 1 MUL (by element) 乘(向量,按元素)。该指令将第一个源 SIMDFP 寄存器中的向量元素乘以第二个源 SIMDFP 寄存器中的指定值,将结果放入向

    2024年02月08日
    浏览(41)
  • 【ARMv8 SIMD和浮点指令编程】NEON 通用数据处理指令——复制、反转、提取、转置...

    NEON 通用数据处理指令包括以下指令(不限于): • DUP 将标量复制到向量的所有向量线。 • EXT 提取。 • REV16、REV32、REV64 反转向量中的元素。 • TBL、TBX 向量表查找。 • TRN 向量转置。 • UZP、ZIP 向量交叉存取和反向交叉存取。 1 DUP (element) 将向量元素复制为向量或标量。

    2024年02月07日
    浏览(42)
  • 【ARMv8 SIMD和浮点指令编程】NEON 存储指令——如何将数据从寄存器存储到内存?

    和加载指令一样,NEON 有一系列的存储指令。比如 ST1、ST2、ST3、ST4。 1 ST1 (multiple structures) 从一个、两个、三个或四个寄存器存储多个单元素结构。该指令将元素从一个、两个、三个或四个 SIMDFP 寄存器存储到内存,无需交错。每个寄存器的每个元素都被存储。 无偏移 一个寄

    2024年02月07日
    浏览(49)
  • 【ARMv8 SIMD和浮点指令编程】NEON 加载指令——如何将数据从内存搬到寄存器(其它指令)?

    除了基础的 LDx 指令,还有 LDP、LDR 这些指令,我们也需要关注。 1 LDNP (SIMDFP) 加载 SIMDFP 寄存器对,带有非临时提示。该指令从内存加载一对 SIMDFP 寄存器, 向内存系统发出访问是非临时的提示 。用于加载的地址是根据基址寄存器值和可选的立即偏移量计算得出的。 32-bit (

    2024年02月07日
    浏览(45)
  • MATLAB :向量、矩阵、数组、数据类型

    目录 一、基本概念 1. 常量、变量和标量   (1)常量   (2)变量         1) 变量的命名规则         2)变量的声明与删除   (3)标量 2. 向量、矩阵和数组   (1)向量   (2)矩阵   (3)数组 二、向量 1. 向量的创建   (1)直接输入法   (2)冒号表达式法   (3)函

    2024年01月22日
    浏览(39)
  • 【OpenCV】SIMD向量化加速教程

    介绍OpenCV内部通用的特征来向量化C++代码以此获取更快的运行时间。我们将简要介绍SIMD内部函数和如何使用宽寄存器。 在这一节,我们简要的介绍一些概念来更好理解功能。 内建函数(Intrinsics) 是编译器单独处理函数。这些函数通常使用更高效的方式来优化运行效率。 但是

    2024年02月12日
    浏览(25)
  • 浮点类型详解及 IEEE754 规定

       🔗 《C语言趣味教程》👈 猛戳订阅!!! 0x00 引入:什么是浮点数? 在讲解浮点类型前,我们不妨先先来了解一下什么是浮点数, 浮点 (float point),又称  实型数 。 顾名思义就是 \\\"一个漂浮的点\\\",其英文 float 也是这个含义(浮动, 漂浮之意)。

    2024年02月16日
    浏览(37)
  • 【⑬MySQL | 数据类型(一)】简介 | 整数 | 浮点 | 定点 | 时间/日期类型

    ✨欢迎来到小K的MySQL专栏,本节将为大家带来MySQL数据类型简介 | 整数 | 浮点 | 定点 | 时间/日期类型的分享 ✨ 0.数据类型简介 数据类型(data_type)是指系统中所允许的数据的类型。MySQL 数据类型定义了列中可以存储什么数据以及该数据怎样存储的规则。 数据库中的每个列都

    2024年02月11日
    浏览(38)
  • C语言 —— 浮点类型详解及 IEEE754 规定

       🔗 《C语言趣味教程》👈 猛戳订阅!!! 0x00 引入:什么是浮点数? 在讲解浮点类型前,我们不妨先先来了解一下什么是浮点数, 浮点 (float point),又称  实型数 。 顾名思义就是 \\\"一个漂浮的点\\\",其英文 float 也是这个含义(浮动, 漂浮之意)。

    2024年02月17日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包