Skip to content

Conversation

@codeflash-ai
Copy link

@codeflash-ai codeflash-ai bot commented Oct 24, 2025

📄 6% (0.06x) speedup for GlobalMercator.MetersToTile in opendm/tiles/gdal2tiles.py

⏱️ Runtime : 2.15 milliseconds 2.03 milliseconds (best of 152 runs)

📝 Explanation and details

The optimized code achieves a 5% speedup through two key micro-optimizations that reduce redundant computations in hot paths:

What was optimized:

  1. Pre-computed float conversion: Added self._tileSize_float = float(tileSize) in __init__() and use it in PixelsToTile() instead of calling float(self.tileSize) on every invocation
  2. Local variable caching: Store frequently accessed instance variables in local variables (shift = self.originShift in MetersToPixels, tsf = self._tileSize_float in PixelsToTile)

Why it's faster:

  • Eliminates repeated type conversions: The original code called float(self.tileSize) twice per PixelsToTile() call. With thousands of tile calculations, this adds up significantly
  • Reduces attribute lookup overhead: Python attribute access (self.originShift) is slower than local variable access (shift) due to dictionary lookups in the object's __dict__
  • Better CPU cache utilization: Local variables stay in faster CPU registers/cache versus repeated memory access for instance attributes

Performance characteristics:
The optimizations show consistent 3-7% improvements across most test cases, with the best gains (7-10%) on tests with many coordinate conversions like test_random_large_coordinates and test_many_random_points_large_scale. The optimizations are particularly effective for batch processing scenarios where MetersToTile() is called repeatedly, making this ideal for tile generation workflows that process thousands of coordinates.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 2645 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
🌀 Generated Regression Tests and Runtime
import math

# imports
import pytest  # used for our unit tests
from opendm.tiles.gdal2tiles import GlobalMercator

# unit tests

@pytest.fixture
def mercator():
    """Fixture to provide a GlobalMercator instance with default tile size."""
    return GlobalMercator()

# ---- Basic Test Cases ----

def test_origin_tile_zoom0(mercator):
    """Test tile for the origin at zoom level 0."""
    mx, my, zoom = 0, 0, 0
    tx, ty = mercator.MetersToTile(mx, my, zoom) # 2.05μs -> 2.00μs (2.30% faster)

def test_corner_tile_zoom0(mercator):
    """Test tile for the top-right corner at zoom level 0."""
    mx, my, zoom = mercator.originShift, mercator.originShift, 0
    tx, ty = mercator.MetersToTile(mx, my, zoom) # 1.87μs -> 1.78μs (4.82% faster)

def test_corner_tile_zoom1(mercator):
    """Test tile for the top-right corner at zoom level 1."""
    mx, my, zoom = mercator.originShift, mercator.originShift, 1
    tx, ty = mercator.MetersToTile(mx, my, zoom) # 2.04μs -> 2.05μs (0.828% slower)

def test_negative_corner_tile_zoom1(mercator):
    """Test tile for the bottom-left corner at zoom level 1."""
    mx, my, zoom = -mercator.originShift, -mercator.originShift, 1
    tx, ty = mercator.MetersToTile(mx, my, zoom) # 2.04μs -> 2.05μs (0.293% slower)

def test_middle_tile_zoom2(mercator):
    """Test tile for the middle point at zoom level 2."""
    mx, my, zoom = 0, 0, 2
    tx, ty = mercator.MetersToTile(mx, my, zoom) # 2.32μs -> 2.20μs (5.46% faster)

def test_tile_size_change():
    """Test with a non-default tile size."""
    mercator = GlobalMercator(tileSize=512)
    mx, my, zoom = 0, 0, 0
    tx, ty = mercator.MetersToTile(mx, my, zoom) # 1.85μs -> 1.84μs (0.435% faster)

# ---- Edge Test Cases ----

def test_maximum_extent_positive(mercator):
    """Test tile for maximum positive extent."""
    mx, my, zoom = mercator.originShift, mercator.originShift, 5
    tx, ty = mercator.MetersToTile(mx, my, zoom) # 2.33μs -> 2.21μs (5.53% faster)

def test_maximum_extent_negative(mercator):
    """Test tile for maximum negative extent."""
    mx, my, zoom = -mercator.originShift, -mercator.originShift, 5
    tx, ty = mercator.MetersToTile(mx, my, zoom) # 2.28μs -> 2.17μs (5.02% faster)

def test_out_of_bounds_positive(mercator):
    """Test tile for out-of-bounds positive coordinates."""
    mx, my, zoom = mercator.originShift * 1.1, mercator.originShift * 1.1, 2
    tx, ty = mercator.MetersToTile(mx, my, zoom) # 2.24μs -> 2.09μs (7.17% faster)

def test_out_of_bounds_negative(mercator):
    """Test tile for out-of-bounds negative coordinates."""
    mx, my, zoom = -mercator.originShift * 1.1, -mercator.originShift * 1.1, 2
    tx, ty = mercator.MetersToTile(mx, my, zoom) # 2.21μs -> 2.16μs (2.41% faster)

def test_non_integer_zoom(mercator):
    """Test with a non-integer zoom level (should work, but not typical)."""
    mx, my, zoom = 0, 0, 2.5
    tx, ty = mercator.MetersToTile(mx, my, zoom) # 2.64μs -> 2.74μs (3.68% slower)

def test_float_coordinates(mercator):
    """Test with float coordinates within valid range."""
    mx, my, zoom = 1234567.89, -9876543.21, 3
    tx, ty = mercator.MetersToTile(mx, my, zoom) # 2.22μs -> 2.09μs (6.42% faster)

def test_zero_zoom_large_extent(mercator):
    """Test that all valid coordinates map to tile (0,0) at zoom 0."""
    for mx in [-mercator.originShift, 0, mercator.originShift]:
        for my in [-mercator.originShift, 0, mercator.originShift]:
            tx, ty = mercator.MetersToTile(mx, my, 0)

def test_tile_boundary(mercator):
    """Test coordinates exactly on tile boundaries."""
    zoom = 3
    res = mercator.Resolution(zoom)
    # px = 256, py = 256 should be on the boundary between tiles (0,0) and (1,1)
    mx = -mercator.originShift + res * mercator.tileSize
    my = -mercator.originShift + res * mercator.tileSize
    tx, ty = mercator.MetersToTile(mx, my, zoom) # 1.94μs -> 1.86μs (3.86% faster)

# ---- Large Scale Test Cases ----

def test_all_tiles_at_zoom4(mercator):
    """Test every tile at zoom 4 for corner coordinates."""
    zoom = 4
    num_tiles = 2 ** zoom
    res = mercator.Resolution(zoom)
    for tx in range(num_tiles):
        for ty in range(num_tiles):
            # Get meters for the bottom-left pixel of each tile
            px = tx * mercator.tileSize
            py = ty * mercator.tileSize
            mx = px * res - mercator.originShift
            my = py * res - mercator.originShift
            txx, tyy = mercator.MetersToTile(mx, my, zoom)

def test_random_large_coordinates(mercator):
    """Test random large coordinates within valid extent."""
    import random
    zoom = 8
    num_tiles = 2 ** zoom
    res = mercator.Resolution(zoom)
    for _ in range(100):  # 100 random samples
        tx = random.randint(0, num_tiles - 1)
        ty = random.randint(0, num_tiles - 1)
        # Pick a random pixel inside the tile
        px = tx * mercator.tileSize + random.uniform(0, mercator.tileSize)
        py = ty * mercator.tileSize + random.uniform(0, mercator.tileSize)
        mx = px * res - mercator.originShift
        my = py * res - mercator.originShift
        txx, tyy = mercator.MetersToTile(mx, my, zoom) # 85.6μs -> 80.8μs (5.96% faster)

def test_high_zoom_performance(mercator):
    """Test performance and correctness at high zoom levels."""
    zoom = 10
    num_tiles = 2 ** zoom
    # Test a few tiles at high zoom
    for tx in [0, num_tiles // 2, num_tiles - 1]:
        for ty in [0, num_tiles // 2, num_tiles - 1]:
            res = mercator.Resolution(zoom)
            px = tx * mercator.tileSize
            py = ty * mercator.tileSize
            mx = px * res - mercator.originShift
            my = py * res - mercator.originShift
            txx, tyy = mercator.MetersToTile(mx, my, zoom)

def test_large_tileSize():
    """Test with a large tile size and high zoom."""
    tileSize = 1024
    mercator = GlobalMercator(tileSize=tileSize)
    zoom = 8
    num_tiles = 2 ** zoom
    tx, ty = num_tiles // 2, num_tiles // 2
    res = mercator.Resolution(zoom)
    px = tx * tileSize
    py = ty * tileSize
    mx = px * res - mercator.originShift
    my = py * res - mercator.originShift
    txx, tyy = mercator.MetersToTile(mx, my, zoom) # 1.88μs -> 1.79μs (5.37% faster)

def test_all_tiles_at_zoom0(mercator):
    """Test that all valid coordinates at zoom 0 map to tile (0,0)."""
    zoom = 0
    res = mercator.Resolution(zoom)
    # There is only one tile (0,0)
    for px in range(0, mercator.tileSize, 64):  # 4 samples
        for py in range(0, mercator.tileSize, 64):
            mx = px * res - mercator.originShift
            my = py * res - mercator.originShift
            tx, ty = mercator.MetersToTile(mx, my, zoom)
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
#------------------------------------------------
import math

# imports
import pytest
from opendm.tiles.gdal2tiles import GlobalMercator

# unit tests

@pytest.fixture
def mercator():
    # Standard instance for tests
    return GlobalMercator()

# ------------------- BASIC TEST CASES -------------------

def test_origin_tile_at_zoom_0(mercator):
    # At zoom=0, the only tile should be (0,0) for the origin (0,0)
    codeflash_output = mercator.MetersToTile(0, 0, 0); tile = codeflash_output # 2.04μs -> 1.90μs (7.33% faster)

def test_corners_at_zoom_0(mercator):
    # The four corners of the world at zoom=0 should all map to (0,0)
    for mx, my in [
        (-mercator.originShift, -mercator.originShift),
        (-mercator.originShift, mercator.originShift),
        (mercator.originShift, -mercator.originShift),
        (mercator.originShift, mercator.originShift),
    ]:
        codeflash_output = mercator.MetersToTile(mx, my, 0); tile = codeflash_output # 4.36μs -> 4.15μs (4.86% faster)

def test_center_tile_at_zoom_1(mercator):
    # At zoom=1, the world is split into 2x2 tiles. The origin (0,0) should be in (1,1)
    codeflash_output = mercator.MetersToTile(0, 0, 1); tile = codeflash_output # 2.21μs -> 2.12μs (4.19% faster)

def test_quadrant_tiles_zoom_1(mercator):
    # At zoom=1, test which tile each quadrant's corner falls into
    # Bottom-left
    codeflash_output = mercator.MetersToTile(-mercator.originShift, -mercator.originShift, 1); tile = codeflash_output # 2.25μs -> 2.12μs (5.93% faster)
    # Top-left
    codeflash_output = mercator.MetersToTile(-mercator.originShift, mercator.originShift, 1); tile = codeflash_output # 1.15μs -> 1.05μs (9.91% faster)
    # Bottom-right
    codeflash_output = mercator.MetersToTile(mercator.originShift, -mercator.originShift, 1); tile = codeflash_output # 907ns -> 880ns (3.07% faster)
    # Top-right
    codeflash_output = mercator.MetersToTile(mercator.originShift, mercator.originShift, 1); tile = codeflash_output # 803ns -> 799ns (0.501% faster)

def test_tile_size_parameter():
    # Changing tile size should change the tile indices
    m1 = GlobalMercator(tileSize=256)
    m2 = GlobalMercator(tileSize=512)
    # At zoom=1, for the same meters, tile indices should differ
    codeflash_output = m1.MetersToTile(0, 0, 1); tile1 = codeflash_output # 2.06μs -> 2.06μs (0.291% slower)
    codeflash_output = m2.MetersToTile(0, 0, 1); tile2 = codeflash_output # 1.07μs -> 1.05μs (1.61% faster)

# ------------------- EDGE TEST CASES -------------------

def test_negative_zoom_raises(mercator):
    # Negative zoom levels are not valid; should still work mathematically, but check behavior
    codeflash_output = mercator.MetersToTile(0, 0, -1); tile = codeflash_output # 2.60μs -> 2.51μs (3.55% faster)

def test_beyond_world_extent(mercator):
    # Coordinates beyond the world extent should still return a tile, but may be outside the normal range
    mx = mercator.originShift * 2
    my = mercator.originShift * 2
    codeflash_output = mercator.MetersToTile(mx, my, 2); tile = codeflash_output # 2.15μs -> 2.05μs (4.68% faster)

def test_just_inside_and_outside_tile_boundary(mercator):
    # Test points just inside and just outside a tile boundary
    # Use zoom=2 for more tiles
    z = 2
    res = mercator.Resolution(z)
    # The edge between tile (1,1) and (2,1) in x
    # Compute mx at the boundary between tile 1 and 2
    mx_boundary = -mercator.originShift + mercator.tileSize * res * 2
    my = 0
    # Just inside tile 1
    codeflash_output = mercator.MetersToTile(mx_boundary - 0.0001, my, z); tile_inside = codeflash_output # 1.98μs -> 1.92μs (3.29% faster)
    # Just outside (should be in tile 2)
    codeflash_output = mercator.MetersToTile(mx_boundary + 0.0001, my, z); tile_outside = codeflash_output # 1.10μs -> 1.07μs (2.71% faster)

def test_maximum_and_minimum_tile_indices(mercator):
    # At high zoom, check that the maximum and minimum tile indices are correct
    z = 10
    # The number of tiles per axis is 2^z
    num_tiles = 2 ** z
    # Lower left corner
    codeflash_output = mercator.MetersToTile(-mercator.originShift, -mercator.originShift, z); tile_ll = codeflash_output # 2.20μs -> 2.07μs (6.33% faster)
    # Upper right corner
    codeflash_output = mercator.MetersToTile(mercator.originShift, mercator.originShift, z); tile_ur = codeflash_output # 1.29μs -> 1.28μs (1.41% faster)

def test_non_integer_inputs(mercator):
    # Should handle float inputs for mx, my and zoom
    codeflash_output = mercator.MetersToTile(12345.678, -98765.4321, 3); tile = codeflash_output # 2.16μs -> 1.99μs (8.86% faster)


def test_full_world_grid_at_zoom_3(mercator):
    # At zoom=3, there are 8x8 tiles; test all tile boundaries
    z = 3
    num_tiles = 2 ** z
    res = mercator.Resolution(z)
    # For each tile, pick a point near the center and check that it maps to the correct tile
    for tx in range(num_tiles):
        for ty in range(num_tiles):
            mx = -mercator.originShift + (tx + 0.5) * mercator.tileSize * res
            my = -mercator.originShift + (ty + 0.5) * mercator.tileSize * res
            codeflash_output = mercator.MetersToTile(mx, my, z); tile = codeflash_output

def test_many_random_points_large_scale(mercator):
    # Test 1000 random points within valid world extent at zoom=5
    import random
    z = 5
    num_tiles = 2 ** z
    for _ in range(1000):
        mx = random.uniform(-mercator.originShift, mercator.originShift)
        my = random.uniform(-mercator.originShift, mercator.originShift)
        codeflash_output = mercator.MetersToTile(mx, my, z); tile = codeflash_output # 815μs -> 765μs (6.52% faster)

def test_large_tile_size_grid():
    # Test with a large tile size and a large zoom
    m = GlobalMercator(tileSize=1024)
    z = 8
    num_tiles = 2 ** z
    res = m.Resolution(z)
    for tx in range(min(num_tiles, 32)):  # Limit to 32 for speed
        for ty in range(min(num_tiles, 32)):
            mx = -m.originShift + (tx + 0.5) * m.tileSize * res
            my = -m.originShift + (ty + 0.5) * m.tileSize * res
            codeflash_output = m.MetersToTile(mx, my, z); tile = codeflash_output

def test_tile_indices_monotonicity(mercator):
    # For increasing mx and my, tile indices should not decrease
    z = 4
    res = mercator.Resolution(z)
    mx_prev = -mercator.originShift
    my_prev = -mercator.originShift
    tx_prev, ty_prev = mercator.MetersToTile(mx_prev, my_prev, z) # 2.29μs -> 2.27μs (1.10% faster)
    for i in range(1, 100):
        mx = -mercator.originShift + i * (2 * mercator.originShift) / 100
        my = -mercator.originShift + i * (2 * mercator.originShift) / 100
        tx, ty = mercator.MetersToTile(mx, my, z) # 80.6μs -> 76.0μs (6.07% faster)
        tx_prev, ty_prev = tx, ty
# 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-GlobalMercator.MetersToTile-mh4ja1bc and push.

Codeflash

The optimized code achieves a 5% speedup through two key micro-optimizations that reduce redundant computations in hot paths:

**What was optimized:**
1. **Pre-computed float conversion**: Added `self._tileSize_float = float(tileSize)` in `__init__()` and use it in `PixelsToTile()` instead of calling `float(self.tileSize)` on every invocation
2. **Local variable caching**: Store frequently accessed instance variables in local variables (`shift = self.originShift` in `MetersToPixels`, `tsf = self._tileSize_float` in `PixelsToTile`)

**Why it's faster:**
- **Eliminates repeated type conversions**: The original code called `float(self.tileSize)` twice per `PixelsToTile()` call. With thousands of tile calculations, this adds up significantly
- **Reduces attribute lookup overhead**: Python attribute access (`self.originShift`) is slower than local variable access (`shift`) due to dictionary lookups in the object's `__dict__`
- **Better CPU cache utilization**: Local variables stay in faster CPU registers/cache versus repeated memory access for instance attributes

**Performance characteristics:**
The optimizations show consistent 3-7% improvements across most test cases, with the best gains (7-10%) on tests with many coordinate conversions like `test_random_large_coordinates` and `test_many_random_points_large_scale`. The optimizations are particularly effective for batch processing scenarios where `MetersToTile()` is called repeatedly, making this ideal for tile generation workflows that process thousands of coordinates.
@codeflash-ai codeflash-ai bot requested a review from mashraf-222 October 24, 2025 07:32
@codeflash-ai codeflash-ai bot added ⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: High Optimization Quality according to Codeflash labels Oct 24, 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