0

I found that the Python 3.11.1 implementations of all() and any() are 30% slower and 23% slower respectively compared to Python 3.10.9

Any idea why this might be the case? (Unfortunately I don't have the skills to look through the underlying implementation of the CPython code)

import timeit
from functools import partial
import platform


def find_by_keys(
    keys: list[str],
    table: list[dict[str, str | int]],
    match_data: dict[str, str | int],
) -> dict[str, str | int] | None:
    for item in table:
        if all(item[k] == match_data[k] for k in keys):   # 30% slower on 3.11
        # if any(item[k] == match_data[k] for k in keys):   # 23% slower on 3.11
        # if item["id"] == match_data["id"]:   # 3% faster on 3.11
            return item
    return None


def main():
    keys: list[str] = ["id", "key_1", "key_2"]
    table: list[dict[str, str | int]] = [
        {
            "id": i,
            "key_1": "val_1",
            "key_2": "val_2",
        }
        for i in range(1, 5001)
    ]
    match_data: dict[str, str | int] = {
        "id": 3000,
        "key_1": "val_1",
        "key_2": "val_2",
    }

    # Note: used repeat=50000 for all() calls, otherwise used repeat=500000
    timeit_output = timeit.repeat(
        partial(find_by_keys, keys, table, match_data),
        repeat=50000,
        number=1
    )

    average_time = sum(timeit_output) / len(timeit_output)
    best_time = min(timeit_output)
    tps_output = 1 / average_time

    print(f"Python version = {platform.python_version()}")
    print(f"Average time = {average_time}")
    print(f"Best time = {best_time}")
    print(f"Average transactions per second = {tps_output}")


if __name__ == "__main__":
    main()

Results

Console output using Python 3.10.9:

Python version = 3.10.9
Average time = 0.0008256170657486655
Best time = 0.0007106999401003122
Average transactions per second = 1211.2152733824669

Console output using Python 3.11.1:

Python version = 3.11.1
Average time = 0.0011819988898839802
Best time = 0.001033599954098463
Average transactions per second = 846.0244832363215
wjandrea
  • 28,235
  • 9
  • 60
  • 81
  • 4
    Welcome to Stack Overflow. Please read [ask] and https://stackoverflow.com/help/on-topic. If you suspect that there is a problem with the Python implementation, that is a matter for the Python issue tracker, not here. That said, new versions of Python can't be universally faster at every task. Sometimes there are memory tradeoffs to be made, for example, and sometimes a design decision in implementing some data structure, will necessarily make some algorithms faster and others slower. – Karl Knechtel Jan 04 '23 at 05:31
  • 3
    That said, we can't be expected to diagnose the cause of a slowdown given hundreds of lines of code. Please also read [mre] and https://ericlippert.com/2014/03/05/how-to-debug-small-programs/. If you are interested in the performance of the code, then **use a profiler** in order to figure out specifically which parts are faster or slower, under which conditions, and identify a **specific task** that is unexpectedly slow. Note that the Python dev team does its own performance benchmarking, routinely comparing against older versions. – Karl Knechtel Jan 04 '23 at 05:32
  • 1
    Finally: "(Posted both on StackOverflow and Reddit for feedback)" is a **huge red flag**. Reddit is a discussion forum; Stack Overflow is **not**. This means we have fundamentally different expectations - we take topicality much more seriously, we [expect](https://meta.stackoverflow.com/questions/261592) a lot more from OPs, and we write posts [in very different style](https://meta.stackoverflow.com/questions/343721). – Karl Knechtel Jan 04 '23 at 05:33
  • Welcome to Stack Overflow! Please start with the [tour]. SO is a Q&A site, but this isn't a question. Maybe you want to ask *why* the newer version is slower, but you'd need to narrow down the problem first, like do some [profiling](/q/582336/4518341). (This is just to add to what Karl said.) – wjandrea Jan 04 '23 at 05:40
  • `repeat=50000` may be overkill. The test takes more than a minute to run. I reduced it to 1000 and I'm getting pretty comparable results, and it runs in like 2 seconds. – wjandrea Jan 04 '23 at 19:30
  • When you tested `any()`, did you change the data? If not, it'd return on the first `item` since `"key_1"` matches, so that doesn't seem like a comparable test. Instead I'd make `keys = ["id"]`, and if I do that, the difference for `any()` goes away, but the difference for `all()` is still there. – wjandrea Jan 04 '23 at 20:01
  • @wjandrea I didn't change the "key" when testing any(), it is as per the commented code above, since I thought the issue might be generator related, and it might be a fairer comparison with all(). You're right I should have changed the data thanks. Once I do change the data with table being set to [{"id": i, "key_1": "foo_1", "key_2": "foo_2"} for i in range(1, 5001)] it seems there still is a 9% performance degradation on Python 3.11 – Onlooker2186 Jan 04 '23 at 23:41

0 Answers0