Skip to content

perf(config): cache sys getenv#3670

Open
morrisonlevi wants to merge 2 commits intomasterfrom
levi/cache-getenv
Open

perf(config): cache sys getenv#3670
morrisonlevi wants to merge 2 commits intomasterfrom
levi/cache-getenv

Conversation

@morrisonlevi
Copy link
Collaborator

@morrisonlevi morrisonlevi commented Feb 24, 2026

Background

In request initialization aka rinit/activate, calling the system getenv when a SAPI env var isn't found is slow enough it shows up in profiles:

image (1)

Note that glibc's getenv does a linear scan on the environ and does strncmp on the members, so we can definitely do better.

Description

Caches libc's getenv in minit and again in first rinit. Note that php-fpm's env directive actually changes the system env var, it doesn't include it in the fastcgi environment. This change is also more correct: system environment variables map to a system INI setting, which shouldn't change at runtime, so if they do, we shouldn't really respect it.

Here's an example diff from the profiling comparison view:

Screenshot 2026-02-26 at 1 56 45 PM

Reviewer checklist

  • Test coverage seems ok.
  • Appropriate labels assigned.

@morrisonlevi morrisonlevi changed the title perf(config): caches sys getenv on minit perf(config): cache sys getenv on minit Feb 24, 2026
@datadog-official
Copy link

datadog-official bot commented Feb 24, 2026

⚠️ Tests

Fix all issues with BitsAI or with Cursor

⚠️ Warnings

🧪 1028 Tests failed

testSearchPhpBinaries from integration.DDTrace\Tests\Integration\PHPInstallerTest (Datadog) (Fix with Cursor)
DDTrace\Tests\Integration\PHPInstallerTest::testSearchPhpBinaries
Test code or tested code printed unexpected output: Searching for available php binaries, this operation might take a while.
testSimplePushAndProcess from laravel-58-test.DDTrace\Tests\Integrations\Laravel\V5_8\QueueTest (Datadog) (Fix with Cursor)
DDTrace\Tests\Integrations\Laravel\V5_8\QueueTest::testSimplePushAndProcess
Test code or tested code printed unexpected output: spanLinksTraceId: 699ee8e500000000c3b90521ff071a26
tid: 699ee8e500000000
hexProcessTraceId: c3b90521ff071a26
hexProcessSpanId: b88f6e41af859e1c
processTraceId: 14103309351658134054
processSpanId: 13298969453045063196

phpvfscomposer://tests/vendor/phpunit/phpunit/phpunit:106
testSimplePushAndProcess from laravel-8x-test.DDTrace\Tests\Integrations\Laravel\V8_x\QueueTest (Datadog) (Fix with Cursor)
DDTrace\Tests\Integrations\Laravel\V8_x\QueueTest::testSimplePushAndProcess
Test code or tested code printed unexpected output: spanLinksTraceId: 699ee7680000000089cb7bd0e9c8e652
tid: 699ee76800000000
hexProcessTraceId: 89cb7bd0e9c8e652
hexProcessSpanId: 03c9e6fa682c9559
processTraceId: 9929165940674061906
processSpanId: 273003215596590425
View all

ℹ️ Info

❄️ No new flaky tests detected

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: e26db1d | Docs | Datadog PR Page | Was this helpful? React with 👍/👎 or give us feedback!

@codecov-commenter
Copy link

codecov-commenter commented Feb 24, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 62.08%. Comparing base (25893da) to head (5a292ea).

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##           master    #3670   +/-   ##
=======================================
  Coverage   62.08%   62.08%           
=======================================
  Files         141      141           
  Lines       13352    13352           
  Branches     1746     1746           
=======================================
  Hits         8290     8290           
  Misses       4269     4269           
  Partials      793      793           

Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 25893da...5a292ea. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@pr-commenter
Copy link

pr-commenter bot commented Feb 24, 2026

Benchmarks [ tracer ]

Benchmark execution time: 2026-02-25 13:22:47

Comparing candidate commit e26db1d in PR branch levi/cache-getenv with baseline commit 07b618a in branch master.

Found 2 performance improvements and 6 performance regressions! Performance is the same for 186 metrics, 0 unstable metrics.

scenario:ComposerTelemetryBench/benchTelemetryParsing

  • 🟥 execution_time [+0.862µs; +1.738µs] or [+7.559%; +15.248%]

scenario:HookBench/benchHookOverheadTraceMethod-opcache

  • 🟥 execution_time [+3.551µs; +13.430µs] or [+2.047%; +7.743%]

scenario:MessagePackSerializationBench/benchMessagePackSerialization

  • 🟩 execution_time [-10.760µs; -8.940µs] or [-9.767%; -8.115%]

scenario:MessagePackSerializationBench/benchMessagePackSerialization-opcache

  • 🟩 execution_time [-9.814µs; -8.906µs] or [-8.814%; -7.999%]

scenario:SamplingRuleMatchingBench/benchRegexMatching1

  • 🟥 execution_time [+99.665ns; +155.935ns] or [+8.657%; +13.544%]

scenario:SamplingRuleMatchingBench/benchRegexMatching2

  • 🟥 execution_time [+113.060ns; +172.940ns] or [+9.848%; +15.064%]

scenario:SamplingRuleMatchingBench/benchRegexMatching3

  • 🟥 execution_time [+111.600ns; +184.000ns] or [+9.596%; +15.821%]

scenario:SamplingRuleMatchingBench/benchRegexMatching4

  • 🟥 execution_time [+113.780ns; +153.620ns] or [+9.918%; +13.391%]

@morrisonlevi morrisonlevi changed the title perf(config): cache sys getenv on minit perf(config): cache sys getenv Feb 24, 2026
In rinit, calling system getenv when a SAPI env var isn't found is
slow enough it shows up in profiles. glibc's getenv does a linear
scan on the environ and does strncmp on the members, so we can
definitely do better.

This is also arguably more correct: system environment variables map
to a system INI setting, which shouldn't change at runtime.
@morrisonlevi morrisonlevi marked this pull request as ready for review February 27, 2026 09:47
@morrisonlevi morrisonlevi requested a review from a team as a code owner February 27, 2026 09:47
(void)in_request;
#endif

if (in_request) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

We only need it for FPM, right? So let's add a sapi_module check here?
Saving some minor time on CLI scripts.

if (!cached->ptr) return false;
if (cached->len >= buf.len) return false;

memcpy(buf.ptr, cached->ptr, cached->len + 1);
Copy link
Collaborator

@bwoebi bwoebi Feb 27, 2026

Choose a reason for hiding this comment

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

There's not really an intrinsic need to copy. The copying in zai_getenv_ex is also unnecessary, but here we very clearly don't need it (but yeah, you'll have to duplicate branches then).
I'd personally return a simple zai_str instead of zai_env_buffer.

Comment on lines +15 to +22
typedef struct {
// ptr == NULL means env var was unset at MINIT.
// ptr != NULL with len == 0 means env var was set to an empty string.
char *ptr;
size_t len;
} zai_config_cached_env_value;

static zai_config_cached_env_value zai_config_cached_env_values[ZAI_CONFIG_ENTRIES_COUNT_MAX][ZAI_CONFIG_NAMES_COUNT_MAX];
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
typedef struct {
// ptr == NULL means env var was unset at MINIT.
// ptr != NULL with len == 0 means env var was set to an empty string.
char *ptr;
size_t len;
} zai_config_cached_env_value;
static zai_config_cached_env_value zai_config_cached_env_values[ZAI_CONFIG_ENTRIES_COUNT_MAX][ZAI_CONFIG_NAMES_COUNT_MAX];
static zai_string zai_config_cached_env_values[ZAI_CONFIG_ENTRIES_COUNT_MAX][ZAI_CONFIG_NAMES_COUNT_MAX];

Simply?

Copy link
Collaborator Author

@morrisonlevi morrisonlevi Feb 27, 2026

Choose a reason for hiding this comment

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

zai_string isn't supposed to have a null pointer, right? IIRC that's why I did this. This distinguishes between unset and null. But there is a zai_option_str, and I think using a zai_option_str except actually owning it is better than putting null into zai_string. So I'll experiment with that.

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.

3 participants