mirror of
https://github.com/opencv/opencv.git
synced 2026-09-25 04:09:57 +03:00
core: fold a*alpha + b*beta + gamma into the fused OP_ADDW kernel - #29953
Part of #29443 — "fuse `a*alpha + b*beta + gamma`, where alpha, beta and gamma are scalars, into
some new `OP_ADDW`". Builds on #29937, which is required for correctness (see below).
### Problem
`OP_ADDW` already computes `a*alpha + b*beta + gamma` as a single kernel over two `v_fma`, and
`emitBinary()` already knows how to emit it — but the string front-end never recognized the
pattern, so `cv::texpr()` always took the written-out path: two multiplies, two adds, three temp
buffers and four passes over the data.
```
{0}*2.0 + {1}*3.0 + 1.0 at CV_32F
insns=4 temps=3 buffers=3 insns=1 temps=0 buffers=0
0: mul(1, 4) -> 5 ==> 0: addWeighted(1, 2) -> 11
1: mul(2, 7) -> 8 params=[2, 3, 1]
2: add(5, 8) -> 9
3: add(9, 11) -> 13
```
### Fix
A peephole in `emitBinary()`: `a*alpha + b*beta` folds into one `OP_ADDW`, and a trailing scalar
folds into that instruction's gamma rather than costing another pass.
`emitBinary()` may wrap a multiply in casts — an integer array times a fractional scalar computes
in the float domain and lands back in the array's own type — so the matcher accepts the optional
widening and narrowing casts around it. That is what makes the common 8-bit blend fuse; without
it `{0}*0.7 + {1}*0.3` at `CV_8U` stays at eight instructions and seven temps.
Shapes that are not an addWeighted keep their own meaning: an `a*b` term has no scalar factor, a
per-channel constant cannot ride the params block, and `CV_Bool` has no `OP_ADDW` form.
**Dependency on #29937.** This retires the instructions it folds, exactly as the
`abs(x - y) -> absdiff` peephole does. Without the `pinned` flag added in #29937 it would
reintroduce that bug for `u = {0}*2.0; v = {1}*3.0; u + v`, where the named terms are still live.
### Semantics on integer types
The fused kernel evaluates at its own work precision, so intermediate results no longer saturate
at each step. On integer inputs the result changes — it now agrees with `cv::addWeighted`, which
is what the expression means. This is the same trade the existing `abs(x - y)` peephole documents
in `emitUnary()`: the saturation artifacts of the literal expansion are never the desired result.
### Performance
1920x1080, best of 5 runs of 50 iterations, same build rebuilt both ways on 03ae9eac50:
| expression | depth | before | after |
| --- | --- | --- | --- |
| `a*0.7 + b*0.3` | 8U | 0.646 ms | 0.159 ms |
| `a*2 + b*3 + 1` | 8U | 0.261 ms | 0.150 ms |
| `a*2.5 + b*-1.5 + 7` | 32F | 0.360 ms | 0.214 ms |
| `a*2 + b*3 + 1` | 32F | 0.213 ms | 0.155 ms |
| `a*b + b*2` (control, not fused) | 32F | 0.393 ms | 0.358 ms |
The control row runs identical code in both builds and still moves by ~9%, so run-to-run noise on
this machine is around 10% — treat the 32F rows as indicative and the 8-bit rows as the real
result. Timings include `cv::texpr()` re-parsing and recompiling the expression on every call, so
the kernel-level gain is larger than the totals suggest.
### Tests
28 parameterised cases — 7 depths crossed with 4 weight sets, including a zero gamma and negative
weights — assert the fused result matches `cv::addWeighted`. Two further tests cover the variants
(no gamma, scalar written first, leading gamma) and the shapes the peephole must decline. 1801
tests in the arithmetic and TExpr suites pass.
Also adds the missing `OP_ADDW` case to `opName()`, which this change makes visible in every dump
of such a program.
### 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.
- [x] The feature is well documented and sample code can be built with the project CMake
68 lines
2.0 KiB
C++
68 lines
2.0 KiB
C++
#include "perf_precomp.hpp"
|
|
|
|
namespace opencv_test
|
|
{
|
|
using namespace perf;
|
|
|
|
#define TYPICAL_MAT_TYPES_ADWEIGHTED CV_8UC1, CV_8UC4, CV_8SC1, CV_16UC1, CV_16SC1, CV_32SC1
|
|
#define TYPICAL_MATS_ADWEIGHTED testing::Combine(testing::Values(szVGA, sz720p, sz1080p, Size(127, 61)), testing::Values(TYPICAL_MAT_TYPES_ADWEIGHTED))
|
|
|
|
PERF_TEST_P(Size_MatType, addWeighted, TYPICAL_MATS_ADWEIGHTED)
|
|
{
|
|
Size size = get<0>(GetParam());
|
|
int type = get<1>(GetParam());
|
|
int depth = CV_MAT_DEPTH(type);
|
|
Mat src1(size, type);
|
|
Mat src2(size, type);
|
|
double alpha = 3.75;
|
|
double beta = -0.125;
|
|
double gamma = 100.0;
|
|
|
|
Mat dst(size, type);
|
|
|
|
declare.in(src1, src2, dst, WARMUP_RNG).out(dst);
|
|
|
|
if (depth == CV_32S)
|
|
{
|
|
// there might be not enough precision for integers
|
|
src1 /= 2048;
|
|
src2 /= 2048;
|
|
}
|
|
|
|
TEST_CYCLE() cv::addWeighted( src1, alpha, src2, beta, gamma, dst, dst.type() );
|
|
|
|
// accuracy is covered by the accuracy tests; regression data does not exist for every
|
|
// size in the grid (127x61 guards per-call overhead only)
|
|
SANITY_CHECK_NOTHING();
|
|
}
|
|
|
|
|
|
// The same computation written as an expression. cv::texpr() folds a*alpha + b*beta + gamma into
|
|
// the single fused OP_ADDW kernel instead of emitting two multiplies, two adds and three temp
|
|
// buffers, so this shares the grid above and can be compared against it directly.
|
|
PERF_TEST_P(Size_MatType, texpr_addWeighted, TYPICAL_MATS_ADWEIGHTED)
|
|
{
|
|
Size size = get<0>(GetParam());
|
|
int type = get<1>(GetParam());
|
|
int depth = CV_MAT_DEPTH(type);
|
|
Mat src1(size, type);
|
|
Mat src2(size, type);
|
|
|
|
declare.in(src1, src2, WARMUP_RNG);
|
|
|
|
if (depth == CV_32S)
|
|
{
|
|
// there might be not enough precision for integers
|
|
src1 /= 2048;
|
|
src2 /= 2048;
|
|
}
|
|
|
|
std::vector<Mat> inputs{ src1, src2 }, outputs;
|
|
|
|
TEST_CYCLE() cv::texpr("{0} * 3.75 + {1} * -0.125 + 100.0", inputs, outputs);
|
|
|
|
SANITY_CHECK_NOTHING();
|
|
}
|
|
|
|
} // namespace
|