fastero icon indicating copy to clipboard operation
fastero copied to clipboard

Issue when timing code with a call to global

Open RoyLarson opened this issue 2 years ago • 3 comments

I tried fastero after listening to the April 28 2022 episode of Python Bytes.

In installed
fastero 0.2.4
python 3.10.4
windows 10.

I wanted to time two functions one that used global and one that didn't.

I made a file - "using_global.py"

R = 8.314

def calling_global():
    global R
    R*10

calling_global()

and a file - "not_using_global.py"

R=8.314

def not_calling_global():
    R*10

not_calling_global()

then at the command line ran:

python -m fastero "file: not_using_global.py" "file: using_global.py"

I received an error

────────────────────────────────────────────────── Benchmark started… ──────────────────────────────────────────────────
Benchmark 1: R = 8.314


def not_calling_global():
    R * 10


not_calling_global()
  Time  (mean ± σ):       196.1 ns ±   5.2 ns
  Range (min  … max):     190.9 ns … 204.9 ns    [runs: 14,000,000]
Benchmark 2: R = 8.314


def calling_global():
    global R
    R * 10


calling_global()
Traceback (most recent call last):
  File "C:\Users\XXX\Anaconda3\envs\eos\lib\site-packages\fastero\core.py", line 475, in app
    num_in_one_batch, time_taken = _autorange(timer, autorange_callback)
  File "C:\Users\rlarson\Anaconda3\envs\eos\lib\site-packages\fastero\core.py", line 461, in _autorange
    time_taken = timer.timeit(number)
  File "C:\Users\XXX\Anaconda3\envs\eos\lib\timeit.py", line 178, in timeit
    timing = self.inner(it, self.timer)
  File "<timeit-src>", line 14, in inner
    calling_global()
  File "<timeit-src>", line 11, in calling_global
    R * 10
NameError: name 'R' is not defined

if I run the file "using_global.py" with python I do not get the error related to this file.

python using_global.py

RoyLarson avatar Apr 29 '22 13:04 RoyLarson

So after some debugging, I figured out that this is a problem with the python timeit library. If you try the following code:

Expand
import timeit

stmt = '''\
R = 8.314

def calling_global():
    global R
    R*10

calling_global()'''

timeit.timeit(stmt)

Then it would also raise the same error. But this snippet of code does work:

Expand
import timeit


R = 8.314

def calling_global():
    global R
    R*10

timeit.timeit(calling_global)

So from this I can say for certain that the issue is with passing a string with code for a function to timeit, but passing the function itself works like expected. I wonder what can I do to fix this 🤔

The underlying code generated by timeit is this:

Expand
def inner(_it, _timer):
    pass
    _t0 = _timer()
    for _i in _it:
        R = 8.314

        def calling_global():
            global R
            R*10

        calling_global()
    _t1 = _timer()
    return _t1 - _t0

If you try to execute (inner(range(100), timeit)) then it would also raise the same error

The reason for this error is that there is no global variable R, only a local variable R that is defined inside a for-loop in the inner function. So when you're saying global R I guess it doesn't care if it even exists and tries to use a global R and since there isn't a global variable R it fails

wasi-master avatar Apr 30 '22 06:04 wasi-master

In the timeit module near the bottom of the page they have something about using a setup keyword.

def test():
    """Stupid test function"""
    L = [i for i in range(100)]

if __name__ == '__main__':
    import timeit
    print(timeit.timeit("test()", setup="from __main__ import test"))

I was about to make the example work with strings if I used this to import the function through timeit.

timeit docs

RoyLarson avatar Apr 30 '22 13:04 RoyLarson

@RoyLarson you can use setup with fastero too using the --setup / -s

Edit: Wait I misunderstood your comment, I'll think about it later, thanks.

wasi-master avatar Apr 30 '22 15:04 wasi-master