如何在Crystal中手动释放某些结构体分配的内存?

6
我有一个基于Kemal的RESTful web服务,返回“非常大”的JSON数据块(大小从10到17M不等),它是由“大”Hash结构的to_json方法生成的。
根据GC警告信息,我的代码“可能导致内存泄漏”,而我的自己的测量结果显示,在应用程序运行期间内存正在“泄漏”。
因此,我认为,手动释放为Hash及其JSON字符串表示分配的内存将是很好的,但我不知道如何做到这一点:我的使用文档不良的GC.free方法进行实验并不成功,也不知道继续研究方向...
请告诉我怎样避免内存泄漏?
您可以在这里查看我非常简单的应用程序的版本(实际上它是在封闭的公司网络段内开发的)https://github.com/DRVTiny/Druid/blob/master/src/druid_mp.cr 导致内存泄漏的代码:
get "/service/:serviceid" do |env|
        if (svcid = env.params.url["serviceid"]) && svcid.is_a?(String) && svcid =~ /^s?\d+$/
          druid.svc_branch_get((svcid[0] == 's' ? svcid[1..-1] : svcid).to_i).to_json
        else
          halt env, status_code: 404, response: %q({"error": "Wrong service identificator"})
        end
  rescue ex
        halt env, status_code: 503, response: {"error": "Unhandled exception #{ex.message}"}.to_json
  end

P.S. 在每个用户请求后,我插入了after_all钩子执行GC.collect。不知道,也许这可以解决我的问题(但我认为这根本不是正确的方法)。

更新:在我将GC.collect添加到after_all Kemal钩子之后,内存泄漏消失了。但全局GC.collect可能太慢了,并且我知道它会阻止所有纤维和socket.accept()。如果我错了,请告诉我。


相关问题请参考:https://github.com/crystal-lang/crystal/issues/3997 - Faustino Aguilar
1个回答

7

是的,每个请求后不应该调用GC.collect

除了改进GC(这将最终实现)外,最简单的方法就是避免无用的字符串分配。根据您的示例代码,您不需要从to_json调用中的结果作为字符串存储在内存中。您可以直接将其序列化到IO流中,例如to_json(env.response)。这样做总体上更快,不会分配额外的内存,并完全避免了释放内存的问题。


1
谢谢,Johannes。我以前从未使用过 to_json 的可选参数,也不记得它了。我最近会应用你的解决方案,并必要地写下评论,说明它的工作原理。使用缓冲 IO 模拟可以提高我的 Web 服务的性能,这太棒了!现在它已经非常健壮,但如果它能以闪电般的速度运行,那就真的很惊人了!我会投票支持你的答案作为解决方案,再次感谢你! - drvtiny
1
没错,它起作用了!我已经移除了GC.collect和after_all hook - 我发现使用to_json(io: IO)直接将序列化数据写入实现IO接口的env.response中,就不会出现内存泄漏问题。 这是一个很棒的答案,谢谢! - drvtiny

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