SparklyPower's Paper fork, making large servers snappier with high-performance optimizations and improvements! Focused on performance improvements for Survival servers with high player counts.
Our fork has handmade patches to add and optimize some of the things that we have in our server, with some cherry-picked patches from other forks.
SparklyPaper is a fork tailor-made for our Survival server, SparklyPower. It doesn't strive to be a public stable Paper fork like Purpur, Pufferfish, and other similar forks.
We do provide SparklyPaper builds if you want to use it in your server, but keep in mind that we won't provide support for it! If your server has issues while using SparklyPaper, or if it explodes, we won't help you!
This also means that unless a PR fixes an issue that we are having in SparklyPower, we (probably) won't accept it!
If you have the know how, we recommend making your own Paper fork and copying the SparklyPaper's patches that you need for your server.
This does not include all patches included in SparklyPaper, only the patches handmade for SparklyPaper! To see all patches, check out the "patches" directory.
SparklyPaper's config file is sparklypaper.yml
, the file is, by default, placed on the root of your server.
distanceToSqr
call in ServerEntity#sendChanges
if the delta movement hasn't changed
distanceToSqr
call is a bit expensive, so avoiding it is pretty nice, around ~15% calls are skipped with this check. Currently, we only check if both Vec3 objects have the same identity, that means, if they are literally the same object. (that works because Minecraft's code reuses the Vec3 object when caching the current delta movement)MapItem#update()
if the map does not have the default CraftMapRenderer
present
CraftMapRenderer
can avoid these hefty updates, without requiring the map to be locked, which some old map plugins may not do.mapView.isLocked = true
to get the same performance benefits in vanilla Paper!imageToBytes
in multiple threads
getDirty
call. Because the map was only retrieved once, we don't actually need to create a copy of the map just to iterate it, we can just access it directly and clear it manually after use.ItemFrame#getItem()
callsgetItem()
call is a bit expensive, especially because this is only really used if the item in the item frame is a mapcachedMapId
is not null, if it is, then we get the item in the item frame, if not, then we ignore the getItem()
call.EntityScheduler
's executeTick
EntityScheduler
's executeTick
of each entity. This is a bit expensive, due to ArrayDeque
's size()
call because it ain't a simple "get the current queue size" function, due to the thread checks, and because it needs to iterate all server entities to tick them.tickCount
, because tickCount
is relative and not bound to the server's current tick, so it doesn't matter if we don't increase it.executeTick
loop in tickChildren
CPU % usage drops from 7.60% to 0.00% (!!!) in a server with ~15k entities! Sweet!tickChildren
function CPU usage % between vanilla Paper and SparklyPaper, removing all other functions from the profiler result: 7.70% vs 0.02% (wow, such improvement, low mspt)ArrayDeque#size()
is slow! It is mostly that because the executeTick
function is called each tick for each entity, it would be better for us to avoid as many useless calls as possible.getChunkKey(...)
call is a bit expensive, using 0.24% of CPU time with 19k chunks loaded.canSee(...)
checks
canSee(...)
checks is in a hot path (ChunkMap#updatePlayers()
), invoked by each entity for each player on the server if they are in tracking range, so optimizing it is pretty nice.HashMap
to fastutil's Object2ObjectOpenHashMap
, because fastutil's containsKey
throughput is better.isEmpty()
check before attempting to check if the map contains something. This seems stupid, but it does seem that it improves the performance a bit, and it makes sense, containsKey(...)
does not attempt to check the map size before attempting to check if the map contains the key.MC-117075
: TE Unload Lag Spike
blockEntityTickers
list with a custom list based on fastutil's ObjectArrayList
with a small yet huge change for us: A method that allows us to remove a list of indexes from the list.removeAll
with a list of entries to be removed (which is what Paper currently does), because we don't need to calculate the identity of each block entity to be removed, and we can jump directly to where the search should begin, giving a performance boost for small removals (because we don't need to loop thru the entire list to find what element should be removed) and a performance boost for big removals (no need to calculate the identity of each block entity).tickBlockEntities
shouldTickBlocksAt
result, because the shouldTickBlocksAt
is expensive because it requires pulling chunk holder info from an map for each block entity (even if the block entities are on the same chunk!) every single time. So, if the last chunk position is the same as our cached value, we use the last cached shouldTickBlocksAt
result!ejectItems
, especially due to the hopper checking if the target container isFullContainer
./mspt
While we could cherry-pick everything from other forks, only patches that I can see and think "yeah, I can see how this would improve performance" or patches that target specific performance/feature pain points in our server are cherry-picked! In fact, some patches that are used in other forks may be actually borked...
These optimizations and features were originally in SparklyPaper, but now they are in Paper, yay! Thanks Paper team :3
LootContext
for criterions (Merged in Paper #9969)
ItemStack#isSimilar(...)
(Merged in Paper #9979)
getDurability()
ain't free, when you call getDurability()
, it calls getItemMeta()
, which then allocates a new ItemMeta
or clones the current item's item meta.ItemMeta
objects if we are comparing two items, or one of the two items, that don't have any durability, and this impact can be noticed if one of your plugins has a canHoldItem
function that checks if a inventory can hold an item.ItemStack
itself, this was changed in Minecraft 1.13.getDurability()
was using 0.08ms each tick according to spark... yeah, sadly it ain't a super big crazy optimization, the performance impact would be bigger if you have more plugins using isSimilar(...)
tho)isNearWater
check is costly, especially if you have a lot of farm lands. If the block is already moistured, we can change the tick rate of it to avoid these expensive isNearWater
checks.We attempt to upstream everything that we know helps performance and makes the server go zoom, and not stuff that we only hope that it improves performance. I'm still learning after all, so some of my patches may be worthless and not good enough. :)
If you are curious about SparklyPaper's performance, check out our results page!
Because this is a fork made for SparklyPower, we won't give support for any issues that may happen in your server when using SparklyPaper. We know that SparklyPaper may break some plugins, but unless we use these plugins on SparklyPower, we won't go out of our way to fix it!
If you only care about some of the patches included in SparklyPaper, it is better for you to create your own fork and cherry-pick the patches, this way you have full control of what patches you want to use in your server, and even create your own changes!
There are two kinds of builds: One with the Parallel World Ticking feature, and another without it. If you don't want to risk using a very experimental feature that may lead to server crashes and corruption, or if you aren't a developer that can't fix plugin issues related to the feature, then use the version without Parallel World Ticking! (We do run Parallel World Ticking in production @ SparklyPower tho, we live on the edge :3)
It is recommended to use a Mojang mapped (mojmap) version unless if you really have a reason (example: plugins that break on a mojmap JAR) to use a Spigot mapped (reobf) version. Paper, since 1.20.5, provides a mojmapped server JAR and remaps any class/field/Reflection access made by non-mojmap aware plugins, so things (hopefully!) shouldn't break.
Click on a workflow run, scroll down to the Artifacts, and download!