2
\$\begingroup\$

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.

\$\endgroup\$
  • 4
    \$\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 Mar 25 '14 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 Mar 25 '14 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 Mar 25 '14 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 Mar 25 '14 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 Mar 25 '14 at 19:29
7
\$\begingroup\$

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 a var statement

  • a is an unfortunate parameter name

  • ms=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 your 1e3 numbers which do deserve a comment or the |0 trick

  • Other 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 this

  • a,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 an if statement, again, I would applaud you in CodeGolf, I cringe when I see this in any other setting

  • 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, <- 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;
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.