41

我今天一直在看D,从表面上看,它看起来很神奇。我喜欢它直接在语言中包含许多更高级别的构造,因此不必使用愚蠢的技巧或简洁的方法。如果 GC,我真正担心的一件事。我知道这是一个大问题,并且已经阅读了很多关于它的讨论。

我自己从一个问题中得出的简单测试表明 GC 非常慢。做同样事情的速度比直接的 C++ 慢 10 倍以上。(显然,测试并没有直接转换成现实世界,但性能影响是极​​端的,并且会减慢行为相似的现实世界发生(快速分配许多小对象)

我正在考虑编写一个实时低延迟音频应用程序,GC 可能会破坏应用程序的性能,使其几乎无用。从某种意义上说,如果它有任何问题,它将破坏实时音频方面,这更为重要,因为与图形不同,音频以更高的帧速率运行(44000+ vs 30-60)。(由于它的低延迟,它比可以缓冲大量数据的标准音频播放器更重要)

禁用 GC 将结果改进到 C++ 代码的 20% 以内。这很重要。我会在最后给出代码以供分析。

我的问题是:

  1. 用标准的智能指针实现替换 D 的 GC 以使依赖 GC 的库仍然可以使用是多么困难。如果我完全删除 GC,我将失去很多繁重的工作,因为与 C++ 相比,D 已经有限制库。
  2. GC.Disable 是否只是暂时停止垃圾收集(防止 GC 线程运行),而 GC.Enable 会从它停止的地方重新开始。因此,我可能会禁用 GC 在 CPU 使用率较高的情况下运行,以防止出现延迟问题。
  3. 有什么方法可以强制不始终使用 GC 的模式。(这是因为我没有在 D 中编程,当我开始编写不使用 GC 的眼镜时,我想确保我不会忘记实现自己的清理。
  4. 是否可以轻松更换D中的GC?(不是我想要,但有一天玩不同的 GC 方法可能会很有趣......这类似于我想的 1)

我想做的是用内存换取速度。我不需要每隔几秒钟运行一次 GC。事实上,如果我可以为我的数据结构正确实现自己的内存管理,那么它可能根本不需要经常运行。只有当内存变得稀缺时,我才可能需要运行它。不过,从我读过的内容来看,您等待调用它的时间越长,它就会越慢。由于通常在我的应用程序中有时我可以毫无问题地调用它,这将有助于减轻一些压力(但话又说回来,可能有几个小时我将无法调用它)。

我不太担心内存限制。我宁愿“浪费”内存而不是速度(当然,在一定程度上)。首先是延迟问题。

根据我的阅读,我至少可以走 C/C++ 的路线,只要我不使用任何依赖 GC 的库或语言结构。问题是,我不知道那些做的。我已经看到提到的字符串、新字符串等,但这是否意味着如果我不启用 GC,我就不能使用内置字符串?

我在一些错误报告中读到 GC 可能真的有错误,这可以解释它的性能问题吗?

另外,D 使用了更多的内存,实际上,D 在 C++ 程序之前就耗尽了内存。我想在这种情况下大约多出 15% 左右。我想那是给GC的。

我意识到以下代码不能代表您的普通程序,但它说的是当程序实例化很多对象时(例如,在启动时)它们会慢得多(10 倍是一个很大的因素)。GC 可能在启动时“暂停”,那么它不一定是一个问题。

如果我没有专门解除分配本地对象,如果我能以某种方式让编译器自动 GC 一个本地对象,那将是非常好的。这几乎是两全其美。

例如,

{
    Foo f = new Foo();
    ....
    dispose f; // Causes f to be disposed of immediately and treats f outside the GC
               // If left out then f is passed to the GC.
               // I suppose this might actually end up creating two kinds of Foo 
               // behind the scenes. 

    Foo g = new manualGC!Foo();   // Maybe something like this will keep GC's hands off 
                                  // g and allow it to be manually disposed of.
}

事实上,实际上能够将不同类型的 GC 与不同类型的数据相关联,并且每个 GC 都是完全自包含的,这可能会很好。这样我就可以根据我的类型定制 GC 的性能。

代码:

module main;
import std.stdio, std.conv, core.memory;
import core.stdc.time;

class Foo{
    int x;
    this(int _x){x=_x;}
}

void main(string args[]) 
{

    clock_t start, end;
    double cpu_time_used;


    //GC.disable();
    start = clock();

    //int n = to!int(args[1]);
    int n = 10000000;
    Foo[] m = new Foo[n];

    foreach(i; 0..n)
    //for(int i = 0; i<n; i++)
    {
        m[i] = new Foo(i);
    }

    end = clock();
    cpu_time_used = (end - start);
    cpu_time_used = cpu_time_used / 1000.0;
    writeln(cpu_time_used);
    getchar();
}

C++ 代码

#include <cstdlib>
#include <iostream>
#include <time.h>
#include <math.h>
#include <stdio.h>

using namespace std;
class Foo{
public:
    int x;
    Foo(int _x);

};

Foo::Foo(int _x){
    x = _x;
}

int main(int argc, char** argv) {

    int n = 120000000;
    clock_t start, end;
    double cpu_time_used;




    start = clock();

    Foo** gx = new Foo*[n];
    for(int i=0;i<n;i++){
        gx[i] = new Foo(i);
    }


    end = clock();
    cpu_time_used = (end - start);
    cpu_time_used = cpu_time_used / 1000.0;
    cout << cpu_time_used;

    std::cin.get();
    return 0;
}
4

6 回答 6

19
  1. D 可以使用几乎任何 C 库,只需定义所需的函数。D 也可以使用 C++ 库,但 D 不理解某些 C++ 结构。所以... D可以使用几乎与 C++ 一样多的库。它们只是不是本地 D 库。

  2. 来自 D 的图书馆参考。
    核心内存:

    static nothrow void disable();
    

    禁用执行的自动垃圾收集以最小化进程占用空间。在实现认为正确的程序行为所必需的情况下,例如在内存不足的情况下,收集可能会继续发生。此函数是可重入的,但每次调用禁用时必须调用一次启用。

    static pure nothrow void free(void* p);
    

    释放 p 引用的内存。如果 p 为空,则不执行任何操作。如果 p 引用了最初不是由这个垃圾收集器分配的内存,或者它指向内存块的内部,则不会采取任何行动。无论是否设置了 FINALIZE 属性,该块都不会最终确定。如果需要最终确定,请改用 delete。

    static pure nothrow void* malloc(size_t sz, uint ba = 0);
    

    从垃圾收集器请求一个对齐的托管内存块。此内存可以通过调用 free 随意删除,也可以在收集运行期间自动丢弃和清理。如果分配失败,此函数将调用 onOutOfMemory,预计会抛出 OutOfMemoryError。

    所以是的。在这里阅读更多:http: //dlang.org/garbage.html

    在这里:http ://dlang.org/memory.html

    如果你真的需要类,看看这个:http ://dlang.org/memory.html#newdelete delete has been deprecated,但我相信你仍然可以 free() 它。

  3. 不要使用类,使用结构。结构是堆栈分配的,类是堆的。除非您需要多态性或其他类支持的东西,否则它们是您正在做的事情的开销。如果你愿意,你可以使用 malloc 和 free。

  4. 或多或少...在这里填写函数定义:https ://github.com/D-Programming-Language/druntime/blob/master/src/gcstub/gc.d 。设置了一个 GC 代理系统,允许您自定义 GC。所以这并不是设计师不希望你做的事情。

这里很少有 GC 知识:垃圾收集器不能保证为所有未引用的对象运行析构函数。此外,没有指定垃圾收集器为未引用对象调用析构函数的顺序。这意味着,当垃圾收集器为具有引用垃圾收集对象的成员的类的对象调用析构函数时,这些引用可能不再有效。这意味着析构函数不能引用子对象。此规则不适用于自动对象或使用 DeleteExpression 删除的对象,因为垃圾收集器未运行析构函数,这意味着所有引用都是有效的。

导入std.c.stdlib;那应该有malloc和free。

导入core.memory;this has GC.malloc, GC.free, GC.addroots, //add external memory to GC...

字符串需要 GC,因为它们是不可变字符的动态数组。( immutable(char)[] ) 动态数组需要 GC,静态不需要。

如果您想要手动管理,请继续。

import std.c.stdlib;
import core.memory;

char* one = cast(char*) GC.malloc(char.sizeof * 8);.
GC.free(one);//pardon me, I'm not used to manual memory management. 
//I am *asking* you to edit this to fix it, if it needs it.

为什么要为 int 创建一个包装类?您只不过是在放慢速度并浪费内存。

class Foo { int n; this(int _n){ n = _n; } }
writeln(Foo.sizeof);  //it's 8 bytes, btw
writeln(int.sizeof);  //Its *half* the size of Foo; 4 bytes.


Foo[] m;// = new Foo[n]; //8 sec
m.length=n; //7 sec minor optimization. at least on my machine.
foreach(i; 0..n)
    m[i] = new Foo(i);


int[] m;
m.length=n; //nice formatting. and default initialized to 0
//Ooops! forgot this...
foreach(i; 0..n)
    m[i] = i;//.145 sec

如果你真的需要,那么在 C 中编写 Time-sensitive 函数,并从 D 中调用它。哎呀,如果时间真的很重要,请使用 D 的内联汇编来优化一切。

于 2012-11-27T00:41:31.777 回答
9

我建议你阅读这篇文章:http: //3d.benjamin-thaut.de/ ?p= 20 在那里你会发现一个标准库版本,它自己管理内存并完全避免垃圾收集。

于 2012-11-27T00:38:53.967 回答
5

D 的 GC 根本不像 Java 那样复杂。它是开源的,所以任何人都可以尝试改进它。

有一个名为 CDGC 的实验性并发 GC,并且有一个当前的 GSoC 项目来移除全局锁定:http ://www.google-melange.com/gsoc/project/google/gsoc2012/avtuunainen/17001

确保使用 LDC 或 GDC 进行编译以获得更好的优化代码。

XomB 项目也使用自定义运行时,但我认为它是 D 版本 1。 http://wiki.xomb.org/index.php?title=Main_Page

于 2012-11-27T11:23:41.190 回答
4

您也可以只分配您需要的所有内存块,然后使用内存池在没有 GC 的情况下获取块。

顺便说一句,它没有你提到的那么慢。并且 GC.disable() 并没有真正禁用它。

于 2012-12-10T11:06:03.590 回答
3

我们可能会从不同的角度来看待这个问题。您作为问题的基本原理提到的分配许多小对象的次优性能与 GC 无关。相反,这是通用(但次优)和高性能(但任务专用)内存管理工具之间的平衡问题。这个想法是:GC 的存在不会阻止您编写实时应用程序,您只需要针对特殊情况使用更具体的工具(例如,对象池)。

于 2012-12-05T21:44:09.093 回答
2

由于这还没有关闭,最近版本的 D 具有 std.container 库,其中包含一个 Array 数据结构,该结构在内存方面比内置数组更有效。我无法确认库中的其他数据结构也很有效,但如果您需要更多地关注内存而不必求助于手动创建不需要垃圾收集的数据结构,则可能值得研究。

于 2013-03-07T16:19:34.487 回答