Skip to content

Commit 006ae82

Browse files
authored
framework: add parallelism overcommit and annotate heavy examples (#1533)
Add a `parallelism_overcommit` setting and use `resource_size` annotations on the heaviest build targets to give Bazel better scheduling information. On an 8-core machine this reduces uncached Examples build time by ~25% (506s → 378s) and critical path by ~27% (488s → 355s). The overcommit setting (`//foreign_cc/settings:parallelism_overcommit`, default +2) lets build tools use slightly more parallelism than the scheduler reservation, mirroring ninja's own ncpus+2 convention. This hides I/O latency and helps configure_make targets whose configure phase is serial. The "serial" size is exempt — it always enforces `-j1`. Meson/ninja targets (mesa, glib) are deliberately left unannotated: setting `NINJA_JOBS` caps ninja below its auto-detected parallelism, making individual targets ~2x slower and hurting the overall build. Annotations: - enormous: openssl (both variants), python3, subversion, cmake_tool (cmake bootstrap is 86% of the Main job critical path at 235s) - large: gn, curl, log4cxx The `.bazelrc.common` settings rescale enormous/large to fit 8 cores by default.
1 parent 43a63eb commit 006ae82

12 files changed

Lines changed: 88 additions & 40 deletions

File tree

.bazelrc.common

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,7 @@ common:bazel-7.x --show_progress
5454
common:bazel-8.x --incompatible_disable_autoloads_in_main_repo
5555
# Remind people to set good test timeouts / sizes
5656
common --test_verbose_timeout_warnings
57+
58+
# Resource size tuning (default: fits 8-core machine)
59+
common --@rules_foreign_cc//foreign_cc/settings:size_enormous_cpu=4
60+
common --@rules_foreign_cc//foreign_cc/settings:size_large_cpu=3

examples/third_party/curl/BUILD.curl.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ cmake(
5050
"@platforms//os:windows": ["libcurl.lib"],
5151
"//conditions:default": ["libcurl.a"],
5252
}),
53+
resource_size = "large",
5354
toolchains = ["@rules_perl//:current_toolchain"],
5455
visibility = ["//visibility:public"],
5556
deps = [

examples/third_party/gn/BUILD.gn.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ ninja(
3737
"cp -a out/gn $$INSTALLDIR/bin",
3838
]),
3939
}),
40+
resource_size = "large",
4041
targets = select({
4142
"@platforms//os:windows": ["gn.exe"],
4243
"//conditions:default": [""],

examples/third_party/log4cxx/BUILD.log4cxx.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ configure_make(
2626
}),
2727
lib_source = ":all_srcs",
2828
out_static_libs = ["liblog4cxx.a"],
29+
resource_size = "large",
2930
target_compatible_with = select({
3031
"@platforms//os:windows": ["@platforms//:incompatible"],
3132
"//conditions:default": [],

examples/third_party/openssl/BUILD.openssl.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ configure_make_variant(
7878
"libssl-1_1-x64.dll",
7979
"libcrypto-1_1-x64.dll",
8080
],
81+
resource_size = "enormous",
8182
target_compatible_with = select({
8283
"@platforms//os:windows": [],
8384
"//conditions:default": ["@platforms//:incompatible"],
@@ -126,6 +127,7 @@ configure_make(
126127
]),
127128
"//conditions:default": "",
128129
}),
130+
resource_size = "enormous",
129131
target_compatible_with = select({
130132
"@platforms//os:windows": ["@platforms//:incompatible"],
131133
"//conditions:default": [],

examples/third_party/python/BUILD.python3.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ configure_make(
3333
"python3.10",
3434
],
3535
out_data_dirs = ["lib"],
36+
resource_size = "enormous",
3637
target_compatible_with = select({
3738
"@platforms//os:windows": ["@platforms//:incompatible"],
3839
"//conditions:default": [],

examples/third_party/subversion/BUILD.subversion.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ configure_make(
3939
"svn",
4040
"svnversion",
4141
],
42+
resource_size = "enormous",
4243
target_compatible_with = select({
4344
"@platforms//os:windows": ["@platforms//:incompatible"],
4445
"//conditions:default": [],

foreign_cc/private/resource_sets.bzl

Lines changed: 65 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ load("@bazel_lib//lib:resource_sets.bzl", "resource_set_for")
55
load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo", "int_flag", "string_flag")
66
load("@rules_shell//shell:sh_binary.bzl", "sh_binary")
77

8+
_PARALLELISM_OVERCOMMIT_DEFAULT = 2
9+
_PARALLELISM_OVERCOMMIT_SETTING = "parallelism_overcommit"
10+
811
_DEFAULT_SIZE = "default"
912
_SIZES = {
1013
"enormous": {
@@ -56,11 +59,20 @@ def _setting(size, resource, mode):
5659
def create_settings():
5760
"""create the settings that configure these functions."""
5861
settings = {
59-
"size_default": {
62+
_PARALLELISM_OVERCOMMIT_SETTING: {
6063
"sort_key": (0, 0, 0, ""),
64+
"value": _PARALLELISM_OVERCOMMIT_DEFAULT,
65+
},
66+
"size_default": {
67+
"sort_key": (0, 0, 1, ""),
6168
"value": _DEFAULT_SIZE,
6269
},
6370
}
71+
int_flag(
72+
name = _PARALLELISM_OVERCOMMIT_SETTING,
73+
build_setting_default = _PARALLELISM_OVERCOMMIT_DEFAULT,
74+
visibility = ["//visibility:public"],
75+
)
6476
string_flag(
6577
name = "size_default",
6678
build_setting_default = _DEFAULT_SIZE,
@@ -123,23 +135,32 @@ SIZE_ATTRIBUTES = {
123135
default = _DEFAULT_SIZE,
124136
mandatory = False,
125137
doc = """\
126-
Set the approximate size of this build. This does two things:
127-
1. Sets the environment variables to tell the underlying build system the
128-
requested parallelization; examples are CMAKE_BUILD_PARALLEL_LEVEL for cmake
129-
or MAKEFLAGS for autotools.
130-
2. Sets the resource_set attribute on the action to tell bazel how many cores
131-
are being used, so it schedules appropriately.
132-
133-
The sizes map to labels, which can be used to override the meaning of the
134-
sizes. See @rules_foreign_cc//foreign_cc/settings:size_{size}_{cpu|mem}.
135-
Running `bazel run @rules_foreign_cc//foreign_cc/settings` will print out all
136-
the settings in bazelrc format for easy customization.
137-
138-
The `serial` size is special: it sets cpu=1, and provides no override for cpu
139-
(just mem), so `serial` can be used for packages that are known-broken for
140-
parallelization.
138+
Set the approximate size of this build, which controls two things:
139+
140+
1. The Bazel scheduler reservation, so large builds don't all run at once.
141+
2. The parallelism passed to the underlying build system via environment
142+
variables (CMAKE_BUILD_PARALLEL_LEVEL, GNUMAKEFLAGS, NINJA_JOBS, etc.).
143+
144+
Build tool parallelism is set to the scheduler reservation plus a small
145+
overcommit (default +2, matching ninja's ncpus+2 convention). This hides
146+
I/O latency and lets configure_make targets — whose configure phase is
147+
always serial — make better use of their allocation during the parallel
148+
make phase. The overcommit can be tuned with
149+
@rules_foreign_cc//foreign_cc/settings:parallelism_overcommit.
150+
151+
Each size maps to a cpu and mem value that can be overridden per-size.
152+
See @rules_foreign_cc//foreign_cc/settings:size_{size}_{cpu|mem}, or run
153+
`bazel run @rules_foreign_cc//foreign_cc/settings` to print all settings
154+
in bazelrc format.
155+
156+
The `serial` size is special: it fixes cpu=1 with no overcommit, for
157+
packages that are known-broken under parallel builds.
141158
""",
142159
),
160+
"_parallelism_overcommit": attr.label(
161+
default = "//foreign_cc/settings:" + _PARALLELISM_OVERCOMMIT_SETTING,
162+
providers = [BuildSettingInfo],
163+
),
143164
} | {
144165
_setting(size = size, resource = resource, mode = "key"): attr.label(
145166
default = _setting(size, resource, mode = "label"),
@@ -170,10 +191,13 @@ def get_resource_set(attr):
170191
Args:
171192
attr: the ctx.attr associated with the target
172193
Returns:
173-
A tuple of:
174-
- the resource_set, or None if it's the bazel default
175-
- cpu_cores, or 0 if it's the bazel default
176-
- mem in MB, or 0 if it's the bazel default
194+
A struct with:
195+
- resource_set: the resource_set callback, or None if bazel default
196+
- cpu: cpu_cores, or 0 if bazel default
197+
- mem: mem in MB, or 0 if bazel default
198+
- allow_cpu_overcommit: True if the build tool may use more
199+
parallelism than the scheduler reservation (False for sizes
200+
like "serial" that must enforce an exact -j value)
177201
"""
178202
size = _DEFAULT_SIZE
179203
if attr.resource_size != _DEFAULT_SIZE:
@@ -182,7 +206,12 @@ def get_resource_set(attr):
182206
size = _get_size_config(attr, _DEFAULT_SIZE, None)
183207

184208
if size == _DEFAULT_SIZE:
185-
return None, 0, 0
209+
return struct(
210+
resource_set = None,
211+
cpu = 0,
212+
mem = 0,
213+
allow_cpu_overcommit = False,
214+
)
186215

187216
cfg = _SIZES[size]
188217
cpu_value = cfg["cpu"] if _is_fixed(cfg, "cpu") else _get_size_config(attr, size, "cpu")
@@ -207,7 +236,12 @@ def get_resource_set(attr):
207236
actual_cpu = 0
208237
actual_mem = 0
209238

210-
return resource_set, actual_cpu, actual_mem
239+
return struct(
240+
resource_set = resource_set,
241+
cpu = actual_cpu,
242+
mem = actual_mem,
243+
allow_cpu_overcommit = not _is_fixed(cfg, "cpu"),
244+
)
211245

212246
def get_resource_env_vars(attr):
213247
""" get the values of env vars controlling parallelism
@@ -225,28 +259,29 @@ def get_resource_env_vars(attr):
225259
dict[str, str] to pass to run/run_shell
226260
"""
227261

228-
resource_set, cpu, _mem = get_resource_set(attr)
262+
resources = get_resource_set(attr)
229263

230264
env = None
231-
if cpu > 0:
232-
sc = str(cpu)
265+
if resources.cpu > 0:
266+
overcommit = attr._parallelism_overcommit[BuildSettingInfo].value if resources.allow_cpu_overcommit else 0
267+
parallelism = str(resources.cpu + overcommit)
233268
env = {
234-
"CMAKE_BUILD_PARALLEL_LEVEL": sc,
269+
"CMAKE_BUILD_PARALLEL_LEVEL": parallelism,
235270

236271
# we set GNUMAKEFLAGS instead of MAKEFLAGS because nmake sees
237272
# MAKEFLAGS but doesn't accept a -j argument, and we don't have a
238273
# good way of being sure that nmake isn't going to be used as part
239274
# of a build.
240-
"GNUMAKEFLAGS": "-j" + sc,
275+
"GNUMAKEFLAGS": "-j" + parallelism,
241276

242277
# Meson starts to honor this as of 1.7.0; before that, it only uses
243278
# ninja's parallelization controls.
244-
"MESON_NUM_PROCESSES": sc,
279+
"MESON_NUM_PROCESSES": parallelism,
245280

246281
# Note that ninja does not honor this by default; it's our wrapper
247282
# script that handles this.
248283
# https://github.com/ninja-build/ninja/issues/1482
249-
"NINJA_JOBS": sc,
284+
"NINJA_JOBS": parallelism,
250285
}
251286

252-
return resource_set, env
287+
return resources.resource_set, env

test/env_test/BUILD.bazel

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ env_test_make(
3434
# Note that we set GNUMAKEFLAGS, but make itself will convert it into
3535
# MAKEFLAGS, so we'll just see MAKEFLAGS set here
3636
"GNUMAKEFLAGS": "",
37-
"MAKEFLAGS": " -j1 -- PREFIX={{INSTALLDIR}}",
37+
"MAKEFLAGS": " -j3 --jobserver-auth={{JOBSERVER}} -- PREFIX={{INSTALLDIR}}",
3838
},
3939
make_attrs = {
4040
"resource_size": "tiny",
@@ -62,7 +62,7 @@ env_test_ninja(
6262
env_test_ninja(
6363
name = "ninja_tiny",
6464
check_shellvars = STANDARD_VARS | {
65-
"NINJA_JOBS": "1",
65+
"NINJA_JOBS": "3",
6666
},
6767
ninja_attrs = {
6868
"resource_size": "tiny",
@@ -80,8 +80,8 @@ env_test_meson(
8080
env_test_meson(
8181
name = "meson_tiny",
8282
check_shellvars = STANDARD_VARS | {
83-
"MESON_NUM_PROCESSES": "1",
84-
"NINJA_JOBS": "1",
83+
"MESON_NUM_PROCESSES": "3",
84+
"NINJA_JOBS": "3",
8585
},
8686
meson_attrs = {
8787
"resource_size": "tiny",
@@ -121,12 +121,12 @@ env_test_cmake(
121121
# since we replace the binary dir, this overrides the STANDARD_VARS
122122
"BUILD_TMPDIR": "{{CMAKE_BINARY_DIR}}",
123123
"CMAKEVARS_FILE": "{{INSTALLDIR}}/cmakevars.out",
124-
"CMAKE_BUILD_PARALLEL_LEVEL": "1",
124+
"CMAKE_BUILD_PARALLEL_LEVEL": "3",
125125
# Note that we set GNUMAKEFLAGS, but make itself will convert it into
126126
# MAKEFLAGS, so for the cmake test we won't see it converted yet.
127-
"GNUMAKEFLAGS": "-j1",
127+
"GNUMAKEFLAGS": "-j3",
128128
"MAKEFLAGS": "",
129-
"NINJA_JOBS": "1",
129+
"NINJA_JOBS": "3",
130130
},
131131
cmake_attrs = {
132132
"resource_size": "tiny",
@@ -161,15 +161,15 @@ env_test_configure_make(
161161
# since we replace the builddir, this overrides the STANDARD_VARS
162162
"BUILD_TMPDIR": "{{abs_builddir}}",
163163
"INSTALLDIR": "{{INSTALLDIR}}",
164-
"MAKEFLAGS": " -j1",
164+
"MAKEFLAGS": " -j3 --jobserver-auth={{JOBSERVER}}",
165165
"abs_builddir": "{{abs_builddir}}",
166166
"abs_srcdir": "{{abs_srcdir}}",
167167
},
168168
check_shellvars = STANDARD_VARS | {
169169
# since we replace the builddir, this overrides the STANDARD_VARS
170170
"BUILD_TMPDIR": "{{abs_builddir}}",
171171
"INSTALLDIR": "{{INSTALLDIR}}",
172-
"MAKEFLAGS": " -j1",
172+
"MAKEFLAGS": " -j3 --jobserver-auth={{JOBSERVER}}",
173173
},
174174
configure_make_attrs = {
175175
"resource_size": "tiny",

test/env_test/Makefile.in.tmpl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,7 @@ all:
561561
# Note that this is order-sensitive, since INSTALLDIR is a subdir of \
562562
# EXT_BUILD_ROOT and we want the replacement to be most specific first. \
563563
$(SED) -i.bak \
564+
-e 's|--jobserver-auth=[^ ]*|--jobserver-auth={{JOBSERVER}}|g' \
564565
-e 's|$(abs_srcdir)|{{abs_srcdir}}|g' \
565566
-e 's|$(abs_builddir)|{{abs_builddir}}|g' \
566567
-e 's|$(BUILD_TMPDIR)|{{BUILD_TMPDIR}}|g' \

0 commit comments

Comments
 (0)