godot: Lua-style Dictionary operations are slower

Godot version

4.0 e8f9cd8

System information

Windows 10 x64

Issue description

Some facts:

  • Dictionary will implicitly convert StringName to Strings when used as keys
  • Lua-style syntax uses StringNames for access

As a result, Lua-style operations are about twice+ as slow. Benchmark I used:

var d: Dictionary
var time: int

time = Time.get_ticks_usec()
for i in 1000000:
	d.test1 = 1
	d.test2 = 2
	d.test3 = 4
	d.test4 = 8
print("Lua assign: ", (Time.get_ticks_usec() - time) * 0.001, "ms")

time = Time.get_ticks_usec()
for i in 1000000:
	d["test1"] = 1
	d["test2"] = 2
	d["test3"] = 4
	d["test4"] = 8
print("Python assign: ", (Time.get_ticks_usec() - time) * 0.001, "ms")

d = {test1 = 1, test2 = 2, test3 = 4, test4 = 8}
time = Time.get_ticks_usec()
for i in 1000000:
	var a = d.test1
	var b = d.test2
	var c = d.test3
	var e = d.test4
print("Lua read: ", (Time.get_ticks_usec() - time) * 0.001, "ms")

time = Time.get_ticks_usec()
for i in 1000000:
	var a = d["test1"]
	var b = d["test2"]
	var c = d["test3"]
	var e = d["test4"]
print("Python read: ", (Time.get_ticks_usec() - time) * 0.001, "ms")

My results:

Lua assign: 1746.421ms
Python assign: 693.359ms
Lua read: 1985.207ms
Python read: 862.008ms

I think Lua-style dicts should use Strings, to avoid useless conversions.

Steps to reproduce

See above.

Minimal reproduction project

N/A

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 4
  • Comments: 26 (26 by maintainers)

Most upvoted comments

So here’s my takeaway right now. Sorry to spam you all, but I’m enjoying digging into this one. Feel free to take your time to read (or just ignore!). TL;DR at the end.

Currently, String-based accesses are 25% faster than StringName-based accesses.

Across the board, if your method for accessing a dictionary uses a String, then it’s the fastest it can be on the master build. Any access which uses StringNames, such as doing dict.this_is_a_stringname or dict[&"this_is_a_stringname"], has a performance penalty to the tune of about 1/3.

That difference in-editor is actually closer to 40-50% faster for String-based methods

For whatever reason, being in editor (I suspect just meaning TOOLS_ENABLED) significantly exacerbates the difference between String- vs StringName-indexed methods. No idea why that would be, but the big difference doesn’t appear between release and debug builds, so it has to be the editor.

StringName-based dicts could be up 10% faster than String-based ones

According to @rune-scape’s test of performance before and after the merge for unifying String and StringName:

StringName dict lua read: 3013.939ms
String dict python String read: 3321.441ms

This means that fully StringName-based dicts (with StringName-based accesses) could be significantly faster in the grand scheme of things. Right now this isn’t possible since all dictionaries are implicitly converted to being String-based only.

If sticking to String-based dictionaries, #68925 fixes lua-based access underperformance by ~25%

Lua indexing like dict.my_prop is effectively forced to use StringName. In String-based dictionaries, that requires conversion from StringName to String, which has some cost.

TL;DR (aka my conclusion): having a StringName-based dictionary is worth it. If this is done, then #68925 should probably not be merged.

If we were to have something like my_dict.allow_string_name_keys(true), then I believe we would get the fastest possible results. For performance-critical parts of the code, or data-driven game architectures, StringName based access could provide a 10% increase in performance, which is significant.

If this is something that we want to have in godot, then forcibly reducing lua-based StringName accesses to String-based accesses, like what #68925 is doing, is probably a terrible idea 😃

I’ve tun into some memory issues, but wanted to leave this little exciting bit of progress 😃

Untitled

Silly example, but it’s worth noting that StringName access is for some reason faster when the keys are long enough (int performance added for reference):

	var d: Dictionary
	var time: int
	# read performance was affected by the variable declarations
	var a: int
	var b: int
	var c: int
	var e: int

	time = Time.get_ticks_usec()
	for i in 1000000:
		d.test1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = 1
		d.test2aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = 2
		d.test3aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = 4
		d.test4aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = 8
	print("Lua assign: ", (Time.get_ticks_usec() - time) * 0.001, "ms")
	time = Time.get_ticks_usec()
	for i in 1000000:
		d["test1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"] = 1
		d["test2aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"] = 2
		d["test3aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"] = 4
		d["test4aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"] = 8
	print("Python assign: ", (Time.get_ticks_usec() - time) * 0.001, "ms")
	
	time = Time.get_ticks_usec()
	for i in 1000000:
		d[&"test1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"] = 1
		d[&"test2aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"] = 2
		d[&"test3aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"] = 4
		d[&"test4aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"] = 8
	print("Python assign StringName: ", (Time.get_ticks_usec() - time) * 0.001, "ms")

	d = {test1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = 1, test2aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = 2, test3aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = 4, test4aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = 8}
	time = Time.get_ticks_usec()
	for i in 1000000:
		a = d.test1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
		b = d.test2aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
		c = d.test3aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
		e = d.test4aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
	print("StringName dict lua read: ", (Time.get_ticks_usec() - time) * 0.001, "ms")

	time = Time.get_ticks_usec()
	for i in 1000000:
		a = d["test1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]
		b = d["test2aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]
		c = d["test3aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]
		e = d["test4aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]
	print("StringName dict python String read: ", (Time.get_ticks_usec() - time) * 0.001, "ms")

	time = Time.get_ticks_usec()
	for i in 1000000:
		a = d[&"test1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]
		b = d[&"test2aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]
		c = d[&"test3aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]
		e = d[&"test4aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]
	print("StringName dict python StringName read: ", (Time.get_ticks_usec() - time) * 0.001, "ms")
	
	
	d = {"test1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" = 1, "test2aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" = 2, "test3aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" = 4, "test4aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" = 8}
	time = Time.get_ticks_usec()
	for i in 1000000:
		a = d.test1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
		b = d.test2aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
		c = d.test3aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
		e = d.test4aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
	print("String dict lua read: ", (Time.get_ticks_usec() - time) * 0.001, "ms")

	time = Time.get_ticks_usec()
	for i in 1000000:
		a = d["test1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]
		b = d["test2aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]
		c = d["test3aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]
		e = d["test4aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]
	print("String dict python String read: ", (Time.get_ticks_usec() - time) * 0.001, "ms")

	time = Time.get_ticks_usec()
	for i in 1000000:
		a = d[&"test1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]
		b = d[&"test2aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]
		c = d[&"test3aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]
		e = d[&"test4aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]
	print("String dict python StringName read: ", (Time.get_ticks_usec() - time) * 0.001, "ms")
	d = {}
	
	time = Time.get_ticks_usec()
	for i in 1000000:
		d[0] = 0
		d[1] = 1
		d[2] = 2
		d[3] = 3
	print("python int assign: ", (Time.get_ticks_usec() - time) * 0.001, "ms")
	time = Time.get_ticks_usec()
	for i in 1000000:
		a = d[0]
		b = d[1]
		c = d[2]
		e = d[3]
	print("python int read: ", (Time.get_ticks_usec() - time) * 0.001, "ms")

gives me this on 4.2 dev2 (running from the editor):

Lua assign: 680.009ms
Python assign: 1247.879ms
Python assign StringName: 1314.539ms
StringName dict lua read: 763.562ms
StringName dict python String read: 1351.039ms
StringName dict python StringName read: 772.735ms
String dict lua read: 755.639ms
String dict python String read: 1314.273ms
String dict python StringName read: 756.75ms
python int assign: 136.481ms
python int read: 181.979ms

“Python assign StringName” being a weird outlier there.

While in the shorter-named version, Strings are of course faster as established:

Lua assign: 247.813ms
Python assign: 193.062ms
Python assign StringName: 260.788ms
StringName dict lua read: 340.491ms
StringName dict python String read: 258.217ms
StringName dict python StringName read: 354.51ms
String dict lua read: 351.861ms
String dict python String read: 248.908ms
String dict python StringName read: 338.807ms
python int assign: 138.84ms
python int read: 180.552ms

thank you for the graphs @anvilfolk! they make my autism happy 😃 but to be clear, the String conversion happens at a low level in c++, so a runtime switch wouldn’t really make sense

Should be fixed by #68747.

So I got a weird idea. Right now most of the Dictionary code looks like this: https://github.com/godotengine/godot/blob/015dc492de33a41eaeb14c0503a6be10466fe457/core/variant/dictionary.cpp#L103-L108 What if it instead was

if (p_key.get_type() == Variant::STRING_NAME && !_p->variant_map.has(p_key)) {
	const StringName *sn = VariantInternal::get_string_name(&p_key);
	return _p->variant_map[sn->operator String()];
} else {
	return _p->variant_map[p_key];
}

(note the has() check)?

Implicit Strings would become a fallback rather than being forced. Assuming that checking for some value is faster than casting to String, this could improve performance. The problem is that it needs to work 2-way, i.e. Strings should work with StringName keys. Also I wasn’t able to figure out how to allow assigning StringName keys, because Dictionary seems to use [] operator for both reading and writing.

with #68747 i got these numbers (i added a StringName benchmark):

Lua assign: 601.067ms
Python assign: 534.309ms
StringName assign: 649.116ms
Lua read: 939.54ms
Python read: 783.234ms
StringName read: 941.516ms

the only way i can think to make it faster would be to actually store StringName keys in Dictionaries instead of converting them (or convert attributes to Strings specifically for Dictionaries, but that seems hacky to me)

it doesn’t completely fix this though, because it’s still slower i would have removed the StringName to String conversion in Dictionaries, but there was 1 piece of code that failed when i tried, and i was worried there might have been more i couldn’t find

Working on this one 😃 Think I have one possible cause of the issue narrowed down.

The lua style is using a subscript that has is_attribute set to true, whereas the python style is also using the same thing, but is_attribute is false. I’m going to try to reduce the first case to the second when the base is known to be a dictionary and the attribute can’t be found in the builtin methods/properties.