Skip to content

⚡️ Speed up function get_dependency_query_params by 32% - #14

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

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

Conversation

@codeflash-ai

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

Copy link
Copy Markdown

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

⏱️ Runtime : 14.5 milliseconds 10.9 milliseconds (best of 250 runs)

📝 Explanation and details

The optimization avoids unnecessary URL encoding and string parsing for the common case where dictionary parameters contain only scalar values (strings, numbers, booleans) rather than lists or tuples.

Key Changes:

  • Added a check any(isinstance(v, (list, tuple)) for v in params.values()) to detect if any parameter values are multi-valued
  • For scalar-only dictionaries, pass the dict directly to QueryParams(params) instead of going through urlencode
  • Only use the expensive urlencode(params, doseq=True) path when multi-valued parameters are detected

Why This is Faster:
The original code always called urlencode(params, doseq=True) which converts the dictionary to a URL-encoded string, then QueryParams parses that string back into key-value pairs. This double conversion (dict → string → parsed structure) is costly. The optimization bypasses this for the common case where parameters are simple scalars.

Performance Impact:
From the profiler results, the expensive urlencode line dropped from 53% of total time to just 8% when needed. The largest gains are seen in the "large scale" tests - up to 164% speedup for cases with many parameters, since each parameter avoids the encoding/parsing overhead.

Hot Path Benefits:
Based on the function references, get_dependency_query_params is called from deserialize_query_params and extract_query_params, which likely process user input parameters frequently. The optimization particularly benefits workloads with many simple parameters (common in web APIs), while preserving full compatibility for complex multi-valued parameters.

Correctness verification report:

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

# imports
import pytest  # used for our unit tests
from fastapi.datastructures import QueryParams
from fastapi.dependencies.utils import get_dependant, request_params_to_args
from titiler.core.utils import get_dependency_query_params

# --- Unit tests ---

# Helper dependency functions for testing
def dep_no_params():
    return "ok"

def dep_one_param(a: int):
    return a

def dep_multi_params(a: int, b: str, c: float = 2.5):
    return (a, b, c)

def dep_optional(a: Optional[int] = None):
    return a

def dep_list_param(a: List[int]):
    return a

def dep_with_default(a: int = 42):
    return a

def dep_bool_flag(flag: bool = False):
    return flag

def dep_mixed_types(a: int, b: float, c: str, d: bool):
    return (a, b, c, d)

def dep_strict(a: int):
    # Raises error if a is not int
    if not isinstance(a, int):
        raise ValueError("a must be int")
    return a

# --- Basic Test Cases ---

def test_no_params_basic():
    # Test with dependency that takes no params
    valid, errors = get_dependency_query_params(dep_no_params, {}) # 27.2μs -> 26.0μs (4.52% faster)

def test_one_param_basic():
    # Test with dependency that takes one int param
    valid, errors = get_dependency_query_params(dep_one_param, {"a": "123"}) # 283μs -> 279μs (1.53% faster)

def test_multi_params_basic():
    # Test with multiple params, including default
    params = {"a": "5", "b": "hello"}
    valid, errors = get_dependency_query_params(dep_multi_params, params) # 590μs -> 586μs (0.786% faster)

def test_multi_params_with_all():
    # Provide all params including optional
    params = {"a": "7", "b": "world", "c": "3.14"}
    valid, errors = get_dependency_query_params(dep_multi_params, params) # 553μs -> 546μs (1.20% faster)

def test_optional_param_none():
    # Test with missing optional param
    valid, errors = get_dependency_query_params(dep_optional, {}) # 316μs -> 318μs (0.641% slower)

def test_optional_param_provided():
    # Test with provided optional param
    valid, errors = get_dependency_query_params(dep_optional, {"a": "99"}) # 332μs -> 320μs (3.70% faster)

def test_param_with_default():
    # Test with default param not provided
    valid, errors = get_dependency_query_params(dep_with_default, {}) # 248μs -> 247μs (0.556% faster)

def test_param_with_default_provided():
    # Test with default param provided
    valid, errors = get_dependency_query_params(dep_with_default, {"a": "7"}) # 255μs -> 248μs (2.96% faster)

def test_bool_flag_false():
    # Test bool flag default False
    valid, errors = get_dependency_query_params(dep_bool_flag, {}) # 247μs -> 250μs (1.16% slower)

def test_bool_flag_true():
    # Test bool flag set to True
    valid, errors = get_dependency_query_params(dep_bool_flag, {"flag": "true"}) # 256μs -> 250μs (2.15% faster)

def test_bool_flag_1():
    # Test bool flag set to "1"
    valid, errors = get_dependency_query_params(dep_bool_flag, {"flag": "1"}) # 257μs -> 246μs (4.33% faster)

# --- Edge Test Cases ---

def test_missing_required_param():
    # Missing required param should result in error
    valid, errors = get_dependency_query_params(dep_one_param, {}) # 247μs -> 249μs (0.680% slower)

def test_wrong_type_param():
    # Wrong type for int param
    valid, errors = get_dependency_query_params(dep_one_param, {"a": "notanint"}) # 259μs -> 252μs (2.82% faster)

def test_extra_param_ignored():
    # Extra param not in dependency should be ignored
    valid, errors = get_dependency_query_params(dep_one_param, {"a": "5", "extra": "foo"}) # 254μs -> 245μs (3.68% faster)

def test_list_param():
    # List param provided as multiple values
    valid, errors = get_dependency_query_params(dep_list_param, {"a": ["1", "2", "3"]}) # 287μs -> 289μs (0.450% slower)

def test_list_param_single_value():
    # List param provided as single value
    valid, errors = get_dependency_query_params(dep_list_param, {"a": "42"}) # 276μs -> 271μs (1.83% faster)

def test_mixed_types():
    # Test with mixed types
    params = {"a": "1", "b": "2.3", "c": "foo", "d": "true"}
    valid, errors = get_dependency_query_params(dep_mixed_types, params) # 724μs -> 717μs (0.945% faster)

def test_bool_flag_invalid():
    # Test bool flag with invalid value
    valid, errors = get_dependency_query_params(dep_bool_flag, {"flag": "notabool"}) # 259μs -> 255μs (1.59% faster)

def test_queryparams_input():
    # Test using QueryParams directly
    qp = QueryParams("a=5")
    valid, errors = get_dependency_query_params(dep_one_param, qp) # 233μs -> 241μs (3.17% slower)

def test_strict_type_error():
    # Dependency raises error if type wrong
    valid, errors = get_dependency_query_params(dep_strict, {"a": "notanint"}) # 251μs -> 247μs (1.82% faster)

def test_empty_dict_params():
    # Empty params dict for dependency with defaults
    valid, errors = get_dependency_query_params(dep_with_default, {}) # 250μs -> 248μs (0.869% faster)

def test_empty_queryparams_params():
    # Empty QueryParams for dependency with defaults
    qp = QueryParams("")
    valid, errors = get_dependency_query_params(dep_with_default, qp) # 242μs -> 243μs (0.623% slower)

# --- Large Scale Test Cases ---

def large_dep_factory(n):
    # Factory to create dependency with n integer params
    def dep(**kwargs):
        return kwargs
    dep.__annotations__ = {f"x{i}": int for i in range(n)}
    return dep

@pytest.mark.parametrize("n", [10, 100, 500, 999])
def test_large_scale_many_params(n):
    # Test with dependency that takes n integer params
    dep = large_dep_factory(n)
    params = {f"x{i}": str(i) for i in range(n)}
    valid, errors = get_dependency_query_params(dep, params) # 3.46ms -> 1.42ms (144% faster)
    for i in range(n):
        pass

def test_large_scale_missing_some_params():
    # Test with 1000 params, but only half provided
    n = 1000
    dep = large_dep_factory(n)
    params = {f"x{i}": str(i) for i in range(n//2)}
    valid, errors = get_dependency_query_params(dep, params) # 1.01ms -> 402μs (150% faster)

def test_large_scale_wrong_types():
    # Test with 500 params, all wrong types
    n = 500
    dep = large_dep_factory(n)
    params = {f"x{i}": "notanint" for i in range(n)}
    valid, errors = get_dependency_query_params(dep, params) # 1.02ms -> 384μs (164% faster)

def test_large_scale_list_params():
    # Test with 100 list params, each with 3 values
    def dep(**kwargs):
        return kwargs
    dep.__annotations__ = {f"x{i}": List[int] for i in range(100)}
    params = {f"x{i}": ["1", "2", "3"] for i in range(100)}
    valid, errors = get_dependency_query_params(dep, params) # 604μs -> 596μs (1.32% faster)
    for v in valid.values():
        pass

def test_large_scale_mixed_types():
    # Test with 100 params, alternating types
    def dep(**kwargs):
        return kwargs
    dep.__annotations__ = {}
    params = {}
    for i in range(100):
        if i % 4 == 0:
            dep.__annotations__[f"x{i}"] = int
            params[f"x{i}"] = str(i)
        elif i % 4 == 1:
            dep.__annotations__[f"x{i}"] = float
            params[f"x{i}"] = str(i + 0.5)
        elif i % 4 == 2:
            dep.__annotations__[f"x{i}"] = str
            params[f"x{i}"] = f"val{i}"
        else:
            dep.__annotations__[f"x{i}"] = bool
            params[f"x{i}"] = "true" if i % 8 == 3 else "false"
    valid, errors = get_dependency_query_params(dep, params) # 408μs -> 264μs (54.6% faster)
    for i in range(100):
        k = f"x{i}"
        if i % 4 == 0:
            pass
        elif i % 4 == 1:
            pass
        elif i % 4 == 2:
            pass
        else:
            expected = True if i % 8 == 3 else False
# 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-mifo2ot6 and push.

Codeflash Static Badge

The optimization avoids unnecessary URL encoding and string parsing for the common case where dictionary parameters contain only scalar values (strings, numbers, booleans) rather than lists or tuples.

**Key Changes:**
- Added a check `any(isinstance(v, (list, tuple)) for v in params.values())` to detect if any parameter values are multi-valued
- For scalar-only dictionaries, pass the dict directly to `QueryParams(params)` instead of going through `urlencode`
- Only use the expensive `urlencode(params, doseq=True)` path when multi-valued parameters are detected

**Why This is Faster:**
The original code always called `urlencode(params, doseq=True)` which converts the dictionary to a URL-encoded string, then `QueryParams` parses that string back into key-value pairs. This double conversion (dict → string → parsed structure) is costly. The optimization bypasses this for the common case where parameters are simple scalars.

**Performance Impact:**
From the profiler results, the expensive `urlencode` line dropped from 53% of total time to just 8% when needed. The largest gains are seen in the "large scale" tests - up to 164% speedup for cases with many parameters, since each parameter avoids the encoding/parsing overhead.

**Hot Path Benefits:**
Based on the function references, `get_dependency_query_params` is called from `deserialize_query_params` and `extract_query_params`, which likely process user input parameters frequently. The optimization particularly benefits workloads with many simple parameters (common in web APIs), while preserving full compatibility for complex multi-valued parameters.
@codeflash-ai
codeflash-ai Bot requested a review from mashraf-222 November 26, 2025 07:12
@codeflash-ai codeflash-ai Bot added ⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: High Optimization Quality according to Codeflash labels Nov 26, 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: High Optimization Quality according to Codeflash

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants