Cubic root function cbrt() in Visual Studio 2012 - c++

I am writing a program in Visual Studio 2012 Professional (Windows) in C/C++ which consists of calculating many powers using pow(). I ran the profiler to find out why it takes such a long time to run and I found that pow() is the bottleneck.
I have rewritten the powers such as
pow(x,1.5) to x*sqrt(x)
and
pow(x,1.75) to sqrt(x*x*x*sqrt(x))
which significantly improved the speed of the program.
A few powers are of the kind pow(x,1.0/3.0) so I looked for the cubic root function cbrt() to speed up things but it seems not available in Visual Studio which I can hardly imagine so therefore my question:
Where can I find the cbrt() function in Visual Studio 2012 Professional and if not, what are the alternatives except for pow(x,1.0/3.0)?
Kind regards,
Ernst Jan

This site explores several computational methods to compute cube root efficiently in C, and has some source code you can download.
(EDIT: A google search for "fast cube root" comes up with several more promising-looking hits.)
Cube roots are a topic of interest, because they're used in many common formulae and a fast cube root function isn't included with Microsoft Visual Studio.
In the absence of a special cube root function, a typical strategy is calculation via a power function (e.g., pow(x, 1.0/3.0)). This can be problematic in terms of speed and in terms of accuracy when negative numbers aren't handled properly.
His site has some benchmarks on the methods used. All of them are much faster than pow().
32-bit float tests
----------------------------------------
cbrt_5f 8.8 ms 5 mbp 6.223 abp
pow 144.5 ms 23 mbp 23.000 abp
halley x 1 31.8 ms 15 mbp 18.961 abp
halley x 2 59.0 ms 23 mbp 23.000 abp
newton x 1 23.4 ms 10 mbp 12.525 abp
newton x 2 48.9 ms 20 mbp 22.764 abp
newton x 3 72.0 ms 23 mbp 23.000 abp
newton x 4 89.6 ms 23 mbp 23.000 abp
See the site for downloadable source.

