From f310cb6ce1f5cc60aeec278cbd74f86d9c6d4ee2 Mon Sep 17 00:00:00 2001 From: Frank Barchard Date: Sat, 17 Oct 2026 02:32:31 -0700 Subject: [PATCH] [libyuv] Pilot OpenMP support for row processing Add SetMaxThreads() and GetMaxThreads() APIs (with LIBYUV_NUM_THREADS environment variable override, defaulting to half of logical cores capped at 8 when built with -fopenmp). Enable OpenMP parallel row processing for large frames (>= 1280x720) in I420ToARGBMatrix and CopyPlane (including reshaping coalesced contiguous planes across threads), and configure thread counts for ComputeHammingDistance, ComputeSumSquareError, and CalcFrameSsim. 1280x720 Benchmarks (ms): Benchmark Reps 1T 2T 4T 8T Speedup -------------------------------------------------------- I420ToARGB_Opt 1000 272 162 88 60 4.53x CopyPlane_Opt 1000 57 42 19 14 4.07x BenchmarkPsnr_Opt 1000 92 76 56 34 2.71x BenchmarkSsim_Opt 100 518 241 147 89 5.82x R=rrwinterton@gmail.com Bug: none Test: libyuv_unittest --gtest_filter=*TestSetMaxThreads*:*I420ToARGB_Opt*:*CopyPlane_Opt*:*Ssim* Change-Id: I5ca9a2b8c68eaac870da6353c51306930d015c3a CONV=a7f09b98-93ab-4652-a8eb-8d9551361f61 TAG=agy --- docs/environment_variables.md | 1 + include/libyuv/cpu_id.h | 10 +++++++ source/compare.cc | 7 +++-- source/convert_argb.cc | 21 ++++++++++++++ source/cpu_id.cc | 54 ++++++++++++++++++++++++++++++++++- source/planar_functions.cc | 45 +++++++++++++++++++++++++++++ unit_test/cpu_test.cc | 18 ++++++++++++ util/cpuid.c | 1 + 8 files changed, 153 insertions(+), 4 deletions(-) diff --git a/docs/environment_variables.md b/docs/environment_variables.md index a8c7aef6b..01816db56 100644 --- a/docs/environment_variables.md +++ b/docs/environment_variables.md @@ -66,3 +66,4 @@ You can also repeat the test a specified number of iterations, allowing benchmar set LIBYUV_REPEAT=999 set LIBYUV_FLAGS=-1 set LIBYUV_CPU_INFO=-1 + set LIBYUV_NUM_THREADS=4 diff --git a/include/libyuv/cpu_id.h b/include/libyuv/cpu_id.h index 917df1cb4..06b1da475 100644 --- a/include/libyuv/cpu_id.h +++ b/include/libyuv/cpu_id.h @@ -150,6 +150,16 @@ static __inline void SetCpuFlags(int cpu_flags) { LIBYUV_API void CpuId(int info_eax, int info_ecx, int* cpu_info); +// Set maximum number of threads for OpenMP. +// 0 = auto (default: half of logical cores, capped at 8). +// Returns the number of threads configured. +LIBYUV_API +int SetMaxThreads(int num_threads); + +// Get maximum number of threads for OpenMP. +LIBYUV_API +int GetMaxThreads(void); + #ifdef __cplusplus } // extern "C" } // namespace libyuv diff --git a/source/compare.cc b/source/compare.cc index daac8b985..fd2ef6e4b 100644 --- a/source/compare.cc +++ b/source/compare.cc @@ -200,7 +200,7 @@ uint64_t ComputeHammingDistance(const uint8_t* src_a, remainder = count & (kBlockSize - 1) & ~(simd_size - 1); #ifdef _OPENMP -#pragma omp parallel for reduction(+ : diff) +#pragma omp parallel for reduction(+ : diff) num_threads(GetMaxThreads()) #endif for (i = 0; i < (count - (kBlockSize - 1)); i += kBlockSize) { diff += HammingDistance(src_a + i, src_b + i, kBlockSize); @@ -275,7 +275,7 @@ uint64_t ComputeSumSquareError(const uint8_t* src_a, #endif remainder = count & (kBlockSize - 1) & ~(simd_size - 1); #ifdef _OPENMP -#pragma omp parallel for reduction(+ : sse) +#pragma omp parallel for reduction(+ : sse) num_threads(GetMaxThreads()) #endif for (i = 0; i < (count - (kBlockSize - 1)); i += kBlockSize) { sse += SumSquareError(src_a + i, src_b + i, kBlockSize); @@ -532,7 +532,8 @@ double CalcFrameSsim(const uint8_t* src_a, const int rem_width = tile_width & (simd_size - 1); #ifdef _OPENMP -#pragma omp parallel reduction(+ : ssim_total, samples) +#pragma omp parallel reduction(+ : ssim_total, samples) \ + num_threads(GetMaxThreads()) #endif { align_buffer_64(row_buf, (size_t)tile_width * 2 * sizeof(uint32_t)); diff --git a/source/convert_argb.cc b/source/convert_argb.cc index 19ce4cfec..29a175087 100644 --- a/source/convert_argb.cc +++ b/source/convert_argb.cc @@ -13,6 +13,9 @@ #include #include #include // For memcpy. +#ifdef _OPENMP +#include +#endif #include "libyuv/convert_from_argb.h" #include "libyuv/cpu_id.h" @@ -155,6 +158,23 @@ int I420ToARGBMatrix(const uint8_t* src_y, } #endif +#if defined(_OPENMP) + { + int num_threads = GetMaxThreads(); + int use_omp = (num_threads > 1) && + ((ptrdiff_t)width * height >= 1280 * 720) && + (height >= num_threads); +#pragma omp parallel for schedule(static) num_threads(num_threads) if (use_omp) + for (y = 0; y < height; ++y) { + const uint8_t* row_src_y = src_y + (ptrdiff_t)y * src_stride_y; + const uint8_t* row_src_u = src_u + (ptrdiff_t)(y >> 1) * src_stride_u; + const uint8_t* row_src_v = src_v + (ptrdiff_t)(y >> 1) * src_stride_v; + uint8_t* row_dst_argb = dst_argb + (ptrdiff_t)y * dst_stride_argb; + I422ToARGBRow(row_src_y, row_src_u, row_src_v, row_dst_argb, yuvconstants, + width); + } + } +#else for (y = 0; y < height; ++y) { I422ToARGBRow(src_y, src_u, src_v, dst_argb, yuvconstants, width); dst_argb += dst_stride_argb; @@ -164,6 +184,7 @@ int I420ToARGBMatrix(const uint8_t* src_y, src_v += src_stride_v; } } +#endif return 0; } diff --git a/source/cpu_id.cc b/source/cpu_id.cc index dea87ad51..108f78e02 100644 --- a/source/cpu_id.cc +++ b/source/cpu_id.cc @@ -20,8 +20,12 @@ #endif // For ArmCpuCaps() but unittested on all platforms -#include // For fopen() +#include // For fopen() +#include // For getenv(), atoi() #include +#ifdef _OPENMP +#include +#endif #if defined(__linux__) && (defined(__aarch64__) || defined(__loongarch__)) #include // For getauxval() @@ -521,6 +525,54 @@ int InitCpuFlags(void) { return MaskCpuFlags(-1); } +#if defined(_OPENMP) +static int max_threads_ = 0; +#endif + +LIBYUV_API +int SetMaxThreads(int num_threads) { +#if defined(_OPENMP) + if (num_threads <= 0) { + int procs = omp_get_num_procs(); + if (procs <= 2) { + num_threads = 1; + } else if (procs <= 4) { + num_threads = 2; + } else { + num_threads = procs / 2; // Avoid hyperthreads and avoid consuming host + if (num_threads > 8) { + num_threads = 8; // Prevent memory channel saturation + } + } + } + max_threads_ = num_threads; + omp_set_num_threads(num_threads); + return num_threads; +#else + (void)num_threads; + return 1; +#endif +} + +LIBYUV_API +int GetMaxThreads(void) { +#if defined(_OPENMP) + if (max_threads_ <= 0) { + const char* env_threads = getenv("LIBYUV_NUM_THREADS"); + if (env_threads) { + int t = atoi(env_threads); + if (t > 0) { + return SetMaxThreads(t); + } + } + return SetMaxThreads(0); + } + return max_threads_; +#else + return 1; +#endif +} + #ifdef __cplusplus } // extern "C" } // namespace libyuv diff --git a/source/planar_functions.cc b/source/planar_functions.cc index 5af256382..cea7cd250 100644 --- a/source/planar_functions.cc +++ b/source/planar_functions.cc @@ -13,6 +13,9 @@ #include #include #include // for memset() +#ifdef _OPENMP +#include +#endif #include "libyuv/convert_from_argb.h" // For ArgbConstants #include "libyuv/cpu_id.h" @@ -24,6 +27,28 @@ namespace libyuv { extern "C" { #endif +#if defined(_OPENMP) +// Reshape coalesced 1D buffer into a 2D rectangle for parallel row processing. +static void ComputeParallelShape(int total_bytes, + int num_threads, + int* out_width, + int* out_height) { + const int kAlign = 64; + if (num_threads > 1 && total_bytes >= 1280 * 720) { + int total_blocks = total_bytes / kAlign; + int blocks_per_thread = total_blocks / num_threads; + if (blocks_per_thread > 0 && + blocks_per_thread * num_threads == total_blocks) { + *out_width = blocks_per_thread * kAlign; + *out_height = num_threads; + return; + } + } + *out_width = total_bytes; + *out_height = 1; +} +#endif + // Copy a plane of data LIBYUV_API void CopyPlane(const uint8_t* src_y, @@ -46,9 +71,15 @@ void CopyPlane(const uint8_t* src_y, // Coalesce rows. if (src_stride_y == width && dst_stride_y == width && (ptrdiff_t)width * height <= INT_MAX) { +#if defined(_OPENMP) + int num_threads = GetMaxThreads(); + ComputeParallelShape(width * height, num_threads, &width, &height); + src_stride_y = dst_stride_y = (height > 1) ? width : 0; +#else width *= height; height = 1; src_stride_y = dst_stride_y = 0; +#endif } // Nothing to do. if (src_y == dst_y && src_stride_y == dst_stride_y) { @@ -112,11 +143,25 @@ void CopyPlane(const uint8_t* src_y, #endif // Copy plane +#if defined(_OPENMP) + { + int num_threads = GetMaxThreads(); + int use_omp = (num_threads > 1) && + ((ptrdiff_t)width * height >= 1280 * 720) && + (height >= num_threads); +#pragma omp parallel for schedule(static) num_threads(num_threads) if (use_omp) + for (y = 0; y < height; ++y) { + CopyRow(src_y + (ptrdiff_t)y * src_stride_y, + dst_y + (ptrdiff_t)y * dst_stride_y, width); + } + } +#else for (y = 0; y < height; ++y) { CopyRow(src_y, dst_y, width); src_y += src_stride_y; dst_y += dst_stride_y; } +#endif } LIBYUV_API diff --git a/unit_test/cpu_test.cc b/unit_test/cpu_test.cc index 1a08f609b..00ceb0ff6 100644 --- a/unit_test/cpu_test.cc +++ b/unit_test/cpu_test.cc @@ -463,4 +463,22 @@ TEST_F(LibYUVBaseTest, MAYBE_TestSetCpuFlags) { MaskCpuFlags(benchmark_cpu_info_); } +TEST_F(LibYUVBaseTest, TestSetMaxThreads) { +#ifdef _OPENMP + printf("_OPENMP %d\n", _OPENMP); +#endif + int default_threads = GetMaxThreads(); + printf("Max Threads %d\n", default_threads); + EXPECT_GE(default_threads, 1); + + int threads_1 = SetMaxThreads(1); + EXPECT_EQ(1, threads_1); + EXPECT_EQ(1, GetMaxThreads()); + + int restored_threads = SetMaxThreads(0); + EXPECT_GE(restored_threads, 1); + EXPECT_LE(restored_threads, 8); + EXPECT_EQ(restored_threads, GetMaxThreads()); +} + } // namespace libyuv diff --git a/util/cpuid.c b/util/cpuid.c index 33cd4201a..329db23ab 100644 --- a/util/cpuid.c +++ b/util/cpuid.c @@ -61,6 +61,7 @@ int main(int argc, const char* argv[]) { printf("Kernel Version %d.%d\n", kernelversion[0], kernelversion[1]); } #endif // defined(__linux__) + printf("Max Threads %d\n", GetMaxThreads()); #if defined(__arm__) || defined(__aarch64__) int has_arm = TestCpuFlag(kCpuHasARM);