Skip to content

[opt](build) Declare extern template for existing explicit instantiations - #66807

Open
morningman wants to merge 6 commits into
apache:masterfrom
morningman:be-build-opt-2-extern-template
Open

[opt](build) Declare extern template for existing explicit instantiations#66807
morningman wants to merge 6 commits into
apache:masterfrom
morningman:be-build-opt-2-extern-template

Conversation

@morningman

Copy link
Copy Markdown
Contributor

Part of the BE build-time optimization series tracked in #66715.

Split out of #66510. With the include-edge
surgery (#66400, #66672) and the unity-build line (#66712, #66776, #66789) merged,
this PR opens the third mechanism of the batch: making the explicit template
instantiations we already have
actually pay for themselves by declaring them
extern template in the headers.

What problem does this PR solve?

Related PR: #66510, #66789

Problem Summary:

be/src already contains ~221 template class explicit instantiation
definitions
(columns, DataTypes, SerDes, the operator families, …). But almost
none of them are announced in the corresponding headers with an
extern template declaration. The result: every consumer TU that touches
ColumnVector<T> / DataTypeDecimalSerDe<T> / AsyncWriterSink<W, P> still
implicitly instantiates the whole class again, compiles the member functions
as weak symbols, and the linker then throws all the duplicates away. The
explicit-instantiation TU does the same work one more time. We pay the template
instantiation cost N+1 times and keep exactly one copy.

extern template is the standard C++11 tool for this: it suppresses implicit
instantiation in consumers and pins code generation to the one TU that already
carries the explicit definition. It changes symbol ownership only, not
generated code
— a consumer TU compiled before/after this PR produces
byte-identical code for its own functions (verified on a control TU during the
original measurement round).

What the commits do:

  1. Column classes (column_vector.h 18, column_decimal.h 5,
    column_string.h 2): declare extern the 25 instantiations defined in the
    matching .cpp files.
  2. DataType / SerDe surface (6 headers): 47 externs for the
    date/datetime/decimal/number/string SerDe instantiation sets.
  3. Exec operator families (39 headers, 99 externs): AsyncWriterSink,
    DataSinkOperatorX, OperatorX, partitioners, aggregation/table-function
    operators — all instantiated centrally (mostly in operator.cpp) since the
    operator refactor, never externed.
  4. Narrow surfaces (17 headers, 75 externs): parquet/orc readers, segment
    iterators, frame-of-reference coding, phrase queries, JSON parser.
  5. DataTypeNumber<T>: the base class was explicitly instantiated but the
    derived class itself was not — instantiations existed nowhere, so every user
    built the full class. Adds the 8 explicit definitions in
    data_type_number_base.cpp plus matching externs.
  6. One-line correctness fix the externs exposed: inverted_index_writer.h
    forward-declared CppTypeTraits itself; triggering class-level instantiation
    from the extern requires the real definition, and exactly one storage unity
    batch (of 13k+ TUs) lacked it transitively. Include storage/types.h
    directly.

Measured results

Numbers below were taken on the original development branch before the unity
line landed
(macOS arm64, clang 20, -j6, no PCH for the sentinel probes),
because that is where the mechanism was isolated. Landing after unity
(#66789), part of the win is already absorbed — sibling files inside one unity
batch share a single implicit instantiation — so the remaining surface here is
the SKIP-listed heavy individual TUs, cross-batch dedup, and the BE UT tree:

metric before after
full cold build (-j6) 33m15s 32m36s (−2.0%)
multiply.cpp sentinel TU (no PCH) 78.7s 73.0s (−7.3%)
plus.cpp sentinel TU (no PCH) 56.3s 52.0s (−7.6%)
weak-symbol overlap multiply∩plus 1552 491 (−68%)
doris_be size (pre-unity layout) 1856MB 1809MB (−2.5%)

Validation of this PR's tree (master + these 6 commits, macOS arm64 clang20,
unity=ON + PCH=ON):

  • full BE build: 7446/7446 edges, zero failures, doris_be links at 319MB
    (same as current master);
  • static pairing audit: all 266 extern template declarations added here
    resolve to an existing explicit instantiation definition in the current tree;
  • BE UT (BUILD_TYPE_UT=Debug): 8501/8501 edges compiled, doris_be_test
    links with zero duplicate/undefined symbols — the sensitive surface for
    an extern-template change, since tests link the full static-library set;
  • git clang-format clean against master.

Methodology, and what we deliberately did NOT extern

The go/no-go gauge for each candidate was weak symbols owned by consumer
.o files
(llvm-nm -C | grep ' [VvWw] ' filtered by the class prefix), not
the count of instantiation statements. By that gauge three whole families were
rejected as free-of-benefit and are intentionally absent here:

  • Allocator (48 instantiation sites): consumers own ≈0 weak symbols of it;
  • PODArray: same;
  • COWHelper base-class instantiations: same.

Three further individual candidates were dropped because their include
topology would have needed real surgery for a mechanism whose gain there is ≈0.

Risks / disclosures

  • Runtime impact: none by construction. extern template moves symbol
    ownership; it does not change what code is generated for the anchor TU, and
    inline/constexpr members remain inlinable at call sites exactly as before.
  • Cross-platform: all local validation is macOS/clang20. gcc handles
    extern template + in-class-defined members slightly differently in
    diagnostics; the Performance pipeline (the only gcc lane) is the
    authoritative check. Please watch its first round.
  • The DORIS_DEV_DEBUG_INFO developer knob that rode along in the original
    branch is intentionally not in this PR (unrelated mechanism, will be
    proposed separately).

morningman and others added 6 commits August 16, 2026 14:39
column_vector.cpp, column_decimal.cpp and column_string.cpp already
instantiate their class templates explicitly (18 + 5 + 2 types), but
without extern declarations in the headers every including TU still
instantiates all reachable members implicitly, only for the linker to
deduplicate them again: multiply.o alone carried 629 weak definitions
that are provided by the explicit instantiations.

Declaring them extern suppresses the per-TU work across the ~380 TUs
including these headers. Measured on the sentinel TUs (no-PCH, single
compile): multiply 78.7s -> 77.3s with .text -5%, plus 56.3s -> 53.7s
with .text -9%, 629 weak defs gone from each; the linked doris_be
shrinks by 36 MB (-1.9%).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0121aRtZYjjYdNr2a6z8BLzR
data_type_number_base.cpp, data_type_decimal.cpp, data_type_number_serde.cpp,
data_type_decimal_serde.cpp, data_type_string_serde.cpp and
data_type_date_or_datetime_serde.cpp already instantiate their class
templates explicitly (16 + 5 + 16 + 5 + 3 + 2 types); mirror them with
extern template declarations in the headers so including TUs no longer
re-instantiate the reachable members. These headers propagate through
DataTypeNumber/Block into essentially every TU, same surface as P1-1.

data_type_decimal_serde.h forward-declared ColumnDecimal but its member
layout uses ColumnDecimal<T>::Container, so the explicit instantiation
declaration needs the complete type: replace the forward declaration
with the real include (itself extern'd since P1-1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0121aRtZYjjYdNr2a6z8BLzR
…ions

operator.cpp, scan_operator.cpp, join_probe_operator.cpp,
join_build_sink_operator.cpp, the set operator family, partitioner.cpp
and analytic_sink_operator.cpp already instantiate their operator class
templates explicitly (99 specializations in total); mirror them with
extern template declarations so consumer TUs (pipeline construction,
factories, every operator TU including a sibling header) stop
re-instantiating the operator base members.

Placement follows argument visibility: the PipelineX(Sink)LocalState
block sits in operator.h where dependency.h makes every SharedState
complete, except the RecCTE pair whose shared state lives in
rec_cte_shared_state.h, so those go to the rec_cte operator headers.
Specializations parameterized by per-operator LocalState/Writer types go
to the header defining that type. BE_TEST-only instantiations keep the
same guard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0121aRtZYjjYdNr2a6z8BLzR
…tions

Mirror the remaining explicit instantiation definitions with extern
template declarations: frame-of-reference codec (22), inverted index
column writer (24), both parquet reader families (12 + 12, distinct
namespaces), query_v2 scorers (11), DateV2Value (2), json_each table
function (2), JSONDataParser and the page-cache footer page (1 + 1).

Two support changes surfaced by the extern declarations:
- ForEncoder<uint24_t>::numeric_limits_max is specialized in the .cpp,
  so the header must declare the specialization before the extern block
  (specialization-after-instantiation otherwise).
- disjunction_scorer.h / occur_boolean_weight.h consumed the combiner
  alias types without including score_combiner.h.

Skipped, cost exceeds the measured duplication: InvertedIndexVisitor
(member specializations matches/compare would all need header
declarations, 83 weak defs repo-wide), ResultBlockBuffer (class
instantiation needs the complete ctx types from exec/sink, a layering
violation for 36 weak defs), VerticalBetaRowsetWriter (derives from its
argument; cloud/binlog writer types are not visible in the header,
54 weak defs).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0121aRtZYjjYdNr2a6z8BLzR
DataTypeNumberBase is explicitly instantiated but the derived
DataTypeNumber (the class every factory and arithmetic TU actually
names via the DataTypeInt*/Float*/Bool aliases) was not, so each such
TU re-emitted its vtable, typeinfo and equals for every used type.
Instantiate the eight aliased specializations next to the base ones in
data_type_number_base.cpp and declare them extern in the header.

Surveyed for the same gap, measured as not worth covering: PODArray
(members mostly inline away, <= 7 weak defs per TU) and the COW/COWHelper
bases (already suppressed transitively by the P1-1 column externs;
only ~24 trivial nested-pointer symbols remain in multiply).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0121aRtZYjjYdNr2a6z8BLzR
The extern template declarations force class-level instantiation of
InvertedIndexColumnWriter, whose CppType member alias needs the
CppTypeTraits specializations from storage/types.h. The header only
forward-declared the primary template, so TUs that did not pull the
traits in transitively (storage unity_16) failed to compile; the
defining TU passed only via transitive includes. Include the real
header and drop the forward declaration.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0121aRtZYjjYdNr2a6z8BLzR
@hello-stephen

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Apache Doris.
Don't know what should be done next? See How to process your PR.

Please clearly describe your PR:

  1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
  2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
  3. What features were added. Why was this function added?
  4. Which code was refactored and why was this part of the code refactored?
  5. Which functions were optimized and what is the difference before and after the optimization?

@morningman

Copy link
Copy Markdown
Contributor Author

run buildall

@hello-stephen

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

------ Round 1 ----------------------------------
orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
============================================
q1	17584	3030	3014	3014
q2	q3	10864	919	501	501
q4	4677	261	209	209
q5	7651	584	389	389
q6	138	118	94	94
q7	523	509	390	390
q8	9287	911	868	868
q9	3492	2337	2369	2337
q10	6522	891	716	716
q11	453	255	248	248
q12	695	402	335	335
q13	17869	1868	1568	1568
q14	163	153	145	145
q15	q16	457	405	370	370
q17	828	801	731	731
q18	3235	2307	2301	2301
q19	1282	925	799	799
q20	697	550	450	450
q21	5678	1766	1935	1766
q22	338	266	226	226
Total cold run time: 92433 ms
Total hot run time: 17457 ms

----- Round 2, with runtime_filter_mode=off -----
orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
============================================
q1	3381	3301	3283	3283
q2	q3	2319	2371	2165	2165
q4	1252	1218	910	910
q5	2235	2197	2163	2163
q6	170	123	91	91
q7	1062	961	936	936
q8	1623	1448	1433	1433
q9	3205	3179	3162	3162
q10	1995	1927	1701	1701
q11	363	279	267	267
q12	480	461	349	349
q13	1864	1882	1567	1567
q14	182	169	158	158
q15	q16	400	408	373	373
q17	1032	1030	1026	1026
q18	5183	4569	4989	4569
q19	853	820	839	820
q20	1015	987	1036	987
q21	3811	3257	3420	3257
q22	406	348	317	317
Total cold run time: 32831 ms
Total hot run time: 29534 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 83455 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 dfcc73c99c461b6e156d3827d11f9bce21b63ba8, data reload: false

query5	4267	409	317	317
query6	416	162	148	148
query7	4886	462	262	262
query8	305	116	115	115
query9	8780	2884	2891	2884
query10	415	243	220	220
query11	5392	1063	938	938
query12	115	69	67	67
query13	1213	446	332	332
query14	5944	2038	1894	1894
query14_1	1794	1791	1788	1788
query15	174	118	119	118
query16	957	358	349	349
query17	799	444	358	358
query18	2357	326	228	228
query19	185	143	109	109
query20	71	69	68	68
query21	216	117	100	100
query22	5404	5350	5342	5342
query23	7174	6953	6615	6615
query23_1	6802	6811	6794	6794
query24	7294	1074	780	780
query24_1	773	771	774	771
query25	476	284	234	234
query26	1267	275	166	166
query27	2711	439	300	300
query28	4689	1502	1493	1493
query29	1040	431	343	343
query30	289	179	150	150
query31	1033	652	612	612
query32	95	47	49	47
query33	482	199	171	171
query34	1054	887	482	482
query35	406	394	343	343
query36	563	554	508	508
query37	126	85	75	75
query38	1104	859	823	823
query39	525	533	518	518
query39_1	527	534	529	529
query40	238	123	108	108
query41	52	54	55	54
query42	77	74	75	74
query43	262	252	217	217
query44	
query45	142	103	109	103
query46	764	867	560	560
query47	970	979	967	967
query48	305	320	241	241
query49	549	251	198	198
query50	835	340	270	270
query51	7992	8179	7939	7939
query52	71	73	62	62
query53	206	208	159	159
query54	269	169	166	166
query55	81	56	52	52
query56	220	218	222	218
query57	691	668	612	612
query58	247	195	181	181
query59	1110	1073	997	997
query60	301	199	190	190
query61	110	119	114	114
query62	450	206	170	170
query63	183	161	153	153
query64	2887	700	583	583
query65	
query66	1838	308	284	284
query67	10278	9994	9877	9877
query68	
query69	367	216	209	209
query70	589	608	562	562
query71	300	247	226	226
query72	2328	1735	1605	1605
query73	730	595	341	341
query74	2017	1209	1148	1148
query75	1254	1141	1012	1012
query76	2403	698	515	515
query77	233	258	197	197
query78	5219	4896	4731	4731
query79	1363	804	603	603
query80	545	404	346	346
query81	434	199	188	188
query82	655	134	109	109
query83	350	256	238	238
query84	
query85	950	474	438	438
query86	347	173	172	172
query87	1042	1028	940	940
query88	2867	2168	2177	2168
query89	359	241	216	216
query90	1997	157	151	151
query91	168	157	136	136
query92	54	47	45	45
query93	1308	1139	759	759
query94	507	299	258	258
query95	686	439	333	333
query96	816	598	270	270
query97	1167	1089	1052	1052
query98	142	132	128	128
query99	575	364	326	326
Total cold run time: 174430 ms
Total hot run time: 83455 ms

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