Time.zone.at比Time.at慢10倍

Ale*_* K. 5 ruby ruby-on-rails

我有大量的数据,并且Time在1个请求期间我实例化了30_000次.我已经检查了性能,并看到从db查询数据需要0.020秒并实例化Time对象

Time.zone.at(seconds_with_fraction)
Run Code Online (Sandbox Code Playgroud)

花了0.5秒.

我已经基准之间的差异Time.zone.at,并Time.at与有:

puts Benchmark.measure { 30_000.times { Time.zone.at(1439135459.6) } }
  0.510000   0.010000   0.520000 (  0.519298)
Run Code Online (Sandbox Code Playgroud)

puts Benchmark.measure { 30_000.times { Time.at(1439135459.6) } }
  0.060000   0.000000   0.060000 (  0.068141)
Run Code Online (Sandbox Code Playgroud)

有没有办法减少需要Time类在UTC区域中实例化对象的时间?

Tod*_*obs 1

长话短说

由于您尝试解决时间戳问题的方式,您遇到了性能问题。通过以另一种方式处理时间戳转换,您将会得到很好的服务。

您的帖子没有解释您从哪里获取时区信息。因此,不清楚为什么(或如何)实际处理时区。毫无疑问,有很多方法可以避免进行时区练习,但它们超出了您发布的代码的范围。尽管如此,本文的最后一个示例展示了一种将会话时区附加到查询结果中的每一行的方法。

关于 Ruby on Rails 速度问题,您并不是真正实例化Time,而是实际上从 ActiveSupport::TimeZone::ThreadSafe 实例化ActiveSupport::TimeZone,并且 ActiveSupport 和线程安全库都必然会造成性能损失。您无法让 Ruby 更快地实例化对象,但您可以通过使用数据库来执行转换来避免给自己带来麻烦。

此外,虽然SQL-99PostgreSQL将带有时区的时间戳定义为实际的列类型,但 MySQL 和 MariaDB 都没有这样做。因此,确实不清楚您计划如何存储转换后的数据,但您似乎需要重新考虑速度和数据类型处理的方法。

无论您真正想要做什么,几乎在每种情况下,正确的答案都是将更多工作从 Rails 中推送到数据库中。下面是一些 MySQL/MariaDB 示例。

来自纪元秒的数据库时间戳

您可以简单地将数据作为纪元秒传递到 MySQL 或 MariaDB,并让数据库使用FROM_UNIXTIME函数为您执行转换,而不是尝试在 Ruby 中进行转换。例如:

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

是否将纪元存储为数字或时间戳,以及是否在存储或检索时转换数据取决于您。答案可能更多地取决于您计划如何搜索和加入数据,因此您的情况会有所不同。

转换时区

如果您只想使用单个时区并将其附加到时间戳中,那么如果您使用系统或会话时区,那么这很简单。例如:

> 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)
Run Code Online (Sandbox Code Playgroud)

如果您想做更复杂的事情,并且计划使用区域名称而不是数字偏移量,则需要首先确保填充时区表。有关详细信息,请参阅mysql_tzinfo_to_sql

无论您使用命名区域还是数字偏移量,请对您的参数调用CONVERT_TZ 。例如:

> 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)
Run Code Online (Sandbox Code Playgroud)

把它们放在一起

以下内容适用于 MariaDB,并使用 MariaDB 方便的序列引擎来避免必须填充序列表或实际输入任何值以使示例正常工作。然而,其原理(以及速度提升!)在 MySQL 上应该是类似的,但实际上您必须首先将纪元秒或时间戳数据加载到表中。

-- 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;
Run Code Online (Sandbox Code Playgroud)

这返回了一个集合,如下所示:

+---------------------------------------------------------------+
| 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)
Run Code Online (Sandbox Code Playgroud)

在我最旧、最慢的笔记本电脑上,它在 0.02 秒或更短的时间内返回了 30,000 行。服务器级硬件几乎不会注意到该查询,但当然您的情况可能会有所不同。