You may want to take a look at Rounds 1, 2, and 3, though that isn't necessary for understanding what's below.
The major change since Round 3 is that my code is much cleaner and I'm including profiling information here.
Here's my script:
#include "hansel.h"
#include <deque>
int main()
{
// Parameters.
const double tau = 10;
const double g_l = 0.1;
const double I_0 = 2.3;
const double theta = -40;
const double V_l = -60;
const double c = 0.5;
const double N = 128;
const double I_syn_bar = 0;
const double tau_1 = 3;
const double tau_2 = 1;
const double C = 1;
const double Delta_t = 0.25;
// Simulation.
std::deque<double> spike_times;
double V = set_initial_V(tau, g_l, I_0, theta, V_l, c, N);
for (double t = 0; t < 10000; t += Delta_t)
{
double I_syn = get_I_syn(I_syn_bar, N, tau_1, tau_2, t, spike_times);
double dV_dt = get_dV_dt(g_l, V, V_l, I_syn, I_0, C);
V += Delta_t * dV_dt;
if (V > theta)
{
V = V_l;
spike_times.push_back(t + Delta_t);
}
}
}
Here's the header file it includes:
#include <math.h>
#include <deque>
const double get_T(const double tau, const double g_l, const double I_0,
const double theta, const double V_l)
{
return -tau * log(1 - g_l / I_0 * (theta - V_l));
}
double set_initial_V(const double tau, const double g_l, const double I_0,
const double theta, const double V_l, const double c,
const double N)
{
const double T = get_T(tau, g_l, I_0, theta, V_l);
const double i = 1;
return V_l + I_0 / g_l * (1 - exp(-c * (i - 1) / N * T / tau));
}
double get_dV_dt(const double g_l, const double V, const double V_l,
const double I_syn, const double I_0, const double C)
{
return (-g_l * (V - V_l) + I_syn + I_0) / C;
}
double get_f(const double tau_1, const double tau_2, const double t)
{
return 1 / (tau_1 - tau_2) * (exp(-t / tau_1) - exp(-t / tau_2));
}
double get_I_syn(const double I_syn_bar, const double N, const double tau_1,
const double tau_2, const double t,
const std::deque<double> & spike_times)
{
double sum_f = 0;
const int n_spikes = spike_times.size();
for (int i = 0; i < n_spikes; i += 1)
{
sum_f += get_f(tau_1, tau_2, t - spike_times[i]);
}
return I_syn_bar / N * sum_f;
}
Profiling the script, I see that ~100% of the time is spent in calls to get_I_syn
. Within that, time is split between calls to get_f
(50.1%) and use of the deque
[]
operator (45.4%).
I'm hoping to speed up this script by a factor of ~10.
My questions are
1) Is there a way to refactor this line to increase speed:
return 1 / (tau_1 - tau_2) * (exp(-t / tau_1) - exp(-t / tau_2));
2) Is there a way to vectorize (or otherwise speed up) this loop:
for (int i = 0; i < n_spikes; i += 1)
{
sum_f += get_f(tau_1, tau_2, t - spike_times[i]);
}
3) Is it likely to be faster to use an array
or a vector
instead of the deque
object?
vector
got me to a total computing time of~5s
as compared to the original~7s
. (this is still far too slow.) after this change, i'm spending~88.8%
of the time inget_f
. i'm wondering whether i need a larger algorithmic change toget_I_syn
. maybe i should pre-compute values forget_f
for the full range of values fort-spike_times[i]
i'm expecting to get? \$\endgroup\$~5s
to run a10-s
simulation of138
neurons. it takes me this long to simulate just1
neuron for10 s
. \$\endgroup\$75.3%
of the time is spent inexp
-- so it's really the use of thismath
function over and over again that's slowing me down. \$\endgroup\$7s
? What compiler with what options are you using on what kind of a machine? On myi7-4790K
withgcc5.2 -O3
it runsin0.25s
. \$\endgroup\$8 s
using the-O3
compiler option. i'm not sure exactly what to tell you about my machine. this is the processor info:Intel® Core™2 Duo CPU T5250 @ 1.50GHz × 2
. it's an old machine, but not 1998 old. \$\endgroup\$