1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc. All rights reserved.
3 //
4 // Use of this source code is governed by a BSD-style
5 // license that can be found in the LICENSE file or at
6 // https://developers.google.com/open-source/licenses/bsd
7
8 #include "google/protobuf/compiler/cpp/padding_optimizer.h"
9
10 #include "absl/log/absl_log.h"
11 #include "google/protobuf/compiler/cpp/helpers.h"
12
13 namespace google {
14 namespace protobuf {
15 namespace compiler {
16 namespace cpp {
17
18 namespace {
19
20 // FieldGroup is just a helper for PaddingOptimizer below. It holds a vector of
21 // fields that are grouped together because they have compatible alignment, and
22 // a preferred location in the final field ordering.
23 class FieldGroup {
24 public:
FieldGroup()25 FieldGroup() : preferred_location_(0) {}
26
27 // A group with a single field.
FieldGroup(double preferred_location,const FieldDescriptor * field)28 FieldGroup(double preferred_location, const FieldDescriptor* field)
29 : preferred_location_(preferred_location), fields_(1, field) {}
30
31 // Append the fields in 'other' to this group.
Append(const FieldGroup & other)32 void Append(const FieldGroup& other) {
33 if (other.fields_.empty()) {
34 return;
35 }
36 // Preferred location is the average among all the fields, so we weight by
37 // the number of fields on each FieldGroup object.
38 preferred_location_ = (preferred_location_ * fields_.size() +
39 (other.preferred_location_ * other.fields_.size())) /
40 (fields_.size() + other.fields_.size());
41 fields_.insert(fields_.end(), other.fields_.begin(), other.fields_.end());
42 }
43
SetPreferredLocation(double location)44 void SetPreferredLocation(double location) { preferred_location_ = location; }
fields() const45 const std::vector<const FieldDescriptor*>& fields() const { return fields_; }
46
47 // FieldGroup objects sort by their preferred location.
operator <(const FieldGroup & other) const48 bool operator<(const FieldGroup& other) const {
49 return preferred_location_ < other.preferred_location_;
50 }
51
52 private:
53 // "preferred_location_" is an estimate of where this group should go in the
54 // final list of fields. We compute this by taking the average index of each
55 // field in this group in the original ordering of fields. This is very
56 // approximate, but should put this group close to where its member fields
57 // originally went.
58 double preferred_location_;
59 std::vector<const FieldDescriptor*> fields_;
60 // We rely on the default copy constructor and operator= so this type can be
61 // used in a vector.
62 };
63
64 } // namespace
65
OptimizeLayoutHelper(std::vector<const FieldDescriptor * > * fields,const Options & options,MessageSCCAnalyzer * scc_analyzer)66 static void OptimizeLayoutHelper(std::vector<const FieldDescriptor*>* fields,
67 const Options& options,
68 MessageSCCAnalyzer* scc_analyzer) {
69 if (fields->empty()) return;
70
71 // The sorted numeric order of Family determines the declaration order in the
72 // memory layout.
73 enum Family {
74 REPEATED = 0,
75 STRING = 1,
76 MESSAGE = 2,
77 ZERO_INITIALIZABLE = 3,
78 OTHER = 4,
79 kMaxFamily
80 };
81
82 // First divide fields into those that align to 1 byte, 4 bytes or 8 bytes.
83 std::vector<FieldGroup> aligned_to_1[kMaxFamily];
84 std::vector<FieldGroup> aligned_to_4[kMaxFamily];
85 std::vector<FieldGroup> aligned_to_8[kMaxFamily];
86 for (int i = 0; i < fields->size(); ++i) {
87 const FieldDescriptor* field = (*fields)[i];
88
89 Family f = OTHER;
90 if (field->is_repeated()) {
91 f = REPEATED;
92 } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_STRING) {
93 f = STRING;
94 } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
95 f = MESSAGE;
96 } else if (CanInitializeByZeroing(field, options, scc_analyzer)) {
97 f = ZERO_INITIALIZABLE;
98 }
99
100 const int j = field->number();
101 switch (EstimateAlignmentSize(field)) {
102 case 1:
103 aligned_to_1[f].push_back(FieldGroup(j, field));
104 break;
105 case 4:
106 aligned_to_4[f].push_back(FieldGroup(j, field));
107 break;
108 case 8:
109 aligned_to_8[f].push_back(FieldGroup(j, field));
110 break;
111 default:
112 ABSL_LOG(FATAL) << "Unknown alignment size "
113 << EstimateAlignmentSize(field) << "for a field "
114 << field->full_name() << ".";
115 }
116 }
117
118 // For each family, group fields to optimize padding.
119 for (int f = 0; f < kMaxFamily; f++) {
120 // Now group fields aligned to 1 byte into sets of 4, and treat those like a
121 // single field aligned to 4 bytes.
122 for (int i = 0; i < aligned_to_1[f].size(); i += 4) {
123 FieldGroup field_group;
124 for (int j = i; j < aligned_to_1[f].size() && j < i + 4; ++j) {
125 field_group.Append(aligned_to_1[f][j]);
126 }
127 aligned_to_4[f].push_back(field_group);
128 }
129 // Sort by preferred location to keep fields as close to their field number
130 // order as possible. Using stable_sort ensures that the output is
131 // consistent across runs.
132 std::stable_sort(aligned_to_4[f].begin(), aligned_to_4[f].end());
133
134 // Now group fields aligned to 4 bytes (or the 4-field groups created above)
135 // into pairs, and treat those like a single field aligned to 8 bytes.
136 for (int i = 0; i < aligned_to_4[f].size(); i += 2) {
137 FieldGroup field_group;
138 for (int j = i; j < aligned_to_4[f].size() && j < i + 2; ++j) {
139 field_group.Append(aligned_to_4[f][j]);
140 }
141 if (i == aligned_to_4[f].size() - 1) {
142 if (f == OTHER) {
143 // Move incomplete 4-byte block to the beginning. This is done to
144 // pair with the (possible) leftover blocks from the
145 // ZERO_INITIALIZABLE family.
146 field_group.SetPreferredLocation(-1);
147 } else {
148 // Move incomplete 4-byte block to the end.
149 field_group.SetPreferredLocation(double{FieldDescriptor::kMaxNumber});
150 }
151 }
152 aligned_to_8[f].push_back(field_group);
153 }
154 // Sort by preferred location.
155 std::stable_sort(aligned_to_8[f].begin(), aligned_to_8[f].end());
156 }
157
158 // Now pull out all the FieldDescriptors in order.
159 fields->clear();
160 for (int f = 0; f < kMaxFamily; ++f) {
161 for (int i = 0; i < aligned_to_8[f].size(); ++i) {
162 fields->insert(fields->end(), aligned_to_8[f][i].fields().begin(),
163 aligned_to_8[f][i].fields().end());
164 }
165 }
166 }
167
168 // Reorder 'fields' so that if the fields are output into a c++ class in the new
169 // order, fields of similar family (see below) are together and within each
170 // family, alignment padding is minimized.
171 //
172 // We try to do this while keeping each field as close as possible to its field
173 // number order so that we don't reduce cache locality much for function that
174 // access each field in order. Originally, OptimizePadding used declaration
175 // order for its decisions, but generated code minus the serializer/parsers uses
176 // the output of OptimizePadding as well (stored in
177 // MessageGenerator::optimized_order_). Since the serializers use field number
178 // order, we use that as a tie-breaker.
179 //
180 // We classify each field into a particular "family" of fields, that we perform
181 // the same operation on in our generated functions.
182 //
183 // REPEATED is placed first, as the C++ compiler automatically initializes
184 // these fields in layout order.
185 //
186 // STRING is grouped next, as our Clear/SharedCtor/SharedDtor walks it and
187 // calls ArenaStringPtr::Destroy on each.
188 //
189 // MESSAGE is grouped next, as our Clear/SharedDtor code walks it and calls
190 // delete on each. We initialize these fields with a NULL pointer (see
191 // MessageFieldGenerator::GenerateConstructorCode), which allows them to be
192 // memset.
193 //
194 // ZERO_INITIALIZABLE is memset in Clear/SharedCtor
195 //
196 // OTHER these fields are initialized one-by-one.
197 //
198 // If there are split fields in `fields`, they will be placed at the end. The
199 // order within split fields follows the same rule, aka classify and order by
200 // "family".
OptimizeLayout(std::vector<const FieldDescriptor * > * fields,const Options & options,MessageSCCAnalyzer * scc_analyzer)201 void PaddingOptimizer::OptimizeLayout(
202 std::vector<const FieldDescriptor*>* fields, const Options& options,
203 MessageSCCAnalyzer* scc_analyzer) {
204 std::vector<const FieldDescriptor*> normal;
205 std::vector<const FieldDescriptor*> split;
206 for (const auto* field : *fields) {
207 if (ShouldSplit(field, options)) {
208 split.push_back(field);
209 } else {
210 normal.push_back(field);
211 }
212 }
213 OptimizeLayoutHelper(&normal, options, scc_analyzer);
214 OptimizeLayoutHelper(&split, options, scc_analyzer);
215 fields->clear();
216 fields->insert(fields->end(), normal.begin(), normal.end());
217 fields->insert(fields->end(), split.begin(), split.end());
218 }
219
220 } // namespace cpp
221 } // namespace compiler
222 } // namespace protobuf
223 } // namespace google
224