D编程没有垃圾收集器
我今天一直在看D,表面上看起来相当惊人。 我喜欢它如何在语言中直接包含许多更高层次的构造,所以不必使用愚蠢的黑客或简洁的方法。 有一件事真的让我担心,如果GC。 我知道这是一个很大的问题,并且已经阅读了很多关于它的讨论。
我自己从这个问题萌生的简单testing表明GC非常慢。 比直接C ++做同样的事情要慢10倍以上。 (显然,testing并不直接转化为现实世界,但是性能打击是极端的,会减缓真实世界发生的类似行为(快速分配许多小对象)
我正在研究编写一个实时低延迟audio应用程序,GC可能会破坏应用程序的性能,使其几乎无用。 从某种意义上讲,如果它有任何问题,将会毁掉实时audio方面,因为与graphics不同,audio以更高的帧速率(44000+ vs 30-60)运行。 (由于它的低延迟比标准的可以caching大量数据的audio播放器更重要)
禁用GC可将结果提高到C ++代码的20%左右。 这很重要。 我会在最后给出代码进行分析。
我的问题是:
- 用一个标准的智能指针实现代替D的GC是多困难的,这样依赖于GC的库仍然可以被使用。 如果我彻底删除GC,那么我将失去很多烦琐的工作,因为与C ++相比,D已经有了限制库。
- GC.Disable只暂时停止垃圾回收(阻止GC线程运行),并且GC.Enable会在停止的地方回收。 所以我可能会禁用GC在高cpu使用时间运行,以防止延迟问题。
- 有什么办法强制一个模式,不要一直使用GC。 (这是因为我没有在D编程,当我开始写我的眼镜不使用GC我想确保我不会忘记执行自己的清理。
- 是否可以轻松更换D中的GC? (不是我想要的,但有一天玩不同的GC方法可能很有趣…这与我想的类似)
我想要做的是交易记忆的速度。 我不需要GC每隔几秒就运行一次。 事实上,如果我可以正确地实现自己的数据结构的内存pipe理,那么根本就不需要经常运行。 只有当内存稀less时,我才需要运行它。 从我读过的东西看,尽pipe如此,你等待的时间越长,它就越慢。 由于在我的应用程序中通常会有一些时间,我可以不经问题就调用它,这将有助于缓解一些压力(但是再次,可能会有几个小时我将无法调用它)。
我并不担心内存限制。 我宁愿在速度上“浪费”内存(当然,最多也是一个点)。 首要的是延迟问题。
从我读过的内容来看,只要我不使用任何依赖GC的库或语言结构,我至less可以走C / C ++的路线。 问题是,我不知道那些做的。 我已经看到string,新的等提到,但这是否意味着我不能使用string的构build,如果我不启用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. }
事实上,实际上可以将不同types的GC与不同types的数据关联起来,而每个GC都是完全独立的。 这样我可以根据我的types调整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; }
-
D可以使用几乎所有的C库,只需定义所需的function。 D也可以使用C ++库,但D不理解某些C ++结构。 所以… D 可以使用几乎和C ++一样多的库。 他们只是不是本地的D库。
-
从D的图书馆参考。
Core.memory:static nothrow void disable();
禁用执行的自动垃圾回收,以最大限度地减less处理足迹。 在实现认为正确的程序行为所必需的情况下,例如在内存不足的情况下,集合可能会继续发生。 这个函数是可重入的,但是每次调用都必须调用disable来禁用。
static pure nothrow void free(void* p);
取消分配由p引用的内存。 如果p为null,则不会发生任何操作。 如果p引用的内存不是最初由这个垃圾收集器分配的内存,或者如果它指向内存块的内部,将不会采取任何行动。 无论是否设置了FINALIZE属性,块都不会被最终确定。 如果需要完成,请使用delete。
static pure nothrow void* malloc(size_t sz, uint ba = 0);
从垃圾收集器请求一个alignment的pipe理内存块。 这个内存可以随意删除,免费打电话,也可以在收集运行时自动丢弃和清理。 如果分配失败,这个函数将调用onOutOfMemory,这将会抛出一个OutOfMemoryError。
所以是的。 在这里阅读更多: http : //dlang.org/garbage.html
在这里: http : //dlang.org/memory.html
如果你真的需要类,看看这个: http : //dlang.org/memory.html#newdelete删除已被弃用,但我相信你仍然可以免费()。
-
不要使用类,使用结构。 结构是堆栈分配的,类是堆。 除非你需要多态或其他类的支持,否则它们是你正在做的事情的开销。 你可以使用malloc和免费的,如果你想。
-
或多或less地填写这里的函数定义: https : //github.com/D-Programming-Language/druntime/blob/master/src/gcstub/gc.d 。 有一个GC代理系统可以让你自定义GC。 所以不像是devise师不希望你做的事情。
这里的小GC知识:垃圾收集器不能保证为所有未被引用的对象运行析构函数。 此外,垃圾回收器调用非引用对象的析构函数的顺序没有指定。 这意味着当垃圾收集器调用析构函数来获取垃圾收集对象引用的成员的类的对象时,这些引用可能不再有效。 这意味着析构函数不能引用子对象。 此规则不适用于使用DeleteExpression删除的自动对象或对象,因为析构函数没有被垃圾回收器运行,这意味着所有引用都是有效的。
导入std.c.stdlib; 应该有malloc和免费的。
导入core.memory; 这有GC.malloc,GC.free,GC.addroots,/ /外部内存添加到GC …
string需要GC,因为它们是不可变字符的dynamic数组。 (不可变(char)[])dynamic数组需要GC,静态不要。
如果您想要手动pipe理,请继续。
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创build一个包装类? 你所做的只不过是放慢速度,浪费记忆。
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的内联汇编来优化所有的东西。
我build议你阅读这篇文章:http: //3d.benjamin-thaut.de/?p=20在那里你会发现一个标准库的版本,自己的内存pipe理,并完全避免垃圾收集。
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
您也可以只分配所有需要的内存块,然后使用内存池来获取没有GC的块。
顺便说一下,这并不像你提到的那么慢。 而GC.disable()并没有真正禁用它。
我们可以从一个不同的angular度来看问题。 分配许多小对象的performance并不理想,你只是提出这个问题的基本原理,与单独的GC无关。 相反,它是通用(但不是最佳)和高性能(但任务专用)内存pipe理工具之间的平衡问题。 这个想法是:GC的存在并不妨碍你写一个实时应用程序,你只需要使用更具体的工具(比如说, 对象池 )的特殊情况。
由于这还没有被closures,所以最近版本的D有std.container库,它包含一个Array数据结构,与内置数组相比,它的内存效率要高得多。 我无法确认库中的其他数据结构是否也是有效的,但是如果您需要更多地关注内存,而不必诉诸手动创build不需要垃圾回收的数据结构,则可能值得考虑。