Skip to content

⚡️ Speed up function get_dependency_query_params by 18% - #3

Open
codeflash-ai[bot] wants to merge 1 commit into
mainfrom
codeflash/optimize-get_dependency_query_params-mi8cmxz2
Open

⚡️ Speed up function get_dependency_query_params by 18%#3
codeflash-ai[bot] wants to merge 1 commit into
mainfrom
codeflash/optimize-get_dependency_query_params-mi8cmxz2

Conversation

@codeflash-ai

@codeflash-ai codeflash-ai Bot commented Nov 21, 2025

Copy link
Copy Markdown

📄 18% (0.18x) speedup for get_dependency_query_params in src/titiler/core/titiler/core/utils.py

⏱️ Runtime : 78.6 milliseconds 66.5 milliseconds (best of 98 runs)

📝 Explanation and details

The optimized code achieves an 18% speedup through two key optimizations that address the most expensive operations identified in the profiler:

1. Dependant Caching (Major Impact)
The original code calls get_dependant() on every invocation, which consumed 53.7% of runtime. The optimization introduces a global cache using the dependency function's id() as key, stored in get_dependant.__globals__. This eliminates redundant parsing of the same dependency functions - a common scenario given the function references show it's called from extract_query_params() which iterates over dependency lists, and deserialize_query_params() which likely processes the same dependencies repeatedly.

2. QueryParams Construction Optimization (Secondary Impact)
The original always converts Dict params through urlencode(), even for simple cases. The optimization adds fast paths:

  • Direct use when params is already QueryParams
  • Empty string for empty dicts
  • Direct string joining for simple dicts without list/tuple values (avoiding urlencode overhead)
  • Falls back to urlencode() only for complex cases with sequences

Performance Impact by Test Case:

  • Small/simple dependencies see 6-12x speedups (most common case based on test results)
  • Large-scale tests with 100+ parameters show modest 1-5% gains, as request_params_to_args() dominates runtime
  • List parameters maintain performance, with the optimization gracefully handling complex cases

The caching is particularly valuable given the function references show this is called in loops (extract_query_params) and likely in request processing pipelines where the same dependency functions are reused across requests.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 8 Passed
🌀 Generated Regression Tests 34 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
⚙️ Existing Unit Tests and Runtime
🌀 Generated Regression Tests and Runtime
import inspect
from typing import Any, Callable, Dict, List, Tuple, Union
from urllib.parse import urlencode

# imports
import pytest
from fastapi import Query
from fastapi.datastructures import QueryParams
# function to test
from fastapi.dependencies.utils import get_dependant, request_params_to_args
from titiler.core.utils import get_dependency_query_params

# --- Unit tests below ---

# --- Helper dependency callables for testing ---

def dep_simple(a: int = 1, b: str = "foo"):
    return {"a": a, "b": b}

def dep_required(a: int, b: str):
    return {"a": a, "b": b}

def dep_with_defaults(a: int = 42, b: str = "bar"):
    return {"a": a, "b": b}

def dep_with_types(a: int, b: float, c: bool, d: str):
    return {"a": a, "b": b, "c": c, "d": d}

def dep_with_list(a: List[int] = Query([1, 2, 3])):
    return {"a": a}

def dep_with_optional(a: Union[int, None] = None):
    return {"a": a}

def dep_with_no_args():
    return {}

def dep_with_bool_flag(flag: bool = False):
    return {"flag": flag}

def dep_with_complex_types(a: List[int] = Query([1]), b: Union[str, None] = None):
    return {"a": a, "b": b}

# --- Basic Test Cases ---

def test_simple_params_dict():
    # Passes all params as dict, should parse correctly
    params = {"a": "10", "b": "hello"}
    valid, errors = get_dependency_query_params(dep_simple, params) # 462μs -> 47.0μs (886% faster)

def test_simple_params_queryparams():
    # Passes all params as QueryParams object
    qp = QueryParams("a=5&b=world")
    valid, errors = get_dependency_query_params(dep_simple, qp) # 401μs -> 31.8μs (1165% faster)

def test_missing_optional_params():
    # Only one param provided, other should use default
    params = {"a": "99"}
    valid, errors = get_dependency_query_params(dep_simple, params) # 416μs -> 44.4μs (837% faster)

def test_with_defaults():
    # No params provided, should use all defaults
    valid, errors = get_dependency_query_params(dep_with_defaults, {}) # 402μs -> 35.0μs (1049% faster)

def test_required_params_present():
    # All required params present
    params = {"a": "7", "b": "baz"}
    valid, errors = get_dependency_query_params(dep_required, params) # 404μs -> 41.4μs (876% faster)

def test_required_params_missing():
    # Missing required param, should produce error
    params = {"a": "7"}
    valid, errors = get_dependency_query_params(dep_required, params) # 403μs -> 50.6μs (698% faster)

def test_type_conversion():
    # Test conversion of types
    params = {"a": "1", "b": "2.5", "c": "true", "d": "hello"}
    valid, errors = get_dependency_query_params(dep_with_types, params) # 716μs -> 71.4μs (904% faster)

def test_bool_flag_true():
    # Test bool flag with value 'true'
    params = {"flag": "true"}
    valid, errors = get_dependency_query_params(dep_with_bool_flag, params) # 253μs -> 33.1μs (666% faster)

def test_bool_flag_false():
    # Test bool flag with value 'false'
    params = {"flag": "false"}
    valid, errors = get_dependency_query_params(dep_with_bool_flag, params) # 257μs -> 32.1μs (704% faster)

def test_bool_flag_missing():
    # Should use default value
    valid, errors = get_dependency_query_params(dep_with_bool_flag, {}) # 252μs -> 27.9μs (802% faster)

def test_list_param():
    # List param via repeated keys
    params = [("a", "4"), ("a", "5"), ("a", "6")]
    qp = QueryParams(params)
    valid, errors = get_dependency_query_params(dep_with_list, qp) # 288μs -> 23.7μs (1120% faster)

def test_list_param_single_value():
    # List param with single value
    params = {"a": "10"}
    valid, errors = get_dependency_query_params(dep_with_list, params) # 268μs -> 32.1μs (736% faster)

def test_optional_param_none():
    # Optional param not provided, should be None
    valid, errors = get_dependency_query_params(dep_with_optional, {}) # 333μs -> 31.4μs (963% faster)

def test_optional_param_present():
    # Optional param provided
    params = {"a": "123"}
    valid, errors = get_dependency_query_params(dep_with_optional, params) # 340μs -> 42.8μs (694% faster)

def test_no_args():
    # Dependency has no params
    valid, errors = get_dependency_query_params(dep_with_no_args, {}) # 26.0μs -> 5.87μs (343% faster)

# --- Edge Test Cases ---

def test_extra_params_ignored():
    # Extra params not in dependency should be ignored
    params = {"a": "1", "b": "foo", "c": "should_ignore"}
    valid, errors = get_dependency_query_params(dep_simple, params) # 445μs -> 46.2μs (863% faster)

def test_wrong_type_param():
    # Param of wrong type should produce error
    params = {"a": "not_an_int", "b": "foo"}
    valid, errors = get_dependency_query_params(dep_simple, params) # 423μs -> 52.8μs (702% faster)

def test_empty_params_dict():
    # Empty params dict, should use defaults or error if required
    valid, errors = get_dependency_query_params(dep_required, {}) # 400μs -> 41.2μs (873% faster)

def test_empty_params_queryparams():
    # Empty QueryParams object
    qp = QueryParams("")
    valid, errors = get_dependency_query_params(dep_with_defaults, qp) # 399μs -> 32.3μs (1136% faster)

def test_list_param_empty():
    # List param not provided, should use default
    valid, errors = get_dependency_query_params(dep_with_list, {}) # 275μs -> 39.0μs (607% faster)

def test_list_param_empty_list():
    # List param provided as empty list
    qp = QueryParams([])
    valid, errors = get_dependency_query_params(dep_with_list, qp) # 265μs -> 32.3μs (721% faster)

def test_complex_types():
    # Complex types: list and optional
    params = [("a", "10"), ("a", "20"), ("b", "hello")]
    qp = QueryParams(params)
    valid, errors = get_dependency_query_params(dep_with_complex_types, qp) # 537μs -> 44.5μs (1107% faster)

def test_complex_types_missing_optional():
    # Complex types: missing optional
    params = [("a", "5")]
    qp = QueryParams(params)
    valid, errors = get_dependency_query_params(dep_with_complex_types, qp) # 495μs -> 38.9μs (1174% faster)

def test_param_with_empty_string():
    # Param provided as empty string, should error for int
    params = {"a": ""}
    valid, errors = get_dependency_query_params(dep_simple, params) # 437μs -> 54.3μs (706% faster)

def test_param_with_none_string():
    # Param provided as string 'None', should error for int
    params = {"a": "None"}
    valid, errors = get_dependency_query_params(dep_simple, params) # 414μs -> 51.2μs (711% faster)

def test_param_with_multiple_types():
    # List param provided with one int, one non-int
    params = [("a", "1"), ("a", "bad")]
    qp = QueryParams(params)
    valid, errors = get_dependency_query_params(dep_with_list, qp) # 268μs -> 29.4μs (812% faster)

def test_param_with_special_chars():
    # String param with special characters
    params = {"a": "3", "b": "sp&cial=chars"}
    valid, errors = get_dependency_query_params(dep_simple, params) # 443μs -> 44.9μs (887% faster)

def test_param_with_unicode():
    # String param with unicode
    params = {"a": "5", "b": "你好"}
    valid, errors = get_dependency_query_params(dep_simple, params) # 423μs -> 44.3μs (856% faster)

# --- Large Scale Test Cases ---

def make_large_dep(num_params: int):
    # Dynamically create a dependency with many int parameters
    def _dep(**kwargs):
        return kwargs
    params = {
        f"p{i}": (int, Query(i))
        for i in range(num_params)
    }
    # Build a signature dynamically
    sig_params = [
        inspect.Parameter(
            name,
            inspect.Parameter.POSITIONAL_OR_KEYWORD,
            default=Query(default),
            annotation=typ,
        )
        for name, (typ, default) in params.items()
    ]
    _dep.__signature__ = inspect.Signature(sig_params)
    return _dep

def test_large_number_of_params_all_present():
    # 100 params, all present
    dep = make_large_dep(100)
    params = {f"p{i}": str(i*2) for i in range(100)}
    valid, errors = get_dependency_query_params(dep, params) # 12.5ms -> 12.1ms (3.29% faster)
    expected = {f"p{i}": i*2 for i in range(100)}

def test_large_number_of_params_some_missing():
    # 100 params, half present
    dep = make_large_dep(100)
    params = {f"p{i}": str(i*2) for i in range(0, 100, 2)}
    valid, errors = get_dependency_query_params(dep, params) # 18.5ms -> 18.0ms (2.77% faster)
    # Odd params use default, even params use provided
    expected = {f"p{i}": i*2 if i % 2 == 0 else i for i in range(100)}

def test_large_number_of_params_all_missing():
    # 100 params, none provided, all use default
    dep = make_large_dep(100)
    valid, errors = get_dependency_query_params(dep, {}) # 23.9ms -> 23.7ms (1.02% faster)
    expected = {f"p{i}": i for i in range(100)}

def test_large_list_param():
    # List param with 500 elements
    def dep(a: List[int] = Query([0])):
        return {"a": a}
    params = [("a", str(i)) for i in range(500)]
    qp = QueryParams(params)
    valid, errors = get_dependency_query_params(dep, qp) # 364μs -> 369μs (1.18% slower)

def test_large_list_param_some_bad():
    # List param with 500 elements, one is bad
    def dep(a: List[int] = Query([0])):
        return {"a": a}
    params = [("a", str(i)) for i in range(499)] + [("a", "bad")]
    qp = QueryParams(params)
    valid, errors = get_dependency_query_params(dep, qp) # 341μs -> 336μs (1.76% faster)

def test_large_scale_with_mixed_types():
    # 50 int, 50 str, all present
    def dep(
        **kwargs
    ):
        return kwargs
    sig_params = []
    for i in range(50):
        sig_params.append(inspect.Parameter(f"i{i}", inspect.Parameter.POSITIONAL_OR_KEYWORD, default=Query(i), annotation=int))
    for i in range(50):
        sig_params.append(inspect.Parameter(f"s{i}", inspect.Parameter.POSITIONAL_OR_KEYWORD, default=Query(str(i)), annotation=str))
    dep.__signature__ = inspect.Signature(sig_params)
    params = {f"i{i}": str(i*3) for i in range(50)}
    params.update({f"s{i}": f"val{i}" for i in range(50)})
    valid, errors = get_dependency_query_params(dep, params) # 11.3ms -> 10.8ms (4.61% faster)
    expected = {f"i{i}": i*3 for i in range(50)}
    expected.update({f"s{i}": f"val{i}" for i in range(50)})
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.

To edit these changes git checkout codeflash/optimize-get_dependency_query_params-mi8cmxz2 and push.

Codeflash Static Badge

The optimized code achieves an 18% speedup through two key optimizations that address the most expensive operations identified in the profiler:

**1. Dependant Caching (Major Impact)**
The original code calls `get_dependant()` on every invocation, which consumed 53.7% of runtime. The optimization introduces a global cache using the dependency function's `id()` as key, stored in `get_dependant.__globals__`. This eliminates redundant parsing of the same dependency functions - a common scenario given the function references show it's called from `extract_query_params()` which iterates over dependency lists, and `deserialize_query_params()` which likely processes the same dependencies repeatedly.

**2. QueryParams Construction Optimization (Secondary Impact)**
The original always converts Dict params through `urlencode()`, even for simple cases. The optimization adds fast paths:
- Direct use when params is already `QueryParams` 
- Empty string for empty dicts
- Direct string joining for simple dicts without list/tuple values (avoiding urlencode overhead)
- Falls back to `urlencode()` only for complex cases with sequences

**Performance Impact by Test Case:**
- Small/simple dependencies see 6-12x speedups (most common case based on test results)
- Large-scale tests with 100+ parameters show modest 1-5% gains, as `request_params_to_args()` dominates runtime
- List parameters maintain performance, with the optimization gracefully handling complex cases

The caching is particularly valuable given the function references show this is called in loops (`extract_query_params`) and likely in request processing pipelines where the same dependency functions are reused across requests.
@codeflash-ai
codeflash-ai Bot requested a review from mashraf-222 November 21, 2025 04:17
@codeflash-ai codeflash-ai Bot added ⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: Medium Optimization Quality according to Codeflash labels Nov 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: Medium Optimization Quality according to Codeflash

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants