HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Simple neural-network simulation in C++

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
neuralsimplenetworksimulation

Problem

The C++ code below simulates the timecourse of the membrane potential (V) of a population of 128 leaky integrate-and-fire neurons using the Euler method for numerical integration.

It produces data for three data points in Figure 1 of this paper. (Well, it produces 400 ms worth of data for three data points.)

I have an equivalent script written in Python (which I'm happy to share, if anyone wants to help optimize that). Expecting a major speed boost with C++ (e.g., 10-100X), I was surprised to see that this script took 20.7 s to run on my laptop, as compared to 77.5 s with Python (a smaller than 4X boost).

But, since I'm a C++ newbie, I'm hoping there's work I can do to optimize this. (I'd also love to get style critiques, because I'm sure this is stylistically a mess.)

This script saves three text files to a directory on my laptop. In order to run this, you will need to change the directory specification to a directory that exists on your computer.

```
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
using Eigen::MatrixXd;
using Eigen::ArrayXd;

bool save_mat(MatrixXd pdata, const string& file_path)
{
ofstream os(file_path.c_str());
if (!os.is_open())
{
cout
vector arange(T start, T stop, T step = 1)
{
vector values;
for (T value = start; value (0, 400, dt);
n_t = t_vector.size();
MatrixXd V(N, n_t);
V.col(0) = set_initial_V(tau, g_L, I_0, theta, V_L, N, c);
double I_syn = 0; // uA / cm^2.
ArrayXd t_spike_array = ArrayXd::Zero(N);
for (int i = 1; i theta)
{
t_spike_array(j) = t;
V(j, i) = V_L;
}
I_syn += I_syn_bar / N * f(t - t_spike_array(j), tau_1, tau_2);
}
}
sprintf(complete_save_file, save_file.c_str(), dt);
save_mat(V, complete_save_file);
}
return 0;

}

double f(double t, double tau_1, double tau_2)
{

Solution

Performance

Compiler flags

Your main performance gain will simple be using the -O3 flag for compilation. You may very well achieve even better performance with more sophisticated flags depending on your compiler and system. On my system it takes ~4s.

Output

Using a the simple linux profiling tool perf, shows that > 60 % of the remaining time is actually spent in vfprintf. A specific performance analysis tool recommendation depends on your OS.

Disabling the output, the remaining runtime is ~0.2s.

So you could now go ahead and try to optimize the output formatting, but is it really worth it for your use case?

Style

Don't use sprintf

sprintf is very dangerous. Don't use it, not in C, nor in C++. In C++ concatenate your std::strings with + or use std::stringstream. If you really want format like syntax use boost::format.

Split up your main function

Don't handle file name computations and math stuff in the same function. It's very distracting.

Dont use using namespace std;

Yes, it requires more typing, but its bad practice. For an explanation take a look at the answers to this question.

Use (const) references when appropriate

Disclaimer: I don't know Eigen, but I assume MatrixXd is an expensive-to-copy object.

You should use references to hand expensive-to-copy objects to functions. If they are input only, use const references - if they are in/out non-const references. So for save_mat use a const MatrixXd& just like you do for the file name string.

One could argue that auto current_V = prev_V ... is also dangerously expensive because it requries assignment of expensive-to-copy objects. But in this case it is an rvalue so move assignment can be used. You could also use const auto& current V = ... instead which would express the intent of not modifying it later and perform slightly better (the latter being probably irrelevant)

Note The rest is mainly C++11 biased.

Loops

In C++11, avoid raw loops. Use range-based loops instead.

Especially with the vector rcreated by arrange - It's neither efficient nor very clear to store the values you are going to process in the t_vector that you can easily generate on the fly instead of wasting memory on that. Usually I would recommend looking at cppitertools or range-v3, which allows for a more efficient and cleaner abstraction of the t values.

Also use range-based loops for the outer loop through dt_array. You can also replace the innermost loop with with cppitertools/range-v3.

Use const or constexpr....

... for local variables that don't change. Further define one variable per line and set it's value right at the definition (even if it is not const).

Use std::array instead of array[]

Simply use a constexpr n_dt and stay away for those sizeof hacks to figure out the number of elements you just defined a few lines earlier.

Context

StackExchange Code Review Q#107621, answer score: 5

Revisions (0)

No revisions yet.