2

I am writing a scientific program which uses common values of sines in its main algorithm, namely sin(M_PI/N) for N = 1, 2, 3, 4, 5, 6.

Since I want my program to be as fast as possible, I thought : let's store these values in a vector instead of having them computed over and over again. It looks like this:

sin_pi_over_n_.clear();
sin_pi_over_n_.push_back(0.0);
sin_pi_over_n_.push_back(1.0);
sin_pi_over_n_.push_back(sqrt(3.0)/2.0);
sin_pi_over_n_.push_back(sqrt(2.0)/2.0);
sin_pi_over_n_.push_back(sqrt(2.0)*0.25*sqrt(5.0-sqrt(5.0)));
sin_pi_over_n_.push_back(0.5);

so now in my main algorithm I write s = sin_pi_over_n_[n-1]; instead of s = sin(M_PI/n);.

But to my great surprise, the program turned out to be almost twice as slow! I thought, really, does it take that long to read a value in a vector? But then I realized that wasn't the problem: if I write instead

sin_pi_over_n_.push_back(sin(M_PI/1.0));
sin_pi_over_n_.push_back(sin(M_PI/2.0));
sin_pi_over_n_.push_back(sin(M_PI/3.0));
sin_pi_over_n_.push_back(sin(M_PI/4.0));
sin_pi_over_n_.push_back(sin(M_PI/5.0));
sin_pi_over_n_.push_back(sin(M_PI/6.0));

then the program is fast again! I then thought: something's wrong with my values of sines. But the crazy thing is, even if I only replace the last line sin_pi_over_n_.push_back(sin(M_PI/6.0)); by sin_pi_over_n_.push_back(0.5); then the program is slow again! Is it about double precision? I kinda doubt it: if I ask std::cout << abs(sin(M_PI/6.0) - 0.5) << std::endl;, I get 0 in my terminal.

Oops : I just realized ; if I ask std::cout << sin(M_PI/6.0) - 0.5 << std::endl; (without the abs), I then get -5.55112e-17. I am still going to go ahead and post that question, because this behavior seems incredible to me. How can I possibly optimize my program's speed if such unpredictable phenomenons have such a great impact on the performance?

Thanks for your insights!

Edit : Maybe I haven't made myself clear enough. In my program, I have a class Algo. When I execute my program, some function, say my_function, is called an enormous amount of times. In this function, one line is: s = sin(M_PI/n);. I thought I'd replace this line by s = sin_pi_over_n_[n-1];, where sin_pi_over_n_[n-1] is a vector that is stored as a member variable of the class Algo and that I fill once and for all in the constructor of Algo. Hope that makes things clearer.

Edit 2 : Okay, it appears some of you want me to post more code. Here it comes:

Class Algo:

class Algo : public QThread
{
    Q_OBJECT

public:
    Algo() {}
    void reset_algo(...)

public slots:
    void run();

private:
    void create_sines();
    double super_total_neighbors_angle(const unsigned int &index, double &error);
    double super_radius_update(const unsigned int &index, double &error);
    // etc

    std::vector<double> radii_;
    std::vector<double> sin_pi_over_n_;
    std::vector<unsigned int> neighbors_lists_sizes_;
    // etc
};

Member function create_sines:

void Algo::create_sines()
{
    sin_pi_over_n_.clear();

    /*sin_pi_over_n_.push_back(0.0);
    sin_pi_over_n_.push_back(1.0);
    sin_pi_over_n_.push_back(0.5*sqrt(3.0));
    sin_pi_over_n_.push_back(0.5*sqrt(2.0));
    sin_pi_over_n_.push_back(0.25*sqrt(2.0)*sqrt(5.0-sqrt(5.0)));
    sin_pi_over_n_.push_back(0.5);*/

    sin_pi_over_n_.push_back(sin(M_PI/1.0));
    sin_pi_over_n_.push_back(sin(M_PI/2.0));
    sin_pi_over_n_.push_back(sin(M_PI/3.0));
    sin_pi_over_n_.push_back(sin(M_PI/4.0));
    sin_pi_over_n_.push_back(sin(M_PI/5.0));
    sin_pi_over_n_.push_back(sin(M_PI/6.0));

    return;
}

Member function super_radius_update (which I renamed my_function above):

inline double Algo::super_radius_update(const unsigned int &index, double &error)
{
    int n = neighbors_lists_sizes_[index];
    double s = sin(super_total_neighbors_angle(index, error)*0.5/n);
    double rv = radii_[index]*s/(1-s);
    //s = sin(M_PI/n);
    s = sin_pi_over_n_[n-1];
    return (1-s)*rv/s;
}

I'm afraid it's hard for me to send a minimal complete example that you could run, because the entire class is a bit long and complicated. I could try, but I would sill have to post a big amount of code, and it would take me quite some time to extract...

I'm using Qt creator 4.8 64 bits on a Linux Ubuntu 12.04 64 bits laptop.

Edit 3 or 4 : I apologize for all the edits. There's an important piece of info I did not tell you : the program is going to run (basically call the function super_radius_update) until some error falls under some tolerance. As a consequence, what is making the program slower or faster is not the time of execution of super_radius_update but the number of calls of that function, I believe. Using such or such value for sin(M_PI/N) is going to have an impact on how quickly the tolerance is reached by the error.

Seub
  • 2,451
  • 4
  • 25
  • 34
  • 3
    Are you claiming that running 5 `sqrt`s is making your program noticeably slower? – Blindy Jul 04 '12 at 15:32
  • 1
    No no. I'm claiming that using the value 0.5 instead of the value sin(M_PI/6) makes my program noticeably slower (!) – Seub Jul 04 '12 at 15:36
  • 3
    please, provide a minimal complete example. Also, you should tell us: the platform, the compiler and the compiler options – Alessandro Teruzzi Jul 04 '12 at 15:39
  • 1
    Provide a minimal self-contained compilable example that shows the behaviour you are experiencing. Until that, no spoon exists. – PlasmaHH Jul 04 '12 at 15:40
  • You probably access the vector with a float which is converted to a size_t in one case. Just a guess. – Viktor Sehr Jul 04 '12 at 15:40
  • 1
    You should really post code (a small, complete program) that displays the issue. You can't just describe your program, because you don't know what the issue is and so your description will leave out relevant details. – bames53 Jul 04 '12 at 15:45
  • @user1367124, my question is, very, very specifically, even if it *was* slower, how is pushing *five* numbers in an array once going to make a difference? And if it's not only once, maybe you should refactor your code so it becomes only once because those numbers are constants. – Blindy Jul 04 '12 at 15:54
  • 1
    @user1367124: Regarding "Edits 3/4". If your convergence algorithm is so unstable that it's sensitive to a miniscule error in the value of your sin() values (presumably due to running more iterations?), that's probably a bug in your algorithm. It's unlikely to be a problem with compiler optimisations. – Oliver Charlesworth Jul 04 '12 at 16:12
  • 1
    @user1367124: If the number of iterations it takes to converge is so different, it shouldn't be a big problem for you to discover the nature of that difference. Print the consecutive converging solutions and analyze them. Is the a single iteration or a compact group that causes the difference in convergence? Or is it distributed evenly across all iterations? – AnT stands with Russia Jul 04 '12 at 16:18
  • perhaps `http://stackoverflow.com/a/2284932/811335` explains why calling sin() function is faster than calling the sqrt() to calculate common value of sines. – A. K. Jul 04 '12 at 16:24
  • @ Blindly and Aditya Kumar : I think you did not understand the problem right, it's probably my fault though for lacking clarity in my explanations. – Seub Jul 04 '12 at 16:35
  • @ Oli C. and AndreyT, you're right, I should look into that. It's not necessarily going to be easy though ;) (in my sample test, there is an incredible number of iterations - like 1 billion) – Seub Jul 04 '12 at 16:37
  • By the way, if N = 1, 2, 3, 4, 5, 6 don't recalculate the same values again and again, use precalculated table of results. – SChepurin Jul 04 '12 at 16:52
  • At least start by printing the number of iterations run for each input and each variant, and start looking for patterns in that. – Phil Miller Jul 04 '12 at 17:13
  • Although you expect a vector to be very fast access, what if the slowdown is coming from all the calls and dereferencing that a vector must entail? This is ugly but have you tried replacing it with a macro that is a switch to hardcoded values? – SpacedMonkey Jul 04 '12 at 17:47

2 Answers2

1
double rv = radii_[index]*s/(1-s);
return (1-s)*rv/s;

This code has singularities at s = 0 and s = 1, both of which occur, and will be numerically unstable due to the singularities. You probably triggered the unstabilities with the different computations of sin_pi_over_n_[0] and sin_pi_over_n_[1].

While this doesn't explain the different behaviour for different sin_pi_over_n_[5] calculations, similar unstabilities are likely.

thiton
  • 35,651
  • 4
  • 70
  • 100
  • Actually, both cases *do not* occur, s stays far away from 0 and 1. Moreover, cases n=0 and n=1 never occur. – Seub Jul 04 '12 at 16:33
0

As was pointed out, it turned out my algorithm was unstable, couldn't blame anything on the computer or C++ ;)

Seub
  • 2,451
  • 4
  • 25
  • 34