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

Reduce method overhead in loops by specializing to metatables #899

Open
wants to merge 7 commits into
base: v2.1
Choose a base branch
from

Conversation

TurkeyMcMac
Copy link

@TurkeyMcMac TurkeyMcMac commented Sep 19, 2022

When a method is called, the function will be constified. Given this assumption of constancy, I figure it's reasonable to assume that the metatable will also be constant. If this is often not the case, this patch could sometimes be a detriment. The patch specializes the code path to the metatable as well as the method.

The JIT specializes to the metatable for __index and __newindex when the key is a constant string, as is the case for most method calls. It's probably fine to specialize to the metatable for other constant accesses, too.

Here's a benchmark. Admittedly this represents the ideal case for the patch:

local m = setmetatable({}, {__call = function() end, __index = {a = function() end}})
local ms = {[0] = m, m}

local function bench()
	local start = os.clock()
	for i = 1, 30000000 do
		ms[i % 2]:a(1, 2)
		ms[i % 2]()
	end
	local finish = os.clock()
	return finish - start
end

do
	collectgarbage("stop")
	jit.flush()

	bench()
	collectgarbage()
	print(bench())
end

Output on my computer:

  • With patch: 0.043
  • Without patch: 0.20

@TurkeyMcMac
Copy link
Author

Here's a simpler patch that's a bit more aggressive with the constification:

diff --git a/src/lj_record.c b/src/lj_record.c
index bfd41236..1c3c62c5 100644
--- a/src/lj_record.c
+++ b/src/lj_record.c
@@ -1102,8 +1102,15 @@ int lj_record_mm_lookup(jit_State *J, RecordIndex *ix, MMS mm)
       GG_OFS(g.gcroot[GCROOT_BASEMT+itypemap(&ix->tabv)]));
     goto nocheck;
   }
-  ix->mt = mt ? mix.tab : TREF_NIL;
-  emitir(IRTG(mt ? IR_NE : IR_EQ, IRT_TAB), mix.tab, lj_ir_knull(J, IRT_TAB));
+  if (mt && !(mm == MM_newindex || mm == MM_metatable)) {
+    TRef kmt = lj_ir_ktab(J, mt);
+    emitir(IRTG(IR_EQ, IRT_TAB), mix.tab, kmt);
+    mix.tab = kmt;
+    ix->mt = kmt;
+  } else {
+    ix->mt = mt ? mix.tab : TREF_NIL;
+    emitir(IRTG(mt ? IR_NE : IR_EQ, IRT_TAB), mix.tab, lj_ir_knull(J, IRT_TAB));
+  }
 nocheck:
   if (mt) {
     GCstr *mmstr = mmname_str(J2G(J), mm);

This helps in the case of

   o:a()
   o.a(o)

But not in the case of

   o.a(o)
   o:a()
@TurkeyMcMac
Copy link
Author

One issue with this more complex patch is that it makes some loads constant and others not. The non-constant loads can suffer from a lack of CSE with the constant loads in some cases. I have mitigated this for the case where the constant loads come before the non-constant loads.

@TurkeyMcMac
Copy link
Author

Actually, I think I'll rewrite this patch to make specialization depend on the keys for loading and storing being constant strings. That way specific bytecode instructions are not required.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

1 participant