Skip to content

Commit eb9af35

Browse files
authored
perf(compile): close the fresh compile gap with ordered native scheduling and mimalloc (#889)
* perf(analysis): schedule native query analysis largest first Hand out each native analysis batch through a shared queue ordered by SQL size, so the widest queries start first instead of becoming a late critical path. Results keep request order. Fresh 3000 native analysis wall falls from about 2.24 s to 1.91 s with identical output. * perf(native): use mimalloc as the native extension allocator Native analysis spends about 40% of its time in the system allocator building and dropping syntax trees. mimalloc (local-dynamic TLS for the dlopen-loaded extension) cuts fresh 3000 native analysis from about 6.4 to 4.8 CPU-s and 1.9 to 1.4 s wall with identical output. Fresh 3000 peak memory falls from about 830 to 690 MB; dense 3000 CPU falls about 7% at similar peak memory. * perf(tests): reuse parsed model SQL when rendering SQL-test comparisons Tests in one project render the same model SQL many times while lifting its CTEs into the comparison query; fresh 3000 splits about 1,150 statements from only 50 distinct SQL strings. Keep a parse of SQL once it repeats within a render batch, bounded to 1 MiB of SQL text, and clone it for each later use; per-test CTE renaming is unchanged. Unrepeated SQL is never retained, so dense projects keep their peak memory. Fresh 3000 native planning and rendering falls from about 455 to 380 ms with identical artifacts. * ci: restore the fresh same-runner compile ratio limit to 1.10 Fresh 3000 --no-cache is back within 1.10x of 0.119.1 on the same machine (medians of 3 interleaved runs: 8.22 s vs 7.86 s wall, 12.56 vs 12.54 CPU-s), so the temporary 1.30 limit is no longer needed. * ci: build every wheel platform on pull requests that change native inputs Build the same Linux, macOS and Windows wheel matrix as publish.yml, without uploading, whenever crates, Cargo manifests, pyproject.toml or the verify workflow change, and smoke-import sqlbuild._native from each built wheel. The aggregate Verify job treats the job like the other path-selected suites. * test: allow the 10,000-model cache-write CPU overhead the same 1.15 as wall
1 parent 7c904e9 commit eb9af35

15 files changed

Lines changed: 340 additions & 60 deletions

File tree

‎.github/workflows/publish.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ jobs:
3434
echo "tag=$tag" >> "$GITHUB_OUTPUT"
3535
3636
wheels:
37+
# Keep the platform matrix and maturin settings in sync with verify.yml.
3738
name: Build wheel (${{ matrix.platform.name }})
3839
needs: release-tag
3940
runs-on: ${{ matrix.platform.runner }}

‎.github/workflows/verify.yml‎

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ jobs:
2222
performance: ${{ steps.scope.outputs.performance }}
2323
format_performance: ${{ steps.scope.outputs.format_performance }}
2424
rules_performance: ${{ steps.scope.outputs.rules_performance }}
25+
wheels: ${{ steps.scope.outputs.wheels }}
2526
steps:
2627
- uses: actions/checkout@v4
2728
with:
@@ -41,11 +42,12 @@ jobs:
4142
echo "format_performance=$4"
4243
echo "rules_performance=$5"
4344
echo "checks=$6"
45+
echo "wheels=$7"
4446
} >> "${GITHUB_OUTPUT}"
4547
}
4648
4749
if [[ "${EVENT_NAME}" != "pull_request" ]]; then
48-
write_scope true true true true true true
50+
write_scope true true true true true true true
4951
exit 0
5052
fi
5153
@@ -54,6 +56,7 @@ jobs:
5456
performance=false
5557
format_performance=false
5658
rules_performance=false
59+
wheels=false
5760
broad=false
5861
changed=false
5962
# A change that only touches the website, root-level Markdown, the README demo GIFs or the
@@ -69,6 +72,11 @@ jobs:
6972
*.md) ;;
7073
*) docs_only=false ;;
7174
esac
75+
case "${path}" in
76+
crates/*|Cargo.toml|Cargo.lock|pyproject.toml|.github/workflows/verify.yml)
77+
wheels=true
78+
;;
79+
esac
7280
case "${path}" in
7381
crates/*|Cargo.toml|Cargo.lock|pyproject.toml|uv.lock)
7482
broad=true
@@ -123,7 +131,7 @@ jobs:
123131
done < <(git diff --name-only -z "${BASE_SHA}...${HEAD_SHA}")
124132
125133
if [[ "${changed}" != "true" ]]; then
126-
write_scope true true true true true true
134+
write_scope true true true true true true true
127135
exit 0
128136
fi
129137
@@ -143,7 +151,8 @@ jobs:
143151
"${performance}" \
144152
"${format_performance}" \
145153
"${rules_performance}" \
146-
"${checks}"
154+
"${checks}" \
155+
"${wheels}"
147156
148157
lockfile:
149158
name: Lockfile consistency
@@ -414,7 +423,7 @@ jobs:
414423
- kind: dense
415424
max_ratio: "1.10"
416425
- kind: fresh
417-
max_ratio: "1.30"
426+
max_ratio: "1.10"
418427
steps:
419428
- uses: actions/checkout@v4
420429
- name: Check out the comparison base
@@ -601,6 +610,59 @@ jobs:
601610
-m "real_warehouse and postgres"
602611
-vv --color=yes -n auto --dist loadfile
603612
613+
wheels:
614+
# Keep the platform matrix and maturin settings in sync with publish.yml.
615+
name: Build wheel (${{ matrix.platform.name }})
616+
needs: [changes]
617+
if: needs.changes.outputs.wheels == 'true'
618+
runs-on: ${{ matrix.platform.runner }}
619+
timeout-minutes: 45
620+
strategy:
621+
fail-fast: false
622+
matrix:
623+
platform:
624+
- name: linux-x86_64
625+
runner: ubuntu-24.04
626+
target: x86_64
627+
- name: linux-aarch64
628+
runner: ubuntu-24.04-arm
629+
target: aarch64
630+
- name: macos-x86_64
631+
runner: macos-15-intel
632+
target: x86_64
633+
- name: macos-arm64
634+
runner: macos-15
635+
target: aarch64
636+
- name: windows-x86_64
637+
runner: windows-2025
638+
target: x86_64
639+
steps:
640+
- uses: actions/checkout@v4
641+
- uses: actions/setup-python@v5
642+
with:
643+
python-version: "3.12"
644+
- uses: dtolnay/rust-toolchain@master
645+
with:
646+
toolchain: stable
647+
- uses: PyO3/maturin-action@v1
648+
with:
649+
command: build
650+
args: --release --out dist --features extension-module
651+
target: ${{ matrix.platform.target }}
652+
manylinux: auto
653+
- name: Smoke import the native extension from the built wheel
654+
shell: bash
655+
run: |
656+
python -m venv "${RUNNER_TEMP}/wheel-smoke"
657+
if [[ -x "${RUNNER_TEMP}/wheel-smoke/Scripts/python.exe" ]]; then
658+
smoke_python="${RUNNER_TEMP}/wheel-smoke/Scripts/python.exe"
659+
else
660+
smoke_python="${RUNNER_TEMP}/wheel-smoke/bin/python"
661+
fi
662+
"${smoke_python}" -m pip install --no-deps dist/*.whl
663+
cd "${RUNNER_TEMP}"
664+
"${smoke_python}" -c "import sqlbuild._native"
665+
604666
verify:
605667
name: Verify
606668
if: always()
@@ -618,6 +680,7 @@ jobs:
618680
cache_compile_performance,
619681
rules_performance,
620682
postgres,
683+
wheels,
621684
]
622685
runs-on: ubuntu-24.04
623686
steps:
@@ -636,6 +699,7 @@ jobs:
636699
CACHE_COMPILE_PERFORMANCE_RESULT: ${{ needs.cache_compile_performance.result }}
637700
RULES_PERFORMANCE_RESULT: ${{ needs.rules_performance.result }}
638701
POSTGRES_RESULT: ${{ needs.postgres.result }}
702+
WHEELS_RESULT: ${{ needs.wheels.result }}
639703
run: |
640704
if [[ "${CHANGES_RESULT}" != "success" || "${LOCKFILE_RESULT}" != "success" ]]; then
641705
echo "Required classification or lockfile checks did not pass" >&2
@@ -655,7 +719,8 @@ jobs:
655719
"${COMPILE_PERFORMANCE_RATIO_RESULT}" \
656720
"${CACHE_COMPILE_PERFORMANCE_RESULT}" \
657721
"${RULES_PERFORMANCE_RESULT}" \
658-
"${POSTGRES_RESULT}"; do
722+
"${POSTGRES_RESULT}" \
723+
"${WHEELS_RESULT}"; do
659724
if [[ "${result}" != "success" && "${result}" != "skipped" ]]; then
660725
echo "A selected verification suite did not pass: ${result}" >&2
661726
exit 1

‎Cargo.lock‎

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ rust-version = "1.88"
1313
[workspace.dependencies]
1414
fensu-policy = "=0.22.0"
1515
globset = "0.4"
16+
# Python loads the extension with dlopen, so mimalloc must not use initial-exec TLS.
17+
mimalloc = { version = "0.1", default-features = false, features = ["local_dynamic_tls"] }
1618
proc-macro2 = { version = "1", features = ["span-locations"] }
1719
pyo3 = { version = "0.26", features = ["abi3-py312"] }
1820
polyglot-sql = { git = "https://github.com/chio-labs/polyglot.git", rev = "25ad011c49f2c7cd48f4427abb8fc0a6af2e4120", default-features = false, features = [

‎crates/sqlbuild-rules-native/Cargo.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ extension-module = ["pyo3/extension-module"]
1717
[dependencies]
1818
fensu-policy.workspace = true
1919
globset.workspace = true
20+
mimalloc.workspace = true
2021
polyglot-sql.workspace = true
2122
pyo3.workspace = true
2223
rayon.workspace = true
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
//! Process-wide allocator for the many short-lived syntax trees built across analysis threads.
2+
3+
#[global_allocator]
4+
static GLOBAL_ALLOCATOR: mimalloc::MiMalloc = mimalloc::MiMalloc;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
mod allocator;
12
pub(crate) mod functions;
23
pub(crate) mod panics;

‎crates/sqlbuild-rules-native/src/compiler/_helpers/sql_tests/planning.rs‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use crate::compiler::_helpers::sql_tests::markers::{
2222
replace_dbt_ref_markers, replace_named_markers,
2323
};
2424
use crate::compiler::_helpers::sql_tests::rendering::{
25-
AssertionStep, ChainStep, RenderRequest, render_comparison_sql, render_dialect,
25+
AssertionStep, ChainStep, RenderRequest, StatementCache, render_comparison_sql, render_dialect,
2626
rendered_chain_steps,
2727
};
2828
use crate::constants::{TABLE_FUNCTION_TEST_MODE, UDF_TEST_MODE};
@@ -206,6 +206,7 @@ struct ProjectContext {
206206
analysis_templates: Arc<AnalysisTemplateCache>,
207207
patterns: SqlTestPatterns,
208208
render_dialect: Arc<Dialect>,
209+
rendered_statements: Arc<StatementCache>,
209210
}
210211

211212
#[derive(Clone)]
@@ -614,6 +615,7 @@ pub(crate) fn plan_and_render_json(request_json: &str) -> Result<String, String>
614615
analysis_templates: Arc::new(Mutex::new(HashMap::new())),
615616
patterns: SqlTestPatterns::new()?,
616617
render_dialect,
618+
rendered_statements: Arc::default(),
617619
};
618620
let workers = request.workers.clamp(1, MAX_WORKERS);
619621
let pool = rayon::ThreadPoolBuilder::new()
@@ -631,8 +633,13 @@ pub(crate) fn plan_and_render_json(request_json: &str) -> Result<String, String>
631633
let mut planned = plan_test(test, &context)?;
632634
let planning_ns = planning_start.elapsed().as_nanos();
633635
let rendering_start = Instant::now();
634-
let sql = render_sql
635-
.then(|| render_comparison_sql(&planned.request, &context.render_dialect));
636+
let sql = render_sql.then(|| {
637+
render_comparison_sql(
638+
&planned.request,
639+
&context.render_dialect,
640+
Some(&context.rendered_statements),
641+
)
642+
});
636643
if !include_plan {
637644
planned.request.chain.clear();
638645
planned.request.assertions.clear();

0 commit comments

Comments
 (0)