Implementation below is 4x faster than std::pow with relatively higher tolerance (0.000001) on an AVX-512 CPU. It is made of vertically auto-vectorizable loops for every basic operation like multiplication and division so that it computes 8,16,32 elements at once instead of horizontally vectorizing the Newton-Raphson loop.
#include <cmath>
/*
Newton-Raphson iterative solution
f_err(x) = x*x*x - N
f'_err(x) = 3*x*x
x = x - (x*x*x - N)/(3*x*x)
x = x - (x - N/(x*x))/3 <--- repeat until error < tolerance
but with vertical-parallelization
*/
template < typename Type, int Simd, int inverseTolerance>
inline
void cubeRootFast(Type *const __restrict__ data,
Type *const __restrict__ result) noexcept
{
// alignment 64 required for AVX512 vectorization
alignas(64)
Type xd[Simd];
alignas(64)
Type resultData[Simd];
alignas(64)
Type xSqr[Simd];
alignas(64)
Type nDivXsqr[Simd];
alignas(64)
Type diff[Simd];
// cube root checking mask
for (int i = 0; i < Simd; i++)
{
xd[i] = data[i] <= Type(0.000001);
}
// skips division by zero if input is zero or close to zero
for (int i = 0; i < Simd; i++)
{
resultData[i] = xd[i] ? Type(1.0) : data[i];
}
// Newton-Raphson Iterations in parallel
bool work = true;
while (work)
{
// compute x*x
for (int i = 0; i < Simd; i++)
{
xSqr[i] = resultData[i] *resultData[i];
}
// compute N/(x*x)
for (int i = 0; i < Simd; i++)
{
nDivXsqr[i] = data[i] / xSqr[i];
}
// compute x - N/(x*x)
for (int i = 0; i < Simd; i++)
{
nDivXsqr[i] = resultData[i] - nDivXsqr[i];
}
// compute (x-N/(x*x))/3
for (int i = 0; i < Simd; i++)
{
nDivXsqr[i] = nDivXsqr[i] / Type(3.0);
}
// compute x - (x-N/(x*x))/3
for (int i = 0; i < Simd; i++)
{
diff[i] = resultData[i] - nDivXsqr[i];
}
// compute error
for (int i = 0; i < Simd; i++)
{
diff[i] = resultData[i] - diff[i];
}
// compute absolute error
for (int i = 0; i < Simd; i++)
{
diff[i] = std::abs(diff[i]);
}
// compute condition to stop looping (error < tolerance)?
for (int i = 0; i < Simd; i++)
{
diff[i] = diff[i] > Type(1.0/inverseTolerance);
}
// all SIMD lanes have to have zero work left to end
Type check = 0;
for (int i = 0; i < Simd; i++)
{
check += diff[i];
}
work = (check > Type(0.0));
// compute the next x guess
for (int i = 0; i < Simd; i++)
{
resultData[i] = resultData[i] - nDivXsqr[i];
}
}
// if input was close to zero, output zero
// output result otherwise
for (int i = 0; i < Simd; i++)
{
result[i] = xd[i] ? Type(0.0) : resultData[i];
}
}
#include <iostream>
int main()
{
constexpr int n = 8192;
constexpr int simd = 16;
constexpr int inverseTolerance = 1000;
float data[n];
for (int i = 0; i < n; i++)
{
data[i] = i;
}
for (int i = 0; i < n; i += simd)
{
cubeRootFast<float, simd, inverseTolerance> (data + i, data + i);
}
for (int i = 0; i < 10; i++)
std::cout << data[i *i *i] << std::endl;
return 0;
}
It is tested only with GCC so it may require extra MSVC-pragmas on each loop to force auto-vectorization. If you have OpenMP, then you can also use #pragma omp simd safelen(Simd) to achieve same thing.
The performance only holds within [0,1] range. To use bigger values, you should use range reduction like this:
// example: max value is 1000
for(auto & input:inputs)
input = input/1000.0f // normalize
for(..)
cubeRootFast<float, simd, inverseTolerance> (input + i, input + i)
for(auto & input:inputs)
input = 10.0f*input // de-normalize (1000 = 10 x 10 x 10)
If you need only 0.005 error on low-range like [0,1000] with 16x speedup, you can try below implementation that uses polynomial approximation (Horner-Scheme is applied to compute with FMA instructions and no explicit auto-vectorization required since it doesn't include any branching/loop inside):
// optimized for [0,1] range: ~1 cycles on AVX512, 0.003 average error
// polynomial approximation with Horner Scheme for FMA optimization
template<typename T>
T cubeRootFast(T x)
{
T xd = x-T(1.0);
T result = T(-55913.0/4782969.0);
result *= xd;
result += T(21505.0/1594323.0);
result *= xd;
result += T(-935.0/59049.0);
result *= xd;
result += T(374.0/19683.0);
result *= xd;
result += T(-154.0/6561.0);
result *= xd;
result += T(22.0/729.0);
result *= xd;
result += T(-10.0/243.0);
result *= xd;
result += T(5.0/81.0);
result *= xd;
result += T(-1.0/9.0);
result *= xd;
result += T(1.0/3.0);
result *= xd;
result += T(1.0);
return result;
}
// range reduction + dereduction: ~ 1 cycles on AVX512
for(int i=0;i<8192;i++)
{
float inp = input[i];
// scaling + descaling for range [1,999]
float scaling = (inp>333.0f)?(1000.0f):(333.0f);
scaling = (inp>103.0f)?scaling:(103.0f);
scaling = (inp>29.0f)?scaling:(29.0f);
scaling = (inp>7.0f)?scaling:(7.0f);
scaling = (inp>3.0f)?scaling:(3.0f);
output[i] = powf(scaling,0.33333333333f)*cubeRootFast<float>(inp/scaling);
}

Related

Dot Product between 2 Mx3 Matrices in Eigen C++ ( Similar to np.dot(x.transpose, y) )

I`m trying to find the fastest way to compute a MxN matrix with dot products in c++ using Eigen.
3rd Update: This is part of a Maya 2018 plugin ( For which I'm using visual studio 2018 with the toolset v141 which is what they recommend apparently to work properly with their devkit )
Lets say we have 2 Matrices:
A(3,5812) and B(3,23686)
What I'm looking for in Numpy is achieved using:
result = np.dot(A.T, B) which creates a result matrix of 5812 x 23686
Np.Dot takes 394 ms
So my progress so far:
Loop in loop method:
for (int i = 0; i < (int)A.cols(); i++) {
for (int j = 0; j < (int)B.cols(); j++) {
result(i, j) = A.col(i).cwiseProduct(B.col(j)).sum();
}
}
Process completed in: 891 ms
and using Eigen broadcasting:
for (int i = 0; i < (int)A.cols(); i++) {
result.row(i) = A.col(i).replicate(1, B.cols()).cwiseProduct(B).colwise().sum();
}
Process completed in: 844 ms. #
result = A.transpose() * B
Process completed in: 690 ms. # ( Update 3 .. 170ms)
I used OpenMP to multithreaded the loops, but I removed that in the code above for readability.
There has to be a faster way to do this properly maybe without using loops at all? I`ve been searching for a couple of days now... So, in the end, I decided to post here...
Thanks!
Second Update:
Managed to reduce the time to 170ms using A.transpose() * B... with openblas, and using MatrixXf instead of Xd..since for this particular implementation that precision is fine... still working on it trying various optimizations...
Third Update:
So for the sake of making things more clear I will post the function I'm currently working on...
Python version:
def getCorrespondences(X, Y, Cx, Cy, Rx):
timer = time.time()
X_ = np.dot(Rx, X - Cx)
Y_ = Y - Cy
ab = np.dot(X_.T, Y_) # each cell is X_i dot Y_j
xx = np.sum(X_*X_, 0)
yy = np.sum(Y_*Y_, 0)
D = (xx[:, np.newaxis] + yy[np.newaxis, :]) - 2*ab
idx = np.argmin(D, 1)
elapsed_time = time.time() - timer
print (elapsed_time)
return idx
Python computes this in 1.377s
And my current c++ implementation
DWORD tcStart1 = GetTickCount();
X.transposeInPlace();
Y.transposeInPlace();
Cx.transposeInPlace();
Cy.transposeInPlace();
Rx.transposeInPlace();
int vtxCountX = (int)X.cols();
int vtxCountY = (int)Y.cols();
MatrixXf X_ = Rx * (X - Cx.col(0).replicate(1, vtxCountX));
MatrixXf Y_ = Y - Cy.col(0).replicate(1, vtxCountY);
MatrixXf ab = X_.transpose() * Y_;
MatrixXf xx = X_.cwiseProduct(X_).colwise().sum();
MatrixXf yy = Y_.cwiseProduct(Y_).colwise().sum();
MatrixXf D(vtxCountX, vtxCountY);
MatrixXf::Index index;
VectorXi idx(D.rows());
#ifdef _OPENMP
int a, b;
if (vtxCountX > vtxCountY) { b = threads / 4; a = threads - b; }
else { a = threads / 4; b = threads - a; }
#pragma omp parallel for num_threads(a)
#endif
for (int i = 0; i < vtxCountX; i++) {
#ifdef _OPENMP
#pragma omp parallel for num_threads(b)
#endif
for (int j = 0; j < vtxCountY; j++) {
D(i, j) = xx(i) + yy(j) - 2 * ab(i,j);
}
D.row(i).minCoeff(&index);
idx(i) = (int)index;
}
DWORD tcTotal1 = GetTickCount() - tcStart1;
//cout << idx.transpose() << endl;
cout << "Correspondence computed in: " << tcTotal1 << endl;
return idx;
This seems to the job in 906 ms with MatrixXf, and 1297ms with MatrixXd
I still need to figure out how I can squeeze more speed out of this since this function will end up getting fired hundreds of times.
I will try other things proposed in the comments... or if you guys have any other suggestions Im all ears :)... I ended up casting to float since the result is a bunch of indexes... so I dont need precision... the function basically computes distances and figures out some point correspondences, for Procrustes alignment.
I ended up using loops at a certain point instead of eigen replicate and cwise broadcasting since that seemed to be slower:/. Anyway I hope this gives more context. Cheers
Update 4...
int vtxCountX = (int)X.rows(); int vtxCountY = (int)Y.rows();
MatrixXf X_ = (X - Cx.row(0).replicate(vtxCountX, 1)) * Rx.transpose();
MatrixXf Y_ = Y - Cy.row(0).replicate(vtxCountY, 1);
MatrixXf ab = Y_* X_.transpose();
MatrixXf xx = X_.cwiseProduct(X_).rowwise().sum();
MatrixXf yy = Y_.cwiseProduct(Y_).rowwise().sum();
MatrixXf D(vtxCountX, vtxCountY);
MatrixXf::Index index;
VectorXi idx(D.rows());
#ifdef _OPENMP
int a, b;
if (vtxCountX > vtxCountY) { b = threads / 4; a = threads - b; }
else { a = threads / 4; b = threads - a; }
#pragma omp parallel for num_threads(a)
#endif
for (int i = 0; i < vtxCountX; i++) {
#ifdef _OPENMP
#pragma omp parallel for num_threads(4)
#endif
for (int j = 0; j < vtxCountY; j++) {
D(i, j) = xx(i) + yy(j) - 2 * ab(j,i);
}
D.row(i).minCoeff(&index);
idx(i) = (int)index;
}
This does the job in 469 ms... 17 faster than my original implementation... and ~3 times faster than numpy...

I would like to improve the performance of this code using AVX

I profiled my code and the most expensive part of the code is the loop included in the post. I want to improve the performance of this loop using AVX. I have tried manually unrolling the loop and, while that does improve performance, the improvements are not satisfactory.
int N = 100000000;
int8_t* data = new int8_t[N];
for(int i = 0; i< N; i++) { data[i] = 1 ;}
std::array<float, 10> f = {1,2,3,4,5,6,7,8,9,10};
std::vector<float> output(N, 0);
int k = 0;
for (int i = k; i < N; i = i + 2) {
for (int j = 0; j < 10; j++, k = j + 1) {
output[i] += f[j] * data[i - k];
output[i + 1] += f[j] * data[i - k + 1];
}
}
Could I have some guidance on how to approach this.
I would assume that data is a large input array of signed bytes, and f is a small array of floats of length 10, and output is the large output array of floats. Your code goes out of bounds for the first 10 iterations by i, so I will start i from 10 instead. Here is a clean version of the original code:
int s = 10;
for (int i = s; i < N; i += 2) {
for (int j = 0; j < 10; j++) {
output[i] += f[j] * data[i-j-1];
output[i+1] += f[j] * data[i-j];
}
}
As it turns out, processing two iterations by i does not change anything, so we simplify it further to:
for (int i = s; i < N; i++)
for (int j = 0; j < 10; j++)
output[i] += f[j] * data[i-j-1];
This version of code (along with declarations of input/output data) should have been present in the question itself, without others having to clean/simplify the mess.
Now it is obvious that this code applies one-dimensional convolution filter, which is a very common thing in signal processing. For instance, it can by computed in Python using numpy.convolve function. The kernel has very small length 10, so Fast Fourier Transform won't provide any benefits compared to bruteforce approach. Given that the problem is well-known, you can read a lot of articles on vectorizing small-kernel convolution. I will follow the article by hgomersall.
First, let's get rid of reverse indexing. Obviously, we can reverse the kernel before running the main algorithm. After that, we have to compute the so-called cross-correlation instead of convolution. In simple words, we move the kernel array along the input array, and compute the dot product between them for every possible offset.
std::reverse(f.data(), f.data() + 10);
for (int i = s; i < N; i++) {
int b = i-10;
float res = 0.0;
for (int j = 0; j < 10; j++)
res += f[j] * data[b+j];
output[i] = res;
}
In order to vectorize it, let's compute 8 consecutive dot products at once. Recall that we can pack eight 32-bit float numbers into one 256-bit AVX register. We will vectorize the outer loop by i, which means that:
The loop by i will be advanced by 8 every iteration.
Every value inside the outer loop turns into a 8-element pack, such that k-th element of the pack holds this value for (i+k)-th iteration of the outer loop from the scalar version.
Here is the resulting code:
//reverse the kernel
__m256 revKernel[10];
for (size_t i = 0; i < 10; i++)
revKernel[i] = _mm256_set1_ps(f[9-i]); //every component will have same value
//note: you have to compute the last 16 values separately!
for (size_t i = s; i + 16 <= N; i += 8) {
int b = i-10;
__m256 res = _mm256_setzero_ps();
for (size_t j = 0; j < 10; j++) {
//load: data[b+j], data[b+j+1], data[b+j+2], ..., data[b+j+15]
__m128i bytes = _mm_loadu_si128((__m128i*)&data[b+j]);
//convert first 8 bytes of loaded 16-byte pack into 8 floats
__m256 floats = _mm256_cvtepi32_ps(_mm256_cvtepi8_epi32(bytes));
//compute res = res + floats * revKernel[j] elementwise
res = _mm256_fmadd_ps(revKernel[j], floats, res);
}
//store 8 values packed in res into: output[i], output[i+1], ..., output[i+7]
_mm256_storeu_ps(&output[i], res);
}
For 100 millions of elements, this code takes about 120 ms on my machine, while the original scalar implementation took 850 ms. Beware: I have Ryzen 1600 CPU, so results on Intel CPUs may be somewhat different.
Now if you really want to unroll something, the inner loop by 10 kernel elements is the perfect place. Here is how it is done:
__m256 revKernel[10];
for (size_t i = 0; i < 10; i++)
revKernel[i] = _mm256_set1_ps(f[9-i]);
for (size_t i = s; i + 16 <= N; i += 8) {
size_t b = i-10;
__m256 res = _mm256_setzero_ps();
#define DOIT(j) {\
__m128i bytes = _mm_loadu_si128((__m128i*)&data[b+j]); \
__m256 floats = _mm256_cvtepi32_ps(_mm256_cvtepi8_epi32(bytes)); \
res = _mm256_fmadd_ps(revKernel[j], floats, res); \
}
DOIT(0);
DOIT(1);
DOIT(2);
DOIT(3);
DOIT(4);
DOIT(5);
DOIT(6);
DOIT(7);
DOIT(8);
DOIT(9);
_mm256_storeu_ps(&output[i], res);
}
It takes 110 ms on my machine (slightly better that the first vectorized version).
The simple copy of all elements (with conversion from bytes to floats) takes 40 ms for me, which means that this code is not memory-bound yet, and there is still some room for improvement left.

parallel for with omp stucks

I have problem with the following code:
int *chosen_pts = new int[k];
std::pair<float, int> *dist2 = new std::pair<float, int>[x.n];
// initialize dist2
for (int i = 0; i < x.n; ++i) {
dist2[i].first = std::numeric_limits<float>::max();
dist2[i].second = i;
}
// choose the first point randomly
int ndx = 1;
chosen_pts[ndx - 1] = rand() % x.n;
double begin, end;
double elapsed_secs;
while (ndx < k) {
float sum_distribution = 0.0;
// look for the point that is furthest from any center
begin = omp_get_wtime();
#pragma omp parallel for reduction(+:sum_distribution)
for (int i = 0; i < x.n; ++i) {
int example = dist2[i].second;
float d2 = 0.0, diff;
for (int j = 0; j < x.d; ++j) {
diff = x(example,j) - x(chosen_pts[ndx - 1],j);
d2 += diff * diff;
}
if (d2 < dist2[i].first) {
dist2[i].first = d2;
}
sum_distribution += dist2[i].first;
}
end = omp_get_wtime() - begin;
std::cout << "center assigning -- "
<< ndx << " of " << k << " = "
<< (float)ndx / k * 100
<< "% is done. Elasped time: "<< (float)end <<"\n";
/**/
bool unique = true;
do {
// choose a random interval according to the new distribution
float r = sum_distribution * (float)rand() / (float)RAND_MAX;
float sum_cdf = dist2[0].first;
int cdf_ndx = 0;
while (sum_cdf < r) {
sum_cdf += dist2[++cdf_ndx].first;
}
chosen_pts[ndx] = cdf_ndx;
for (int i = 0; i < ndx; ++i) {
unique = unique && (chosen_pts[ndx] != chosen_pts[i]);
}
} while (! unique);
++ndx;
}
As you can see i use omp to make parallel the for loop. It works fine and i can achive a significant speed up. However if i increase the value of x.n over 20000000 the function stops to work after 8-10 loops:
It doestn produces any output (std::cout)
Only one core works
No error, whatsoever
If i comment out the do while loop, it works again as expected. All cores are busy and there is an output after each iteration, and i can increase k.n over 100 millions just as i need it.
It's not OpenMP parallel for getting stuck, it's obviously in your serial do-while loop.
One particular issue that I see is that there is no array boundary checks in the inner while loop accessing dist2. In theory, out-of-boundary access should never happen; but in practice it may - see below why. So first of all I would rewrite the calculation of cdf_ndx to guarantee that the loop ends when all elements are inspected:
float sum_cdf = 0;
int cdf_ndx = 0;
while (sum_cdf < r && cdf_ndx < x.n ) {
sum_cdf += dist2[cdf_ndx].first;
++cdf_ndx;
}
Now, how it may happen that sum_cdf does not reach r? It is due to specifics of floating-point arithmetic and the fact that sum_distribution was computed in parallel, while sum_cdf is computed serially. The problem is that contribution of one element to the sum can be below the accuracy for floats; in other words, when you sum two float values that differ more than ~8 orders of magnitude, the smaller one does not affect the sum.
So, with 20M of floats after some point it might happen that the next value to add is so small comparing to the accumulated sum_cdf that adding this value does not change it! On the other hand, sum_distribution was essentially computed as several independent partial sums (one per thread) then combined together. Thus it is more accurate, and possibly bigger than sum_cdf can ever reach.
A solution can be to compute sum_cdf in portions, having two nested loops. For example:
float sum_cdf = 0;
int cdf_ndx = 0;
while (sum_cdf < r && cdf_ndx < x.n ) {
float block_sum = 0;
int block_end = min(cdf_ndx+10000, x.n); // 10000 is arbitrary selected block size
for (int i=cdf_ndx; i<block_end; ++i ) {
block_sum += dist2[i].first;
if( sum_cdf+block_sum >=r ) {
block_end = i; // adjust to correctly compute cdf_ndx
break;
}
}
sum_cdf += block_sum;
cdf_ndx = block_end;
}
And after the loop you need to check that cdf_ndx < x.n, otherwise repeat with a new random interval.

