Skip to content

Build: add concurrency options for Git and Python builds#12853

Open
humitos wants to merge 2 commits intomainfrom
humitos/more-parallelism
Open

Build: add concurrency options for Git and Python builds#12853
humitos wants to merge 2 commits intomainfrom
humitos/more-parallelism

Conversation

@humitos
Copy link
Copy Markdown
Member

@humitos humitos commented Mar 18, 2026

While investigating slow build times, I found that there are some projects with big Git repositories that take a lot of time unpacking, checking out, and fetching submodules. This commit adds a few options to try to improve the performance of these Git operations.

Besides, it adds some known environment variables to try to improve the performance of Python package builds, especially for C and C++ extensions.

I noticed that only 1vCPU is used at 100% all the time on these two type of operations and it seems we can make them to perform better.

Related https://github.com/readthedocs/readthedocs-ops/issues/1588

While investigating slow build times, I found that there are some
projects with big Git repositories that take a lot of time unpacking,
checking out, and fetching submodules. This commit adds a few options
to try to improve the performance of these Git operations.

Besides, it adds some known environment variables to try to improve the
performance of Python package builds, especially for C and C++ extensions.

I noticed that only 1vCPU is used at 100% all the time on these two
type of operations and it seems we can make them to perform better.

Related readthedocs/readthedocs-ops#1588
@humitos humitos requested a review from a team as a code owner March 18, 2026 12:16
@humitos humitos requested a review from stsewd March 18, 2026 12:16
@humitos humitos requested a review from ericholscher March 18, 2026 12:16
Copy link
Copy Markdown
Member

@stsewd stsewd left a comment

Choose a reason for hiding this comment

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

Have you tested how much are we gaining with these options?

"MAKEFLAGS": f"-j {cpus}",
# Pillow and other libraries use this variable
# https://pillow.readthedocs.io/en/stable/installation/building-from-source.html#build-options
"MAX_CONCURRENCY": cpus,
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.

Looks like this is the default

By default, it uses as many CPUs as are present.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah, I put Pillow as an example, but I found that it was used by other libraries as well while doing the research. We can remove it if we want, tho.

# https://git-scm.com/docs/git-config#Documentation/git-config.txt-checkoutworkers
self.run("git", "config", "--global", "checkout.workers", "-1")
# https://git-scm.com/docs/git-config#Documentation/git-config.txt-checkoutthresholdForParallelism
self.run("git", "config", "--global", "checkout.thresholdForParallelism", "100")
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.

Looks like this is already the default?

The default is 100.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ah, yeah, I was testing other numbers locally and forgot to remove it from here.

Co-authored-by: Santos Gallegos <stsewd@proton.me>
@humitos
Copy link
Copy Markdown
Member Author

humitos commented Mar 26, 2026

Have you tested how much are we gaining with these options?

I tested the other day when I pushed the PR and I've noticed very small improvements in a basic repository. Today, I went deeper and found for a repository that takes more than 5 minutes to clone in our platform and I used it to test this specifically: weblate

Here is what I found:

Command Without optimization With optimization (this PR)
git clone 16s 15s
git fetch 9m41s 6m56s

We have a reduction of 3 minutes here just by setting this configs 🤯

Note

This is the query I used to find this project:

BuildCommandResult.objects.filter(command__startswith="git", exit_code=0).annotate(length=F("end_time")-F("start_time")).filter(length__gt=timezone.timedelta(seconds=60*5))
Details
readthedocs@build-default-i-0c1ad2ab83414f21f(org):~$ cd /tmp
readthedocs@build-default-i-0c1ad2ab83414f21f(org):/tmp$ mkdir weblate
readthedocs@build-default-i-0c1ad2ab83414f21f(org):/tmp$ cd weblate/
readthedocs@build-default-i-0c1ad2ab83414f21f(org):/tmp/weblate$ time git clone --depth 1 https://github.com/WeblateOrg/weblate.git . 
Cloning into '.'...
remote: Enumerating objects: 2935, done.
remote: Counting objects: 100% (2935/2935), done.
remote: Compressing objects: 100% (2079/2079), done.
remote: Total 2935 (delta 670), reused 1625 (delta 436), pack-reused 0 (from 0)
Receiving objects: 100% (2935/2935), 26.78 MiB | 8.84 MiB/s, done.
Resolving deltas: 100% (670/670), done.
Updating files: 100% (2347/2347), done.

real	0m16.602s
user	0m1.655s
sys	0m0.493s
readthedocs@build-default-i-0c1ad2ab83414f21f(org):/tmp/weblate$ time git fetch origin --force --prune --prune-tags --depth 50
remote: Enumerating objects: 219919, done.
remote: Counting objects: 100% (219907/219907), done.
remote: Compressing objects: 100% (67502/67502), done.
Receiving objects: 100% (218218/218218), 1.52 GiB | 13.94 MiB/s, done.
remote: Total 218218 (delta 120128), reused 189044 (delta 93498), pack-reused 0 (from 0)
Resolving deltas: 100% (120128/120128), completed with 1066 local objects.
From https://github.com/WeblateOrg/weblate
 * [new tag]             weblate-0.1    -> weblate-0.1
 * [new tag]             weblate-0.2    -> weblate-0.2
 
 ... chunked ...

 * [new tag]             weblate-5.9.1  -> weblate-5.9.1
 * [new tag]             weblate-5.9.2  -> weblate-5.9.2

real	9m41.013s
user	1m46.225s
sys	0m9.699s
readthedocs@build-default-i-0c1ad2ab83414f21f(org):/tmp/weblate$
readthedocs@build-default-i-076bcdc5b8c537656(org):~$ cd /tmp
readthedocs@build-default-i-076bcdc5b8c537656(org):/tmp$ mkdir weblate-git-optimized
readthedocs@build-default-i-076bcdc5b8c537656(org):/tmp$ cd weblate-git-optimized/
readthedocs@build-default-i-076bcdc5b8c537656(org):/tmp/weblate-git-optimized$ git config --global fetch.parallel 0 
readthedocs@build-default-i-076bcdc5b8c537656(org):/tmp/weblate-git-optimized$ git config --global checkout.workers 0 
readthedocs@build-default-i-076bcdc5b8c537656(org):/tmp/weblate-git-optimized$ git config --global checkout.thresholdForParallelism 100 
readthedocs@build-default-i-076bcdc5b8c537656(org):/tmp/weblate-git-optimized$ git config --global submodule.fetchJobs 0 
readthedocs@build-default-i-076bcdc5b8c537656(org):/tmp/weblate-git-optimized$ git config --global pack.threads 0 
readthedocs@build-default-i-076bcdc5b8c537656(org):/tmp/weblate-git-optimized$ time git clone --depth 1 https://github.com/WeblateOrg/weblate.git .  
Cloning into '.'...
remote: Enumerating objects: 2935, done.
remote: Counting objects: 100% (2935/2935), done.
remote: Compressing objects: 100% (2079/2079), done.
remote: Total 2935 (delta 670), reused 1624 (delta 436), pack-reused 0 (from 0)
Receiving objects: 100% (2935/2935), 26.78 MiB | 8.73 MiB/s, done.
Resolving deltas: 100% (670/670), done.
Updating files: 100% (2347/2347), done.

real	0m15.473s
user	0m1.718s
sys	0m0.435s
readthedocs@build-default-i-076bcdc5b8c537656(org):/tmp/weblate-git-optimized$ time git fetch origin --force --prune --prune-tags --depth 50  
remote: Enumerating objects: 219919, done.
remote: Counting objects: 100% (219907/219907), done.
remote: Compressing objects: 100% (67528/67528), done.
Receiving objects: 100% (218218/218218), 1.52 GiB | 20.12 MiB/s, done.
remote: Total 218218 (delta 120144), reused 189001 (delta 93472), pack-reused 0 (from 0)
Resolving deltas: 100% (120144/120144), completed with 1065 local objects.
From https://github.com/WeblateOrg/weblate
 * [new tag]             weblate-0.1    -> weblate-0.1
 * [new tag]             weblate-0.2    -> weblate-0.2
 
... chunked ...

 * [new tag]             weblate-5.9.1  -> weblate-5.9.1
 * [new tag]             weblate-5.9.2  -> weblate-5.9.2

real	6m56.180s
user	1m45.886s
sys	0m9.090s
readthedocs@build-default-i-076bcdc5b8c537656(org):/tmp/weblate-git-optimized$

@humitos humitos requested review from agjohnson and stsewd March 26, 2026 18:06
@stsewd
Copy link
Copy Markdown
Member

stsewd commented Mar 26, 2026

I'm not sure how I feel with adding extra env vars to the build process, that's kind of breaking our rule to try to run the same steps a user runs locally. We may want to put these as general recommendations instead.

A note from your test, you are running git fetch without a refspec, that's probably fetching all tags and branches. Looks like when we are building from the default branch, we are skipping the refspec, that's probably slowing down some builds. I'll open a PR to fix that.

@humitos
Copy link
Copy Markdown
Member Author

humitos commented Mar 27, 2026

I'm not sure how I feel with adding extra env vars to the build process,

Yeah, we can remove those vars. I'm more interested in the Git configs here.

I copied the commands from the build detail pages of each of those projects, so I'm testing with the same commands we are running in production

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants