title: PyPy 17x faster than Python. Can Python be sped up?
body: <p>Solving a recent <a href="http://adventofcode.com/2017/day/5" rel="noreferrer">Advent of Code problem</a>, I found my default Python was ~40x slower than PyPy. I was able to get that down to about 17x with <a href="https://gist.github.com/llimllib/bd05381c431d8d01fb434a918e2a2c56" rel="noreferrer">this code</a> by limiting calls to <code>len</code> and limiting global lookups by running it in a function.</p>
  
  <p>Right now, <code>e.py</code> runs in 5.162 seconds on python 3.6.3 and .297 seconds on PyPy on my machine.</p>
  
  <p>My question is: is this the irreducible speedup of JIT, or is there some way to speed up the CPython answer? (short of extreme means: I could go to Cython/Numba or something?) How do I convince myself that there's nothing more I can do?</p>
  
  <hr>
  
  <p>See the gist for the list of numbers input file.</p>
  
  <p>As described in <a href="http://adventofcode.com/2017/day/5" rel="noreferrer">the problem statement</a>, they represent jump offsets. <code>position += offsets[current]</code>, and increment the current offset by 1.  You're done when a jump takes you outside the list.</p>
  
  <p>Here's the example given (the full input that takes 5 seconds is much longer, and has larger numbers):</p>
  
  <pre><code>(0) 3  0  1  -3  - before we have taken any steps.
  (1) 3  0  1  -3  - jump with offset 0 (that is, don't jump at all). Fortunately, the instruction is then incremented to 1.
   2 (3) 0  1  -3  - step forward because of the instruction we just modified. The first instruction is incremented again, now to 2.
   2  4  0  1 (-3) - jump all the way to the end; leave a 4 behind.
   2 (4) 0  1  -2  - go back to where we just were; increment -3 to -2.
   2  5  0  1  -2  - jump 4 steps forward, escaping the maze.
  </code></pre>
  
  <p>The code:</p>
  
  <pre><code>def run(cmds):
      location = 0
      counter = 0
      while 1:
          try:
              cmd = cmds[location]
              if cmd &gt;= 3:
                  cmds[location] -= 1
              else:
                  cmds[location] += 1
              location += cmd
              if location &lt; 0:
                  print(counter)
                  break
              counter += 1
          except:
              print(counter)
              break
  
  if __name__=="__main__":
      text = open("input.txt").read().strip().split("\n")
      cmds = [int(cmd) for cmd in text]
      run(cmds)
  </code></pre>
  
  <p>edit: I compiled and ran the code with Cython, that dropped runtime to 2.53s, but that's still almost an order of magnitude slower than PyPy.</p>
  
  <p>edit: <a href="https://gist.github.com/llimllib/bd05381c431d8d01fb434a918e2a2c56#file-f_numba-py" rel="noreferrer">Numba gets me to within 2x</a></p>
  
  <p>edit: The best <a href="https://gist.github.com/llimllib/bd05381c431d8d01fb434a918e2a2c56#file-g_cython-pyx" rel="noreferrer">Cython I could write</a> got down to 1.32s, a little over 4x pypy</p>
  
  <p>edit: Moving the <code>cmd</code> variable into a cdef, as suggested by @viraptor, got the Cython down to .157 seconds! Nearly a full order of magnitude faster, and not <em>that</em> far from regular python. Still, I come away from this extremely impressed with the PyPy JIT, which did all this for free!</p>