163 posts
Location
Pennsylvania
Posted 29 April 2014 - 04:17 PM
I need to check if a character 4 digits from the end of a string is a period. My question is which of the following
two three methods is lighter (faster):
function foo(str)
return str:sub(-4,-4) == '.'
end
-or-
-- it doesnt look like string.byte supports negative indices
function bar(str)
return str:byte(str:len() - 4) == 46 -- the ascii value of '.' is 46
end
-or-
function foobar(str)
return str:find('.') == str:len() - 4
end
I know micro-optimization isn't terribly important, but it certainly doesn't hurt
Edited on 29 April 2014 - 02:24 PM
350 posts
Posted 29 April 2014 - 04:42 PM
i think the first will be faster
7083 posts
Location
Tasmania (AU)
Posted 29 April 2014 - 05:00 PM
I suspect it's unlikely anyone can give you an "off-the-top-of-their-head" answer to this, given that said answer is likely to be specific to ComputerCraft's version of LuaJ. The "simple" way to figure it out would be to call the functions in a loop a few thousand times each, while keeping tabs on
os.clock() to see how long the loops take to complete.
8543 posts
Posted 29 April 2014 - 05:47 PM
Of course, if the fastest of those three methods is really that important, you've probably made some less than optimal design choices to begin with. Are you having performance problems with your script? If so, it would be better to post the whole thing and describe where you're noticing the slowness. It will be easier to provide useful optimization suggestions. This kind of "optimizing" in unnecessary places often leads to unmaintainable code. It is more valuable to make maintainable code than to have the most highly optimized code possible, especially when less optimized and more readable code would run plenty fast enough.
163 posts
Location
Pennsylvania
Posted 29 April 2014 - 06:22 PM
I suspect it's unlikely anyone can give you an "off-the-top-of-their-head" answer to this, given that said answer is likely to be specific to ComputerCraft's version of LuaJ. The "simple" way to figure it out would be to call the functions in a loop a few thousand times each, while keeping tabs on
os.clock() to see how long the loops take to complete.
Good point. I'll try that.
Of course, if the fastest of those three methods is really that important, you've probably made some less than optimal design choices to begin with. Are you having performance problems with your script? If so, it would be better to post the whole thing and describe where you're noticing the slowness. It will be easier to provide useful optimization suggestions. This kind of "optimizing" in unnecessary places often leads to unmaintainable code. It is more valuable to make maintainable code than to have the most highly optimized code possible, especially when less optimized and more readable code would run plenty fast enough.
Its really just a decision for the one line. In all three cases, it's fairly clear what the code does, so I don't think there will be any major code maintenance.issues. But you're right, it's not reasonable to do this for every function, its just that this one gets called a lot, and saving a few ms a loop could cut ~1s off execution time. I'm not having any particular slowness, this line just gets called a lot. There's really no way around it; I have to call this on every element in a huge array.
163 posts
Location
Pennsylvania
Posted 29 April 2014 - 07:36 PM
I need to check if a character 4 digits from the end of a string is a period. My question is which of the following
two three methods is lighter (faster):
function foo(str)
return str:sub(-4,-4) == '.'
end
-or-
-- it doesnt look like string.byte supports negative indices
function bar(str)
return str:byte(str:len() - 4) == 46 -- the ascii value of '.' is 46
end
-or-
function foobar(str)
return str:find('.') == str:len() - 4
end
I know micro-optimization isn't terribly important, but it certainly doesn't hurt
In case anyone is interested, here are the results:
Spoiler
These results don't take into account the overhead of the for loop, or the function calls.
+--------------------------------+------------+
| str:sub(-4, -4) == '.' | ~4.0E-7 S |
+--------------------------------+------------+
| str:byte(str:len() - 4) == 46 | ~7.65E-7 S |
+--------------------------------+------------+
| str:find('.') == str:len() - 4 | ~8.65E-7 S |
+--------------------------------+------------+
Edited on 29 April 2014 - 05:41 PM
8543 posts
Posted 29 April 2014 - 08:00 PM
Could also throw a str:match("(%.)….$") into the mix. Would return nil if it doesn't find the character there.
163 posts
Location
Pennsylvania
Posted 29 April 2014 - 08:12 PM
Could also throw a str:match("(%.)….$") into the mix. Would return nil if it doesn't find the character there.
~6.0E-7 S
It looks like str:len() is what was killing str:byte() and str:find()… I'm gonna try with #str and see if its faster somehow.
EDIT:
Using #str cut str:byte()'s time down to ~3.75E-7 S, making it the fastest.
str:find() dropped to ~4.5E-7 S when #str was used instead of str:len().
I'm gonna use str:byte(#str - 4) == 46 because it's the fastest, and also because I've never had a reason to use string.byte before.
Edited on 29 April 2014 - 06:26 PM
131 posts
Posted 29 April 2014 - 11:14 PM
Have you tried this?
function foobartoo(str)
return string.find(str, '.', -4, true)
end
47 posts
Location
Quebec, Canada
Posted 30 April 2014 - 01:54 AM
function foo(str)
return str:match("%....$") and true or false
end
I thought I would shorten Lyqyd's code snippet by using "{3}" as a quantifier instead of writing three periods in a row, but it seems Lua does not support them. :(/>
Still, instead of returning a period '.' when successful and nil when unsuccessful, it shall return true or false. :)/>
7508 posts
Location
Australia
Posted 30 April 2014 - 03:34 AM
function foo(str)
return str:match("%....$") and true or false
end
Still, instead of returning a period '.' when successful and nil when unsuccessful, it shall return true or false. :)/>
function foo(str)
return str:match("(%.)...$") ~= nil
end
8543 posts
Posted 30 April 2014 - 04:38 AM
function foo(str)
return str:match("%....$") and true or false
end
I thought I would shorten Lyqyd's code snippet by using "{3}" as a quantifier instead of writing three periods in a row, but it seems Lua does not support them. :(/>
Still, instead of returning a period '.' when successful and nil when unsuccessful, it shall return true or false. :)/>
How is ".{3}" shorter than "…" anyway?
163 posts
Location
Pennsylvania
Posted 30 April 2014 - 11:12 AM
Have you tried this?
function foobartoo(str)
return string.find(str, '.', -4, true)
end
~6.0E-7 S
Still slower than str:byte()
function foo(str)
return str:match("%....$") and true or false
end
Still, instead of returning a period '.' when successful and nil when unsuccessful, it shall return true or false. :)/>
function foo(str)
return str:match("(%.)...$") ~= nil
end
Also ~6.0E-7 S; meaning link's (which is heavier, since there's more math) is slower too.
I'm not particularly worried about how the output is; as long as I can say something like 'if foo(str) then', the function will work for me.
function foo(str)
return str:match("%....$") and true or false
end
I thought I would shorten Lyqyd's code snippet by using "{3}" as a quantifier instead of writing three periods in a row, but it seems Lua does not support them. :(/>
Still, instead of returning a period '.' when successful and nil when unsuccessful, it shall return true or false. :)/>
How is ".{3}" shorter than "…" anyway?
Link - What syntax is that? It's regex, right? Cause match isn't regex. It's not even particularly close.
Lyqyd - I think Link wanted to know which was faster. (shorten == less time) It would make sense if it were, since '….' is four operations, and '.{3}' should be two (if this were regex).
Edited on 30 April 2014 - 09:13 AM
1522 posts
Location
The Netherlands
Posted 30 April 2014 - 12:47 PM
Link - What syntax is that? It's regex, right? Cause match isn't regex. It's not even particularly close.
http://lua-users.org/wiki/StringLibraryTutorialString.match definitely uses
patterns, though patterns != regex :P/>
I think .{3} is supported though
7083 posts
Location
Tasmania (AU)
Posted 30 April 2014 - 01:26 PM
By the way, are you performing each test multiple times? I assume you're aware that MineCraft's performance tends to be a bit variable, so you're unlikely to get the same result with each pass.
163 posts
Location
Pennsylvania
Posted 30 April 2014 - 03:41 PM
By the way, are you performing each test multiple times? I assume you're aware that MineCraft's performance tends to be a bit variable, so you're unlikely to get the same result with each pass.
I perform each command 10000000 times twice and average the results.
Since os.clock's resolution is .05 S, my times are accurate +- .0000000025 (2.5E-9) S. Since we're dealing with times that are 100 to 300 times larger, my ordering can be said to be accurate. The timings themselves are not, because I don't adjust for the overhead of the function call and for loop. Also, the timings are specific to my machine, and are likely affected by the other mods I have installed. So the only useful thing I get is the order of the timings, which tells me what routine is faster.
Link - What syntax is that? It's regex, right? Cause match isn't regex. It's not even particularly close.
http://lua-users.org...LibraryTutorialString.match definitely uses
patterns, though patterns != regex :P/>
I think .{3} is supported though
I wish patterns == regex…
Edited on 30 April 2014 - 01:44 PM
171 posts
Location
Eastern USA
Posted 30 April 2014 - 10:54 PM
I would expect, and your experiments seem to support this, that whatever code has the least table indexes will be the fastest. What if you make all of the string functions local? How much faster is it then? Try this with all 4 methods - I'll bet they'll all have faster (and similar) speeds.
local sub = string.sub -- saves indexing _G to get string and string to get sub - saves (2 indexes)*(however many times you call string.sub) indexes
local byte = string.byte
local len = string.len
local find = string.find
local match = string.match
function foo(str)
return sub(-4,-4) == '.'
end
function bar(str)
return byte(len() - 4) == 46
end
function foobar(str)
return find('.') == len() - 4
end
function baz(str)
return match("%....$") and true or false
end
By the way, find('.') == len() - 4 won't work if there's another dot before the one at position str:len() - 4
Edited on 30 April 2014 - 08:54 PM
163 posts
Location
Pennsylvania
Posted 01 May 2014 - 09:08 PM
I would expect, and your experiments seem to support this, that whatever code has the least table indexes will be the fastest. What if you make all of the string functions local? How much faster is it then? Try this with all 4 methods - I'll bet they'll all have faster (and similar) speeds.
local sub = string.sub -- saves indexing _G to get string and string to get sub - saves (2 indexes)*(however many times you call string.sub) indexes
local byte = string.byte
local len = string.len
local find = string.find
local match = string.match
function foo(str)
return sub(-4,-4) == '.'
end
function bar(str)
return byte(len() - 4) == 46
end
function foobar(str)
return find('.') == len() - 4
end
function baz(str)
return match("%....$") and true or false
end
By the way, find('.') == len() - 4 won't work if there's another dot before the one at position str:len() - 4
I'll check, but it looks to me more like it scales the most with function calls, as is common in most languages.