与直接调用函数相比,Thrift 的速度太慢了

3
我要翻译的内容如下:

我从“http://thrift-tutorial.readthedocs.org/en/latest/usage-example.html”中尝试了一个例子。这个例子只是计算两个数字的乘积。服务器使用Java,客户端使用Python。

如果我通过Thrift获取乘积3000次,经过的时间大约是4.8秒。 如果我在Python中创建一个简单的函数(multiply)并直接调用它3000次,花费的时间大约是0.007秒(比前者快686倍)。

那么我该如何提高性能?我想构建一个应用程序,并将其分成几个子应用程序。它们可以使用多种语言实现,并通过Thrift相互通信,但是像这样性能较差的情况下,我是否应该考虑将它们合并为一个应用程序?

App-A (Java)                   App-B (Python)
     |                                 |
     |------------ App-C (C++) --------|

或者

App-A+C (Java)                   App-B+C (Python)
(implement C in Java)            (implement C in Python)
2个回答

3
您可以设定两个关键的优化目标:
1. 在等待之前发送所有已有数据。 2. 如果唯一要做的事情是将计算结果直接返回,则不要通过通道发送计算结果。
您在问题中描述的是“啰嗦协议”的极端情况。网络具有延迟,如果在开始下一个计算之前等待每个结果,则大部分时间都花在等待网络传输而不是实际计算上。通过在接收第一个结果之前发送另一个计算,您可以显著提高吞吐量。
因此,最简单的方法是允许重叠请求。第二组值的积不取决于第一个结果,因此不必等待第一个结果到达。
当您处理本地IPC时,这并没有太大帮助。通信成本不是延迟,而是消息处理和线程同步,这取决于请求的数量,但不是顺序。
更大的变化及回报是使每个请求表示复杂的算法。例如,与其对两个数字进行远程调用相乘,不如尝试对整个过滤操作进行远程调用,其中参数是整个数据向量或矩阵,服务器将执行FFT、多个、反向FFT、缩放,然后传回结果。这既满足了原始目标:所有可用数据都一起发送,而不是单独发送,从而减少等待时间。同时也减少了总网络流量,因为中间结果不需要交换。
最后一种选择是将三种语言的代码链接到单个进程中,以便数据访问和函数调用是直接的。许多语言允许构建导出纯“C”函数和数据的对象。
此外,虚拟机例如.NET运行可以从不同源语言的编译生成的中间语言。使用.NET您有C#(类似于Java)、C++/CLI(支持完整的C++,以及用于处理.NET数据的扩展)和IronPython,这些都涵盖了您的问题图。还有F#、JavaScript、Ruby变体等等。Java虚拟机应该是特定于语言的,但人们已经编写了Clojure和其他编译成字节码的语言。
虚拟机技术的优点是它使一些跨语言优化成为可能(.NET JIT执行跨模块内联)。缺点是您的性能由JIT优化所决定,通常是最低公共分母。C++/CLI实际上非常适合弥合这种差距,因为它支持完全优化的本地代码(包括SIMD)、.NET中间语言(MSIL)和用于它们之间通信的最低开销层(C++“It Just Works”Interop)。
但是,您可以通过使用JNI将完全优化的C++代码与SIMD进行接口来在Java VM上实现大致相同的事情。

我的示例只是虚拟的。 好的,假设我有一个服务器,并且成千上万的客户端同时发送请求,我的服务器通过使用Thrift与“某个东西”通信来为每个客户端的每个请求提供服务,“某个东西”通过Thrift执行“实际工作”。问题在于,无论“实际工作”有多复杂,我的服务器直接调用函数比Thrift快约700倍。我曾经认为Thrift能够帮助一个应用程序直接调用其他应用程序的函数(而不是通过网络),就像Python调用C ++扩展一样。 - William
@William:如果跨进程调用确实对你来说太昂贵了,那么可以考虑将多种语言的代码合并为一个程序。例如,.NET 可以将 C#(类似于 Java)、C++ 和 IronPython(Python)加载到单个进程中,并以低成本相互通信。 - Ben Voigt
1
“我曾经认为Thrift可以帮助一个应用程序直接调用另一个函数(而不是通过网络),但是我是否提到过传输成本不为零?并且这也适用于IPC(无需网络)?实际上,我想我说过了。” - JensG
@Jens:是的,你显然这样做了。但是我在我的解释中用了“网络”这个词。 - Ben Voigt
1
@William:好问题。这取决于情况。如果C只充当中继,那么这不会对性能有太大的影响。但是,如果C进行某种处理,导致A-C和B-C之间传递的数据量总和显著小于C与您的数据库之间的流量,并且C检索的数据可以满足A和/或B的多个请求,则这确实可以提高整体性能。 - JensG
显示剩余6条评论

1
您的比较基于错误的假设。这个假设是,跨进程调用(至少)和进程内调用一样快,但事实并非如此。
这是由Peter Deutsch提出的8个网络谬论之一,后来被其他人扩展,不仅适用于网络,还适用于单台机器上的IPC:与您想象的相反,传输成本不为零
根据您有限的信息,我认为您每次IPC往返需要1.5毫秒听起来还不错。

我一直在遭受这个问题的困扰,已经尝试了两天来找出我的错误所在。至少现在我知道,试图使它成为快速的进程调用是徒劳无功的。一个随机样本需要68秒才能完成... - Tony
1
@TonyTannous:如果是68秒,那么很可能不是传输层的问题,除非你正在尝试移动大量的数据。在不了解更多情况的情况下,我建议开始检查服务器端。 - JensG
实际上,我正在发送大量数据。客户端到服务器共有10MB。 将这10MB分成更小的部分是否更好?我将1GB的数据分成10MB的块并发送... - Tony
哇!将 1GB 文件切成 110KB 的块,而不是 10MB,它只用了12分钟就完成了,而不是22分钟!太神奇了。谢谢 :) 我已经点了赞。 - Tony
太棒了!如果你还没有使用它,考虑使用TFramedTransport或者TBufferedTransport。这有助于减少内存分配的数量,从而提高性能。 - JensG
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接