Files
opencv/modules/core/perf/perf_arithm.cpp
T
Teddy-Yangjiale 95e8141250 Merge pull request #30069 from Teddy-Yangjiale:rvv-f64-arithm
core: enable CV_64F arithmetic SIMD kernels for scalable SIMD - #30069

### Summary

The CV_64F element-wise arithmetic kernels in `modules/core/src/arithm.simd.hpp` are guarded by `CV_SIMD_64F`, which is 0 on scalable-vector (RVV) builds, while the kernels themselves are already written with width-independent universal intrinsics (`v_float64`, `vx_load`, `VTraits<>::vlanes()`). On RVV every CV_64F arithmetic operation therefore falls back to the scalar kernels even though `CV_SIMD_SCALABLE_64F` is 1 and `v_float64` is fully supported.

The same file already uses the dual guard `CV_SIMD_64F || CV_SIMD_SCALABLE_64F` in four other places (see the comment at L166: "scalable (RVV) has v_float64 without CV_SIMD_64F"), so the 12 remaining single-guard sites look like an oversight. This PR widens them.

### What becomes vectorized on RVV (VLEN=256 => 4 double lanes)

- `cv::add` / `cv::subtract` (CV_64F)
- `cv::multiply` (CV_64F, plus the 32U/32S f64-work-type kernels)
- `cv::divide` (DIVW group: 32U/32S/64U/64S/64F)
- `cv::min` / `cv::max` (CV_64F)
- `cv::absdiff` (CV_64F)
- `cv::compare` (CV_64F)
- `cv::addWeighted` (AWD group: 32U/32S/64U/64S/64F)

All affected kernels are element-wise with no cross-lane reductions, so results are identical to the scalar kernels (per-element IEEE operations). The only theoretical difference is that the addWeighted vector body uses fused `v_fma` while the scalar tail does separate mul+add (at most 1 ulp); the same code already passes x86 AVX2/AVX512 CI where FMA is used as well.

On fixed-width backends (x86, AArch64, LoongArch, MIPS MSA, Power VSX) `CV_SIMD_64F` is already 1, so the guard change compiles to exactly the same code as before — no impact on other architectures. ARMv7/AArch32 and WASM SIMD128 have no f64 vector type at all (`CV_SIMD128_64F=0` by design) and keep the scalar path unchanged.

### Scope

Generic `rv64gc + CPU_DISPATCH=RVV` builds do not benefit yet (`arithm` is not a dispatched file); the win applies to builds where the core library itself is compiled with RVV enabled (`CPU_BASELINE=RVV`, the common embedded configuration on SpacemiT K1/K3 boards).

### Build configuration

Cross-compiled for SpacemiT K1 (8x 1.6 GHz, RVV 1.0, VLEN=256), GCC 14.2.1, `-O3`, both sides built with identical flags:

```bash
cmake -GNinja -DCMAKE_TOOLCHAIN_FILE=riscv64-spacemit-k1.toolchain.cmake \
      -DCMAKE_BUILD_TYPE=Release -DCPU_BASELINE=RVV -DCPU_DISPATCH= \
      -DBUILD_LIST=core,ts,calib,geometry -DBUILD_TESTS=ON -DBUILD_PERF_TESTS=ON \
      -DWITH_LAPACK=OFF -DWITH_EIGEN=OFF -DWITH_OPENCL=OFF \
      -DWITH_TIFF=OFF -DWITH_OPENJPEG=OFF -DWITH_JASPER=OFF -DWITH_OPENEXR=OFF \
      -DWITH_GDAL=OFF -DWITH_GDCM=OFF -DWITH_AVIF=OFF -DWITH_JPEGXL=OFF -DWITH_IMGCODEC_GIF=OFF ..

ninja opencv_core opencv_test_core opencv_perf_core opencv_test_calib opencv_test_geometry
```


### Correctness validation (on board)

```bash
export OPENCV_TEST_DATA_PATH=.../opencv_extra/testdata
export OPENCV_OPENCL_RUNTIME=disabled
./bin/opencv_test_core     --test_threads=4
./bin/opencv_test_calib    --test_threads=4
./bin/opencv_test_geometry --test_threads=4
```

| suite | baseline | with this PR |
|---|---|---|
| `opencv_test_core` (16757 tests) | 16756 pass, 1 fail: `Samples.findFile` (missing samples data path, environment) | **identical failure set** |
| `opencv_test_calib` (34 tests) | 34/34 pass | 34/34 pass |
| `opencv_test_geometry` (323 tests) | 323/323 pass | 323/323 pass |

Zero new failures; the arithm accuracy suites (`Core_*ElemWiseTest`, `Core_AddWeighted*`, `Core_*Mixed/ArithmMixedTest`, compare) exercise the newly vectorized kernels at all depths and pass unchanged.

### Performance (SpacemiT K1, single-threaded)

This PR adds a CV_64F perf fixture (`F64ArithmTest`: 9 ops x 4 sizes x CV_64FC1 = 36 cases) to `modules/core/perf/perf_arithm.cpp`, since the existing `BinaryOpTest` fixture does not parametrize CV_64F (and has no `compare`/`addWeighted` cases at all). Numbers below are per-case minima over 4 alternating A/B rounds x 50 samples, reported with the standard `modules/ts/misc/summary.py` (`-m min`).

With the default build flags (`-O3`, GCC 14.2.1):

```
Min (ms)

                 Name of Test                   base   cand     cand
                                                min4   min4     min4
                                                                 vs
                                                                base
                                                                min4
                                                             (x-factor)
absdiff::F64ArithmTest::(127x61, 64FC1)        0.035  0.026     1.33
absdiff::F64ArithmTest::(640x480, 64FC1)       1.580  1.644     0.96
absdiff::F64ArithmTest::(1280x720, 64FC1)      4.533  5.049     0.90
absdiff::F64ArithmTest::(1920x1080, 64FC1)     10.059 10.936    0.92
add::F64ArithmTest::(127x61, 64FC1)            0.027  0.028     0.98
add::F64ArithmTest::(640x480, 64FC1)           1.529  1.561     0.98
add::F64ArithmTest::(1280x720, 64FC1)          4.560  4.450     1.02
add::F64ArithmTest::(1920x1080, 64FC1)         9.544  9.549     1.00
addWeighted::F64ArithmTest::(127x61, 64FC1)    0.033  0.028     1.14
addWeighted::F64ArithmTest::(640x480, 64FC1)   1.582  1.581     1.00
addWeighted::F64ArithmTest::(1280x720, 64FC1)  4.579  4.797     0.95
addWeighted::F64ArithmTest::(1920x1080, 64FC1) 10.255 9.885     1.04
compare::F64ArithmTest::(127x61, 64FC1)        0.099  0.035     2.85
compare::F64ArithmTest::(640x480, 64FC1)       3.834  1.300     2.95
compare::F64ArithmTest::(1280x720, 64FC1)      11.456 3.847     2.98
compare::F64ArithmTest::(1920x1080, 64FC1)     25.670 8.738     2.94
divide::F64ArithmTest::(127x61, 64FC1)         0.064  0.065     0.98
divide::F64ArithmTest::(640x480, 64FC1)        2.493  2.352     1.06
divide::F64ArithmTest::(1280x720, 64FC1)       7.359  7.192     1.02
divide::F64ArithmTest::(1920x1080, 64FC1)      16.306 15.548    1.05
max::F64ArithmTest::(127x61, 64FC1)            0.029  0.026     1.12
max::F64ArithmTest::(640x480, 64FC1)           1.583  1.600     0.99
max::F64ArithmTest::(1280x720, 64FC1)          4.746  4.963     0.96
max::F64ArithmTest::(1920x1080, 64FC1)         9.827  10.143    0.97
min::F64ArithmTest::(127x61, 64FC1)            0.029  0.026     1.10
min::F64ArithmTest::(640x480, 64FC1)           1.625  1.630     1.00
min::F64ArithmTest::(1280x720, 64FC1)          4.821  5.000     0.96
min::F64ArithmTest::(1920x1080, 64FC1)         9.363  11.126    0.84
multiply::F64ArithmTest::(127x61, 64FC1)       0.029  0.027     1.10
multiply::F64ArithmTest::(640x480, 64FC1)      1.680  1.762     0.95
multiply::F64ArithmTest::(1280x720, 64FC1)     4.835  4.929     0.98
multiply::F64ArithmTest::(1920x1080, 64FC1)    10.427 10.564    0.99
subtract::F64ArithmTest::(127x61, 64FC1)       0.027  0.026     1.06
subtract::F64ArithmTest::(640x480, 64FC1)      1.605  1.660     0.97
subtract::F64ArithmTest::(1280x720, 64FC1)     4.758  5.016     0.95
subtract::F64ArithmTest::(1920x1080, 64FC1)    11.156 10.594    1.05
```

x-factor geomean **1.13x**. `compare` is a robust ~2.9x; most other ops land near parity because GCC already auto-vectorizes the simple scalar f64 loops at `-O3` on RVV (exactly the issue #30066 works around with `-fno-tree-vectorize`). Per-case values near parity swing with the known K1 bimodal timing behavior.

With `-fno-tree-vectorize` added to the build flags (the direction proposed in #30066 for RVV), the same A/B shows the kernel-level gains directly:

```
Min (ms)

                 Name of Test                    nt     nt       nt
                                                base   cand     cand
                                                min4   min4     min4
                                                                 vs
                                                                 nt
                                                                base
                                                                min4
                                                             (x-factor)
absdiff::F64ArithmTest::(127x61, 64FC1)        0.087  0.026     3.41
absdiff::F64ArithmTest::(640x480, 64FC1)       3.351  1.423     2.36
absdiff::F64ArithmTest::(1280x720, 64FC1)      10.189 4.595     2.22
absdiff::F64ArithmTest::(1920x1080, 64FC1)     22.476 10.094    2.23
add::F64ArithmTest::(127x61, 64FC1)            0.037  0.027     1.35
add::F64ArithmTest::(640x480, 64FC1)           1.538  1.446     1.06
add::F64ArithmTest::(1280x720, 64FC1)          4.644  4.403     1.05
add::F64ArithmTest::(1920x1080, 64FC1)         10.316 9.629     1.07
addWeighted::F64ArithmTest::(127x61, 64FC1)    0.076  0.027     2.81
addWeighted::F64ArithmTest::(640x480, 64FC1)   2.978  1.363     2.18
addWeighted::F64ArithmTest::(1280x720, 64FC1)  9.141  4.118     2.22
addWeighted::F64ArithmTest::(1920x1080, 64FC1) 19.856 9.075     2.19
compare::F64ArithmTest::(127x61, 64FC1)        0.103  0.031     3.31
compare::F64ArithmTest::(640x480, 64FC1)       3.993  1.225     3.26
compare::F64ArithmTest::(1280x720, 64FC1)      11.801 3.596     3.28
compare::F64ArithmTest::(1920x1080, 64FC1)     26.611 8.125     3.28
divide::F64ArithmTest::(127x61, 64FC1)         0.156  0.065     2.41
divide::F64ArithmTest::(640x480, 64FC1)        6.095  2.330     2.62
divide::F64ArithmTest::(1280x720, 64FC1)       18.191 6.880     2.64
divide::F64ArithmTest::(1920x1080, 64FC1)      40.743 15.331    2.66
max::F64ArithmTest::(127x61, 64FC1)            0.085  0.025     3.36
max::F64ArithmTest::(640x480, 64FC1)           3.272  1.428     2.29
max::F64ArithmTest::(1280x720, 64FC1)          10.009 4.581     2.18
max::F64ArithmTest::(1920x1080, 64FC1)         21.962 10.130    2.17
min::F64ArithmTest::(127x61, 64FC1)            0.086  0.025     3.40
min::F64ArithmTest::(640x480, 64FC1)           3.316  1.417     2.34
min::F64ArithmTest::(1280x720, 64FC1)          10.133 4.583     2.21
min::F64ArithmTest::(1920x1080, 64FC1)         22.334 10.160    2.20
multiply::F64ArithmTest::(127x61, 64FC1)       0.062  0.024     2.61
multiply::F64ArithmTest::(640x480, 64FC1)      2.430  1.362     1.78
multiply::F64ArithmTest::(1280x720, 64FC1)     7.427  4.344     1.71
multiply::F64ArithmTest::(1920x1080, 64FC1)    16.160 9.370     1.72
subtract::F64ArithmTest::(127x61, 64FC1)       0.038  0.025     1.48
subtract::F64ArithmTest::(640x480, 64FC1)      1.696  1.523     1.11
subtract::F64ArithmTest::(1280x720, 64FC1)     4.794  4.805     1.00
subtract::F64ArithmTest::(1920x1080, 64FC1)    12.058 10.820    1.11
```

x-factor geomean **2.09x** (36 cases, 1.00-3.41x): compare ~3.3x, divide ~2.6x, min/max ~2.2x, absdiff ~2.2x, addWeighted ~2.2x, multiply ~1.7-2.6x. `add`/`subtract` stay near parity in both regimes — they appear to be served by a different (already-vectorized) path before the kernel table is consulted.



### Pull Request Readiness Checklist

See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request

- [x] I agree to contribute to the project under Apache 2 License.
- [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch
- [x] There is a reference to the original bug report and related work
- [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
      Patch to opencv_extra has the same branch name.
- [ ] The feature is well documented and sample code can be built with the project CMake
2026-09-29 08:31:04 +03:00

1018 lines
25 KiB
C++

#include "perf_precomp.hpp"
#include <numeric>
#include "opencv2/core/softfloat.hpp"
namespace opencv_test
{
using namespace perf;
using BroadcastTest = perf::TestBaseWithParam<std::tuple<std::vector<int>, perf::MatType, std::vector<int>>>;
typedef Size_MatType BinaryOpTest;
PERF_TEST_P_(BroadcastTest, basic)
{
std::vector<int> shape_src = get<0>(GetParam());
int dt_type = get<1>(GetParam());
std::vector<int> shape_dst = get<2>(GetParam());
cv::Mat src(static_cast<int>(shape_src.size()), shape_src.data(), dt_type);
cv::Mat dst(static_cast<int>(shape_dst.size()), shape_dst.data(), dt_type);
cv::randu(src, -1.f, 1.f);
TEST_CYCLE() cv::broadcast(src, shape_dst, dst);
SANITY_CHECK_NOTHING();
}
INSTANTIATE_TEST_CASE_P(/*nothing*/ , BroadcastTest,
testing::Combine(
testing::Values(std::vector<int>{1, 100, 800},
std::vector<int>{10, 1, 800},
std::vector<int>{10, 100, 1}),
testing::Values(CV_32FC1),
testing::Values(std::vector<int>{10, 100, 800})
)
);
PERF_TEST_P_(BinaryOpTest, min)
{
Size sz = get<0>(GetParam());
int type = get<1>(GetParam());
cv::Mat a = Mat(sz, type);
cv::Mat b = Mat(sz, type);
cv::Mat c = Mat(sz, type);
declare.in(a, b, WARMUP_RNG).out(c);
TEST_CYCLE() cv::min(a, b, c);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(BinaryOpTest, minScalarDouble)
{
Size sz = get<0>(GetParam());
int type = get<1>(GetParam());
cv::Mat a = Mat(sz, type);
cv::Scalar b;
cv::Mat c = Mat(sz, type);
declare.in(a, b, WARMUP_RNG).out(c);
TEST_CYCLE() cv::min(a, b, c);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(BinaryOpTest, minScalarSameType)
{
Size sz = get<0>(GetParam());
int type = get<1>(GetParam());
cv::Mat a = Mat(sz, type);
cv::Scalar b;
cv::Mat c = Mat(sz, type);
declare.in(a, b, WARMUP_RNG).out(c);
if (CV_MAT_DEPTH(type) < CV_32S)
{
b = Scalar(1, 0, 3, 4); // don't pass non-integer values for 8U/8S/16U/16S processing
}
else if (CV_MAT_DEPTH(type) == CV_32S)
{
b = Scalar(1, 0, -3, 4); // don't pass non-integer values for 32S processing
}
TEST_CYCLE() cv::min(a, b, c);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(BinaryOpTest, max)
{
Size sz = get<0>(GetParam());
int type = get<1>(GetParam());
cv::Mat a = Mat(sz, type);
cv::Mat b = Mat(sz, type);
cv::Mat c = Mat(sz, type);
declare.in(a, b, WARMUP_RNG).out(c);
TEST_CYCLE() cv::max(a, b, c);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(BinaryOpTest, maxScalarDouble)
{
Size sz = get<0>(GetParam());
int type = get<1>(GetParam());
cv::Mat a = Mat(sz, type);
cv::Scalar b;
cv::Mat c = Mat(sz, type);
declare.in(a, b, WARMUP_RNG).out(c);
TEST_CYCLE() cv::max(a, b, c);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(BinaryOpTest, maxScalarSameType)
{
Size sz = get<0>(GetParam());
int type = get<1>(GetParam());
cv::Mat a = Mat(sz, type);
cv::Scalar b;
cv::Mat c = Mat(sz, type);
declare.in(a, b, WARMUP_RNG).out(c);
if (CV_MAT_DEPTH(type) < CV_32S)
{
b = Scalar(1, 0, 3, 4); // don't pass non-integer values for 8U/8S/16U/16S processing
}
else if (CV_MAT_DEPTH(type) == CV_32S)
{
b = Scalar(1, 0, -3, 4); // don't pass non-integer values for 32S processing
}
TEST_CYCLE() cv::max(a, b, c);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(BinaryOpTest, absdiff)
{
Size sz = get<0>(GetParam());
int type = get<1>(GetParam());
cv::Mat a = Mat(sz, type);
cv::Mat b = Mat(sz, type);
cv::Mat c = Mat(sz, type);
declare.in(a, b, WARMUP_RNG).out(c);
if (CV_MAT_DEPTH(type) == CV_32S)
{
//see ticket 1529: absdiff can be without saturation on 32S
a /= 2;
b /= 2;
}
TEST_CYCLE() cv::absdiff(a, b, c);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(BinaryOpTest, absdiffScalarDouble)
{
Size sz = get<0>(GetParam());
int type = get<1>(GetParam());
cv::Mat a = Mat(sz, type);
cv::Scalar b;
cv::Mat c = Mat(sz, type);
declare.in(a, b, WARMUP_RNG).out(c);
if (CV_MAT_DEPTH(type) == CV_32S)
{
//see ticket 1529: absdiff can be without saturation on 32S
a /= 2;
b /= 2;
}
TEST_CYCLE() cv::absdiff(a, b, c);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(BinaryOpTest, absdiffScalarSameType)
{
Size sz = get<0>(GetParam());
int type = get<1>(GetParam());
cv::Mat a = Mat(sz, type);
cv::Scalar b;
cv::Mat c = Mat(sz, type);
declare.in(a, b, WARMUP_RNG).out(c);
if (CV_MAT_DEPTH(type) < CV_32S)
{
b = Scalar(1, 0, 3, 4); // don't pass non-integer values for 8U/8S/16U/16S processing
}
else if (CV_MAT_DEPTH(type) == CV_32S)
{
//see ticket 1529: absdiff can be without saturation on 32S
a /= 2;
b = Scalar(1, 0, -3, 4); // don't pass non-integer values for 32S processing
}
TEST_CYCLE() cv::absdiff(a, b, c);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(BinaryOpTest, add)
{
Size sz = get<0>(GetParam());
int type = get<1>(GetParam());
cv::Mat a = Mat(sz, type);
cv::Mat b = Mat(sz, type);
cv::Mat c = Mat(sz, type);
declare.in(a, b, WARMUP_RNG).out(c);
declare.time(50);
if (CV_MAT_DEPTH(type) == CV_32S)
{
//see ticket 1529: add can be without saturation on 32S
a /= 2;
b /= 2;
}
TEST_CYCLE() cv::add(a, b, c);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(BinaryOpTest, addScalarDouble)
{
Size sz = get<0>(GetParam());
int type = get<1>(GetParam());
cv::Mat a = Mat(sz, type);
cv::Scalar b;
cv::Mat c = Mat(sz, type);
declare.in(a, b, WARMUP_RNG).out(c);
if (CV_MAT_DEPTH(type) == CV_32S)
{
//see ticket 1529: add can be without saturation on 32S
a /= 2;
b /= 2;
}
TEST_CYCLE() cv::add(a, b, c);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(BinaryOpTest, addScalarSameType)
{
Size sz = get<0>(GetParam());
int type = get<1>(GetParam());
cv::Mat a = Mat(sz, type);
cv::Scalar b;
cv::Mat c = Mat(sz, type);
declare.in(a, b, WARMUP_RNG).out(c);
if (CV_MAT_DEPTH(type) < CV_32S)
{
b = Scalar(1, 0, 3, 4); // don't pass non-integer values for 8U/8S/16U/16S processing
}
else if (CV_MAT_DEPTH(type) == CV_32S)
{
//see ticket 1529: add can be without saturation on 32S
a /= 2;
b = Scalar(1, 0, -3, 4); // don't pass non-integer values for 32S processing
}
TEST_CYCLE() cv::add(a, b, c, noArray(), type);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(BinaryOpTest, subtract)
{
Size sz = get<0>(GetParam());
int type = get<1>(GetParam());
cv::Mat a = Mat(sz, type);
cv::Mat b = Mat(sz, type);
cv::Mat c = Mat(sz, type);
declare.in(a, b, WARMUP_RNG).out(c);
if (CV_MAT_DEPTH(type) == CV_32S)
{
//see ticket 1529: subtract can be without saturation on 32S
a /= 2;
b /= 2;
}
TEST_CYCLE() cv::subtract(a, b, c);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(BinaryOpTest, subtractScalarDouble)
{
Size sz = get<0>(GetParam());
int type = get<1>(GetParam());
cv::Mat a = Mat(sz, type);
cv::Scalar b;
cv::Mat c = Mat(sz, type);
declare.in(a, b, WARMUP_RNG).out(c);
if (CV_MAT_DEPTH(type) == CV_32S)
{
//see ticket 1529: subtract can be without saturation on 32S
a /= 2;
b /= 2;
}
TEST_CYCLE() cv::subtract(a, b, c);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(BinaryOpTest, subtractScalarSameType)
{
Size sz = get<0>(GetParam());
int type = get<1>(GetParam());
cv::Mat a = Mat(sz, type);
cv::Scalar b;
cv::Mat c = Mat(sz, type);
declare.in(a, b, WARMUP_RNG).out(c);
if (CV_MAT_DEPTH(type) < CV_32S)
{
b = Scalar(1, 0, 3, 4); // don't pass non-integer values for 8U/8S/16U/16S processing
}
else if (CV_MAT_DEPTH(type) == CV_32S)
{
//see ticket 1529: subtract can be without saturation on 32S
a /= 2;
b = Scalar(1, 0, -3, 4); // don't pass non-integer values for 32S processing
}
TEST_CYCLE() cv::subtract(a, b, c, noArray(), type);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(BinaryOpTest, multiply)
{
Size sz = get<0>(GetParam());
int type = get<1>(GetParam());
cv::Mat a(sz, type), b(sz, type), c(sz, type);
declare.in(a, b, WARMUP_RNG).out(c);
if (CV_MAT_DEPTH(type) == CV_32S)
{
//According to docs, saturation is not applied when result is 32bit integer
a /= (2 << 16);
b /= (2 << 16);
}
TEST_CYCLE() cv::multiply(a, b, c);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(BinaryOpTest, multiplyScale)
{
Size sz = get<0>(GetParam());
int type = get<1>(GetParam());
cv::Mat a(sz, type), b(sz, type), c(sz, type);
double scale = 0.5;
declare.in(a, b, WARMUP_RNG).out(c);
if (CV_MAT_DEPTH(type) == CV_32S)
{
//According to docs, saturation is not applied when result is 32bit integer
a /= (2 << 16);
b /= (2 << 16);
}
TEST_CYCLE() cv::multiply(a, b, c, scale);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(BinaryOpTest, divide)
{
Size sz = get<0>(GetParam());
int type = get<1>(GetParam());
cv::Mat a(sz, type), b(sz, type), c(sz, type);
double scale = 0.5;
declare.in(a, b, WARMUP_RNG).out(c);
TEST_CYCLE() cv::divide(a, b, c, scale);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(BinaryOpTest, reciprocal)
{
Size sz = get<0>(GetParam());
int type = get<1>(GetParam());
cv::Mat b(sz, type), c(sz, type);
double scale = 0.5;
declare.in(b, WARMUP_RNG).out(c);
TEST_CYCLE() cv::divide(scale, b, c);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(BinaryOpTest, transpose2d)
{
Size sz = get<0>(GetParam());
int type = get<1>(GetParam());
Size tsz = Size(sz.height, sz.width);
cv::Mat a(sz, type), b(tsz, type);;
declare.in(a, WARMUP_RNG).out(b);
TEST_CYCLE() cv::transpose(a, b);
SANITY_CHECK_NOTHING();
}
static cv::Mat makeTransposeNDOutput(const cv::Mat& src, const std::vector<int>& order)
{
std::vector<int> new_sz(order.size());
for (size_t i = 0; i < order.size(); ++i)
new_sz[i] = src.size[order[i]];
return Mat(static_cast<int>(new_sz.size()), new_sz.data(), src.type());
}
static cv::Mat makeTransposeNDInput3D(Size sz, int type)
{
const int channels = CV_MAT_CN(type);
int dims[] = { sz.height, sz.width, channels };
return Mat(3, dims, CV_MAKETYPE(CV_MAT_DEPTH(type), 1));
}
PERF_TEST_P_(BinaryOpTest, transposeND_identity_order)
{
Size sz = get<0>(GetParam());
int type = get<1>(GetParam());
cv::Mat a = Mat(sz, type).reshape(1);
std::vector<int> order(a.dims);
std::iota(order.begin(), order.end(), 0);
cv::Mat b = makeTransposeNDOutput(a, order);
declare.in(a, WARMUP_RNG).out(b);
TEST_CYCLE() cv::transposeND(a, order, b);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(BinaryOpTest, transposeND_2d_swap_order)
{
Size sz = get<0>(GetParam());
int type = get<1>(GetParam());
cv::Mat a = Mat(sz, type).reshape(1);
std::vector<int> order{1, 0};
cv::Mat b = makeTransposeNDOutput(a, order);
declare.in(a, WARMUP_RNG).out(b);
TEST_CYCLE() cv::transposeND(a, order, b);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(BinaryOpTest, transposeND_generic_keep_tail_order)
{
Size sz = get<0>(GetParam());
int type = get<1>(GetParam());
cv::Mat a = makeTransposeNDInput3D(sz, type);
std::vector<int> order{1, 0, 2};
cv::Mat b = makeTransposeNDOutput(a, order);
randu(a, 0, 255);
//BUG: //BUG: in/out calls do not support arrays with more than 2 dimentions
//declare.in(a).out(b);
TEST_CYCLE() cv::transposeND(a, order, b);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(BinaryOpTest, transposeND_generic_move_tail_order)
{
Size sz = get<0>(GetParam());
int type = get<1>(GetParam());
cv::Mat a = makeTransposeNDInput3D(sz, type);
std::vector<int> order{2, 0, 1};
cv::Mat b = makeTransposeNDOutput(a, order);
randu(a, 0, 255);
//BUG: in/out calls do not support arrays with more than 2 dimentions
//declare.in(a).out(b);
TEST_CYCLE() cv::transposeND(a, order, b);
SANITY_CHECK_NOTHING();
}
INSTANTIATE_TEST_CASE_P(/*nothing*/ , BinaryOpTest,
testing::Combine(
testing::Values(szVGA, sz720p, sz1080p, Size(127, 61)), // 127x61 guards per-call overhead
testing::Values(CV_8UC1, CV_8UC3, CV_8UC4, CV_8SC1, CV_16SC1, CV_16SC2, CV_16SC3, CV_16SC4, CV_32SC1, CV_32FC1, CV_64FC1)
)
);
///////////// Mixed type arithmetics ////////
typedef perf::TestBaseWithParam<std::tuple<cv::Size, std::tuple<perf::MatType, perf::MatType>>> ArithmMixedTest;
PERF_TEST_P_(ArithmMixedTest, add)
{
auto p = GetParam();
Size sz = get<0>(p);
int srcType = get<0>(get<1>(p));
int dstType = get<1>(get<1>(p));
cv::Mat a = Mat(sz, srcType);
cv::Mat b = Mat(sz, srcType);
cv::Mat c = Mat(sz, dstType);
declare.in(a, b, WARMUP_RNG).out(c);
declare.time(50);
if (CV_MAT_DEPTH(dstType) == CV_32S)
{
//see ticket 1529: add can be without saturation on 32S
a /= 2;
b /= 2;
}
TEST_CYCLE() cv::add(a, b, c, /* mask */ noArray(), dstType);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(ArithmMixedTest, addScalarDouble)
{
auto p = GetParam();
Size sz = get<0>(p);
int srcType = get<0>(get<1>(p));
int dstType = get<1>(get<1>(p));
cv::Mat a = Mat(sz, srcType);
cv::Scalar b;
cv::Mat c = Mat(sz, dstType);
declare.in(a, b, WARMUP_RNG).out(c);
if (CV_MAT_DEPTH(dstType) == CV_32S)
{
//see ticket 1529: add can be without saturation on 32S
a /= 2;
b /= 2;
}
TEST_CYCLE() cv::add(a, b, c, /* mask */ noArray(), dstType);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(ArithmMixedTest, addScalarSameType)
{
auto p = GetParam();
Size sz = get<0>(p);
int srcType = get<0>(get<1>(p));
int dstType = get<1>(get<1>(p));
cv::Mat a = Mat(sz, srcType);
cv::Scalar b;
cv::Mat c = Mat(sz, dstType);
declare.in(a, b, WARMUP_RNG).out(c);
if (CV_MAT_DEPTH(dstType) < CV_32S)
{
b = Scalar(1, 0, 3, 4); // don't pass non-integer values for 8U/8S/16U/16S processing
}
else if (CV_MAT_DEPTH(dstType) == CV_32S)
{
//see ticket 1529: add can be without saturation on 32S
a /= 2;
b = Scalar(1, 0, -3, 4); // don't pass non-integer values for 32S processing
}
TEST_CYCLE() cv::add(a, b, c, /* mask */ noArray(), dstType);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(ArithmMixedTest, subtract)
{
auto p = GetParam();
Size sz = get<0>(p);
int srcType = get<0>(get<1>(p));
int dstType = get<1>(get<1>(p));
cv::Mat a = Mat(sz, srcType);
cv::Mat b = Mat(sz, srcType);
cv::Mat c = Mat(sz, dstType);
declare.in(a, b, WARMUP_RNG).out(c);
if (CV_MAT_DEPTH(dstType) == CV_32S)
{
//see ticket 1529: subtract can be without saturation on 32S
a /= 2;
b /= 2;
}
TEST_CYCLE() cv::subtract(a, b, c, /* mask */ noArray(), dstType);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(ArithmMixedTest, subtractScalarDouble)
{
auto p = GetParam();
Size sz = get<0>(p);
int srcType = get<0>(get<1>(p));
int dstType = get<1>(get<1>(p));
cv::Mat a = Mat(sz, srcType);
cv::Scalar b;
cv::Mat c = Mat(sz, dstType);
declare.in(a, b, WARMUP_RNG).out(c);
if (CV_MAT_DEPTH(dstType) == CV_32S)
{
//see ticket 1529: subtract can be without saturation on 32S
a /= 2;
b /= 2;
}
TEST_CYCLE() cv::subtract(a, b, c, /* mask */ noArray(), dstType);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(ArithmMixedTest, subtractScalarSameType)
{
auto p = GetParam();
Size sz = get<0>(p);
int srcType = get<0>(get<1>(p));
int dstType = get<1>(get<1>(p));
cv::Mat a = Mat(sz, srcType);
cv::Scalar b;
cv::Mat c = Mat(sz, dstType);
declare.in(a, b, WARMUP_RNG).out(c);
if (CV_MAT_DEPTH(dstType) < CV_32S)
{
b = Scalar(1, 0, 3, 4); // don't pass non-integer values for 8U/8S/16U/16S processing
}
else if (CV_MAT_DEPTH(dstType) == CV_32S)
{
//see ticket 1529: subtract can be without saturation on 32S
a /= 2;
b = Scalar(1, 0, -3, 4); // don't pass non-integer values for 32S processing
}
TEST_CYCLE() cv::subtract(a, b, c, /* mask */ noArray(), dstType);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(ArithmMixedTest, multiply)
{
auto p = GetParam();
Size sz = get<0>(p);
int srcType = get<0>(get<1>(p));
int dstType = get<1>(get<1>(p));
cv::Mat a(sz, srcType), b(sz, srcType), c(sz, dstType);
declare.in(a, b, WARMUP_RNG).out(c);
if (CV_MAT_DEPTH(dstType) == CV_32S)
{
//According to docs, saturation is not applied when result is 32bit integer
a /= (2 << 16);
b /= (2 << 16);
}
TEST_CYCLE() cv::multiply(a, b, c, /* scale */ 1.0, dstType);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(ArithmMixedTest, multiplyScale)
{
auto p = GetParam();
Size sz = get<0>(p);
int srcType = get<0>(get<1>(p));
int dstType = get<1>(get<1>(p));
cv::Mat a(sz, srcType), b(sz, srcType), c(sz, dstType);
double scale = 0.5;
declare.in(a, b, WARMUP_RNG).out(c);
if (CV_MAT_DEPTH(dstType) == CV_32S)
{
//According to docs, saturation is not applied when result is 32bit integer
a /= (2 << 16);
b /= (2 << 16);
}
TEST_CYCLE() cv::multiply(a, b, c, scale, dstType);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(ArithmMixedTest, divide)
{
auto p = GetParam();
Size sz = get<0>(p);
int srcType = get<0>(get<1>(p));
int dstType = get<1>(get<1>(p));
cv::Mat a(sz, srcType), b(sz, srcType), c(sz, dstType);
double scale = 0.5;
declare.in(a, b, WARMUP_RNG).out(c);
TEST_CYCLE() cv::divide(a, b, c, scale, dstType);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P_(ArithmMixedTest, reciprocal)
{
auto p = GetParam();
Size sz = get<0>(p);
int srcType = get<0>(get<1>(p));
int dstType = get<1>(get<1>(p));
cv::Mat b(sz, srcType), c(sz, dstType);
double scale = 0.5;
declare.in(b, WARMUP_RNG).out(c);
TEST_CYCLE() cv::divide(scale, b, c, dstType);
SANITY_CHECK_NOTHING();
}
INSTANTIATE_TEST_CASE_P(/*nothing*/ , ArithmMixedTest,
testing::Combine(
testing::Values(szVGA, sz720p, sz1080p),
testing::Values(std::tuple<perf::MatType, perf::MatType>{CV_8U, CV_16U},
std::tuple<perf::MatType, perf::MatType>{CV_8S, CV_16S},
std::tuple<perf::MatType, perf::MatType>{CV_8U, CV_32F},
std::tuple<perf::MatType, perf::MatType>{CV_8S, CV_32F}
)
)
);
typedef perf::TestBaseWithParam<std::tuple<cv::Size, int, bool>> SqrtFixture;
PERF_TEST_P_(SqrtFixture, Sqrt) {
Size sz = get<0>(GetParam());
int type = get<1>(GetParam());
bool inverse = get<2>(GetParam());
Mat src(sz, type), dst(sz, type);
randu(src, FLT_EPSILON, 1000);
declare.in(src).out(dst);
TEST_CYCLE() cv::pow(src, inverse ? -0.5 : 0.5, dst);
SANITY_CHECK_NOTHING();
}
INSTANTIATE_TEST_CASE_P(/*nothing*/ , SqrtFixture,
testing::Combine(
testing::Values(TYPICAL_MAT_SIZES),
testing::Values(CV_32FC1, CV_64FC1),
testing::Bool()
)
);
///////////// Rotate ////////////////////////
typedef perf::TestBaseWithParam<std::tuple<cv::Size, int, perf::MatType>> RotateTest;
PERF_TEST_P_(RotateTest, rotate)
{
Size sz = get<0>(GetParam());
int rotatecode = get<1>(GetParam());
int type = get<2>(GetParam());
cv::Mat a(sz, type), b(sz, type);
declare.in(a, WARMUP_RNG).out(b);
TEST_CYCLE() cv::rotate(a, b, rotatecode);
SANITY_CHECK_NOTHING();
}
INSTANTIATE_TEST_CASE_P(/*nothing*/ , RotateTest,
testing::Combine(
testing::Values(szVGA, sz720p, sz1080p),
testing::Values(ROTATE_180, ROTATE_90_CLOCKWISE, ROTATE_90_COUNTERCLOCKWISE),
testing::Values(CV_8UC1, CV_8UC2, CV_8UC3, CV_8UC4, CV_8SC1, CV_16SC1, CV_16SC2, CV_16SC3, CV_16SC4, CV_32SC1, CV_32FC1)
)
);
///////////// PatchNaNs ////////////////////////
template<typename _Tp>
_Tp randomNan(RNG& rng);
template<>
float randomNan(RNG& rng)
{
uint32_t r = rng.next();
Cv32suf v;
v.u = r;
// exp & set a bit to avoid zero mantissa
v.u = v.u | 0x7f800001;
return v.f;
}
template<>
double randomNan(RNG& rng)
{
uint32_t r0 = rng.next();
uint32_t r1 = rng.next();
Cv64suf v;
v.u = (uint64_t(r0) << 32) | uint64_t(r1);
// exp &set a bit to avoid zero mantissa
v.u = v.u | 0x7ff0000000000001;
return v.f;
}
typedef Size_MatType PatchNaNsFixture;
PERF_TEST_P_(PatchNaNsFixture, PatchNaNs)
{
const Size_MatType_t params = GetParam();
Size srcSize = get<0>(params);
const int type = get<1>(params), cn = CV_MAT_CN(type), depth = CV_MAT_DEPTH(type);
Mat src(srcSize, type);
declare.in(src, WARMUP_RNG).out(src);
// generating NaNs
{
srcSize.width *= cn;
RNG& rng = theRNG();
for (int y = 0; y < srcSize.height; ++y)
{
float *const ptrf = src.ptr<float>(y);
double *const ptrd = src.ptr<double>(y);
for (int x = 0; x < srcSize.width; ++x)
{
if (depth == CV_32F)
{
ptrf[x] = (x + y) % 2 == 0 ? randomNan<float >(rng) : ptrf[x];
}
else if (depth == CV_64F)
{
ptrd[x] = (x + y) % 2 == 0 ? randomNan<double>(rng) : ptrd[x];
}
}
}
}
TEST_CYCLE() cv::patchNaNs(src, 17.7);
SANITY_CHECK(src);
}
INSTANTIATE_TEST_CASE_P(/*nothing*/ , PatchNaNsFixture,
testing::Combine(
testing::Values(szVGA, sz720p, sz1080p, sz2160p),
testing::Values(CV_32FC1, CV_32FC2, CV_32FC3, CV_32FC4, CV_64FC1, CV_64FC2, CV_64FC3, CV_64FC4)
)
);
////////////// finiteMask ////////////////////////
typedef Size_MatType FiniteMaskFixture;
PERF_TEST_P_(FiniteMaskFixture, FiniteMask)
{
const Size_MatType_t params = GetParam();
Size srcSize = get<0>(params);
const int type = get<1>(params), cn = CV_MAT_CN(type), depth = CV_MAT_DEPTH(type);
Mat src(srcSize, type);
Mat mask(srcSize, CV_8UC1);
declare.in(src, WARMUP_RNG).out(mask);
// generating NaNs
{
srcSize.width *= cn;
const softfloat fpinf = softfloat ::inf();
const softfloat fninf = softfloat ::inf().setSign(true);
const softdouble dpinf = softdouble::inf();
const softdouble dninf = softdouble::inf().setSign(true);
RNG& rng = theRNG();
for (int y = 0; y < srcSize.height; ++y)
{
float *const ptrf = src.ptr<float>(y);
double *const ptrd = src.ptr<double>(y);
for (int x = 0; x < srcSize.width; ++x)
{
int rem = (x + y) % 10;
if (depth == CV_32F)
{
ptrf[x] = rem < 4 ? randomNan<float >(rng) :
rem == 5 ? (float )((x + y)%2 ? fpinf : fninf) : ptrf[x];
}
else if (depth == CV_64F)
{
ptrd[x] = rem < 4 ? randomNan<double>(rng) :
rem == 5 ? (double)((x + y)%2 ? dpinf : dninf) : ptrd[x];
}
}
}
}
TEST_CYCLE() cv::finiteMask(src, mask);
SANITY_CHECK(mask);
}
INSTANTIATE_TEST_CASE_P(/*nothing*/ , FiniteMaskFixture,
testing::Combine(
testing::Values(szVGA, sz720p, sz1080p, sz2160p),
testing::Values(CV_32FC1, CV_32FC2, CV_32FC3, CV_32FC4, CV_64FC1, CV_64FC2, CV_64FC3, CV_64FC4)
)
);
//////////////EXP////////////
typedef Size_MatType ExpFixture;
PERF_TEST_P(ExpFixture, Exp,
testing::Combine(testing::Values(TYPICAL_MAT_SIZES), testing::Values(CV_32F, CV_64F)))
{
cv::Size size = std::get<0>(GetParam());
int type = std::get<1>(GetParam());
cv::Mat src(size, type);
cv::Mat dst(size, type);
declare.in(src).out(dst);
cv::randu(src, -5.0, 5.0);
TEST_CYCLE()
{
cv::exp(src, dst);
}
SANITY_CHECK_NOTHING();
}
//////////////LOG////////////
typedef Size_MatType LogFixture;
PERF_TEST_P(LogFixture, Log,
testing::Combine(testing::Values(TYPICAL_MAT_SIZES), testing::Values(CV_32F, CV_64F)))
{
cv::Size size = std::get<0>(GetParam());
int type = std::get<1>(GetParam());
cv::Mat src(size, type);
cv::Mat dst(size, type);
declare.in(src).out(dst);
cv::randu(src, 1e-5, 1e5);
TEST_CYCLE()
{
cv::log(src, dst);
}
SANITY_CHECK_NOTHING();
}
} // namespace