Skip to content

⚡️ Speed up method GlobalGeodetic.PixelsToTile by 15%#21

Open
codeflash-ai[bot] wants to merge 1 commit into
masterfrom
codeflash/optimize-GlobalGeodetic.PixelsToTile-mh4zojev
Open

⚡️ Speed up method GlobalGeodetic.PixelsToTile by 15%#21
codeflash-ai[bot] wants to merge 1 commit into
masterfrom
codeflash/optimize-GlobalGeodetic.PixelsToTile-mh4zojev

Conversation

@codeflash-ai
Copy link
Copy Markdown

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

📄 15% (0.15x) speedup for GlobalGeodetic.PixelsToTile in opendm/tiles/gdal2tiles.py

⏱️ Runtime : 125 microseconds 108 microseconds (best of 132 runs)

📝 Explanation and details

The optimization achieves a 15% speedup by eliminating redundant computations and attribute lookups in the frequently called PixelsToTile method:

Key optimizations:

  1. Eliminated redundant float() calls: The original code called float(self.tileSize) twice per method invocation (once for each axis). The optimized version stores self.tileSize in a local variable ts and lets Python handle implicit float conversion during division, avoiding the function call overhead.

  2. Reduced attribute lookups: Instead of accessing self.tileSize twice per call, the optimized code accesses it once and stores it in the local variable ts. Attribute lookups are more expensive than local variable access in Python.

  3. Moved float conversion to initialization: In __init__, the division is now performed with a pre-converted float value, eliminating code duplication between the two branches.

Why this works:

  • Function calls like float() have overhead in Python's interpreter
  • Attribute lookups (self.tileSize) are slower than local variable access (ts)
  • The math.ceil() operation with division automatically handles the int-to-float conversion, making the explicit float() call unnecessary

Performance characteristics:
The optimization is most effective for:

  • High-frequency tile calculations (14-28% speedup across test cases)
  • Large-scale mapping operations where PixelsToTile is called thousands of times
  • Any tile size, though benefits are slightly more pronounced with standard sizes like 256x256

The line profiler shows the optimization successfully reduced per-hit execution time while maintaining identical mathematical behavior.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 265 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 GlobalGeodetic

# unit tests

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

def test_tile_origin():
    # Test pixel (0,0) should be in tile (0,0)
    geodetic = GlobalGeodetic(tmscompatible=True, tileSize=256)
    codeflash_output = geodetic.PixelsToTile(0, 0) # 1.36μs -> 1.19μs (14.2% faster)

def test_tile_exact_tile_size():
    # Test pixel at exactly one tile size should be in tile (1,1)
    geodetic = GlobalGeodetic(tmscompatible=True, tileSize=256)
    codeflash_output = geodetic.PixelsToTile(256, 256) # 1.25μs -> 1.07μs (16.5% faster)

def test_tile_just_before_next_tile():
    # Test pixel at one less than tile size should be in tile (0,0)
    geodetic = GlobalGeodetic(tmscompatible=True, tileSize=256)
    codeflash_output = geodetic.PixelsToTile(255, 255) # 1.17μs -> 1.00μs (17.2% faster)

def test_tile_middle_of_tile():
    # Test pixel in the middle of tile 0,0
    geodetic = GlobalGeodetic(tmscompatible=True, tileSize=256)
    codeflash_output = geodetic.PixelsToTile(128, 128) # 1.17μs -> 1.02μs (14.3% faster)

def test_tile_different_tile_sizes():
    # Test with a different tile size
    geodetic = GlobalGeodetic(tmscompatible=True, tileSize=512)
    codeflash_output = geodetic.PixelsToTile(512, 512) # 1.09μs -> 1.00μs (9.50% faster)
    codeflash_output = geodetic.PixelsToTile(511, 511) # 459ns -> 411ns (11.7% faster)
    codeflash_output = geodetic.PixelsToTile(1023, 1023) # 420ns -> 363ns (15.7% faster)
    codeflash_output = geodetic.PixelsToTile(1024, 1024) # 387ns -> 335ns (15.5% faster)

def test_tile_non_square_pixels():
    # Test with px != py
    geodetic = GlobalGeodetic(tmscompatible=True, tileSize=256)
    codeflash_output = geodetic.PixelsToTile(300, 100) # 1.15μs -> 968ns (19.2% faster)
    codeflash_output = geodetic.PixelsToTile(100, 300) # 490ns -> 465ns (5.38% faster)

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

def test_tile_negative_pixels():
    # Negative pixel coordinates should fall in tile -1,-1
    geodetic = GlobalGeodetic(tmscompatible=True, tileSize=256)
    codeflash_output = geodetic.PixelsToTile(-1, -1) # 1.23μs -> 1.01μs (21.3% faster)
    codeflash_output = geodetic.PixelsToTile(-256, -256) # 680ns -> 578ns (17.6% faster)
    codeflash_output = geodetic.PixelsToTile(-255, -255) # 456ns -> 394ns (15.7% faster)


def test_tile_float_pixels():
    # Floating point pixels should work
    geodetic = GlobalGeodetic(tmscompatible=True, tileSize=256)
    codeflash_output = geodetic.PixelsToTile(255.9, 255.9) # 1.63μs -> 1.43μs (14.4% faster)
    codeflash_output = geodetic.PixelsToTile(256.1, 256.1) # 500ns -> 462ns (8.23% faster)
    codeflash_output = geodetic.PixelsToTile(0.0, 0.0) # 487ns -> 435ns (12.0% faster)
    codeflash_output = geodetic.PixelsToTile(-0.1, -0.1) # 375ns -> 362ns (3.59% faster)

def test_tile_large_negative_pixels():
    # Very large negative pixels
    geodetic = GlobalGeodetic(tmscompatible=True, tileSize=256)
    codeflash_output = geodetic.PixelsToTile(-10000, -10000) # 1.38μs -> 1.22μs (12.8% faster)

def test_tile_large_tile_size():
    # Large tile size
    geodetic = GlobalGeodetic(tmscompatible=True, tileSize=1024)
    codeflash_output = geodetic.PixelsToTile(1023, 1023) # 1.18μs -> 996ns (18.9% faster)
    codeflash_output = geodetic.PixelsToTile(1024, 1024) # 447ns -> 424ns (5.42% faster)
    codeflash_output = geodetic.PixelsToTile(1025, 1025) # 409ns -> 381ns (7.35% faster)

def test_tile_zero_pixels():
    # Zero pixel coordinates
    geodetic = GlobalGeodetic(tmscompatible=True, tileSize=256)
    codeflash_output = geodetic.PixelsToTile(0, 0) # 1.16μs -> 1.09μs (6.80% faster)

def test_tile_max_tile_size():
    # Edge case where px and py are exactly the max allowed tileSize-1
    geodetic = GlobalGeodetic(tmscompatible=True, tileSize=256)
    codeflash_output = geodetic.PixelsToTile(255, 255) # 1.11μs -> 1.01μs (10.2% faster)

def test_tile_boundary_conditions():
    # Boundary conditions for tiles
    geodetic = GlobalGeodetic(tmscompatible=True, tileSize=256)
    # Just on the edge
    codeflash_output = geodetic.PixelsToTile(256, 0) # 1.16μs -> 1.06μs (9.13% faster)
    codeflash_output = geodetic.PixelsToTile(0, 256) # 524ns -> 449ns (16.7% faster)
    # Just before the edge
    codeflash_output = geodetic.PixelsToTile(255.9999, 255.9999) # 479ns -> 487ns (1.64% slower)

# -------------------------
# LARGE SCALE TEST CASES
# -------------------------

def test_tile_large_scale_many_tiles():
    # Test many tiles in a grid, checking determinism and scalability
    geodetic = GlobalGeodetic(tmscompatible=True, tileSize=256)
    for i in range(0, 1000, 100):  # px from 0 to 900, step 100
        for j in range(0, 1000, 100):  # py from 0 to 900, step 100
            tx_expected = int(math.ceil(i / 256.0) - 1)
            ty_expected = int(math.ceil(j / 256.0) - 1)
            codeflash_output = geodetic.PixelsToTile(i, j)

def test_tile_large_scale_high_pixel_values():
    # Test with very large pixel values
    geodetic = GlobalGeodetic(tmscompatible=True, tileSize=256)
    px = 256 * 999  # Large pixel value, but within 1000 tiles
    py = 256 * 999
    codeflash_output = geodetic.PixelsToTile(px, py) # 1.28μs -> 1.12μs (14.1% faster)
    # Test with px just before the next tile
    codeflash_output = geodetic.PixelsToTile(px + 255, py + 255) # 514ns -> 461ns (11.5% faster)
    # Test with px at the next tile
    codeflash_output = geodetic.PixelsToTile(px + 256, py + 256) # 419ns -> 378ns (10.8% faster)

def test_tile_large_scale_random_points():
    # Test a variety of random points within a large range
    geodetic = GlobalGeodetic(tmscompatible=True, tileSize=256)
    test_points = [
        (0, 0),
        (500, 500),
        (999, 999),
        (256 * 10, 256 * 20),
        (256 * 100, 256 * 200),
        (256 * 999, 256 * 999),
        (256 * 999 + 128, 256 * 999 + 128),
        (256 * 500, 256 * 900),
        (10000, 20000),
        (256 * 999 - 1, 256 * 999 - 1),
    ]
    for px, py in test_points:
        tx_expected = int(math.ceil(px / 256.0) - 1)
        ty_expected = int(math.ceil(py / 256.0) - 1)
        codeflash_output = geodetic.PixelsToTile(px, py) # 4.63μs -> 4.04μs (14.4% faster)

def test_tile_large_scale_different_tile_sizes():
    # Test with a variety of tile sizes and large pixel values
    for tile_size in [1, 16, 128, 256, 512, 999]:
        geodetic = GlobalGeodetic(tmscompatible=True, tileSize=tile_size)
        px = tile_size * 999
        py = tile_size * 999
        tx_expected = int(math.ceil(px / float(tile_size)) - 1)
        ty_expected = int(math.ceil(py / float(tile_size)) - 1)
        codeflash_output = geodetic.PixelsToTile(px, py) # 2.78μs -> 2.54μs (9.62% faster)
        # Also test just before the next tile
        codeflash_output = geodetic.PixelsToTile(px + tile_size - 1, py + tile_size - 1)
        # And at the next tile
        codeflash_output = geodetic.PixelsToTile(px + tile_size, py + tile_size) # 2.51μs -> 2.17μs (15.7% faster)
# 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  # used for our unit tests
from opendm.tiles.gdal2tiles import GlobalGeodetic

# unit tests

@pytest.mark.parametrize(
    "tileSize, px, py, expected",
    [
        # Basic: pixel (0,0) is in tile (0,0)
        (256, 0, 0, (0, 0)),
        # Basic: pixel (255,255) is in tile (0,0)
        (256, 255, 255, (0, 0)),
        # Basic: pixel (256,256) is in tile (1,1)
        (256, 256, 256, (1, 1)),
        # Basic: pixel (257,0) is in tile (1,0)
        (256, 257, 0, (1, 0)),
        # Basic: pixel (0,257) is in tile (0,1)
        (256, 0, 257, (0, 1)),
        # Basic: pixel (511,511) is in tile (1,1)
        (256, 511, 511, (1, 1)),
        # Basic: pixel (512,512) is in tile (2,2)
        (256, 512, 512, (2, 2)),
        # Basic: pixel (500,100) is in tile (1,0)
        (256, 500, 100, (1, 0)),
        # Basic: pixel (100,500) is in tile (0,1)
        (256, 100, 500, (0, 1)),
        # Basic: tileSize 128, pixel (129,129) is in tile (1,1)
        (128, 129, 129, (1, 1)),
        # Basic: tileSize 512, pixel (513,513) is in tile (1,1)
        (512, 513, 513, (1, 1)),
    ]
)
def test_pixels_to_tile_basic(tileSize, px, py, expected):
    """
    Basic Test Cases: Test PixelsToTile for typical pixel coordinates and various tile sizes.
    """
    g = GlobalGeodetic(tmscompatible=True, tileSize=tileSize)
    codeflash_output = g.PixelsToTile(px, py); result = codeflash_output # 14.2μs -> 11.8μs (20.8% faster)

@pytest.mark.parametrize(
    "tileSize, px, py, expected",
    [
        # Edge: negative pixel coordinates (should still work, returns -1 for -1, etc.)
        (256, -1, -1, (-1, -1)),
        # Edge: pixel exactly on tile boundary (should be in next tile)
        (256, 256, 0, (1, 0)),
        (256, 0, 256, (0, 1)),
        (256, 256, 256, (1, 1)),
        # Edge: just below tile boundary (should be in previous tile)
        (256, 255.9999, 255.9999, (0, 0)),
        # Edge: large negative pixel values
        (256, -256, -256, (-2, -2)),
        # Edge: floating point pixel coordinates
        (256, 128.5, 128.5, (0, 0)),
        (256, 256.1, 256.1, (1, 1)),
        # Edge: tileSize = 1 (every pixel is a new tile)
        (1, 0, 0, (0, 0)),
        (1, 1, 1, (1, 1)),
        (1, 100, 100, (100, 100)),
        # Edge: tileSize = very large
        (1024, 1023, 1023, (0, 0)),
        (1024, 1024, 1024, (1, 1)),
        # Edge: zero pixel coordinates, any tileSize
        (512, 0, 0, (0, 0)),
        (1024, 0, 0, (0, 0)),
    ]
)
def test_pixels_to_tile_edge(tileSize, px, py, expected):
    """
    Edge Test Cases: Test PixelsToTile for edge and corner cases, including negative and floating point values.
    """
    g = GlobalGeodetic(tmscompatible=True, tileSize=tileSize)
    codeflash_output = g.PixelsToTile(px, py); result = codeflash_output # 19.5μs -> 16.7μs (16.9% faster)

def test_pixels_to_tile_large_scale():
    """
    Large Scale Test Cases: Test PixelsToTile for performance and correctness with large pixel values and many sequential tiles.
    """
    tileSize = 256
    g = GlobalGeodetic(tmscompatible=True, tileSize=tileSize)
    # Test a range of pixel values up to 999*tileSize+tileSize-1 (ensures we test up to tile index 999)
    for tile_idx in range(0, 1000, 100):  # test every 100th tile to keep test fast
        px = tile_idx * tileSize
        py = tile_idx * tileSize
        expected = (tile_idx, tile_idx)
        codeflash_output = g.PixelsToTile(px, py); result = codeflash_output # 5.21μs -> 4.46μs (16.8% faster)

    # Test maximum pixel within 1000th tile
    px = 999 * tileSize + (tileSize - 1)
    py = 999 * tileSize + (tileSize - 1)
    expected = (999, 999)
    codeflash_output = g.PixelsToTile(px, py); result = codeflash_output # 403ns -> 344ns (17.2% faster)

    # Test just over 1000th tile boundary
    px = 1000 * tileSize
    py = 1000 * tileSize
    expected = (1000, 1000)
    codeflash_output = g.PixelsToTile(px, py); result = codeflash_output # 402ns -> 352ns (14.2% faster)

def test_pixels_to_tile_various_tilesizes_large():
    """
    Large Scale: Test PixelsToTile with different large tile sizes and pixel values.
    """
    for tileSize in [128, 256, 512, 1024]:
        g = GlobalGeodetic(tmscompatible=True, tileSize=tileSize)
        # For each tileSize, test a pixel in the 500th tile
        px = 500 * tileSize + (tileSize // 2)
        py = 500 * tileSize + (tileSize // 2)
        expected = (500, 500)
        codeflash_output = g.PixelsToTile(px, py); result = codeflash_output # 2.65μs -> 2.23μs (18.9% faster)

def test_pixels_to_tile_float_inputs():
    """
    Edge: Test PixelsToTile with floating point pixel values, including values very close to tile boundaries.
    """
    g = GlobalGeodetic(tmscompatible=True, tileSize=256)
    # Just below the boundary
    codeflash_output = g.PixelsToTile(255.9999, 255.9999) # 1.11μs -> 1.03μs (7.44% faster)
    # Exactly at the boundary
    codeflash_output = g.PixelsToTile(256.0, 256.0) # 499ns -> 448ns (11.4% faster)
    # Just above the boundary
    codeflash_output = g.PixelsToTile(256.0001, 256.0001) # 392ns -> 359ns (9.19% faster)
    # Negative floating point
    codeflash_output = g.PixelsToTile(-0.0001, -0.0001) # 468ns -> 422ns (10.9% faster)

def test_pixels_to_tile_non_tmscompatible():
    """
    Edge: Test PixelsToTile with tmscompatible=False (should not affect PixelsToTile behavior).
    """
    g = GlobalGeodetic(tmscompatible=False, tileSize=256)
    # The PixelsToTile function does not use tmscompatible in its calculation
    codeflash_output = g.PixelsToTile(0, 0) # 1.16μs -> 991ns (16.8% faster)
    codeflash_output = g.PixelsToTile(256, 256) # 566ns -> 534ns (5.99% faster)
    codeflash_output = g.PixelsToTile(511, 511) # 426ns -> 379ns (12.4% faster)

def test_pixels_to_tile_zero_tilesize():
    """
    Edge: Test PixelsToTile with tileSize=0 should raise ZeroDivisionError.
    """
    g = GlobalGeodetic(tmscompatible=True, tileSize=0)
    with pytest.raises(ZeroDivisionError):
        g.PixelsToTile(100, 100)

def test_pixels_to_tile_extremely_large_pixels():
    """
    Large Scale: Test PixelsToTile with extremely large pixel values.
    """
    g = GlobalGeodetic(tmscompatible=True, tileSize=256)
    px = 2**30
    py = 2**30
    expected_tile = (int(math.ceil(px / 256.0) - 1), int(math.ceil(py / 256.0) - 1))
    codeflash_output = g.PixelsToTile(px, py); result = codeflash_output # 1.32μs -> 1.03μs (27.8% faster)

def test_pixels_to_tile_extremely_small_pixels():
    """
    Edge: Test PixelsToTile with extremely small (negative) pixel values.
    """
    g = GlobalGeodetic(tmscompatible=True, tileSize=256)
    px = -2**30
    py = -2**30
    expected_tile = (int(math.ceil(px / 256.0) - 1), int(math.ceil(py / 256.0) - 1))
    codeflash_output = g.PixelsToTile(px, py); result = codeflash_output # 1.04μs -> 881ns (17.7% faster)
# 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-GlobalGeodetic.PixelsToTile-mh4zojev and push.

Codeflash

The optimization achieves a **15% speedup** by eliminating redundant computations and attribute lookups in the frequently called `PixelsToTile` method:

**Key optimizations:**

1. **Eliminated redundant `float()` calls**: The original code called `float(self.tileSize)` twice per method invocation (once for each axis). The optimized version stores `self.tileSize` in a local variable `ts` and lets Python handle implicit float conversion during division, avoiding the function call overhead.

2. **Reduced attribute lookups**: Instead of accessing `self.tileSize` twice per call, the optimized code accesses it once and stores it in the local variable `ts`. Attribute lookups are more expensive than local variable access in Python.

3. **Moved float conversion to initialization**: In `__init__`, the division is now performed with a pre-converted float value, eliminating code duplication between the two branches.

**Why this works:**
- Function calls like `float()` have overhead in Python's interpreter
- Attribute lookups (`self.tileSize`) are slower than local variable access (`ts`) 
- The `math.ceil()` operation with division automatically handles the int-to-float conversion, making the explicit `float()` call unnecessary

**Performance characteristics:**
The optimization is most effective for:
- **High-frequency tile calculations** (14-28% speedup across test cases)
- **Large-scale mapping operations** where `PixelsToTile` is called thousands of times
- **Any tile size**, though benefits are slightly more pronounced with standard sizes like 256x256

The line profiler shows the optimization successfully reduced per-hit execution time while maintaining identical mathematical behavior.
@codeflash-ai codeflash-ai Bot requested a review from mashraf-222 October 24, 2025 15:11
@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