Summary
为了加快速度,请使用 Database Toolbox 或自定义 Java 代码将循环和列数据类型转换下推到 Java 层。 Matlab 到 Java 方法的调用开销可能是让您丧命的原因,并且无法使用普通 JDBC 进行块获取(一次调用中的多行)。确保您使用的 JDBC 驱动程序上的旋钮设置正确。然后优化昂贵的列数据类型(如字符串和日期)的传输。
(注意:我还没有对 Postgres 这样做过,但对其他 DBMS 这样做过,这也适用于 Postgres,因为其中大部分是关于它上面的 JDBC 和 Matlab 层的。)
Details
将循环下推到 Java 以获取块
更快地实现这一点的最直接方法是将行和列上的循环向下推送到 Java 层,并将数据块(例如一次 100 或 1000 行)返回到 Matlab 层。从 Matlab 调用 Java 方法会产生大量的每次调用开销,并且会在 M 代码中循环 JDBC 调用(请参阅MATLAB OOP 速度慢还是我做错了什么? https://stackoverflow.com/questions/1693429/is-matlab-oop-slow-or-am-i-doing-something-wrong/1745686#1745686- 完全披露:这就是我的答案)。如果您像这样从 M 代码调用 JDBC,那么您将在每一行的每一列上产生开销,而这可能是您现在执行时间的大部分。
JDBC API 本身不支持像 ODBC 那样的“块游标”,因此您需要将该循环深入到 Java 层。像 Oleg 建议的那样使用数据库工具箱是一种方法,因为他们用 Java 实现了较低级别的游标内容。 (可能正是出于这个原因。)但是,如果您不能拥有数据库工具箱依赖项,您可以编写自己的瘦 Java 层来执行此操作,并从 M 代码中调用它。 (可能通过与您的自定义 Java 代码耦合并知道如何与其交互的 Matlab 类。)使 Java 代码和 Matlab 代码共享块大小,在 Java 端缓冲整个块,使用原始数组而不是尽可能使用列缓冲区的对象数组,并让您的 M 代码批量获取结果集,将这些块缓冲在原始列数组,然后将它们连接在一起。
Matlab层的伪代码:
colBufs = repmat( {{}}, [1 nCols] );
while (cursor.hasMore())
cursor.fetchBlock();
for iCol = 1:nCols
colBufs{iCol}{end+1} = cursor.getBlock(iCol); % should come back as primitive
end
end
for iCol = 1:nCols
colResults{iCol} = cat(2, colBufs{iCol}{:});
end
旋转 JDBC DBMS 驱动程序旋钮
确保您的代码向 M 代码层公开 DBMS 特定的 JDBC 连接参数,并使用它们。阅读您的特定 DBMS 的文档并适当地修改它们。例如,Oracle 的 JDBC 驱动程序默认将默认获取缓冲区大小(其 JDBC 驱动程序中的缓冲区大小,而不是您正在构建的缓冲区大小)设置为大约 10 行,这对于典型的数据分析集大小来说太小了。 (每次缓冲区填满时,都会导致到数据库的网络往返。)简单地将其设置为 1,000 或 10,000 行就像打开出厂时设置为“关闭”的“Go Fast”开关。使用示例数据集对您的速度进行基准测试,并绘制结果图表以选择适当的设置。
优化列数据类型传输
除了为您提供块获取功能之外,编写自定义 Java 代码还提供了针对特定列类型进行优化类型转换的可能性。处理完每行和每单元的 Java 调用开销后,您的瓶颈可能会出现在日期解析以及将字符串从 Java 传回 Matlab 中。通过将 SQL 日期类型转换为 Matlab,将日期解析推入 Javadatenum
s(如Java双倍,带有列类型指示器),因为它们正在被缓冲,可能使用缓存来避免重新计算同一组中的重复日期。 (注意TimeZone
问题。考虑乔达时间。)转换任何BigDecimal
s to double
在Java方面。 cellstr 是一个很大的瓶颈 - 单个 char 列可能会淹没多个 float 列的成本。如果可以的话,将窄 CHAR 列作为二维字符而不是 cellstr 返回(通过返回一个大的 Javachar[]
然后使用reshape()
),转换为cellstr
如有必要,在 Matlab 端。 (返回一个JavaString[]
转换为cellstr
效率较低。)并且您可以通过将低基数字符列作为“符号”传回来优化它们的检索 - 在 Java 端,构建唯一字符串值的列表并将它们映射到数字代码,然后返回字符串作为数字代码的原始数组以及数字 -> 字符串的映射;在 Matlab 端将不同的字符串转换为 cellstr,然后使用索引将其扩展为完整数组。这会更快并且节省大量内存,因为写时复制优化将重复使用相同的原始字符数据来表示重复的字符串值。或者将它们转换为categorical
or ordinal
如果合适的话,用对象代替 cellstr。这个符号优化可以是big如果您使用大量字符数据并拥有大型结果集,则会获胜,因为您的字符串列以大约原始数字速度传输,这要快得多,并且它减少了 cellstr 的典型内存碎片。 (数据库工具箱现在也可能支持其中一些东西。我已经有几年没有实际使用它了。)
之后,根据您的 DBMS,您可以通过将 DBMS 支持的所有数字列类型变体包含到 Matlab 中适当的数字类型的映射,并尝试在模式中使用它们或在 SQL 中进行转换来提高速度询问。例如,甲骨文的BINARY_DOUBLE
可能比平常快一点NUMERIC
像这样完整地浏览 db/Matlab 堆栈。 YMMV。
您可以考虑通过用更便宜的数字标识符替换字符串和日期列来优化此用例的架构,可能作为分隔查找表的外键以将它们解析为原始字符串和日期。具有足够模式知识的查找可以在客户端缓存。
如果您想疯狂,您可以在 Java 级别使用多线程,让它异步预取并在单独的 Java 工作线程上解析下一个结果块,如果您有大量数据,则可能会并行化每列日期和字符串处理。当您对前一个块进行 M 代码级处理时,光标块的大小。不过,这确实增加了难度,而且理想情况下会带来较小的性能提升,因为您已经将昂贵的数据处理推到了 Java 层。把这个留到最后。并检查 JDBC 驱动程序 doco;它可能已经有效地为您做到了这一点。
各种各样的
如果您不愿意编写自定义 Java 代码,您仍然可以通过更改 Java 方法调用的语法来获得一些加速obj.method(...)
to method(obj, ...)
. E.g. getDouble(RESULTSET, n)
。这只是 Matlab OOP 的一个奇怪的怪癖。但这不会带来太大的好处,因为您仍然需要为每次调用的 Java/Matlab 数据转换付费。
另外,请考虑更改您的代码,以便您可以使用?
SQL 查询中的占位符和绑定参数,而不是将字符串作为 SQL 文本进行插值。如果您正在执行自定义 Java 层,那么定义您自己的 @connection 和 @preparedstatement M 代码类是一个不错的方法。所以它看起来像这样。
QUERYSTRING = ['SELECT * FROM ' TABLENAME ' WHERE ts BETWEEN ? AND ?'];
query = conn.prepare(QUERYSTRING);
rslt = query.exec(startTime, endTime);
这将为您提供更好的类型安全性和更可读的代码,并且还可以减少查询解析的服务器端开销。在只有几个客户端的情况下,这不会给您带来太大的加速,但它会让编码变得更容易。
定期分析和测试您的代码(在 M 代码和 Java 级别),以确保您的瓶颈位于您认为的位置,并查看是否有参数需要根据您的数据集大小进行调整,无论是在行数、列数和类型。我还喜欢在 Matlab 和 Java 层构建一些仪器和日志记录,以便您可以轻松获得性能测量(例如,让它总结解析不同列类型所花费的时间、Java 层中的时间以及Matlab 层,以及等待服务器响应的时间(由于管道可能不会太多,但你永远不知道))。如果您的 DBMS 公开了其内部工具,也许也可以将其拉入,这样您就可以看到服务器端时间都花在哪里了。