Given an Image I
with dimensions as {rows, columns}
, for every pixel in the image, the following needs to be computed -
calc1 = image[i][j - 1] - image[i][j];
calc2 = image[i-1][j] - image[i][j];
calc3 = image[i][j-1] - 2 * image[i][j] + image[i][j + 1]
calc4 = image[i-1][j] - 2 * image[i][j] + image[i+1][j];
calc5 = image[i-1][j-1] - image[i-1][j] - image[i][j - 1] + image[i][j];
My current implementation is as follows -
inline void _myFunc(float* img_ptr,
float* dx_ptr, float* dy_ptr, float* dxx_ptr, float* dyy_ptr, float* dxy_ptr,
size_t rows, size_t cols){
__m256 negTwos = _mm256_set1_ps(-2);
for (size_t i = 1; i <rows - 1; i++)
{
#pragma unroll
for (size_t j = 1; j <cols - 1; j += 8)
{
auto idx = i * cols + j;
auto idx_before = (i - 1) * cols + j;
auto idx_after = (i + 1) * cols + j;
_mm_prefetch(img_ptr + idx - 1, _MM_HINT_T0);
_mm_prefetch(img_ptr + idx_before - 1, _MM_HINT_T0);
_mm_prefetch(img_ptr + idx_after - 1, _MM_HINT_T0);
__m256 __ij = _mm256_loadu_ps(img_ptr + idx);
__m256 __ijn1 = _mm256_loadu_ps(img_ptr + idx - 1);
__m256 __ijp1 = _mm256_loadu_ps(img_ptr + idx + 1);
__m256 __in1jn1 = _mm256_loadu_ps(img_ptr + idx_before - 1);
__m256 __in1j = _mm256_loadu_ps(img_ptr + idx_before);
__m256 __ip1j = _mm256_loadu_ps(img_ptr + idx_after);
// Read takes a total of 9 cycles
__m256 _dx = _mm256_sub_ps(__ijn1, __ij);
__m256 _dy = _mm256_sub_ps(__in1j, __ij);
//__m256 negTwo_times_ij = _mm256_mul_ps(negTwos, __ij);
__m256 _dxx = _mm256_add_ps(__ijn1, _mm256_fmadd_ps(negTwos, __ij, __ijp1)); // Will FMA be Better ?
__m256 _dyy = _mm256_add_ps(__in1j, _mm256_fmadd_ps(negTwos, __ij, __ip1j));
/*
__m256 _dxx = _mm256_add_ps(__ijn1, _mm256_add_ps(negTwo_times_ij, __ijp1));
__m256 _dyy = _mm256_add_ps(__in1j, _mm256_add_ps(negTwo_times_ij, __ip1j));
*/
__m256 _dxy = _mm256_sub_ps(_mm256_sub_ps(__in1jn1, __in1j), _dx);
_mm256_storeu_ps(dx_ptr + idx, _dx);
_mm256_storeu_ps(dy_ptr + idx, _dy);
_mm256_storeu_ps(dxx_ptr + idx, _dxx);
_mm256_storeu_ps(dyy_ptr + idx, _dyy);
_mm256_storeu_ps(dxy_ptr + idx, _dxy);
}
}
}
Currently, for a 9024 x 12032
image size, I obtain an average runtime of 220 ms
and GFlops of 4.1, on a single thread (compiled with -O3 -mavx2 -march=native
).
However, I am not satisfied with the current performance, and I suppose it can be better, specially by better computation and memory access.
Using a profiling tool,, I got to know the parts which take the maximum time(Among other things) -
__m256 _dxy = _mm256_sub_ps(_mm256_sub_ps(__in1jn1, __in1j), _dx); :: 68.025ms
__m256 __ij = _mm256_loadu_ps(img_ptr + idx); :: 51.98 ms
All the Store Functions :: A cumulative of 42.065ms
__m256 _dxx = _mm256_add_ps(__ijn1, _mm256_add_ps(negTwo_times_ij, __ijp1)); :: 12.003ms
__m256 _dx = _mm256_sub_ps(__ijn1, __ij); :: 7ms
A couple of observation (or rather questions)..
Consider the lines
__m256 _dxy = _mm256_sub_ps(_mm256_sub_ps(__in1jn1, __in1j), _dx);
and__m256 _dxx = _mm256_add_ps(__ijn1, _mm256_add_ps(negTwo_times_ij, __ijp1));
. Even though both essentially are the same in terms of cumulative latency,vaddps
andvsubps
both have a latency 4 each, but there is a stark difference in the time spent in these lines, that too almost 5 times more than the other. Why so ?All the store operations take ~ equal amount of time, the initial load operation
__m256 __ij = _mm256_loadu_ps(img_ptr + idx);
takes a majority of the time and other are relatively way faster. Is this due to the fact that the data would already be in the cache. If so, should not this be circumvented by_mm_prefetch
and initial load should be fast as well. Is there any way load / store operations could be made faster.
I realise this kernel is both computationally as well as bandwidth bound(more computationally I suppose).
How could I make this faster.
I am running on Intel i7 9750h and the average frequency of CPU during the run is 4.1Ghz. I have used Intel Advisor tool to profile the code and obtain the runtimes.
TIA