Time.zone.at is 10 times slower than Time.at

I have a huge amount of data, and I have a Time instance 30_000 times in 1 request. I checked the performance and saw that data requests from db take 0.020 seconds and create a time object with

 Time.zone.at(seconds_with_fraction) 

took 0.5 seconds.

I compared the difference between Time.zone.at and Time.at and got:

 puts Benchmark.measure { 30_000.times { Time.zone.at(1439135459.6) } } 0.510000 0.010000 0.520000 ( 0.519298) 

and

 puts Benchmark.measure { 30_000.times { Time.at(1439135459.6) } } 0.060000 0.000000 0.060000 ( 0.068141) 

Is there a way to reduce the time it takes for the class to create an object over time in the UTC zone?

+5
source share
2 answers

TL DR

You have a performance issue due to the way you are trying to solve a timestamp issue. You will be well served by handling your time conversions differently.

Your message does not explain where you get the time zone information from. As a result, it is unclear why (or how) you are actually processing time zones. Sure, there are many ways to avoid time zone gymnastics, but they are not within the scope of your published code. However, the last example in this post shows one way to add the session time zone to each row in the query results.

As for your problem with Ruby on Rails, you are not actually creating Time , you are actually creating an instance of ActiveSupport :: TimeZone from ActiveSupport :: TimeZone :: ThreadSafe, and the ActiveSupport and thread safety libraries are required to accurately evaluate performance. You cannot force Ruby objects to create objects faster, but you can avoid headaches by using your database to perform transformations.

In addition, while SQL-99 and PostgreSQL define timestamps with time zones as the actual column types, neither MySQL nor MariaDB. Thus, it is actually unclear how you plan to store this converted data, but it seems you need to reconsider your approach for both speed and data processing.

No matter what you are really trying to do, in almost every case the right answer is to get more work out of Rails and into the database. The following are examples of MySQL / MariaDB.

Database timestamps from milestone seconds

You can simply transfer the data in the form of epoch-seconds to MySQL or MariaDB and convert the database using the FROM_UNIXTIME function, rather than trying to convert to Ruby. For instance:

 > SELECT FROM_UNIXTIME(1439135459.6); +-----------------------------+ | FROM_UNIXTIME(1439135459.6) | +-----------------------------+ | 2015-08-09 10:50:59.6 | +-----------------------------+ 1 row in set (0.00 sec) 

It depends on whether epoch-seconds should be stored as numbers or as a timestamp, and therefore whether data should be converted to storage or retrieval. The answer will most likely depend on how you plan to search and join your data, so your mileage will vary.

Time Zone Conversion

If you just want to use one time zone and add it to your timestamp, this is trivial if you use system or session time zones. For instance:

 > SELECT CONCAT(FROM_UNIXTIME(1439135459.6), " ", @@session.time_zone); +---------------------------------------------------------------+ | CONCAT(FROM_UNIXTIME(1439135459.6), " ", @@session.time_zone) | +---------------------------------------------------------------+ | 2015-08-09 10:50:59.6 -05:00 | +---------------------------------------------------------------+ 1 row in set (0.00 sec) 

If you want to do more complex things, you first need to make sure your time zone tables are full if you plan on using zone names rather than numeric offsets. See mysql_tzinfo_to_sql for more details.

If you use named zones or numeric offsets, call CONVERT_TZ in your arguments. For instance:

 > SELECT CONVERT_TZ('2004-01-01 12:00:00','+00:00','+10:00'); +-----------------------------------------------------+ | CONVERT_TZ('2004-01-01 12:00:00','+00:00','+10:00') | +-----------------------------------------------------+ | 2004-01-01 22:00:00 | +-----------------------------------------------------+ 1 row in set (0.02 sec) 

Introducing everything together

The following works on MariaDB and MariaDB uses a convenient sequence mechanism to avoid having to fill out the sequence table or actually enter any values ​​to make an example work. However, the principle (and speedup!) Should be similar to MySQL, but you really have to take on the task of first loading the data from the second era or timestamps into a table.

 -- Set a session time zone to concatenate with query results. SET time_zone = '-5:00'; -- Load sequence engine on MariaDB. -- Skip on MySQL, which does not currently have a sequence engine. INSTALL SONAME "ha_sequence"; -- Select from the sequence on MariaDB. -- Change query to SELECT...FROM a table on MySQL. SELECT CONCAT( FROM_UNIXTIME(seq), @@session.time_zone ) FROM seq_1439135459_to_1439165458; 

This returns the set as follows:

 +---------------------------------------------------------------+ | CONCAT( FROM_UNIXTIME(seq), @@session.time_zone ) | +---------------------------------------------------------------+ | 2015-08-09 10:50:59-05:00 | | 2015-08-09 10:51:00-05:00 | | 2015-08-09 10:51:01-05:00 | | ... | | 2015-08-09 19:10:57-05:00 | | 2015-08-09 19:10:58-05:00 | +---------------------------------------------------------------+ 30000 rows in set (0.02 sec) 

On my old, slowest laptop, it returned 30,000 rows in 0.02 seconds or less. Server-level hardware should not even notice the request, but, of course, your mileage may change.

+1
source

This is not just a time zone change that takes so long. If you just use Time.now , you will not see the same slowdown:

 irb(main):022:0> puts Benchmark.measure { 30_000.times { Time.now } } 0.030000 0.000000 0.030000 ( 0.028038) => nil irb(main):023:0> puts Benchmark.measure { 30_000.times { Time.now.utc } } 0.040000 0.010000 0.050000 ( 0.076438) => nil 

The best way to solve your problem is to define your Time different way than to indicate the number of seconds since unix. However, if you have no way, then a faster solution would be to use a DateTime as follows:

 irb(main):038:0* puts Benchmark.measure { 30_000.times { DateTime.strptime("1439135459.6",'%s') } } 0.170000 0.030000 0.200000 ( 0.217340) => nil 
+3
source

All Articles