问答摘要
特定类型的对象,比如说
type Foo
a::A
b::B
end
可以以两种方式存储:
内联(又名按值):在这种情况下,语句"变量foo::Foo
存储在x
"实际上意味着我们在位置foo.a::A
有一个变量x
,在位置foo.b::B
有一个变量x + sizeof(A)
(从技术上讲,这些地址可能有点复杂,但是与我们的目的无关)。
引用(又名参考):" foo::Foo
存储在x
"表示位置x
包含指针fooptr::Ptr{Foo}
,以便位置foo.a::A
和fooptr
位置foo.b::B
处有变量fooptr + sizeof(A)
。
与其他语言(我看着你,C/C++
)不同,Julia自己决定是否存储内联或引用的变量,并且它是根据类型的属性来实现的:
此规则至少有两个原因:
StefanKarpinski的回答:垃圾收集器需要能够找到堆栈上堆分配对象的所有指针。目前,Julia通过将所有这些指针存储在单独的"阴影堆栈中来确保这一点,但如果我们允许包含指针的复合类型放置在堆栈上,那么这样的整齐分离将不再可能。相反,编译器需要在其他变量中寻找引起技术困难的指针。
yuyichao的回答:Julia要求内联/引用决策是基于每个类型而不是每个对象,这意味着假设类型
immutable A
a::A
end
如果我们坚持内联它,那么必须是无限大的。所以我们要么禁止这样的递归不可变类型,要么我们最多允许内联非递归不可变类型。
原始问题
我对朱莉娅的记忆管理的理解是:
任何人都可以向我解释一下,他对垃圾收集的工作原理只有非常肤浅的知识吗?
答案 0 :(得分:6)
引用其他对象的对象的堆栈分配问题是知道在垃圾回收期间需要跟踪它们。执行此操作的最简单方法是Julia所做的事情:堆分配对象并使用“影子堆栈”将它们“root”,并将其与实际堆栈同步推送和弹出。这会引入相当大的开销并迫使这些对象进行堆分配。
避免影子堆栈和堆分配开销的更复杂方法是堆栈分配这些对象,然后扫描执行垃圾收集的堆栈,并跟踪堆栈中对象的引用到堆上的对象。但是,这需要知道堆栈中的哪些对象是指向堆上对象的指针 - 通常,不保证非堆分配的对象在寄存器或堆栈中保持完整或连续。执行此操作的一种方法称为“保守堆栈扫描”,其需要在gc期间假设堆栈上看起来像它的任何值实际上是指向堆上的对象的指针。这种方法已成功用于Safari的JavaScript引擎等应用程序,但它并非没有挑战。我们已经考虑在Julia中使用保守的堆栈扫描,并且已经开始这样做,但是努力从未完成。
<强>参考文献:强>
答案 1 :(得分:5)
有很多问题/概念经常混合在一起。
mutable或non-pointerfree immutable并不一定意味着堆分配,我们已经有优化传递以避免一些优化,并且正在努力进一步改进它们。
对象布局ABI是用户可见的行为,而不是优化传递可以轻易更改的东西(除非它可以证明它想要做的本地优化不会逃脱)。当前的ABI是只有isbits immutable将被内联存储(当用作局部变量时,“stack allocated”)。提升内联对象的指针自由度的要求是一个基本限制,即处理递归类型的必要性。不可能将所有类型都存储在内联存储的参考圆中,如果我们想让它们中的一些内联,则必须在某个地方打破循环。我相信我们确实有一个一致且可预测的模型来做到这一点,尽管这是否是可取的是另一个问题。
这与性能有些相关但并非总是如此。存储内联意味着更多的副本,因此如果我们进行切换,很难确保没有回归。
编辑:我还应该提到无指针是无循环的充分条件,更容易计算,这也是我们目前使用它来打破内联周期的部分原因。
GC支持。这基本上是最容易的部分。使GC识别堆栈上的指针非常容易。如果我们决定更改对象布局ABI,则需要完成。
编辑:我应该补充说“GC支持”是必需的,因为我们目前只支持对象引用的有限/简单堆栈布局(即指针数组)。这是需要改进的。