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