C# 使用Alea GPU加速嵌套循环和按位操作

C# 使用Alea GPU加速嵌套循环和按位操作,c#,cuda,aleagpu,C#,Cuda,Aleagpu,我正在尝试使用Alea来加速我正在开发的程序,但我需要一些帮助 我需要做的是对存储在两个数组中的值进行大量的位计数和逐位操作 对于第一个数组中的每个元素,我必须对第二个数组中的每个元素执行按位&运算,然后对结果中设置为1的位进行计数 如果结果大于/等于某个值,我需要退出内部for并转到第一个数组的下一个元素 第一个数组通常是一个大数组,包含数百万个元素,第二个数组通常少于200000个元素 要并行执行所有这些操作,我的代码如下: [GpuManaged] private long[] Check

我正在尝试使用Alea来加速我正在开发的程序,但我需要一些帮助

我需要做的是对存储在两个数组中的值进行大量的位计数和逐位操作

对于第一个数组中的每个元素,我必须对第二个数组中的每个元素执行按位&运算,然后对结果中设置为1的位进行计数

如果结果大于/等于某个值,我需要退出内部for并转到第一个数组的下一个元素

第一个数组通常是一个大数组,包含数百万个元素,第二个数组通常少于200000个元素

要并行执行所有这些操作,我的代码如下:

[GpuManaged]
private long[] Check(long[] arr1, long[] arr2, int limit)
{
    Gpu.FreeAllImplicitMemory(true);
    var gpu = Gpu.Default;
    long[] result = new long[arr1.Length];
    gpu.For(0, arr1.Length, i =>
    {
        bool found = false;
        long b = arr1[i];
        for (int i2 = 0; i2 < arr2.Length; i2++)
        {
            if (LibDevice.__nv_popcll(b & arr2[i2]) >= limit)
            {
                found = true;
                break;
            }
        }
        if (!found)
        {
            result[i] = b;
        }
    });
    return result;
}
[gpumanage]
专用长[]检查(长[]arr1,长[]arr2,整数限制)
{
Gpu.FreeAllImplicitMemory(true);
var gpu=gpu.Default;
long[]结果=新的long[arr1.Length];
对于(0,arr1.Length,i=>
{
bool-found=false;
长b=arr1[i];
for(int i2=0;i2=限制)
{
发现=真;
打破
}
}
如果(!找到)
{
结果[i]=b;
}
});
返回结果;
}
这和预期的一样,但只比我的版本在四核CPU上并行运行快一点

我肯定错过了一些东西,这是我第一次尝试编写GPU代码

顺便说一句,我的NVIDIA是GeForce GT 740M

编辑

下面的代码比上一个代码快2倍,至少在我的电脑上是这样。非常感谢Michael Randall为我指明了正确的方向

private static int[] CheckWithKernel(Gpu gpu, int[] arr1, int[] arr2, int limit)
{
    var lp = new LaunchParam(16, 256);
    var result = new int[arr1.Length];
    try
    {
        using (var dArr1 = gpu.AllocateDevice(arr1))
        using (var dArr2 = gpu.AllocateDevice(arr2))
        using (var dResult = gpu.AllocateDevice<int>(arr1.Length))
        {
            gpu.Launch(Kernel, lp, arr1.Length, arr2.Length, dArr1.Ptr, dArr2.Ptr, dResult.Ptr, limit);
            Gpu.Copy(dResult, result);
            return result;
        }
    }
    finally
    {
        Gpu.Free(arr1);
        Gpu.Free(arr2);
        Gpu.Free(result);
    }
}

private static void Kernel(int a1, int a2, deviceptr<int> arr1, deviceptr<int> arr2, deviceptr<int> arr3, int limit)
{
    var iinit = blockIdx.x * blockDim.x + threadIdx.x;
    var istep = gridDim.x * blockDim.x;
    for (var i = iinit; i < a1; i += istep)
    {
        bool found = false;
        int b = arr1[i];
        for (var j = 0; j < a2; j++)
        {
            if (LibDevice.__nv_popcll(b & arr2[j]) >= limit)
            {
                found = true;
                break;
            }
        }
        if (!found)
        {
            arr3[i] = b;
        }
    }
}
private static int[]CheckWithKernel(Gpu Gpu,int[]arr1,int[]arr2,int limit)
{
var lp=新启动参数(16256);
var结果=新整数[arr1.Length];
尝试
{
使用(var dArr1=gpu.AllocateDevice(arr1))
使用(var dArr2=gpu.AllocateDevice(arr2))
使用(var dResult=gpu.AllocateDevice(arr1.Length))
{
启动(内核、lp、arr1.Length、arr2.Length、dArr1.Ptr、dArr2.Ptr、dResult.Ptr、limit);
Gpu.Copy(dResult,result);
返回结果;
}
}
最后
{
Gpu.Free(arr1);
Gpu.Free(arr2);
Gpu.Free(结果);
}
}
私有静态无效内核(int a1、int a2、deviceptr arr1、deviceptr arr2、deviceptr arr3、int limit)
{
变量iinit=blockIdx.x*blockDim.x+threadIdx.x;
var istep=gridDim.x*blockDim.x;
对于(var i=iinit;i=限制)
{
发现=真;
打破
}
}
如果(!找到)
{
arr3[i]=b;
}
}
}
更新 似乎用GCHandle.Alloc()固定不起作用

然而,这个答案的要点是,通过直接内存访问,您将获得更大的性能增益

直接使用设备内存

设备内存提供了更大的灵活性,因为它还允许所有 一种指针算法。设备内存分配为

原始答案 忽略正在进行的逻辑,或者这与GPU代码的相关性。但是,您可以通过使用和标志将数组固定在内存中,并使用直接指针访问(如果您可以运行代码)来补充并行例程,并可能加快速度

注释

  • 固定内存会带来很大的影响,但是对于大型阵列,直接访问可以实现很多性能*

  • 您必须在生成属性中将程序集标记为不安全*

  • 这显然没有经过测试,只是一个例子*

  • 您可以使用fixed,但是平行Lambda使其更加烦躁

示例

private unsafe long[] Check(long[] arr1, long[] arr2, int limit)
{   
   Gpu.FreeAllImplicitMemory(true);
   var gpu = Gpu.Default;    
   var result = new long[arr1.Length];

   // Create some pinned memory
   var resultHandle = GCHandle.Alloc(result, GCHandleType.Pinned);
   var arr2Handle = GCHandle.Alloc(result, GCHandleType.Pinned);
   var arr1Handle = GCHandle.Alloc(result, GCHandleType.Pinned);

   // Get the addresses
   var resultPtr = (int*)resultHandle.AddrOfPinnedObject().ToPointer();
   var arr2Ptr = (int*)arr2Handle.AddrOfPinnedObject().ToPointer();
   var arr1Ptr = (int*)arr2Handle.AddrOfPinnedObject().ToPointer();

   // I hate nasty lambda statements. I always find local methods easier to read.    
   void Workload(int i)
   {
      var found = false;    
      var b = *(arr1Ptr + i);

      for (var j = 0; j < arr2.Length; j++)
      {
         if (LibDevice.__nv_popcll(b & *(arr2Ptr + j)) >= limit)
         {
            found = true;
            break;
         }
      }

      if (!found)
      {
         *(resultPtr + i) = b;
      }
   }

   try
   {
      gpu.For(0, arr1.Length, i => Workload(i));
   }
   finally 
   {
      // Make sure we free resources
      arr1Handle.Free();
      arr2Handle.Free();
      resultHandle.Free();
   } 
   return result;    
}
private-unsafe-long[]检查(long[]arr1,long[]arr2,int-limit)
{   
Gpu.FreeAllImplicitMemory(true);
var gpu=gpu.Default;
var结果=新长[arr1.Length];
//创建一些固定内存
var resultHandle=GCHandle.Alloc(结果,GCHandleType.pinted);
var arr2Handle=GCHandle.Alloc(结果,GCHandleType.pinted);
var arr1Handle=GCHandle.Alloc(结果,GCHandleType.pinted);
//获取地址
var resultPtr=(int*)resultHandle.AddRofPindedObject().ToPointer();
var arr2Ptr=(int*)arr2Handle.AddrOfPinnedObject().ToPointer();
var arr1Ptr=(int*)arr2Handle.AddrOfPinnedObject().ToPointer();
//我讨厌令人讨厌的lambda语句。我总是觉得本地方法更容易阅读。
无效工作负载(int i)
{
var=false;
var b=*(arr1Ptr+i);
对于(var j=0;j=极限)
{
发现=真;
打破
}
}
如果(!找到)
{
*(结果tPTR+i)=b;
}
}
尝试
{
对于(0,arr1.Length,i=>Workload(i));
}
最后
{
//确保我们释放资源
arr1Handle.Free();
arr2Handle.Free();
resultHandle.Free();
} 
返回结果;
}

额外资源

一个新的GCHandle,用于保护对象不被垃圾回收。这 当不再需要GCHandle时,必须使用Free释放它

固定的:此句柄类型类似于普通句柄类型,但允许获取固定对象的地址。这就防止了垃圾的产生 收集器阻止移动对象,从而降低效率 垃圾收集器的一部分。使用Free方法释放已分配的 尽快处理

共同语言
using (var dArg1 = gpu.AllocateDevice(arg1))
using (var dArg2 = gpu.AllocateDevice(arg2))
using (var dOutput = gpu.AllocateDevice<int>(Length/2))
{           
    // pointer arithmetics to access subset of data
    gpu.Launch(Kernel, lp, dOutput.Length, dOutput.Ptr, dArg1.Ptr + Length/2, dArg2.Ptr + Length / 2);

    var result = dOutput.ToArray();

    var expected = arg1.Skip(Length/2).Zip(arg2.Skip(Length/2), (x, y) => x + y);

    Assert.That(result, Is.EqualTo(expected));
}
private unsafe long[] Check(long[] arr1, long[] arr2, int limit)
{   
   Gpu.FreeAllImplicitMemory(true);
   var gpu = Gpu.Default;    
   var result = new long[arr1.Length];

   // Create some pinned memory
   var resultHandle = GCHandle.Alloc(result, GCHandleType.Pinned);
   var arr2Handle = GCHandle.Alloc(result, GCHandleType.Pinned);
   var arr1Handle = GCHandle.Alloc(result, GCHandleType.Pinned);

   // Get the addresses
   var resultPtr = (int*)resultHandle.AddrOfPinnedObject().ToPointer();
   var arr2Ptr = (int*)arr2Handle.AddrOfPinnedObject().ToPointer();
   var arr1Ptr = (int*)arr2Handle.AddrOfPinnedObject().ToPointer();

   // I hate nasty lambda statements. I always find local methods easier to read.    
   void Workload(int i)
   {
      var found = false;    
      var b = *(arr1Ptr + i);

      for (var j = 0; j < arr2.Length; j++)
      {
         if (LibDevice.__nv_popcll(b & *(arr2Ptr + j)) >= limit)
         {
            found = true;
            break;
         }
      }

      if (!found)
      {
         *(resultPtr + i) = b;
      }
   }

   try
   {
      gpu.For(0, arr1.Length, i => Workload(i));
   }
   finally 
   {
      // Make sure we free resources
      arr1Handle.Free();
      arr2Handle.Free();
      resultHandle.Free();
   } 
   return result;    
}