Red-Black Gauss Seidel and OpenMP

I was trying to prove a point with OpenMP compared to MPICH, and I cooked up the following example to demonstrate how easy it was to do some high performance in OpenMP.
The Gauss-Seidel iteration is split into two separate runs, such that in each sweep every operation can be performed in any order, and there should be no dependency between each task. So in theory each processor should never have to wait for another process to perform any kind of synchronization.
The problem I am encountering, is that I, independent of problem size, find there is only a weak speed-up of 2 processors and with more than 2 processors it might even be slower.
Many other linear paralleled routine I can obtain very good scaling, but this one is tricky.
My fear is that I am unable to "explain" to the compiler that operation that I perform on the array, is thread-safe, such that it is unable to be really effective.
See the example below.
Anyone has any clue on how to make this more effective with OpenMP?
void redBlackSmooth(std::vector<double> const & b,
std::vector<double> & x,
double h)
{
// Setup relevant constants.
double const invh2 = 1.0/(h*h);
double const h2 = (h*h);
int const N = static_cast<int>(x.size());
double sigma = 0;
// Setup some boundary conditions.
x[0] = 0.0;
x[N-1] = 0.0;
// Red sweep.
#pragma omp parallel for shared(b, x) private(sigma)
for (int i = 1; i < N-1; i+=2)
{
sigma = -invh2*(x[i-1] + x[i+1]);
x[i] = (h2/2.0)*(b[i] - sigma);
}
// Black sweep.
#pragma omp parallel for shared(b, x) private(sigma)
for (int i = 2; i < N-1; i+=2)
{
sigma = -invh2*(x[i-1] + x[i+1]);
x[i] = (h2/2.0)*(b[i] - sigma);
}
}
Addition:
I have now also tried with a raw pointer implementation and it has the same behavior as using STL container, so it can be ruled out that it is some pseudo-critical behavior comming from STL.
First of all, make sure that the x vector is aligned to cache boundaries. I did some test, and I get something like a 100% improvement with your code on my machine (core duo) if I force the alignment of memory:
double * x;
const size_t CACHE_LINE_SIZE = 256;
posix_memalign( reinterpret_cast<void**>(&x), CACHE_LINE_SIZE, sizeof(double) * N);
Second, you can try to assign more computation to each thread (in this way you can keep cache-lines separated), but I suspect that openmp already does something like this under the hood, so it may be worthless with large N.
In my case this implementation is much faster when x is not cache-aligned.
const int workGroupSize = CACHE_LINE_SIZE / sizeof(double);
assert(N % workGroupSize == 0); //Need to tweak the code a bit to let it work with any N
const int workgroups = N / workGroupSize;
int j, base , k, i;
#pragma omp parallel for shared(b, x) private(sigma, j, base, k, i)
for ( j = 0; j < workgroups; j++ ) {
base = j * workGroupSize;
for (int k = 0; k < workGroupSize; k+=2)
{
i = base + k + (redSweep ? 1 : 0);
if ( i == 0 || i+1 == N) continue;
sigma = -invh2* ( x[i-1] + x[i+1] );
x[i] = ( h2/2.0 ) * ( b[i] - sigma );
}
}
In conclusion, you definitely have a problem of cache-fighting, but given the way openmp works (sadly I am not familiar with it) it should be enough to work with properly allocated buffers.
I think the main problem is about type of array structure you are using. Lets try comparing results with vectors and arrays. (Arrays = c-arrays using new operator).
Vector and array sizes are N = 10000000. I force the smoothing function to repeat in order to maintain runtime > 0.1secs.
Vector Time: 0.121007 Repeat: 1 MLUPS: 82.6399
Array Time: 0.164009 Repeat: 2 MLUPS: 121.945
MLUPS = ((N-2)*repeat/runtime)/1000000 (Million Lattice Points Update per second)
MFLOPS are misleading when it comes to grid calculation. A few changes in the basic equation can lead to consider high performance for the same runtime.
The modified code:
double my_redBlackSmooth(double *b, double* x, double h, int N)
{
// Setup relevant constants.
double const invh2 = 1.0/(h*h);
double const h2 = (h*h);
double sigma = 0;
// Setup some boundary conditions.
x[0] = 0.0;
x[N-1] = 0.0;
double runtime(0.0), wcs, wce;
int repeat = 1;
timing(&wcs);
for(; runtime < 0.1; repeat*=2)
{
for(int r = 0; r < repeat; ++r)
{
// Red sweep.
#pragma omp parallel for shared(b, x) private(sigma)
for (int i = 1; i < N-1; i+=2)
{
sigma = -invh2*(x[i-1] + x[i+1]);
x[i] = (h2*0.5)*(b[i] - sigma);
}
// Black sweep.
#pragma omp parallel for shared(b, x) private(sigma)
for (int i = 2; i < N-1; i+=2)
{
sigma = -invh2*(x[i-1] + x[i+1]);
x[i] = (h2*0.5)*(b[i] - sigma);
}
// cout << "In Array: " << r << endl;
}
if(x[0] != 0) dummy(x[0]);
timing(&wce);
runtime = (wce-wcs);
}
// cout << "Before division: " << repeat << endl;
repeat /= 2;
cout << "Array Time:\t" << runtime << "\t" << "Repeat:\t" << repeat
<< "\tMLUPS:\t" << ((N-2)*repeat/runtime)/1000000.0 << endl;
return runtime;
}
I didn't change anything in the code except than array type. For better cache access and blocking you should look into data alignment (_mm_malloc).

