【问题标题】:How to get AVX512 in C#?如何在 C# 中获取 AVX512?
【发布时间】:2021-07-18 15:03:50
【问题描述】:

我想在 C# 中使用AVX-512 指令,但我的理解是:不支持它(或者我在互联网上搜索非常糟糕)。所以我决定为它创建自己的绑定。但是我得到了:

外部组件抛出异常。

我不知道我在这里搞砸了什么。

这是我的C 代码:

#include <immintrin.h>

__declspec(dllexport) 
__m512i
load_s32(const void *ptr) {
    return _mm512_load_epi32(ptr);
}

使用以下命令编译:

gcc -c decl.c -mavx512f
gcc -shared -o libavx512.dll decl.o -Wl,--out-implib,libavx512.dll.a -mavx512f

C# 我创建了一个包含以下部分的库:

using System.Runtime.InteropServices;

using S64 = System.Int64;

namespace AVX512Sharp
{
    [StructLayout(LayoutKind.Sequential, Size = 64)]
    public struct M512S32
    {
        public S64 M0;
        public S64 M1;
        public S64 M2;
        public S64 M3;
        public S64 M4;
        public S64 M5;
        public S64 M6;
        public S64 M7;
    }

    public static class AVX512
    {
        [DllImport("libavx512.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "load_s32")]
        public extern unsafe static M512S32 LoadS32(void* ptr);
    }
}

在我的测试程序中,我是这样使用它的:

int* mem = stackalloc int[16];
for (int i = 0; i < 16; ++i)
    mem[i] = i * 10;

M512S32 zmm0;
zmm0 = AVX512.LoadS32(mem);

我真的不知道我在这里做错了什么。

注意事项

  • 为了测试绑定是否有效,我删除了 SIMD 函数:
__declspec(dllexport) 
void
load_s32(const void *ptr) {
    return;
}

并且还更新了AVX512 类:

public static class AVX512
{
    [DllImport("libavx512.dll", EntryPoint = "load_s32")]
    public extern unsafe static void LoadS32(void* ptr);
}

这没有引发异常。

  • 在第二步中,我尝试在C 应用程序中使用dll。也没有任何错误。
  • 还尝试使用额外的属性,例如:-Wl,--export-all-symbols-Wl,--enable-auto-import。相关文档为here

【问题讨论】:

  • 您调试的第一步应该是从非托管 DLL 中删除特定于 AVX 的部分,以确保您已正确设置互操作内容。如果这可行,那么在非托管上下文中使用您的非托管 DLL 以确保它从根本上是正确的。如果在这之后,它仍然不起作用,那么你知道你有一个实际的 C# 问题。
  • @jdweng 这不是必需的,但是我尝试过它并没有用(也是“默认”struct 不能为空)。 stackoverflow.com/questions/7767669/…
  • 我建议针对 x64 构建库和应用程序(不是任何 CPU),并使用 Fastcall 调用约定。 Cdecl 是 x86 约定,不适用于 x64。 Some read.
  • @jdweng:更正,_mm512_load_epi32 必须是 64 byte 对齐而不是 64 位;这是_mm512_load_si512 的一个愚蠢的替代名称,我建议不要使用它。 (What is the difference between _mm512_load_epi32 and _mm512_load_si512?)。仅在进行屏蔽加载时才使用 _epi32 版本,因为这样元素大小才有意义。使用 loadu 而不是 load 进行未对齐的加载,但请注意对齐对于 512 位向量的性能更重要:每个未对齐的向量都是缓存行拆分,并且它的减速更大.
  • @PeterCordes 是的,我的 MACHINE 上的 CPU 不支持 AVX512。在多台机器上工作有它的缺点。

标签: c# simd avx avx512


【解决方案1】:

我决定为它创建自己的绑定。

你不能。您可以做的最好的事情是,用 C 或 C++ 编写一个使用 AVX512 的 DLL,并从 C# 使用 DLL。如果您尝试从 DLL 中导出单个指令,则性能不会很好,因为内存访问和 pinvoke 开销。相反,您应该在 C 中编写更大的功能。

我真的不知道我在这里做错了什么。

您的 C 函数需要 rcx 寄存器中的输入指针,并在 zmm0 向量寄存器中返回结果。

您的 C# 函数不知道 zmm0。运行时在堆栈上为返回值分配 64 个字节,在rcx 寄存器中传递返回值缓冲区的地址,在rdx 寄存器中传递输入指针,并期望函数在@987654329 中返回在rcx 中传递的指针@注册。

互操作两侧的语言在调用约定上存在分歧,您的代码在运行时崩溃。

【讨论】:

  • (另外,被问者显然把他们的机器弄混了(?)和was trying this one one that didn't support AVX-512,所以这没有帮助。C 函数中的非法指令错误,所以它甚至在此之前就崩溃了返回。但是,是的,包装单个指令对于性能来说是不可取的,即使你确实得到了正确的调用约定。)
  • @Sonts 我试图复制 MS 为AVX2 所做的事情。我使用ILSpy 查看了他们的代码,他们在那里使用了类似的结构。我确实从互操作函数调用中获取了数据(在修复调用对流之后),但是你是对的:这里的性能不会是完美的(甚至是好的)。
  • .NET5 引入了“本机函数指针”(doc)。有人可以使用它们来改善那里的开销吗?
  • @Hrant 您在 ILSpy 中看到的只是故事的高级部分。请注意,Vector256&lt;T&gt; 结构在 CIL 中标有 [Intrinsic] 属性。 .NET 运行时知道如何正确处理这些类型:将它们保存在这些向量寄存器中(如果是 32 字节的 AVX 向量,它们被命名为 ymm0ymm15),当它们在内存中时,尽可能以 32 字节对齐等。运行时不支持 AVX512 并将您的 M512S32 视为常规结构类型,而不是本机向量。
  • @Hrant 如果您不想等待,请在 C++ 中实现对性能至关重要的函数并使用 dllimport。不要导出单个指令,导出更大的函数,将数据从内存流向内存,性能会很好。
猜你喜欢
  • 2020-05-04
  • 1970-01-01
  • 2010-12-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多