• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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