⚡️ Speed up function _format_method by 7%
#609
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
📄 7% (0.07x) speedup for
_format_methodinmarimo/_plugins/stateless/inspect.py⏱️ Runtime :
22.8 milliseconds→21.3 milliseconds(best of77runs)📝 Explanation and details
The optimization adds LRU caching to the
inspect.signature()call, which is the most expensive operation in this function.Key Changes:
@lru_cache(maxsize=128)decorator to a new_cached_signature()function that wrapsinspect.signature()inspect_.signature(method)call with_cached_signature(method)Why This Improves Performance:
The
inspect.signature()function is computationally expensive as it needs to analyze function metadata, parse parameter information, and construct signature objects. From the line profiler, this operation takes 73.1% of the total runtime (76.2ms out of 104.3ms). By caching these results, repeated calls for the same method objects avoid this expensive computation entirely.Performance Impact:
Test Case Analysis:
Most individual test cases show slight slowdowns (3-12%) due to cache overhead on first calls, but the large-scale tests demonstrate the optimization's value:
test_large_batch_with_various_types: 188% faster (4.01ms → 1.39ms) - shows dramatic improvement when the same objects are processed repeatedlyThis optimization is particularly beneficial when
_format_methodis called repeatedly on the same set of methods, which is common in introspection scenarios like IDE tooltips, documentation generation, or interactive debugging tools.✅ Correctness verification report:
🌀 Generated Regression Tests and Runtime
from future import annotations
import inspect
import inspect as inspect_
imports
import pytest # used for our unit tests
from marimo._plugins.stateless.inspect import _format_method
unit tests
--- Basic Test Cases ---
def test_basic_sync_function_no_doc():
# Simple function, no docstring
def foo(a, b):
return a + b
codeflash_output = _format_method("foo", foo, docs=False); result = codeflash_output # 21.3μs -> 23.5μs (9.04% slower)
def test_basic_sync_function_with_doc():
# Simple function, with docstring
def bar(x):
"""Adds one to x."""
return x + 1
codeflash_output = _format_method("bar", bar, docs=True); result = codeflash_output # 21.5μs -> 23.2μs (7.56% slower)
def test_basic_async_function_no_doc():
# Simple async function, no docstring
async def baz(y):
return y * 2
codeflash_output = _format_method("baz", baz, docs=False); result = codeflash_output # 18.2μs -> 19.7μs (7.88% slower)
def test_basic_async_function_with_doc():
# Async function, with docstring
async def qux(z):
"""Doubles z asynchronously."""
return z * 2
codeflash_output = _format_method("qux", qux, docs=True); result = codeflash_output # 20.6μs -> 22.7μs (9.26% slower)
def test_function_with_default_args():
# Function with default arguments
def foo(a, b=10):
return a + b
codeflash_output = _format_method("foo", foo, docs=False); result = codeflash_output # 22.2μs -> 23.6μs (5.71% slower)
def test_function_with_varargs_kwargs():
# Function with *args and **kwargs
def bar(*args, **kwargs):
pass
codeflash_output = _format_method("bar", bar, docs=False); result = codeflash_output # 21.6μs -> 22.6μs (4.72% slower)
def test_method_of_class():
# Method of a class
class MyClass:
def method(self, x):
"""Method doc."""
return x
codeflash_output = _format_method("method", MyClass.method, docs=True); result = codeflash_output # 24.2μs -> 25.1μs (3.62% slower)
def test_static_method_of_class():
# Static method of a class
class MyClass:
@staticmethod
def smethod(x):
"""Static method doc."""
return x
codeflash_output = _format_method("smethod", MyClass.smethod, docs=True); result = codeflash_output # 21.4μs -> 22.8μs (6.43% slower)
def test_class_method_of_class():
# Class method of a class
class MyClass:
@classmethod
def cmethod(cls, y):
"""Class method doc."""
return y
codeflash_output = _format_method("cmethod", MyClass.cmethod, docs=True); result = codeflash_output # 28.8μs -> 31.3μs (8.07% slower)
--- Edge Test Cases ---
def test_function_no_args():
# Function with no arguments
def foo():
pass
codeflash_output = _format_method("foo", foo, docs=False); result = codeflash_output # 14.4μs -> 15.4μs (6.81% slower)
def test_function_with_annotations():
# Function with type annotations
def bar(x: int, y: str = "test") -> bool:
return str(x) == y
codeflash_output = _format_method("bar", bar, docs=False); result = codeflash_output # 26.0μs -> 27.4μs (5.06% slower)
def test_function_with_long_docstring():
# Function with a long docstring (>80 chars)
def foo(a):
"""This is a very long docstring that exceeds eighty characters and should be truncated by the function."""
return a
codeflash_output = _format_method("foo", foo, docs=True); result = codeflash_output # 21.7μs -> 23.1μs (6.04% slower)
# The first line is truncated to 77 chars + "..."
expected = "def foo(a): This is a very long docstring that exceeds eighty characters and should be trun..."
def test_function_with_multiline_docstring():
# Function with multiline docstring
def bar(a):
"""First line.
Second line.
Third line."""
return a
codeflash_output = _format_method("bar", bar, docs=True); result = codeflash_output # 24.4μs -> 25.4μs (4.20% slower)
def test_function_with_empty_docstring():
# Function with empty docstring
def foo(a):
""""""
return a
codeflash_output = _format_method("foo", foo, docs=True); result = codeflash_output # 20.5μs -> 22.0μs (7.09% slower)
def test_function_with_none_docstring():
# Function with no docstring at all
def bar(a):
return a
codeflash_output = _format_method("bar", bar, docs=True); result = codeflash_output # 22.5μs -> 23.3μs (3.63% slower)
def test_function_with_signature_exception(monkeypatch):
# Monkeypatch inspect.signature to raise exception
def foo(a):
return a
def raise_exc(obj):
raise ValueError("bad signature")
monkeypatch.setattr(inspect_, "signature", raise_exc)
codeflash_output = _format_method("foo", foo, docs=False); result = codeflash_output # 1.53μs -> 2.29μs (33.0% slower)
def test_function_with_non_callable():
# Non-callable object
obj = 12345
codeflash_output = _format_method("not_callable", obj, docs=True); result = codeflash_output # 15.5μs -> 16.8μs (7.34% slower)
def test_function_with_unicode_docstring():
# Function with unicode in docstring
def foo(a):
"""Привет мир!"""
return a
codeflash_output = _format_method("foo", foo, docs=True); result = codeflash_output # 26.4μs -> 28.1μs (6.28% slower)
def test_function_with_special_chars_in_docstring():
# Function with special chars in docstring
def bar(a):
"""Docstring with newline\nand tab\tcharacters."""
return a
codeflash_output = _format_method("bar", bar, docs=True); result = codeflash_output # 24.1μs -> 25.8μs (6.50% slower)
def test_function_with_signature_edge_cases():
# Function with positional-only and keyword-only arguments
def foo(a, /, b, *, c):
pass
codeflash_output = _format_method("foo", foo, docs=False); result = codeflash_output # 23.4μs -> 25.0μs (6.07% slower)
def test_async_function_with_signature_exception(monkeypatch):
# Monkeypatch inspect.signature to raise exception for async function
async def foo(a):
return a
def raise_exc(obj):
raise ValueError("bad signature")
monkeypatch.setattr(inspect_, "signature", raise_exc)
codeflash_output = _format_method("foo", foo, docs=False); result = codeflash_output # 1.52μs -> 2.27μs (33.1% slower)
--- Large Scale Test Cases ---
def test_large_number_of_args():
# Function with a large number of arguments
def foo(*args):
pass
# Create a signature with 999 arguments
code = "def foo(" + ", ".join(f"a{i}" for i in range(999)) + "): pass"
namespace = {}
exec(code, namespace)
large_func = namespace["foo"]
codeflash_output = _format_method("foo", large_func, docs=False); result = codeflash_output # 1.00ms -> 988μs (1.60% faster)
expected_sig = "(" + ", ".join(f"a{i}" for i in range(999)) + ")"
def test_large_docstring_truncation():
# Function with a very long docstring (1000 chars)
long_doc = "A" * 1000
def foo(a):
"""{}""".format(long_doc)
return a
codeflash_output = _format_method("foo", foo, docs=True); result = codeflash_output # 26.9μs -> 28.8μs (6.59% slower)
expected = "def foo(a): " + ("A" * 77) + "..."
def test_many_functions_in_batch():
# Test formatting many different functions in a batch
funcs = []
for i in range(500):
def f(x, i=i):
"""Function {}""".format(i)
return x + i
funcs.append((f"f{i}", f))
for name, func in funcs:
codeflash_output = _format_method(name, func, docs=True); result = codeflash_output # 4.66ms -> 5.15ms (9.54% slower)
def test_large_async_function():
# Async function with many arguments
code = "async def foo(" + ", ".join(f"a{i}" for i in range(500)) + "): pass"
namespace = {}
exec(code, namespace)
large_async_func = namespace["foo"]
codeflash_output = _format_method("foo", large_async_func, docs=False); result = codeflash_output # 511μs -> 505μs (1.29% faster)
expected_sig = "(" + ", ".join(f"a{i}" for i in range(500)) + ")"
def test_large_batch_with_various_types():
# Mix of sync, async, and non-callable objects
def sync_func(a): return a
async def async_func(b): return b
objs = [
("sync_func", sync_func, False, "def sync_func(a)"),
("async_func", async_func, False, "async def async_func(b)"),
("not_callable", 123, False, "def not_callable(...)"),
]
for name, obj, docs, expected in objs * 300: # 900 total
codeflash_output = _format_method(name, obj, docs); result = codeflash_output # 4.01ms -> 1.39ms (188% faster)
codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
#------------------------------------------------
from future import annotations
import inspect
import inspect as inspect_
import types
imports
import pytest
from marimo._plugins.stateless.inspect import _format_method
unit tests
---- BASIC TEST CASES ----
def test_basic_function_no_args_no_docs():
# Simple function, no arguments, no docstring
def foo():
pass
codeflash_output = _format_method("foo", foo, docs=False); result = codeflash_output # 16.4μs -> 18.7μs (12.5% slower)
def test_basic_function_with_args():
# Function with positional and default arguments
def bar(x, y=2):
pass
codeflash_output = _format_method("bar", bar, docs=False); result = codeflash_output # 24.0μs -> 25.8μs (6.96% slower)
def test_basic_function_with_docstring():
# Function with docstring
def baz():
"""This is a test docstring."""
pass
codeflash_output = _format_method("baz", baz, docs=True); result = codeflash_output # 18.1μs -> 19.9μs (9.22% slower)
def test_basic_function_with_multiline_docstring():
# Function with multi-line docstring
def qux():
"""
First line.
Second line.
"""
pass
codeflash_output = _format_method("qux", qux, docs=True); result = codeflash_output # 21.4μs -> 23.4μs (8.61% slower)
def test_basic_function_with_long_docstring():
# Function with a docstring longer than 80 chars
def longdoc():
"""{}""".format("A" * 100)
pass
codeflash_output = _format_method("longdoc", longdoc, docs=True); result = codeflash_output # 18.2μs -> 20.8μs (12.7% slower)
def test_basic_function_with_no_docstring_docs_true():
# Function with no docstring, docs=True
def nodoc():
pass
codeflash_output = _format_method("nodoc", nodoc, docs=True); result = codeflash_output # 18.2μs -> 20.3μs (10.6% slower)
def test_basic_function_with_args_and_docstring():
# Function with arguments and docstring
def add(a, b):
"""Adds two numbers."""
return a + b
codeflash_output = _format_method("add", add, docs=True); result = codeflash_output # 24.7μs -> 27.1μs (9.02% slower)
def test_basic_function_with_varargs_kwargs():
# Function with *args and **kwargs
def f(*args, **kwargs):
pass
codeflash_output = _format_method("f", f, docs=False); result = codeflash_output # 22.5μs -> 24.5μs (8.09% slower)
def test_basic_method_bound():
# Bound method (should show self as first arg)
class C:
def m(self, x):
pass
obj = C()
codeflash_output = _format_method("m", obj.m, docs=False); result = codeflash_output # 27.4μs -> 29.3μs (6.65% slower)
def test_basic_method_unbound():
# Unbound method (should show self as first arg)
class C:
def m(self, x):
pass
codeflash_output = _format_method("m", C.m, docs=False); result = codeflash_output # 20.5μs -> 22.4μs (8.13% slower)
def test_basic_staticmethod():
# Static method (should not show self)
class C:
@staticmethod
def sm(x):
pass
codeflash_output = _format_method("sm", C.sm, docs=False); result = codeflash_output # 18.7μs -> 20.2μs (7.41% slower)
def test_basic_classmethod():
# Class method (should show cls as first arg)
class C:
@classmethod
def cm(cls, x):
pass
codeflash_output = _format_method("cm", C.cm, docs=False); result = codeflash_output # 25.9μs -> 28.1μs (7.92% slower)
def test_basic_lambda_function():
# Lambda function
l = lambda x, y=1: x + y
codeflash_output = _format_method("lambda", l, docs=False); result = codeflash_output # 22.0μs -> 23.7μs (7.11% slower)
def test_basic_builtin_function():
# Built-in function (signature may not be available)
codeflash_output = _format_method("len", len, docs=False); result = codeflash_output # 123μs -> 124μs (0.777% slower)
def test_basic_builtin_function_with_docs():
# Built-in function with docs=True
codeflash_output = _format_method("len", len, docs=True); result = codeflash_output # 113μs -> 10.4μs (991% faster)
# Should include first line of docstring
doc = inspect.getdoc(len)
first_line = doc.split("\n")[0] if doc else None
if first_line and len(first_line) > 80:
first_line = first_line[:77] + "..."
expected = "def len(...)"
if first_line:
expected += f": {first_line}"
def test_basic_async_function():
# Async function
async def coro(x, y):
"""Async doc."""
return x + y
codeflash_output = _format_method("coro", coro, docs=True); result = codeflash_output # 26.0μs -> 26.8μs (2.89% slower)
def test_basic_partial_function():
# functools.partial object (signature may not be available)
import functools
def f(x, y):
return x + y
pf = functools.partial(f, 1)
codeflash_output = _format_method("pf", pf, docs=False); result = codeflash_output # 42.5μs -> 43.9μs (2.99% slower)
---- EDGE TEST CASES ----
def test_edge_function_with_annotations():
# Function with type annotations
def annotated(x: int, y: str = "hi") -> bool:
"""Returns True if x > 0."""
return x > 0
codeflash_output = _format_method("annotated", annotated, docs=True); result = codeflash_output # 29.6μs -> 31.4μs (5.52% slower)
def test_edge_function_with_keyword_only_args():
# Function with keyword-only arguments
def kwonly(x, *, y=2):
pass
codeflash_output = _format_method("kwonly", kwonly, docs=False); result = codeflash_output # 22.6μs -> 24.1μs (6.14% slower)
def test_edge_function_with_posonly_args():
# Python 3.8+: function with positional-only arguments
import sys
if sys.version_info >= (3, 8):
exec("""
def posonly(x, /, y):
pass
result = _format_method("posonly", posonly, docs=False)
assert result == "def posonly(x, /, y)"
""", globals())
def test_edge_function_with_no_name():
# Function with no name attribute (simulate)
class NoName:
def call(self, x):
return x
nn = NoName()
codeflash_output = _format_method("foo", nn, docs=False); result = codeflash_output # 35.9μs -> 36.8μs (2.21% slower)
def test_edge_function_with_exception_in_signature():
# Object that raises in signature
class BadSig:
def call(self, x):
return x
@Property
def signature(self):
raise RuntimeError("fail")
bad = BadSig()
codeflash_output = _format_method("bad", bad, docs=False); result = codeflash_output # 6.14μs -> 6.91μs (11.1% slower)
def test_edge_function_with_empty_docstring():
# Function with empty docstring
def emptydoc():
""""""
pass
codeflash_output = _format_method("emptydoc", emptydoc, docs=True); result = codeflash_output # 23.0μs -> 24.2μs (4.72% slower)
def test_edge_function_with_only_whitespace_docstring():
# Function with whitespace docstring
def wsdoc():
" "
pass
codeflash_output = _format_method("wsdoc", wsdoc, docs=True); result = codeflash_output # 18.4μs -> 20.0μs (7.98% slower)
def test_edge_function_with_unicode_docstring():
# Function with unicode in docstring
def ud():
"Résumé — café"
pass
codeflash_output = _format_method("ud", ud, docs=True); result = codeflash_output # 19.0μs -> 20.2μs (6.13% slower)
def test_edge_function_with_non_ascii_name():
# Function with non-ASCII name
def f_é(x):
pass
codeflash_output = _format_method("f_é", f_é, docs=False); result = codeflash_output # 20.8μs -> 21.8μs (4.52% slower)
def test_edge_function_with_many_args():
# Function with many arguments
def many(a, b, c, d, e, f, g, h, i, j):
pass
codeflash_output = _format_method("many", many, docs=False); result = codeflash_output # 30.2μs -> 32.0μs (5.60% slower)
def test_edge_function_with_signature_but_no_doc():
# Function with signature but doc is None
def f(x):
pass
f.doc = None
codeflash_output = _format_method("f", f, docs=True); result = codeflash_output # 23.4μs -> 24.8μs (5.37% slower)
def test_edge_object_not_callable():
# Non-callable object
class NotCallable:
pass
nc = NotCallable()
codeflash_output = _format_method("nc", nc, docs=False); result = codeflash_output # 5.50μs -> 5.86μs (6.22% slower)
def test_edge_object_with_weird_signature():
# Object with signature returning a non-signature
class WeirdSig:
def call(self, x):
return x
@Property
def signature(self):
return "not a signature"
ws = WeirdSig()
codeflash_output = _format_method("ws", ws, docs=False); result = codeflash_output # 7.15μs -> 7.91μs (9.53% slower)
def test_edge_function_with_signature_and_long_multiline_docstring():
# Function with long, multiline docstring
def f():
"""{}""".format("A" * 90 + "\nSecond line\nThird line")
codeflash_output = _format_method("f", f, docs=True); result = codeflash_output # 20.6μs -> 21.8μs (5.43% slower)
---- LARGE SCALE TEST CASES ----
def test_large_function_many_args():
# Function with 100 args
def f(*args):
pass
# Create a function with 100 named arguments
code = "def bigfunc({}): pass".format(", ".join(f"arg{i}" for i in range(100)))
ns = {}
exec(code, ns)
bigfunc = ns["bigfunc"]
codeflash_output = _format_method("bigfunc", bigfunc, docs=False); result = codeflash_output # 122μs -> 124μs (2.01% slower)
def test_large_function_long_docstring():
# Function with a very long docstring (>1000 chars)
longdoc = "A" * 1200
def f():
"""{}""".format(longdoc)
codeflash_output = _format_method("f", f, docs=True); result = codeflash_output # 19.8μs -> 21.6μs (8.25% slower)
def test_large_class_many_methods():
# Class with 500 methods
methods = {}
for i in range(500):
exec(f"def m{i}(self): return {i}", methods)
C = type("C", (), methods)
obj = C()
for i in range(500):
meth = getattr(obj, f"m{i}")
codeflash_output = _format_method(f"m{i}", meth, docs=False); result = codeflash_output # 3.71ms -> 3.97ms (6.44% slower)
def test_large_many_functions():
# Test 500 functions with different signatures
funcs = []
for i in range(500):
def make_func(n):
def f(arg=n):
return arg
return f
funcs.append(make_func(i))
for idx, f in enumerate(funcs):
codeflash_output = _format_method(f"f{idx}", f, docs=False); result = codeflash_output # 3.22ms -> 3.38ms (4.93% slower)
def test_large_async_functions():
# 100 async functions
for i in range(100):
code = f"async def afunc{i}(x): return x"
ns = {}
exec(code, ns)
afunc = ns[f"afunc{i}"]
codeflash_output = _format_method(f"afunc{i}", afunc, docs=False); result = codeflash_output # 677μs -> 740μs (8.52% slower)
def test_large_docstring_truncation():
# Function with a docstring of exactly 80 chars
def f():
"""{}""".format("A" * 80)
codeflash_output = _format_method("f", f, docs=True); result = codeflash_output # 19.2μs -> 20.7μs (6.97% slower)
def test_large_builtin_functions():
# Test many built-in functions
for name in dir(builtins):
obj = getattr(builtins, name)
if callable(obj):
codeflash_output = _format_method(name, obj, docs=False); result = codeflash_output
def test_large_partial_functions():
# 100 partial functions
import functools
def f(x, y):
return x + y
for i in range(100):
pf = functools.partial(f, i)
codeflash_output = _format_method(f"pf{i}", pf, docs=False); result = codeflash_output # 1.60ms -> 1.75ms (8.72% slower)
codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
#------------------------------------------------
from marimo._plugins.stateless.inspect import _format_method
def test__format_method():
_format_method('', 0, True)
🔎 Concolic Coverage Tests and Runtime
codeflash_concolic_bps3n5s8/tmp1nyn465i/test_concolic_coverage.py::test__format_methodTo edit these changes
git checkout codeflash/optimize-_format_method-mhvhw8wrand push.