Skip to content

ext/pdo_pgsql: Parse integer columns inline - #24385

Open
brainexe wants to merge 1 commit into
php:masterfrom
brainexe:perf/pdo-pgsql-inline-int-parse
Open

brainexe wants to merge 1 commit into
php:masterfrom
brainexe:perf/pdo-pgsql-inline-int-parse

Conversation

@brainexe

@brainexe brainexe commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

After some more profiling of our heavy workload with tidewayy/perf etc in came up this optimization.

PostgreSQL sends int2/int4/int8 values in text format. pgsql_stmt_get_col() converted each of them with ZEND_ATOL(), i.e. strtoll(), which handles whitespace, signs, bases and locales. In profiles of integer-heavy fetches, glibc's strtoll accounted for up to 10% of the CPU time.

PostgreSQL always sends these in canonical decimal form: an optional minus sign followed by digits without leading zeros. That is exactly what ZEND_HANDLE_NUMERIC_STR() accepts for numeric array keys, including the range check for zend_long. This PR uses it instead of ZEND_ATOL(), and keeps ZEND_ATOL() as fallback for anything else, so the results are unchanged.

A hand-written loop was about 3 percentage points faster, but reusing the existing helper avoids adding another integer parser. is_numeric_string() only gave about 2%, because it also handles whitespace, floats and overflow to double.

Benchmark

fetchAll(PDO::FETCH_ASSOC) of 1M rows with 4 integer columns, compared to master (perf stat, mean of 5 runs):

master this PR
instructions 3228M 2866M −11%
cycles 785M 712M −9%

Mixed workloads gain less, depending on how many columns are integers. For example, fetchAll of 10k rows with 11 columns, 2 of them integers, executes about 2% fewer instructions.

Verification

  • New test ext/pdo_pgsql/tests/fetch_integers.phpt (64-bit only) covers int2, int4 and int8 at their limits and around the 18/19-digit threshold, including PHP_INT_MIN (19 digits).
  • Differential check against master: 200k rows of random int2/int4/int8 values with 1–19 digits, both signs and the extreme values. The fetched values and their types are identical.
  • The ext/pdo and ext/pdo_pgsql test suites pass with native prepares. With emulated prepares forced on, the same tests fail as on master.

Comment thread ext/pdo_pgsql/pgsql_statement.c Outdated

/* PostgreSQL sends integers as an optional minus sign followed by digits. Parse that
* inline, and leave long values that might not fit into a zend_long to ZEND_ATOL(). */
static zend_always_inline zend_long pgsql_parse_long(const char *ptr, size_t len)

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.

hmmm feels like "reinventing the wheel" here.

PostgreSQL sends int2/int4/int8 values in text format in canonical
decimal form: an optional minus sign followed by digits without leading
zeros. That is exactly what ZEND_HANDLE_NUMERIC_STR() accepts for
numeric array keys, including the range check. Use it instead of
calling strtoll() through ZEND_ATOL() for every value, and keep
ZEND_ATOL() as fallback for anything else.

fetchAll(PDO::FETCH_ASSOC) of 1M rows with 4 integer columns:
-11% instructions, -9% cycles.
@brainexe
brainexe force-pushed the perf/pdo-pgsql-inline-int-parse branch from dd772c9 to 3e479d7 Compare October 17, 2026 19:07
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