Program Listing for File perfSuite.cpp

Program Listing for File perfSuite.cpp#

↰ Return to documentation for file (Test/perf/perfSuite.cpp)

#include "kataglyphis_test_support.h"

#include <benchmark/benchmark.h>

#include <cstddef>
#include <cstdint>
#include <string>
#include <string_view>
#include <vector>

#include "kShippedInferenceConfig.inc"

import kataglyphis.config_loader;
import kataglyphis.gstreamer_pipeline;
import kataglyphis.onnx_inference;
import kataglyphis.toml_config;
import kataglyphis.yolo_detector;

namespace inference = kataglyphis::inference;
namespace detection = kataglyphis::detection;
namespace gstreamer = kataglyphis::gstreamer;
using kataglyphis::test::TempFile;
using kataglyphis::test::YoloReshapeGeometry;

namespace {

constexpr std::string_view kSettings =
  R"({"signalingServerUrl": "wss://example.test/webrtc-ws", "reconnectionTimeoutMs": 2500,
      "stunServers": ["stun:one.example:3478", "stun:two.example:3478"], "turnServers": ["turn:relay.example:3478"],
      "video": {"defaultWidth": 1920, "defaultHeight": 1080, "defaultFramerate": 60, "defaultBitrateKbps": 8000},
      "texture": {"width": 800, "height": 600}, "android": {"width": 640, "height": 360, "fps": 24},
      "stream": {"source": "v4l2", "encoder": "vp8", "device": "/dev/video4", "producerId": "producer-7"}})";

// YOLOv5-style output, 1680 rows of 4 box values, objectness and 80 class scores.
constexpr YoloReshapeGeometry kYoloGeometry{ 560, 85, 1680, 85 };

auto float_input(std::size_t elements) -> std::vector<float>
{
    std::vector<float> data(elements);
    for (std::size_t i = 0; i < elements; ++i) { data[i] = static_cast<float>(i % 17) - 8.0F; }
    return data;
}

// The first `candidates` rows are confident, disjoint boxes of one class, the worst case for NMS's pairwise loop.
auto yolo_output(std::size_t candidates) -> std::vector<float>
{
    std::vector<float> rows(kYoloGeometry.elements(), 0.0F);
    const auto values = static_cast<std::size_t>(kYoloGeometry.values_per_row);
    for (std::size_t row = 0; row < candidates; ++row) {
        float *values_of_row = rows.data() + row * values;
        values_of_row[0] = static_cast<float>(row % 40) * 16.0F + 8.0F;
        values_of_row[1] = static_cast<float>(row / 40) * 16.0F + 8.0F;
        values_of_row[2] = 10.0F;
        values_of_row[3] = 10.0F;
        values_of_row[4] = 0.9F;
        values_of_row[5] = 0.9F;
    }
    return rows;
}

}// namespace

static void BM_ParseWebRtcSettings(benchmark::State &state)
{
    const std::string json(kSettings);
    for (auto _ : state) {
        auto config = kataglyphis::config::parse_webrtc_config(json);
        benchmark::DoNotOptimize(config);
    }
    state.SetBytesProcessed(static_cast<std::int64_t>(state.iterations() * json.size()));
}
BENCHMARK(BM_ParseWebRtcSettings);

static void BM_ParseShippedInferenceConfig(benchmark::State &state)
{
    const std::string toml(kShippedInferenceConfig);
    for (auto _ : state) {
        auto config = kataglyphis::config::parse_inference_config(toml);
        benchmark::DoNotOptimize(config);
    }
    state.SetBytesProcessed(static_cast<std::int64_t>(state.iterations() * toml.size()));
}
BENCHMARK(BM_ParseShippedInferenceConfig);

// The engine's per-call overhead around ORT: the input copy, tensor wrapping and the output copy.
static void BM_OnnxRunInference(benchmark::State &state)
{
    const auto elements = static_cast<std::int64_t>(state.range(0));
    const TempFile model("kataglyphis-bench-relu",
      ".onnx",
      kataglyphis::test::build_onnx_model(
        { { "X", { 1, elements } } }, { { "Y", { 1, elements } } }, { { "Relu", { "X" }, { "Y" } } }));
    inference::OnnxInferenceEngine engine;
    auto config = inference::create_default_session_config(model.path());
    config.intra_op_num_threads = 1;
    config.inter_op_num_threads = 1;
    if (!engine.initialize(config)) {
        state.SkipWithError("ONNX Runtime could not load the generated model");
        return;
    }
    const auto input = float_input(static_cast<std::size_t>(elements));
    const inference::TensorShape shape{ { 1, static_cast<std::size_t>(elements) } };
    for (auto _ : state) {
        auto result = engine.run_inference(input, shape, "X");
        benchmark::DoNotOptimize(result);
    }
    state.SetBytesProcessed(
      static_cast<std::int64_t>(state.iterations()) * elements * static_cast<std::int64_t>(sizeof(float)));
}
BENCHMARK(BM_OnnxRunInference)->Arg(1024)->Arg(1 << 16)->Arg(3 * 640 * 640);

// Detection end to end on a YOLOv5-sized output: inference, decoding and NMS over `candidates` confident boxes.
static void BM_YoloDetect(benchmark::State &state)
{
    const TempFile model("kataglyphis-bench-yolo", ".onnx", kataglyphis::test::yolo_reshape_model(kYoloGeometry));
    detection::YoloDetector detector;
    detection::YoloConfig config;
    config.model_path = model.path();
    config.input_width = static_cast<std::uint32_t>(kYoloGeometry.width);
    config.input_height = static_cast<std::uint32_t>(kYoloGeometry.height);
    if (!detector.initialize(config)) {
        state.SkipWithError("ONNX Runtime could not load the generated model");
        return;
    }
    const auto rows = yolo_output(static_cast<std::size_t>(state.range(0)));
    std::size_t boxes = 0;
    for (auto _ : state) {
        auto result = detector.detect(rows, 1280, 720);
        boxes = result ? result->boxes.size() : 0;
        benchmark::DoNotOptimize(result);
    }
    state.counters["boxes"] = static_cast<double>(boxes);
}
BENCHMARK(BM_YoloDetect)->Arg(0)->Arg(64)->Arg(512);

// The frame path's copy out of GStreamer: one pulled 640x480 RGB frame per iteration.
static void BM_GStreamerPullSample(benchmark::State &state)
{
    if (!gstreamer::GStreamerPipeline::initialize_gstreamer()) {
        state.SkipWithMessage("gst_init failed");
        return;
    }
    gstreamer::GStreamerPipeline pipeline;
    if (!pipeline.create_pipeline_from_string(
          "videotestsrc pattern=black ! video/x-raw,format=RGB,width=640,height=480,framerate=30/1 "
          "! appsink name=sink sync=false max-buffers=4")
        || !pipeline.start()) {
        state.SkipWithMessage("videotestsrc, capsfilter or appsink is not installed here");
        return;
    }
    std::size_t bytes = 0;
    for (auto _ : state) {
        auto sample = pipeline.pull_sample(5000);
        if (!sample) {
            state.SkipWithError("the pipeline stopped delivering frames");
            break;
        }
        bytes += sample->size;
        benchmark::DoNotOptimize(sample);
    }
    (void)pipeline.stop();
    state.SetBytesProcessed(static_cast<std::int64_t>(bytes));
}
BENCHMARK(BM_GStreamerPullSample);

BENCHMARK_MAIN();