2013-03-25 1 views
17

Hash-consing состоит в сохранении в памяти только одной копии данного объекта; то есть, если два объекта семантически равны (одно и то же содержимое), то они должны быть физически равными (одно и то же местоположение в памяти). Этот метод обычно реализуется путем сохранения глобального набора хэшей и создания новых объектов только в том случае, если они не равны объекту в хэш-наборе.Хэш-consing в F # и слабые хэш-таблицы в .net

Дополнительным требованием является то, что объекты в хэш-таблице должны собираться, если на них не ссылаются ни на что иное, кроме хеш-таблицы; в противном случае хеш-таблица должна содержать слабые ссылки.

Проблема, кроме того, осложняется необходимостью иметь постоянное время, таким образом, неглубокие, хэширование и тесты на равенство; таким образом, объекты имеют уникальный идентификатор, который увеличивается при добавлении нового объекта в таблицу.

У меня есть рабочая реализация, которая использует System.Collections.Generic.Dictionary<key, node>, где key является кортежем, дающим мелкую сводку узла (подходит для теста хэширования и равенства по умолчанию) и node является объектом. Единственная проблема заключается в том, что Dictionary сохраняет сильные ссылки на узлы!

Я мог бы использовать Dictionary до WeakReference, но это не освободит ключи, указывающие на оборванные ссылки.

Некоторые защитники используют System.Runtime.CompilerServices.ConditionalWeakTable, но этот класс, похоже, делает обратное: он освобождает значение при сборке ключа, тогда как мне нужно освободить ключ при сборке значения.

Можно попробовать использовать System.Runtime.CompilerServices.ConditionalWeakTable<node, node>, но я должен был бы заказ хэширования и равенства тестов ... и ConditionalWeakTable документирована не использовать виртуальный метод GetHashCode(), вместо того, чтобы с помощью функции хэширования по умолчанию.

Таким образом, мой вопрос: есть ли какой-то эквивалент Dictionary, который будет поддерживать слабые ссылки на значения и освобождать ключи, когда ссылки становятся свисающими?

+0

Вам нужно освободить ключ сразу после сбора значения? Или вы могли бы расслабиться и просто освободить ключ в какой-то более поздний момент? –

+0

Мне не нужно, чтобы их немедленно освободили - просто я не хочу, чтобы они накапливались и бесполезно потребляли много памяти.Я думал о запуске другого потока, чтобы периодически убивать ключи с оборванными ссылками, но это кажется сложным и подверженным ошибкам параллелизма. –

+0

Для чего это стоит, у меня также есть реализация OCaml с использованием хеш-таблицы из модуля 'Weak' и реализация Java usiong' WeakHashMap'. –

ответ

3

Вы правы, что CWT не решает проблему хеш-consing, потому что он задает вопрос - его ключи принимают ссылочное равенство. Однако, может быть, стоит отметить, что CWT не держится за ключи или ценности. Вот небольшой тест:

open System.Collections.Generic 
open System.Runtime.CompilerServices 

let big() = 
    ref (Array.zeroCreate (1024 * 1024) : byte []) 

let test1() = 
    let d = Dictionary(HashIdentity.Reference) 
    for i in 1 .. 10000 do 
     stdout.WriteLine(i) 
     let big = big() 
     d.Add(big, big) 
    d 

let test2() = 
    let d = ConditionalWeakTable() 
    for i in 1 .. 10000 do 
     stdout.WriteLine(i) 
     let big = big() 
     d.Add(big, big) 
    d 

На моей машине, test1 бежит из памяти и test2 успешно. Похоже, что это произойдет только в том случае, если CWT не будет держаться за ключи, а также за ценности.

Для хеш-consing ваша лучшая ставка может быть тем, что Артем предлагает в комментариях. Если это звучит слишком сложно, это также делает много смысла, чтобы просто дать пользователю контроль, скажем:

let f = MyFactory() // a dictionary with weak reference values hidden inside 
f.Create(..) : MyObject // MyObject has no constructors of its own 
f.Cleanup() // explicitly cleans up entries for collected keys 

Тогда вам не нужно вводить многопоточность, изучить, как GC Internals работу, или сделать какой-либо магии. Пользователь библиотеки может решить, где уместно очистить или просто «забыть» фабричный объект, который собирал бы всю таблицу.

+1

Я попытался использовать CWT, но оказалось, что данные, помещенные внутри таблицы, были собраны сразу (потому что значение собирается, как только ключ становится недоступным). Вы пытались восстановить данные из CWT? Невозможно использовать CWT от A до A, потому что CWT * не * использует функцию hashcode из типа данных, но вместо этого обращается к хэш-функции по умолчанию, которая непригодна для хэш-consing (требуется мелкое хеширование с уникальными идентификаторами). Одним из решений было бы скопировать исходный код CWT и адаптировать его. –

+0

@monniaux: да, я согласен, что CWT не подходит для хеширования. Очевидно, выигрывает слабый стол OCaml. Восстановление данных из CWT прекрасно, хотя если вы держитесь за клавиши - это то, для чего он предназначен. Да, напишите здесь, если вы найдете хорошее решение или напишите свой собственный - для хэш-consing. – t0yv0