使用renderscript计算数组中的值的总和

时间:2014-02-12 16:50:21

标签: arrays renderscript

您好我是新手,并尝试使用 Renderscript 进行编码。我想知道如何使用渲染脚本在数组中执行元素的总和。有没有办法可以将输出传递回脚本以便顺序添加?我的问题陈述是:  矢量和

描述:计算数组中值的总和。

输入:整数数组

输出:整数

非常感谢任何帮助!

3 个答案:

答案 0 :(得分:5)

我担心这会比看起来复杂得多,但我会尽力在这里解释一下你可以采取的实现这个目的的路线。

你要求的是更好地称为并行缩减算法,它可以实现像你的情况一样的数组求和,或任何其他可交换+关联运算符,当迭代地应用于数组时,它将“减少”它到一个数字。其他示例是查找大型数组的最大值或最小值。在CUDA和OpenCL中,有一个众所周知的计算模式,能够最好地使用并行线程,例如,如果你谷歌“CUDA减少”,你将获得大量关于这个算法的有用信息。

实现它的方法是反复将数组减半,一遍又一遍,直到你得到一个元素。每次减少它时,每个新元素都是前两个元素的总和。这是一张更好地描绘该算法的图片:

Parallel Reduction

例如,您从一个16元素数组开始。您运行该算法一次,最终得到一个8元素数组 - 其中这8个元素中的每一个都是原始数组中两个数字的总和。

你再次运行它,最后得到4个元素 - 其中每个元素都是上一步中两个数字的总和。等等...

你一直这样做,直到你最终得到一个数字 - 你的总和。

在RenderScript中实现此方法的效率低下的方式是:

爪哇:

int[] ints; // Your data is held here.

Allocation data = Allocation.createSized(rs, Element.I32(rs), ints.length, Allocation.USAGE_SCRIPT);
data.copy1DRangeFrom(0, ints.length, ints);

ScriptC_Reduce script = new ScriptC_Reduce(rs);
script.bind_data(data);

for (int stride = ints.length / 2; stride > 0; stride /= 2) {
    script.set_stride(stride);
    script.forEach_root(input, output);
}

data.copyTo(ints);
int totalsum = ints[0];

的renderScript:

#pragma version(1)
#pragma rs java_package_name(...[your package here]...)

int stride;
int * data;

void root(const int32_t *v_in, int32_t *v_out, uint32_t x) {
    if (x < stride) data[x] += data[x + stride];
}

如果您之前曾使用过RS,您可能会注意到一些奇怪的事情:

  1. 请注意,RS内核中的“v_in”和“v_out”根本不使用,因为它们仅限于读取和写入与当前线程索引相对应的数据元素,而reduce算法需要访问数据元素其他职位。因此,有一个int数组指针“data”,它从具有相同名称的分配中绑定到Java,这就是内核直接处理的内容。
  2. 从Java中的循环中多次调用内核,而不是在内核中执行该循环。这是因为在每次迭代时,来自previuos步骤的所有数据必须已经准备好在其预期位置,否则,“data [x + stride]”将不同步。在RS中,内核调用锁定,这意味着在内核处理完整个数据之前不执行任何其他操作。这与__syncthreads()在CUDA内核中执行的操作类似,如果您熟悉它。
  3. 然而,我在上面提到过,这是一个效率低下的实现。但它应该指向正确的方向。为了提高效率,您可能需要将数据拆分为更小的块以便单独计算,因为这里给出的算法将在每个迭代步骤中运行ints.length线程数,并且在非常大的数组上运行将导致一个很多的步骤,以及每个步骤中空闲线程的批次

    此外,这假设您的数组的长度恰好是2的幂,因此多个减半将导致恰好一个元素。对于其他大小的数组,您可能需要对数组进行0填充。再次,当处理非常大的数组时,0填充将需要大量浪费的内存。

    因此,为了解决这些问题,您可能希望将阵列分成多个块,例如每个64个元素。因此,如果您没有精确的数组长度,将“最后”块填充到64将不需要那么多内存。此外,您将需要更少的迭代步骤(以及更少的空闲线程)来减少64个元素。当然,64是我刚刚组成的神奇数字。尝试其他2的幂来查看它们的结果,你可能会看到更好的结果与其他块大小,如16或32.我怀疑性能与块大小将是非常依赖硬件。

    编辑:这假设RenderScript可以为运行它的设备使用GPU驱动程序,这样它就可以实际启动大量并行线程。否则,像这样的仅执行内核的CPU可能比处理数组线性更慢。

答案 1 :(得分:3)

唐&#39;吨。除非你有比1加入更多的东西。唐&#39;吨。在该阵列中至少有400万个整数之前,代码不会更快。

