使用GPU(OpenGL)作为目标的Halide - 基准测试和使用HalideRuntimeOpenGL.h

时间:2016-06-13 20:36:30

标签: c++ opengl gpu halide

我是Halide的新手。我一直在玩教程,以了解语言。现在,我正在编写一个小型演示应用程序,以便在OSX上从命令行运行。

我的目标是对图像执行逐像素操作,在GPU上安排并测量性能。我尝试了一些我想在这里分享的东西,并对下一步有几个问题。

第一种方法

我在GPU上安排了算法,目标是OpenGL,但因为我无法访问GPU内存来写入文件,所以在Halide例程中,我通过创建类似于Func cpu_out的{​​{1}}将输出复制到CPU Halide回购中的glsl sample app

pixel_operation_cpu_out.cpp

#include "Halide.h"
#include <stdio.h>

using namespace Halide;

const int _number_of_channels = 4;

int main(int argc, char** argv)
{
    ImageParam input8(UInt(8), 3);

    input8
        .set_stride(0, _number_of_channels) // stride in dimension 0 (x) is three
        .set_stride(2, 1); // stride in dimension 2 (c) is one

    Var x("x"), y("y"), c("c");

    // algorithm
    Func input;
    input(x, y, c) = cast<float>(input8(clamp(x, input8.left(), input8.right()),
                                 clamp(y, input8.top(), input8.bottom()),
                                 clamp(c, 0, _number_of_channels))) / 255.0f;

    Func pixel_operation;

    // calculate the corresponding value for input(x, y, c) after doing a 
    // pixel-wise operation on each each pixel. This gives us pixel_operation(x, y, c).
    // This operation is not location dependent, eg: brighten

    Func out;
    out(x, y, c) = cast<uint8_t>(pixel_operation(x, y, c) * 255.0f + 0.5f);
    out.output_buffer()
        .set_stride(0, _number_of_channels)
        .set_stride(2, 1);
    input8.set_bounds(2, 0, _number_of_channels); // Dimension 2 (c) starts at 0 and has extent _number_of_channels.
    out.output_buffer().set_bounds(2, 0, _number_of_channels);

    // schedule

     out.compute_root();
     out.reorder(c, x, y)
         .bound(c, 0, _number_of_channels)
         .unroll(c);

    // Schedule for GLSL

    out.glsl(x, y, c);

    Target target = get_target_from_environment();
    target.set_feature(Target::OpenGL);

    // create a cpu_out Func to copy over the data in Func out from GPU to CPU
    std::vector<Argument> args = {input8};
    Func cpu_out;
    cpu_out(x, y, c) = out(x, y, c);
    cpu_out.output_buffer()
        .set_stride(0, _number_of_channels)
        .set_stride(2, 1);
    cpu_out.output_buffer().set_bounds(2, 0, _number_of_channels);
    cpu_out.compile_to_file("pixel_operation_cpu_out", args, target);

    return 0;
}

因为我编译了这个AOT,所以我在我的main()中进行了一个函数调用。 main()位于另一个文件中。

main_file.cpp

注意:此处使用的 Image 类与此Halide sample app

中的类相同
int main()
{
    char *encodeded_jpeg_input_buffer = read_from_jpeg_file("input_image.jpg");
    unsigned char *pixelsRGBA = decompress_jpeg(encoded_jpeg_input_buffer);

    Image input(width, height, channels, sizeof(uint8_t), Image::Interleaved);
    Image output(width, height, channels, sizeof(uint8_t), Image::Interleaved);
    input.buf.host = &pixelsRGBA[0];
    unsigned char *outputPixelsRGBA = (unsigned char *)malloc(sizeof(unsigned char) * width * height * channels);
    output.buf.host = &outputPixelsRGBA[0];

    double best = benchmark(100, 10, [&]() {
         pixel_operation_cpu_out(&input.buf, &output.buf);
    });

    char* encoded_jpeg_output_buffer = compress_jpeg(output.buf.host);
    write_to_jpeg_file("output_image.jpg", encoded_jpeg_output_buffer);
}

这很好用,给了我期望的输出。据我所知,cpu_out使out中的值可用于CPU内存,这就是我可以通过访问output.buf.host中的main_file.cpp来访问这些值的原因

第二种方法:

我尝试的第二件事是通过创建Func cpu_out,而不是使用copy_to_host中的main_file.cpp功能,在Halide计划中不通过设备进行主机复制。

pixel_operation_gpu_out.cpp

#include "Halide.h"
#include <stdio.h>

using namespace Halide;

const int _number_of_channels = 4;

int main(int argc, char** argv)
{
    ImageParam input8(UInt(8), 3);

    input8
        .set_stride(0, _number_of_channels) // stride in dimension 0 (x) is three
        .set_stride(2, 1); // stride in dimension 2 (c) is one

    Var x("x"), y("y"), c("c");

    // algorithm
    Func input;
    input(x, y, c) = cast<float>(input8(clamp(x, input8.left(), input8.right()),
                                 clamp(y, input8.top(), input8.bottom()),
                                 clamp(c, 0, _number_of_channels))) / 255.0f;

    Func pixel_operation;

    // calculate the corresponding value for input(x, y, c) after doing a 
    // pixel-wise operation on each each pixel. This gives us pixel_operation(x, y, c).
    // This operation is not location dependent, eg: brighten

    Func out;
    out(x, y, c) = cast<uint8_t>(pixel_operation(x, y, c) * 255.0f + 0.5f);
    out.output_buffer()
        .set_stride(0, _number_of_channels)
        .set_stride(2, 1);
    input8.set_bounds(2, 0, _number_of_channels); // Dimension 2 (c) starts at 0 and has extent _number_of_channels.
    out.output_buffer().set_bounds(2, 0, _number_of_channels);

    // schedule

     out.compute_root();
     out.reorder(c, x, y)
         .bound(c, 0, _number_of_channels)
         .unroll(c);

    // Schedule for GLSL

    out.glsl(x, y, c);

    Target target = get_target_from_environment();
    target.set_feature(Target::OpenGL);

    std::vector<Argument> args = {input8};
    out.compile_to_file("pixel_operation_gpu_out", args, target);

    return 0;
}

main_file.cpp

#include "pixel_operation_gpu_out.h"
#include "runtime/HalideRuntime.h"

int main()
{
    char *encodeded_jpeg_input_buffer = read_from_jpeg_file("input_image.jpg");
    unsigned char *pixelsRGBA = decompress_jpeg(encoded_jpeg_input_buffer);

    Image input(width, height, channels, sizeof(uint8_t), Image::Interleaved);
    Image output(width, height, channels, sizeof(uint8_t), Image::Interleaved);
    input.buf.host = &pixelsRGBA[0];
    unsigned char *outputPixelsRGBA = (unsigned char *)malloc(sizeof(unsigned char) * width * height * channels);
    output.buf.host = &outputPixelsRGBA[0];

    double best = benchmark(100, 10, [&]() {
         pixel_operation_gpu_out(&input.buf, &output.buf);
    });

    int status = halide_copy_to_host(NULL, &output.buf);

    char* encoded_jpeg_output_buffer = compress_jpeg(output.buf.host);
    write_to_jpeg_file("output_image.jpg", encoded_jpeg_output_buffer);

    return 0;
}

所以,现在,我认为正在发生的事情是pixel_operation_gpu_outoutput.buf保留在GPU上,当我执行copy_to_host时,就是当我将内存复制到CPU时。该程序也为我提供了预期的输出。

问题:

第二种方法比第一种方法慢得多。然而,缓慢的部分不在基准部分。例如,对于第一种方法,我得到17ms作为4k图像的基准时间。对于同一图像,在第二种方法中,我将基准时间设为22us,copy_to_host所用的时间为10s。我不确定这种行为是否是预期的,因为方法1和方法2基本上都在做同样的事情。

我接下来尝试的是使用[HalideRuntimeOpenGL.h][3]并将纹理链接到输入和输出缓冲区,以便能够从main_file.cpp直接绘制到OpenGL上下文,而不是保存到jpeg文件。但是,我找不到任何例子来弄清楚如何使用HalideRuntimeOpenGL.h中的函数,我自己尝试的任何事情总是给我运行时错误,我无法弄清楚如何解决。如果有人有任何资源他们可以指出,那将是伟大的。

此外,我也欢迎任何有关上述代码的反馈。我知道它有效并正在做我想做的事情,但它可能是完全错误的做法,我不会更清楚。

1 个答案:

答案 0 :(得分:0)

10s复制内存的原因很可能是因为GPU API已将所有内核调用排队,然后在调用halide_copy_to_host时等待它们完成。在运行所有计算调用之后,您可以在基准时序内调用halide_device_sync来获取循环内的计算时间,而无需复制时间。

我无法从代码中判断出从这段代码运行内核的次数。 (我的猜测是100,但可能是那些基准测试的参数设置某种参数化,它试图运行它需要多次才能获得重要性。如果是这样,这是一个问题,因为排队调用非常快但是计算当然是异步的。如果是这种情况,你可以做一些事情,比如队列十个调用然后调用halide_device_sync并使用数字“10”来获得一个真实的图片,看看需要多长时间。)