timed-decorator


Nametimed-decorator JSON
Version 1.5.1 PyPI version JSON
download
home_pageNone
SummaryA timing decorator for python functions.
upload_time2024-05-08 08:50:34
maintainerNone
docs_urlNone
authorNone
requires_python>=3.7
licenseCopyright (c) 2024 George Stoica Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
keywords timing
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # timed-decorator

Simple and configurable timing decorator with little overhead that can be attached to python functions to measure their execution time.
Can easily display parameter types and lengths if available and is compatible with NumPy ndarrays, Pandas DataFrames and PyTorch tensors.


## Installation

```
pip install --upgrade timed-decorator
```

## Usage

Attach it to the function you want to time and run the application. 


```py
from timed_decorator.simple_timed import timed


@timed()
def fibonacci(n: int) -> int:
    assert n > 0
    a, b = 0, 1
    for _ in range(n):
        a, b = b, a + b
    return a


fibonacci(10000)
# fibonacci() -> total time: 1114100ns
```

For more advanced usage, consider registering a timed decorator and using it afterward through your codebase. See [Registering a timed decorator](#registering-a-timed-decorator).

### Documentation

1. `timed`
    * `collect_gc` (`bool`): If `True`, runs a full garbage collection before timing the wrapped function. Default: `True`.
    * `disable_gc` (`bool`): If `True`, disabled garbage collection during function execution. Default: `False`.
    * `use_seconds` (`bool`): If `True`, displays the elapsed time in seconds. Default: `False`.
    * `precision` (`int`): Used in conjunction with `use_seconds`, represents the decimal points used for printing seconds. Default: `9`.
    * `show_args` (`bool`): If `True`, displays the function arguments according to `display_level`. Useful when timing function calls with arguments of different magnitude. Default: `False`.
    * `show_kwargs` (`bool`): If `True`, displays the keyword arguments according to `display_level`. Default: `False`.
    * `display_level` (`int`): The level of verbosity used when printing function arguments ad keyword arguments. If `0`, prints the type of the parameters. If `1`, prints values for all primitive types, shapes for arrays, tensors, dataframes and length for sequences. Otherwise, prints values for all parameters. Default: `1`.
    * `sep` (`str`): The separator used when printing function arguments and keyword arguments. Default: `', '`.
    * `stdout` (`bool`): If `True`, writes the elapsed time to stdout. Default: `True`.
    * `file_path` (`str`): If not `None`, writes the measurement at the end of the given file path. For thread safe file writing configure use `logger_name` instead. Default: `None`.
    * `logger_name` (`str`): If not `None`, uses the given logger to print the measurement. Can't be used in conjunction with `file_path`. Default: `None`. See [Using a logger](#using-a-logger).
    * `return_time` (`bool`): If `True`, returns the elapsed time in addition to the wrapped function's return value. Default: `False`.
    * `out` (`dict`): If not `None`, stores the elapsed time in nanoseconds in the given dict using the fully qualified function name as key, in the following format: (function call counts, total elapsed time, total "own time"). If the key already exists, updates the existing value. The elapsed time is equal to "own time" for the simple timed decorator. For the nested time decorator, the elapsed time is different from "own time" only when another function decorated with a nested timer is called during the execution of the current function. Default: `None`. See [Storing the elapsed time in a dict](#storing-the-elapsed-time-in-a-dict).
    * `use_qualname` (`bool`): If `True`, If `True`, uses the qualified name of the function when logging the elapsed time. Default: `False`.

2. `nested_timed` is similar to `timed`, however it is designed to work nicely with multiple timed functions that call each other, displaying both the total execution time and the difference after subtracting other timed functions on the same call stack. See [Nested timing decorator](#nested-timing-decorator).

3. `create_timed_decorator` registers a timed decorator with a given name. Can be enabled or disabled during creation.
   * `name` (`str`): The name of the timed decorator which will be instantiated using the provided arguments. Use this name for retrieving the timed decorator with `timed_decorator.builder.get_timed_decorator`.
   * `nested` (`bool`): If `True`, uses the `timed_decorator.nested_timed.nested_timed` as decorator, otherwise uses `timed_decorator.simple_timed.timed`. Default: `False`.
   * `enabled` (`bool`): If `True`, the timed decorator is enabled and used for timing decorated functions. Otherwise, functions decorated with `name` will not be timed. Default: `True`.
   * Also receives all the other arguments accepted by `timed` and `nested_timed`.

4. `get_timed_decorator` wraps the decorated function and lazily measures its elapsed time using the registered timed decorator. The timer can be registered after the function definition, but must be registered before the first function call. If the timer is disabled, the elapsed time will not be measured.

   * `name` (`str`): The name of the timed decorator registered using `timed_decorator.builder.create_timed_decorator`.


### Examples

Simple usage.
```py
from timed_decorator.simple_timed import timed


@timed()
def fibonacci(n: int) -> int:
    assert n > 0
    a, b = 0, 1
    for _ in range(n):
        a, b = b, a + b
    return a


fibonacci(10000)
# fibonacci() -> total time: 1114100ns
```

Getting both the function's return value and the elapsed time.
```py
from timed_decorator.simple_timed import timed


@timed(return_time=True)
def fibonacci(n: int) -> int:
    assert n > 0
    a, b = 0, 1
    for _ in range(n):
        a, b = b, a + b
    return a


value, elapsed = fibonacci(10000)
print(f'10000th fibonacci number has {len(str(value))} digits. Calculating it took {elapsed}ns.')
# fibonacci() -> total time: 1001200ns
# 10000th fibonacci number has 2090 digits. Calculating it took 1001200ns.
```

Set `collect_gc=False` to disable pre-collection of garbage.

```py
from timed_decorator.simple_timed import timed


@timed(collect_gc=False)
def fibonacci(n: int) -> int:
    assert n > 0
    a, b = 0, 1
    for _ in range(n):
        a, b = b, a + b
    return a


fibonacci(10000)
# fibonacci() -> total time: 1062400ns
```

Using seconds instead of nanoseconds. 
```py
from timed_decorator.simple_timed import timed


@timed(disable_gc=True, use_seconds=True, precision=3)
def call_recursive_fibonacci(n: int) -> int:
    return recursive_fibonacci(n)


def recursive_fibonacci(n: int) -> int:
    assert n > 0
    if n > 3:
        return recursive_fibonacci(n - 1) + recursive_fibonacci(n - 2)
    if n == 1:
        return 0
    return 1


call_recursive_fibonacci(30)
# call_recursive_fibonacci() -> total time: 0.045s
```

Displaying function parameters:
```py
from timed_decorator.simple_timed import timed
import numpy as np


@timed(show_args=True, display_level=0)
def numpy_operation(array_list, single_array, inplace=False, aggregate='mean', weights=None):
    x = np.array(array_list)
    if weights is not None:
        x = (x.T * weights).T

    if aggregate == 'mean':
        x = x.mean(axis=0)
    else:
        x = x.sum(axis=0)

    if inplace:
        single_array += x
        return single_array
    else:
        other_array = single_array + x
        return other_array


numpy_operation(
    [np.random.rand(2, 3) for _ in range(10)],
    np.random.rand(2, 3),
    weights=[i / 10 for i in range(10)],
    inplace=True
)
# numpy_operation(list, ndarray) -> total time: 204200ns
```

Using the default display level (1).

```py
from timed_decorator.simple_timed import timed
import numpy as np


@timed(show_args=True)
def numpy_operation(array_list, single_array, inplace=False, aggregate='mean', weights=None):
    x = np.array(array_list)
    if weights is not None:
        x = (x.T * weights).T

    if aggregate == 'mean':
        x = x.mean(axis=0)
    else:
        x = x.sum(axis=0)

    if inplace:
        single_array += x
        return single_array
    else:
        other_array = single_array + x
        return other_array


numpy_operation(
    [np.random.rand(2, 3) for _ in range(10)],
    np.random.rand(2, 3),
    weights=[i / 10 for i in range(10)],
    inplace=True,
    aggregate='sum'
)
# numpy_operation(list(ndarray)[10], ndarray(2, 3)) -> total time: 166400ns
```

Showing the keyword arguments.

```py
from timed_decorator.simple_timed import timed
import numpy as np


@timed(show_args=True, show_kwargs=True)
def numpy_operation(array_list, single_array, inplace=False, aggregate='mean', weights=None):
    x = np.array(array_list)
    if weights is not None:
        x = (x.T * weights).T

    if aggregate == 'mean':
        x = x.mean(axis=0)
    else:
        x = x.sum(axis=0)

    if inplace:
        single_array += x
        return single_array
    else:
        other_array = single_array + x
        return other_array


numpy_operation(
    [np.random.rand(2, 3) for _ in range(10)],
    np.random.rand(2, 3),
    weights=[i / 10 for i in range(10)],
    inplace=True,
    aggregate='sum'
)
# numpy_operation(list(ndarray)[10], ndarray(2, 3), ('weights', 'list(float)[10]'), ('inplace', 'True'), ('aggregate', 'sum')) -> total time: 166400ns
```

Not recommended: using display level 2 shows unformatted function arguments.

```py
from timed_decorator.simple_timed import timed
import numpy as np


@timed(show_args=True, show_kwargs=True, display_level=2)
def numpy_operation(array_list, single_array, inplace=False, aggregate='mean', weights=None):
    x = np.array(array_list)
    if weights is not None:
        x = (x.T * weights).T

    if aggregate == 'mean':
        x = x.mean(axis=0)
    else:
        x = x.sum(axis=0)

    if inplace:
        single_array += x
        return single_array
    else:
        other_array = single_array + x
        return other_array


numpy_operation(
    [np.random.rand(1, 3) for _ in range(1)],
    np.random.rand(1, 3),
    weights=[i / 10 for i in range(1)],
    inplace=True
)
# numpy_operation([array([[0.74500602, 0.70666224, 0.83888559]])], [[0.74579988 0.51878032 0.06419635]], ('weights', '[0.0]'), ('inplace', 'True')) -> total time: 185300ns
```

Using the fully qualified name for timing. 

```py
from time import sleep
from timed_decorator.simple_timed import timed
class ClassA:
    @timed(use_qualname=True)
    def wait(self, x):
        sleep(x)
ClassA().wait(0.1)
# ClassA.wait() -> total time: 100943000ns
```

### Nested timing decorator

```py
from time import sleep

from timed_decorator.nested_timed import nested_timed


@nested_timed()
def nested_fn():
    @nested_timed()
    def sleeping_fn(x):
        sleep(x)

    @nested_timed()
    def other_fn():
        sleep(0.5)
        sleeping_fn(0.5)

    sleep(1)
    sleeping_fn(1)
    other_fn()
    sleeping_fn(1)


nested_fn()
```
Prints
```
        sleeping_fn() -> total time: 1000592700ns, own time: 1000592700ns
                sleeping_fn() -> total time: 500687200ns, own time: 500687200ns
        other_fn() -> total time: 1036725800ns, own time: 536038600ns
        sleeping_fn() -> total time: 1000705600ns, own time: 1000705600ns
nested_fn() -> total time: 4152634300ns, own time: 1114610200ns
```

### Using a logger
```py
import logging
from time import sleep

from timed_decorator.simple_timed import timed

logging.basicConfig()
logging.root.setLevel(logging.NOTSET)


@timed(logger_name='TEST_LOGGER', stdout=False)
def fn():
    sleep(1)


fn()
fn()
```
Prints
```
INFO:TEST_LOGGER:fn() -> total time: 1000368900ns
INFO:TEST_LOGGER:fn() -> total time: 1001000200ns
```

Capture a logger's input
```py
import logging
from io import StringIO
from time import sleep

from timed_decorator.simple_timed import timed

log_stream = StringIO()
log_handler = logging.StreamHandler(log_stream)
logging.root.setLevel(logging.NOTSET)
logging.getLogger('TEST_LOGGER').addHandler(log_handler)


@timed(logger_name='TEST_LOGGER', stdout=False)
def fn():
    sleep(1)


fn()
fn()

print(log_stream.getvalue().split('\n')[:-1])
```
Prints
```
['fn() -> total time: 1000214700ns', 'fn() -> total time: 1000157800ns']
```

### Storing the elapsed time in a dict
```py
from time import sleep

from timed_decorator.simple_timed import timed

ns = {}


@timed(out=ns, stdout=False)
def fn():
    sleep(1)


fn()
print(ns)
fn()
print(ns)
```
Prints
```
{'fn': [1, 1000672000, 1000672000]}
{'fn': [2, 2001306900, 2001306900]}
```

### Compatible with PyTorch tensors

Synchronizes cuda device when cuda tensors are passed as function parameters.

```py
import torch
from torch import Tensor

from timed_decorator.simple_timed import timed


@timed(show_args=True)
def batched_euclidean_distance(x: Tensor, y: Tensor) -> Tensor:
    diff = x @ y.T
    x_squared = (x ** 2).sum(dim=1)
    y_squared = (b ** 2).sum(dim=1)
    return x_squared.unsqueeze(-1) + y_squared.unsqueeze(0) - 2 * diff


a = torch.rand((10000, 800))
b = torch.rand((12000, 800))
batched_euclidean_distance(a, b)

if torch.cuda.is_available():
    a = a.cuda()
    b = b.cuda()
    batched_euclidean_distance(a, b)  # Cuda device is synchronized if function arguments are on device.
```
Prints:
```
batched_euclidean_distance(CpuTensor[10000, 800], CpuTensor[12000, 800]) -> total time: 685659400ns
batched_euclidean_distance(CudaTensor[10000, 800], CudaTensor[12000, 800]) -> total time: 260411900ns
```


### Registering a timed decorator

```py
from time import sleep

from timed_decorator.builder import create_timed_decorator, get_timed_decorator


@get_timed_decorator("MyCustomTimer")
def main():
    @get_timed_decorator("MyCustomTimer")
    def function_1():
        sleep(0.1)

    @get_timed_decorator("MyCustomTimer")
    def nested_function():
        @get_timed_decorator("MyCustomTimer")
        def function_2():
            sleep(0.2)

        @get_timed_decorator("MyCustomTimer")
        def function_3():
            sleep(0.3)

        function_2()
        function_2()
        function_3()

    nested_function()
    function_1()
    nested_function()
    function_1()


if __name__ == '__main__':
    my_measurements = {}
    create_timed_decorator("MyCustomTimer",
                           nested=False,  # This is true by default
                           collect_gc=False,  # I don't want to explicitly collect garbage
                           disable_gc=True,  # I don't want to wait for garbage collection during measuring
                           stdout=False,  # I don't wat to print stuff to console
                           out=my_measurements  # My measurements dict
                           )
    main()
    for key, (counts, elapsed, own_time) in my_measurements.items():
        print(f'Function {key} was called {counts} time(s) and took {elapsed / 1e+9}s')
    print()

    # Now I can do stuff with my measurements.
    functions = sorted(my_measurements.keys(), reverse=True)

    for i in range(len(functions)):
        fn_1 = functions[i]
        print(f'Function {fn_1}:')
        for j in range(i + 1, len(functions)):
            fn_2 = functions[j]
            if fn_1.startswith(fn_2):
                _, elapsed_1, _ = my_measurements[fn_1]
                _, elapsed_2, _ = my_measurements[fn_2]
                ratio = elapsed_1 / elapsed_2 * 100
                print(f'* took {ratio:.2f}% from {fn_2}')
        print()
```

Prints:
```
Function main.<locals>.nested_function.<locals>.function_2 was called 4 time(s) and took 0.8019482s
Function main.<locals>.nested_function.<locals>.function_3 was called 2 time(s) and took 0.6010157s
Function main.<locals>.nested_function was called 2 time(s) and took 1.403365s
Function main.<locals>.function_1 was called 2 time(s) and took 0.2007625s
Function main was called 1 time(s) and took 1.6043592s

Function main.<locals>.nested_function.<locals>.function_3:
* took 42.83% from main.<locals>.nested_function
* took 37.46% from main

Function main.<locals>.nested_function.<locals>.function_2:
* took 57.14% from main.<locals>.nested_function
* took 49.99% from main

Function main.<locals>.nested_function:
* took 87.47% from main

Function main.<locals>.function_1:
* took 12.51% from main

Function main:

```

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "timed-decorator",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.7",
    "maintainer_email": "George Stoica <george.stoica@senticlab.com>",
    "keywords": "timing",
    "author": null,
    "author_email": "George Stoica <george.stoica@senticlab.com>",
    "download_url": "https://files.pythonhosted.org/packages/f5/4d/e6fc276412705901eeec31d77acdc46264af4f873995bcfb26dba54746f5/timed_decorator-1.5.1.tar.gz",
    "platform": null,
    "description": "# timed-decorator\n\nSimple and configurable timing decorator with little overhead that can be attached to python functions to measure their execution time.\nCan easily display parameter types and lengths if available and is compatible with NumPy ndarrays, Pandas DataFrames and PyTorch tensors.\n\n\n## Installation\n\n```\npip install --upgrade timed-decorator\n```\n\n## Usage\n\nAttach it to the function you want to time and run the application. \n\n\n```py\nfrom timed_decorator.simple_timed import timed\n\n\n@timed()\ndef fibonacci(n: int) -> int:\n    assert n > 0\n    a, b = 0, 1\n    for _ in range(n):\n        a, b = b, a + b\n    return a\n\n\nfibonacci(10000)\n# fibonacci() -> total time: 1114100ns\n```\n\nFor more advanced usage, consider registering a timed decorator and using it afterward through your codebase. See [Registering a timed decorator](#registering-a-timed-decorator).\n\n### Documentation\n\n1. `timed`\n    * `collect_gc` (`bool`): If `True`, runs a full garbage collection before timing the wrapped function. Default: `True`.\n    * `disable_gc` (`bool`): If `True`, disabled garbage collection during function execution. Default: `False`.\n    * `use_seconds` (`bool`): If `True`, displays the elapsed time in seconds. Default: `False`.\n    * `precision` (`int`): Used in conjunction with `use_seconds`, represents the decimal points used for printing seconds. Default: `9`.\n    * `show_args` (`bool`): If `True`, displays the function arguments according to `display_level`. Useful when timing function calls with arguments of different magnitude. Default: `False`.\n    * `show_kwargs` (`bool`): If `True`, displays the keyword arguments according to `display_level`. Default: `False`.\n    * `display_level` (`int`): The level of verbosity used when printing function arguments ad keyword arguments. If `0`, prints the type of the parameters. If `1`, prints values for all primitive types, shapes for arrays, tensors, dataframes and length for sequences. Otherwise, prints values for all parameters. Default: `1`.\n    * `sep` (`str`): The separator used when printing function arguments and keyword arguments. Default: `', '`.\n    * `stdout` (`bool`): If `True`, writes the elapsed time to stdout. Default: `True`.\n    * `file_path` (`str`): If not `None`, writes the measurement at the end of the given file path. For thread safe file writing configure use `logger_name` instead. Default: `None`.\n    * `logger_name` (`str`): If not `None`, uses the given logger to print the measurement. Can't be used in conjunction with `file_path`. Default: `None`. See [Using a logger](#using-a-logger).\n    * `return_time` (`bool`): If `True`, returns the elapsed time in addition to the wrapped function's return value. Default: `False`.\n    * `out` (`dict`): If not `None`, stores the elapsed time in nanoseconds in the given dict using the fully qualified function name as key, in the following format: (function call counts, total elapsed time, total \"own time\"). If the key already exists, updates the existing value. The elapsed time is equal to \"own time\" for the simple timed decorator. For the nested time decorator, the elapsed time is different from \"own time\" only when another function decorated with a nested timer is called during the execution of the current function. Default: `None`. See [Storing the elapsed time in a dict](#storing-the-elapsed-time-in-a-dict).\n    * `use_qualname` (`bool`): If `True`, If `True`, uses the qualified name of the function when logging the elapsed time. Default: `False`.\n\n2. `nested_timed` is similar to `timed`, however it is designed to work nicely with multiple timed functions that call each other, displaying both the total execution time and the difference after subtracting other timed functions on the same call stack. See [Nested timing decorator](#nested-timing-decorator).\n\n3. `create_timed_decorator` registers a timed decorator with a given name. Can be enabled or disabled during creation.\n   * `name` (`str`): The name of the timed decorator which will be instantiated using the provided arguments. Use this name for retrieving the timed decorator with `timed_decorator.builder.get_timed_decorator`.\n   * `nested` (`bool`): If `True`, uses the `timed_decorator.nested_timed.nested_timed` as decorator, otherwise uses `timed_decorator.simple_timed.timed`. Default: `False`.\n   * `enabled` (`bool`): If `True`, the timed decorator is enabled and used for timing decorated functions. Otherwise, functions decorated with `name` will not be timed. Default: `True`.\n   * Also receives all the other arguments accepted by `timed` and `nested_timed`.\n\n4. `get_timed_decorator` wraps the decorated function and lazily measures its elapsed time using the registered timed decorator. The timer can be registered after the function definition, but must be registered before the first function call. If the timer is disabled, the elapsed time will not be measured.\n\n   * `name` (`str`): The name of the timed decorator registered using `timed_decorator.builder.create_timed_decorator`.\n\n\n### Examples\n\nSimple usage.\n```py\nfrom timed_decorator.simple_timed import timed\n\n\n@timed()\ndef fibonacci(n: int) -> int:\n    assert n > 0\n    a, b = 0, 1\n    for _ in range(n):\n        a, b = b, a + b\n    return a\n\n\nfibonacci(10000)\n# fibonacci() -> total time: 1114100ns\n```\n\nGetting both the function's return value and the elapsed time.\n```py\nfrom timed_decorator.simple_timed import timed\n\n\n@timed(return_time=True)\ndef fibonacci(n: int) -> int:\n    assert n > 0\n    a, b = 0, 1\n    for _ in range(n):\n        a, b = b, a + b\n    return a\n\n\nvalue, elapsed = fibonacci(10000)\nprint(f'10000th fibonacci number has {len(str(value))} digits. Calculating it took {elapsed}ns.')\n# fibonacci() -> total time: 1001200ns\n# 10000th fibonacci number has 2090 digits. Calculating it took 1001200ns.\n```\n\nSet `collect_gc=False` to disable pre-collection of garbage.\n\n```py\nfrom timed_decorator.simple_timed import timed\n\n\n@timed(collect_gc=False)\ndef fibonacci(n: int) -> int:\n    assert n > 0\n    a, b = 0, 1\n    for _ in range(n):\n        a, b = b, a + b\n    return a\n\n\nfibonacci(10000)\n# fibonacci() -> total time: 1062400ns\n```\n\nUsing seconds instead of nanoseconds. \n```py\nfrom timed_decorator.simple_timed import timed\n\n\n@timed(disable_gc=True, use_seconds=True, precision=3)\ndef call_recursive_fibonacci(n: int) -> int:\n    return recursive_fibonacci(n)\n\n\ndef recursive_fibonacci(n: int) -> int:\n    assert n > 0\n    if n > 3:\n        return recursive_fibonacci(n - 1) + recursive_fibonacci(n - 2)\n    if n == 1:\n        return 0\n    return 1\n\n\ncall_recursive_fibonacci(30)\n# call_recursive_fibonacci() -> total time: 0.045s\n```\n\nDisplaying function parameters:\n```py\nfrom timed_decorator.simple_timed import timed\nimport numpy as np\n\n\n@timed(show_args=True, display_level=0)\ndef numpy_operation(array_list, single_array, inplace=False, aggregate='mean', weights=None):\n    x = np.array(array_list)\n    if weights is not None:\n        x = (x.T * weights).T\n\n    if aggregate == 'mean':\n        x = x.mean(axis=0)\n    else:\n        x = x.sum(axis=0)\n\n    if inplace:\n        single_array += x\n        return single_array\n    else:\n        other_array = single_array + x\n        return other_array\n\n\nnumpy_operation(\n    [np.random.rand(2, 3) for _ in range(10)],\n    np.random.rand(2, 3),\n    weights=[i / 10 for i in range(10)],\n    inplace=True\n)\n# numpy_operation(list, ndarray) -> total time: 204200ns\n```\n\nUsing the default display level (1).\n\n```py\nfrom timed_decorator.simple_timed import timed\nimport numpy as np\n\n\n@timed(show_args=True)\ndef numpy_operation(array_list, single_array, inplace=False, aggregate='mean', weights=None):\n    x = np.array(array_list)\n    if weights is not None:\n        x = (x.T * weights).T\n\n    if aggregate == 'mean':\n        x = x.mean(axis=0)\n    else:\n        x = x.sum(axis=0)\n\n    if inplace:\n        single_array += x\n        return single_array\n    else:\n        other_array = single_array + x\n        return other_array\n\n\nnumpy_operation(\n    [np.random.rand(2, 3) for _ in range(10)],\n    np.random.rand(2, 3),\n    weights=[i / 10 for i in range(10)],\n    inplace=True,\n    aggregate='sum'\n)\n# numpy_operation(list(ndarray)[10], ndarray(2, 3)) -> total time: 166400ns\n```\n\nShowing the keyword arguments.\n\n```py\nfrom timed_decorator.simple_timed import timed\nimport numpy as np\n\n\n@timed(show_args=True, show_kwargs=True)\ndef numpy_operation(array_list, single_array, inplace=False, aggregate='mean', weights=None):\n    x = np.array(array_list)\n    if weights is not None:\n        x = (x.T * weights).T\n\n    if aggregate == 'mean':\n        x = x.mean(axis=0)\n    else:\n        x = x.sum(axis=0)\n\n    if inplace:\n        single_array += x\n        return single_array\n    else:\n        other_array = single_array + x\n        return other_array\n\n\nnumpy_operation(\n    [np.random.rand(2, 3) for _ in range(10)],\n    np.random.rand(2, 3),\n    weights=[i / 10 for i in range(10)],\n    inplace=True,\n    aggregate='sum'\n)\n# numpy_operation(list(ndarray)[10], ndarray(2, 3), ('weights', 'list(float)[10]'), ('inplace', 'True'), ('aggregate', 'sum')) -> total time: 166400ns\n```\n\nNot recommended: using display level 2 shows unformatted function arguments.\n\n```py\nfrom timed_decorator.simple_timed import timed\nimport numpy as np\n\n\n@timed(show_args=True, show_kwargs=True, display_level=2)\ndef numpy_operation(array_list, single_array, inplace=False, aggregate='mean', weights=None):\n    x = np.array(array_list)\n    if weights is not None:\n        x = (x.T * weights).T\n\n    if aggregate == 'mean':\n        x = x.mean(axis=0)\n    else:\n        x = x.sum(axis=0)\n\n    if inplace:\n        single_array += x\n        return single_array\n    else:\n        other_array = single_array + x\n        return other_array\n\n\nnumpy_operation(\n    [np.random.rand(1, 3) for _ in range(1)],\n    np.random.rand(1, 3),\n    weights=[i / 10 for i in range(1)],\n    inplace=True\n)\n# numpy_operation([array([[0.74500602, 0.70666224, 0.83888559]])], [[0.74579988 0.51878032 0.06419635]], ('weights', '[0.0]'), ('inplace', 'True')) -> total time: 185300ns\n```\n\nUsing the fully qualified name for timing. \n\n```py\nfrom time import sleep\nfrom timed_decorator.simple_timed import timed\nclass ClassA:\n    @timed(use_qualname=True)\n    def wait(self, x):\n        sleep(x)\nClassA().wait(0.1)\n# ClassA.wait() -> total time: 100943000ns\n```\n\n### Nested timing decorator\n\n```py\nfrom time import sleep\n\nfrom timed_decorator.nested_timed import nested_timed\n\n\n@nested_timed()\ndef nested_fn():\n    @nested_timed()\n    def sleeping_fn(x):\n        sleep(x)\n\n    @nested_timed()\n    def other_fn():\n        sleep(0.5)\n        sleeping_fn(0.5)\n\n    sleep(1)\n    sleeping_fn(1)\n    other_fn()\n    sleeping_fn(1)\n\n\nnested_fn()\n```\nPrints\n```\n        sleeping_fn() -> total time: 1000592700ns, own time: 1000592700ns\n                sleeping_fn() -> total time: 500687200ns, own time: 500687200ns\n        other_fn() -> total time: 1036725800ns, own time: 536038600ns\n        sleeping_fn() -> total time: 1000705600ns, own time: 1000705600ns\nnested_fn() -> total time: 4152634300ns, own time: 1114610200ns\n```\n\n### Using a logger\n```py\nimport logging\nfrom time import sleep\n\nfrom timed_decorator.simple_timed import timed\n\nlogging.basicConfig()\nlogging.root.setLevel(logging.NOTSET)\n\n\n@timed(logger_name='TEST_LOGGER', stdout=False)\ndef fn():\n    sleep(1)\n\n\nfn()\nfn()\n```\nPrints\n```\nINFO:TEST_LOGGER:fn() -> total time: 1000368900ns\nINFO:TEST_LOGGER:fn() -> total time: 1001000200ns\n```\n\nCapture a logger's input\n```py\nimport logging\nfrom io import StringIO\nfrom time import sleep\n\nfrom timed_decorator.simple_timed import timed\n\nlog_stream = StringIO()\nlog_handler = logging.StreamHandler(log_stream)\nlogging.root.setLevel(logging.NOTSET)\nlogging.getLogger('TEST_LOGGER').addHandler(log_handler)\n\n\n@timed(logger_name='TEST_LOGGER', stdout=False)\ndef fn():\n    sleep(1)\n\n\nfn()\nfn()\n\nprint(log_stream.getvalue().split('\\n')[:-1])\n```\nPrints\n```\n['fn() -> total time: 1000214700ns', 'fn() -> total time: 1000157800ns']\n```\n\n### Storing the elapsed time in a dict\n```py\nfrom time import sleep\n\nfrom timed_decorator.simple_timed import timed\n\nns = {}\n\n\n@timed(out=ns, stdout=False)\ndef fn():\n    sleep(1)\n\n\nfn()\nprint(ns)\nfn()\nprint(ns)\n```\nPrints\n```\n{'fn': [1, 1000672000, 1000672000]}\n{'fn': [2, 2001306900, 2001306900]}\n```\n\n### Compatible with PyTorch tensors\n\nSynchronizes cuda device when cuda tensors are passed as function parameters.\n\n```py\nimport torch\nfrom torch import Tensor\n\nfrom timed_decorator.simple_timed import timed\n\n\n@timed(show_args=True)\ndef batched_euclidean_distance(x: Tensor, y: Tensor) -> Tensor:\n    diff = x @ y.T\n    x_squared = (x ** 2).sum(dim=1)\n    y_squared = (b ** 2).sum(dim=1)\n    return x_squared.unsqueeze(-1) + y_squared.unsqueeze(0) - 2 * diff\n\n\na = torch.rand((10000, 800))\nb = torch.rand((12000, 800))\nbatched_euclidean_distance(a, b)\n\nif torch.cuda.is_available():\n    a = a.cuda()\n    b = b.cuda()\n    batched_euclidean_distance(a, b)  # Cuda device is synchronized if function arguments are on device.\n```\nPrints:\n```\nbatched_euclidean_distance(CpuTensor[10000, 800], CpuTensor[12000, 800]) -> total time: 685659400ns\nbatched_euclidean_distance(CudaTensor[10000, 800], CudaTensor[12000, 800]) -> total time: 260411900ns\n```\n\n\n### Registering a timed decorator\n\n```py\nfrom time import sleep\n\nfrom timed_decorator.builder import create_timed_decorator, get_timed_decorator\n\n\n@get_timed_decorator(\"MyCustomTimer\")\ndef main():\n    @get_timed_decorator(\"MyCustomTimer\")\n    def function_1():\n        sleep(0.1)\n\n    @get_timed_decorator(\"MyCustomTimer\")\n    def nested_function():\n        @get_timed_decorator(\"MyCustomTimer\")\n        def function_2():\n            sleep(0.2)\n\n        @get_timed_decorator(\"MyCustomTimer\")\n        def function_3():\n            sleep(0.3)\n\n        function_2()\n        function_2()\n        function_3()\n\n    nested_function()\n    function_1()\n    nested_function()\n    function_1()\n\n\nif __name__ == '__main__':\n    my_measurements = {}\n    create_timed_decorator(\"MyCustomTimer\",\n                           nested=False,  # This is true by default\n                           collect_gc=False,  # I don't want to explicitly collect garbage\n                           disable_gc=True,  # I don't want to wait for garbage collection during measuring\n                           stdout=False,  # I don't wat to print stuff to console\n                           out=my_measurements  # My measurements dict\n                           )\n    main()\n    for key, (counts, elapsed, own_time) in my_measurements.items():\n        print(f'Function {key} was called {counts} time(s) and took {elapsed / 1e+9}s')\n    print()\n\n    # Now I can do stuff with my measurements.\n    functions = sorted(my_measurements.keys(), reverse=True)\n\n    for i in range(len(functions)):\n        fn_1 = functions[i]\n        print(f'Function {fn_1}:')\n        for j in range(i + 1, len(functions)):\n            fn_2 = functions[j]\n            if fn_1.startswith(fn_2):\n                _, elapsed_1, _ = my_measurements[fn_1]\n                _, elapsed_2, _ = my_measurements[fn_2]\n                ratio = elapsed_1 / elapsed_2 * 100\n                print(f'* took {ratio:.2f}% from {fn_2}')\n        print()\n```\n\nPrints:\n```\nFunction main.<locals>.nested_function.<locals>.function_2 was called 4 time(s) and took 0.8019482s\nFunction main.<locals>.nested_function.<locals>.function_3 was called 2 time(s) and took 0.6010157s\nFunction main.<locals>.nested_function was called 2 time(s) and took 1.403365s\nFunction main.<locals>.function_1 was called 2 time(s) and took 0.2007625s\nFunction main was called 1 time(s) and took 1.6043592s\n\nFunction main.<locals>.nested_function.<locals>.function_3:\n* took 42.83% from main.<locals>.nested_function\n* took 37.46% from main\n\nFunction main.<locals>.nested_function.<locals>.function_2:\n* took 57.14% from main.<locals>.nested_function\n* took 49.99% from main\n\nFunction main.<locals>.nested_function:\n* took 87.47% from main\n\nFunction main.<locals>.function_1:\n* took 12.51% from main\n\nFunction main:\n\n```\n",
    "bugtrack_url": null,
    "license": "Copyright (c) 2024 George Stoica  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ",
    "summary": "A timing decorator for python functions.",
    "version": "1.5.1",
    "project_urls": {
        "Issues": "https://github.com/ancestor-mithril/timed-decorator/issues",
        "Repository": "https://github.com/ancestor-mithril/timed-decorator"
    },
    "split_keywords": [
        "timing"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "405e3a43a6807a22b976238cbd6b7d88369647567eca3cdbefee2935373a8e13",
                "md5": "750f3c105c22a47ba1cbc6f89c7cf0de",
                "sha256": "b873d2957eb8a3f34df204dd745498d7eea87c60c0588274a4611efb3fb5838d"
            },
            "downloads": -1,
            "filename": "timed_decorator-1.5.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "750f3c105c22a47ba1cbc6f89c7cf0de",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.7",
            "size": 12605,
            "upload_time": "2024-05-08T08:50:33",
            "upload_time_iso_8601": "2024-05-08T08:50:33.302692Z",
            "url": "https://files.pythonhosted.org/packages/40/5e/3a43a6807a22b976238cbd6b7d88369647567eca3cdbefee2935373a8e13/timed_decorator-1.5.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "f54de6fc276412705901eeec31d77acdc46264af4f873995bcfb26dba54746f5",
                "md5": "d88da129b36b71fca3ec78fffe50c41d",
                "sha256": "66a45f38bf80e8f3ab97ac4e61dc28341f535561077cc74054f3568b6cc1ad01"
            },
            "downloads": -1,
            "filename": "timed_decorator-1.5.1.tar.gz",
            "has_sig": false,
            "md5_digest": "d88da129b36b71fca3ec78fffe50c41d",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.7",
            "size": 15662,
            "upload_time": "2024-05-08T08:50:34",
            "upload_time_iso_8601": "2024-05-08T08:50:34.411791Z",
            "url": "https://files.pythonhosted.org/packages/f5/4d/e6fc276412705901eeec31d77acdc46264af4f873995bcfb26dba54746f5/timed_decorator-1.5.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-05-08 08:50:34",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "ancestor-mithril",
    "github_project": "timed-decorator",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "timed-decorator"
}
        
Elapsed time: 0.38206s