【问题标题】:Is it possible to accelerate (dynamic) LINQ queries using GPU?是否可以使用 GPU 加速(动态)LINQ 查询?
【发布时间】:2012-03-07 02:13:21
【问题描述】:

我这几天一直在寻找有关使用 GPU 加速 LINQ 查询的可能性的可靠信息。

到目前为止我“调查”过的技术:

  • 微软加速器
  • Cudafy
  • 梵天

简而言之,是否有可能在 GPU 上对对象进行内存过滤?

假设我们有一些对象的列表,我们想要过滤如下内容:

var result = myList.Where(x => x.SomeProperty == SomeValue);

有这方面的指点吗?

提前致谢!

更新

我会尝试更具体地说明我想要实现的目标:)

我们的目标是使用任何能够以绝对最快的方式过滤对象列表(范围从 ~50 000 到 ~2 000 000)的技术。

过滤完成后我对数据执行的操作(sum、min、max 等)是使用内置的 LINQ 方法进行的,并且对于我们的应用程序来说已经足够快了,所以这不是问题。

瓶颈是“简单地”过滤数据。

更新

只是想补充一下,我已经测试了大约 15 个数据库,包括 MySQL(检查可能的集群方法/memcached 解决方案)、H2、HSQLDB、VelocityDB(目前正在进一步调查)、SQLite、MongoDB 等,当它涉及过滤数据的速度(当然,NO-sql 解决方案不像 sql 解决方案那样提供这一点,但你明白了)和/或实际数据的返回。

只是总结一下我/我们需要什么:

能够在不到 100 毫秒的时间内以 200 列和大约 250 000 行的格式对数据进行排序的数据库。

我目前有一个并行化 LINQ 的解决方案,当过滤 AND 处理结果时,它能够(在特定机器上)在每一行上只花费 nano 秒!

因此,我们需要对每一行进行亚nano-秒过滤。

  1. 为什么似乎只有内存中的 LINQ 才能提供此功能?
  2. 为什么这是不可能的?

日志文件中的一些数字:

Total tid för 1164 frågor: 2579

这是瑞典语,可以翻译:

Total time for 1164 queries: 2579

在这种情况下的查询是这样的查询:

WHERE SomeProperty = SomeValue

这些查询都是在 225639 行上并行完成的。

因此,225639 行在大约 2.5 秒内在内存中被过滤了 1164 次。

这是 9,5185952917007032597107300413827e-9 秒/行,但是,这也包括数字的实际处理!我们做 Count (not null)、total count、Sum、Min、Max、Avg、Median。因此,我们对这些过滤的行进行了 7 次操作。

因此,我们可以说它实际上比我们尝试过的数据库快 7 倍,因为在这些情况下我们确实执行任何聚合操作!

因此,总而言之,与内存中的 LINQ 过滤相比,为什么数据库在过滤数据方面如此糟糕?微软真的做得好到无法与之抗衡吗? :)

虽然内存过滤应该更快是有道理的,但我不希望 感觉它更快。我想知道什么更快,如果可能的话为什么

【问题讨论】:

标签: c# linq gpu dynamic-linq


【解决方案1】:

我将明确回答 Brahma,因为它是我的库,但它可能也适用于其他方法。 GPU 不了解对象。它的内存也大多与 CPU 内存完全分离。

如果您确实有大量对象并想要对它们进行操作,您只能将要操作的数据打包到适合您正在使用的 GPU/API 的缓冲区中,然后将其发送出去进行处理.

请注意,这将在 CPU-GPU 内存接口上进行两次往返,因此,如果您没有在 GPU 上做足够的工作以使其值得,那么您将比仅在第一名(如上面的示例)。

希望这会有所帮助。

【讨论】:

  • 谢谢!我喜欢你的图书馆,并且将它用于我上面所问的以外的其他东西!实际上我真正想做的就是快速过滤对象 :) 继续与 Brahma 合作!
  • @Ani,我在哪里可以找到有关 Brahma 的信息?有任何教程和/或示例吗?
  • @AlexSanséau 对不起,我已经好几年没有积极开发这个项目了。您可以在此处查看 F# 版本 (github.com/gsvgit/Brahma.FSharp)
【解决方案2】:

GPU 确实不适合所有通用计算目的,尤其是像这样的面向对象设计,像这样过滤任意数据集合确实不是一件合适的事情。

GPU 计算非常适合在大型数据集上执行相同操作的情况 - 这就是矩阵运算和变换之类的事情非常好的原因。在那里,数据复制可能会被 GPU 上令人难以置信的快速计算能力所抵消......

在这种情况下,您必须将所有数据复制到 GPU 中才能完成这项工作,并将其重组为 GPU 可以理解的某种形式,这可能比仅在软件中执行过滤器更昂贵第一名。

相反,我建议使用 PLINQ 来加快这种性质的查询。如果您的过滤器是线程安全的(它必须用于任何与 GPU 相关的工作......),这可能是通用查询优化的更好选择,因为它不需要内存复制数据。 PLINQ 将通过将您的查询重写为:

var result = myList.AsParallel().Where(x => x.SomeProperty == SomeValue);

如果谓词是一项昂贵的操作,或者集合非常大(并且易于分区),与标准 LINQ to Objects 相比,这可以显着提高整体性能。

【讨论】:

  • 感谢您的回答!然而,这是我们今天使用的方法。差不多够了,但很快我们就会有更多的行,而且……即使是我们拥有的 16 核机器也不够了:(
  • @Johan 在某些时候,使用数据库和导致在查询端加速过滤的技术(如 EF)将导致这种类型的操作在大型数据集上更快,尤其是当过滤器是一个简单的属性检查时......复制到 GPU 的成本对于这种类型的操作来说会非常昂贵。你到底在做什么?这是什么类型的数据?
  • 嗯,它是一个来自表的对象列表(我正在使用 Dapper 获取数据)。该表有很多列(出于性能原因未标准化),但没有那么多行 atm(大约 250 000)。但是,与内存过滤相比,到目前为止我尝试过的所有“数据库”都慢了很多。你知道获取数据的任何极快的“在哪里优化”的技术吗? :)
  • @Johan,即使您在SomeProperty 上有索引?
  • @Johan 如果您的查询使用索引列,则数据库应该比任何内存查询快得多。你用的是什么数据库?您的查询列是否已编入索引?
【解决方案3】:

GpuLinq

GpuLinq 的主要任务是通过 LINQ 普及 GPGPU 编程。主要思想是我们将查询表示为表达式树,经过各种转换优化后,我们将其编译成快速的 OpenCL 内核代码。此外,我们提供了一个非常易于使用的 API,而无需弄乱 OpenCL API 的细节。

https://github.com/nessos/GpuLinq

【讨论】:

    【解决方案4】:
    select *
    from table1  -- contains 100k rows
    left join table2 -- contains 1M rows
    on table1.id1=table2.id2 -- this would run for ~100G times 
                             -- unless they are cached on sql side
    where table1.id between 1 and 100000 -- but this optimizes things (depends)
    

    可以变成

    select id1 from table1 -- 400k bytes if id1 is 32 bit 
    -- no need to order
    

    存储在内存中

    select id2 from table2 -- 4Mbytes if id2 is 32 bit
    -- no need to order
    

    存储在内存中,两个数组都使用如下所示的内核(cuda,opencl)发送到 gpu

    int i=get_global_id(0); // to select an id2, we need a thread id
    int selectedID2=id2[i];
    summary__=-1;
    for(int j=0;j<id1Length;j++)
    {
          int selectedID1=id1[j];
          summary__=(selectedID2==selectedID1?j:summary__); // no branching
    }
    summary[i]=j; // accumulates target indexings of 
    "on table1.id1=table2.id2" part.
    

    在主机端,你可以制作

     select * from table1 --- query3
    

     select * from table2 --- query4
    

    然后使用 gpu 中的 id 列表来选择数据

     // x is table1 ' s data
     myList.AsParallel().ForEach(x=>query3.leftjoindata=query4[summary[index]]);
    

    对于具有恒定内存、全球广播能力和数千个内核的 gpu,gpu 代码的速度不应低于 50 毫秒。

    如果使用任何三角函数进行过滤,性能会迅速下降。此外,当左连接表的行数使其复杂度为 O(m*n) 时,数百万对数百万会慢得多。 GPU 内存带宽在这里很重要。

    编辑: gpu.findIdToJoin(table1,table2,"id1","id2") 在我的 hd7870(1280 核)和 R7-240(320 核)上使用“产品表(64k 行)”和“类别表( 64k 行)”(左连接过滤器)在未优化内核的情况下花费了 48 毫秒。

    Ado.Net 的“nosql”风格的 linq-join 耗时超过 2000 毫秒,只有 44k 个产品和 4k 个类别表。

    Edit-2:

    当表增长到 1000 行每行至少有数百个字符时,使用字符串搜索条件的左连接在 gpu 上的速度提高了 50 到 200 倍。

    【讨论】:

      【解决方案5】:

      您的用例的简单答案是否定的。

      1) 即使在原始 linq to object 中,也没有针对这种工作负载的解决方案,更不用说替换数据库了。

      2) 即使您可以一次加载整个数据集(这需要时间),它仍然会慢得多,因为 GPU 的吞吐量很高,但它们的访问延迟很高,所以如果您正在查看“非常“快速的解决方案 GPGPU 通常不是答案,因为只是准备/发送工作负载并取回结果会很慢,在您的情况下可能也需要分块完成。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-01-28
        • 1970-01-01
        • 1970-01-01
        • 2012-12-28
        • 2010-10-04
        • 1970-01-01
        • 2019-04-12
        • 1970-01-01
        相关资源
        最近更新 更多