Re: Debugging reason for python running unreasonably slow when adding numbers

2023-03-21 Thread Edmondo Giovannozzi
Il giorno lunedì 20 marzo 2023 alle 19:10:26 UTC+1 Thomas Passin ha scritto:
> On 3/20/2023 11:21 AM, Edmondo Giovannozzi wrote: 
> > 
> >>> def sum1(): 
> >>> s = 0 
> >>> for i in range(100): 
> >>> s += i 
> >>> return s 
> >>> 
> >>> def sum2(): 
> >>> return sum(range(100)) 
> >> Here you already have the numbers you want to add. 
> > 
> > Actually using numpy you'll be much faster in this case: 
> > 
> > § import numpy as np 
> > § def sum3(): 
> > § return np.arange(1_000_000, dtype=np.int64).sum() 
> > 
> > On my computer sum1 takes 44 ms, while the numpy version just 2.6 ms 
> > One problem is that sum2 gives the wrong result. This is why I used 
> > np.arange with dtype=np.int64.
> On my computer they all give the same result. 
> 
> Python 3.10.9, PyQt version 6.4.1 
> Windows 10 AMD64 (build 10.0.19044) SP0 
> Processor: 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz, 1690 Mhz, 4 
> Core(s), 8 Logical Processor(s)
> > sum2 evidently doesn't uses the python "big integers" e restrict the result 
> > to 32 bits.
> What about your system? Let's see if we can figure the reason for the 
> difference.

I'm using winpython on Windows 11 and the python version is, well, 3.11:

But it is my fault, sorry, I realised now that ipython is importing numpy 
namespace and the numpy sum function is overwriting the intrinsic sum.
The intrinsic sum is behaving correctly and is faster when used in 
sum(range(1_000_000)) then the numpy version.


-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Debugging reason for python running unreasonably slow when adding numbers

2023-03-20 Thread Thomas Passin

On 3/20/2023 11:21 AM, Edmondo Giovannozzi wrote:



def sum1():
 s = 0
 for i in range(100):
 s += i
 return s

def sum2():
 return sum(range(100))

Here you already have the numbers you want to add.


Actually using numpy you'll be much faster in this case:

§ import numpy as np
§ def sum3():
§return np.arange(1_000_000, dtype=np.int64).sum()

On my computer sum1 takes 44 ms, while the numpy version just 2.6 ms
One problem is that sum2 gives the wrong result. This is why I used np.arange 
with dtype=np.int64.


On my computer they all give the same result.

Python 3.10.9, PyQt version 6.4.1
Windows 10 AMD64 (build 10.0.19044) SP0
Processor: 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz, 1690 Mhz, 4 
Core(s), 8 Logical Processor(s)




sum2 evidently doesn't uses the python "big integers" e restrict the result to 
32 bits.


What about your system?  Let's see if we can figure the reason for the 
difference.


--
https://mail.python.org/mailman/listinfo/python-list


Re: Debugging reason for python running unreasonably slow when adding numbers

2023-03-20 Thread MRAB

On 2023-03-20 15:21, Edmondo Giovannozzi wrote:


> def sum1(): 
> s = 0 
> for i in range(100): 
> s += i 
> return s 
> 
> def sum2(): 
> return sum(range(100))
Here you already have the numbers you want to add. 


Actually using numpy you'll be much faster in this case:

§ import numpy as np
§ def sum3():
§return np.arange(1_000_000, dtype=np.int64).sum()

On my computer sum1 takes 44 ms, while the numpy version just 2.6 ms
One problem is that sum2 gives the wrong result. This is why I used np.arange 
with dtype=np.int64.

sum2 evidently doesn't uses the python "big integers" e restrict the result to 
32 bits.


On my computer they all give the same result, as I'd expect.
--
https://mail.python.org/mailman/listinfo/python-list


Re: Debugging reason for python running unreasonably slow when adding numbers

2023-03-20 Thread Edmondo Giovannozzi

> > def sum1(): 
> > s = 0 
> > for i in range(100): 
> > s += i 
> > return s 
> > 
> > def sum2(): 
> > return sum(range(100))
> Here you already have the numbers you want to add. 

Actually using numpy you'll be much faster in this case:

§ import numpy as np
§ def sum3():
§return np.arange(1_000_000, dtype=np.int64).sum()

On my computer sum1 takes 44 ms, while the numpy version just 2.6 ms
One problem is that sum2 gives the wrong result. This is why I used np.arange 
with dtype=np.int64.

sum2 evidently doesn't uses the python "big integers" e restrict the result to 
32 bits.

-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Debugging reason for python running unreasonably slow when adding numbers

2023-03-18 Thread Peter J. Holzer
On 2023-03-15 17:09:52 +, Weatherby,Gerard wrote:
> Sum is faster than iteration in the general case.

I'd say this is the special case, not the general case.

> def sum1():
> s = 0
> for i in range(100):
> s += i
> return s
> 
> def sum2():
> return sum(range(100))

Here you already have the numbers you want to add.

The OP needed to compute those numbers first.

hp

-- 
   _  | Peter J. Holzer| Story must make more sense than reality.
|_|_) ||
| |   | h...@hjp.at |-- Charles Stross, "Creative writing
__/   | http://www.hjp.at/ |   challenge!"


signature.asc
Description: PGP signature
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Debugging reason for python running unreasonably slow when adding numbers

2023-03-16 Thread Roel Schroeven

Op 14/03/2023 om 8:48 schreef Alexander Nestorov:

I have the following code:

 ...
 for i in range(151): # 150 iterations
     ...
Nothing to do with your actual question and it's probably just a small 
oversight, but still I thought it was worth a mention: that comment does 
not accurately describe the code; the code is actually doing 151 
iterations, numbered 0 up to and including 150.


--
"I've come up with a set of rules that describe our reactions to technologies:
1. Anything that is in the world when you’re born is normal and ordinary and is
   just a natural part of the way the world works.
2. Anything that's invented between when you’re fifteen and thirty-five is new
   and exciting and revolutionary and you can probably get a career in it.
3. Anything invented after you're thirty-five is against the natural order of 
things."
-- Douglas Adams, The Salmon of Doubt

--
https://mail.python.org/mailman/listinfo/python-list


Re: Debugging reason for python running unreasonably slow when adding numbers

2023-03-15 Thread Weatherby,Gerard
Moving the generator out:

import timeit

thedata = [i for i in range(1_000_000)]
def sum1():
s = 0
for i in thedata:
s += i
return s

def sum2():
return sum(thedata)

print('For Loop Sum:', timeit.timeit(sum1, number=100))
print( 'Built-in Sum:', timeit.timeit(sum2, number=100))

---
For Loop Sum: 6.984986504539847
Built-in Sum: 0.5175364706665277

From: Weatherby,Gerard 
Date: Wednesday, March 15, 2023 at 1:09 PM
To: python-list@python.org 
Subject: Re: Debugging reason for python running unreasonably slow when adding 
numbers
Sum is faster than iteration in the general case.

Lifting a test program from Stack Overflow 
https://stackoverflow.com/questions/24578896/python-built-in-sum-function-vs-for-loop-performance,


import timeit

def sum1():
s = 0
for i in range(100):
s += i
return s

def sum2():
return sum(range(100))

print('For Loop Sum:', timeit.timeit(sum1, number=100))
print( 'Built-in Sum:', timeit.timeit(sum2, number=100))

---

For Loop Sum: 7.726335353218019
Built-in Sum: 1.0398506000638008

---


From: Python-list  on 
behalf of David Raymond 
Date: Wednesday, March 15, 2023 at 11:46 AM
To: python-list@python.org 
Subject: RE: Debugging reason for python running unreasonably slow when adding 
numbers
*** Attention: This is an external email. Use caution responding, opening 
attachments or clicking on links. ***

> Then I'm very confused as to how things are being done, so I will shut
> up. There's not enough information here to give performance advice
> without actually being a subject-matter expert already.

Short version: In this specific case "weights" is a 5,147 element list of 
floats, and "input" is a 10 element list of integers which has the indexes of 
the 10 elements in weights that he wants to add up.

sum_ = 0
for key in input:
sum_ += weights[key]

vs

sum_ = sum(weights[key] for key in input)

vs... other ways
--
https://urldefense.com/v3/__https://mail.python.org/mailman/listinfo/python-list__;!!Cn_UX_p3!ikhJJxqJnllDHh6JKaMX8g2K-Ceq6ZiRDJxX7AbS-1AiBIrdAmA2qjBtYZbxel2mktyno1s9iJGo_zAl5alBIWxoVXE$<https://urldefense.com/v3/__https:/mail.python.org/mailman/listinfo/python-list__;!!Cn_UX_p3!ikhJJxqJnllDHh6JKaMX8g2K-Ceq6ZiRDJxX7AbS-1AiBIrdAmA2qjBtYZbxel2mktyno1s9iJGo_zAl5alBIWxoVXE$>
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Debugging reason for python running unreasonably slow when adding numbers

2023-03-15 Thread Weatherby,Gerard
Sum is faster than iteration in the general case.

Lifting a test program from Stack Overflow 
https://stackoverflow.com/questions/24578896/python-built-in-sum-function-vs-for-loop-performance,

import timeit

def sum1():
s = 0
for i in range(100):
s += i
return s

def sum2():
return sum(range(100))

print('For Loop Sum:', timeit.timeit(sum1, number=100))
print( 'Built-in Sum:', timeit.timeit(sum2, number=100))

---

For Loop Sum: 7.726335353218019
Built-in Sum: 1.0398506000638008

---

From: Python-list  on 
behalf of David Raymond 
Date: Wednesday, March 15, 2023 at 11:46 AM
To: python-list@python.org 
Subject: RE: Debugging reason for python running unreasonably slow when adding 
numbers
*** Attention: This is an external email. Use caution responding, opening 
attachments or clicking on links. ***

> Then I'm very confused as to how things are being done, so I will shut
> up. There's not enough information here to give performance advice
> without actually being a subject-matter expert already.

Short version: In this specific case "weights" is a 5,147 element list of 
floats, and "input" is a 10 element list of integers which has the indexes of 
the 10 elements in weights that he wants to add up.

sum_ = 0
for key in input:
sum_ += weights[key]

vs

sum_ = sum(weights[key] for key in input)

vs... other ways
--
https://urldefense.com/v3/__https://mail.python.org/mailman/listinfo/python-list__;!!Cn_UX_p3!ikhJJxqJnllDHh6JKaMX8g2K-Ceq6ZiRDJxX7AbS-1AiBIrdAmA2qjBtYZbxel2mktyno1s9iJGo_zAl5alBIWxoVXE$<https://urldefense.com/v3/__https:/mail.python.org/mailman/listinfo/python-list__;!!Cn_UX_p3!ikhJJxqJnllDHh6JKaMX8g2K-Ceq6ZiRDJxX7AbS-1AiBIrdAmA2qjBtYZbxel2mktyno1s9iJGo_zAl5alBIWxoVXE$>
-- 
https://mail.python.org/mailman/listinfo/python-list


RE: Debugging reason for python running unreasonably slow when adding numbers

2023-03-15 Thread David Raymond
> Then I'm very confused as to how things are being done, so I will shut
> up. There's not enough information here to give performance advice
> without actually being a subject-matter expert already.

Short version: In this specific case "weights" is a 5,147 element list of 
floats, and "input" is a 10 element list of integers which has the indexes of 
the 10 elements in weights that he wants to add up.

sum_ = 0
for key in input:
sum_ += weights[key]

vs

sum_ = sum(weights[key] for key in input)

vs... other ways
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Debugging reason for python running unreasonably slow when adding numbers

2023-03-15 Thread Chris Angelico
On Thu, 16 Mar 2023 at 02:14, Thomas Passin  wrote:
>
> On 3/15/2023 11:01 AM, Chris Angelico wrote:
> > On Thu, 16 Mar 2023 at 01:26, David Raymond  
> > wrote:
> >> I'm not quite sure why the built-in sum functions are slower than the for 
> >> loop,
> >> or why they're slower with the generator expression than with the list 
> >> comprehension.
> >
> > For small-to-medium data sizes, genexps are slower than list comps,
> > but use less memory. (At some point, using less memory translates
> > directly into faster runtime.) But even the sum-with-genexp version is
> > notably faster than reduce.
> >
> > Is 'weights' a dictionary? You're iterating over it, then subscripting
> > every time. If it is, try simply taking the sum of weights.values(),
> > as this should be significantly faster.
>
> It's a list.
>

Then I'm very confused as to how things are being done, so I will shut
up. There's not enough information here to give performance advice
without actually being a subject-matter expert already.

ChrisA
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Debugging reason for python running unreasonably slow when adding numbers

2023-03-15 Thread Thomas Passin

On 3/15/2023 11:01 AM, Chris Angelico wrote:

On Thu, 16 Mar 2023 at 01:26, David Raymond  wrote:

I'm not quite sure why the built-in sum functions are slower than the for loop,
or why they're slower with the generator expression than with the list 
comprehension.


For small-to-medium data sizes, genexps are slower than list comps,
but use less memory. (At some point, using less memory translates
directly into faster runtime.) But even the sum-with-genexp version is
notably faster than reduce.

Is 'weights' a dictionary? You're iterating over it, then subscripting
every time. If it is, try simply taking the sum of weights.values(),
as this should be significantly faster.


It's a list.

--
https://mail.python.org/mailman/listinfo/python-list


Re: Debugging reason for python running unreasonably slow when adding numbers

2023-03-15 Thread Thomas Passin

On 3/15/2023 10:24 AM, David Raymond wrote:

Or use the sum() builtin rather than reduce(), which was
*deliberately* removed from the builtins. The fact that you can get
sum() without importing, but have to go and reach for functools to get
reduce(), is a hint that you probably shouldn't use reduce when sum
will work.


Out of curiosity I tried a couple variations and am a little confused by the 
results. Maybe I'm having a brain fart and am missing something obvious?

Each of these was run with the same "data" and "perceptrons" values to keep 
that fair.
Times are averages over 150 iterations like the original.
The only thing changed in the trainPerceptron function was how to calculate sum_


Original:
sum_ = 0
for key in input:
 v = weights[key]
 sum_ += v
418ms

The reduce version:
sum_ = reduce(lambda acc, key: acc + weights[key], input)
758ms

Getting rid of the assignment to v in the original version:
sum_ = 0
for key in input:
 sum_ += weights[key]
380ms

But then using sum seems to be slower

sum with generator expression:
sum_ = sum(weights[key] for key in input)
638ms

sum with list comprehension:
sum_ = sum([weights[key] for key in input])
496ms

math.fsum with generator expression:
sum_ = math.fsum(weights[key] for key in input)
618ms

math.fsum with list comprehension:
sum_ = math.fsum([weights[key] for key in input])
480ms


I'm not quite sure why the built-in sum functions are slower than the for loop,
or why they're slower with the generator expression than with the list 
comprehension.


I tried similar variations yesterday and got similar results.  All the 
sum() versions I tried were slower.  Like you, I got the smallest times for


> for key in input:
>  sum_ += weights[key]

but I didn't get as much of a difference as you did.

I surmise that in using the sum() variations, that the entire sequence 
was constructed first, and then iterated over.  In the non-sum() 
versions, no new sequence had to be constructed first, so it would make 
sense for the latter to be slower.

--
https://mail.python.org/mailman/listinfo/python-list


Re: Debugging reason for python running unreasonably slow when adding numbers

2023-03-15 Thread Chris Angelico
On Thu, 16 Mar 2023 at 01:26, David Raymond  wrote:
> I'm not quite sure why the built-in sum functions are slower than the for 
> loop,
> or why they're slower with the generator expression than with the list 
> comprehension.

For small-to-medium data sizes, genexps are slower than list comps,
but use less memory. (At some point, using less memory translates
directly into faster runtime.) But even the sum-with-genexp version is
notably faster than reduce.

Is 'weights' a dictionary? You're iterating over it, then subscripting
every time. If it is, try simply taking the sum of weights.values(),
as this should be significantly faster.

ChrisA
-- 
https://mail.python.org/mailman/listinfo/python-list


RE: Debugging reason for python running unreasonably slow when adding numbers

2023-03-15 Thread David Raymond
> Or use the sum() builtin rather than reduce(), which was
> *deliberately* removed from the builtins. The fact that you can get
> sum() without importing, but have to go and reach for functools to get
> reduce(), is a hint that you probably shouldn't use reduce when sum
> will work.

Out of curiosity I tried a couple variations and am a little confused by the 
results. Maybe I'm having a brain fart and am missing something obvious?

Each of these was run with the same "data" and "perceptrons" values to keep 
that fair.
Times are averages over 150 iterations like the original.
The only thing changed in the trainPerceptron function was how to calculate sum_


Original:
sum_ = 0
for key in input:
v = weights[key]
sum_ += v
418ms

The reduce version:
sum_ = reduce(lambda acc, key: acc + weights[key], input)
758ms

Getting rid of the assignment to v in the original version:
sum_ = 0
for key in input:
sum_ += weights[key]
380ms

But then using sum seems to be slower

sum with generator expression:
sum_ = sum(weights[key] for key in input)
638ms

sum with list comprehension:
sum_ = sum([weights[key] for key in input])
496ms

math.fsum with generator expression:
sum_ = math.fsum(weights[key] for key in input)
618ms

math.fsum with list comprehension:
sum_ = math.fsum([weights[key] for key in input])
480ms


I'm not quite sure why the built-in sum functions are slower than the for loop,
or why they're slower with the generator expression than with the list 
comprehension.
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Debugging reason for python running unreasonably slow when adding numbers

2023-03-14 Thread Oscar Benjamin
On Tue, 14 Mar 2023 at 16:27, Alexander Nestorov  wrote:
>
> I'm working on an NLP and I got bitten by an unreasonably slow behaviour in 
> Python while operating with small amounts of numbers.
>
> I have the following code:
>
> ```python
> import random, time
> from functools import reduce
>
> def trainPerceptron(perceptron, data):
>   learningRate = 0.002
>   weights = perceptron['weights']
>   error = 0
>   for chunk in data:
>   input = chunk['input']
>   output = chunk['output']
>
>   # 12x slower than equivalent JS
>   sum_ = 0
>   for key in input:
>   v = weights[key]
>   sum_ += v

In Python something like your task here would usually use something
along the lines of NumPy. Your two innermost loops involve adding up a
subset of numbers from a list chosen using a list of indices. This is
something that numpy can do much more efficiently with its fancy
indexing e.g.:

In [3]: a = np.array([1, 2, 3, 4, 5, 6, 7])

In [4]: b = np.array([0, 3, 5])

In [5]: a[b]
Out[5]: array([1, 4, 6])

In [6]: a[b].sum()
Out[6]: 11

This a[b].sum() operation in your code would be weights[input].sum()
and would be much faster than the loop shown (the speed difference
will be larger if you increase the size of the input array).

--
Oscar
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Debugging reason for python running unreasonably slow when adding numbers

2023-03-14 Thread Chris Angelico
On Wed, 15 Mar 2023 at 08:53, Peter J. Holzer  wrote:
>
> On 2023-03-14 16:48:24 +0900, Alexander Nestorov wrote:
> > I'm working on an NLP and I got bitten by an unreasonably slow
> > behaviour in Python while operating with small amounts of numbers.
> >
> > I have the following code:
> [...]
> >   # 12x slower than equivalent JS
> >   sum_ = 0
> >   for key in input:
> >   v = weights[key]
> >   sum_ += v
> >
> >   # 20x slower than equivalent JS
> >   #sum_ = reduce(lambda acc, key: acc + weights[key], input)
>
> Not surprising. Modern JavaScript implementations have a JIT compiler.
> CPython doesn't.
>
> You may want to try PyPy if your code uses tight loops like that.
>
> Or alternatively it may be possible to use numpy to do these operations.
>

Or use the sum() builtin rather than reduce(), which was
*deliberately* removed from the builtins. The fact that you can get
sum() without importing, but have to go and reach for functools to get
reduce(), is a hint that you probably shouldn't use reduce when sum
will work.

Naive code is almost always going to be slower than smart code, and
comparing "equivalent" code across languages is almost always an
unfair comparison to one of them.

ChrisA
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Debugging reason for python running unreasonably slow when adding numbers

2023-03-14 Thread Peter J. Holzer
On 2023-03-14 16:48:24 +0900, Alexander Nestorov wrote:
> I'm working on an NLP and I got bitten by an unreasonably slow
> behaviour in Python while operating with small amounts of numbers.
> 
> I have the following code:
[...]
>       # 12x slower than equivalent JS
>       sum_ = 0
>       for key in input:
>           v = weights[key]
>           sum_ += v
> 
>       # 20x slower than equivalent JS
>       #sum_ = reduce(lambda acc, key: acc + weights[key], input)

Not surprising. Modern JavaScript implementations have a JIT compiler.
CPython doesn't.

You may want to try PyPy if your code uses tight loops like that.

Or alternatively it may be possible to use numpy to do these operations.

hp

-- 
   _  | Peter J. Holzer| Story must make more sense than reality.
|_|_) ||
| |   | h...@hjp.at |-- Charles Stross, "Creative writing
__/   | http://www.hjp.at/ |   challenge!"


signature.asc
Description: PGP signature
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: Debugging reason for python running unreasonably slow when adding numbers

2023-03-14 Thread Thomas Passin

On 3/14/2023 3:48 AM, Alexander Nestorov wrote:

I'm working on an NLP and I got bitten by an unreasonably slow behaviour in 
Python while operating with small amounts of numbers.

I have the following code:

```python
import random, time
from functools import reduce

def trainPerceptron(perceptron, data):
   learningRate = 0.002
   weights = perceptron['weights']
   error = 0
   for chunk in data:
       input = chunk['input']
       output = chunk['output']

       # 12x slower than equivalent JS
       sum_ = 0
       for key in input:
           v = weights[key]
           sum_ += v

       # 20x slower than equivalent JS
       #sum_ = reduce(lambda acc, key: acc + weights[key], input)

       actualOutput = sum_ if sum_ > 0 else 0

       expectedOutput = 1 if output == perceptron['id'] else 0
       currentError = expectedOutput - actualOutput
       if currentError:
           error += currentError ** 2
           change = currentError * learningRate
           for key in input:
               weights[key] += change


[snip]
Just a speculation, but the difference with the javascript behavior 
might be because the JS JIT compiler kicked in for these loops.


--
https://mail.python.org/mailman/listinfo/python-list


Debugging reason for python running unreasonably slow when adding numbers

2023-03-14 Thread Alexander Nestorov
I'm working on an NLP and I got bitten by an unreasonably slow behaviour in 
Python while operating with small amounts of numbers.

I have the following code:

```python
import random, time
from functools import reduce

def trainPerceptron(perceptron, data):
  learningRate = 0.002
  weights = perceptron['weights']
  error = 0
  for chunk in data:
      input = chunk['input']
      output = chunk['output']

      # 12x slower than equivalent JS
      sum_ = 0
      for key in input:
          v = weights[key]
          sum_ += v

      # 20x slower than equivalent JS
      #sum_ = reduce(lambda acc, key: acc + weights[key], input)

      actualOutput = sum_ if sum_ > 0 else 0

      expectedOutput = 1 if output == perceptron['id'] else 0
      currentError = expectedOutput - actualOutput
      if currentError:
          error += currentError ** 2
          change = currentError * learningRate
          for key in input:
              weights[key] += change

  return error

# Build mock data structure
data = [{
   'input': random.sample(range(0, 5146), 10),
   'output': 0
} for _ in range(11514)]
perceptrons = [{
   'id': i,
   'weights': [0.0] * 5147,
} for i in range(60)] # simulate 60 perceptrons

# Simulate NLU
for i in range(151): # 150 iterations
  hrstart = time.perf_counter()
  for perceptron in perceptrons:
    trainPerceptron(perceptron, data)
  hrend = time.perf_counter()
  print(f'Epoch {i} - Time for training: {int((hrend - hrstart) * 1000)}ms')
```

Running it on my M1 MBP I get the following numbers.

```
Epoch 0 - Time for training: 199ms
Epoch 1 - Time for training: 198ms
Epoch 2 - Time for training: 199ms
Epoch 3 - Time for training: 197ms
Epoch 4 - Time for training: 199ms
...
Epoch 146 - Time for training: 198ms
Epoch 147 - Time for training: 200ms
Epoch 148 - Time for training: 198ms
Epoch 149 - Time for training: 198ms
Epoch 150 - Time for training: 198ms
```

Each epoch is taking around 200ms, which is unreasonably slow given the small 
amount of numbers that are being processed. I profiled the code with `cProfile` 
in order to find out what is going on:

```
         655306 function calls (655274 primitive calls) in 59.972 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
      3/1    0.000    0.000   59.972   59.972 {built-in method builtins.exec}
        1    0.005    0.005   59.972   59.972 poc-small.py:1()
     9060   59.850    0.007   59.850    0.007 poc-small.py:4(trainPerceptron)
        1    0.006    0.006    0.112    0.112 poc-small.py:34()
    11514    0.039    0.000    0.106    0.000 random.py:382(sample)
   115232    0.034    0.000    0.047    0.000 
random.py:235(_randbelow_with_getrandbits)
    11548    0.002    0.000    0.012    0.000 {built-in method 
builtins.isinstance}
    11514    0.002    0.000    0.010    0.000 :117(__instancecheck__)
   183616    0.010    0.000    0.010    0.000 {method 'getrandbits' of 
'_random.Random' objects}
    11514    0.002    0.000    0.008    0.000 {built-in method 
_abc._abc_instancecheck}
    11514    0.002    0.000    0.006    0.000 :121(__subclasscheck__)
   115140    0.005    0.000    0.005    0.000 {method 'add' of 'set' objects}
    11514    0.003    0.000    0.004    0.000 {built-in method 
_abc._abc_subclasscheck}
   115232    0.004    0.000    0.004    0.000 {method 'bit_length' of 'int' 
objects}
      151    0.003    0.000    0.003    0.000 {built-in method builtins.print}
```

This wasn't too helpful, so I tried with [line_profiler][1]:

```
Timer unit: 1e-06 s

Total time: 55.2079 s
File: poc-small.py
Function: trainPerceptron at line 4

Line #      Hits         Time  Per Hit   % Time  Line Contents
==
     4                                           @profile
     5                                           def 
trainPerceptron(perceptron, data):
     6      1214        301.0      0.2      0.0    learningRate = 0.002
     7      1214        255.0      0.2      0.0    weights = 
perceptron['weights']
     8      1214        114.0      0.1      0.0    error = 0
     9  13973840    1742427.0      0.1      3.2    for chunk in data:
    10  13973840    1655043.0      0.1      3.0        input = chunk['input']
    11  13973840    1487543.0      0.1      2.7        output = chunk['output']
    12
    13                                                 # 12x slower than 
equivalent JS
    14  13973840    1210755.0      0.1      2.2        sum_ = 0
    15 139738397   13821056.0      0.1     25.0        for key in input:
    16 139738397   13794656.0      0.1     25.0            v = weights[key]
    17 139738396   14942692.0      0.1     27.1            sum_ += v
    18
    19                                                 # 20x slower than 
equivalent JS
    20                                                 #sum_ = reduce(lambda 
acc, key: acc + weights[key], input)
    21
    22  13973839    1618273.0