2

我是 D 的新手。

我使用http://arsdnet.net/web.d/cgi.d.html编写了一个简单的文件服务器

我像这样发送一个文件(大小约为 xxMB):

import std.file;
void SendFile(string request)
{
    auto bytes = read(request);
    cgi_.setResponseContentType("application/zip");
    cgi_.write(bytes);
    bytes = null;
}

它可以很好地发送文件,但似乎没有被垃圾收集。所以在发送一些文件后,它无法分配内存来读取。

我错过了什么?有没有办法手动释放内存?

4

2 回答 2

4

我怀疑问题出在 cgi.d 中看起来很无辜的 makeChunk 程序。我对其进行了更改以避免不必要的分配,并且在我在这里运行的快速测试中表现得更好,所以如果你尝试新版本,你会希望有更好的运气。

在此处获取新版本的 cgi.d:

https://github.com/adamdruppe/misc-stuff-including-D-programming-language-web-stuff

如果这对您有用,最好查看提交以了解修复内容: https ://github.com/adamdruppe/misc-stuff-including-D-programming-language-web-stuff/提交/848566eaf76ae708ccf0109fbb369e2c883f5379

有一个名为 makeChunk 的看似无辜的函数,它通过创建一个 byte[] 并将所需的部分附加到它来工作:长度、数据和终止符。如果您想知道为什么会这样,请在网上搜索 http 1.1 规范并查看“Transfer-Encoding: chunked”标头部分。(如果您在 Embedded_httpd 以外的任何模式下使用 cgi.d,它不会调用此函数 - 对于 cgi.fastcgi 和 scgi,Apache/IIS/nginx/whatever 服务器处理这样的细节,所以 cgi.d 只是通过数据直接出来。但是在embedded_httpd中,它需要注意协议的在线细节。实际上我最常在普通的旧cgi模式下使用它,所以像这样的错误可以从我身边溜走!)

为什么我这样做而不是现在的方式呢?嗯,我不记得了,我不认为我在编写该函数之后才添加接收器委托,因为它适用于小案例,没什么大不了的。

无论如何,发生的事情是 D 中的 ~= 运算符,至少在一个空数组上,通过垃圾收集器分配新内存。如果文件很大——在我的测试中,我只使用了 100 MB 的随机数据——这个引用会占用大量的地址空间。

在 32 位上,地址空间为 4 GB。当然,100 MB 大约是 1/40。D 垃圾收集器是保守的。也就是说,它会扫描所有可用空间并假设,除非它确实知道,否则它看到的任何数字都可能是指针。您在堆栈上的任何随机数据都假定它可能只是一个指针,因此如果它指向那个巨大的数组,gc 不会释放它,以防它实际上仍在使用中。

这对于小型数组(以及 64 位的大型数组,因为随机数实际上指向偶数大数据的几率几乎为零)非常有效,但这里的几率仅为 1/40。因此,如果 gc 扫描的数据中有 100 个或多或少的随机数,它很可能会通过纯粹的随机几率意外地将那个巨大的数组固定到内存中。

这就是为什么这首先是一个问题,以及为什么我的第一条评论之一是尝试“删除字节”,以便将其排除在运行之外。但是,棘手的部分是 cgi.d 无意中制作了一个副本,并且从未告诉任何人......所以你可能非常勤奋,但仍然会泄漏内存,因为同样的随机效应也会发生在这个看似无辜的 makeChunk 函数上。

所以解决方案是避免在那里进行数组追加操作。相反,它现在只是将片段直接写入套接字。

于 2013-06-03T02:11:34.540 回答
2

我正在运行这个:

import std.stdio;
import std.file;

void main()
{
    foreach (i; 0 .. 1_000_000) {
        auto bytes = read("bigfile.mkv");
        write(bytes.length, ' ', i, '\r');
        stdout.flush();
    }
}

打印 737876138 后跟越来越多的数字。循环的速度约为每秒 6.75 次迭代。根据top,所有与内存相关的参数都很快稳定下来(所以我假设每次传递有一个 GC 周期)。RSS 为 1411M,即文件大小的两倍。

修改后的版本要慢得多:

import std.stdio;
import std.file;
import core.memory;

void main()
{
    GC.disable();
    foreach (i; 0 .. 1_000_000) {
        auto bytes = read("Movies/Act of Valor 2012.mkv");
        write(bytes.length, ' ', i, '\r');
        stdout.flush();
    }
}

这工作得慢得多,大概是因为缓存不友好,并且确实由于内存不足而很快死亡。

于 2013-06-02T16:13:50.610 回答