C#的GPU加速方法

前往我的主页以获得更好的阅读体验C#的GPU加速方法 - DearXuan的主页icon-default.png?t=M3C8https://blog.dearxuan.com/2021/08/13/C-%E7%9A%84GPU%E5%8A%A0%E9%80%9F%E6%96%B9%E6%B3%95/

本文将通过C#调用dll的方法来实现并发计算

在VS2019里新建动态链接库项目,在pch.h里定义函数

// pch.h: 这是预编译标头文件。
// 下方列出的文件仅编译一次,提高了将来生成的生成性能。
// 这还将影响 IntelliSense 性能,包括代码完成和许多代码浏览功能。
// 但是,如果此处列出的文件中的任何一个在生成之间有更新,它们全部都将被重新编译。
// 请勿在此处添加要频繁更新的文件,这将使得性能优势无效。

#ifndef PCH_H
#define PCH_H

// 添加要在此处预编译的标头
#include "framework.h"
extern "C" _declspec(dllexport) void Sum(int* s,int a[],int b[],int length);

#endif //PCH_H

在pch.cpp里实现该函数

// pch.cpp: 与预编译标头对应的源文件

#include "pch.h"
#include <amp.h>

// 当使用预编译的头时,需要使用此源文件,编译才能成功。

using namespace Concurrency;

extern "C" _declspec(dllexport) void Sum(int* s, int a[], int b[],int length) {
    array_view<const int, 1> aArray(length, a);
    array_view<const int, 1> bArray(length, b);
    array_view<int, 1> sum(length, s);
    parallel_for_each(
        sum.extent,
        [=](index<1> idx) restrict(amp) {
            sum[idx] = aArray[idx] + bArray[idx];
        }
    );
}

该函数接收4个参数,分别用来储存结果,a数组,b数组,数组长度,并将a和b数组相加,结果储存在s里面。

array_view表示包含在一个容器中的数据的N维视图,各项参数的含义如下:

        const int:类型,

        1:维数

        aArray:array_view的实例

        length:长度

        a:数据源

如果是二维数组,则要改成下面的形式

array_view<const int, 2> aArray(width,height, a);

parallel_for_each语句能够进行并发计算,index<1>指idx是一维的,如果是二维数组,需要改成index<2>,此时idx相当于(i,j),通过idx[0]和idx[1]获得行号和列号

例如

int row = idx[0];
int col = idx[1];

aArray[idx]和aArray(row,col)是等效的

将上述代码生成dll,并放在C#程序的目录下

导入刚刚写的dll

[DllImport("Dll1.dll", EntryPoint = "Sum", CallingConvention = CallingConvention.Cdecl)]
public static extern void Sum(IntPtr s,int[] a, int[] b,int length);

生成随机数数组,求和

static void Main(string[] args)
{
    const int size = 100;
    int[] s = new int[size];
    int[] a = new int[size];
    int[] b = new int[size];
    Random random = new Random();
    for(int i = 0; i < size; i++)
    {
        a[i] = random.Next(0, 100);
        b[i] = random.Next(100, 200);
    }
    unsafe
    {
        IntPtr p = Marshal.UnsafeAddrOfPinnedArrayElement(s, 0);
        Sum(p, a, b, size);
    }
    for(int i = 0; i < size; i++)
    {
        Console.WriteLine(a[i] + "+" + b[i] + "=" + s[i]);
    }
    Console.ReadLine();
}

使用StopWatch类来计算耗时(命名空间System.Diagnostics)

Stopwatch watch1 = new Stopwatch();
watch1.Start();
for(int i = 0; i < size; i++)
{
    s[i] = a[i] + b[i];
}
watch1.Stop();
Console.WriteLine("CPU耗时:" + watch1.Elapsed.TotalMilliseconds);
Stopwatch watch2 = new Stopwatch();
watch2.Start();
Sum(p, a, b, size);
watch2.Stop();
Console.WriteLine("GPU耗时:" + watch2.Elapsed.TotalMilliseconds);

由于加载dll本身需要时间,所以在计时之前需要先调用一次Sum函数。

测试代码是计算4亿个数的和,可以看到GPU计算比CPU计算少了300毫秒,但是CPU在循环2亿次的情况下居然仅仅比GPU多了300毫秒,这是因为GPU无法从内存读取数据,需要把数据先复制到显存里才能计算,计算完又需要把数据复制回来,而主要时间开销都在数据的复制里面。

现实情况下,循环体里不可能只有一行代码,假设循环体里有10个语句,那么CPU的执行时间就会翻10倍,而GPU的执行时间也会翻10倍,但是由于主要耗时操作是数据的复制,所以实际增长不会特别明显。

下面我们修改一下代码。

extern "C" _declspec(dllexport) void Sum(int* s, int a[], int b[],int length) {
    array_view<const int, 1> aArray(length, a);
    array_view<const int, 1> bArray(length, b);
    array_view<int, 1> sum(length, s);
    parallel_for_each(
        sum.extent,
        [=](index<1> idx) restrict(amp) {
            sum[idx] = aArray[idx] + bArray[idx];
            if (idx[0] % 5 == 0) {
                sum[idx] += 5;
            }
            if (idx[0] % 7 == 0) {
                sum[idx] += 7;
            }
            if (idx[0] % 11 == 0) {
                sum[idx] += 11;
            }
            if (idx[0] % 13 == 0) {
                sum[idx] += 13;
            }
            if (idx[0] % 17 == 0) {
                sum[idx] += 17;
            }
        }
    );
}
watch1.Start();
for(int i = 0; i < size; i++)
{
    s[i] = a[i] + b[i];
    if (i % 5 == 0)
    {
        s[i] += 5;
    }
    if (i % 7 == 0)
    {
        s[i] += 7;
    }
    if (i % 11 == 0)
    {
        s[i] += 11;
    }
    if (i % 13 == 0)
    {
        s[i] += 13;
    }
    if (i % 17 == 0)
    {
        s[i] += 17;
    }
}
watch1.Stop();
Console.WriteLine("CPU耗时:" + watch1.Elapsed.TotalMilliseconds);

这次改用100万量级的数据

 现在GPU的优势就完全体现出来了

### C# 中使用 GPU 加速 YOLOv8 配置 #### 使用 TensorRT 进行 GPU 推理加速 对于希望利用 NVIDIA GPU加速 YOLOv8 的推理过程的情况,可以借助于 NVIDIA 提供的 TensorRT 工具包来完成这一目标。TensorRT 是一种高性能深度学习推理优化器和运行时环境,专为生产环境中部署神经网络而设计。 安装并配置好适用于开发系统的 TensorRT 版本后[^3],可以通过 ONNX Runtime 或者直接调用 TensorRT API 实现对预训练好的 YOLOv8 模型文件(.onnx)进行加载与执行推断操作。具体来说: - 将官方发布的 PyTorch 训练得到的 .pt 文件转换成通用交换格式——ONNX (.onnx),以便后续能够被不同框架所读取; - 利用 TensorRT 创建序列化模型,并将其保存下来作为最终用于实际应用中的二进制权重文件; ```csharp using Microsoft.ML.OnnxRuntime; using Microsoft.ML.OnnxRuntime.Tensors; // 初始化会话选项,指定使用 CUDA 执行提供程序 var sessionOptions = new SessionOptions(); sessionOptions.ExecutionMode = ExecutionMode.ORT_SEQUENTIAL; sessionOptions.AddExecutionProvider_CUDA(0); // 设备ID设为默认的第一个可用GPU设备 // 创建推理会话实例 InferenceSession inferenceSession = new InferenceSession(modelPath, sessionOptions); // 准备输入张量... var inputMeta = inferenceSession.InputMetadata; var inputData = File.ReadAllBytes(imageFilePath); var tensor = Value.CreateTensor<float>(new DenseTensor<byte>(inputData)); // 获取输出结果 List<NamedOnnxValue> outputs = inferenceSession.Run(new[] { NamedOnnxValue.CreateFromTensor(inputName, tensor) }); ``` 需要注意的是上述代码片段仅展示了部分核心逻辑,完整的解决方案还需要考虑更多细节问题比如数据预处理、后处理以及性能优化等方面的工作。 #### 基于 OpenVINO™ 的另一种选择 除了通过 TensorRT 外,还可以探索 Intel 开发的 OpenVINO™ Toolkit 方案来进行 GPU 加速。此工具集允许开发者轻松地将计算机视觉工作负载迁移到英特尔硬件平台上,即使是没有集成或独立图形处理器的情况下也能获得不错的效率提升效果[^5]。 不过考虑到当前需求集中在NVIDIA GPU上,因此建议优先尝试前者即基于CUDA平台下的方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dear_Xuan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值