Fast Time conversion
I'm trying to write a very fast time-to-string and string-to-time function.
I noticed that on low CPU mobile devices this function has, even if minimal, impact on the performance of the overall canvas animation.
Milliseconds to Time string (HH:MM:SS.mss)
In this function: is there a shorter & faster way to pad the numbers?
function ms2TimeString(a,ms,s,m,h){// time(ms)
return ms=a%1e3|0,
s=a/1e3%60|0,
m=a/6e4%60|0,
h=a/36e5%24|0,
(h>0?(h<10?'0'+h:h)+':':'')+ //display hours only if necessary
(m<10?'0'+m:m)+':'+
(s<10?'0'+s:s)+'.'+
(ms<100?(ms<10?'00'+ms:'0'+ms):ms)
}
Here are some other ways to pad the numbers, but I guess executing new array
,join
,slice
or substr
has more impact on the performance than (m<10?'0'+m:m)
.
function padL2(a){//number or string
return a+='','00'.substr(0,2-a.length)+a;
}
function padL(a,b,c){//string,length=2,char=0
return (new Array(b||2).join(c||0)+a).slice(-b);
}
Time string (HH:MM:SS.mss) to Milliseconds
This function uses 2 split
and the array a
is not cached.
function timeString2ms(a,b,c){// time(HH:MM:SS.mss)
return c=0,
a=a.split('.'),
!a[1]||(c+=a[1]*1),
a=a[0].split(':'),b=a.length,
c+=(b==3?a[0]*3600+a[1]*60+a[2]*1:b==2?a[0]*60+a[1]*1:s=a[0]*1)*1e3,
c
}
UPDATE
the first most important part of this function should be the performance, the second is to have a very short code. it should be a one line function.
ms2TimeString (4 updates)
function ms2TimeString(a,k,s,m,h){
return k=a%1e3, // optimized by konijn
s=a/1e3%60|0,
m=a/6e4%60|0,
h=a/36e5%24|0,
(h?(h<10?'0'+h:h)+':':'')+ // optimized
(m<10?0:'')+m+':'+ // optimized
(s<10?0:'')+s+'.'+ // optimized
(k<100?k<10?'00':0:'')+k // optimized
}
Description:
function ms2TimeString(inputInMilliseconds,milliseconds,seconds,minutes,hours){
The milliseconds, seconds, minutes, hours are just placeholders. They are undefined variables which I set inside the function before I need them. Sometimes they are really useful so you don't have to write multiple times var
.
return
I start with return (another point why I don't want use var
) as this function is meant to be a one-line-function, even if it's a long function.
milliseconds=inputInMilliseconds%1e3,
I removed the unecessary |0
.
The strange number 1e3
means 1000
.
1e7 means that the first number is followed by 7 zeros
seconds=inputInMilliseconds/1e3%60|0,
To get only the seconds I need to divide the milliseconds per 1000 (so 1e3) then apply the modulo calculation and finally floor the number.
But there is a trick. Using the bitwise operator |
or >>
, you can floor numbers up to 32bit, so numbers up to 10 decimal digits. As this function is meant to be for a a/v player that's enough. And the cool thing is that this bitwise operator is much much faster than the Math.floor
function.
minutes=inputInMilliseconds/6e4%60|0,
Same as above, ms/60000%60|0
gives you the seconds. You will notice the ,
as I started with return
the comma allows me to apply some calculations before I output the final result.
hours=inputInMilliseconds/36e5%24|0,
to get the hours 60*60*1000
=3600000
=36e5
.....Math.floor(ms/3600000%24)
in this case we have 24 hours max.
(hours?(hours<10?'0'+hours:hours)+':':'')+
Now if you look at the first function, I made some optimizations. As hours>0
and hours
will give you the same value, I removed those 2 chars. If hours is bigger than 0, then it's true, else false, same as hours is 0 it will return 0 which is false or null. It works like a charm and it does not have to check with another value which in this case is 0.
(minutes<10?0:'')+minutes+':'+
(seconds<10?0:'')+seconds+'.'+
Also in these two lines I optimized the code a little. As we already appending to a string, I can pass 0 as number, and don't need the ''
. I also don't need to write 3 times minutes or seconds. I saved a char, but also added performance as when compiling the var s(seconds) appears 2 times vs 3.
(milliseconds<100?milliseconds<10?'00':0:'')+milliseconds
I can't apply the above number trick because 00 is always 0 if interpreted as a number.
}
Usage:
console.log(ms2timeString(89754)); // milliseconds
timeString2ms (3 updates)
function timeString2ms(a,b){// time(HH:MM:SS.mss) // optimized
return a=a.split('.'), // optimized
b=a[1]*1||0, // optimized
a=a[0].split(':'),
b+(a[2]?a[0]*3600+a[1]*60+a[2]*1:a[1]?a[0]*60+a[1]*1:a[0]*1)*1e3 // optimized
}
Description
function timeString2ms(a,b){
At the moment a
is the input as a Time String HH:MM:SS.mss
and b
is a placeholder. I don't give special names to this 2 variables as they change the role inside the function. These are just 2 temp variables. If you prefer, I can call them temp1
and temp2
.
return a=a.split('.'),
Here I split the string in 2 pieces where the first is HH:MM:SS
and the second is milliseconds
.
b=a[1]*1||0,
If the input contains milliseconds I multiply them by one to avoid the slow parseInt
function and set b
the first temp variable to the milliseconds, else I set it to 0.
a=a[0].split(':'),
Here I reuse a
which is the input string as an array where I store hours, minutes and seconds.
b+
add my milliseconds with
(a[2]?
if the array length is 3, and so I have hours, minutes and seconds
a[0]*3600+a[1]*60+a[2]*1
I multiply the hours with 3600 add them to minutes multiplied with 60 add seconds multiplied with 1 (to avoid parseInt
). In this case a
contains hours
, minutes
, seconds
, b
milliseconds
.
:a[1]?
else if no hours. In this case a
contains minutes,seconds
,b
milliseconds
a[0]*60+a[1]*1
minutes multiplied with 60 add seconds multiplied with 1 (to avoid parseInt
)
:
else. In this case a
contains only seconds
,b
milliseconds
a[0]*1
I multiplied the seconds with 1 (to avoid parseInt
)
)*1e3
and finally multiply everything with 1000
}
Usage:
console.log(timeString2ms('10:21:32.093')); // hh:mm:ss.mss
console.log(timeString2ms('21:32.093')); // mm:ss.mss
console.log(timeString2ms('21:32')); // mm:ss
console.log(timeString2ms('32.093')); // ss.mss
console.log(timeString2ms('32')); // ss
console.log(timeString2ms('32467.784')); // seconds.ms
console.log(timeString2ms(32467.784+'')); // seconds.ms
Extra
Notes
I did not use any minifier or compressor. The purpose of this code is already explained in the first lines of the post (performance and byte-saving).
Using bitwise operators and shorthand makes this function faster in the real world (if you don't think so create your own function and show me a jsperf). If you don't understand, ask.
No memoization.
Executing/initializing functions inside other functions is slower.
http://jsperf.com/multiple-functions
Accessing objects is slower than arrays or single vars.
http://jsperf.com/store-array-vs-obj
Maybe the result between every second would be more fluid, but I would see a higher performance loss when the second value changes. So in this case, I prefer some random lag that every second a fixed lag.
-
5\$\begingroup\$ Obfuscation/minification != optimization. Your code wouldn't run any slower if you made it more readable. Also, you can use jsperf.com to benchmark your code \$\endgroup\$Flambino– Flambino2014年03月25日 18:37:04 +00:00Commented Mar 25, 2014 at 18:37
-
\$\begingroup\$ i don't think you can minify more this code except the ms var.btw i'm asking for tips to improve the code. i know jsperf but if i have no new ideas on how to change the code. So there is no reason to use jsperf.if you are talking about the pad functions... then yep, i have already tested ... and no... they are slower... i just added them to just to show more ways to implement the padding maybe writing it in another way could also improve the code. \$\endgroup\$cocco– cocco2014年03月25日 18:46:10 +00:00Commented Mar 25, 2014 at 18:46
-
\$\begingroup\$ At the other side ... i wanna contradict you as shorthand and bitwiser is faster than normal readable code most of the time... only in the advanced chrome and maybe firefox the compilation is done properly. \$\endgroup\$cocco– cocco2014年03月25日 19:09:50 +00:00Commented Mar 25, 2014 at 19:09
-
\$\begingroup\$ @cocco For fast code, you write it out long, and then feed it through an minifier when you go to deploy. Minifying it yourself only makes it harder for you to find algorithmic simplifications, while a minifier program has the advantage that it can minify it well past the limit of comprehensibility, as well as optimize the code minification. \$\endgroup\$AJMansfield– AJMansfield2014年03月25日 19:18:03 +00:00Commented Mar 25, 2014 at 19:18
-
2\$\begingroup\$ @cocco You are also disregarding the golden rules of optimization, which are: Don't do it, don't do it yet, and don't do it unless test results show that a particular piece of code needs it. \$\endgroup\$AJMansfield– AJMansfield2014年03月25日 19:29:39 +00:00Commented Mar 25, 2014 at 19:29
1 Answer 1
To start,
I would like to offer an analogy; you have built a really fast car with a hood that wont open ( it's hard to maintain ), and you keep insisting that it's okay, because the car is fast. We keep telling you that you're doing it wrong.
As for "Don't do it, don't do it yet, and don't do it unless test results show that a particular piece of code needs it.", the commenter is referring to Premature Optimization is the root of all evil.
For function ms2TimeString(a,ms,s,m,h){// time(ms)
You made your function lie about parameters, to avoid a
var
statement, this is considered a code golf trick, not production code, there would be no slowdown by declaring avar
statementa
is an unfortunate parameter namems=a%1e3|0;
<- No need for|0
since you are not dividing//display hours only if necessary
is out of place, you comment on this obvious line of code, but do not comment on your1e3
numbers which do deserve a comment or the|0
trickOther than that, I think this function is readable/understandable once you rename
a
and add a few more comments. I see no opportunities to optimize it.
For function timeString2ms(a,b,c){// time(HH:MM:SS.mss)
Avoiding
var
again, not documenting well how to call thisa
,b
,c
, you're not even trying now to make this readable, those parameter names are terrible!a[1]||(c+=a[1]*1)
<- You are avoiding anif
statement, again, I would applaud you in CodeGolf, I cringe when I see this in any other settingc+=(b==3?a[0]*3600+a[1]*60+a[2]*1:b==2?a[0]*60+a[1]*1:s=a[0]*1)*1e3,
<- Wow, you just managed to write Perl in JavaScript, that's a bad thing.
All in all, if you have a performance problem with this function on canvas, then I assume you call it more than once per second, you probably call it several times per second.
You might want to try memoization, something like this:
function cachedMsToString(ms/*milliSeconds*/){
var seconds = ms - (ms % 1000);
ms = ms % 1000;
//Is this similar to our last call ?
if( cachedMsToString.seconds == seconds )
return cachedMsToString.time + (ms<100?(ms<10?'00'+ms:'0'+ms):ms);
/* It is different,
set cachedMsToString.seconds,
set cachedMsToString.time,
return the correct value
*/
}
//Initialize the cache
cachedMsToString.seconds = 0;
-
\$\begingroup\$ thx for your help, i added more info about everything. \$\endgroup\$cocco– cocco2014年03月26日 18:27:25 +00:00Commented Mar 26, 2014 at 18:27
-
\$\begingroup\$ Looks like the post is locked now.Anyway here are some performance examples that show my decisions. jsperf.com/parseint-multi jsperf.com/bw-math jsperf.com/multiple-functions jsperf.com/store-array-vs-obj jsperf.com/if-short \$\endgroup\$cocco– cocco2014年03月26日 21:51:44 +00:00Commented Mar 26, 2014 at 21:51
Explore related questions
See similar questions with these tags.