Skip to content

gh-100239: Specialize binary operations using BINARY_OP_EXTEND#128956

Open
eendebakpt wants to merge 9 commits intopython:mainfrom
eendebakpt:binary_op_list_list
Open

gh-100239: Specialize binary operations using BINARY_OP_EXTEND#128956
eendebakpt wants to merge 9 commits intopython:mainfrom
eendebakpt:binary_op_list_list

Conversation

@eendebakpt
Copy link
Copy Markdown
Contributor

@eendebakpt eendebakpt commented Jan 17, 2025

  • We add list and tuple concatenation to BINARY_OP_EXTEND
  • We pass type information in BINARY_OP_EXTEND in tier 2. This allows the jit to perform better optimizations.
  • In the jit we can now eliminate the _GUARD_BINARY_OP_EXTEND if type information is known.
  • The fraction of code specialized for BINARY_OP increases from 70% to 90%.

Benchmark are performance neutral (in the +- 1% range) is seems.

Benchmark script
"""Benchmark for BINARY_OP_EXTEND type propagation.

Tests whether the tier 2 optimizer can eliminate guards when types are
known from previous BINARY_OP_EXTEND results.

Usage:
    ./python bench_binary_op_extend.py
    ./python bench_binary_op_extend.py --save result.json
    ./python bench_binary_op_extend.py --compare a.json b.json
"""

import sys
import pyperf

INNER = 2000


def bench_list_concat_subscr(n):
    """list + list followed by subscript — tests list type propagation."""
    a = [1, 2, 3]
    b = [4, 5, 6]
    total = 0
    for _ in range(n):
        c = a + b
        total += c[0] + c[3]
    return total


def bench_tuple_concat_unpack(n):
    """tuple + tuple followed by unpack — tests tuple type propagation."""
    t1 = (1, 2)
    t2 = (3, 4)
    total = 0
    for _ in range(n):
        a, b, c, d = t1 + t2
        total += a + d
    return total


def bench_str_repeat(n):
    """str * int in a loop — tests str type propagation."""
    s = "ab"
    total = 0
    for i in range(n):
        r = s * (i % 5)
        total += len(r)
    return total


def bench_bytes_concat(n):
    """bytes + bytes in a loop — tests bytes type propagation."""
    a = b"hello"
    b_ = b" world"
    total = 0
    for _ in range(n):
        c = a + b_
        total += len(c)
    return total


def bench_bytes_repeat(n):
    """bytes * int in a loop — tests bytes type propagation."""
    b = b"ab"
    total = 0
    for i in range(n):
        r = b * (i % 3)
        total += len(r)
    return total


def bench_tuple_repeat(n):
    """tuple * int in a loop — tests tuple type propagation."""
    t = (1, 2, 3)
    total = 0
    for i in range(n):
        r = t * (i % 3)
        total += len(r)
    return total


def bench_dict_merge(n):
    """dict | dict in a loop — tests dict type propagation."""
    d1 = {"a": 1, "b": 2}
    d2 = {"c": 3, "d": 4}
    total = 0
    for _ in range(n):
        d = d1 | d2
        total += len(d)
    return total


def bench_chained_list_ops(n):
    """Multiple list ops chained — tests guard elimination across ops."""
    a = [1, 2]
    b = [3, 4]
    total = 0
    for _ in range(n):
        c = a + b
        d = c + a
        total += d[0] + d[4]
    return total


def bench_mixed_float_int(n):
    """float + int and int + float — existing EXTEND specializations."""
    x = 1.5
    total = 0.0
    for i in range(n):
        a = x + i
        total += a 
    return total

def float_mix_mul(n):
    """float + int then float * float — tests unique flag for inplace mul."""
    x = 1.5
    total = 0.0
    for i in range(n):
        a = (x + i) * 2.0  # result of x+i should be unique -> inplace multiply
        total += a
    return total

BENCHMARKS = [
    ("list_concat_subscr", bench_list_concat_subscr),
    ("tuple_concat_unpack", bench_tuple_concat_unpack),
    ("str_repeat", bench_str_repeat),
    ("bytes_concat", bench_bytes_concat),
    ("bytes_repeat", bench_bytes_repeat),
    ("tuple_repeat", bench_tuple_repeat),
    ("dict_merge", bench_dict_merge),
    ("chained_list_ops", bench_chained_list_ops),
    ("mixed_float_int", bench_mixed_float_int),
    ("float_mix_mul", float_mix_mul),
]
    

def main():
    args = sys.argv[1:]

    if "--compare" in args:
        idx = args.index("--compare")
        file_a = args[idx + 1]
        file_b = args[idx + 2]
        import subprocess
        subprocess.run([sys.executable, "-m", "pyperf", "compare_to",
                       file_a, file_b, "--table"])
        return

    save_file = None
    if "--save" in args:
        idx = args.index("--save")
        save_file = args[idx + 1]

    runner = pyperf.Runner()
    for name, func in BENCHMARKS:
        # Warm up
        func(INNER)
        runner.bench_func(name, func, INNER)

    if save_file and runner.args.output:
        import shutil
        shutil.copy(runner.args.output, save_file)


if __name__ == "__main__":
    main()

@eendebakpt eendebakpt force-pushed the binary_op_list_list branch from 03b3922 to 51d1b11 Compare April 5, 2026 20:00
# Conflicts:
#	Lib/test/test_capi/test_opt.py
#	Python/specialize.c
@eendebakpt eendebakpt marked this pull request as draft April 6, 2026 19:56
@eendebakpt eendebakpt changed the title gh-100239: Specialize concatenation of lists and tuples gh-100239: Specialize binary operations using BINARY_OP_EXTEND Apr 6, 2026
@markshannon
Copy link
Copy Markdown
Member

This looks good overall, but I've not done a detailed review.

I have a couple of general concerns about the BINARY_OP_EXTEND optimization in general, not in this PR, but something to keep in mind:

  • How do we ensure the robustness of the VM and optimizations when we expose this to 3rd party code as we intend to do at some point in the future?
  • As binaryop_extend_descrs gets larger, specialization of binary ops will get slower. Can we sort the array, or use a mapping to reduce the overhead? It shouldn't be a problem yet, but could be in the future if there were 100s of entries.

@eendebakpt eendebakpt marked this pull request as ready for review April 7, 2026 21:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants