EditSession#replaceBlocks + adaptWorld slower when async
Anarchick opened this issue ยท 6 comments
Server Implementation
Paper
Server Version
1.20.4
Describe the bug
Context : I'm creating a world generator doing a large amount of operation and I tried to optimized the render time. I had the idea to execute some task of a large region into smaller region with their own thread but I was surprised to get the same render times without this method. After more test I have conclude that some FAWE operation are slower in other async thread (and almost instant when you did it via command).
When you run an async task from Bukkit sheduler :
- EditSession#setBlocks(Region region, Pattern pattern) is faster
- AdaptWorld is slower
- EditSession#replaceBlocks(Region region, Mask mask, Pattern pattern) is slower
test async: false, replace: false
adapt world : 2ms
create edit session : 4ms
create region : 7ms
create mask : 15ms
create pattern : 17ms
operation : 356ms
test async: true, replace: false
adapt world : 39ms
create edit session : 39ms
create region : 40ms
create mask : 43ms
create pattern : 44ms
operation : 196ms
test async: false, replace: true
adapt world : 0ms
create edit session : 0ms
create region : 1ms
create mask : 5ms
create pattern : 5ms
operation : 549ms
test async: true, replace: true
adapt world : 40ms
create edit session : 40ms
create region : 41ms
create mask : 44ms
create pattern : 45ms
operation : 26164ms
To Reproduce
public static void test(Location loc, boolean isAsync, boolean isReplace) {
PlayerUtils.broadcastMessage(String.format("<yellow>test async: %s, replace: %s", isAsync, isReplace));
System.out.println("test async: " + isAsync + ", replace: " + isReplace);
long start = System.currentTimeMillis();
Runnable runnable = () -> {
World world = BukkitAdapter.adapt(loc.getWorld());
PlayerUtils.broadcastMessage("adapt world : " + (System.currentTimeMillis() - start) + "ms");
System.out.println("adapt world : " + (System.currentTimeMillis() - start) + "ms");
EditSession session = WorldEdit.getInstance().newEditSessionBuilder()
.world(world)
.checkMemory(false)
.fastMode(true)
.changeSetNull()
.allowedRegionsEverywhere()
.relightMode(RelightMode.NONE)
.limitUnlimited()
.build();
PlayerUtils.broadcastMessage("create edit session : " + (System.currentTimeMillis() - start) + "ms");
System.out.println("create edit session : " + (System.currentTimeMillis() - start) + "ms");
//BlockVector3 pos2 = BlockVector3.at(loc.getBlockX(), loc.getBlockY() -1, loc.getBlockZ());
//BlockVector3 pos1 = pos2.subtract(150, 150, 150);
int r = 25;
BlockVector3 pos2 = BlockVector3.at(loc.getBlockX() + r, loc.getBlockY() -1, loc.getBlockZ() + r);
BlockVector3 pos1 = pos2.subtract(r*2, r*2, r*2);
Region region = new CuboidRegion(world, pos1, pos2);
PlayerUtils.broadcastMessage("create region : " + (System.currentTimeMillis() - start) + "ms");
System.out.println("create region : " + (System.currentTimeMillis() - start) + "ms");
Mask mask = toMask(">##wool,glass", session);
PlayerUtils.broadcastMessage("create mask : " + (System.currentTimeMillis() - start) + "ms");
System.out.println("create mask : " + (System.currentTimeMillis() - start) + "ms");
Pattern pattern = toPattern("##wool", session);
PlayerUtils.broadcastMessage("create pattern : " + (System.currentTimeMillis() - start) + "ms");
System.out.println("create pattern : " + (System.currentTimeMillis() - start) + "ms");
if (isReplace) {
// This is slowest when async
session.replaceBlocks(region, mask, pattern);
} else {
// this is fastest when async
session.setBlocks(region, pattern);
}
//session.flushQueue();
session.close();
PlayerUtils.broadcastMessage("operation : " + (System.currentTimeMillis() - start) + "ms");
System.out.println("operation : " + (System.currentTimeMillis() - start) + "ms");
};
if (isAsync) {
Bukkit.getScheduler().runTaskAsynchronously(INSTANCE, runnable);
} else {
runnable.run();
}
}
@Nullable
public static Mask toMask(final String input, final EditSession session) {
if (input.isEmpty()) return null;
MaskFactory maskFactory = WorldEdit.getInstance().getMaskFactory();
//LocalSession local = new LocalSession();
ParserContext context = toContext(session);
//local.remember(session);
try {
return maskFactory.parseFromInput(input, context);
} catch (InputParseException ignored) {}
return null;
}
@Nullable
public static Pattern toPattern(final String input, final EditSession session) {
PatternFactory patternFactory = WorldEdit.getInstance().getPatternFactory();
ParserContext context = toContext(session);
try {
return patternFactory.parseFromInput(input, context);
} catch(InputParseException ignored) {}
return null;
}
public static ParserContext toContext(final EditSession session) {
ParserContext context = new ParserContext();
context.setActor(BukkitAdapter.adapt(Bukkit.getConsoleSender()));
context.setWorld(session.getWorld());
context.setSession(new LocalSession());
context.setExtent(session.getWorld());
return context;
}
Expected behaviour
test(loc, true, true)
should run as fast as //replace >##wool,glass ##wool wich is almost instant.
- Split a huge operation into some threads (limited to the amount of logical Thread availaible by the CPU) should be faster than doing this on 1 Thread
Screenshots / Videos
No response
Error log (if applicable)
No response
Fawe Debugpaste
https://athion.net/ISPaster/paste/view/76191c6340b840a789f18f4430e62daf
Fawe Version
FastAsyncWorldEdit version 2.11.1-SNAPSHOT-858;ddacb97
Checklist
- I have included a Fawe debugpaste.
- I am using the newest build from https://ci.athion.net/job/FastAsyncWorldEdit/ and the issue still persists.
Anything else?
Tell me if I'm wrong, but I have read that FAWE should not be executed in main Thread, that's why I use Bukkit.getScheduler().runTaskAsynchronously or Executors.newFixedThreadPool(threads)
Note : I have not test other operations
Intel Core I7 8750H
2x8GB Ram 3600MHz DDR4 CAS17 in Dual Channel
SSD M.2 Pcie3.0
I'm not using 100% of my CPU or Ram when I did all this tests
Are you sure the much of the extra time isn't waiting for the bukkit scheduler to run the task?
When setting blocks FAWE doesn't have to read from the world when setting so can just do the operation. When replacing, it must read from the world so relies on loading the chunks, relying on synchronous tasks to do so. If the area isn't loaded this can be slow (i.e. a player is or is not in the area).
After more test :
- Yes it was not the World Adapter but the Thread call wich cost 16-50ms
- All of the chunks are loaded around my player
I have modified the code for this ;
int r = 25;
BlockVector3 pos2 = BlockVector3.at(loc.getBlockX() + r, loc.getBlockY() -1, loc.getBlockZ() + r);
BlockVector3 pos1 = pos2.subtract(r*2, r*2, r*2);
With this small cuboid the total time is 28ms on main Thread and 809ms async. This difference is not from Thread creation.
Does FAWE //replace is call on the main Minecraft Thread ?
On a fresh started server running since ~3minutes , server and client on different pc
#test() is running from the plugin FloatingIsleGenerator , Anapi is only used for the commandBuilder and broadcast Minimessage
using the command /spark profiler start --thread *
test(loc, false, true) 37ms https://spark.lucko.me/6bQ4rN3fh2
test(loc, true, true) 684ms https://spark.lucko.me/dIu6eXoQUZ
//replace ##wool ##wool https://spark.lucko.me/twooQbachI
I think I have found the solution :
new MaskTraverser(mask).setNewExtent(session);
int modified = session.replaceBlocks(region, mask, pattern);
Using MaskTraverser is The most important part that allows to get the same execution time as //replace
I recommend adding a class description and writing it in the Wiki, I don't undestand why this is so important.
I think that this is basically wanted behaviour. FAWE shouldn't be overwriting the extent set to masks that the API uses, and there is not really a good way to assuredly set editsessions when parsing masks, etc. Given the use of parser here, it can only know about what you give it, which in this case is the world. In FAWE itself, the mask extent is replaced with the editsession in relevant commands. This is therefore not a bug and is FAWE doing what it is told