没有垃圾收集器的D编程

时间:2012-11-26 22:53:08

标签: garbage-collection d

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

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

我正在研究编写实时低延迟音频应用程序,GC可能会破坏应用程序的性能,使其几乎无用。从某种意义上说,如果它有任何问题,它将破坏实时音频方面,这更加重要,因为与图形不同,音频以更高的帧速率运行(44000+对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的不同方法可能会很有趣......这与我想的相似)
  5. 我想做的是交易记忆以提高速度。我不需要GC每隔几秒就运行一次。事实上,如果我可以为我的数据结构正确实现自己的内存管理,那么很可能根本不需要经常运行。我可能只需要在内存稀缺时运行它。但是,从我读过的内容来看,你等待它的时间越长,它就越慢。由于在我的应用程序中通常会有时间我可以毫无问题地调用它,这将有助于减轻一些压力(但话说再说一次,可能会有几个小时我无法调用它)。

    我并不担心内存限制。我宁愿“浪费”记忆超速(当然也要高达一点)。首要的是延迟问题。

    从我读过的内容来看,只要我不使用依赖于GC的任何库或语言结构,我至少可以走C / C ++的路线。问题是,我不知道那些做的。我已经看过提到过string,new等,但这是否意味着如果我不启用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;
    }
    

6 个答案:

答案 0 :(得分:19)

  1. D几乎可以使用任何C库,只需定义所需的功能。 D也可以使用C ++库,但D不理解某些C ++构造。所以...... D 可以使用几乎和C ++一样多的库。它们不是原生的D libs。

  2. 来自D的图书馆参考。
    Core.memory:

    static nothrow void disable();
    
         

    禁用执行的自动垃圾收集,以最大限度地减少进程占用空间。在实现认为正确的程序行为所必需的情况下,例如在内存不足的情况下,集合可能继续发生。此函数是可重入的,但每次调用禁用都必须调用enable。

    static pure nothrow void free(void* p);
    
         

    释放p引用的内存。如果p为null,则不执行任何操作。如果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 删除已被弃用,但我相信你仍然可以免费()它。

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

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

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

    import std.c.stdlib;应该有malloc和free。

    import core.memory;这有GC.malloc,GC.free,GC.addroots,//为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中写下时间敏感函数,并从D调用它。 哎呀,如果时间真是太大了,请使用D的内联汇编来优化所有内容。

答案 1 :(得分:9)

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

答案 2 :(得分: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

答案 3 :(得分:4)

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

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

答案 4 :(得分:3)

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

答案 5 :(得分:2)

由于尚未关闭,因此最新版本的D具有std.container库,该库包含一个数组数据结构,与内置数组相比,它在内存方面的效率要高得多。我无法确认库中的其他数据结构是否也有效,但是如果您需要更加注重内存而不必手动创建不需要垃圾回收的数据结构,则可能值得研究。 / p>