当Perl不够快时

去年在$work,我们举办了一场Web应用程序“烘焙大赛”比赛,目的是为了找到一种技术堆栈,以尽可能快的速度服务于我们网站的一些重要页面。我们的开发者可以单独或以团队形式参加比赛,并且可以使用他们想要的任何编程语言。

现有的解决方案是基于Perl的Catalyst框架,使用Template::Toolkit,代码已经变得非常臃肿,以至于服务页面需要几百毫秒。问题并不在于技术本身:纯Catalyst应用程序可以在10毫秒内提供响应,问题在于应用程序代码被几个不同的团队共享,随着每个团队添加各种功能和功能,性能下降。

因此,我们的总体目标就是看看如果我们“从头开始”会做什么。烘焙大赛引起了很大的轰动:我们被允许花尽可能多的时间来工作,这非常有趣。我们有Python、Go、Java、Haskell、Lua、Node、Elixir和当然,Perl的参赛作品。

第一轮

第一轮的目标是开发一个Web应用程序,该应用程序可以通过服务特定的模板来响应某些GET请求。模板的大部分是静态的,但其中包含一些动态逻辑。

我的团队在Plack的基础上构建了一个解决方案。我们使用Moo创建了瘦请求和响应类,使用C编写的Router::XS作为路由器,并使用Text::XSlate进行模板。这个解决方案非常出色——它能够每秒服务超过10,000个请求,我们最终名列第二,仅输给了Java参赛作品。

第二轮

在第二轮中,事情变得更加复杂:我们的解决方案需要向其他内部服务发出多个请求,以便形成响应。此外,我们的解决方案还会被评判一个“乐趣”因素:开发者是否喜欢使用这个堆栈?

为了满足“乐趣”因素,我们将我们的代码与另一个基于Kelp的团队的作品合并。这给了我们一个真正的Web框架来开发,而不是我们在第一轮中开发的薄弱类。

向其他服务发出多个请求的要求对我们造成了伤害。关键是要并发地发出请求并计算响应。这是因为一个请求所需的数据来自两个不同的数据存储,可以并发地获取和处理。换句话说,我们需要线程。

Perl可以使用像IO::AsyncCoro这样的模块进行异步编程,但它是单线程的。您可以使用threads编译Perl,它提供了多线程计算。它们是在很久以前由微软开发的,以便在没有fork()的情况下使mod_perl在Windows上运行。Perl的线程通过克隆Perl解释器的内部数据结构,并通过传递线程上下文变量来告诉Perl哪个线程请求什么数据来工作。这些有可预测的缺点:由于克隆的数据,它们需要更多的系统资源,并且由于所有线程上下文检查,每个线程都比单线程Perl运行得更慢。

Perl无法高效地多线程,迫使我们保持单线程状态,这给我们带来了很大的困扰:性能最佳的Java和Go方案之间的吞吐量相差仅为3%,但我们的解决方案却慢了50%。

结论

Perl是一种非常灵活的语言:从终端到脚本编写和应用编程,它在许多领域都表现出色。我们能够开发出快速的应用程序,与几个高性能语言竞争,甚至在某些方面胜过它们。然而,$work最终决定使用Go作为解决方案,因为我们需要一个高度可扩展且性能出色的堆栈。

Perl 6可能很快就会成为一个可行的替代品。最新的6.c 版本包含一个通过调度器实现的混合(M:N)线程模型,该模型在执行高级构造时生效。为了绕过调度器并获得更多控制,它有一个Thread类,每个实例都与一个操作系统线程一一对应。我怀疑它目前运行得太慢,无法竞争,但我将密切关注未来Perl 6的基准测试。


封面图片来自 psdgraphics.com

标签

David Farrell

David是一位专业程序员,他经常推文博客关于代码和编程的艺术。

浏览他们的文章

反馈

这篇文章有什么问题吗?请在GitHub上打开一个问题或拉取请求,帮助我们。