react-native-windows 0.81.35 → 0.81.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/Microsoft.ReactNative/Fabric/Composition/TooltipService.cpp +48 -2
  2. package/Microsoft.ReactNative/Fabric/WindowsImageManager.cpp +40 -13
  3. package/Microsoft.ReactNative/TurboModulesProvider.cpp +30 -13
  4. package/PropertySheets/Generated/PackageVersion.g.props +3 -3
  5. package/Scripts/creaternwlib.cmd +2 -2
  6. package/external/fast-float/.syncignore +0 -1
  7. package/external/fast-float/README.md +545 -0
  8. package/external/fmt/.syncignore +0 -2
  9. package/external/fmt/README.md +484 -0
  10. package/external/fmt/doc/ChangeLog-old.md +3290 -0
  11. package/external/fmt/doc/api.md +650 -0
  12. package/external/fmt/doc/fmt.css +61 -0
  13. package/external/fmt/doc/fmt.js +4 -0
  14. package/external/fmt/doc/get-started.md +222 -0
  15. package/external/fmt/doc/index.md +151 -0
  16. package/external/fmt/doc/perf.svg +1 -0
  17. package/external/fmt/doc/python-license.txt +290 -0
  18. package/external/fmt/doc/syntax.md +887 -0
  19. package/external/folly/.syncignore +2 -3
  20. package/external/folly/README.md +263 -0
  21. package/external/folly/folly/docs/AtomicHashMap.md +134 -0
  22. package/external/folly/folly/docs/Benchmark.md +310 -0
  23. package/external/folly/folly/docs/Conv.md +291 -0
  24. package/external/folly/folly/docs/Dynamic.md +220 -0
  25. package/external/folly/folly/docs/DynamicConverter.md +71 -0
  26. package/external/folly/folly/docs/Executors.md +63 -0
  27. package/external/folly/folly/docs/FBString.md +46 -0
  28. package/external/folly/folly/docs/FBVector.md +200 -0
  29. package/external/folly/folly/docs/Fbvector--graphical_solutions.png +0 -0
  30. package/external/folly/folly/docs/Format.md +177 -0
  31. package/external/folly/folly/docs/Function.md +92 -0
  32. package/external/folly/folly/docs/Futures.md +263 -0
  33. package/external/folly/folly/docs/GroupVarint.md +46 -0
  34. package/external/folly/folly/docs/Histogram.md +104 -0
  35. package/external/folly/folly/docs/MapUtils.md +80 -0
  36. package/external/folly/folly/docs/Overview.md +263 -0
  37. package/external/folly/folly/docs/PackedSyncPtr.md +77 -0
  38. package/external/folly/folly/docs/Poly.md +500 -0
  39. package/external/folly/folly/docs/ProducerConsumerQueue.md +82 -0
  40. package/external/folly/folly/docs/Rcu.md +218 -0
  41. package/external/folly/folly/docs/SmallLocks.md +24 -0
  42. package/external/folly/folly/docs/Synchronized.md +793 -0
  43. package/external/folly/folly/docs/ThreadCachedInt.md +98 -0
  44. package/external/folly/folly/docs/ThreadLocal.md +105 -0
  45. package/external/folly/folly/docs/Traits.md +161 -0
  46. package/external/folly/folly/docs/small_vector.md +64 -0
  47. package/external/folly/folly/docs/style.css +23 -0
  48. package/external/folly/static/logo.svg +1 -0
  49. package/package.json +2 -2
  50. package/templates/cpp-lib/example/metro.config.js +31 -6
  51. package/Scripts/Warm-RnwFeedCache.ps1 +0 -413
@@ -0,0 +1,220 @@
1
+ `folly/dynamic.h`
2
+ -----------------
3
+
4
+ `folly/dynamic.h` provides a runtime dynamically typed value for
5
+ C++, similar to the way languages with runtime type systems work
6
+ (e.g. Python). It can hold types from a predetermined set of types
7
+ (ints, bools, arrays of other dynamics, etc), similar to something like
8
+ `std::variant`, but the syntax is intended to be a little more like
9
+ using the native type directly.
10
+
11
+ ### Overview
12
+ ***
13
+
14
+ Here are some code samples to get started (assumes a `using
15
+ folly::dynamic;` was used):
16
+
17
+ ``` Cpp
18
+ dynamic twelve = 12; // creates a dynamic that holds an integer
19
+ dynamic str = "string"; // yep, this one is an fbstring
20
+
21
+ // A few other types.
22
+ dynamic nul = nullptr;
23
+ dynamic boolean = false;
24
+
25
+ // Arrays can be initialized with dynamic::array.
26
+ dynamic array = dynamic::array("array ", "of ", 4, " elements");
27
+ assert(array.size() == 4);
28
+ dynamic emptyArray = dynamic::array;
29
+ assert(emptyArray.empty());
30
+
31
+ // Maps from dynamics to dynamics are called objects. The
32
+ // dynamic::object constant is how you make an empty map from dynamics
33
+ // to dynamics.
34
+ dynamic map = dynamic::object;
35
+ map["something"] = 12;
36
+ map["another_something"] = map["something"] * 2;
37
+
38
+ // Dynamic objects may be initialized this way
39
+ dynamic map2 = dynamic::object("something", 12)("another_something", 24);
40
+ ```
41
+
42
+ ### Runtime Type Checking and Conversions
43
+ ***
44
+
45
+ Any operation on a dynamic requires checking at runtime that the
46
+ type is compatible with the operation. If it isn't, you'll get a
47
+ `folly::TypeError`. Other exceptions can also be thrown if
48
+ you try to do something impossible (e.g. if you put a very large
49
+ 64-bit integer in and try to read it out as a double).
50
+
51
+ More examples should hopefully clarify this:
52
+
53
+ ``` Cpp
54
+ dynamic dint = 42;
55
+
56
+ dynamic str = "foo";
57
+ dynamic anotherStr = str + "something"; // fine
58
+ dynamic thisThrows = str + dint; // TypeError is raised
59
+ ```
60
+
61
+ Explicit type conversions can be requested for some of the basic types:
62
+
63
+ ``` Cpp
64
+ dynamic dint = 12345678;
65
+ dynamic doub = dint.asDouble(); // doub will hold 12345678.0
66
+ dynamic str = dint.asString(); // str == "12345678"
67
+
68
+ dynamic hugeInt = std::numeric_limits<int64_t>::max();
69
+ dynamic hugeDoub = hugeInt.asDouble(); // throws a folly/Conv.h error,
70
+ // since it can't fit in a double
71
+ ```
72
+
73
+ For more complicated conversions, see [DynamicConverter](DynamicConverter.md).
74
+
75
+ ### Comparison Operators and Hashing
76
+ #### Equality Operators
77
+ Equality operators (`==`, `!=`) are supported for all types.
78
+
79
+ For dynamics of the same type, the underlying equality operator shall apply.
80
+
81
+ For comparisons between different numeric types (double and int64), numeric
82
+ equality will apply - thus `2.0 == 2`.
83
+
84
+ Values of any other different types will be deemed to not be equal.
85
+
86
+ #### Ordering operators
87
+ Ordering operators (`<`, `<=`, `>`, `>=`) are supported for all types, except
88
+ `dynamic::object` which will throw if it is involved in an ordering operator.
89
+
90
+ For dynamics of the same type, the underlying ordering operator shall apply.
91
+
92
+ For comparisons between different numeric types (double and int64), numeric
93
+ ordering will apply - thus `1.5 < 2`.
94
+
95
+ Ordering of values between other different types will maintain total ordering
96
+ properties and be consistent within a given binary run, and thus safe for use in
97
+ e.g. `std::set`. The actual ordering is undefined and could change across
98
+ versions, thus a dependency should not be taken outside of the total ordering
99
+ property within a given binary.
100
+
101
+ #### Hashing
102
+ Hashing is supported by all types, and the hashes of two values will match if
103
+ they are equal per `dynamic::operator==`.
104
+
105
+ As a result, numerical types have the same numerical hashing regardless of int64
106
+ vs double - so e.g. `std::hash<dynamic>()(2)` will give the same value as
107
+ `std::hash<dynamic>()(2.0)`.
108
+
109
+ ### Iteration and Lookup
110
+ ***
111
+
112
+ You can iterate over dynamic arrays as you would over any C++ sequence container.
113
+
114
+ ``` Cpp
115
+ dynamic array = dynamic::array(2, 3, "foo");
116
+
117
+ for (auto& val : array) {
118
+ doSomethingWith(val);
119
+ }
120
+ ```
121
+
122
+ You can iterate over dynamic maps by calling `items()`, `keys()`,
123
+ `values()`, which behave similarly to the homonymous methods of Python
124
+ dictionaries.
125
+
126
+ ``` Cpp
127
+ dynamic obj = dynamic::object(2, 3)("hello", "world")("x", 4);
128
+
129
+ for (auto& pair : obj.items()) {
130
+ // Key is pair.first, value is pair.second
131
+ processKey(pair.first);
132
+ processValue(pair.second);
133
+ }
134
+
135
+ for (auto& key : obj.keys()) {
136
+ processKey(key);
137
+ }
138
+
139
+ for (auto& value : obj.values()) {
140
+ processValue(value);
141
+ }
142
+ ```
143
+
144
+ You can find an element by key in a dynamic map using the `find()` method,
145
+ which returns an iterator compatible with `items()`:
146
+
147
+ ``` Cpp
148
+ dynamic obj = dynamic::object(2, 3)("hello", "world")("x", 4);
149
+
150
+ auto pos = obj.find("hello");
151
+ // pos->first is "hello"
152
+ // pos->second is "world"
153
+
154
+ auto pos = obj.find("no_such_key");
155
+ // pos == obj.items().end()
156
+ ```
157
+
158
+
159
+ ### Use for JSON
160
+ ***
161
+
162
+ The original motivation for implementing this type was to try to
163
+ make dealing with json documents in C++ almost as easy as it is
164
+ in languages with dynamic type systems (php or javascript, etc).
165
+ The reader can judge whether we're anywhere near that goal, but
166
+ here's what it looks like:
167
+
168
+ ``` Cpp
169
+ // Parsing JSON strings and using them.
170
+ std::string jsonDocument = R"({"key":12,"key2":[false, null, true, "yay"]})";
171
+ dynamic parsed = folly::parseJson(jsonDocument);
172
+ assert(parsed["key"] == 12);
173
+ assert(parsed["key2"][0] == false);
174
+ assert(parsed["key2"][1] == nullptr);
175
+
176
+ // Building the same document programmatically.
177
+ dynamic sonOfAJ = dynamic::object
178
+ ("key", 12)
179
+ ("key2", dynamic::array(false, nullptr, true, "yay"));
180
+
181
+ // Printing. (See also folly::toPrettyJson)
182
+ auto str = folly::toJson(sonOfAJ);
183
+ assert(jsonDocument.compare(str) == 0);
184
+ ```
185
+
186
+ ### Performance
187
+ ***
188
+
189
+ Dynamic typing is more expensive than static typing, even when
190
+ you do it in C++. ;)
191
+
192
+ However, some effort has been made to keep `folly::dynamic` and
193
+ the json (de)serialization at least reasonably performant for
194
+ common cases. The heap is only used for arrays and objects, and
195
+ move construction is fully supported. String formatting
196
+ internally also uses the highly performant `folly::to<>` (see
197
+ `folly/Conv.h`).
198
+
199
+ A trade off to keep in mind though, is that
200
+ `sizeof(folly::dynamic)` is 64 bytes. You probably don't want to
201
+ use it if you need to allocate large numbers of them (prefer
202
+ static types, etc).
203
+
204
+ ### Some Design Rationale
205
+ ***
206
+
207
+ **Q. Why doesn't a dynamic string support begin(), end(), and operator[]?**
208
+
209
+ The value_type of a dynamic iterator is `dynamic`, and `operator[]`
210
+ (or the `at()` function) has to return a reference to a dynamic. If
211
+ we wanted this to work for strings, this would mean we'd have to
212
+ support dynamics with a character type, and moreover that the internal
213
+ representation of strings would be such that we can hand out
214
+ references to dynamic as accessors on individual characters. There
215
+ are a lot of potential efficiency drawbacks with this, and it seems
216
+ like a feature that is not needed too often in practice.
217
+
218
+ **Q. Isn't this just a poor imitation of the C# language feature?**
219
+
220
+ Pretty much.
@@ -0,0 +1,71 @@
1
+ `folly/DynamicConverter.h`
2
+ --------------------------
3
+
4
+ When dynamic objects contain data of a known type, it is sometimes
5
+ useful to have its well-typed representation. A broad set of
6
+ type-conversions are contained in `DynamicConverter.h`, and
7
+ facilitate the transformation of dynamic objects into their well-typed
8
+ format.
9
+
10
+ ### Usage
11
+ ***
12
+
13
+ Simply pass a dynamic into a templated convertTo:
14
+
15
+ ```cpp
16
+ dynamic d = { { 1, 2, 3 }, { 4, 5 } }; // a vector of vector of int
17
+ auto vvi = convertTo<fbvector<fbvector<int>>>(d);
18
+ ```
19
+
20
+ ### Supported Types
21
+ ***
22
+
23
+ convertTo naturally supports conversions to
24
+
25
+ 1. arithmetic types (such as int64_t, unsigned short, bool, and double)
26
+ 2. fbstring, std::string
27
+ 3. containers and map-containers
28
+
29
+ NOTE:
30
+
31
+ convertTo<Type> will assume that Type is a container if
32
+ * it has a Type::value_type, and
33
+ * it has a Type::iterator, and
34
+ * it has a constructor that accepts two InputIterators
35
+
36
+ Additionally, convertTo<Type> will assume that Type is a map if
37
+ * it has a Type::key_type, and
38
+ * it has a Type::mapped_type, and
39
+ * value_type is a pair of const key_type and mapped_type
40
+
41
+ If Type meets the container criteria, then it will be constructed
42
+ by calling its InputIterator constructor.
43
+
44
+ ### Customization
45
+ ***
46
+
47
+ If you want to use convertTo to convert dynamics into your own custom
48
+ class, then all you have to do is provide a template specialization
49
+ of DynamicConverter with the static method convert. Make sure you put it
50
+ in namespace folly.
51
+
52
+ Example:
53
+
54
+ ``` Cpp
55
+ struct Token {
56
+ int kind_;
57
+ fbstring lexeme_;
58
+
59
+ explicit Token(int kind, const fbstring& lexeme)
60
+ : kind_(kind), lexeme_(lexeme) {}
61
+ };
62
+ namespace folly {
63
+ template <> struct DynamicConverter<Token> {
64
+ static Token convert(const dynamic& d) {
65
+ int k = convertTo<int>(d["KIND"]);
66
+ fbstring lex = convertTo<fbstring>(d["LEXEME"]);
67
+ return Token(k, lex);
68
+ }
69
+ };
70
+ }
71
+ ```
@@ -0,0 +1,63 @@
1
+ <section class="dex_document"><h1>Thread pools &amp; Executors</h1><p class="dex_introduction">Run your concurrent code in a performant way</p><h2 id="all-about-thread-pools">All about thread pools <a href="#all-about-thread-pools" class="headerLink">#</a></h2>
2
+
3
+ <h3 id="how-do-i-use-the-thread">How do I use the thread pools? <a href="#how-do-i-use-the-thread" class="headerLink">#</a></h3>
4
+
5
+ <p>Wangle provides two concrete thread pools (IOThreadPoolExecutor, CPUThreadPoolExecutor) as well as building them in as part of a complete async framework. Generally you might want to grab the global executor, and use it with a future, like this:</p>
6
+
7
+ <div class="remarkup-code-block" data-code-lang="php"><pre class="remarkup-code"><span class="no">auto</span> <span class="no">f</span> <span class="o">=</span> <span class="nf" data-symbol-name="someFutureFunction">someFutureFunction</span><span class="o">().</span><span class="nf" data-symbol-name="via">via</span><span class="o">(</span><span class="nf" data-symbol-name="getCPUExecutor">getCPUExecutor</span><span class="o">()).</span><span class="nf" data-symbol-name="then">then</span><span class="o">(...)</span></pre></div>
8
+
9
+ <p>Or maybe you need to construct a thrift/memcache client, and need an event base:</p>
10
+
11
+ <div class="remarkup-code-block" data-code-lang="php"><pre class="remarkup-code"><span class="no">auto</span> <span class="no">f</span> <span class="o">=</span> <span class="nf" data-symbol-name="getClient">getClient</span><span class="o">(</span><span class="nf" data-symbol-name="getIOExecutor">getIOExecutor</span><span class="o">()-&gt;</span><span class="na" data-symbol-name="getEventBase">getEventBase</span><span class="o">())-&gt;</span><span class="na" data-symbol-name="callSomeFunction">callSomeFunction</span><span class="o">(</span><span class="no">args</span><span class="o">...)</span>
12
+ <span class="o">.</span><span class="nf" data-symbol-name="via">via</span><span class="o">(</span><span class="nf" data-symbol-name="getCPUExecutor">getCPUExecutor</span><span class="o">())</span>
13
+ <span class="o">.</span><span class="nf" data-symbol-name="then">then</span><span class="o">([](</span><span class="no">Result</span> <span class="no">r</span><span class="o">)&#123;</span> <span class="o">....</span> <span class="k">do</span> <span class="no">something</span> <span class="no">with</span> <span class="no">result</span><span class="o">&#125;);</span></pre></div>
14
+
15
+ <h3 id="vs-c-11-s-std-launch">vs. C++11&#039;s std::launch <a href="#vs-c-11-s-std-launch" class="headerLink">#</a></h3>
16
+
17
+ <p>The current C++11 std::launch only has two modes: async or deferred. In a production system, neither is what you want: async will launch a new thread for every launch without limit, while deferred will defer the work until it is needed lazily, but then do the work <strong>in the current thread synchronously</strong> when it is needed.</p>
18
+
19
+ <p>Wangle&#039;s thread pools always launch work as soon as possible, have limits to the maximum number of tasks / threads allowed, so we will never use more threads than absolutely needed. See implementation details below about each type of executor.</p>
20
+
21
+ <h3 id="why-do-we-need-yet-anoth">Why do we need yet another set of thread pools? <a href="#why-do-we-need-yet-anoth" class="headerLink">#</a></h3>
22
+
23
+ <p>Unfortunately none of the existing thread pools had every feature needed - things based on pipes are too slow. Several older ones didn&#039;t support std::function.</p>
24
+
25
+ <h3 id="why-do-we-need-several-d">Why do we need several different types of thread pools? <a href="#why-do-we-need-several-d" class="headerLink">#</a></h3>
26
+
27
+ <p>If you want epoll support, you need an fd - event_fd is the latest notification hotness. Unfortunately, an active fd triggers all the epoll loops it is in, leading to thundering herd - so if you want a fair queue (one queue total vs. one queue per worker thread), you need to use some kind of semaphore. Unfortunately semaphores can&#039;t be put in epoll loops, so they are incompatible with IO. Fortunately, you usually want to separate the IO and CPU bound work anyway to give stronger tail latency guarantees on IO.</p>
28
+
29
+ <h3 id="iothreadpoolexecutor">IOThreadPoolExecutor <a href="#iothreadpoolexecutor" class="headerLink">#</a></h3>
30
+
31
+ <ul>
32
+ <li>Uses event_fd for notification, and waking an epoll loop.</li>
33
+ <li>There is one queue (NotificationQueue specifically) per thread/epoll.</li>
34
+ <li>If the thread is already running and not waiting on epoll, we don&#039;t make any additional syscalls to wake up the loop, just put the new task in the queue.</li>
35
+ <li>If any thread has been waiting for more than a few seconds, its stack is madvised away. Currently however tasks are scheduled round robin on the queues, so unless there is <strong>no</strong> work going on, this isn&#039;t very effective.</li>
36
+ <li>::getEventBase() will return an EventBase you can schedule IO work on directly, chosen round-robin.</li>
37
+ <li>Since there is one queue per thread, there is hardly any contention on the queues - so a simple spinlock around an std::deque is used for the tasks. There is no max queue size.</li>
38
+ <li>By default, there is one thread per core - it usually doesn&#039;t make sense to have more IO threads than this, assuming they don&#039;t block.</li>
39
+ </ul>
40
+
41
+ <h3 id="cputhreadpoolexecutor">CPUThreadPoolExecutor <a href="#cputhreadpoolexecutor" class="headerLink">#</a></h3>
42
+
43
+ <ul>
44
+ <li>A single queue backed by folly/LifoSem and folly/MPMC queue. Since there is only a single queue, contention can be quite high, since all the worker threads and all the producer threads hit the same queue. MPMC queue excels in this situation. MPMC queue dictates a max queue size.</li>
45
+ <li>LifoSem wakes up threads in Lifo order - i.e. there are only few threads as necessary running, and we always try to reuse the same few threads for better cache locality.</li>
46
+ <li>Inactive threads have their stack madvised away. This works quite well in combination with Lifosem - it almost doesn&#039;t matter if more threads than are necessary are specified at startup.</li>
47
+ <li>stop() will finish all outstanding tasks at exit</li>
48
+ <li>Supports priorities - priorities are implemented as multiple queues - each worker thread checks the highest priority queue first. Threads themselves don&#039;t have priorities set, so a series of long running low priority tasks could still hog all the threads. (at last check pthreads thread priorities didn&#039;t work very well)</li>
49
+ </ul>
50
+
51
+ <h3 id="threadpoolexecutor">ThreadPoolExecutor <a href="#threadpoolexecutor" class="headerLink">#</a></h3>
52
+
53
+ <p>Base class that contains the thread startup/shutdown/stats logic, since this is pretty disjoint from how tasks are actually run</p>
54
+
55
+ <h3 id="observers">Observers <a href="#observers" class="headerLink">#</a></h3>
56
+
57
+ <p>An observer interface is provided to listen for thread start/stop events. This is useful to create objects that should be one-per-thread, but also have them work correctly if threads are added/removed from the thread pool.</p>
58
+
59
+ <h3 id="stats">Stats <a href="#stats" class="headerLink">#</a></h3>
60
+
61
+ <p>PoolStats are provided to get task count, running time, waiting time, etc.</p>
62
+ </section>
63
+
@@ -0,0 +1,46 @@
1
+ `folly/FBString.h`
2
+ ------------------
3
+
4
+ `fbstring` is a drop-in replacement for `std::string`. The main
5
+ benefit of `fbstring` is significantly increased performance on
6
+ virtually all important primitives. This is achieved by using a
7
+ three-tiered storage strategy and by cooperating with the memory
8
+ allocator. In particular, `fbstring` is designed to detect use of
9
+ jemalloc and cooperate with it to achieve significant improvements in
10
+ speed and memory usage.
11
+
12
+ `fbstring` supports 32- and 64-bit and little- and big-endian
13
+ architectures.
14
+
15
+ ### Storage strategies
16
+ ***
17
+
18
+ * Small strings (<= 23 chars) are stored in-situ without memory
19
+ allocation.
20
+
21
+ * Medium strings (24 - 255 chars) are stored in malloc-allocated
22
+ memory and copied eagerly.
23
+
24
+ * Large strings (> 255 chars) are stored in malloc-allocated memory and
25
+ copied lazily.
26
+
27
+ ### Implementation highlights
28
+ ***
29
+
30
+ * 100% compatible with `std::string`.
31
+
32
+ * Thread-safe reference counted copy-on-write for "large"
33
+ strings (> 255 chars).
34
+
35
+ * Uses `malloc` instead of allocators.
36
+
37
+ * Jemalloc-friendly. `fbstring` automatically detects if application
38
+ uses jemalloc and if so, significantly improves allocation
39
+ strategy by using non-standard jemalloc extensions.
40
+
41
+ * `find()` is implemented using simplified Boyer-Moore
42
+ algorithm. Casual tests indicate a 30x speed improvement over
43
+ `string::find()` for successful searches and a 1.5x speed
44
+ improvement for failed searches.
45
+
46
+ * Offers conversions to and from `std::string`.
@@ -0,0 +1,200 @@
1
+ `folly/FBVector.h`
2
+ ------------------
3
+
4
+ Simply replacing `std::vector` with `folly::fbvector` (after
5
+ having included the `folly/FBVector.h` header file) will
6
+ improve the performance of your C++ code using vectors with
7
+ common coding patterns. The improvements are always non-negative,
8
+ almost always measurable, frequently significant, sometimes
9
+ dramatic, and occasionally spectacular.
10
+
11
+ ### Sample
12
+ ***
13
+ ```cpp
14
+ folly::fbvector<int> numbers({0, 1, 2, 3});
15
+ numbers.reserve(10);
16
+ for (int i = 4; i < 10; i++) {
17
+ numbers.push_back(i * 2);
18
+ }
19
+ assert(numbers[6] == 12);
20
+ ```
21
+ ### Motivation
22
+ ***
23
+
24
+ `std::vector` is the stalwart abstraction many use for
25
+ dynamically-allocated arrays in C++. It is also the best known
26
+ and most used of all containers. It may therefore seem a
27
+ surprise that `std::vector` leaves important - and sometimes
28
+ vital - efficiency opportunities on the table. This document
29
+ explains how our own drop-in abstraction `fbvector` improves key
30
+ performance aspects of `std::vector`. Refer to
31
+ folly/test/FBVectorTest.cpp for a few benchmarks.
32
+
33
+ ### Memory Handling
34
+ ***
35
+
36
+ It is well known that `std::vector` grows exponentially (at a
37
+ constant factor) in order to avoid quadratic growth performance.
38
+ The trick is choosing a good factor. Any factor greater than 1
39
+ ensures O(1) amortized append complexity towards infinity. But a
40
+ factor that's too small (say, 1.1) causes frequent vector reallocation, and
41
+ one that's too large (say, 3 or 4) forces the vector to consume much more
42
+ memory than needed.
43
+
44
+ The initial HP implementation by Stepanov used a
45
+ growth factor of 2; i.e., whenever you'd `push_back` into a vector
46
+ without there being room, it would double the current capacity. This
47
+ was not a good choice: it can be mathematically proven that a growth factor of
48
+ 2 is rigorously the <i>worst</i> possible because it never allows the vector
49
+ to reuse any of its previously-allocated memory. Despite other compilers
50
+ reducing the growth factor to 1.5, gcc has staunchly maintained its factor of
51
+ 2. This makes `std::vector` cache- unfriendly and memory manager unfriendly.
52
+
53
+ To see why that's the case, consider a large vector of capacity C.
54
+ When there's a request to grow the vector, the vector
55
+ (assuming no in-place resizing, see the appropriate section in
56
+ this document) will allocate a chunk of memory next to its current chunk,
57
+ copy its existing data to the new chunk, and then deallocate the old chunk.
58
+ So now we have a chunk of size C followed by a chunk of size k * C. Continuing
59
+ this process we'll then have a chunk of size k * k * C to the right and so on.
60
+ That leads to a series of the form (using ^^ for power):
61
+
62
+ C, C*k, C*k^^2, C*k^^3, ...
63
+
64
+ If we choose k = 2 we know that every element in the series will
65
+ be strictly larger than the sum of all previous ones because of
66
+ the remarkable equality:
67
+
68
+ 1 + 2^^1 + 2^^2 + 2^^3... + 2^^n = 2^^(n+1) - 1
69
+
70
+ This means that any new chunk requested will be larger
71
+ than all previously used chunks combined, so the vector must
72
+ crawl forward in memory; it can't move back to its deallocated chunks.
73
+ But any number smaller than 2 guarantees that you'll at some point be
74
+ able to reuse the previous chunks. For instance, choosing 1.5 as the factor
75
+ allows memory reuse after 4 reallocations; 1.45 allows memory reuse after 3
76
+ reallocations; and 1.3 allows reuse after only 2 reallocations.
77
+
78
+ Of course, the above makes a number of simplifying assumptions
79
+ about how the memory allocator works, but definitely you don't
80
+ want to choose the theoretically absolute worst growth factor.
81
+ `fbvector` uses a growth factor of 1.5. That does not impede good
82
+ performance at small sizes because of the way `fbvector`
83
+ cooperates with jemalloc (below).
84
+
85
+ ### The jemalloc Connection
86
+ ***
87
+
88
+ Virtually all modern allocators allocate memory in fixed-size
89
+ quanta that are chosen to minimize management overhead while at
90
+ the same time offering good coverage at low slack. For example, an
91
+ allocator may choose blocks of doubling size (32, 64, 128,
92
+ <t_co>, ...) up to 4096, and then blocks of size multiples of a
93
+ page up until 1MB, and then 512KB increments and so on.
94
+
95
+ As discussed above, `std::vector` also needs to (re)allocate in
96
+ quanta. The next quantum is usually defined in terms of the
97
+ current size times the infamous growth constant. Because of this
98
+ setup, `std::vector` has some slack memory at the end much like
99
+ an allocated block has some slack memory at the end.
100
+
101
+ It doesn't take a rocket surgeon to figure out that an allocator-
102
+ aware `std::vector` would be a marriage made in heaven: the
103
+ vector could directly request blocks of "perfect" size from the
104
+ allocator so there would be virtually no slack in the allocator.
105
+ Also, the entire growth strategy could be adjusted to work
106
+ perfectly with allocator's own block growth strategy. That's
107
+ exactly what `fbvector` does - it automatically detects the use
108
+ of jemalloc and adjusts its reallocation strategy accordingly.
109
+
110
+ But wait, there's more. Many memory allocators do not support in-
111
+ place reallocation, although most of them could. This comes from
112
+ the now notorious design of `realloc()` to opaquely perform
113
+ either in-place reallocation or an allocate-memcpy-deallocate
114
+ cycle. Such lack of control subsequently forced all clib-based
115
+ allocator designs to avoid in-place reallocation, and that
116
+ includes C++'s `new` and `std::allocator`. This is a major loss of
117
+ efficiency because an in-place reallocation, being very cheap,
118
+ may mean a much less aggressive growth strategy. In turn that
119
+ means less slack memory and faster reallocations.
120
+
121
+ ### Object Relocation
122
+ ***
123
+
124
+ One particularly sensitive topic about handling C++ values is
125
+ that they are all conservatively considered <i>non-
126
+ relocatable</i>. In contrast, a relocatable value would preserve
127
+ its invariant even if its bits were moved arbitrarily in memory.
128
+ For example, an `int32_t` is relocatable because moving its 4 bytes
129
+ would preserve its actual value, so the address of that value
130
+ does not "matter" to its integrity.
131
+
132
+ C++'s assumption of non-relocatable values hurts everybody for
133
+ the benefit of a few questionable designs. The issue is that
134
+ moving a C++ object "by the book" entails (a) creating a new copy
135
+ from the existing value; (b) destroying the old value. This is
136
+ quite vexing and violates common sense; consider this
137
+ hypothetical conversation between Captain Picard and an
138
+ incredulous alien:
139
+
140
+ Incredulous Alien: "So, this teleporter, how does it work?"<br>
141
+ Picard: "It beams people and arbitrary matter from one place to
142
+ another."<br> Incredulous Alien: "Hmmm... is it safe?"<br>
143
+ Picard: "Yes, but earlier models were a hassle. They'd clone the
144
+ person to another location. Then the teleporting chief would have
145
+ to shoot the original. Ask O'Brien, he was an intern during those
146
+ times. A bloody mess, that's what it was."
147
+
148
+ Only a tiny minority of objects are genuinely non-relocatable:
149
+
150
+ * Objects that use internal pointers, e.g.:
151
+ ```cpp
152
+ class Ew {
153
+ char buffer[1024];
154
+ char * pointerInsideBuffer;
155
+ public:
156
+ Ew() : pointerInsideBuffer(buffer) {}
157
+ ...
158
+ }
159
+ ```
160
+ * Objects that need to update "observers" that store pointers to them.
161
+
162
+ The first class of designs can always be redone at small or no
163
+ cost in efficiency. The second class of objects should not be
164
+ values in the first place - they should be allocated with `new`
165
+ and manipulated using (smart) pointers. It is highly unusual for
166
+ a value to have observers that alias pointers to it.
167
+
168
+ Relocatable objects are of high interest to `std::vector` because
169
+ such knowledge makes insertion into the vector and vector
170
+ reallocation considerably faster: instead of going to Picard's
171
+ copy-destroy cycle, relocatable objects can be moved around
172
+ simply by using `memcpy` or `memmove`. This optimization can
173
+ yield arbitrarily high wins in efficiency; for example, it
174
+ transforms `vector< vector<double> >` or `vector< hash_map<int,
175
+ string> >` from risky liabilities into highly workable
176
+ compositions.
177
+
178
+ In order to allow fast relocation without risk, `fbvector` uses a
179
+ trait `folly::IsRelocatable` defined in `"folly/Traits.h"`. By default,
180
+ `folly::IsRelocatable::value` conservatively yields false. If
181
+ you know that your type `Widget` is in fact relocatable, go right
182
+ after `Widget`'s definition and write this:
183
+ ```cpp
184
+ // at global namespace level
185
+ namespace folly {
186
+ struct IsRelocatable<Widget> : boost::true_type {};
187
+ }
188
+ ```
189
+ If you don't do this, `fbvector<Widget>` will fail to compile
190
+ with a `static_assert`.
191
+
192
+ ### Miscellaneous
193
+ ***
194
+
195
+ `fbvector` uses a careful implementation all around to make
196
+ sure it doesn't lose efficiency through the cracks. Some future
197
+ directions may be in improving raw memory copying (`memcpy` is
198
+ not an intrinsic in gcc and does not work terribly well for
199
+ large chunks) and in furthering the collaboration with
200
+ jemalloc. Have fun!