-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
libct: speedup process.Env handling #4325
base: main
Are you sure you want to change the base?
Conversation
libcontainer/standard_init_linux.go
Outdated
@@ -277,7 +278,7 @@ func (l *linuxStandardInit) Init() error { | |||
|
|||
if l.dmzExe != nil { | |||
l.config.Args[0] = name | |||
return system.Fexecve(l.dmzExe.Fd(), l.config.Args, os.Environ()) | |||
return system.Fexecve(l.dmzExe.Fd(), l.config.Args, l.config.Env) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before we merge #4323 , maybe we should also need to include l.config.Env
when running StartContainer
hook.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is definitely something to think about. Maybe it makes sense to do it selectively (for those hooks that are run inside the container -- AFAIR not all of them are)
9b67a06
to
b0512bc
Compare
Interesting; I didn't know about that discrepancy; sounds like something that wouldn't hurt to define in the OCI spec; given that all original implementations were in Go, and ISTR Docker also had its own code to remove duplicates, I'm inclined to describe that as the expected behavior (possibly recommend producers of the OCI config to handle duplicates themselves to prevent any ambiguity). |
I was not entirely correct here. Let me rephrase this:
On an unrelated note, I also took a look at crun and it seems it is following runc logic, calling |
PTAL @opencontainers/runc-maintainers |
LGTM, it’s a big step. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kolyshkin left some comments, but seems great it makes a difference! This is marked for 1.3, though, but I guess you asking for reviews now means you want this for 1.2?
I'm fine with this in 1.2 or 1.3
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks!
OTOH I think this is ready for more reviews and potential inclusion. |
The PR to release 1.2 is out already, and it will be released today. I'd prefer to merge this after the 1.2.0 release, if the speedup is nice, we can add it in a patch release (or maybe to a 1.3 release in 3 months ;)) |
This is totally 1.3 material; let me rebase |
Here's what it shows on my laptop (with -count 10 -benchtime 10s, summarized by benchstat): │ sec/op │ ExecTrue-20 8.477m ± 2% ExecInBigEnv-20 61.53m ± 1% Signed-off-by: Kir Kolyshkin <[email protected]>
The current implementation sets all the environment variables passed in Process.Env in the current process, one by one, then uses os.Environ to read those back. As pointed out in [1], this is slow, as runc calls os.Setenv for every variable, and there may be a few thousands of those. Looking into how os.Setenv is implemented, it is indeed slow, especially when cgo is enabled. Looking into why it was implemented, I found commit 9744d72 and traced it to [2], which discusses the actual reasons. At the time were: - HOME is not passed into container as it is set in setupUser by os.Setenv and has no effect on config.Env; - there is no deduplication of environment variables. Yet it was decided to not go ahead with this patch, but later [3] was merged with the carry of this patch. Now, from what I see: 1. Passing environment to exec is way faster than using os.Setenv and os.Environ (tests show ~20x faster in simple Go test, and 2x faster in real-world test, see below). 2. Setting environment variables in the runc context can result is ugly side effects (think GODEBUG, LD_PRELOAD, or _LIBCONTAINER_*). 3. Nothing in runtime spec says that the environment needs to be deduplicated, or the order of preference (whether the first or the last value of a variable with the same name is to be used). We should stick to what we have in order to maintain backward compatibility. This patch: - switches to passing env directly to exec; - adds deduplication mechanism to retain backward compatibility; - sets PATH from process.Env in the current process; - adds HOME to process.Env if not set; The benchmark added by the previous commit shows ~3x improvement: │ before │ after │ │ sec/op │ sec/op vs base │ ExecInBigEnv-20 61.53m ± 1% 21.87m ± 16% -64.46% (p=0.000 n=10) [1]: opencontainers#1983 [2]: docker-archive/libcontainer#418 [3]: docker-archive/libcontainer#432 Signed-off-by: Kir Kolyshkin <[email protected]>
FWIW, I sent a patch to improve the performance of |
This is a rework/carry of #1983.
The current implementation sets all the environment variables passed in
Process.Env
in the current process, one by one, then usesos.Environ
toread those back.
As pointed out in 1, this is slow, as runc calls
os.Setenv
forevery variable, and there may be a few thousands of those.
Looking into why it was implemented, I found commit 9744d72 and traced
it to 2, which discusses the actual reasons. At the time were:
os.Setenv and has no effect on config.Env;
Yet it was decided to not go ahead with this patch, but later 3 was
merged with the carry of this patch.
Now, from what I see:
os.Setenv
andos.Environment()
(benchmarks show ~20x improvement in simple Go test,and ~3x improvement in real-world test, see below).
side effects (think
GODEBUG
,LD_PRELOAD
,_LIBCONTAINER_*
).deduplicated, or the order of preference (whether the first or the
last value of a variable with the same name is to be used). In C
(Linux/glibc), the first value is used. In Go, it's the last one.
We should probably stick to what we have in order to maintain
backward compatibility.
This patch:
PATH
fromprocess.Env
in the current process (same as before);HOME
toprocess.Env
if not set (same as before);removes os.Clearenv call as it's no longer needed.The benchmark added shows 3x improvement
for in-container exec with 5000 environment variables:
The remaining questions are:
values from
process.Env
to the current process);PATH
(e.g"/bin:/usr/bin"
should be added,when
PATH
is not set.