Garbage Collection for Python

Pythonのガベージコレクションについて書かれた論文の一部を訳す。
実際にどういう風なアプローチで実装されてるかが分かる部分だけ。

Garbage Collection for Python

How does this Approach Work?

このアプローチはどう働く?

Conceptually, this approach does the opposite of traditional garbage collection. Instead of trying to find all reachable objects it tries to find unreachable objects. This is much safer because if the algorithm fails we are no worse off than with no garbage collection (except for the time and space wasted).

概念的に、このアプローチは伝統的なガベージコレクションの反対のことを行う。全ての到達可能なオブジェクトを見付ける試みの代わりに到達不可能なオブジェクトを見付けるようと試みる。これはより安全である、なぜならばもしアルゴリズムが失敗しても(時間と空間の無駄を除いて)ガベージコレクション無しより悪くなることはない。

Since we are still using reference counting, the garbage collector only has to find reference cycles. The reference counting will handle freeing other types of garbage. First we observe that reference cycles can only be created by container objects. These are objects which can hold references to other objects. In Python lists, dictionaries, instances, classes, and tuples are all examples of container objects. Integers and strings are not containers. With this observation we realize that non-container objects can be ignored for the purposes of garbage collection. This is a useful optimization because things like integers and strings should be fast and small.

まだ参照カウントを使用しているので、ガベージコレクターは参照サイクルを見付けるだけである。参照カウントは他の型のゴミの開放を扱う。最初に参照サイクルはコンテナオブジェクトによってのみせいせいされることに気付く。これらは他のオブジェクトへの参照を持つオブジェクトである。Pythonではリスト、辞書、インスタンス、クラス、そしてタプルはコンテナオブジェクトの例の全てである。整数と文字列はコンテナではない。この所見から非コンテナオブジェクトはガベージコレクションの目的のために無視できることに気付く。これは有用な最適化である、なぜならば整数と文字列のようなものは速くて小さくあるべきである。

Our idea now is to keep track of all container objects. There are several ways that this can be done but one of the best is using doubly linked lists with the link fields inside the objects structure. This allows objects to be quickly inserted and removed from the set as well as not requiring extra memory allocations. When a container is created it is inserted into this set and when deleted it is removed.

アイデアは今、全てのコンテナオブジェクトのトラックを把握することである。いくつかの方法があるり可能かもしれないが、最高のうちの1つはオブジェクトの構造の中にリンクフィールドを備えた二重のリンクリストを使用することである。これはオブジェクトが余計なメモリ割り当てを必要としないだけでなく集合から速やかに挿入や削除することを許す。

Now that we have access to all the container objects, how to we find reference cycles? First we to add another field to container objects in addition to the two link pointers. We will call this field gc_refs. Here are the steps to find reference cycles:

今全てのコンテナオブジェクトへのアクセスを持つ、どのように参照サイクルをみつけるか?最初にコンテナオブジェクトへ2つのリンクポインタに加えて他のフィールドを追加。このフィールドをgc_refsと呼ぶ。参照サイクルを見付けるためのステップがここにある:

1. For each container object, set gc_refs equal to the object's reference count.
2. For each container object, find which container objects it references and decrement the referenced container's gc_refs field.
3. All container objects that now have a gc_refs field greater than one are referenced from outside the set of container objects. We cannot free these objects so we move them to a different set.
4. Any objects referenced from the objects moved also cannot be freed. We move them and all the objects reachable from them too.
5. Objects left in our original set are referenced only by objects within that set (ie. they are inaccessible from Python and are garbage). We can now go about freeing these objects.

1. 各コンテナオブジェクトについて、オブジェクトの参照カウントと同じになるようにgc_refsを設定。
2. 各コンテナオブジェクトについて、到達可能なコンテナオブジェクトを見付けて参照されたコンテナのgc_refsフィールドをデクリメント。
3. 今1より大きなgc_refsフィールドを持つ全てのコンテナオブジェクトは外部のコンテナオブジェクトの集合から参照されている。それらを異なる集合へ移動するのでこれらのオブジェクトを開放できない。
4. 移動されるオブジェクトから参照されるどんなオブジェクトもまた開放することができない。それらを移動してそれらから到達可能な全てのオブジェクトも。
5. オリジナルの集合に残るオブジェクトはその集合に属するオブジェクトによってだけ参照される。

newLisp -- Implementing ORO memory management

Implementing ORO memory management

OROメモリ管理の実装

The following pseudo code illustrates the algorithm implemented in newLISP in the context of LISP expression evaluation. Only two functions and one data structure are necessary to implement ORO memory management:

次の擬似コードはLISPの式評価のnewLISPで実装されたアルゴリズムを示します:

function pushResultStack(evalationResult)

function popResultStack() ; implies deleting

array resultStack[] ; preallocated stack area

The first two functions pushResultStack and popResultStack push or pop a LISP object handle on or off a stack. pushResultStack increases the value resultStackIndex while popResultStack decreases it. In newLISP every object is contained in a LISP cell structure. The object handle of that structure is simply the memory pointer to the cell structure. The cell itself may contain pointer addresses to other memory objects like string buffers or other LISP cells linked to the original object. Small objects like numbers are stored directly. In this paper function popResultStack() also implies that the popped object gets deleted.

最初の2つの関数pushResultStackとpopResultStackは、LISPオブジェクトハンドルをスタックの上にまたは外すプッシュまたはポップをします。
pushResultStackはresultStackIndexの値をインクリメントします、と同時にpopResultStackはその値をデクリメントします。
newLISPでは各オブジェクトはLISPセル構造を含みます。
その構造のオブジェクトハンドルは単純にセル構造へのメモリポインタです。
セルそれ自身は文字列バッファのようなメモリオブジェクトやオリジナルオブジェクトへ接続された他のLISPセルへのポインタアドレスを含むでしょう。
小さな数のようなオブジェクトは直接格納されます。
この論文では関数popResultStack()でもまたポップされたオブジェクトが削除されることを意味します。

The two resultStack management functions described are called by newLISP's evaluateExpression function:

2つのresultStack管理関数の記述はnewLISPのevaluateExpression関数と呼ばれます:

function evaluateExpression(expr)
{
resultStackIndexSave = resultStackIndex

if typeOf(expr) is BOOLEAN or NUMBER or STRING
return(expr)

if typeOf(expr) is SYMBOL
return(symbolContents(expr))

if typeOf(expr) is QUOTE
return(quoteContents(expr))

if typeOf(expr) is LIST
{
func = evaluateExpression(firstOf(expr))
args = rest(expr)
if typeOf(func) is BUILTIN_FUNCTION
result = evaluateFunc(func, args)
else if typeOf(func) = LAMBDA_FUNCTION
result = evaluateLambda(func, args)
}
}

while (resultStackIndex > resultStackIndexSave)
deleteList(popResultStack())

pushResultStack(result)

return(result)
}

The function evaluateExpression introduces the two variables resultStackIndexSave and resultStackIndex and a few other functions:

関数evaluateExpressionは2つの変数resultStackIndexSaveとresultStackIndexそして他の少しの関数を導入します:

* resultStackIndex is an index pointing to the top element in the resultStack. The deeper the level of evaluation the higher the value of resultStackIndex.

resultStackIndexはresultStackの一番上の要素を指しているインデックスです。
評価のより深いレベルはresultStackIndexの値がより高いです。

* resultStackIndexSave serves as a temporary storage for the value of resultStackIndex upon entry of the evaluateExpression(func, args) function. Before exit the resultStack is popped to the saved level of resultStackIndex. Popping the resultStack implies deleting the memory objects pointed to by entries in the resultStack.

resultStackIndexSaveはevaluateExpression(func, args)関数の登録でresultStackIndexの値のための一時的なストレージとして機能します。

* resultStack[] is a preallocated stack area for saving pointers to LISP cells and indexed by resultStackIndex.

resultStack[]は事前割振りされたLISPセルへのポインタを保存すうるためのスタック領域でresultStackIndexによってインデックスされます。

* symbolContents(expr) and quoteContents(expr) extract contents from symbols or quote-envelope cells.

symoblContents(expr)とquoteContents(expr)はシンボルやquote-envelopeセルから内容を引き出します。

* typeOf(expr) extracts the type of an expression, which is either a BOOLEAN constant like nil or true or a NUMBER or STRING, or is a variable SYMBOL holding some contents, or a QUOTE serving as an envelope to some other LIST expression expr.

typeoOf(expr)は式の型を引き出します、それはnilやtrueのようなBOOLEAN定数やNUMBERやSTRING、いくつかの内容を持っているSYMBOL変数、あるいはいくつか他のLIST式exprを包むような役割のQUOTEです。

* evaluateFunc(func, args) is the application of a built-in function to its arguments. The built-in function is the evaluated first member of a list in expr and the arguments are the rest of the list in expr. The function func is extracted calling evaluateExpression(first(expr)) recursively. For example if the expression (expr is (foo x y)) than foo is a built-in function and x and y are the function arguments or parameters.

evaluateFunc(func, args)は組み込み関数のその引数への適応です。
組み込み関数はexprのリストの評価された最初のメンバーです、そして引数はexprのリストの残りです。
関数funcは再帰的にevaluateExpression(first(expr))を呼び出すことで引き出されます。
例えばもし式が(expr is (foo x y))ならばfooは組み込み関数です、そしてxとyは関数の引数やパラメータです。

* evaluateLambda(func, args) works simlar to evaluateFunc(func, args), applying a user-defined function first(expr) to its arguments in rest(expr). In case of a user-defined function we have two types of arguments in rest(expr), a list of local parameters followed by one or more body expressions evaluated in sequence.

evaluateLambda(func, args)はevaluateFunc(func, args)と似た働きをします、ユーザー定義の関数first(expr)をその引数rest(expr)で適応します。
ユーザー定義の関数の場合、私たちは2つのタイプのrest(expr)の引数を持ちます、ローカルパラメータのリストは1つ以上の順に評価された塊の式に従います。

Both, evaluateFunc(func, args) and evaluateLambda(func, args) will return a newly created LISP cell object, which may be any type of the above mentioned expressions. The result values from these functions will always be newly created LISP cell objects destined to be destroyed on the next higher evaluation level, after the current evaluateExpression(expr) function excution returned.

evaluateFunc(func, args)とevaluateLambda(func, args)は両方、新しい生成されたLISPセルオブジェクトを返すでしょう、それは上で述べた式のどの型でもある可能があります。
これらの関数の結果の値はいつでも現在のevaluateExpression(expr)関数の実行から帰ったあとにより次の高い評価のレベルで削除されることを運命付けられた新しく生成されたLISPセルオブジェクトです。

Both functions will recursively call evaluateExpression(expr) to
evaluate their arguments. As recursion deepens it increases the
recursion level of the function.

両方の関数はそれらの引数を評価するためにevaluateExpression(expr)を再帰的に呼ぶでしょう。
再帰が深まるにつれて関数の再帰レベルが増加します。

Before evaluateExpression(func, args) returns it will pop the resultStack deleting the result values from deeper level of evaluation and returned by one of the two functions, either evaluateFunc or evaluateLambda.

evaluateExpression(func, args)が返る前にそれは評価のより深いレベルからの結果の値を削除してevaluateFuncかevaluateLambdaの二つの関数の一つによって返されたresultStackをポップするでしょう。

Any result expression is destined to be destroyed later but its deletion is delayed at a lower level of evaluation. This permits results to be used or copied by calling functions.

どんな結果の式も後に削除される運命にありますが、その削除は評価の低いレベルで遅延されます。
これは結果が呼び出している関数によって使用またはコピーされることを許します。

The following example shows the evaluation of a small user-defined LISP function sum-of-squares and the creation and deletion of associated memory objects:

次の例は小さいユーザー定義のLISP関数sum-of-squaresとメモリオブジェクトに関連付けられた生成と削除の評価を示します:

(define (sum-of-squares x y)
(+ (* x x) (* y y)))

(sum-of-squares 3 4) => 25

sum-of-squares is a user-define lambda-function calling to built-in functions + and *.

sum-of-squaresは組込み関数+と*を呼び出すユーザー定義lambda-functionです。

The following trace shows the relevant steps when defining the sum-of-squares function and when executing it with the arguments 3 and 4.

次のトレースはsum-of-squares関数を定義する時とそれを引数3と4で実行する時の関連したステップを示します。

> (define (sum-of-squares x y) (+ (* x x) (* y y)))

level 0: evaluateExpression( (define (sum-of-squares x y)
(+ (* x x) (* y y))) )
level 1: evaluateFunc( define <6598> )
level 1: return( (lambda (x y) (+ (* x x) (* y y))) )

→ (lambda (x y) (+ (* x x) (* y y)))

> (sum-of-squares 3 4)
level 0: evaluateExpression( (sum-of-squares 3 4) )
level 1: evaluateLambda( (lambda (x y) (+ (* x x) (* y y))), (3 4) )
level 1: evaluateExpression( (+ (* x x) (* y y)) )
level 2: evaluateFunc( +, ((* x x) (* y y)) )
level 2: evaluateExpression( (* x x) )
level 3: evaluateFunc( *, (x x) )
level 3: pushResultStack( 9 )
level 3: return( 9 )
level 2: evaluateExpression( (* y y) )
level 3: evaluateFunc( *, (y y) )
level 3: pushResultStack( 16 )
level 3: return( 16 )
level 2: popResultStack() ->16
level 2: popResultStack() ->9
level 2: pushResultStack( 25 )
level 2: return( 25 )
level 1: return( 25 )

→ 25

The actual C-language implementation is optimized in some places to avoid pushing the resultStack and avoid calling evaluateExpression(expr). Only the most relevant steps are shown. The function evaluateLambda(func, args) does not need to call evaluateLambda(func, args) to evaluate its arguments 3 and 4 becuase they are constants, but evaluateLambda(func, args) will call evaluateExpression(expr) twice to evaluate the two body expressions (+ (* x x) and (+ (* x x). Lines preceded by the prompt > show the command-line entry.

実際のC言語の実装は一部でresultStackをプッシュすることを避け、evaluateExpression(expr)を呼ぶことを避けることで最適化されます。
最も関連したステップだけが示されます。
関数evaluateLambda(func, args)はそれらは定数なのでそれを引数3と4で評価するためにevaluateLambda(func, args)を呼ぶことを必要としません、しかしevaluateLambda(func, args)は2つの式(+ (* x x))と(+ (* x x))を評価するためにevaluateExpression(expr)を2回呼ぶでしょう。

evaluateLambda(func, args) also saves the environment for the variable symbols x and y, copies parameters into local variables and restores the old environment upon exit. These actions too involve creation and deletion of memory objects. Details are omitted, becuase they are similar to to methods in other dynamic languages.

evaluateLambda(func, args)もまた変数のシンボルxとyのために環境を保存します、パラメータを局所変数へコピーして出口で古い環境を復元します。
これら作用はメモリオブジェクトの生成と削除を必要とします。
詳細は省略されます、それらは他の動的言語での方法に似ているので。


>> Automatic Memory Management in newLISP
>> Traditional automatic memory management (Garbage Collection)
>> One reference only, (ORO) memory management
>> Performance considerations with value-passing
>> Memory and datatypes in newLISP
>> Implementing ORO memory management

newLisp -- Memory and datatypes in newLISP

Memory and datatypes in newLISP

newLISPのメモリとデータ型

The memory objects of newLISP strings are allocated from and freed to the host's OS whenever newLISP recycles the cells from its allocated chunks of cell memory. This means that newLISP handles cell memory more efficiently than string memory. As a result, it is often better to use symbols than strings for efficient text processing. For example, when handling natural language it is more efficient to handle natural language words as individual symbols in a separated name-space, rather than as a single string. The bayes-train function in newLISP uses this method. newLISP can handle millions of symbols without degrading performance.

newLISP文字列のメモリオブジェクトはその割り当てられたセルメモリのチャンクからセルを再利用したときはいつでもホストのOSへ割り当てられホストOSへ開放されます。
これはnewLISPがセルメモリを文字列メモリより効果的に扱うことを意味します。
結果として、効果的なテキスト処理のためにシンボルを使用することはしばしば文字列を使用するよりよいです。
例えば、自然言語を扱うとき分離した名前空間で個々のシンボルとして自然言語を扱うことは単独の文字列より、より効果的です。
newLISPのbayes-train関数はこの方法を使用します。
newLISPはパフォーマンスの低下なしに何百万ものシンボルを扱うことができます。

Programmers coming from other programming languages frequently overlook that symbols in LISP can act as more than just variables or object references. The symbol is a useful data type in itself, which in many cases can replace the string data type.

しばしば他のプログラミング言語から来ているプログラマーはLISPのシンボルがただの変数やオブジェクトの参照より多くのあたら気をすることが出来ることを見落とします。
シンボルはそれ自身役に立つデータ型です、それは多くの場合文字列型に代わることができます。

Integer numbers and double floating-point numbers are stored directly in newLISP's LISP cells and do not need a separate memory allocation cycle.

整数と2倍の浮動小数点数はnewLISPのLISPセルに直接格納されて個々のメモリ割り当てサイクルを必要としません。

For efficiency during matrix operations like matrix multiplication or inversion, newLISP allocates non-cell memory objects for matrices, converts the results to LISP cells, and then frees the matrix memory objects.

行列の掛け算や転置のような行列演算の間の効率のために、newLISPは行列のために非セルメモリオブジェクトを割り当てます、結果をLISPセルに変換、そして行列メモリオブジェクトを開放します。

newLISP allocates an array as a group of LISP cells. The LISP cells are allocated linearly. As a result, array indices have faster random access to the LISP cells. Only a subset of newLISP list functions can be used on arrays. Automatic memory management in newLISP handles arrays in a manner similar to how it handles lists.

newLISPはLISPセルの集まりとして配列を割り当てます。
LISPセルは線形に割り当てられます。
結果として、配列インデックスはLISPセルへより早いランダムアクセスを持ちます。
newLISPのリスト関数のサブセットだけが配列で使用することが出来ます。
newLISPの自動メモリ管理はそれがリストを扱うのに似た方法で配列を扱います。


>> Automatic Memory Management in newLISP
>> Traditional automatic memory management (Garbage Collection)
>> One reference only, (ORO) memory management
>> Performance considerations with value-passing
>> Memory and datatypes in newLISP
>> Implementing ORO memory management