I got some speedup by replacing pow(u,2)
with u * u
. Time for i == 0
went from 33 sec to 26 sec.
It can be noted that pdf(u)
is h
invariant [unlike cdf(h - u)
].
So, at each integral level pdf(u)
can be cached and only calculated once. This is akin to memoization in dynamic programming.
By doing this, I reduced the runtime to 12 sec. That is, it is 2.75x faster than the original
Note that on the first call to pdf(u)
, u
will be 0. It follows the sequence: 0, du, 2*du, ..., iter*du
where iter
is the iteration index of the loop.
This is true regardless of what level integral we're calculating. The pdf
sequence will always be the same.
So, we can keep track of the largest iter
index we've seen. If the current index is greater than the largest, we do: pdf_cache[iter] = pdf(u)
. Otherwise, we use pdf_cache[iter]
.
See the code below for full details.
As a possible alternative, the pdf_cache
array could be fully precalculated once before starting anything. Then, all usage of pdf(u)
could be replaced with pdf_cache[i]
.
This could be done in main
before its for
loop as pdf_cache
will be the same no matter what main
's values for i
or gammath
(aka h
) are.
Further, this precalculation could be split up amongst multiple cores if needed.
Also, if openmp
is used within integ
[as others have suggested], the full precalculation would probably be necessary to prevent interthread write contention.
Edit: I added this version as an update at the bottom
Here is the code. Please pardon the C-like stuff. This is also a prototype level.
#include <iostream>
#include <cmath>
#include <stdio.h>
#include <time.h>
double
tvgetf(void)
{
struct timespec ts;
double sec;
clock_gettime(CLOCK_REALTIME,&ts);
sec = ts.tv_nsec;
sec /= 1e9;
sec += ts.tv_sec;
return sec;
}
#define inline_always static inline __attribute__((__always_inline__))
// NOTE: the size of pdf_cache is _hardwired_. The max size _can_ be calculated
// and this can become a pointer to an allocated area
int pdf_max;
float pdf_cache[10000000];
inline_always float
pdf(float u)
{
//u += 1;
return (1 / (u * u));
}
inline_always float
cdf(float u)
{
u += 1;
return (1 - 1 / (u));
}
inline_always float
pdfx(float u,int i)
{
//u += 1;
if (i > pdf_max) {
pdf_cache[i] = pdf(u);
pdf_max = i;
}
return pdf_cache[i];
}
// The main function that implements the numerical integration,
//and it is a recursive function
float
integ(float h, int k, float du)
{
float res = 0;
float u = 1;
int i;
int iter = h / du;
k -= 1;
h += 1;
if (k == 1) {
for (i = 0; i < iter; ++i) {
res += cdf(h - u) * pdfx(u,i) * du;
u += du;
}
}
else {
for (i = 0; i < iter; ++i) {
res += integ(h - u, k, du) * pdfx(u,i) * du;
u += du;
}
}
return res;
}
// The main function that implements the numerical integration,
//and it is a recursive function
float
integ_top(float h, int k, float du)
{
float res = 0;
pdf_max = -1;
if (k == 1)
res = cdf(h);
else
res = integ(h,k,du);
return res;
}
int
main()
{
float du = 0.0001;
int K = 3;
float gamma[4] = { 0.31622777, 0.79432823,
1.99526231, 5.01187234
};
int G = 50;
int Q = 2;
for (int i = 0; i < 4; i++) {
if ((G - Q * (K - 1)) > 0) {
float gammath = (gamma[i] / Q) * (G - Q * (K - 1));
double tvbeg = tvgetf();
double rtn = 1 - integ_top(gammath, K, du);
//std::cout << 1 - integ(gammath, K, du) << endl;
double tvdif = tvgetf() - tvbeg;
printf("i=%d rtn=%f tvdif=%.9f\n",i,rtn,tvdif);
fflush(stdout);
}
}
return 0;
}
This technique may have some roundoff error as I ran the original and got:
i=0 rtn=0.418665 tvdif=27.172003746
i=1 rtn=0.183092 tvdif=168.498732328
The cached version output was:
i=0 rtn=0.418630 tvdif=13.691759109
i=1 rtn=0.183040 tvdif=85.953905582
i=2 rtn=0.070858 tvdif=526.217260361
This might be alleviated by using double
for the pdf_cache
array
UPDATE:
Here is a version that precalculates the pdf_cache
values (again, please pardon the C-like stuff):
#include <iostream>
#include <cmath>
#include <stdio.h>
#include <time.h>
double
tvgetf(void)
{
struct timespec ts;
double sec;
clock_gettime(CLOCK_REALTIME,&ts);
sec = ts.tv_nsec;
sec /= 1e9;
sec += ts.tv_sec;
return sec;
}
#define inline_always static inline __attribute__((__always_inline__))
// NOTE: the size of pdf_cache is _hardwired_. The max size _can_ be calculated
// and this can become a pointer to an allocated area
int pdf_max;
float *pdf_cache;
inline_always float
pdf(float u)
{
//u += 1;
return (1 / (u * u));
}
inline_always float
cdf(float u)
{
u += 1;
return (1 - 1 / (u));
}
inline_always float
pdfx(float u,int i)
{
//u += 1;
if (i > pdf_max) {
pdf_cache[i] = pdf(u);
pdf_max = i;
}
return pdf_cache[i];
}
// The main function that implements the numerical integration,
//and it is a recursive function
float
integ(float h, int k, float du)
{
float res = 0;
float u = 1;
int i;
int iter = h / du;
k -= 1;
h += 1;
if (k == 1) {
for (i = 0; i < iter; ++i) {
res += cdf(h - u) * pdf_cache[i] * du;
u += du;
}
}
else {
for (i = 0; i < iter; ++i) {
res += integ(h - u, k, du) * pdf_cache[i] * du;
u += du;
}
}
return res;
}
// The main function that implements the numerical integration,
//and it is a recursive function
float
integ_top(float h, int k, float du)
{
float res = 0;
if (k == 1)
res = cdf(h);
else
res = integ(h,k,du);
return res;
}
int
main()
{
float du = 0.0001;
int K = 3;
float gamma[4] = { 0.31622777, 0.79432823,
1.99526231, 5.01187234
};
int G = 50;
int Q = 2;
float maxgam = 0;
int initflg = 1;
for (int i = 0; i < 4; i++) {
if ((G - Q * (K - 1)) > 0) {
float gammath = (gamma[i] / Q) * (G - Q * (K - 1));
printf("i=%d gammath=%f\n",i,gammath);
if (initflg) {
maxgam = gammath;
initflg = 0;
continue;
}
if (gammath > maxgam)
maxgam = gammath;
}
}
pdf_max = maxgam / du;
printf("pdf_max=%d\n",pdf_max);
pdf_cache = (float *) malloc(sizeof(*pdf_cache) * pdf_max);
float u = 1;
for (int i = 0; i < pdf_max; ++i) {
pdf_cache[i] = pdf(u);
u += du;
}
for (int i = 0; i < 4; i++) {
if ((G - Q * (K - 1)) > 0) {
float gammath = (gamma[i] / Q) * (G - Q * (K - 1));
double tvbeg = tvgetf();
double rtn = 1 - integ_top(gammath, K, du);
//std::cout << 1 - integ(gammath, K, du) << endl;
double tvdif = tvgetf() - tvbeg;
printf("i=%d rtn=%f tvdif=%.9f\n",i,rtn,tvdif);
fflush(stdout);
}
}
return 0;
}
-Ox
simply refers to compiler optimizations possible on gcc. Here is a short overview \$\endgroup\$