Skip to content

[Opt](lambda) Aviod redundant copies during lambda execute - #66590

Open
linrrzqqq wants to merge 2 commits into
apache:masterfrom
linrrzqqq:opt-array-map
Open

[Opt](lambda) Aviod redundant copies during lambda execute#66590
linrrzqqq wants to merge 2 commits into
apache:masterfrom
linrrzqqq:opt-array-map

Conversation

@linrrzqqq

Copy link
Copy Markdown
Collaborator

What problem does this PR solve?

Issue Number: close #xxx

Related PR: #xxx

Problem Summary:

array_map flattens array elements into an internal block before executing its lambda expression. The previous implementation introduced several redundant copies and materializations on this path:

  • Nested array elements were copied into new columns even when all elements could be processed in a single lambda batch.
  • Captured outer columns were expanded repeatedly for each outer row.
  • Constant captured columns were materialized as full columns.
  • Sparse, unused input positions could create unnecessary placeholder column work.
  • The first lambda result batch was copied into a newly created result column.
  • Internal lambda execution could receive the outer selector even though its input block had already been selected and flattened.

These operations increased CPU usage and peak memory consumption, especially for large arrays, wide captured inputs, and complex lambda expressions.

This change optimizes the array_map execution path by:

  • Reusing the nested array data columns directly when all nested rows fit within one lambda batch.
  • Preserving captured constant columns as ColumnConst.
  • Expanding row-dependent captured columns in one operation with selector-aware source row indices.
  • Representing unused sparse input positions with ColumnNothing.
  • Reusing lambda block column allocations across batches.
  • Taking ownership of the first lambda result column instead of cloning and copying it.
  • Passing no outer selector when executing the already-materialized internal lambda block.
  • Centralizing nullable array result construction to keep the execution path simpler.
  • Sizing internal lambda batches according to the runtime row and byte budgets.

The optimization preserves array element order, nullable semantics, captured-column mapping, nested lambda behavior, and SQL result compatibility.

Release note

None

Check List (For Author)

  • Test

    • Regression test
    • Unit Test
    • Manual test (add detailed scripts or steps below)
    • No need to test or manual test. Explain why:
      • This is a refactor/code format and no logic has been changed.
      • Previous test can cover this change.
      • No code files have been changed.
      • Other reason
  • Behavior changed:

    • No.
    • Yes.
  • Does this need documentation?

    • No.
    • Yes.

Check List (For Reviewer who merge this PR)

  • Confirm the release note
  • Confirm test cases
  • Confirm document
  • Add branch pick label

@linrrzqqq

Copy link
Copy Markdown
Collaborator Author

/review

@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: Your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.
Workflow run: https://github.com/apache/doris/actions/runs/31246269731

Please inspect the workflow logs and rerun the review after the underlying issue is resolved.

@linrrzqqq

Copy link
Copy Markdown
Collaborator Author

@linrrzqqq review

@linrrzqqq linrrzqqq left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Rarion independent review completed and converged.

No findings.

This result is based on an independent static code review. No builds or tests were run; refer to CI for test status.

@linrrzqqq

Copy link
Copy Markdown
Collaborator Author

run buildall

1 similar comment
@hello-stephen

Copy link
Copy Markdown
Contributor

run buildall

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 96.36% (159/165) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 76.00% (32361/42580)
Line Coverage 60.84% (362779/596245)
Region Coverage 57.41% (304656/530622)
Branch Coverage 58.89% (137863/234084)

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-H: Total hot run time: 28625 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit 86e0aea08253bed3e91b021108a37e8de213c214, data reload: false

------ Round 1 ----------------------------------
============================================
q1	17672	3970	3972	3970
q2	2348	313	199	199
q3	10244	1338	816	816
q4	4679	468	334	334
q5	7639	832	553	553
q6	194	177	138	138
q7	758	810	587	587
q8	9891	1628	1581	1581
q9	6833	4089	4068	4068
q10	8260	1613	1333	1333
q11	733	352	331	331
q12	786	585	452	452
q13	18069	3747	2752	2752
q14	262	254	229	229
q15	q16	736	734	660	660
q17	1038	1078	840	840
q18	6586	5611	5530	5530
q19	1177	1287	1066	1066
q20	817	688	600	600
q21	5653	2527	2284	2284
q22	447	350	302	302
Total cold run time: 104822 ms
Total hot run time: 28625 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4363	4312	4314	4312
q2	286	320	207	207
q3	4498	4893	4396	4396
q4	2160	2250	1415	1415
q5	4275	4080	4110	4080
q6	237	173	128	128
q7	2163	1809	1581	1581
q8	2481	2032	2012	2012
q9	7241	7252	7183	7183
q10	4302	4323	3913	3913
q11	530	399	362	362
q12	742	764	644	644
q13	3247	3514	2929	2929
q14	307	314	270	270
q15	q16	707	747	651	651
q17	1311	1303	1290	1290
q18	12110	11062	11696	11062
q19	1198	1130	1120	1120
q20	2216	2239	1922	1922
q21	5725	4787	4576	4576
q22	518	476	433	433
Total cold run time: 60617 ms
Total hot run time: 54486 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 158326 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit 86e0aea08253bed3e91b021108a37e8de213c214, data reload: false

query5	4321	573	447	447
query6	463	213	210	210
query7	4932	612	336	336
query8	331	172	169	169
query9	8811	4033	4026	4026
query10	469	354	313	313
query11	5883	2221	2027	2027
query12	154	109	100	100
query13	1265	569	434	434
query14	6077	4271	3999	3999
query14_1	3834	3796	3820	3796
query15	205	196	185	185
query16	3022	493	457	457
query17	952	719	570	570
query18	2220	473	350	350
query19	215	189	151	151
query20	106	102	105	102
query21	237	163	130	130
query22	13121	12955	12806	12806
query23	15866	14881	14604	14604
query23_1	14736	14580	14708	14580
query24	7666	1722	1241	1241
query24_1	1273	1215	1230	1215
query25	575	460	394	394
query26	1570	359	211	211
query27	2614	585	380	380
query28	4570	2017	2039	2017
query29	1078	627	500	500
query30	353	260	226	226
query31	1181	1123	1041	1041
query32	114	64	62	62
query33	533	316	263	263
query34	1188	1123	591	591
query35	736	745	637	637
query36	775	776	692	692
query37	157	109	99	99
query38	1839	1768	1723	1723
query39	872	822	786	786
query39_1	786	783	789	783
query40	242	161	142	142
query41	64	62	60	60
query42	94	91	92	91
query43	311	315	278	278
query44	1432	772	768	768
query45	182	175	166	166
query46	983	1180	699	699
query47	1536	1566	1417	1417
query48	425	403	295	295
query49	580	394	295	295
query50	1093	419	340	340
query51	10415	10584	10624	10584
query52	84	86	80	80
query53	284	282	194	194
query54	271	222	231	222
query55	73	69	68	68
query56	300	310	288	288
query57	1043	979	911	911
query58	292	249	250	249
query59	1527	1554	1380	1380
query60	311	306	259	259
query61	158	149	149	149
query62	415	313	262	262
query63	230	193	197	193
query64	2477	1008	865	865
query65	3838	3830	3797	3797
query66	1744	459	374	374
query67	20158	19901	19951	19901
query68	3097	1527	982	982
query69	396	299	263	263
query70	859	769	779	769
query71	369	334	313	313
query72	2959	2621	2524	2524
query73	837	761	447	447
query74	4628	4484	4275	4275
query75	2368	2334	1986	1986
query76	1898	1118	765	765
query77	335	379	264	264
query78	11146	11103	10555	10555
query79	1432	1157	748	748
query80	1206	560	472	472
query81	516	327	284	284
query82	662	177	137	137
query83	379	326	298	298
query84	325	160	130	130
query85	984	601	518	518
query86	401	231	215	215
query87	1985	2015	1843	1843
query88	3715	2774	2749	2749
query89	387	310	279	279
query90	1891	199	199	199
query91	203	186	161	161
query92	67	59	54	54
query93	1662	1628	1021	1021
query94	700	330	315	315
query95	774	529	564	529
query96	1073	806	344	344
query97	2473	2453	2333	2333
query98	194	188	188	188
query99	720	734	617	617
Total cold run time: 246871 ms
Total hot run time: 158326 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
ClickBench: Total hot run time: 23.91 s
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
ClickBench test result on commit 86e0aea08253bed3e91b021108a37e8de213c214, data reload: false

query1	0.01	0.01	0.01
query2	0.09	0.04	0.05
query3	0.25	0.14	0.13
query4	1.61	0.13	0.14
query5	0.24	0.22	0.22
query6	1.17	0.86	0.85
query7	0.04	0.00	0.00
query8	0.06	0.04	0.05
query9	0.40	0.31	0.31
query10	0.57	0.55	0.55
query11	0.19	0.14	0.14
query12	0.19	0.15	0.14
query13	0.47	0.47	0.47
query14	1.01	0.99	1.00
query15	0.60	0.60	0.58
query16	0.32	0.34	0.34
query17	1.10	1.09	1.10
query18	0.21	0.20	0.19
query19	2.09	1.90	1.98
query20	0.01	0.02	0.01
query21	15.45	0.22	0.14
query22	4.75	0.06	0.05
query23	16.16	0.31	0.12
query24	2.97	0.40	0.32
query25	0.12	0.05	0.04
query26	0.73	0.22	0.15
query27	0.04	0.04	0.03
query28	3.54	0.78	0.35
query29	12.51	4.03	3.19
query30	0.27	0.15	0.15
query31	2.77	0.56	0.31
query32	3.22	0.58	0.50
query33	3.27	3.15	3.26
query34	15.45	3.96	3.28
query35	3.23	3.22	3.22
query36	0.54	0.44	0.45
query37	0.08	0.07	0.06
query38	0.05	0.04	0.04
query39	0.04	0.02	0.03
query40	0.17	0.15	0.14
query41	0.09	0.03	0.03
query42	0.03	0.02	0.02
query43	0.05	0.04	0.03
Total cold run time: 96.16 s
Total hot run time: 23.91 s


if (std::ranges::any_of(
data_types,
[](const auto& type) { return !type->have_maximum_size_of_value(); }) ||

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

这个函数完全没必要传data_type和加这个判断,用_has_variable_length_column(lambda_expr)就足够了啊

@linrrzqqq

Copy link
Copy Markdown
Collaborator Author

/review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Request changes: two distinct batching-budget issues remain.

Critical checkpoint conclusions:

  • Goal and coverage: The direct-reuse, selector mapping, const/sparse capture, first-result ownership, and nested-scope changes are generally coherent, and the new unit/regression cases cover those paths. However, the adaptive sizing goal is not met for a large variable-width capture, and the fixed-width estimator can defeat direct reuse. Missing boundary tests correspond to the two inline issues.
  • Correctness and lifecycle: Selector-to-capture mapping is consistent in direct and multi-batch paths, including empty/NULL rows; nested frame visibility/shadowing and COW/nullable result assembly are sound. LambdaFunction objects are factory-created per expression and budgets are prepared once, so no new concurrency, locking, or lifecycle issue was found.
  • Compatibility and parallel paths: Removing the unused base batch_size member does not affect array_filter/array_sort; legacy lambda binding remains handled. No protocol, storage-format, persistence, transaction, configuration, or observability change applies.
  • Performance and memory: Blocking issues are inline: variable-width captures can still overflow ColumnString despite the byte budget, and alias-only VColumnRefs are over-counted into unnecessary batches.
  • Tests: Static review only; no builds/tests were run per runner instructions. Existing SQL results are ordered and consistent, but add the two focused adaptive-budget regressions described inline.
  • User focus: No additional user-provided review focus.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Request changes: two distinct batching-budget issues remain.

Critical checkpoint conclusions:

  • Goal and coverage: The direct-reuse, selector mapping, const/sparse capture, first-result ownership, and nested-scope changes are generally coherent, and the new unit/regression cases cover those paths. However, the adaptive sizing goal is not met for a large variable-width capture, and the fixed-width estimator can defeat direct reuse. Missing boundary tests correspond to the two inline issues.
  • Correctness and lifecycle: Selector-to-capture mapping is consistent in direct and multi-batch paths, including empty/NULL rows; nested frame visibility/shadowing and COW/nullable result assembly are sound. LambdaFunction objects are factory-created per expression and budgets are prepared once, so no new concurrency, locking, or lifecycle issue was found.
  • Compatibility and parallel paths: Removing the unused base batch_size member does not affect array_filter/array_sort; legacy lambda binding remains handled. No protocol, storage-format, persistence, transaction, configuration, or observability change applies.
  • Performance and memory: Blocking issues are inline: variable-width captures can still overflow ColumnString despite the byte budget, and alias-only VColumnRefs are over-counted into unnecessary batches.
  • Tests: Static review only; no builds/tests were run per runner instructions. Existing SQL results are ordered and consistent, but add the two focused adaptive-budget regressions described inline.
  • User focus: No additional user-provided review focus.

: current_bytes + additional_bytes;
};

if (_has_variable_length_column(lambda_expr)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P1] Keep expanded variable-width captures within the byte budget

This early return skips preferred_block_size_bytes exactly when a row-dependent STRING/ARRAY capture can be most expensive. For example, with batch_size = 65535, one non-const 70,000-byte string capture and a 65,535-element input array take the direct path, which repeats that string into at least 4,587,450,000 bytes before evaluating an otherwise fixed-width lambda. That exceeds ColumnString's 4,294,967,295-byte offset limit and fails a valid query even though the normal block budget is much smaller. Please include the measured get_max_row_byte_size() of expanded non-const captures (and other known variable inputs) in effective_max_rows, while retaining a conservative policy for unknown intermediates, and add a large variable-capture test that proves the work is split below the byte/offset limit.

return _lambda_block_budget.max_rows;
}

size_t estimated_lambda_bytes_per_row = lambda_expr->estimate_memory(1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P2] Exclude alias-only lambda references from the byte estimate

lambda_expr->estimate_memory(1) charges every VColumnRef occurrence as a newly allocated value, and the loop below then charges the backing lambda_data again. Inside array_map the lambda is executed with a null selector, so those refs only return the existing lambda-block column. For example, 64 references to x in a fixed-width greatest(x, ... x) over 20,000 nullable INTs with a 1 MiB budget are estimated at roughly 320 bytes/row and split into about seven batches, although the single input plus result is only about 200 KiB and fits the direct path. Please make this lambda-local estimate treat forwarding refs (and the forwarding lambda wrapper) as aliases rather than materialized intermediates, without changing selector-aware estimates globally, and add an adaptive-budget test with repeated argument refs.

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.

3 participants