RenderScript: Entries:1 Total: 3 Time: 0.067ms
Simple Loop : Entries:1 Total: 3 Time: 0.001ms
RenderScript: Entries:2 Total: 97 Time: 0.614ms
Simple Loop : Entries:2 Total: 97 Time: 0.001ms
RenderScript: Entries:4 Total: 227 Time: 0.28ms
Simple Loop : Entries:4 Total: 227 Time: 0.002ms
RenderScript: Entries:8 Total: 320 Time: 0.445ms
Simple Loop : Entries:8 Total: 320 Time: 0.002ms
RenderScript: Entries:16 Total: 700 Time: 0.486ms
Simple Loop : Entries:16 Total: 700 Time: 0.002ms
RenderScript: Entries:32 Total: 1807 Time: 0.595ms
Simple Loop : Entries:32 Total: 1807 Time: 0.002ms
RenderScript: Entries:64 Total: 3218 Time: 0.624ms
Simple Loop : Entries:64 Total: 3218 Time: 0.002ms
RenderScript: Entries:128 Total: 6230 Time: 0.737ms
Simple Loop : Entries:128 Total: 6230 Time: 0.003ms
RenderScript: Entries:256 Total: 12968 Time: 0.769ms
Simple Loop : Entries:256 Total: 12968 Time: 0.005ms
RenderScript: Entries:512 Total: 26253 Time: 0.895ms
Simple Loop : Entries:512 Total: 26253 Time: 0.01ms
RenderScript: Entries:1024 Total: 52345 Time: 0.987001ms
Simple Loop : Entries:1024 Total: 52345 Time: 0.017ms
RenderScript: Entries:2048 Total: 100223 Time: 1.715ms
Simple Loop : Entries:2048 Total: 100223 Time: 0.034ms
RenderScript: Entries:4096 Total: 200375 Time: 1.213ms
Simple Loop : Entries:4096 Total: 200375 Time: 0.065ms
RenderScript: Entries:8192 Total: 403713 Time: 1.196ms
Simple Loop : Entries:8192 Total: 403713 Time: 0.163001ms
RenderScript: Entries:16384 Total: 812411 Time: 1.929ms
Simple Loop : Entries:16384 Total: 812411 Time: 0.41ms
RenderScript: Entries:32768 Total: 1620542 Time: 1.822ms
Simple Loop : Entries:32768 Total: 1620542 Time: 0.617ms
RenderScript: Entries:65536 Total: 3250733 Time: 5.955ms
Simple Loop : Entries:65536 Total: 3250733 Time: 1.384ms
RenderScript: Entries:131072 Total: 6478866 Time: 2.622ms
Simple Loop : Entries:131072 Total: 6478866 Time: 2.008ms
RenderScript: Entries:262144 Total: 12980832 Time: 3.979999ms
Simple Loop : Entries:262144 Total: 12980832 Time: 4.377001ms
RenderScript: Entries:524288 Total: 25956676 Time: 10.163ms
Simple Loop : Entries:524288 Total: 25956676 Time: 8.326ms
RenderScript: Entries:1048576 Total: 51897168 Time: 12.723001ms
Simple Loop : Entries:1048576 Total: 51897168 Time: 15.871999ms
RenderScript: Entries:2097152 Total: 103867356 Time: 32.229001ms
Simple Loop : Entries:2097152 Total: 103867356 Time: 31.367ms
RenderScript: Entries:4194304 Total: 207646704 Time: 61.628999ms
Simple Loop : Entries:4194304 Total: 207646704 Time: 63.378ms
RenderScript: Entries:8388608 Total: 415058480 Time: 103.734999ms
Simple Loop : Entries:8388608 Total: 415058480 Time: 140.088ms

这正在削减一切有利于renderscript。就像假设所有分配都将在主循环之外完成而且所有分配都不必将数据数据复制回来(我简单地称为rs.finish()以确保renderscript完成)。

#pragma version(1)
#pragma rs java_package_name(com.photoembroidery.tat.olsennoise)

int stride;
int * data;

void root(const int32_t *v_in, int32_t *v_out, uint32_t x) {
    data[x] += data[x + stride];
}

请注意启动选项。你进行第一次缩减以将数组调整到正确的2因子。所以你在那之前采用大小和因子2之间的剩余量来处理这些条目,这样它们就会减少,其余的则是2。然后你处理两个因素。

//int[] array = //array of your data//;

        ScriptC_reduce script = new ScriptC_reduce(mRS);
        Allocation data = Allocation.createSized(mRS, Element.I32(mRS), array.length, Allocation.USAGE_SCRIPT);
        data.copy1DRangeFrom(0, array.length, array);
        script.bind_data(data);

        int smallest2ExpBiggerThanLength = 1;
        for (int length = arraysize; length != 0; length >>= 1,smallest2ExpBiggerThanLength <<= 1);

        int end = smallest2ExpBiggerThanLength / 2;
        int start = smallest2ExpBiggerThanLength - arraysize;
        if (start == end) {
            start = 0;
            end = end/2;
        }

        while (end > 0) {
            launchOptions.setX(start, end);
            script.set_stride(end - start);
            script.forEach_root(data, data, launchOptions);
            script.forEach_root(data,data);
            end = end >> 1;
            start = 0;
        }
        data.copyTo(array);
        int total = array[0];

另一个答案中最大的低效率是启动选项。你最好不要从一开始就限制范围,而不是检查范围的有效性。你失去了4倍的速度。一个简单的循环将更快更普遍,无需调用renderscript错误。 - 你需要做一些比1更难的事情来使这个值得。

答案 2 :(得分:3)

我们实际上正在努力支持“reduce”作为下一个Android版本的新内核类型。这将允许您在分配的单元格上运行关联操作(如添加),并返回单个简化结果。代码已经存在于AOSP中,但我们正在努力使其更加灵活/通用。当前表单已允许您指定2输入 - &gt; 1个输出内核,可以应用于所有单元格。

与此同时,您可以通过在invokable中按顺序运行并使用rsGetElementAt _ *()来遍历单元格来近似减少内核。它会比Java快得多,在这种情况下,你经常支付不必要的边界检查(以及其他开销)。