|
@@ -1,18 +1,21 @@
|
|
|
-This file tries to explain the changes done in FerriteCore, and how much memory they save.
|
|
|
-The saved memory refers to a ForgeCraft 1 instance around 19th December 2020.
|
|
|
+This file tries to explain the changes done in FerriteCore, and how much memory they save. The saved memory for the
|
|
|
+first 4 points refers to a ForgeCraft 1 instance around 19th December 2020, after that to version 1.2.0 of the (smaller)
|
|
|
+1.16.4 Direwolf20 pack. This is mostly because FC 1 uses [ServerPackLocator](https://github.com/cpw/serverpacklocator/),
|
|
|
+which makes reproducible tests near-impossible (and means that I can't test when the server is down :smile:)
|
|
|
|
|
|
-### Optionals in `PropertyValueCondition`
|
|
|
-This change is irrelevant with the last point, it is only included in this list for
|
|
|
-completeness.
|
|
|
+### 1. Optionals in `PropertyValueCondition`
|
|
|
+
|
|
|
+This change is made obsolete by the 4th point, it is only included in this list for completeness.
|
|
|
|
|
|
The vanilla implementation contains code along these lines:
|
|
|
+
|
|
|
```java
|
|
|
-Optional<T> opt = newlyCreatedOptional();
|
|
|
-if (!opt.isPresent()) {
|
|
|
- // Something
|
|
|
-} else {
|
|
|
- return () -> doThing(opt.get());
|
|
|
-}
|
|
|
+Optional<T> opt=newlyCreatedOptional();
|
|
|
+ if(!opt.isPresent()){
|
|
|
+ // Something
|
|
|
+ }else{
|
|
|
+ return()->doThing(opt.get());
|
|
|
+ }
|
|
|
```
|
|
|
|
|
|
The created lambda is kept around for a long time, and there are a few million of them. In
|
|
@@ -28,7 +31,7 @@ Saved memory: 100 MB
|
|
|
CPU impact: zero or negative (one less pointer to follow)
|
|
|
Side: client
|
|
|
|
|
|
-### BlockState neighbors
|
|
|
+### 2. BlockState neighbors
|
|
|
To implement `StateHolder#with` (mostly seen as `BlockState#with`) a state needs to be
|
|
|
able to quickly find its "neighbor states". In vanilla this is implemented using a
|
|
|
`Table<Property<?>, Comparable<?>, S>` for each state. In total these tables use about 600
|
|
@@ -44,7 +47,7 @@ CPU impact: hard to prove, but most likely near zero
|
|
|
Side: both
|
|
|
Mixin subpackage: `fastmap`
|
|
|
|
|
|
-### BlockState property storage
|
|
|
+### 3. BlockState property storage
|
|
|
Each blockstate stores its properties as an `ImmutableMap<Property<?>, Comparable<?>>`,
|
|
|
which takes around 170 MB in total. Most operations do not actually require this map, they
|
|
|
can be implemented with similar speed using the `FastMap` from the previous point. There
|
|
@@ -61,7 +64,7 @@ CPU impact: unclear (see second paragraph)
|
|
|
Side: both
|
|
|
Mixin subpackage: `nopropertymap`
|
|
|
|
|
|
-### Multipart model predicate caching
|
|
|
+### 4. Multipart model predicate caching
|
|
|
Each multipart model stores a number of predicates to determine which parts to show under
|
|
|
what conditions. These predicates take up 300-400 MB. However in many cases these
|
|
|
predicates are checking the same thing, they are just newly created every time. For
|
|
@@ -71,14 +74,28 @@ list of input predicates sorted by hash value.
|
|
|
One detail that makes this even more effective is that a block can never have two
|
|
|
properties that are equal according to `equals`, while the common property implementations
|
|
|
include `equals`. Additionally `StateHolder#get` also considers `equals` (as opposed to
|
|
|
-reference equality), so using the same lambda for equivalent (but non
|
|
|
-reference-equivalent) properties and values is actually possible. This is particularly
|
|
|
-useful as one of the most common usages of multipart models is pipes, where the states are
|
|
|
-nearly always boolean properties named `north` etc. As a result the number of predicates
|
|
|
-is reduced from between 10s of thousands and millions to a few ten or hundred instances.
|
|
|
+reference equality), so using the same lambda for equivalent (but non reference-equivalent) properties and values is
|
|
|
+actually possible. This is particularly useful as one of the most common usages of multipart models is pipes, where the
|
|
|
+states are nearly always boolean properties named `north` etc. As a result the number of predicates is reduced from
|
|
|
+between 10s of thousands and millions to a few ten or hundred instances.
|
|
|
|
|
|
-Saved memory: 300-400 MB (relative to the state after the first change, so 100 MB more
|
|
|
-compared to a "clean" instance)
|
|
|
+Saved memory: 300-400 MB (relative to the state after the first change, so 100 MB more compared to a "clean" instance)
|
|
|
CPU impact: Some impact in model loading (but less allocations), zero while playing
|
|
|
Side: client
|
|
|
-Mixin subpackage: `predicates`
|
|
|
+Mixin subpackage: `predicates`
|
|
|
+
|
|
|
+### 5. String instance reduction in `ModelResourceLocation`
|
|
|
+
|
|
|
+The `ModelResourceLocation` constructor accepting a `ResourceLocation` and a `BlockState`
|
|
|
+(the main one used in practice) is implemented by first converting the RL to a string and then splitting it again. This
|
|
|
+is not only a waste of CPU time, it also means that new
|
|
|
+`String` instances are created for the path and namespace of the MRL.
|
|
|
+Another optimization is to deduplicate the `variant` string of the MRL, i.e. to use the same `String` instance for all
|
|
|
+MRLs with a given `variant`.
|
|
|
+
|
|
|
+Saved memory: about 300 MB (DW20 pack version 1.2.0)
|
|
|
+CPU impact: Zero or negative for the first part, slight (<1s) during loading for the second part
|
|
|
+Side: client
|
|
|
+Mixin subpackage: `mrl`
|
|
|
+Note: The CPU impact of the current Mixin implementation is positive for both parts, because a negative impact for the
|
|
|
+first part would require changing what constructor the constructor in question redirects to.
|