Skip to content

Executor job add fast path#162758

Closed
erwindouna wants to merge 5 commits into
home-assistant:devfrom
erwindouna:exectur-job-fast-path
Closed

Executor job add fast path#162758
erwindouna wants to merge 5 commits into
home-assistant:devfrom
erwindouna:exectur-job-fast-path

Conversation

@erwindouna
Copy link
Copy Markdown
Member

@erwindouna erwindouna commented Feb 10, 2026

Breaking change

Proposed change

The async_add_executor_job is rightfully considered an expensive resource. Upon digging in the code, I am in the assumption I found a micro-optimization, which introduces a fast path and hot path code pattern.

Add an internal ha_background flag to background tasks and use it as a fast path in async_add_executor_job to avoid a set-member. I'm trying to leverage here, that an attribute check is faster than computing a hash, then check if there's a hit. Talking about micro/nanoseconds, but I thought it was worth a shot in profiling this:

image

Type of change

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New integration (thank you!)
  • New feature (which adds functionality to an existing integration)
  • Deprecation (breaking change to happen in the future)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Additional information

  • This PR fixes or closes issue: fixes #
  • This PR is related to issue:
  • Link to documentation pull request:
  • Link to developer documentation pull request:
  • Link to frontend pull request:

Checklist

  • I understand the code I am submitting and can explain how it works.
  • The code change is tested and works locally.
  • Local tests pass. Your PR cannot be merged unless tests pass
  • There is no commented out code in this PR.
  • I have followed the development checklist
  • I have followed the perfect PR recommendations
  • The code has been formatted using Ruff (ruff format homeassistant tests)
  • Tests have been added to verify that the new code works.
  • Any generated code has been carefully reviewed for correctness and compliance with project standards.

If user exposed functionality or configuration variables are added/changed:

If the code communicates with devices, web services, or third-party tools:

  • The manifest file has all fields filled out correctly.
    Updated and included derived files by running: python3 -m script.hassfest.
  • New or updated dependencies have been added to requirements_all.txt.
    Updated by running python3 -m script.gen_requirements_all.
  • For the updated dependencies a diff between library versions and ideally a link to the changelog/release notes is added to the PR description.

To help with the load of incoming pull requests:

@home-assistant home-assistant Bot added cla-signed core small-pr PRs with less than 30 lines. labels Feb 10, 2026
@erwindouna erwindouna marked this pull request as ready for review February 10, 2026 23:12
@erwindouna erwindouna requested a review from a team as a code owner February 10, 2026 23:12
Copilot AI review requested due to automatic review settings February 10, 2026 23:12
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to micro-optimize HomeAssistant.async_add_executor_job by adding a fast path to decide whether an executor future should be tracked as a normal task or a background task, based on a marker set on background tasks created by async_create_background_task.

Changes:

  • Add a marker attribute to tasks created via async_create_background_task.
  • Use that marker as a fast path in async_add_executor_job to avoid a set membership check on the common background-task path.

Comment thread homeassistant/core.py Outdated
Comment thread homeassistant/core.py Outdated
@epenet epenet marked this pull request as draft February 11, 2026 07:19
@erwindouna erwindouna marked this pull request as ready for review February 11, 2026 08:22
@MartinHjelmare
Copy link
Copy Markdown
Member

I'm wondering about how you profiled this. Was it done before or after the latest fix?

@MartinHjelmare MartinHjelmare marked this pull request as draft February 11, 2026 11:40
@erwindouna
Copy link
Copy Markdown
Member Author

erwindouna commented Feb 11, 2026

Benchmark code added. There are two commits to check, one containing the original and potential optimized code. You can run the benchmark via: python -m homeassistant --script benchmark executor_job.

Here's a copy of three runs on my machine: first the change and second the original code.bThe potential optimization is, as mentioned a micro-optimization, so it's marginal, as expected. Curious to see what it does on your machines and if it's worth proceeding.

(ha-venv) vscode ➜ /workspaces/ha-core (exectur-job-fast-path) $ python -m homeassistant --script benchmark executor_job              

Using event loop: _UnixSelectorEventLoop
WARNING:asyncio:Executing <Task pending name='Task-1' coro=<run_benchmark() running at /workspaces/ha-core/homeassistant/scripts/benchmark/__init__.py:49> wait_for=<Future pending cb=[Task.task_wakeup()] created at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/base_events.py:459> cb=[_run_until_complete_cb() at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/base_events.py:181] created at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/runners.py:109> took 4.438 seconds
Benchmark executor_job done in 4.535403819000294s
WARNING:asyncio:Executing <Task pending name='Task-4' coro=<run_benchmark() running at /workspaces/ha-core/homeassistant/scripts/benchmark/__init__.py:49> wait_for=<Future pending cb=[Task.task_wakeup()] created at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/base_events.py:459> cb=[_run_until_complete_cb() at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/base_events.py:181] created at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/runners.py:109> took 3.868 seconds
Benchmark executor_job done in 3.975433449999855s
^CWARNING:asyncio:Executing <Task cancelling name='Task-7' coro=<run_benchmark() running at /workspaces/ha-core/homeassistant/scripts/benchmark/__init__.py:49> wait_for=<Future cancelled created at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/base_events.py:459> cb=[_run_until_complete_cb() at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/base_events.py:181] created at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/runners.py:109> took 4.712 seconds
(ha-venv) vscode ➜ /workspaces/ha-core (exectur-job-fast-path) $ python -m homeassistant --script benchmark executor_job

Using event loop: _UnixSelectorEventLoop
WARNING:asyncio:Executing <Task pending name='Task-1' coro=<run_benchmark() running at /workspaces/ha-core/homeassistant/scripts/benchmark/__init__.py:49> wait_for=<Future pending cb=[Task.task_wakeup()] created at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/base_events.py:459> cb=[_run_until_complete_cb() at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/base_events.py:181] created at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/runners.py:109> took 4.442 seconds
Benchmark executor_job done in 4.553718712000318s
WARNING:asyncio:Executing <Task pending name='Task-4' coro=<run_benchmark() running at /workspaces/ha-core/homeassistant/scripts/benchmark/__init__.py:49> wait_for=<Future pending cb=[Task.task_wakeup()] created at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/base_events.py:459> cb=[_run_until_complete_cb() at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/base_events.py:181] created at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/runners.py:109> took 3.922 seconds
Benchmark executor_job done in 4.021768822000013s
^CWARNING:asyncio:Executing <Task cancelling name='Task-7' coro=<run_benchmark() running at /workspaces/ha-core/homeassistant/scripts/benchmark/__init__.py:49> wait_for=<Future cancelled created at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/base_events.py:459> cb=[_run_until_complete_cb() at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/base_events.py:181] created at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/runners.py:109> took 4.319 seconds

@erwindouna erwindouna marked this pull request as ready for review February 11, 2026 12:59
@MartinHjelmare
Copy link
Copy Markdown
Member

MartinHjelmare commented Feb 11, 2026

I think we should remove the commented code and clean up the commits and rebase to have two clean commits, so we can test this easily.

@MartinHjelmare MartinHjelmare marked this pull request as draft February 11, 2026 13:21

@benchmark
async def executor_job(hass: core.HomeAssistant) -> float:
"""Schedule executor jobs from multiple background tasks."""
Copy link
Copy Markdown
Member

@MartinHjelmare MartinHjelmare Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should add another benchmark for non background tasks (foreground), so we can check that those are not negatively impacted by the change.

Copy link
Copy Markdown
Member Author

@erwindouna erwindouna Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added. Same sequence, first the potential optimized and then the original code. I spot a slight improvement, still a micro-optimization. But at this stage I think it goes up in the noise and is barely noticable.

vscode ➜ /workspaces/ha-core (exectur-job-fast-path) $ source /home/vscode/.local/ha-venv/bin/activate
(ha-venv) vscode ➜ /workspaces/ha-core (exectur-job-fast-path) $ python -m homeassistant --script benchmark executor_job_foreground
Using event loop: _UnixSelectorEventLoop
WARNING:asyncio:Executing <Task pending name='Task-1' coro=<run_benchmark() running at /workspaces/ha-core/homeassistant/scripts/benchmark/__init__.py:49> wait_for=<Future pending cb=[Task.task_wakeup()] created at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/base_events.py:459> cb=[_run_until_complete_cb() at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/base_events.py:181] created at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/runners.py:109> took 4.125 seconds
Benchmark executor_job_foreground done in 4.227339091000431s
WARNING:asyncio:Executing <Task pending name='Task-4' coro=<run_benchmark() running at /workspaces/ha-core/homeassistant/scripts/benchmark/__init__.py:49> wait_for=<Future pending cb=[Task.task_wakeup()] created at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/base_events.py:459> cb=[_run_until_complete_cb() at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/base_events.py:181] created at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/runners.py:109> took 4.137 seconds
Benchmark executor_job_foreground done in 4.243188408000606s
WARNING:asyncio:Executing <Task pending name='Task-7' coro=<run_benchmark() running at /workspaces/ha-core/homeassistant/scripts/benchmark/__init__.py:49> wait_for=<Future pending cb=[Task.task_wakeup()] created at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/base_events.py:459> cb=[_run_until_complete_cb() at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/base_events.py:181] created at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/runners.py:109> took 3.895 seconds
Benchmark executor_job_foreground done in 4.008277234000161s
^CWARNING:asyncio:Executing <Task cancelling name='Task-10' coro=<run_benchmark() running at /workspaces/ha-core/homeassistant/scripts/benchmark/__init__.py:49> wait_for=<Future cancelled created at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/base_events.py:459> cb=[_run_until_complete_cb() at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/base_events.py:181] created at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/runners.py:109> took 4.203 seconds

(ha-venv) vscode ➜ /workspaces/ha-core (exectur-job-fast-path) $ python -m homeassistant --script benchmark executor_job_foreground
Using event loop: _UnixSelectorEventLoop
WARNING:asyncio:Executing <Task pending name='Task-1' coro=<run_benchmark() running at /workspaces/ha-core/homeassistant/scripts/benchmark/__init__.py:49> wait_for=<Future pending cb=[Task.task_wakeup()] created at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/base_events.py:459> cb=[_run_until_complete_cb() at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/base_events.py:181] created at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/runners.py:109> took 4.144 seconds
Benchmark executor_job_foreground done in 4.2441349760001685s
WARNING:asyncio:Executing <Task pending name='Task-4' coro=<run_benchmark() running at /workspaces/ha-core/homeassistant/scripts/benchmark/__init__.py:49> wait_for=<Future pending cb=[Task.task_wakeup()] created at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/base_events.py:459> cb=[_run_until_complete_cb() at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/base_events.py:181] created at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/runners.py:109> took 4.022 seconds
Benchmark executor_job_foreground done in 4.125795158999608s
WARNING:asyncio:Executing <Task pending name='Task-7' coro=<run_benchmark() running at /workspaces/ha-core/homeassistant/scripts/benchmark/__init__.py:49> wait_for=<Future pending cb=[Task.task_wakeup()] created at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/base_events.py:459> cb=[_run_until_complete_cb() at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/base_events.py:181] created at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/runners.py:109> took 4.087 seconds
Benchmark executor_job_foreground done in 4.192483848999473s
^CWARNING:asyncio:Executing <Task cancelling name='Task-10' coro=<run_benchmark() running at /workspaces/ha-core/homeassistant/scripts/benchmark/__init__.py:49> wait_for=<Future cancelled created at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/base_events.py:459> cb=[_run_until_complete_cb() at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/base_events.py:181] created at /home/vscode/.local/share/uv/python/cpython-3.14.3-linux-x86_64-gnu/lib/python3.14/asyncio/runners.py:109> took 4.145 seconds

Comment thread homeassistant/core.py Outdated
# Use loop.create_task
# to avoid the extra function call in asyncio.create_task.
task = self.loop.create_task(target, name=name)
setattr(task, "ha_background", True)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should investigate setting an attribute on the foreground tasks that are tracked too.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you wish to first run the benchmarks yourself and see if it's worth proceeding? Or, you like the idea and give green light that I continue to work it out.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Brought it to two commits again and included the foreground tasks as an attribute. I'm very curious to see how your benchmark results are looking.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can probably also remove the current variable at this stage. But let's see first on how you want to progress.

@erwindouna erwindouna force-pushed the exectur-job-fast-path branch from 429f496 to 7f19cae Compare February 11, 2026 13:27
@erwindouna erwindouna marked this pull request as ready for review February 11, 2026 13:37
@erwindouna erwindouna force-pushed the exectur-job-fast-path branch from eecacf1 to 72a0f83 Compare February 11, 2026 18:50
Copilot AI review requested due to automatic review settings March 12, 2026 14:17
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.


You can also share your feedback on Copilot code review. Take the survey.

Comment thread homeassistant/scripts/benchmark/__init__.py
Comment thread homeassistant/scripts/benchmark/__init__.py
Comment thread homeassistant/scripts/benchmark/__init__.py
Comment thread homeassistant/core.py
Comment thread homeassistant/core.py
Copilot AI review requested due to automatic review settings June 1, 2026 15:12
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

Comment thread homeassistant/core.py
Comment on lines 840 to 845
# Use loop.create_task
# to avoid the extra function call in asyncio.create_task.
task = self.loop.create_task(target, name=name)
setattr(task, "ha_background_task", True)
self._background_tasks.add(task)
task.add_done_callback(self._background_tasks.remove)
Comment thread homeassistant/core.py
Comment on lines +855 to +858
current = asyncio.current_task()
# In this code I skip an extra (el)if, because we default to background_tasks
# Both tasks are now labeled and I believe this is the fastest approach
task_bucket = (
Comment on lines +67 to +77
def _executor_func() -> None:
"""Run in executor."""
nonlocal count
count += 1

@core.callback
def _check_done(future: asyncio.Future) -> None:
"""Check if all jobs are done."""
if count == jobs_to_run:
event.set()

Comment on lines +102 to +112
def _executor_func() -> None:
"""Run in executor."""
nonlocal count
count += 1

@core.callback
def _check_done(future: asyncio.Future) -> None:
"""Check if all jobs are done."""
if count == jobs_to_run:
event.set()

@emontnemery
Copy link
Copy Markdown
Contributor

emontnemery commented Jun 1, 2026

I ran 50 loops each of the two added benchmarks with PR code and the original code, and I can't find any evidence that this PR improves the runtime of the code.

If anything, the executor_job_background runs slightly slower, both median and on average, with the modified code.

I'm not sure this is a viable concept.

@erwindouna I'm closing this now, please don't hesitate to open a new PR if you can reliably show the change in the PR improves performance.

@emontnemery emontnemery marked this pull request as draft June 1, 2026 15:22
@emontnemery emontnemery closed this Jun 1, 2026
@erwindouna
Copy link
Copy Markdown
Member Author

It was a micro-optimization and my benchmark was 4 months old already. Not quite sure what changed in the main time.
Bottom line is that it should improve, which it now apparently no longer shows.
Maybe I'll pick it up later. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants