Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/OpenMPUtilities.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
#include "Settings.h"

// Calculate the # of OpenMP Threads to allow
#define OPEN_MP_NUM_PROCESSORS std::min(omp_get_num_procs(), std::max(2, openshot::Settings::Instance()->OMP_THREADS))
#define FF_VIDEO_NUM_PROCESSORS std::min(omp_get_num_procs(), std::max(2, openshot::Settings::Instance()->FF_THREADS))
#define FF_AUDIO_NUM_PROCESSORS std::min(omp_get_num_procs(), std::max(2, openshot::Settings::Instance()->FF_THREADS))
#define OPEN_MP_NUM_PROCESSORS openshot::Settings::Instance()->EffectiveOMPThreads()
#define FF_VIDEO_NUM_PROCESSORS std::clamp(openshot::Settings::Instance()->FF_THREADS, 2, openshot::Settings::Instance()->MaxAllowedThreads())
#define FF_AUDIO_NUM_PROCESSORS std::clamp(openshot::Settings::Instance()->FF_THREADS, 2, openshot::Settings::Instance()->MaxAllowedThreads())

// Set max-active-levels to the max supported, if possible
// (supported_active_levels is OpenMP 5.0 (November 2018) or later, only.)
Expand Down
29 changes: 27 additions & 2 deletions src/Settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
//
// SPDX-License-Identifier: LGPL-3.0-or-later

#include <algorithm>
#include <cstdlib>
#include <omp.h>
#include "Settings.h"
Expand All @@ -19,18 +20,42 @@ using namespace openshot;
// Global reference to Settings
Settings *Settings::m_pInstance = nullptr;

int Settings::EffectiveOMPThreads() const
{
return std::clamp(OMP_THREADS, 2, MaxAllowedThreads());
}

int Settings::MaxAllowedThreads() const
{
return std::max(2, std::max(2, omp_get_num_procs()) * 3);
}

void Settings::ApplyOpenMPSettings()
{
const int requested_threads = EffectiveOMPThreads();
if (applied_omp_threads != requested_threads) {
omp_set_num_threads(requested_threads);
applied_omp_threads = requested_threads;
}
}

// Create or Get an instance of the settings singleton
Settings *Settings::Instance()
{
if (!m_pInstance) {
// Create the actual instance of Settings only once
m_pInstance = new Settings;
m_pInstance->OMP_THREADS = omp_get_num_procs();
m_pInstance->FF_THREADS = omp_get_num_procs();
const int machine_threads = std::max(2, omp_get_num_procs());
m_pInstance->default_omp_threads = machine_threads;
m_pInstance->default_ff_threads = machine_threads;
m_pInstance->OMP_THREADS = machine_threads;
m_pInstance->FF_THREADS = machine_threads;
auto env_debug = std::getenv("LIBOPENSHOT_DEBUG");
if (env_debug != nullptr)
m_pInstance->DEBUG_TO_STDERR = true;
}

m_pInstance->ApplyOpenMPSettings();

return m_pInstance;
}
28 changes: 26 additions & 2 deletions src/Settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ namespace openshot {
/// Private variable to keep track of singleton instance
static Settings * m_pInstance;

/// Last OMP thread count applied to the OpenMP runtime
int applied_omp_threads = 0;

/// Machine default OpenMP thread count detected at startup
int default_omp_threads = 2;

/// Machine default FFmpeg thread count detected at startup
int default_ff_threads = 2;

public:
/**
* @brief Use video codec for faster video decoding (if supported)
Expand All @@ -64,8 +73,8 @@ namespace openshot {
/// Scale mode used in FFmpeg decoding and encoding (used as an optimization for faster previews)
bool HIGH_QUALITY_SCALING = false;

/// Number of threads of OpenMP
int OMP_THREADS = 16;
/// Number of OpenMP threads
int OMP_THREADS = 2;

/// Number of threads that ffmpeg uses
int FF_THREADS = 16;
Expand Down Expand Up @@ -116,6 +125,21 @@ namespace openshot {
/// Whether to dump ZeroMQ debug messages to stderr
bool DEBUG_TO_STDERR = false;

/// Return the effective OpenMP worker budget used by libopenshot heuristics
int EffectiveOMPThreads() const;

/// Return the maximum allowed thread override based on this machine
int MaxAllowedThreads() const;

/// Return the machine default OpenMP thread count detected at startup
int DefaultOMPThreads() const { return default_omp_threads; }

/// Return the machine default FFmpeg thread count detected at startup
int DefaultFFThreads() const { return default_ff_threads; }

/// Apply any explicit OpenMP thread override to the runtime
void ApplyOpenMPSettings();

/// Create or get an instance of this logger singleton (invoke the class with this method)
static Settings * Instance();
};
Expand Down
54 changes: 33 additions & 21 deletions tests/Benchmark.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
// SPDX-License-Identifier: LGPL-3.0-or-later

#include <chrono>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <string>
#include <vector>

#include "BenchmarkOptions.h"
#include "Clip.h"
#include "FFmpegReader.h"
#include "FFmpegWriter.h"
Expand All @@ -26,6 +26,7 @@
#include "QtImageReader.h"
#endif
#include "ReaderBase.h"
#include "Settings.h"
#include "Timeline.h"
#include "effects/Brightness.h"
#include "effects/ChromaKey.h"
Expand Down Expand Up @@ -62,24 +63,35 @@ int main(int argc, char* argv[]) {
const string video = base + "sintel_trailer-720p.mp4";
const string mask_img = base + "mask.png";
const string overlay = base + "front3.png";
string filter_test;
bool list_only = false;
benchmark::BenchmarkOptions options;
const int64_t chroma_bench_frames = 500;

for (int i = 1; i < argc; ++i) {
const string arg = argv[i];
if ((arg == "--test" || arg == "-t") && i + 1 < argc) {
filter_test = argv[++i];
} else if (arg == "--list" || arg == "-l") {
list_only = true;
} else if (arg == "--help" || arg == "-h") {
cout << "Usage: openshot-benchmark [--test <name>] [--list]\n";
return 0;
} else {
cerr << "Unknown argument: " << arg << "\n";
cerr << "Usage: openshot-benchmark [--test <name>] [--list]\n";
return 1;
}
try {
vector<string> args;
args.reserve(std::max(0, argc - 1));
for (int i = 1; i < argc; ++i)
args.emplace_back(argv[i]);
options = benchmark::ParseBenchmarkOptions(args);
} catch (const std::exception& e) {
cerr << e.what() << "\n";
cerr << benchmark::BenchmarkUsage() << "\n";
return 1;
}

if (options.show_help) {
cout << benchmark::BenchmarkUsage() << "\n";
return 0;
}

// Route benchmark thread settings through libopenshot's Settings singleton,
// matching how an application should configure the library before opening readers.
Settings *settings = Settings::Instance();
if (options.omp_threads > 0) {
settings->OMP_THREADS = options.omp_threads;
settings->ApplyOpenMPSettings();
}
if (options.ff_threads > 0) {
settings->FF_THREADS = options.ff_threads;
}

vector<Trial> trials;
Expand Down Expand Up @@ -278,7 +290,7 @@ int main(int argc, char* argv[]) {
r.Close();
});

if (list_only) {
if (options.list_only) {
for (const auto& trial : trials)
cout << trial.first << "\n";
return 0;
Expand All @@ -288,14 +300,14 @@ int main(int argc, char* argv[]) {
double total = 0.0;
int executed = 0;
for (const auto& trial : trials) {
if (!filter_test.empty() && trial.first != filter_test)
if (!options.filter_test.empty() && trial.first != options.filter_test)
continue;
total += time_trial(trial.first, trial.second);
executed++;
}

if (!filter_test.empty() && executed == 0) {
cerr << "Unknown test: " << filter_test << "\nAvailable tests:\n";
if (!options.filter_test.empty() && executed == 0) {
cerr << "Unknown test: " << options.filter_test << "\nAvailable tests:\n";
for (const auto& trial : trials)
cerr << " " << trial.first << "\n";
return 2;
Expand Down
75 changes: 75 additions & 0 deletions tests/BenchmarkArgs.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* @file
* @brief Unit tests for benchmark CLI option parsing
* @author OpenShot Studios, LLC
*
* @ref License
*/

// Copyright (c) 2026 OpenShot Studios, LLC
//
// SPDX-License-Identifier: LGPL-3.0-or-later

#include "openshot_catch.h"

#include "BenchmarkOptions.h"

using namespace openshot::benchmark;

static void CHECK_RUNTIME_ERROR_CONTAINS(const std::vector<std::string>& args,
const std::string& expected_fragment) {
try {
(void) ParseBenchmarkOptions(args);
FAIL("Expected ParseBenchmarkOptions() to throw std::runtime_error");
} catch (const std::runtime_error& e) {
CHECK(std::string(e.what()).find(expected_fragment) != std::string::npos);
} catch (...) {
FAIL("Expected std::runtime_error");
}
}

TEST_CASE("Benchmark usage string includes new thread flags", "[benchmark][args]") {
const std::string usage = BenchmarkUsage();
CHECK(usage.find("--omp-threads <n>") != std::string::npos);
CHECK(usage.find("--ff-threads <n>") != std::string::npos);
}

TEST_CASE("Benchmark args default correctly", "[benchmark][args]") {
const BenchmarkOptions options = ParseBenchmarkOptions({});
CHECK(options.filter_test.empty());
CHECK_FALSE(options.list_only);
CHECK_FALSE(options.show_help);
CHECK(options.omp_threads == 0);
CHECK(options.ff_threads == 0);
}

TEST_CASE("Benchmark args parse valid values", "[benchmark][args]") {
const BenchmarkOptions options = ParseBenchmarkOptions({
"--test", "Timeline",
"--list",
"--omp-threads", "12",
"--ff-threads", "16"
});

CHECK(options.filter_test == "Timeline");
CHECK(options.list_only);
CHECK_FALSE(options.show_help);
CHECK(options.omp_threads == 12);
CHECK(options.ff_threads == 16);
}

TEST_CASE("Benchmark args reject invalid thread values", "[benchmark][args]") {
CHECK_RUNTIME_ERROR_CONTAINS({"--omp-threads", "0"}, "Invalid --omp-threads value");
CHECK_RUNTIME_ERROR_CONTAINS({"--omp-threads", "1"}, "Invalid --omp-threads value");
CHECK_RUNTIME_ERROR_CONTAINS({"--omp-threads", "-1"}, "Invalid --omp-threads value");
CHECK_RUNTIME_ERROR_CONTAINS({"--ff-threads", "0"}, "Invalid --ff-threads value");
CHECK_RUNTIME_ERROR_CONTAINS({"--ff-threads", "1"}, "Invalid --ff-threads value");
CHECK_RUNTIME_ERROR_CONTAINS({"--ff-threads", "-1"}, "Invalid --ff-threads value");
CHECK_RUNTIME_ERROR_CONTAINS({"--omp-threads", "abc"}, "Invalid --omp-threads value");
}

TEST_CASE("Benchmark args reject missing values and unknown args", "[benchmark][args]") {
CHECK_RUNTIME_ERROR_CONTAINS({"--test"}, "Missing value for --test");
CHECK_RUNTIME_ERROR_CONTAINS({"--ff-threads"}, "Missing value for --ff-threads");
CHECK_RUNTIME_ERROR_CONTAINS({"--wat"}, "Unknown argument");
}
76 changes: 76 additions & 0 deletions tests/BenchmarkOptions.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* @file
* @brief Shared benchmark CLI option parsing helpers
* @author OpenShot Studios, LLC
*
* @ref License
*/

// Copyright (c) 2026 OpenShot Studios, LLC
//
// SPDX-License-Identifier: LGPL-3.0-or-later

#include "BenchmarkOptions.h"

#include <stdexcept>

namespace openshot {
namespace benchmark {

std::string BenchmarkUsage() {
return "Usage: openshot-benchmark [--test <name>] [--list] [--omp-threads <n>] [--ff-threads <n>]";
}

static int ParseThreadArg(const std::string& flag, const std::string& value) {
int parsed = 0;
try {
size_t consumed = 0;
parsed = std::stoi(value, &consumed);
if (consumed != value.size()) {
throw std::invalid_argument("extra characters");
}
} catch (const std::exception&) {
throw std::runtime_error("Invalid " + flag + " value: " + value + " (expected >= 2)");
}

if (parsed < 2) {
throw std::runtime_error("Invalid " + flag + " value: " + value + " (expected >= 2)");
}

return parsed;
}

BenchmarkOptions ParseBenchmarkOptions(const std::vector<std::string>& args) {
BenchmarkOptions options;

for (size_t i = 0; i < args.size(); ++i) {
const std::string& arg = args[i];
if (arg == "--test" || arg == "-t") {
if (i + 1 >= args.size()) {
throw std::runtime_error("Missing value for --test");
}
options.filter_test = args[++i];
} else if (arg == "--omp-threads") {
if (i + 1 >= args.size()) {
throw std::runtime_error("Missing value for --omp-threads");
}
options.omp_threads = ParseThreadArg("--omp-threads", args[++i]);
} else if (arg == "--ff-threads") {
if (i + 1 >= args.size()) {
throw std::runtime_error("Missing value for --ff-threads");
}
options.ff_threads = ParseThreadArg("--ff-threads", args[++i]);
} else if (arg == "--list" || arg == "-l") {
options.list_only = true;
} else if (arg == "--help" || arg == "-h") {
options.show_help = true;
} else {
throw std::runtime_error("Unknown argument: " + arg);
}
}

return options;
}

} // namespace benchmark
} // namespace openshot
Loading
Loading