Skip to content

Add FastBarIterator: native iterator accelerator for python-progressbar#15

Open
wolph wants to merge 1 commit into
developfrom
progressbar-iterator
Open

Add FastBarIterator: native iterator accelerator for python-progressbar#15
wolph wants to merge 1 commit into
developfrom
progressbar-iterator

Conversation

@wolph

@wolph wolph commented Jun 24, 2026

Copy link
Copy Markdown
Owner

Adds a small Cython cdef iterator (speedups.progressbar.FastBarIterator, "Approach B") that wraps an iterable and drives a progressbar2 ProgressBar, counting items in a C field so the per-item loop does no Python attribute writes. bar.value/previous_value are synced only at redraw crossings (~20x/sec, like tqdm.n) and once more at finish.

progressbar2 imports this automatically as the progressbar2[fast] extra and falls back to its pure-Python gate when absent. Measured ~5 ns/iter (~4x faster than rich, ~11x faster than tqdm) vs ~30 ns pure Python.

Protocol consumed from the bar: _fast_begin(), _fast_tick(value), _fast_end(), _fast_end_dirty() + plain attrs value/_next_update/_finished. __dealloc__ calls _fast_end_dirty() on early break/exception so redirected streams are restored (progressbar issue #212).

Includes a .pyi stub and indirect integration tests (Cython modules can't be measured by Python coverage). Companion progressbar2 PR: wolph/python-progressbar#316.

A small Cython cdef iterator (Approach B) that wraps an iterable and drives a
progressbar2 ProgressBar, counting items in a C field so the per-item loop does
no Python attribute writes. `bar.value`/`previous_value` are synced only at
redraw crossings (~20x/sec, like tqdm.n) and once more at finish.

The bar implements a small protocol consumed here: `_fast_begin()`,
`_fast_tick(value)`, `_fast_end()`, `_fast_end_dirty()` plus the plain
attributes `value`, `_next_update`, `_finished`. `__dealloc__` calls
`_fast_end_dirty()` when iteration is abandoned (break/exception) so redirected
streams are restored, matching the generator's GeneratorExit cleanup
(progressbar issue #212).

progressbar2 imports this automatically as the `progressbar2[fast]` extra and
falls back to its pure-Python gate when absent. Measured ~5 ns/iter (~4x faster
than rich) vs ~30 ns pure Python.

Includes a .pyi stub and indirect integration tests (fake bar) since Cython
modules can't be measured by Python coverage.
Copilot AI review requested due to automatic review settings June 24, 2026 02:12

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a native iterator accelerator (FastBarIterator) implemented in Cython to optimize the performance of progressbar2. It includes the Cython implementation, type stubs, setup configuration, and comprehensive integration tests using a mock progress bar. The feedback suggests catching BaseException instead of Exception in the __dealloc__ method of FastBarIterator to ensure no exceptions escape during teardown or interpreter shutdown.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread speedups/progressbar.pyx
if not bar._finished:
bar.value = self._value
bar._fast_end_dirty()
except Exception:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

In __dealloc__, using except Exception: may not catch all exceptions. Subclasses of BaseException (such as KeyboardInterrupt, SystemExit, or other interpreter shutdown-related exceptions) can still propagate. Since exceptions escaping __dealloc__ cannot be properly handled and will print a warning to stderr, it is safer to use except BaseException: to guarantee that absolutely no exception escapes during teardown.

            except BaseException:

Copilot AI left a comment

Copy link
Copy Markdown

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 introduces a new Cython-backed iterator accelerator (speedups.progressbar.FastBarIterator) intended to drive progressbar2’s ProgressBar via a small “fast protocol”, reducing per-iteration Python overhead by keeping the counter in a C field and only syncing state at redraw boundaries and at completion.

Changes:

  • Added FastBarIterator as a new Cython extension module (speedups/progressbar.pyx).
  • Added a matching type stub (speedups/progressbar.pyi) and new integration-style tests using a protocol-compatible fake bar (tests/test_progressbar.py).
  • Registered the new extension for build via setup.py.

Reviewed changes

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

File Description
speedups/progressbar.pyx Adds the Cython FastBarIterator implementation (fast protocol driver + __dealloc__ dirty-finish teardown).
speedups/progressbar.pyi Provides typing for the new native iterator module.
tests/test_progressbar.py Adds integration tests validating iteration order, tick crossings, and dealloc dirty-finish behavior.
setup.py Includes speedups.progressbar in the cythonized extension list.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread speedups/progressbar.pyx
Comment on lines +4 to +7
The item count lives in a C field; the per-item loop does no Python attribute
writes. ``bar.value``/``previous_value`` are synced to the bar only at redraw
crossings (~20x/sec, like ``tqdm.n``) and once more at finish, so they stay
plain attributes and no other code path pays any overhead.
Comment thread speedups/progressbar.pyi
Comment on lines +13 to +18
"""Iterates ``iterable``, driving ``bar`` and counting in a C field.

``bar.value``/``previous_value`` are synced only at redraw crossings
(~20x/sec, like ``tqdm.n``) and once more at finish, so the per-item loop
does no Python attribute writes.
"""
Comment thread tests/test_progressbar.py
Comment on lines +78 to +91
def test_break_finishes_dirty_on_dealloc():
bar = FakeBar(step=5)
it = FastBarIterator(bar, range(1000))
collected = []
for x in it:
collected.append(x)
if x == 7:
break
assert collected == list(range(8))
del it
gc.collect()
assert bar.ended == 'dirty'
assert bar._finished

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e4a228f691

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread speedups/progressbar.pyx
try:
item = next(self._it)
except StopIteration:
self._bar.value = self._value # sync final count

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Sync previous_value when finishing native iteration

When the iterable ends without the last item crossing _next_update (the common gated case), this clean-exhaustion path only writes bar.value; bar.previous_value remains whatever the last _fast_tick() left behind. The module contract says both value and previous_value are synced once more at finish, so widgets or callers that inspect previous_value after native iteration completes can observe stale state compared with the final count.

Useful? React with 👍 / 👎.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants