Repository navigation
ext/pdo: Size PDO::FETCH_BOTH rows for both keys of each column - #24565
Merged
Merged
Conversation
PDO::FETCH_BOTH, the default fetch mode, stores each value under its column name and its index, but the row array was sized for one entry per column. Rows with more than 4 columns were therefore resized and rehashed once per fetched row. The final array size is unchanged. 50x fetchAll(PDO::FETCH_BOTH) of 10k rows with 11 columns (pdo_pgsql): -4.9% instructions, -9% cycles
iliaal
approved these changes
Oct 23, 2026
iliaal
left a comment
Member
There was a problem hiding this comment.
Looks fine to me, the only corner case (very rare) is when numeric column names are used and this would result and double allocation, but that is corner case and common case would benefit from this change.
devnexen
approved these changes
Oct 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PDO::FETCH_BOTHis the default fetch mode. It stores every value twice, under the column name and under the column index, butdo_fetch()created the row array witharray_init_size(return_value, stmt->column_count), i.e. room for one entry per column. With the minimum hash size of 8, every row with 5 or more columns therefore filled its table halfway through and was resized and rehashed once, for every fetched row.This PR sizes the array for
2 * column_countentries inFETCH_BOTHmode. The array ends up with the same size as before, it just gets there without the intermediate table. Other fetch modes are unchanged. (PDO_FETCH_USE_DEFAULTis resolved to the statement's default mode at the start ofdo_fetch(), so the default case is covered.)Benchmark
Release build (
-O2 -g -fno-omit-frame-pointer), opcache on, JIT off, pinned to one P-core,perf stat -r5, user-space counters. Compared to mastera2e4d85bf31.pdo_pgsql,
fetchAll(PDO::FETCH_BOTH)of 10k rows with 11 columns (int8, text ×4, timestamptz ×2, numeric, int4, bool, float8; one nullable), 50 times, PostgreSQL 18 on localhost:pdo_sqlite, in-memory table with N columns (alternating integer and text),
fetchAll(PDO::FETCH_BOTH)of 10k rows, 20 times. The numbers include creating and filling the table, so the relative gain is smaller than for the fetch alone:Cycles move by −4.7% (5 columns) to −6.2% (20 columns). Controls:
FETCH_ASSOC(pgsql 10k×11 and sqlite 11 columns) and a prepare/execute/fetch-one loop stay within the noise of ±1%.Memory: unchanged.
fetchAll(FETCH_BOTH)of the 10k×11 pgsql result retains 17.00 MB and peaks at 17.42 MB on both builds.Verification
ext/pdo,ext/pdo_pgsql,ext/pdo_sqlitetest suites pass.FETCH_BOTHis the default mode, so most existing tests exercise it, including duplicate and numeric column names.