[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
This commit is contained in:
Frank Barchard 2026-10-17 02:32:31 -07:00
parent 700f153cb8
commit f310cb6ce1
8 changed files with 153 additions and 4 deletions

View File

@ -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

View File

@ -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

View File

@ -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));

View File

@ -13,6 +13,9 @@
#include <assert.h>
#include <limits.h>
#include <string.h> // For memcpy.
#ifdef _OPENMP
#include <omp.h>
#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;
}

View File

@ -20,8 +20,12 @@
#endif
// For ArmCpuCaps() but unittested on all platforms
#include <stdio.h> // For fopen()
#include <stdio.h> // For fopen()
#include <stdlib.h> // For getenv(), atoi()
#include <string.h>
#ifdef _OPENMP
#include <omp.h>
#endif
#if defined(__linux__) && (defined(__aarch64__) || defined(__loongarch__))
#include <sys/auxv.h> // 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

View File

@ -13,6 +13,9 @@
#include <assert.h>
#include <limits.h>
#include <string.h> // for memset()
#ifdef _OPENMP
#include <omp.h>
#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

View File

@ -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

View File

@ -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);