Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement vector and node conversion in Lua #12609

Merged
merged 7 commits into from
Oct 18, 2022

Conversation

TurkeyMcMac
Copy link
Contributor

@TurkeyMcMac TurkeyMcMac commented Jul 29, 2022

As @paradust7 found and reported in #12438, constructing Lua strings and tables from C++ is slow. Looking at the code of PUC Lua and LuaJIT, this slowness affects calls such as lua_getfield, which have to construct/hash strings every time they are called. On the other hand, LuaJIT can optimize regular field accesses by storing strings and their hashes inline etc. This PR reimplements the functions for pushing and reading nodes and vectors using Lua. Unlike #12438, it does not touch specific API functions. Therefore, it will have wider reaching effects, but will probably not give as much improvement for get_node in particular. I have found that it indeed provides significant improvement.

To do

This PR is Ready for Review.

How to test

Run the unit tests, and maybe some other mods.

Benchmarks

First apply this patch for some extra benchmarking commands:

diff --git a/games/devtest/mods/experimental/commands.lua b/games/devtest/mods/experimental/commands.lua
index e42ae954d..1b67add1c 100644
--- a/games/devtest/mods/experimental/commands.lua
+++ b/games/devtest/mods/experimental/commands.lua
@@ -86,6 +86,60 @@ minetest.register_chatcommand("bench_bulk_set_node", {
 	end,
 })
 
+minetest.register_chatcommand("bench_get_node", {
+	params = "",
+	description = "Benchmark: get node",
+	func = function(name, param)
+		local player = minetest.get_player_by_name(name)
+		if not player then
+			return false, "No player."
+		end
+		local pos = player:get_pos()
+
+		minetest.chat_send_player(name, "Benchmarking minetest.get_node. Warming up ...");
+
+		for i = 1, 1000000 do
+			minetest.get_node(pos)
+			pos = pos:add(1)
+		end
+
+		minetest.chat_send_player(name, "Warming up finished, now benchmarking ...");
+
+		local middle_time = minetest.get_us_time()
+		pos = player:get_pos()
+		for i = 1, 1000000 do
+			minetest.get_node(pos)
+			pos = pos:add(1)
+		end
+		local end_time = minetest.get_us_time()
+		return true, ("Finished. Time: %.2fms"):format((end_time - middle_time) / 1000)
+	end,
+})
+
+minetest.register_chatcommand("bench_pos", {
+	params = "",
+	description = "Benchmark: get node",
+	func = function(name, param)
+		local player = minetest.get_player_by_name(name)
+		if not player then
+			return false, "No player."
+		end
+		local pos1 = player:get_pos():round()
+		local pos2 = pos1:add(110)
+
+		minetest.chat_send_player(name, "Benchmarking position creation. Warming up ...");
+
+		minetest.find_nodes_in_area(pos1, pos2, "air")
+
+		minetest.chat_send_player(name, "Warming up finished, now benchmarking ...");
+
+		local middle_time = minetest.get_us_time()
+		minetest.find_nodes_in_area(pos1, pos2, "air")
+		local end_time = minetest.get_us_time()
+		return true, ("Finished. Time: %.2fms"):format((end_time - middle_time) / 1000)
+	end,
+})
+
 local function advance_pos(pos, start_pos, advance_z)
 	if advance_z then
 		pos.z = pos.z + 2

Then run these benchmarks:

  1. /bench_bulk_set_node: This benchmarks the reading of nodes and vectors. The set_node loop tests both in equal proportion, while bulk_set_node mainly just benchmarks reading vectors. I have noticed a 10-15% decrease in runtime of the set_node loop, and a 5-10% decrease in runtime of bulk_set_node.
  2. /bench_get_node: This benchmarks the reading of vectors and the pushing of nodes. I have noticed a 10-40% decrease in the runtime of this benchmark.
  3. /bench_pos: This benchmarks the pushing of vectors. I have noticed a 5-10% decrease in its runtime.

@Desour
Copy link
Member

Desour commented Jul 29, 2022

Some suggestions:

  • The push and read functions could be called in lua to reduce the number of C++ to Lua calls (which are expensive, AFAIK).
    For example:
    objref_methods.set_pos = function(v) original_set_pos(read_vector(v)) end
    This would, however, require to wrap all those functions in builtin. (Or just do this for commonly used functions.)
  • Instead of content_id, param1, param2, you could pack the node into one 32 bit number: content_id + 65536 * (param1 + 256 * param2)).
  • Same for node positions: One could use hash_node_position after round.

@sfan5 sfan5 added the Concept approved Approved by a core dev: PRs welcomed! label Jul 30, 2022
@TurkeyMcMac
Copy link
Contributor Author

@Desour

@sfan5
Copy link
Member

sfan5 commented Aug 4, 2022

  • Instead of content_id, param1, param2, you could pack the node into one 32 bit number: content_id + 65536 * (param1 + 256 * param2)).

  • Same for node positions: One could use hash_node_position after round.

I don't see the point here. You're trading two lua_pushinteger calls for having to do several bit operations that LuaJIT probably doesn't optimize as well and RAM usage is the same if not temporarily higher.

@Desour
Copy link
Member

Desour commented Aug 4, 2022

I thought using the Lua stack less could maybe result in a performance benefit. But those suggestions weren't based on any real knowledge. They were meant as something that one could try out. I should've made that more clear, sorry.

TurkeyMcMac added a commit to TurkeyMcMac/minetest that referenced this pull request Aug 21, 2022
This is to let minetest#12609 work on the client.
@sfan5 sfan5 added the Rebase needed The PR needs to be rebased by its author. label Sep 18, 2022
@Zughy
Copy link
Member

Zughy commented Sep 18, 2022

@TurkeyMcMac please rebase

@sfan5 sfan5 added the Waiting (on dependency) Waiting on another PR or external circumstances (not for rebases/changes requested) label Sep 18, 2022
TurkeyMcMac added a commit to TurkeyMcMac/minetest that referenced this pull request Sep 18, 2022
This is to let minetest#12609 work on the client.
@sfan5 sfan5 removed the Rebase needed The PR needs to be rebased by its author. label Sep 18, 2022
@sfan5 sfan5 removed the Waiting (on dependency) Waiting on another PR or external circumstances (not for rebases/changes requested) label Sep 18, 2022
@sfan5 sfan5 self-requested a review September 18, 2022 19:18
Copy link
Member

@Desour Desour left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks fine otherwise.

Haven't tested.

builtin/client/init.lua Outdated Show resolved Hide resolved
builtin/common/vector.lua Show resolved Hide resolved
src/script/common/c_content.cpp Show resolved Hide resolved
builtin/common/vector.lua Show resolved Hide resolved
src/script/common/c_content.cpp Show resolved Hide resolved
@TurkeyMcMac TurkeyMcMac requested a review from sfan5 October 7, 2022 16:23
@sfan5
Copy link
Member

sfan5 commented Oct 8, 2022

The only thing I'm wondering about is whether we should have a short snippet in C++ that checks that READ_NODE, PUSH_NODE, READ_VECTOR, PUSH_VECTOR are actually set on startup.
Plus I wanted to test this.
And lastly put a comment in a suitable place detailing why we're doing the extra work of having table creation done by lua code.

@sfan5 sfan5 added the Rebase needed The PR needs to be rebased by its author. label Oct 18, 2022
Copy link
Member

@sfan5 sfan5 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works, measured a 30% improvement to bench_get_node

@sfan5 sfan5 removed the Rebase needed The PR needs to be rebased by its author. label Oct 18, 2022
@TurkeyMcMac TurkeyMcMac merged commit b38ffde into minetest:master Oct 18, 2022
@sfan5
Copy link
Member

sfan5 commented Dec 28, 2022

This makes error messages quite less friendly:

minetest/bin/../builtin/common/item_s.lua:229: bad argument #1 to '__index' (string expected, got nil)

happens when you pass a string, empty table or other stuff to a function expecting a node

appgurueu pushed a commit to appgurueu/minetest that referenced this pull request May 2, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants