An implementation of a 1D convolution operation will often need to load a vectors of data that sequentially step through a buffer of data offset by one element each iteration.
For example, consider a buffer of input data X[0], X[1], ..., X[n-1], where n is greater than twice the kernel length. If the convolution length is three, and we can fit eight elements in each vector, we might first want a vector with X[0], X[1], ..., X[7], then the next with X[1], X[2], ..., X[8] and the last with X[2], X[3], ..., X[9].
Consider the case where the kernel length as well as the vector length is 8. We must load eight vectors, that might look sequentially like this:
{ 0 1 2 3 4 5 6 7 }
{ 1 2 3 4 5 6 7 8 }
{ 2 3 4 5 6 7 8 9 }
{ 3 4 5 6 7 8 9 10 }
{ 4 5 6 7 8 9 10 11 }
{ 5 6 7 8 9 10 11 12 }
{ 6 7 8 9 10 11 12 13 }
{ 7 8 9 10 11 12 13 14 }
By reducing this sequence vertically, we could produce a running mean or sum. I.e., the sum of these vectors will have the sum of the first 8 elements in it's first position.
Consider that the order of the elements in the column does not matter. Any permutation of the elements in each column will still produce the same result. For a convolution, this permutation can be accounted for by altering the order of the constants used in the kernel.
Is there a faster way to load these vectors that takes advantage of this? Consider as a baseline the simple sequence of unaligned loads:
// Any sort of sliding window function, i.e. running mean, running max, convolution, etc.
void sliding_window(const float* input, unsigned length)
{
for (unsigned i = 0; i < length - 7; i += 8) {
for (unsigned j = 0; i < 8; j++) {
__m256 v = _mm256_loadu_ps(input[i + j]);
// reduction operation on v (e.g. max or fmadd) goes here
}
}
// handle tail here
}