From d32d4da9a39ce92bdc5603749ea6f2d90215f4ea Mon Sep 17 00:00:00 2001 From: kyler1cartesis <87242609+kyler1cartesis@users.noreply.github.com> Date: Wed, 19 Feb 2025 12:13:48 +0300 Subject: [PATCH] Merge pull request #26887 from kyler1cartesis:4.x invSqrt SIMD_SCALABLE implementation & HAL tests refactoring #26887 Enable CV_SIMD_SCALABLE for invSqrt. * Banana Pi BF3 (SpacemiT K1) RISC-V * Compiler: Syntacore Clang 18.1.4 (build 2024.12) ``` Geometric mean (ms) Name of Test baseline simd simd scalable scalable vs baseline (x-factor) InvSqrtf::InvSqrtfFixture::(127x61, 32FC1) 0.163 0.051 3.23 InvSqrtf::InvSqrtfFixture::(127x61, 64FC1) 0.241 0.103 2.35 InvSqrtf::InvSqrtfFixture::(640x480, 32FC1) 6.460 1.893 3.41 InvSqrtf::InvSqrtfFixture::(640x480, 64FC1) 9.687 3.999 2.42 InvSqrtf::InvSqrtfFixture::(1280x720, 32FC1) 19.292 5.701 3.38 InvSqrtf::InvSqrtfFixture::(1280x720, 64FC1) 29.452 11.963 2.46 InvSqrtf::InvSqrtfFixture::(1920x1080, 32FC1) 43.326 12.805 3.38 InvSqrtf::InvSqrtfFixture::(1920x1080, 64FC1) 65.566 26.881 2.44 ``` --- modules/core/perf/perf_arithm.cpp | 17 ++ modules/core/src/mathfuncs_core.simd.hpp | 4 +- modules/core/test/test_hal_core.cpp | 289 +++++++++++------------ 3 files changed, 161 insertions(+), 149 deletions(-) diff --git a/modules/core/perf/perf_arithm.cpp b/modules/core/perf/perf_arithm.cpp index d98eb9abb3..6cc25a3476 100644 --- a/modules/core/perf/perf_arithm.cpp +++ b/modules/core/perf/perf_arithm.cpp @@ -706,6 +706,23 @@ INSTANTIATE_TEST_CASE_P(/*nothing*/ , ArithmMixedTest, ) ); +typedef Size_MatType InvSqrtFixture; +PERF_TEST_P(InvSqrtFixture, InvSqrt, testing::Combine( + testing::Values(TYPICAL_MAT_SIZES), + testing::Values(CV_32FC1, CV_64FC1))) +{ + Size sz = get<0>(GetParam()); + int type = get<1>(GetParam()); + + Mat src(sz, type), dst(sz, type); + randu(src, FLT_EPSILON, 1000); + declare.in(src).out(dst); + + TEST_CYCLE() cv::pow(src, -0.5, dst); + + SANITY_CHECK_NOTHING(); +} + ///////////// Rotate //////////////////////// typedef perf::TestBaseWithParam> RotateTest; diff --git a/modules/core/src/mathfuncs_core.simd.hpp b/modules/core/src/mathfuncs_core.simd.hpp index cb21064041..3fa3cba1b8 100644 --- a/modules/core/src/mathfuncs_core.simd.hpp +++ b/modules/core/src/mathfuncs_core.simd.hpp @@ -340,7 +340,7 @@ void invSqrt32f(const float* src, float* dst, int len) int i = 0; -#if CV_SIMD +#if (CV_SIMD || CV_SIMD_SCALABLE) const int VECSZ = VTraits::vlanes(); for( ; i < len; i += VECSZ*2 ) { @@ -368,7 +368,7 @@ void invSqrt64f(const double* src, double* dst, int len) CV_INSTRUMENT_REGION(); int i = 0; -#if CV_SIMD_64F +#if (CV_SIMD_64F || CV_SIMD_SCALABLE_64F) const int VECSZ = VTraits::vlanes(); for ( ; i < len; i += VECSZ*2) { diff --git a/modules/core/test/test_hal_core.cpp b/modules/core/test/test_hal_core.cpp index f9078e55f9..a86016d44f 100644 --- a/modules/core/test/test_hal_core.cpp +++ b/modules/core/test/test_hal_core.cpp @@ -42,168 +42,163 @@ namespace opencv_test { namespace { -enum +enum HALFunc { HAL_EXP = 0, HAL_LOG = 1, - HAL_SQRT = 2 + HAL_SQRT = 2, + HAL_INV_SQRT = 3, + HAL_LU = 4, + HAL_CHOL = 5, }; -TEST(Core_HAL, mathfuncs) +void PrintTo(const HALFunc& v, std::ostream* os) { - for( int hcase = 0; hcase < 6; hcase++ ) - { - int depth = hcase % 2 == 0 ? CV_32F : CV_64F; - double eps = depth == CV_32F ? 1e-5 : 1e-10; - int nfunc = hcase / 2; - int n = 100; - - Mat src(1, n, depth), dst(1, n, depth), dst0(1, n, depth); - randu(src, 1, 10); - - double min_hal_t = DBL_MAX, min_ocv_t = DBL_MAX; - - for( int iter = 0; iter < 10; iter++ ) - { - double t = (double)getTickCount(); - switch (nfunc) - { - case HAL_EXP: - if( depth == CV_32F ) - hal::exp32f(src.ptr(), dst.ptr(), n); - else - hal::exp64f(src.ptr(), dst.ptr(), n); - break; - case HAL_LOG: - if( depth == CV_32F ) - hal::log32f(src.ptr(), dst.ptr(), n); - else - hal::log64f(src.ptr(), dst.ptr(), n); - break; - case HAL_SQRT: - if( depth == CV_32F ) - hal::sqrt32f(src.ptr(), dst.ptr(), n); - else - hal::sqrt64f(src.ptr(), dst.ptr(), n); - break; - default: - CV_Error(Error::StsBadArg, "unknown function"); - } - t = (double)getTickCount() - t; - min_hal_t = std::min(min_hal_t, t); - - t = (double)getTickCount(); - switch (nfunc) - { - case HAL_EXP: - exp(src, dst0); - break; - case HAL_LOG: - log(src, dst0); - break; - case HAL_SQRT: - pow(src, 0.5, dst0); - break; - default: - CV_Error(Error::StsBadArg, "unknown function"); - } - t = (double)getTickCount() - t; - min_ocv_t = std::min(min_ocv_t, t); - } - EXPECT_LE(cvtest::norm(dst, dst0, NORM_INF | NORM_RELATIVE), eps); - - double freq = getTickFrequency(); - printf("%s (N=%d, %s): hal time=%.2fusec, ocv time=%.2fusec\n", - (nfunc == HAL_EXP ? "exp" : nfunc == HAL_LOG ? "log" : nfunc == HAL_SQRT ? "sqrt" : "???"), - n, (depth == CV_32F ? "f32" : "f64"), min_hal_t*1e6/freq, min_ocv_t*1e6/freq); - } + switch (v) { + case HAL_EXP: *os << "HAL_EXP"; return; + case HAL_LOG: *os << "HAL_LOG"; return; + case HAL_SQRT: *os << "HAL_SQRT"; return; + case HAL_INV_SQRT: *os << "HAL_INV_SQRT"; return; + case HAL_LU: *os << "LU"; return; + case HAL_CHOL: *os << "Cholesky"; return; + } // don't use "default:" to emit compiler warnings } -enum +typedef testing::TestWithParam > mathfuncs; +TEST_P(mathfuncs, accuracy) { - HAL_LU = 0, - HAL_CHOL = 1 -}; + const int depth = std::get<0>(GetParam()); + const int nfunc = std::get<1>(GetParam()); -typedef testing::TestWithParam HAL; + double eps = depth == CV_32F ? 1e-5 : 1e-10; + int n = 100; -TEST_P(HAL, mat_decomp) -{ - int hcase = GetParam(); - SCOPED_TRACE(cv::format("hcase=%d", hcase)); + Mat src(1, n, depth), dst(1, n, depth), dst0(1, n, depth); + randu(src, 1, 10); + + switch (nfunc) { - int depth = hcase % 2 == 0 ? CV_32F : CV_64F; - int size = (hcase / 2) % 4; - size = size == 0 ? 3 : size == 1 ? 4 : size == 2 ? 6 : 15; - int nfunc = (hcase / 8); - #if CV_LASX - double eps = depth == CV_32F ? 1e-5 : 2e-10; - #else - double eps = depth == CV_32F ? 1e-5 : 1e-10; - #endif + case HAL_EXP: + if( depth == CV_32F ) + hal::exp32f(src.ptr(), dst.ptr(), n); + else + hal::exp64f(src.ptr(), dst.ptr(), n); + break; + case HAL_LOG: + if( depth == CV_32F ) + hal::log32f(src.ptr(), dst.ptr(), n); + else + hal::log64f(src.ptr(), dst.ptr(), n); + break; + case HAL_SQRT: + if( depth == CV_32F ) + hal::sqrt32f(src.ptr(), dst.ptr(), n); + else + hal::sqrt64f(src.ptr(), dst.ptr(), n); + break; + case HAL_INV_SQRT: + if( depth == CV_32F ) + hal::invSqrt32f(src.ptr(), dst.ptr(), n); + else + hal::invSqrt64f(src.ptr(), dst.ptr(), n); + break; - if( size == 3 ) - return; // TODO ??? - - Mat a0(size, size, depth), a(size, size, depth), b(size, 1, depth), x(size, 1, depth), x0(size, 1, depth); - randu(a0, -1, 1); - a0 = a0*a0.t(); - randu(b, -1, 1); - - double min_hal_t = DBL_MAX, min_ocv_t = DBL_MAX; - size_t asize = size*size*a.elemSize(); - size_t bsize = size*b.elemSize(); - - for( int iter = 0; iter < 10; iter++ ) - { - memcpy(x.ptr(), b.ptr(), bsize); - memcpy(a.ptr(), a0.ptr(), asize); - - double t = (double)getTickCount(); - switch (nfunc) - { - case HAL_LU: - if( depth == CV_32F ) - hal::LU32f(a.ptr(), a.step, size, x.ptr(), x.step, 1); - else - hal::LU64f(a.ptr(), a.step, size, x.ptr(), x.step, 1); - break; - case HAL_CHOL: - if( depth == CV_32F ) - hal::Cholesky32f(a.ptr(), a.step, size, x.ptr(), x.step, 1); - else - hal::Cholesky64f(a.ptr(), a.step, size, x.ptr(), x.step, 1); - break; - default: - CV_Error(Error::StsBadArg, "unknown function"); - } - t = (double)getTickCount() - t; - min_hal_t = std::min(min_hal_t, t); - - t = (double)getTickCount(); - bool solveStatus = solve(a0, b, x0, (nfunc == HAL_LU ? DECOMP_LU : DECOMP_CHOLESKY)); - t = (double)getTickCount() - t; - EXPECT_TRUE(solveStatus); - min_ocv_t = std::min(min_ocv_t, t); - } - //std::cout << "x: " << Mat(x.t()) << std::endl; - //std::cout << "x0: " << Mat(x0.t()) << std::endl; - - EXPECT_LE(cvtest::norm(x, x0, NORM_INF | NORM_RELATIVE), eps) - << "x: " << Mat(x.t()) - << "\nx0: " << Mat(x0.t()) - << "\na0: " << a0 - << "\nb: " << b; - - double freq = getTickFrequency(); - printf("%s (%d x %d, %s): hal time=%.2fusec, ocv time=%.2fusec\n", - (nfunc == HAL_LU ? "LU" : nfunc == HAL_CHOL ? "Cholesky" : "???"), - size, size, - (depth == CV_32F ? "f32" : "f64"), - min_hal_t*1e6/freq, min_ocv_t*1e6/freq); + default: + CV_Error(Error::StsBadArg, "unknown function"); } + + src.copyTo(dst0); + switch (nfunc) + { + case HAL_EXP: + if( depth == CV_32F ) + dst0.forEach([](float& v, const int*) -> void { v = std::exp(v); }); + else + dst0.forEach([](double& v, const int*) -> void { v = std::exp(v); }); + break; + case HAL_LOG: + if( depth == CV_32F ) + dst0.forEach([](float& v, const int*) -> void { v = std::log(v); }); + else + dst0.forEach([](double& v, const int*) -> void { v = std::log(v); }); + break; + case HAL_SQRT: + if( depth == CV_32F ) + dst0.forEach([](float& v, const int*) -> void { v = std::sqrt(v); }); + else + dst0.forEach([](double& v, const int*) -> void { v = std::sqrt(v); }); + break; + case HAL_INV_SQRT: + if( depth == CV_32F ) + dst0.forEach([](float& v, const int*) -> void { v = std::pow(v, -0.5f); }); + else + dst0.forEach([](double& v, const int*) -> void { v = std::pow(v, -0.5); }); + break; + default: + CV_Error(Error::StsBadArg, "unknown function"); + } + EXPECT_LE(cvtest::norm(dst, dst0, NORM_INF | NORM_RELATIVE), eps); +} +INSTANTIATE_TEST_CASE_P(Core_HAL, mathfuncs, + testing::Combine( + testing::Values(CV_32F, CV_64F), + testing::Values(HAL_EXP, HAL_LOG, HAL_SQRT, HAL_INV_SQRT) + ) +); + +typedef testing::TestWithParam > mat_decomp; +TEST_P(mat_decomp, accuracy) +{ + const int depth = std::get<0>(GetParam()); + const int nfunc = std::get<1>(GetParam()); + const int size = std::get<2>(GetParam()); + +#if CV_LASX + double eps = depth == CV_32F ? 1e-5 : 2e-10; +#else + double eps = depth == CV_32F ? 1e-5 : 1e-10; +#endif + + Mat a0(size, size, depth), x0(size, 1, depth); + randu(a0, -1, 1); + a0 = a0*a0.t(); + randu(x0, -1, 1); + Mat b = a0 * x0; + Mat x = b.clone(); + Mat a = a0.clone(); + + int solveStatus; + switch (nfunc) + { + case HAL_LU: + if( depth == CV_32F ) + solveStatus = hal::LU32f(a.ptr(), a.step, size, x.ptr(), x.step, 1); + else + solveStatus = hal::LU64f(a.ptr(), a.step, size, x.ptr(), x.step, 1); + break; + case HAL_CHOL: + if( depth == CV_32F ) + solveStatus = hal::Cholesky32f(a.ptr(), a.step, size, x.ptr(), x.step, 1); + else + solveStatus = hal::Cholesky64f(a.ptr(), a.step, size, x.ptr(), x.step, 1); + break; + default: + CV_Error(Error::StsBadArg, "unknown function"); + } + EXPECT_NE(0, solveStatus); + EXPECT_LE(cvtest::norm(a0 * x, b, NORM_INF | NORM_RELATIVE), eps) + << "x: " << Mat(x.t()) + << "\nx0: " << Mat(x0.t()) + << "\na0: " << a0 + << "\nb: " << b; } -INSTANTIATE_TEST_CASE_P(Core, HAL, testing::Range(0, 16)); +INSTANTIATE_TEST_CASE_P(Core_HAL, mat_decomp, + testing::Combine( + testing::Values(CV_32F, CV_64F), + testing::Values(HAL_LU, HAL_CHOL), + testing::Values(3, 4, 6, 15) + ) +); }} // namespace