Audio Processing C++ - FFT

I'm probably going to ask this incorrectly and make myself look very stupid but here goes:
I'm trying to do some audio manipulate and processing on a .wav file. Now, I am able to read all of the data (including the header) but need the data to be in frequency, and, in order to this I need to use an FFT.
I searched the internet high and low and found one, and the example was taken out of the "Numerical Recipes in C" book, however, I amended it to use vectors instead of arrays. Ok so here's the problem:
I have been given (as an example to use) a series of numbers and a sampling rate:
X = {50, 206, -100, -65, -50, -6, 100, -135}
Sampling Rate : 8000
Number of Samples: 8
And should therefore answer this:
0Hz A=0 D=1.57079633
1000Hz A=50 D=1.57079633
2000HZ A=100 D=0
3000HZ A=100 D=0
4000HZ A=0 D=3.14159265
The code that I re-wrote compiles, however, when trying to input these numbers into the equation (function) I get a Segmentation fault.. Is there something wrong with my code, or is the sampling rate too high? (The algorithm doesn't segment when using a much, much smaller sampling rate). Here is the code:
#include <iostream>
#include <math.h>
#include <vector>
using namespace std;
#define SWAP(a,b) tempr=(a);(a)=(b);(b)=tempr;
#define pi 3.14159
void ComplexFFT(vector<float> &realData, vector<float> &actualData, unsigned long sample_num, unsigned int sample_rate, int sign)
{
unsigned long n, mmax, m, j, istep, i;
double wtemp,wr,wpr,wpi,wi,theta,tempr,tempi;
// CHECK TO SEE IF VECTOR IS EMPTY;
actualData.resize(2*sample_rate, 0);
for(n=0; (n < sample_rate); n++)
{
if(n < sample_num)
{
actualData[2*n] = realData[n];
}else{
actualData[2*n] = 0;
actualData[2*n+1] = 0;
}
}
// Binary Inversion
n = sample_rate << 1;
j = 0;
for(i=0; (i< n /2); i+=2)
{
if(j > i)
{
SWAP(actualData[j], actualData[i]);
SWAP(actualData[j+1], actualData[i+1]);
if((j/2)<(n/4))
{
SWAP(actualData[(n-(i+2))], actualData[(n-(j+2))]);
SWAP(actualData[(n-(i+2))+1], actualData[(n-(j+2))+1]);
}
}
m = n >> 1;
while (m >= 2 && j >= m) {
j -= m;
m >>= 1;
}
j += m;
}
mmax=2;
while(n > mmax) {
istep = mmax << 1;
theta = sign * (2*pi/mmax);
wtemp = sin(0.5*theta);
wpr = -2.0*wtemp*wtemp;
wpi = sin(theta);
wr = 1.0;
wi = 0.0;
for(m=1; (m < mmax); m+=2) {
for(i=m; (i <= n); i += istep)
{
j = i*mmax;
tempr = wr*actualData[j-1]-wi*actualData[j];
tempi = wr*actualData[j]+wi*actualData[j-1];
actualData[j-1] = actualData[i-1] - tempr;
actualData[j] = actualData[i]-tempi;
actualData[i-1] += tempr;
actualData[i] += tempi;
}
wr = (wtemp=wr)*wpr-wi*wpi+wr;
wi = wi*wpr+wtemp*wpi+wi;
}
mmax = istep;
}
// determine if the fundamental frequency
int fundemental_frequency = 0;
for(i=2; (i <= sample_rate); i+=2)
{
if((pow(actualData[i], 2)+pow(actualData[i+1], 2)) > pow(actualData[fundemental_frequency], 2)+pow(actualData[fundemental_frequency+1], 2)) {
fundemental_frequency = i;
}
}
}
int main(int argc, char *argv[]) {
vector<float> numbers;
vector<float> realNumbers;
numbers.push_back(50);
numbers.push_back(206);
numbers.push_back(-100);
numbers.push_back(-65);
numbers.push_back(-50);
numbers.push_back(-6);
numbers.push_back(100);
numbers.push_back(-135);
ComplexFFT(numbers, realNumbers, 8, 8000, 0);
for(int i=0; (i < realNumbers.size()); i++)
{
cout << realNumbers[i] << "\n";
}
}
The other thing, (I know this sounds stupid) but I don't really know what is expected of the
"int sign" That is being passed through the ComplexFFT function, this is where I could be going wrong.
Does anyone have any suggestions or solutions to this problem?
Thank you :)
I think the problem lies in errors in how you translated the algorithm.
Did you mean to initialize j to 1 rather than 0?
for(i = 0; (i < n/2); i += 2) should probably be for (i = 1; i < n; i += 2).
Your SWAPs should probably be
SWAP(actualData[j - 1], actualData[i - 1]);
SWAP(actualData[j], actualData[i]);
What are the following SWAPs for? I don't think they're needed.
if((j/2)<(n/4))
{
SWAP(actualData[(n-(i+2))], actualData[(n-(j+2))]);
SWAP(actualData[(n-(i+2))+1], actualData[(n-(j+2))+1]);
}
The j >= m in while (m >= 2 && j >= m) should probably be j > m if you intended to do bit reversal.
In the code implementing the Danielson-Lanczos section, are you sure j = i*mmax; was not supposed to be an addition, i.e. j = i + mmax;?
Apart from that, there are a lot of things you can do to simplify your code.
Using your SWAP macro should be discouraged when you can just use std::swap... I was going to suggest std::swap_ranges, but then I realized you only need to swap the real parts, since your data is all reals (your time-series imaginary parts are all 0):
std::swap(actualData[j - 1], actualData[i - 1]);
You can simplify the entire thing using std::complex, too.
I reckon its down to the re-sizing of your vector.
One possibility: Maybe re-sizing will create temp objects on the stack before moving them back to heap i think.
The FFT in Numerical Recipes in C uses the Cooley-Tukey Algorithm, so in answer to your question at the end, the int sign being passed allows the same routine to be used to compute both the forward (sign=-1) and inverse (sign=1) FFT. This seems to be consistent with the way you are using sign when you define theta = sign * (2*pi/mmax).