C# math is fast
I was reading an article on neural networks that mentioned the usual sigmoid activation function when the inputs are real numbers in the [0, 1) interval:
The article mentions that this is probably where the program would spend at least half of its time so I thought "why not pre-compute a bunch of values and trade memory and precision for time"? It turns out, C# math is quite fast and the gain might not be worth it. (I haven't tested it yet with a NN.)
This is the code I wrote to benchmark the two options, using LinqPad 5:
void Main() { const int STEPS = 1 * 1000 * 1000; Func<double, double> activation = x => 1.0 / (1.0 + Math.Exp(-x)); var cache = Precompute(0.0, 1.0, STEPS, activation); Benchmark("Using the cache", x => cache[(int) Math.Truncate(x * STEPS)]); Benchmark("Calling the function each time", activation); } double[] Precompute(double lower, double upper, int steps, Func<double, double> func) { var result = new List<double>(); For(0.0, 1.0, 1.0 / steps, value => result.Add(func(value))); return result.ToArray(); } void Benchmark(string message, Func<double, double> func) { const int COUNT = 100 * 1000 * 1000; var dummy = 0.0; var ts = ComputeBenchmark(() => For(0.0, 1.0, 1.0 / COUNT, value => dummy += func(value))); Console.WriteLine(message + ": " + ts + " - sum = " + dummy); } void For(double lower, double upper, double increment, Action<double> action) { for (var value = lower; value <= upper; value += increment) action(value); } TimeSpan ComputeBenchmark(Action action) { var sw = new Stopwatch(); sw.Start(); action(); sw.Stop(); return sw.Elapsed; }
These are the results; using the cache takes about 70% of the time required when calling the function every time, at the expense of several MB of memory and some loss of precision. In a large NN, where billions of evaluations are expected in training, the trade-off might be worth it. I don't yet have enough experience to decide.
Using the cache: 00:00:01.9850792 - sum = 62011439.025702 Calling the function each time: 00:00:02.7755272 - sum = 62011450.5899971
As a side-note, I have computed and printed the sum to avoid the compiler from optimizing away the function calls; I don't know if LinqPad would do that but I wanted to avoid it just in case.
Comments