Optimize parser by removing repeated hash merges - #515
Merged
Conversation
This leads to a noticeable performance improvement when a large number of environment variables have already been set
bkeepers
commented
Dec 12, 2024
Owner
@andrewts129 awesome, thanks for finding this and working on a fix! I pushed some benchmarks in 1877fa0 just to compare. Here's the results of loading a 1000 line main this branch 13-21x faster. Nice work! |
bkeepers added a commit
that referenced
this pull request
Dec 13, 2024
Part of the optimization in #515 was to skip parsing variables that were already defined. But that had the side-effect of not returning them in the resulting hash. This adds a test for this behavior and restores it.
idrozd
commented
May 26, 2025
Jesus Thanks @andrewts129 , I thought I was going nuts Spent hours and hours figuring this out, ending up with monkeypatch. I could swear ENV#each / #to_h was always fast until it wasn't sometime last year. |
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 freeto 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.
Changes
Thanks for the helpful gem!
While profiling the startup time for a large Rails app that's manually invoking
Dotenv.loadvery early on in the boot process in order to get access to envvars ASAP, I noticed that the subsequentDotenv.loadbeing automatically called by this gem's provided railtie was taking an unusual amount of time to complete. The time was mostly being spent in the variable substitution module, on this line:Since
ENVhad already been loaded up with ~2,000 extra variables from the first run of dotenv, this hash merge is not a trivially cheap operation and it added up being run when parsing each line.From what I understand, the purpose of that line is to build a lookup table that gives priority to either envvars already in
ENVor envvars from an earlier line in the file, depending on the value of the "overwrite" flag. We can make this operation unnecessary by updating the parser to simply skip over lines re-defining a variable already inENVwhen "overwrite = false", leaving the variable substitution module not even having to worry about the prioritization.This leads to a modest performance improvement when parsing a large .env file, and a significant one when parsing a large .env file when
ENVis already very populated by some other process (most likely a previous run of dotenv, but I can imagine there are other, less avoidable reasons this could happen as well):The .env file used for this benchmark was created from this script:
Validation
The RSpec test suite for this gem looks to be pretty thorough and it's all still passing after this change, so from that I don't believe that this will have any unintended changes in functionality.