use this function as a profiler for both functions and generators
to see how many iterations or cycles they can run per second 

Example usage for timing simple operations/functions:

``` 
    print(runs_per_second(lambda:1+2))
    # 2074558
    print(runs_per_second(lambda:1-2))
    # 2048523
    print(runs_per_second(lambda:1/2))
    # 2075186
    print(runs_per_second(lambda:1*2))
    # 2101722
    print(runs_per_second(lambda:1**2))
    # 2104572
```

Example usage for timing iteration speed of generators:
  
``` 
    def counter():
        c = 0
        while 1:
            yield c
            c+=1

    print(runs_per_second(counter()))
    # 1697328
    print(runs_per_second((i for i in range(2000))))
    # 1591301
```

def runs_per_second(generator, seconds=3):
    ''' 
use this function as a profiler for both functions and generators
to see how many iterations or cycles they can run per second 

Example usage for timing simple operations/functions:

``` 
    print(runs_per_second(lambda:1+2))
    # 2074558
    print(runs_per_second(lambda:1-2))
    # 2048523
    print(runs_per_second(lambda:1/2))
    # 2075186
    print(runs_per_second(lambda:1*2))
    # 2101722
    print(runs_per_second(lambda:1**2))
    # 2104572
```

Example usage for timing iteration speed of generators:
  
``` 
    def counter():
        c = 0
        while 1:
            yield c
            c+=1

    print(runs_per_second(counter()))
    # 1697328
    print(runs_per_second((i for i in range(2000))))
    # 1591301
``` 
'''
    assert isinstance(seconds, int), 'runs_per_second needs seconds to be an int, not {}'.format(repr(seconds))
    assert seconds>0, 'runs_per_second needs seconds to be positive, not {}'.format(repr(seconds))
    # if generator is a function, turn it into a generator for testing
    if callable(generator) and not any(i in ('next', '__next__', '__iter__') for i in dir(generator)):
        try:
            # get the output of the function
            output = generator()
        except:
            # if the function crashes without any arguments
            raise Exception('runs_per_second needs a working function that accepts no arguments')
        else:
            # this usage of iter infinitely calls a function until the second argument is the output
            # so I set the second argument to something that isnt what output was.
            generator = iter(generator, (1 if output is None else None))
            del output
    c=0  # run counter, keep this one short for performance reasons
    entire_test_time_used = False
    start = ts()
    end = start+seconds
    for _ in generator:
        if ts()>end:
            entire_test_time_used = True
            break
        else:
            c += 1
    duration = (ts())-start  # the ( ) around ts ensures that it will be the first thing calculated
    return int(c/(seconds if entire_test_time_used else duration))